MCP Ethers Wallet
import { ethers } from 'ethers';
import fs from 'fs';
import path from 'path';
async function mineBlock(provider: ethers.Provider): Promise<void> {
// Cast to JsonRpcProvider to access send method
const jsonRpcProvider = provider as ethers.JsonRpcProvider;
await jsonRpcProvider.send('evm_mine', []);
}
async function getNonceAfterMining(signer: ethers.Signer, provider: ethers.Provider): Promise<number> {
// Mine a block to ensure all pending transactions are processed
await mineBlock(provider);
// Now get the nonce - it should be accurate since we've mined all pending transactions
return provider.getTransactionCount(await signer.getAddress(), 'latest');
}
export interface TestTokenInterface {
name(): Promise<string>;
symbol(): Promise<string>;
decimals(): Promise<number>;
totalSupply(): Promise<bigint>;
balanceOf(account: string): Promise<bigint>;
transfer(to: string, value: bigint): Promise<ethers.ContractTransactionResponse>;
approve(spender: string, value: bigint): Promise<ethers.ContractTransactionResponse>;
transferFrom(from: string, to: string, value: bigint): Promise<ethers.ContractTransactionResponse>;
mint(to: string, value: bigint): Promise<ethers.ContractTransactionResponse>;
connect(signer: ethers.Signer): TestToken;
getAddress(): Promise<string>;
}
export type TestToken = ethers.Contract & TestTokenInterface;
function getContractArtifact() {
try {
const artifactPath = path.join(process.cwd(), 'artifacts/contracts/TestToken.sol/TestToken.json');
return JSON.parse(fs.readFileSync(artifactPath, 'utf8'));
} catch (error) {
console.error('Failed to read contract artifact:', error);
throw new Error('Contract artifact not found. Make sure the contract is compiled.');
}
}
function hasMethod(obj: any, method: string): boolean {
return typeof obj[method] === 'function';
}
function isTestToken(contract: ethers.Contract): contract is TestToken {
const requiredMethods = [
'name',
'symbol',
'decimals',
'totalSupply',
'balanceOf',
'transfer',
'approve',
'transferFrom',
'mint',
'connect',
'getAddress'
];
return requiredMethods.every(method => hasMethod(contract, method));
}
async function waitForTransaction(provider: ethers.Provider, hash: string, maxAttempts = 30): Promise<void> {
for (let i = 0; i < maxAttempts; i++) {
try {
const receipt = await provider.getTransactionReceipt(hash);
if (receipt) return;
} catch (error) {
// Ignore errors and keep trying
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
throw new Error(`Transaction ${hash} was not mined within ${maxAttempts} seconds`);
}
export async function deployTestToken(
provider: ethers.Provider,
signer: ethers.Signer
): Promise<TestToken> {
const artifact = getContractArtifact();
try {
const nonce = await getNonceAfterMining(signer, provider);
// Create and deploy contract with our nonce
const factory = new ethers.ContractFactory(artifact.abi, artifact.bytecode, signer);
const contract = await factory.deploy({ nonce });
await contract.waitForDeployment();
const testToken = contract as unknown as TestToken;
if (!isTestToken(testToken)) {
throw new Error('Deployed contract does not implement TestToken interface');
}
return testToken;
} catch (error) {
console.error('Failed to deploy contract:', error);
throw error;
}
}
export async function getTestTokenAt(
address: string,
provider: ethers.Provider,
signer?: ethers.Signer
): Promise<TestToken> {
try {
const artifact = getContractArtifact();
const contract = new ethers.Contract(
address,
artifact.abi,
signer || provider
);
// Verify the contract exists and has the expected interface
const code = await provider.getCode(address);
if (code === '0x') {
throw new Error('No contract found at the specified address');
}
const testToken = contract as unknown as TestToken;
if (!isTestToken(testToken)) {
throw new Error('Contract at address does not implement TestToken interface');
}
return testToken;
} catch (error) {
console.error('Failed to get contract at address:', address, error);
throw error;
}
}