EVM MCP Server
by mcpdotdirect
Verified
- evm-mcp-server
- src
- core
- services
import {
parseEther,
parseUnits,
formatUnits,
type Address,
type Hash,
type Hex,
type Abi,
getContract,
type Account
} from 'viem';
import { getPublicClient, getWalletClient } from './clients.js';
import { getChain } from '../chains.js';
import { resolveAddress } from './ens.js';
// Standard ERC20 ABI for transfers
const erc20TransferAbi = [
{
inputs: [
{ type: 'address', name: 'to' },
{ type: 'uint256', name: 'amount' }
],
name: 'transfer',
outputs: [{ type: 'bool' }],
stateMutability: 'nonpayable',
type: 'function'
},
{
inputs: [
{ type: 'address', name: 'spender' },
{ type: 'uint256', name: 'amount' }
],
name: 'approve',
outputs: [{ type: 'bool' }],
stateMutability: 'nonpayable',
type: 'function'
},
{
inputs: [],
name: 'decimals',
outputs: [{ type: 'uint8' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [],
name: 'symbol',
outputs: [{ type: 'string' }],
stateMutability: 'view',
type: 'function'
}
] as const;
// Standard ERC721 ABI for transfers
const erc721TransferAbi = [
{
inputs: [
{ type: 'address', name: 'from' },
{ type: 'address', name: 'to' },
{ type: 'uint256', name: 'tokenId' }
],
name: 'transferFrom',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
},
{
inputs: [],
name: 'name',
outputs: [{ type: 'string' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [],
name: 'symbol',
outputs: [{ type: 'string' }],
stateMutability: 'view',
type: 'function'
},
{
inputs: [{ type: 'uint256', name: 'tokenId' }],
name: 'ownerOf',
outputs: [{ type: 'address' }],
stateMutability: 'view',
type: 'function'
}
] as const;
// ERC1155 ABI for transfers
const erc1155TransferAbi = [
{
inputs: [
{ type: 'address', name: 'from' },
{ type: 'address', name: 'to' },
{ type: 'uint256', name: 'id' },
{ type: 'uint256', name: 'amount' },
{ type: 'bytes', name: 'data' }
],
name: 'safeTransferFrom',
outputs: [],
stateMutability: 'nonpayable',
type: 'function'
},
{
inputs: [
{ type: 'address', name: 'account' },
{ type: 'uint256', name: 'id' }
],
name: 'balanceOf',
outputs: [{ type: 'uint256' }],
stateMutability: 'view',
type: 'function'
}
] as const;
/**
* Transfer ETH to an address
* @param privateKey Sender's private key
* @param toAddressOrEns Recipient address or ENS name
* @param amount Amount to send in ETH
* @param network Network name or chain ID
* @returns Transaction hash
*/
export async function transferETH(
privateKey: string | Hex,
toAddressOrEns: string,
amount: string, // in ether
network = 'ethereum'
): Promise<Hash> {
// Resolve ENS name to address if needed
const toAddress = await resolveAddress(toAddressOrEns, network);
// Ensure the private key has 0x prefix
const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x')
? `0x${privateKey}` as Hex
: privateKey as Hex;
const client = getWalletClient(formattedKey, network);
const amountWei = parseEther(amount);
return client.sendTransaction({
to: toAddress,
value: amountWei,
account: client.account!,
chain: client.chain
});
}
/**
* Transfer ERC20 tokens to an address
* @param tokenAddressOrEns Token contract address or ENS name
* @param toAddressOrEns Recipient address or ENS name
* @param amount Amount to send (in token units)
* @param privateKey Sender's private key
* @param network Network name or chain ID
* @returns Transaction details
*/
export async function transferERC20(
tokenAddressOrEns: string,
toAddressOrEns: string,
amount: string,
privateKey: string | `0x${string}`,
network: string = 'ethereum'
): Promise<{
txHash: Hash;
amount: {
raw: bigint;
formatted: string;
};
token: {
symbol: string;
decimals: number;
};
}> {
// Resolve ENS names to addresses if needed
const tokenAddress = await resolveAddress(tokenAddressOrEns, network) as Address;
const toAddress = await resolveAddress(toAddressOrEns, network) as Address;
// Ensure the private key has 0x prefix
const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x')
? `0x${privateKey}` as `0x${string}`
: privateKey as `0x${string}`;
// Get token details
const publicClient = getPublicClient(network);
const contract = getContract({
address: tokenAddress,
abi: erc20TransferAbi,
client: publicClient,
});
// Get token decimals and symbol
const decimals = await contract.read.decimals();
const symbol = await contract.read.symbol();
// Parse the amount with the correct number of decimals
const rawAmount = parseUnits(amount, decimals);
// Create wallet client for sending the transaction
const walletClient = getWalletClient(formattedKey, network);
// Send the transaction
const hash = await walletClient.writeContract({
address: tokenAddress,
abi: erc20TransferAbi,
functionName: 'transfer',
args: [toAddress, rawAmount],
account: walletClient.account!,
chain: walletClient.chain
});
return {
txHash: hash,
amount: {
raw: rawAmount,
formatted: amount
},
token: {
symbol,
decimals
}
};
}
/**
* Approve ERC20 token spending
* @param tokenAddressOrEns Token contract address or ENS name
* @param spenderAddressOrEns Spender address or ENS name
* @param amount Amount to approve (in token units)
* @param privateKey Owner's private key
* @param network Network name or chain ID
* @returns Transaction details
*/
export async function approveERC20(
tokenAddressOrEns: string,
spenderAddressOrEns: string,
amount: string,
privateKey: string | `0x${string}`,
network: string = 'ethereum'
): Promise<{
txHash: Hash;
amount: {
raw: bigint;
formatted: string;
};
token: {
symbol: string;
decimals: number;
};
}> {
// Resolve ENS names to addresses if needed
const tokenAddress = await resolveAddress(tokenAddressOrEns, network) as Address;
const spenderAddress = await resolveAddress(spenderAddressOrEns, network) as Address;
// Ensure the private key has 0x prefix
const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x')
? `0x${privateKey}` as `0x${string}`
: privateKey as `0x${string}`;
// Get token details
const publicClient = getPublicClient(network);
const contract = getContract({
address: tokenAddress,
abi: erc20TransferAbi,
client: publicClient,
});
// Get token decimals and symbol
const decimals = await contract.read.decimals();
const symbol = await contract.read.symbol();
// Parse the amount with the correct number of decimals
const rawAmount = parseUnits(amount, decimals);
// Create wallet client for sending the transaction
const walletClient = getWalletClient(formattedKey, network);
// Send the transaction
const hash = await walletClient.writeContract({
address: tokenAddress,
abi: erc20TransferAbi,
functionName: 'approve',
args: [spenderAddress, rawAmount],
account: walletClient.account!,
chain: walletClient.chain
});
return {
txHash: hash,
amount: {
raw: rawAmount,
formatted: amount
},
token: {
symbol,
decimals
}
};
}
/**
* Transfer an NFT (ERC721) to an address
* @param tokenAddressOrEns NFT contract address or ENS name
* @param toAddressOrEns Recipient address or ENS name
* @param tokenId Token ID to transfer
* @param privateKey Owner's private key
* @param network Network name or chain ID
* @returns Transaction details
*/
export async function transferERC721(
tokenAddressOrEns: string,
toAddressOrEns: string,
tokenId: bigint,
privateKey: string | `0x${string}`,
network: string = 'ethereum'
): Promise<{
txHash: Hash;
tokenId: string;
token: {
name: string;
symbol: string;
};
}> {
// Resolve ENS names to addresses if needed
const tokenAddress = await resolveAddress(tokenAddressOrEns, network) as Address;
const toAddress = await resolveAddress(toAddressOrEns, network) as Address;
// Ensure the private key has 0x prefix
const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x')
? `0x${privateKey}` as `0x${string}`
: privateKey as `0x${string}`;
// Create wallet client for sending the transaction
const walletClient = getWalletClient(formattedKey, network);
const fromAddress = walletClient.account!.address;
// Send the transaction
const hash = await walletClient.writeContract({
address: tokenAddress,
abi: erc721TransferAbi,
functionName: 'transferFrom',
args: [fromAddress, toAddress, tokenId],
account: walletClient.account!,
chain: walletClient.chain
});
// Get token metadata
const publicClient = getPublicClient(network);
const contract = getContract({
address: tokenAddress,
abi: erc721TransferAbi,
client: publicClient,
});
// Get token name and symbol
let name = 'Unknown';
let symbol = 'NFT';
try {
[name, symbol] = await Promise.all([
contract.read.name(),
contract.read.symbol()
]);
} catch (error) {
console.error('Error fetching NFT metadata:', error);
}
return {
txHash: hash,
tokenId: tokenId.toString(),
token: {
name,
symbol
}
};
}
/**
* Transfer ERC1155 tokens to an address
* @param tokenAddressOrEns Token contract address or ENS name
* @param toAddressOrEns Recipient address or ENS name
* @param tokenId Token ID to transfer
* @param amount Amount to transfer
* @param privateKey Owner's private key
* @param network Network name or chain ID
* @returns Transaction details
*/
export async function transferERC1155(
tokenAddressOrEns: string,
toAddressOrEns: string,
tokenId: bigint,
amount: string,
privateKey: string | `0x${string}`,
network: string = 'ethereum'
): Promise<{
txHash: Hash;
tokenId: string;
amount: string;
}> {
// Resolve ENS names to addresses if needed
const tokenAddress = await resolveAddress(tokenAddressOrEns, network) as Address;
const toAddress = await resolveAddress(toAddressOrEns, network) as Address;
// Ensure the private key has 0x prefix
const formattedKey = typeof privateKey === 'string' && !privateKey.startsWith('0x')
? `0x${privateKey}` as `0x${string}`
: privateKey as `0x${string}`;
// Create wallet client for sending the transaction
const walletClient = getWalletClient(formattedKey, network);
const fromAddress = walletClient.account!.address;
// Parse amount to bigint
const amountBigInt = BigInt(amount);
// Send the transaction
const hash = await walletClient.writeContract({
address: tokenAddress,
abi: erc1155TransferAbi,
functionName: 'safeTransferFrom',
args: [fromAddress, toAddress, tokenId, amountBigInt, '0x'],
account: walletClient.account!,
chain: walletClient.chain
});
return {
txHash: hash,
tokenId: tokenId.toString(),
amount
};
}