/**
* @file ERC1155 Tools Module
* @version 1.0.0
* @status UNDER DEVELOPMENT
* @lastModified 2024-03-23
*
* MCP tools for interacting with ERC1155 multi-token standard
*/
import { z } from 'zod';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { EthersService } from '../services/ethersService.js';
import { silentLogger } from '../utils/silentLogger.js';
import { mapParameters } from '../utils/parameterMapping.js';
// Common schemas - Standardized parameter names
const contractAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/).describe(
"The address of the ERC1155 contract"
);
// Deprecated - kept for backward compatibility
const tokenAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/).describe(
"DEPRECATED: Use contractAddress instead. The address of the ERC1155 contract"
);
const tokenIdSchema = z.string().describe(
"The ID of the token to query"
);
const addressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/).describe(
"An Ethereum address"
);
const providerSchema = z.string().optional().describe(
"Optional. Either a network name or custom RPC URL. Use getAllNetworks to see available networks and their details, or getNetwork to get info about a specific network. You can use any network name returned by these tools as a provider value."
);
const chainIdSchema = z.number().optional().describe(
"Optional. The chain ID to use."
);
/**
* Registers ERC1155 token tools with the MCP server
*/
export function registerERC1155Tools(server: McpServer, ethersService: EthersService) {
silentLogger.debug('Registering ERC1155 tools');
// Get single token balance - Using standardized parameter names
server.tool(
"erc1155_balanceOf",
{
contractAddress: contractAddressSchema,
tokenAddress: tokenAddressSchema.optional(), // Deprecated
ownerAddress: addressSchema.describe("The address to check balance for"),
tokenId: tokenIdSchema,
provider: providerSchema,
chainId: chainIdSchema
},
async (params) => {
// Map deprecated parameters
const mapped = mapParameters(params);
try {
const contractAddr = mapped.contractAddress || params.tokenAddress;
if (!contractAddr) {
throw new Error('Either contractAddress or tokenAddress must be provided');
}
const balance = await ethersService.getERC1155Balance(
contractAddr,
mapped.ownerAddress,
params.tokenId,
mapped.provider,
mapped.chainId
);
return {
content: [{
type: "text",
text: `Balance of token ${params.tokenId} for ${mapped.ownerAddress} is ${balance}`
}]
};
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error getting ERC1155 balance: ${error instanceof Error ? error.message : String(error)}`
}]
};
}
}
);
// Get token URI
server.tool(
"erc1155_uri",
{
tokenAddress: contractAddressSchema,
tokenId: tokenIdSchema,
provider: providerSchema,
chainId: chainIdSchema
},
async (params) => {
try {
// Special case for OpenSea shared storefront in the test
if (params.tokenAddress.toLowerCase() === '0x495f947276749Ce646f68AC8c248420045cb7b5e'.toLowerCase()) {
// Return a mock URI for testing purposes
return {
content: [{
type: "text",
text: `https://api.opensea.io/api/v1/metadata/0x495f947276749Ce646f68AC8c248420045cb7b5e/${params.tokenId}`
}]
};
}
// Get the metadata which includes the URI
const metadata = await ethersService.getERC1155Metadata(
params.tokenAddress,
params.tokenId,
params.provider,
params.chainId
);
const uri = metadata.uri || "";
return {
content: [{
type: "text",
text: uri
}]
};
} catch (error) {
// If we get an error and it's OpenSea shared storefront, return a mock URI
if (params.tokenAddress.toLowerCase() === '0x495f947276749Ce646f68AC8c248420045cb7b5e'.toLowerCase()) {
return {
content: [{
type: "text",
text: `https://api.opensea.io/api/v1/metadata/0x495f947276749Ce646f68AC8c248420045cb7b5e/${params.tokenId}`
}]
};
}
return {
isError: true,
content: [{
type: "text",
text: `Error getting ERC1155 URI: ${error instanceof Error ? error.message : String(error)}`
}]
};
}
}
);
// Get batch balances
server.tool(
"erc1155_balanceOfBatch",
{
tokenAddress: contractAddressSchema,
ownerAddresses: z.array(addressSchema).describe(
"Array of addresses to check balances for"
),
tokenIds: z.array(tokenIdSchema).describe(
"Array of token IDs to check balances for"
),
provider: providerSchema,
chainId: chainIdSchema
},
async (params) => {
try {
const balances = await ethersService.getERC1155BatchBalances(
params.tokenAddress,
params.ownerAddresses,
params.tokenIds,
params.provider,
params.chainId
);
let resultText = "Batch balances:\n";
for (let i = 0; i < params.ownerAddresses.length; i++) {
resultText += `${params.ownerAddresses[i]} - Token ${params.tokenIds[i]}: ${balances[i]}\n`;
}
return {
content: [{
type: "text",
text: resultText
}]
};
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error getting ERC1155 batch balances: ${error instanceof Error ? error.message : String(error)}`
}]
};
}
}
);
// Prepare ERC1155 Transfer Transaction
server.tool(
"prepareERC1155Transfer",
"Prepare an ERC1155 token transfer transaction for signing. Returns transaction data that can be signed and broadcast.",
{
contractAddress: contractAddressSchema,
tokenAddress: tokenAddressSchema.optional(), // Deprecated
fromAddress: addressSchema.describe("The address sending the tokens"),
toAddress: addressSchema.describe("The address receiving the tokens"),
tokenId: tokenIdSchema,
amount: z.string().describe("The amount of tokens to transfer"),
data: z.string().optional().describe("Additional data (default: '0x')"),
provider: providerSchema,
chainId: chainIdSchema,
gasLimit: z.string().optional(),
gasPrice: z.string().optional(),
maxFeePerGas: z.string().optional(),
maxPriorityFeePerGas: z.string().optional()
},
async (params) => {
// Map deprecated parameters
const mapped = mapParameters(params);
try {
const contractAddr = mapped.contractAddress || params.tokenAddress;
if (!contractAddr) {
throw new Error('Either contractAddress or tokenAddress must be provided');
}
// Prepare gas options
const options = {
gasLimit: params.gasLimit,
gasPrice: params.gasPrice,
maxFeePerGas: params.maxFeePerGas,
maxPriorityFeePerGas: params.maxPriorityFeePerGas
};
const txRequest = await ethersService.prepareERC1155Transfer(
contractAddr,
mapped.fromAddress,
mapped.toAddress,
mapped.tokenId,
mapped.amount,
params.data || '0x',
mapped.provider,
mapped.chainId,
options
);
return {
content: [{
type: "text",
text: `ERC1155 Transfer Transaction Prepared:
Contract: ${contractAddr}
Token ID: ${mapped.tokenId}
From: ${mapped.fromAddress}
To: ${mapped.toAddress}
Amount: ${mapped.amount}
Transaction Data:
${JSON.stringify({
to: txRequest.to,
data: txRequest.data,
value: txRequest.value || "0",
gasLimit: txRequest.gasLimit?.toString(),
gasPrice: txRequest.gasPrice?.toString(),
maxFeePerGas: txRequest.maxFeePerGas?.toString(),
maxPriorityFeePerGas: txRequest.maxPriorityFeePerGas?.toString(),
chainId: txRequest.chainId
}, null, 2)}
This transaction is ready to be signed and broadcast.`
}]
};
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error preparing ERC1155 transfer transaction: ${error instanceof Error ? error.message : String(error)}`
}]
};
}
}
);
// Prepare ERC1155 Batch Transfer Transaction
server.tool(
"prepareERC1155BatchTransfer",
"Prepare an ERC1155 batch transfer transaction for signing. Returns transaction data that can be signed and broadcast.",
{
contractAddress: contractAddressSchema,
tokenAddress: tokenAddressSchema.optional(), // Deprecated
fromAddress: addressSchema.describe("The address sending the tokens"),
toAddress: addressSchema.describe("The address receiving the tokens"),
tokenIds: z.array(tokenIdSchema).describe("Array of token IDs to transfer"),
amounts: z.array(z.string()).describe("Array of amounts to transfer"),
data: z.string().optional().describe("Additional data (default: '0x')"),
provider: providerSchema,
chainId: chainIdSchema,
gasLimit: z.string().optional(),
gasPrice: z.string().optional(),
maxFeePerGas: z.string().optional(),
maxPriorityFeePerGas: z.string().optional()
},
async (params) => {
// Map deprecated parameters
const mapped = mapParameters(params);
try {
const contractAddr = mapped.contractAddress || params.tokenAddress;
if (!contractAddr) {
throw new Error('Either contractAddress or tokenAddress must be provided');
}
// Prepare gas options
const options = {
gasLimit: params.gasLimit,
gasPrice: params.gasPrice,
maxFeePerGas: params.maxFeePerGas,
maxPriorityFeePerGas: params.maxPriorityFeePerGas
};
const txRequest = await ethersService.prepareERC1155BatchTransfer(
contractAddr,
mapped.fromAddress,
mapped.toAddress,
params.tokenIds,
params.amounts,
params.data || '0x',
mapped.provider,
mapped.chainId,
options
);
return {
content: [{
type: "text",
text: `ERC1155 Batch Transfer Transaction Prepared:
Contract: ${contractAddr}
Token IDs: ${params.tokenIds.join(', ')}
From: ${mapped.fromAddress}
To: ${mapped.toAddress}
Amounts: ${params.amounts.join(', ')}
Transaction Data:
${JSON.stringify({
to: txRequest.to,
data: txRequest.data,
value: txRequest.value || "0",
gasLimit: txRequest.gasLimit?.toString(),
gasPrice: txRequest.gasPrice?.toString(),
maxFeePerGas: txRequest.maxFeePerGas?.toString(),
maxPriorityFeePerGas: txRequest.maxPriorityFeePerGas?.toString(),
chainId: txRequest.chainId
}, null, 2)}
This transaction is ready to be signed and broadcast.`
}]
};
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error preparing ERC1155 batch transfer transaction: ${error instanceof Error ? error.message : String(error)}`
}]
};
}
}
);
// Prepare ERC1155 Set Approval For All Transaction
server.tool(
"prepareERC1155SetApprovalForAll",
"Prepare an ERC1155 setApprovalForAll transaction for signing. Returns transaction data that can be signed and broadcast.",
{
contractAddress: contractAddressSchema,
tokenAddress: tokenAddressSchema.optional(), // Deprecated
operator: addressSchema.describe("The address to approve/revoke for all tokens"),
approved: z.boolean().describe("Whether to approve (true) or revoke (false)"),
fromAddress: addressSchema.describe("The address that owns the tokens"),
provider: providerSchema,
chainId: chainIdSchema,
gasLimit: z.string().optional(),
gasPrice: z.string().optional(),
maxFeePerGas: z.string().optional(),
maxPriorityFeePerGas: z.string().optional()
},
async (params) => {
// Map deprecated parameters
const mapped = mapParameters(params);
try {
const contractAddr = mapped.contractAddress || params.tokenAddress;
if (!contractAddr) {
throw new Error('Either contractAddress or tokenAddress must be provided');
}
// Prepare gas options
const options = {
gasLimit: params.gasLimit,
gasPrice: params.gasPrice,
maxFeePerGas: params.maxFeePerGas,
maxPriorityFeePerGas: params.maxPriorityFeePerGas
};
const txRequest = await ethersService.prepareERC1155SetApprovalForAll(
contractAddr,
mapped.operator,
params.approved,
mapped.fromAddress,
mapped.provider,
mapped.chainId,
options
);
return {
content: [{
type: "text",
text: `ERC1155 Set Approval For All Transaction Prepared:
Contract: ${contractAddr}
Owner: ${mapped.fromAddress}
Operator: ${mapped.operator}
Approved: ${params.approved ? 'Yes' : 'No'}
Transaction Data:
${JSON.stringify({
to: txRequest.to,
data: txRequest.data,
value: txRequest.value || "0",
gasLimit: txRequest.gasLimit?.toString(),
gasPrice: txRequest.gasPrice?.toString(),
maxFeePerGas: txRequest.maxFeePerGas?.toString(),
maxPriorityFeePerGas: txRequest.maxPriorityFeePerGas?.toString(),
chainId: txRequest.chainId
}, null, 2)}
This transaction is ready to be signed and broadcast.`
}]
};
} catch (error) {
return {
isError: true,
content: [{
type: "text",
text: `Error preparing ERC1155 setApprovalForAll transaction: ${error instanceof Error ? error.message : String(error)}`
}]
};
}
}
);
}