import { z } from "zod"
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import type { ChainlinkApiClient } from "../utils/api-client.js"
import { validateNetwork, validateAddress } from "../utils/validators.js"
import { formatNetworkName, formatTimestamp, formatAddress } from "../utils/formatters.js"
export function registerCcipTools(server: McpServer, apiClient: ChainlinkApiClient) {
// Tool: Send Cross-Chain Message
server.tool(
"send_cross_chain_message",
"Send a message from one blockchain to another using Chainlink CCIP. Supports arbitrary data transfer.",
{
destinationChain: z.string().describe("Destination blockchain network"),
receiver: z.string().describe("Receiver address on destination chain"),
message: z.string().describe("Message data to send"),
gasLimit: z.number().optional().describe("Gas limit for execution on destination"),
feeToken: z.enum(["LINK", "native"]).default("LINK").describe("Token to pay fees with"),
sourceChain: z.string().optional().describe("Source blockchain (default: ethereum)")
},
async ({ destinationChain, receiver, message, gasLimit, feeToken = "LINK", sourceChain = "ethereum" }) => {
try {
const sourceValidation = validateNetwork(sourceChain)
if (!sourceValidation.valid) {
throw new Error(`Invalid source chain: ${sourceValidation.error}`)
}
const destValidation = validateNetwork(destinationChain)
if (!destValidation.valid) {
throw new Error(`Invalid destination chain: ${destValidation.error}`)
}
const addressValidation = validateAddress(receiver)
if (!addressValidation.valid) {
throw new Error(addressValidation.error)
}
// Mock message sending
const messageId = `0x${Math.random().toString(16).slice(2).padStart(64, '0')}`
const estimatedCost = feeToken === "LINK" ? "0.5 LINK" : "0.02 ETH"
const result = {
messageId,
sourceChain: formatNetworkName(sourceChain),
destinationChain: formatNetworkName(destinationChain),
receiver: formatAddress(receiver),
message: message.substring(0, 100) + (message.length > 100 ? "..." : ""),
feeToken,
estimatedCost,
gasLimit: gasLimit || 200000,
status: "pending",
sentAt: formatTimestamp(Date.now() / 1000),
estimatedDeliveryTime: "2-20 minutes"
}
return {
content: [
{
type: "text",
text: `Cross-chain message sent successfully\n\n` +
`Message ID: ${messageId}\n` +
`Route: ${result.sourceChain} → ${result.destinationChain}\n` +
`Receiver: ${result.receiver}\n` +
`Status: ${result.status}\n` +
`Cost: ${result.estimatedCost}\n` +
`Estimated delivery: ${result.estimatedDeliveryTime}\n\n` +
`Full Details:\n${JSON.stringify(result, null, 2)}`
}
]
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error sending cross-chain message: ${error instanceof Error ? error.message : String(error)}`
}
]
}
}
}
)
// Tool: Transfer Tokens Cross-Chain
server.tool(
"transfer_tokens_cross_chain",
"Transfer tokens from one blockchain to another using Chainlink CCIP. Supports various token standards.",
{
destinationChain: z.string().describe("Destination blockchain network"),
receiver: z.string().describe("Receiver address on destination chain"),
tokenAddress: z.string().describe("Token contract address on source chain"),
amount: z.string().describe("Amount to transfer (in token's smallest unit)"),
gasLimit: z.number().optional().describe("Gas limit for execution"),
feeToken: z.enum(["LINK", "native"]).default("LINK").describe("Token to pay fees with"),
sourceChain: z.string().optional().describe("Source blockchain (default: ethereum)")
},
async ({ destinationChain, receiver, tokenAddress, amount, gasLimit, feeToken = "LINK", sourceChain = "ethereum" }) => {
try {
const sourceValidation = validateNetwork(sourceChain)
if (!sourceValidation.valid) {
throw new Error(`Invalid source chain: ${sourceValidation.error}`)
}
const destValidation = validateNetwork(destinationChain)
if (!destValidation.valid) {
throw new Error(`Invalid destination chain: ${destValidation.error}`)
}
const receiverValidation = validateAddress(receiver)
if (!receiverValidation.valid) {
throw new Error(`Invalid receiver: ${receiverValidation.error}`)
}
const tokenValidation = validateAddress(tokenAddress)
if (!tokenValidation.valid) {
throw new Error(`Invalid token address: ${tokenValidation.error}`)
}
// Mock transfer
const transferId = `0x${Math.random().toString(16).slice(2).padStart(64, '0')}`
const estimatedCost = feeToken === "LINK" ? "0.8 LINK" : "0.03 ETH"
const result = {
transferId,
sourceChain: formatNetworkName(sourceChain),
destinationChain: formatNetworkName(destinationChain),
tokenAddress: formatAddress(tokenAddress),
receiver: formatAddress(receiver),
amount,
feeToken,
estimatedCost,
gasLimit: gasLimit || 300000,
status: "pending",
initiatedAt: formatTimestamp(Date.now() / 1000),
estimatedDeliveryTime: "5-30 minutes"
}
return {
content: [
{
type: "text",
text: `Cross-chain token transfer initiated successfully\n\n` +
`Transfer ID: ${transferId}\n` +
`Route: ${result.sourceChain} → ${result.destinationChain}\n` +
`Token: ${result.tokenAddress}\n` +
`Amount: ${amount}\n` +
`Receiver: ${result.receiver}\n` +
`Status: ${result.status}\n` +
`Cost: ${result.estimatedCost}\n` +
`Estimated delivery: ${result.estimatedDeliveryTime}\n\n` +
`Full Details:\n${JSON.stringify(result, null, 2)}`
}
]
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error transferring tokens cross-chain: ${error instanceof Error ? error.message : String(error)}`
}
]
}
}
}
)
// Tool: Get Message Status
server.tool(
"get_message_status",
"Check the status of a cross-chain message or token transfer. Returns delivery confirmation and details.",
{
messageId: z.string().describe("CCIP message ID to check"),
sourceChain: z.string().optional().describe("Source blockchain network"),
destinationChain: z.string().optional().describe("Destination blockchain network")
},
async ({ messageId, sourceChain = "ethereum", destinationChain }) => {
try {
const sourceValidation = validateNetwork(sourceChain)
if (!sourceValidation.valid) {
throw new Error(`Invalid source chain: ${sourceValidation.error}`)
}
if (destinationChain) {
const destValidation = validateNetwork(destinationChain)
if (!destValidation.valid) {
throw new Error(`Invalid destination chain: ${destValidation.error}`)
}
}
// Mock status checking
const statusOptions = ["pending", "delivered", "failed"]
const status = statusOptions[Math.floor(Math.random() * statusOptions.length)]
let result: any = {
messageId,
sourceChain: formatNetworkName(sourceChain),
destinationChain: destinationChain ? formatNetworkName(destinationChain) : "unknown",
status,
lastUpdated: formatTimestamp(Date.now() / 1000)
}
if (status === "delivered") {
result = {
...result,
deliveredAt: formatTimestamp(Date.now() / 1000 - Math.random() * 3600),
destinationTxHash: `0x${Math.random().toString(16).slice(2).padStart(64, '0')}`,
gasUsed: "180000",
deliveryTime: "12 minutes"
}
} else if (status === "failed") {
result = {
...result,
failureReason: "Insufficient gas on destination chain",
failedAt: formatTimestamp(Date.now() / 1000 - Math.random() * 1800),
retryable: true
}
} else {
result = {
...result,
estimatedDeliveryTime: "5-15 minutes remaining",
currentStep: "Awaiting destination chain confirmation"
}
}
return {
content: [
{
type: "text",
text: `CCIP message status: ${status}\n\n` +
`Message ID: ${messageId}\n` +
`Route: ${result.sourceChain} → ${result.destinationChain}\n` +
`Status: ${status}\n` +
`Last Updated: ${result.lastUpdated}\n\n` +
(status === "delivered" ? `Delivered: ${result.deliveredAt}\nTx Hash: ${result.destinationTxHash}\n` : "") +
(status === "failed" ? `Failure Reason: ${result.failureReason}\nRetryable: ${result.retryable}\n` : "") +
(status === "pending" ? `Current Step: ${result.currentStep}\nETA: ${result.estimatedDeliveryTime}\n` : "") +
`\nFull Details:\n${JSON.stringify(result, null, 2)}`
}
]
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error checking message status: ${error instanceof Error ? error.message : String(error)}`
}
]
}
}
}
)
// Tool: List Supported Networks
server.tool(
"list_supported_networks",
"List all blockchain networks supported by Chainlink CCIP with their configurations and capabilities.",
{
includeTestnets: z.boolean().default(false).describe("Include testnet networks")
},
async ({ includeTestnets = false }) => {
try {
// Mock supported networks
const mainnets = [
{
name: "Ethereum",
chainId: 1,
ccipChainSelector: "5009297550715157269",
nativeToken: "ETH",
linkToken: "0x514910771AF9Ca656af840dff83E8264EcF986CA",
ccipRouter: "0x80226fc0Ee2b096224EeAc085Bb9a8cba1146f7D",
gasToken: "ETH",
features: ["messaging", "token_transfer"]
},
{
name: "Polygon",
chainId: 137,
ccipChainSelector: "4051577828743386545",
nativeToken: "MATIC",
linkToken: "0x53E0bca35eC356BD5ddDFebbD1Fc0fD03FaBad39",
ccipRouter: "0x849c5ED5a80F5B408Dd4969b78c2C8fdf0cbf2e",
gasToken: "MATIC",
features: ["messaging", "token_transfer"]
},
{
name: "Arbitrum",
chainId: 42161,
ccipChainSelector: "4949039107694359620",
nativeToken: "ETH",
linkToken: "0xf97f4df75117a78c1A5a0DBb814Af92458539FB4",
ccipRouter: "0x141fa059441E0ca23ce184B6A78bafD2A517DdE8",
gasToken: "ETH",
features: ["messaging", "token_transfer"]
}
]
const testnets = [
{
name: "Ethereum Sepolia",
chainId: 11155111,
ccipChainSelector: "16015286601757825753",
nativeToken: "ETH",
linkToken: "0x779877A7B0D9E8603169DdbD7836e478b4624789",
ccipRouter: "0xd0daae2231e9cb96b94c8512223533293c3693bf",
gasToken: "ETH",
features: ["messaging", "token_transfer"]
}
]
const networks = includeTestnets ? [...mainnets, ...testnets] : mainnets
const result = {
totalNetworks: networks.length,
includeTestnets,
supportedFeatures: ["cross_chain_messaging", "token_transfers", "programmable_token_transfers"],
networks
}
return {
content: [
{
type: "text",
text: `Chainlink CCIP supported networks\n\n` +
`Total Networks: ${result.totalNetworks}\n` +
`Include Testnets: ${includeTestnets}\n` +
`Supported Features: ${result.supportedFeatures.join(", ")}\n\n` +
`Networks:\n${networks.map(n => `- ${n.name} (Chain ID: ${n.chainId})`).join("\n")}\n\n` +
`Full Details:\n${JSON.stringify(result, null, 2)}`
}
]
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error listing supported networks: ${error instanceof Error ? error.message : String(error)}`
}
]
}
}
}
)
// Tool: Estimate CCIP Fees
server.tool(
"estimate_ccip_fees",
"Estimate the cost of sending messages or transferring tokens via CCIP between specific networks.",
{
sourceChain: z.string().describe("Source blockchain network"),
destinationChain: z.string().describe("Destination blockchain network"),
messageSize: z.number().optional().describe("Message size in bytes (for messaging)"),
tokenAmount: z.string().optional().describe("Token amount (for transfers)"),
gasLimit: z.number().optional().describe("Gas limit for destination execution"),
feeToken: z.enum(["LINK", "native"]).default("LINK").describe("Token to pay fees with")
},
async ({ sourceChain, destinationChain, messageSize, tokenAmount, gasLimit, feeToken = "LINK" }) => {
try {
const sourceValidation = validateNetwork(sourceChain)
if (!sourceValidation.valid) {
throw new Error(`Invalid source chain: ${sourceValidation.error}`)
}
const destValidation = validateNetwork(destinationChain)
if (!destValidation.valid) {
throw new Error(`Invalid destination chain: ${destValidation.error}`)
}
// Mock fee calculation
let baseFee = 0.1
let messageFee = messageSize ? (messageSize / 100) * 0.01 : 0
let tokenFee = tokenAmount ? 0.05 : 0
let gasFee = gasLimit ? (gasLimit / 100000) * 0.02 : 0
const totalFee = baseFee + messageFee + tokenFee + gasFee
const nativeFee = totalFee * (feeToken === "native" ? 20 : 1) // Convert to ETH equivalent
const result = {
sourceChain: formatNetworkName(sourceChain),
destinationChain: formatNetworkName(destinationChain),
feeToken,
estimatedFee: feeToken === "LINK" ? `${totalFee.toFixed(4)} LINK` : `${(nativeFee / 1000).toFixed(6)} ETH`,
estimatedUsdFee: `$${(totalFee * 15).toFixed(2)}`,
feeBreakdown: {
baseFee: `${baseFee} LINK`,
messageFee: `${messageFee.toFixed(4)} LINK`,
tokenTransferFee: `${tokenFee} LINK`,
gasExecutionFee: `${gasFee.toFixed(4)} LINK`
},
parameters: {
messageSize: messageSize || 0,
tokenAmount: tokenAmount || "0",
gasLimit: gasLimit || 200000
},
estimatedDeliveryTime: "5-20 minutes"
}
return {
content: [
{
type: "text",
text: `CCIP fee estimation\n\n` +
`Route: ${result.sourceChain} → ${result.destinationChain}\n` +
`Estimated Fee: ${result.estimatedFee}\n` +
`USD Estimate: ${result.estimatedUsdFee}\n` +
`Delivery Time: ${result.estimatedDeliveryTime}\n\n` +
`Fee Breakdown:\n` +
`- Base Fee: ${result.feeBreakdown.baseFee}\n` +
`- Message Fee: ${result.feeBreakdown.messageFee}\n` +
`- Token Transfer: ${result.feeBreakdown.tokenTransferFee}\n` +
`- Gas Execution: ${result.feeBreakdown.gasExecutionFee}\n\n` +
`Full Details:\n${JSON.stringify(result, null, 2)}`
}
]
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error estimating CCIP fees: ${error instanceof Error ? error.message : String(error)}`
}
]
}
}
}
)
}