transfer_erc20
Transfer ERC20 tokens between addresses on EVM-compatible networks using the sender's private key, token contract address, and recipient details. Supports Ethereum, Optimism, Arbitrum, and more.
Instructions
Transfer ERC20 tokens to another address
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| amount | Yes | The amount of tokens to send (in token units, e.g., '10' for 10 tokens) | |
| network | No | Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet. | |
| privateKey | Yes | Private key of the sending account (this is used for signing and is never stored) | |
| toAddress | Yes | The recipient address | |
| tokenAddress | Yes | The address of the ERC20 token contract |
Implementation Reference
- src/core/tools.ts:673-724 (handler)MCP tool handler function that executes the transfer_erc20 tool. Formats private key, calls the core transferERC20 service function, and formats the response.async ({ privateKey, tokenAddress, toAddress, amount, network = 'ethereum' }) => { try { // Get the formattedKey with 0x prefix const formattedKey = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`); const result = await services.transferERC20( tokenAddress as Address, toAddress as Address, amount, formattedKey, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, txHash: result.txHash, network, tokenAddress, recipient: toAddress, amount: result.amount.formatted, symbol: result.token.symbol }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error transferring ERC20 tokens: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; }
- src/core/tools.ts:652-672 (schema)Zod input schema defining parameters for the transfer_erc20 tool: privateKey, tokenAddress, toAddress, amount, network.privateKey: z .string() .describe( 'Private key of the sending account (this is used for signing and is never stored)' ), tokenAddress: z .string() .describe('The address of the ERC20 token contract'), toAddress: z.string().describe('The recipient address'), amount: z .string() .describe( "The amount of tokens to send (in token units, e.g., '10' for 10 tokens)" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet." ) },
- src/core/tools.ts:648-726 (registration)Registration of the 'transfer_erc20' tool on the MCP server using server.tool(), including schema and handler.server.tool( 'transfer_erc20', 'Transfer ERC20 tokens to another address', { privateKey: z .string() .describe( 'Private key of the sending account (this is used for signing and is never stored)' ), tokenAddress: z .string() .describe('The address of the ERC20 token contract'), toAddress: z.string().describe('The recipient address'), amount: z .string() .describe( "The amount of tokens to send (in token units, e.g., '10' for 10 tokens)" ), network: z .string() .optional() .describe( "Network name (e.g., 'ethereum', 'optimism', 'arbitrum', 'base', etc.) or chain ID. Supports all EVM-compatible networks. Defaults to Ethereum mainnet." ) }, async ({ privateKey, tokenAddress, toAddress, amount, network = 'ethereum' }) => { try { // Get the formattedKey with 0x prefix const formattedKey = privateKey.startsWith('0x') ? (privateKey as `0x${string}`) : (`0x${privateKey}` as `0x${string}`); const result = await services.transferERC20( tokenAddress as Address, toAddress as Address, amount, formattedKey, network ); return { content: [ { type: 'text', text: JSON.stringify( { success: true, txHash: result.txHash, network, tokenAddress, recipient: toAddress, amount: result.amount.formatted, symbol: result.token.symbol }, null, 2 ) } ] }; } catch (error) { return { content: [ { type: 'text', text: `Error transferring ERC20 tokens: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } } );
- Core helper function transferERC20 that performs ENS resolution, fetches token metadata, parses amount, and executes the ERC20 transfer using viem's writeContract.export async function transferERC20( tokenAddressOrEns: string, toAddressOrEns: string, amount: string, privateKey: string | `0x${string}`, network: string = 'ethereum' ): Promise<{ txHash: Hash; amount: { raw: bigint; formatted: string; }; token: { symbol: string; decimals: number; }; }> { // Resolve ENS names to addresses if needed const tokenAddress = (await resolveAddress( tokenAddressOrEns, network )) as Address; const toAddress = (await resolveAddress(toAddressOrEns, network)) as Address; // Ensure the private key has 0x prefix const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x') ? (`0x${privateKey}` as `0x${string}`) : (privateKey as `0x${string}`); // Get token details const publicClient = getPublicClient(network); const contract = getContract({ address: tokenAddress, abi: erc20TransferAbi, client: publicClient }); // Get token decimals and symbol const decimals = await contract.read.decimals(); const symbol = await contract.read.symbol(); // Parse the amount with the correct number of decimals const rawAmount = parseUnits(amount, decimals); // Create wallet client for sending the transaction const walletClient = getWalletClient(formattedKey, network); // Send the transaction const hash = await walletClient.writeContract({ address: tokenAddress, abi: erc20TransferAbi, functionName: 'transfer', args: [toAddress, rawAmount], account: walletClient.account!, chain: walletClient.chain }); return { txHash: hash, amount: { raw: rawAmount, formatted: amount }, token: { symbol, decimals } }; }
- src/core/services/transfer.ts:17-52 (helper)ERC20 ABI fragment used for transfer, approve, decimals, and symbol functions in the transferERC20 implementation.const erc20TransferAbi = [ { inputs: [ { type: 'address', name: 'to' }, { type: 'uint256', name: 'amount' } ], name: 'transfer', outputs: [{ type: 'bool' }], stateMutability: 'nonpayable', type: 'function' }, { inputs: [ { type: 'address', name: 'spender' }, { type: 'uint256', name: 'amount' } ], name: 'approve', outputs: [{ type: 'bool' }], stateMutability: 'nonpayable', type: 'function' }, { inputs: [], name: 'decimals', outputs: [{ type: 'uint8' }], stateMutability: 'view', type: 'function' }, { inputs: [], name: 'symbol', outputs: [{ type: 'string' }], stateMutability: 'view', type: 'function' } ] as const;