Skip to main content
Glama

cross-chain-asset-transfer

Transfer tokens between blockchain networks using Hyperlane's cross-chain infrastructure. Move assets like USDC from Ethereum to Polygon or execute multi-chain transfers in a single operation.

Instructions

Transfers tokens/assets between multiple blockchain networks using Hyperlane's cross-chain infrastructure.

FUNCTIONALITY: • Moves tokens from one blockchain to another (e.g., USDC from Ethereum to Polygon) • Supports sequential transfers across multiple chains in a single operation • Handles various token types including native tokens, ERC20 tokens, and synthetic tokens

PREREQUISITES: • A warp route must exist for the specified token symbol and chain combination • If no warp route exists, deploy one first using the deploy-warp-route tool • Sufficient token balance on the origin chain • Sufficient gas tokens on all involved chains for transaction fees

PARAMETERS: • symbol: The token identifier (e.g., "USDC", "ETH", "WBTC") • chains: Array of blockchain names in transfer order (e.g., ["ethereum", "polygon", "arbitrum"]) • amount: Token amount in wei or smallest token units (e.g., "1000000" for 1 USDC with 6 decimals) • recipient: Destination wallet address (defaults to sender if not specified)

OUTPUT: • Returns transaction hashes and message IDs for each cross-chain transfer • Each transfer between adjacent chains generates one transaction • Use message IDs to track delivery status across chains

EXAMPLE USE CASES: • Bridge USDC from Ethereum to Polygon • Multi-hop transfer: ETH from Ethereum → Arbitrum → Base • Cross-chain token arbitrage or yield farming

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
symbolYesToken symbol to transfer
chainsYesChains to transfer asset between in order of transfer
amountYesAmount to transfer (in wei or token units)
recipientNoRecipient address0x1Be31A94361a391bBaFB2a4CCd704F57dc04d4bb

Implementation Reference

  • src/index.ts:271-450 (registration)
    Registration of the 'cross-chain-asset-transfer' MCP tool, including description, Zod input schema, and wrapper handler that validates warp routes, sets up MultiProvider, finds WarpCoreConfig, and delegates to assetTransfer helper.
    server.tool( 'cross-chain-asset-transfer', "Transfers tokens/assets between multiple blockchain networks using Hyperlane's cross-chain infrastructure.\n\n" + 'FUNCTIONALITY:\n' + '• Moves tokens from one blockchain to another (e.g., USDC from Ethereum to Polygon)\n' + '• Supports sequential transfers across multiple chains in a single operation\n' + '• Handles various token types including native tokens, ERC20 tokens, and synthetic tokens\n\n' + 'PREREQUISITES:\n' + '• A warp route must exist for the specified token symbol and chain combination\n' + '• If no warp route exists, deploy one first using the `deploy-warp-route` tool\n' + '• Sufficient token balance on the origin chain\n' + '• Sufficient gas tokens on all involved chains for transaction fees\n\n' + 'PARAMETERS:\n' + '• symbol: The token identifier (e.g., "USDC", "ETH", "WBTC")\n' + '• chains: Array of blockchain names in transfer order (e.g., ["ethereum", "polygon", "arbitrum"])\n' + '• amount: Token amount in wei or smallest token units (e.g., "1000000" for 1 USDC with 6 decimals)\n' + '• recipient: Destination wallet address (defaults to sender if not specified)\n\n' + 'OUTPUT:\n' + '• Returns transaction hashes and message IDs for each cross-chain transfer\n' + '• Each transfer between adjacent chains generates one transaction\n' + '• Use message IDs to track delivery status across chains\n\n' + 'EXAMPLE USE CASES:\n' + '• Bridge USDC from Ethereum to Polygon\n' + '• Multi-hop transfer: ETH from Ethereum → Arbitrum → Base\n' + '• Cross-chain token arbitrage or yield farming', { symbol: z.string().describe('Token symbol to transfer'), chains: z .array(z.string()) .describe('Chains to transfer asset between in order of transfer'), amount: z.string().describe('Amount to transfer (in wei or token units)'), recipient: z .string() .length(42) .regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid EVM address') .optional() .default(signer.address) .describe('Recipient address'), }, async ({ symbol, chains, amount, recipient }) => { server.server.sendLoggingMessage({ level: 'info', data: `Starting cross-chain asset transfer... Parameters: symbol=${symbol}, chains=${chains.join( ', ' )}, amount=${amount}, recipient=${recipient}`, }); // Fetch warp route config from registry const warpRoutes = await registry.getWarpRoutesBySymbolAndChains( symbol, chains ); if (!warpRoutes || warpRoutes.length === 0) { return { content: [ { type: 'text', text: `No warp route config found for symbol "${symbol}" and chains [${chains.join( ', ' )}]. Please deploy a warp route first using the 'deploy-warp-route' tool.`, }, ], }; } const chainMetadata: ChainMap<ChainMetadata> = Object.fromEntries( await Promise.all( chains.map(async (chain) => [ chain, (await registry.getChainMetadata(chain))!, ]) ) ); const multiProvider = new MultiProvider(chainMetadata, { signers: Object.fromEntries(chains.map((chain) => [chain, signer])), providers: Object.fromEntries( await Promise.all( chains.map(async (chain) => [ chain, new ethers.providers.JsonRpcProvider( chainMetadata[chain].rpcUrls[0].http ), ]) ) ), }); server.server.sendLoggingMessage({ level: 'info', data: `MultiProvider initialized with chains: ${JSON.stringify( multiProvider, null, 2 )}`, }); let warpCoreConfig: WarpCoreConfig | null = null; for (const route of warpRoutes) { const warpCore = WarpCore.FromConfig( MultiProtocolProvider.fromMultiProvider(multiProvider), route ); const tokensForRoute = warpCore.getTokensForRoute(chains[0], chains[1]); if (tokensForRoute.length > 0) { warpCoreConfig = route; break; } } if (!warpCoreConfig) { return { content: [ { type: 'text', text: `No valid warp core config found for symbol "${symbol}" and chains [${chains.join( ', ' )}]. Please deploy a warp route first using the 'deploy-warp-route' tool.`, }, ], }; } else { server.server.sendLoggingMessage({ level: 'info', data: `Found warp core config: ${JSON.stringify( warpCoreConfig, null, 2 )}`, }); } server.server.sendLoggingMessage({ level: 'info', data: 'Initiating asset transfer...', }); const deliveryResult = await assetTransfer({ warpCoreConfig, chains, amount, recipient, multiProvider, }); if (!deliveryResult || deliveryResult.length !== chains.length - 1) { return { content: [ { type: 'text', text: `Error in asset transfer. No delivery result couldn't be generated`, }, ], }; } server.server.sendLoggingMessage({ level: 'info', data: 'Asset transfer completed successfully', }); return { content: [ { mimeType: 'application/json', type: 'text', text: JSON.stringify( deliveryResult.map(([dispatchTx, message]) => ({ transactionHash: dispatchTx.transactionHash, messageId: message.id, })), null, 2 ), }, ], }; } );
  • Zod schema for input parameters: symbol, chains array, amount, optional recipient address.
    { symbol: z.string().describe('Token symbol to transfer'), chains: z .array(z.string()) .describe('Chains to transfer asset between in order of transfer'), amount: z.string().describe('Amount to transfer (in wei or token units)'), recipient: z .string() .length(42) .regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid EVM address') .optional() .default(signer.address) .describe('Recipient address'),
  • MCP tool handler function: logs params, fetches warp routes, sets up providers and metadata, selects warpCoreConfig, calls assetTransfer helper, processes and returns results as JSON.
    async ({ symbol, chains, amount, recipient }) => { server.server.sendLoggingMessage({ level: 'info', data: `Starting cross-chain asset transfer... Parameters: symbol=${symbol}, chains=${chains.join( ', ' )}, amount=${amount}, recipient=${recipient}`, }); // Fetch warp route config from registry const warpRoutes = await registry.getWarpRoutesBySymbolAndChains( symbol, chains ); if (!warpRoutes || warpRoutes.length === 0) { return { content: [ { type: 'text', text: `No warp route config found for symbol "${symbol}" and chains [${chains.join( ', ' )}]. Please deploy a warp route first using the 'deploy-warp-route' tool.`, }, ], }; } const chainMetadata: ChainMap<ChainMetadata> = Object.fromEntries( await Promise.all( chains.map(async (chain) => [ chain, (await registry.getChainMetadata(chain))!, ]) ) ); const multiProvider = new MultiProvider(chainMetadata, { signers: Object.fromEntries(chains.map((chain) => [chain, signer])), providers: Object.fromEntries( await Promise.all( chains.map(async (chain) => [ chain, new ethers.providers.JsonRpcProvider( chainMetadata[chain].rpcUrls[0].http ), ]) ) ), }); server.server.sendLoggingMessage({ level: 'info', data: `MultiProvider initialized with chains: ${JSON.stringify( multiProvider, null, 2 )}`, }); let warpCoreConfig: WarpCoreConfig | null = null; for (const route of warpRoutes) { const warpCore = WarpCore.FromConfig( MultiProtocolProvider.fromMultiProvider(multiProvider), route ); const tokensForRoute = warpCore.getTokensForRoute(chains[0], chains[1]); if (tokensForRoute.length > 0) { warpCoreConfig = route; break; } } if (!warpCoreConfig) { return { content: [ { type: 'text', text: `No valid warp core config found for symbol "${symbol}" and chains [${chains.join( ', ' )}]. Please deploy a warp route first using the 'deploy-warp-route' tool.`, }, ], }; } else { server.server.sendLoggingMessage({ level: 'info', data: `Found warp core config: ${JSON.stringify( warpCoreConfig, null, 2 )}`, }); } server.server.sendLoggingMessage({ level: 'info', data: 'Initiating asset transfer...', }); const deliveryResult = await assetTransfer({ warpCoreConfig, chains, amount, recipient, multiProvider, }); if (!deliveryResult || deliveryResult.length !== chains.length - 1) { return { content: [ { type: 'text', text: `Error in asset transfer. No delivery result couldn't be generated`, }, ], }; } server.server.sendLoggingMessage({ level: 'info', data: 'Asset transfer completed successfully', }); return { content: [ { mimeType: 'application/json', type: 'text', text: JSON.stringify( deliveryResult.map(([dispatchTx, message]) => ({ transactionHash: dispatchTx.transactionHash, messageId: message.id, })), null, 2 ), }, ], }; }
  • Core helper function that performs sequential cross-chain asset transfers by looping over chain pairs and executing delivery for each hop using executeDelivery.
    export async function assetTransfer({ warpCoreConfig, chains, amount, recipient, multiProvider, }: { warpCoreConfig: WarpCoreConfig; chains: ChainName[]; amount: string; recipient?: string; multiProvider: MultiProvider; }): Promise<[ContractReceipt, DispatchedMessage][]> { const results: [ContractReceipt, DispatchedMessage][] = []; for (let i = 0; i < chains.length; i++) { const origin = chains[i]; const destination = chains[i + 1]; if (destination) { const deliveryResult = await timeout( executeDelivery({ origin, destination, warpCoreConfig, amount, recipient, multiProvider, }), 120_000, 'Timed out waiting for messages to be delivered' ); if (deliveryResult) { const [dispatchTx, message] = deliveryResult; results.push([dispatchTx, message]); } else { break; } } } return results; }
  • Helper function that executes a single hop transfer: initializes WarpCore, selects token, validates, prepares and sends transfer transactions, extracts dispatch receipt and dispatched message.
    async function executeDelivery({ origin, destination, warpCoreConfig, amount, recipient, multiProvider, }: { origin: ChainName; destination: ChainName; warpCoreConfig: WarpCoreConfig; amount: string; recipient?: string; multiProvider: MultiProvider; }): Promise<[ContractReceipt, DispatchedMessage]> { const signer = multiProvider.getSigner(origin); const recipientSigner = multiProvider.getSigner(destination); const recipientAddress = await recipientSigner.getAddress(); const signerAddress = await signer.getAddress(); recipient ||= recipientAddress; const provider = multiProvider.getProvider(origin); const connectedSigner = signer.connect(provider); const warpCore = WarpCore.FromConfig( MultiProtocolProvider.fromMultiProvider(multiProvider), warpCoreConfig ); let token: Token; const tokensForRoute = warpCore.getTokensForRoute(origin, destination); if (tokensForRoute.length === 0) { // console.error(`No Warp Routes found from ${origin} to ${destination}`); throw new Error( `Error finding warp route.\n tokensForRoute: ${JSON.stringify( tokensForRoute )}` ); } else if (tokensForRoute.length === 1) { token = tokensForRoute[0]; } else { // console.info(`Please select a token from the Warp config`); // const routerAddress = await runTokenSelectionStep(tokensForRoute); // token = warpCore.findToken(origin, routerAddress)!; throw new Error('Multiple tokens found for route'); } const errors = await warpCore.validateTransfer({ originTokenAmount: token.amount(amount), destination, recipient, sender: signerAddress, }); if (errors) { console.error('Error validating transfer: ', JSON.stringify(errors)); } // TODO: override hook address for self-relay const transferTxs = await warpCore.getTransferRemoteTxs({ originTokenAmount: new TokenAmount(amount, token), destination, sender: signerAddress, recipient, }); const txReceipts: ContractReceipt[] = []; for (const tx of transferTxs) { if (tx.type === ProviderType.EthersV5) { const txResponse = await connectedSigner.sendTransaction(tx.transaction); const txReceipt = await multiProvider.handleTx(origin, txResponse); txReceipts.push(txReceipt); } } const dispatchTx = txReceipts[txReceipts.length - 1]; const messageIndex: number = 0; const message: DispatchedMessage = HyperlaneCore.getDispatchedMessages(dispatchTx)[messageIndex]; const parsed = parseWarpRouteMessage(message.parsed.body); return [dispatchTx, message]; }

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/Suryansh-23/hyperlane-mcp'

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