index.js•7.72 kB
const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
const { ethers } = require('ethers');
const { z } = require('zod');
require('dotenv').config();
// BSC contract details
const CONTRACT_ADDRESS = '0x2e1d30460265bfebedacf5bb6f9a80f0e74b7498';
const CONTRACT_ABI = [
{
"constant": false,
"inputs": [
{ "name": "receivers", "type": "address[]" },
{ "name": "amounts", "type": "uint256[]" }
],
"name": "transferMulti",
"outputs": [],
"payable": true,
"stateMutability": "payable",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "tokenAddress", "type": "address" },
{ "name": "receivers", "type": "address[]" },
{ "name": "amounts", "type": "uint256[]" }
],
"name": "transferMultiToken",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{ "indexed": false, "name": "from", "type": "address" },
{ "indexed": false, "name": "to", "type": "address" },
{ "indexed": false, "name": "amount", "type": "uint256" },
{ "indexed": false, "name": "tokenAddress", "type": "address" }
],
"name": "transfer",
"type": "event"
}
];
// ERC20 ABI for decimals, allowance, and approve
const ERC20_ABI = [
{
"constant": true,
"inputs": [],
"name": "decimals",
"outputs": [{ "name": "", "type": "uint8" }],
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{ "name": "owner", "type": "address" },
{ "name": "spender", "type": "address" }
],
"name": "allowance",
"outputs": [{ "name": "", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"constant": false,
"inputs": [
{ "name": "spender", "type": "address" },
{ "name": "value", "type": "uint256" }
],
"name": "approve",
"outputs": [{ "name": "", "type": "bool" }],
"stateMutability": "nonpayable",
"type": "function"
}
];
// Initialize ethers provider
const provider = new ethers.JsonRpcProvider(process.env.BSC_RPC || 'https://bsc-dataseed.binance.org/');
// Wallet setup
const PRIVATE_KEY = process.env.PRIVATE_KEY;
if (!PRIVATE_KEY) {
throw new Error('PRIVATE_KEY must be set in .env');
}
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
const WALLET_ADDRESS = wallet.address;
// Initialize C98MultiSend contract
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, wallet);
// Create MCP server
const server = new McpServer({
name: 'BSC-MultiSend-Server',
version: '1.0.0'
});
// Tool: Distribute native BNB to multiple addresses
server.tool(
'distributeNative',
'Send BNB to multiple addresses on BSC using the C98MultiSend contract.',
z.object({
receivers: z.array(z.string().refine((val) => ethers.isAddress(val), {
message: 'Invalid BSC address'
})).describe('Array of BSC addresses to receive BNB.'),
amounts: z.array(z.number().positive('Amount must be positive')).describe('Array of BNB amounts (in BNB, e.g., 0.1) to send to each receiver.')
}),
async ({ receivers, amounts }) => {
try {
// Validate inputs
if (receivers.length !== amounts.length) {
return {
content: [{ type: 'text', text: 'Receivers and amounts arrays must have the same length' }],
isError: true
};
}
// Convert amounts to wei and calculate total
const amountsWei = amounts.map(amount => ethers.parseEther(amount.toString()));
const totalAmountWei = amountsWei.reduce((sum, amount) => sum + BigInt(amount), BigInt(0));
// Prepare and send transaction
const tx = await contract.transferMulti(receivers, amountsWei, {
value: totalAmountWei,
gasLimit: await contract.transferMulti.estimateGas(receivers, amountsWei, { value: totalAmountWei })
});
// Wait for transaction confirmation
const receipt = await tx.wait();
return {
content: [{
type: 'text',
text: `BNB distribution successful. Tx Hash: ${receipt.hash}`
}],
isError: false
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error executing BNB distribution: ${error.message}` }],
isError: true
};
}
}
);
// Tool: Distribute ERC20 tokens to multiple addresses
server.tool(
'distributeToken',
'Send ERC20 tokens to multiple addresses on BSC using the C98MultiSend contract.',
z.object({
tokenAddress: z.string().refine((val) => ethers.isAddress(val), {
message: 'Invalid token address'
}).describe('Address of the ERC20 token contract to distribute.'),
receivers: z.array(z.string().refine((val) => ethers.isAddress(val), {
message: 'Invalid BSC address'
})).describe('Array of BSC addresses to receive tokens.'),
amounts: z.array(z.number().positive('Amount must be positive')).describe('Array of token amounts (in tokens, e.g., 100.5) to send to each receiver.')
}),
async ({ tokenAddress, receivers, amounts }) => {
try {
// Validate inputs
if (receivers.length !== amounts.length) {
return {
content: [{ type: 'text', text: 'Receivers and amounts arrays must have the same length' }],
isError: true
};
}
// Initialize token contract
const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, wallet);
// Query token decimals
let decimals;
try {
decimals = await tokenContract.decimals();
} catch (error) {
return {
content: [{ type: 'text', text: `Invalid ERC20 token: Unable to fetch decimals (${error.message})` }],
isError: true
};
}
// Convert amounts to token units
const amountsUnits = amounts.map(amount => ethers.parseUnits(amount.toString(), decimals));
// Calculate total amount needed
const totalAmount = amountsUnits.reduce((sum, amount) => sum + BigInt(amount), BigInt(0));
// Check allowance
const allowance = await tokenContract.allowance(WALLET_ADDRESS, CONTRACT_ADDRESS);
if (BigInt(allowance) < totalAmount) {
// Approve the contract to spend tokens
try {
const approveTx = await tokenContract.approve(CONTRACT_ADDRESS, totalAmount, {
gasLimit: await tokenContract.approve.estimateGas(CONTRACT_ADDRESS, totalAmount)
});
await approveTx.wait();
} catch (error) {
return {
content: [{ type: 'text', text: `Failed to approve token spending: ${error.message}` }],
isError: true
};
}
}
// Prepare and send transaction
const tx = await contract.transferMultiToken(tokenAddress, receivers, amountsUnits, {
gasLimit: await contract.transferMultiToken.estimateGas(tokenAddress, receivers, amountsUnits)
});
// Wait for transaction confirmation
const receipt = await tx.wait();
return {
content: [{
type: 'text',
text: `Token distribution successful. Tx Hash: ${receipt.hash}`
}],
isError: false
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error executing token distribution: ${error.message}` }],
isError: true
};
}
}
);
// Start server with stdio transport
async function startServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
startServer().catch(console.error);