import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { DEFAULT_NETWORK } from "../chains.js";
import * as services from "../services/index.js";
import { type Address } from 'viem';
import { getPrivateKeyAsHex } from "../config.js";
/**
* Registers tools related to tokens (native, ERC20, ERC721, ERC1155).
* @param server The MCP server instance
*/
export function registerTokenTools(server: McpServer) {
// Get Sei balance
server.tool(
"get_balance",
"Get the native token balance (Sei) for an address",
{
address: z.string().describe("The wallet address to check the balance for"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ address, network = DEFAULT_NETWORK }) => {
try {
const balance = await services.getBalance(address, network);
return {
content: [{
type: "text",
text: JSON.stringify({
address,
network,
wei: balance.wei.toString(),
ether: balance.sei
}, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error fetching balance: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Get ERC20 token balance
server.tool(
"get_erc20_balance",
"Get the ERC20 token balance of an EVM address",
{
address: z.string().describe("The EVM address to check"),
tokenAddress: z.string().describe("The ERC20 token contract address"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ address, tokenAddress, network = DEFAULT_NETWORK }) => {
try {
const balance = await services.getERC20Balance(
tokenAddress as Address,
address as Address,
network
);
return {
content: [{
type: "text",
text: JSON.stringify({
address,
tokenAddress,
network,
balance: {
raw: balance.raw.toString(),
formatted: balance.formatted,
decimals: balance.token.decimals
}
}, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error fetching ERC20 balance for ${address}: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Transfer Sei
server.tool(
"transfer_sei",
"Transfer native tokens (Sei) to an address",
{
to: z.string().describe("The recipient address"),
amount: z.string().describe("Amount to send in SEI"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ to, amount, network = DEFAULT_NETWORK }) => {
try {
const txHash = await services.transferSei(to, amount, network);
return {
content: [{
type: "text",
text: JSON.stringify({ success: true, txHash, to, amount, network }, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error transferring Sei: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Transfer ERC20
server.tool(
"transfer_erc20",
"Transfer ERC20 tokens to another address",
{
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"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ tokenAddress, toAddress, amount, network = DEFAULT_NETWORK }) => {
try {
const result = await services.transferERC20(tokenAddress, toAddress, amount, 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
};
}
}
);
// Approve ERC20 token spending
server.tool(
"approve_token_spending",
"Approve another address to spend your ERC20 tokens.",
{
tokenAddress: z.string().describe("The contract address of the ERC20 token"),
spenderAddress: z.string().describe("The contract address being approved"),
amount: z.string().describe("The amount of tokens to approve"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ tokenAddress, spenderAddress, amount, network = DEFAULT_NETWORK }) => {
try {
const result = await services.approveERC20(tokenAddress, spenderAddress, amount, network);
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
txHash: result.txHash,
network,
tokenAddress,
spender: spenderAddress,
amount: result.amount.formatted,
symbol: result.token.symbol
}, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error approving token spending: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Transfer NFT (ERC721)
server.tool(
"transfer_nft",
"Transfer an NFT (ERC721 token) from one address to another.",
{
tokenAddress: z.string().describe("The contract address of the NFT collection"),
tokenId: z.string().describe("The ID of the specific NFT to transfer"),
toAddress: z.string().describe("The recipient wallet address"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ tokenAddress, tokenId, toAddress, network = DEFAULT_NETWORK }) => {
try {
const result = await services.transferERC721(tokenAddress, toAddress, BigInt(tokenId), network);
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
txHash: result.txHash,
network,
collection: tokenAddress,
tokenId: result.tokenId,
recipient: toAddress,
name: result.token.name,
symbol: result.token.symbol
}, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error transferring NFT: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Transfer ERC1155 token
server.tool(
"transfer_erc1155",
"Transfer ERC1155 tokens to another address.",
{
tokenAddress: z.string().describe("The contract address of the ERC1155 token collection"),
tokenId: z.string().describe("The ID of the specific token to transfer"),
amount: z.string().describe("The quantity of tokens to send"),
toAddress: z.string().describe("The recipient wallet address"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ tokenAddress, tokenId, amount, toAddress, network = DEFAULT_NETWORK }) => {
try {
const result = await services.transferERC1155(tokenAddress, toAddress, BigInt(tokenId), amount, network);
return {
content: [{
type: "text",
text: JSON.stringify({
success: true,
txHash: result.txHash,
network,
contract: tokenAddress,
tokenId: result.tokenId,
amount: result.amount,
recipient: toAddress
}, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error transferring ERC1155 tokens: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Get ERC20 token information
server.tool(
"get_token_info",
"Get comprehensive information about an ERC20 token.",
{
tokenAddress: z.string().describe("The contract address of the ERC20 token"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ tokenAddress, network = DEFAULT_NETWORK }) => {
try {
const tokenInfo = await services.getERC20TokenInfo(tokenAddress as Address, network);
const result = {
...tokenInfo,
totalSupply: tokenInfo.totalSupply.toString(),
};
return {
content: [{
type: "text",
text: JSON.stringify(result, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error fetching token info: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Get NFT (ERC721) information
server.tool(
"get_nft_info",
"Get detailed information about a specific NFT (ERC721 token).",
{
tokenAddress: z.string().describe("The contract address of the NFT collection"),
tokenId: z.string().describe("The ID of the specific NFT token"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ tokenAddress, tokenId, network = DEFAULT_NETWORK }) => {
try {
const nftInfo = await services.getERC721TokenMetadata(tokenAddress as Address, BigInt(tokenId), network);
return {
content: [{
type: "text",
text: JSON.stringify({ contract: tokenAddress, tokenId, network, ...nftInfo }, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error fetching NFT info: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Check NFT ownership
server.tool(
"check_nft_ownership",
"Check if an address owns a specific NFT",
{
tokenAddress: z.string().describe("The contract address of the NFT collection"),
tokenId: z.string().describe("The ID of the NFT to check"),
ownerAddress: z.string().describe("The wallet address to check ownership against"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ tokenAddress, tokenId, ownerAddress, network = DEFAULT_NETWORK }) => {
try {
const isOwner = await services.isNFTOwner(tokenAddress, ownerAddress, BigInt(tokenId), network);
return {
content: [{
type: "text",
text: JSON.stringify({ tokenAddress, tokenId, ownerAddress, network, isOwner }, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error checking NFT ownership: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Get ERC1155 token URI
server.tool(
"get_erc1155_token_uri",
"Get the metadata URI for an ERC1155 token.",
{
tokenAddress: z.string().describe("The contract address of the ERC1155 token collection"),
tokenId: z.string().describe("The ID of the specific token"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ tokenAddress, tokenId, network = DEFAULT_NETWORK }) => {
try {
const uri = await services.getERC1155TokenURI(tokenAddress as Address, BigInt(tokenId), network);
return {
content: [{
type: "text",
text: JSON.stringify({ contract: tokenAddress, tokenId, network, uri }, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error fetching ERC1155 token URI: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Get ERC721 NFT balance
server.tool(
"get_nft_balance",
"Get the total number of NFTs owned by an address from a specific collection.",
{
tokenAddress: z.string().describe("The contract address of the NFT collection"),
ownerAddress: z.string().describe("The wallet address to check the NFT balance for"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ tokenAddress, ownerAddress, network = DEFAULT_NETWORK }) => {
try {
const balance = await services.getERC721Balance(tokenAddress as Address, ownerAddress as Address, network);
return {
content: [{
type: "text",
text: JSON.stringify({ collection: tokenAddress, owner: ownerAddress, network, balance: balance.toString() }, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error fetching NFT balance: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Get ERC1155 token balance
server.tool(
"get_erc1155_balance",
"Get the balance of a specific ERC1155 token ID owned by an address.",
{
tokenAddress: z.string().describe("The contract address of the ERC1155 token collection"),
tokenId: z.string().describe("The ID of the specific token to check"),
ownerAddress: z.string().describe("The wallet address to check the balance for"),
network: z.string().optional().describe("Network name or chain ID. Defaults to Sei mainnet.")
},
async ({ tokenAddress, tokenId, ownerAddress, network = DEFAULT_NETWORK }) => {
try {
const balance = await services.getERC1155Balance(tokenAddress as Address, ownerAddress as Address, BigInt(tokenId), network);
return {
content: [{
type: "text",
text: JSON.stringify({ contract: tokenAddress, tokenId, owner: ownerAddress, network, balance: balance.toString() }, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error fetching ERC1155 token balance: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Get address from private key
server.tool(
"get_address_from_private_key",
"Get the EVM address derived from a private key",
{},
async () => {
try {
const privateKeyValue = getPrivateKeyAsHex();
if (!privateKeyValue) {
return {
content: [{ type: "text", text: "Error: The PRIVATE_KEY environment variable is not set." }],
isError: true
};
}
const address = services.getAddressFromPrivateKey(privateKeyValue);
return {
content: [{
type: "text",
text: JSON.stringify({ address }, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error deriving address from private key: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Wrap Sei using WrappedSEI contract directly
server.tool(
"wrap_sei_directly",
"Wrap SEI to WSEI token",
{
wrappedSeiAddress: z.string().describe("The WSEI contract address"),
amount: z.string().describe("Amount of SEI to wrap"),
network: z.string().optional().describe("Network to use")
},
async ({ wrappedSeiAddress, amount, network = DEFAULT_NETWORK }) => {
try {
const txHash = await services.wrapSeiDirectly(wrappedSeiAddress, amount, network);
return {
content: [{
type: "text",
text: JSON.stringify({ txHash }, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error wrapping SEI: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
// Unwrap WSEI using WrappedSEI contract directly
server.tool(
"unwrap_sei_directly",
"Unwrap WSEI to SEI token",
{
wrappedSeiAddress: z.string().describe("The WSEI contract address"),
amount: z.string().describe("Amount of WSEI to unwrap"),
network: z.string().optional().describe("Network to use")
},
async ({ wrappedSeiAddress, amount, network = DEFAULT_NETWORK }) => {
try {
const txHash = await services.unwrapWseiDirectly(wrappedSeiAddress, amount, network);
return {
content: [{
type: "text",
text: JSON.stringify({ txHash }, null, 2)
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error unwrapping WSEI: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
);
}