Skip to main content
Glama

transfer_erc20

Destructive

Send ERC20 tokens to a recipient address on EVM networks using a configured wallet. Specify token contract, recipient, amount, and network.

Instructions

Transfer ERC20 tokens to an address. Uses the configured wallet.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
tokenAddressYesThe ERC20 token contract address
toYesRecipient address or ENS name
amountYesAmount to send (in token units, accounting for decimals)
networkNoNetwork name or chain ID. Defaults to Ethereum mainnet.

Implementation Reference

  • MCP tool registration for 'transfer_erc20', including Zod input schema, description, and async handler function that formats private key, calls services.transferERC20, and formats success/error response.
    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 implementation of transferERC20 function: resolves ENS names, fetches token decimals and symbol, parses amount, creates wallet client, sends transfer transaction via viem, returns tx hash and token details.
    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
        }
      };
    }
  • ERC20 ABI constants used by transferERC20 for contract interactions (transfer, approve, decimals, symbol).
    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;
  • Top-level registration call to registerEVMTools(server), which includes the transfer_erc20 tool.
    registerEVMTools(server);
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations provide key behavioral hints (destructiveHint: true, idempotentHint: false, readOnlyHint: false), covering safety and idempotency. The description adds minimal context ('Uses the configured wallet'), which hints at authentication needs but doesn't elaborate on rate limits, error handling, or transaction specifics. It doesn't contradict annotations, so a baseline score is appropriate given the annotation coverage.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two concise sentences that are front-loaded with the core purpose and essential context. Every word earns its place, with no redundancy or unnecessary elaboration, making it highly efficient and well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity (destructive transfer operation), rich annotations, and 100% schema coverage, the description is adequate but minimal. It lacks details on output (no schema provided), error cases, or network defaults beyond schema hints. For a high-stakes tool, more context on behavior or results would enhance completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, with clear parameter descriptions in the input schema (e.g., tokenAddress as contract address, amount in token units). The description adds no additional parameter semantics beyond the schema, so it meets the baseline for high coverage without compensating value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('Transfer ERC20 tokens') and resource ('to an address'), distinguishing it from sibling tools like transfer_native (for native tokens) and approve_token_spending (for approvals). It precisely identifies the tool's function without being vague or tautological.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage context by specifying 'Uses the configured wallet,' which suggests it's for sending tokens from a pre-configured source. However, it doesn't explicitly state when to use this vs. alternatives like transfer_native or write_contract, nor does it mention prerequisites (e.g., wallet setup, token approvals). The guidance is clear but lacks sibling differentiation and exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/chulanpro5/evm-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server