Base, Arbitrum, Optimism, BSC, Polygon, Avalanche query support
This commit is contained in:
249
src/chains/ethereum.ts
Normal file
249
src/chains/ethereum.ts
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
import { JsonRpcProvider, formatEther, formatUnits, Contract } from 'ethers';
|
||||||
|
import { z } from "zod";
|
||||||
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
|
|
||||||
|
// Network configurations
|
||||||
|
interface NetworkConfig {
|
||||||
|
name: string;
|
||||||
|
rpc: string;
|
||||||
|
chainId: number;
|
||||||
|
currencySymbol: string;
|
||||||
|
explorer: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NETWORKS: { [key: string]: NetworkConfig } = {
|
||||||
|
ethereum: {
|
||||||
|
name: "Ethereum",
|
||||||
|
rpc: "https://eth-mainnet.g.alchemy.com/v2/demo",
|
||||||
|
chainId: 1,
|
||||||
|
currencySymbol: "ETH",
|
||||||
|
explorer: "https://etherscan.io"
|
||||||
|
},
|
||||||
|
base: {
|
||||||
|
name: "Base",
|
||||||
|
rpc: "https://mainnet.base.org",
|
||||||
|
chainId: 8453,
|
||||||
|
currencySymbol: "ETH",
|
||||||
|
explorer: "https://basescan.org"
|
||||||
|
},
|
||||||
|
arbitrum: {
|
||||||
|
name: "Arbitrum",
|
||||||
|
rpc: "https://arb1.arbitrum.io/rpc",
|
||||||
|
chainId: 42161,
|
||||||
|
currencySymbol: "ETH",
|
||||||
|
explorer: "https://arbiscan.io"
|
||||||
|
},
|
||||||
|
optimism: {
|
||||||
|
name: "Optimism",
|
||||||
|
rpc: "https://mainnet.optimism.io",
|
||||||
|
chainId: 10,
|
||||||
|
currencySymbol: "ETH",
|
||||||
|
explorer: "https://optimistic.etherscan.io"
|
||||||
|
},
|
||||||
|
bsc: {
|
||||||
|
name: "BNB Smart Chain",
|
||||||
|
rpc: "https://bsc-dataseed.binance.org",
|
||||||
|
chainId: 56,
|
||||||
|
currencySymbol: "BNB",
|
||||||
|
explorer: "https://bscscan.com"
|
||||||
|
},
|
||||||
|
polygon: {
|
||||||
|
name: "Polygon",
|
||||||
|
rpc: "https://polygon-rpc.com",
|
||||||
|
chainId: 137,
|
||||||
|
currencySymbol: "MATIC",
|
||||||
|
explorer: "https://polygonscan.com"
|
||||||
|
},
|
||||||
|
avalanche: {
|
||||||
|
name: "Avalanche",
|
||||||
|
rpc: "https://api.avax.network/ext/bc/C/rpc",
|
||||||
|
chainId: 43114,
|
||||||
|
currencySymbol: "AVAX",
|
||||||
|
explorer: "https://snowtrace.io"
|
||||||
|
},
|
||||||
|
tron: {
|
||||||
|
name: "Tron",
|
||||||
|
rpc: "https://api.trongrid.io",
|
||||||
|
chainId: 728126428, // TRON's chainId
|
||||||
|
currencySymbol: "TRX",
|
||||||
|
explorer: "https://tronscan.org"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize providers for each network
|
||||||
|
const providers: { [key: string]: JsonRpcProvider } = {};
|
||||||
|
for (const [network, config] of Object.entries(NETWORKS)) {
|
||||||
|
if (network !== 'tron') { // Tron needs special handling
|
||||||
|
providers[network] = new JsonRpcProvider(config.rpc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ERC-20 minimal ABI
|
||||||
|
const ERC20_ABI = [
|
||||||
|
"function balanceOf(address owner) view returns (uint256)",
|
||||||
|
"function decimals() view returns (uint8)",
|
||||||
|
"function symbol() view returns (string)"
|
||||||
|
];
|
||||||
|
|
||||||
|
export function registerEthereumTools(server: McpServer) {
|
||||||
|
// Get native token balance for any EVM network
|
||||||
|
server.tool(
|
||||||
|
"getEvmBalance",
|
||||||
|
"Get native token balance for an EVM address on any supported network",
|
||||||
|
{
|
||||||
|
address: z.string().describe("EVM account address"),
|
||||||
|
network: z.string().describe("Network name (ethereum, base, arbitrum, optimism, bsc, polygon, avalanche)"),
|
||||||
|
},
|
||||||
|
async ({ address, network }) => {
|
||||||
|
try {
|
||||||
|
if (!NETWORKS[network]) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Unsupported network: ${network}. Supported networks are: ${Object.keys(NETWORKS).join(", ")}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = providers[network];
|
||||||
|
const balance = await provider.getBalance(address);
|
||||||
|
const formattedBalance = formatEther(balance);
|
||||||
|
const networkConfig = NETWORKS[network];
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Balance on ${networkConfig.name}:\n${formattedBalance} ${networkConfig.currencySymbol}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error;
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Failed to retrieve balance: ${error.message}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get ERC-20 token balance for any EVM network
|
||||||
|
server.tool(
|
||||||
|
"getEvmTokenBalance",
|
||||||
|
"Get ERC-20 token balance for an address on any supported EVM network",
|
||||||
|
{
|
||||||
|
address: z.string().describe("EVM account address"),
|
||||||
|
tokenAddress: z.string().describe("ERC-20 token contract address"),
|
||||||
|
network: z.string().describe("Network name (ethereum, base, arbitrum, optimism, bsc, polygon, avalanche)"),
|
||||||
|
},
|
||||||
|
async ({ address, tokenAddress, network }) => {
|
||||||
|
try {
|
||||||
|
if (!NETWORKS[network]) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Unsupported network: ${network}. Supported networks are: ${Object.keys(NETWORKS).join(", ")}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = providers[network];
|
||||||
|
const contract = new Contract(tokenAddress, ERC20_ABI, provider);
|
||||||
|
|
||||||
|
const [balance, decimals, symbol] = await Promise.all([
|
||||||
|
contract.balanceOf(address),
|
||||||
|
contract.decimals(),
|
||||||
|
contract.symbol()
|
||||||
|
]);
|
||||||
|
|
||||||
|
const formattedBalance = formatUnits(balance, decimals);
|
||||||
|
const networkConfig = NETWORKS[network];
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Token Balance on ${networkConfig.name}:\n${formattedBalance} ${symbol} (${tokenAddress})\nExplorer: ${networkConfig.explorer}/token/${tokenAddress}`
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error;
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Failed to retrieve token balance: ${error.message}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get gas price for any EVM network
|
||||||
|
server.tool(
|
||||||
|
"getGasPrice",
|
||||||
|
"Get current gas price for any supported EVM network",
|
||||||
|
{
|
||||||
|
network: z.string().describe("Network name (ethereum, base, arbitrum, optimism, bsc, polygon, avalanche)"),
|
||||||
|
},
|
||||||
|
async ({ network }) => {
|
||||||
|
try {
|
||||||
|
if (!NETWORKS[network]) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Unsupported network: ${network}. Supported networks are: ${Object.keys(NETWORKS).join(", ")}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const provider = providers[network];
|
||||||
|
const feeData = await provider.getFeeData();
|
||||||
|
const gasPrice = formatUnits(feeData.gasPrice || 0, 'gwei');
|
||||||
|
const maxFeePerGas = feeData.maxFeePerGas ? formatUnits(feeData.maxFeePerGas, 'gwei') : null;
|
||||||
|
const maxPriorityFeePerGas = feeData.maxPriorityFeePerGas ? formatUnits(feeData.maxPriorityFeePerGas, 'gwei') : null;
|
||||||
|
const networkConfig = NETWORKS[network];
|
||||||
|
|
||||||
|
let response = `Gas Prices on ${networkConfig.name}:\nGas Price: ${gasPrice} Gwei`;
|
||||||
|
if (maxFeePerGas) {
|
||||||
|
response += `\nMax Fee: ${maxFeePerGas} Gwei`;
|
||||||
|
}
|
||||||
|
if (maxPriorityFeePerGas) {
|
||||||
|
response += `\nMax Priority Fee: ${maxPriorityFeePerGas} Gwei`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: response,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error;
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Failed to retrieve gas price: ${error.message}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
396
src/chains/solana.ts
Normal file
396
src/chains/solana.ts
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
import {
|
||||||
|
Connection,
|
||||||
|
PublicKey,
|
||||||
|
Keypair,
|
||||||
|
Transaction,
|
||||||
|
SystemProgram,
|
||||||
|
sendAndConfirmTransaction,
|
||||||
|
LAMPORTS_PER_SOL,
|
||||||
|
} from '@solana/web3.js';
|
||||||
|
import {
|
||||||
|
TOKEN_PROGRAM_ID,
|
||||||
|
AccountLayout,
|
||||||
|
} from '@solana/spl-token';
|
||||||
|
import { z } from "zod";
|
||||||
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
|
import bs58 from 'bs58';
|
||||||
|
|
||||||
|
const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
||||||
|
|
||||||
|
// Initialize Solana connection
|
||||||
|
const solanaConnection = new Connection(SOLANA_RPC, 'confirmed');
|
||||||
|
|
||||||
|
// Helper function to get token accounts for a wallet
|
||||||
|
async function getTokenAccounts(walletAddress: string) {
|
||||||
|
try {
|
||||||
|
const owner = new PublicKey(walletAddress);
|
||||||
|
const tokenAccounts = await solanaConnection.getParsedTokenAccountsByOwner(
|
||||||
|
owner,
|
||||||
|
{
|
||||||
|
programId: TOKEN_PROGRAM_ID
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return tokenAccounts.value.map(account => {
|
||||||
|
const parsedAccountInfo = account.account.data.parsed.info;
|
||||||
|
return {
|
||||||
|
mint: new PublicKey(parsedAccountInfo.mint),
|
||||||
|
amount: parsedAccountInfo.tokenAmount.uiAmount,
|
||||||
|
decimals: parsedAccountInfo.tokenAmount.decimals,
|
||||||
|
tokenAccount: account.pubkey.toString()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching token accounts:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerSolanaTools(server: McpServer) {
|
||||||
|
server.tool(
|
||||||
|
"getSlot",
|
||||||
|
"Get the current slot",
|
||||||
|
{},
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
const slot = await solanaConnection.getSlot();
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Current slot: ${slot}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error;
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Failed to retrieve current slot: ${error.message}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
"getBalance",
|
||||||
|
"Get balance for a Solana address",
|
||||||
|
{
|
||||||
|
address: z.string().describe("Solana account address"),
|
||||||
|
},
|
||||||
|
async ({ address }) => {
|
||||||
|
try {
|
||||||
|
const publicKey = new PublicKey(address);
|
||||||
|
const balance = await solanaConnection.getBalance(publicKey);
|
||||||
|
const solBalance = balance / LAMPORTS_PER_SOL;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Balance for ${address}:\n${solBalance} SOL`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error;
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Failed to retrieve balance for address: ${error.message}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
"getKeypairInfo",
|
||||||
|
"Get information about a keypair from its secret key",
|
||||||
|
{
|
||||||
|
secretKey: z.string().describe("Base58 encoded secret key or array of bytes"),
|
||||||
|
},
|
||||||
|
async ({ secretKey }) => {
|
||||||
|
try {
|
||||||
|
let keypair: Keypair;
|
||||||
|
try {
|
||||||
|
const decoded = Uint8Array.from(secretKey.split(',').map(num => parseInt(num.trim())));
|
||||||
|
keypair = Keypair.fromSecretKey(decoded);
|
||||||
|
} catch {
|
||||||
|
keypair = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(secretKey)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const publicKey = keypair.publicKey;
|
||||||
|
const balance = await solanaConnection.getBalance(publicKey);
|
||||||
|
const accountInfo = await solanaConnection.getAccountInfo(publicKey);
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Keypair Information:
|
||||||
|
Public Key: ${publicKey.toBase58()}
|
||||||
|
Balance: ${balance / LAMPORTS_PER_SOL} SOL
|
||||||
|
Account Program Owner: ${accountInfo?.owner?.toBase58() || 'N/A'}
|
||||||
|
Account Size: ${accountInfo?.data.length || 0} bytes
|
||||||
|
Is Executable: ${accountInfo?.executable || false}
|
||||||
|
Rent Epoch: ${accountInfo?.rentEpoch || 0}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error;
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Failed to retrieve keypair information: ${error.message}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
"getAccountInfo",
|
||||||
|
"Get detailed account information for a Solana address",
|
||||||
|
{
|
||||||
|
address: z.string().describe("Solana account address"),
|
||||||
|
encoding: z.enum(['base58', 'base64', 'jsonParsed']).optional().describe("Data encoding format"),
|
||||||
|
},
|
||||||
|
async ({ address, encoding = 'base64' }) => {
|
||||||
|
try {
|
||||||
|
const publicKey = new PublicKey(address);
|
||||||
|
const accountInfo = await solanaConnection.getAccountInfo(
|
||||||
|
publicKey,
|
||||||
|
'confirmed'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!accountInfo) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `No account found for address: ${address}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let formattedData: string;
|
||||||
|
if (encoding === 'base58') {
|
||||||
|
formattedData = bs58.encode(accountInfo.data);
|
||||||
|
} else if (encoding === 'base64') {
|
||||||
|
formattedData = Buffer.from(accountInfo.data).toString('base64');
|
||||||
|
} else {
|
||||||
|
formattedData = Buffer.from(accountInfo.data).toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Account Information for ${address}:
|
||||||
|
Lamports: ${accountInfo.lamports} (${accountInfo.lamports / LAMPORTS_PER_SOL} SOL)
|
||||||
|
Owner: ${accountInfo.owner.toBase58()}
|
||||||
|
Executable: ${accountInfo.executable}
|
||||||
|
Rent Epoch: ${accountInfo.rentEpoch}
|
||||||
|
Data Length: ${accountInfo.data.length} bytes
|
||||||
|
Data (${encoding}): ${formattedData}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error;
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Failed to retrieve account information: ${error.message}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
"transfer",
|
||||||
|
"Transfer SOL from your keypair to another address",
|
||||||
|
{
|
||||||
|
secretKey: z.string().describe("Your keypair's secret key (as comma-separated numbers or JSON array)"),
|
||||||
|
toAddress: z.string().describe("Destination wallet address"),
|
||||||
|
amount: z.number().positive().describe("Amount of SOL to send"),
|
||||||
|
},
|
||||||
|
async ({ secretKey, toAddress, amount }) => {
|
||||||
|
try {
|
||||||
|
let fromKeypair: Keypair;
|
||||||
|
try {
|
||||||
|
const decoded = Uint8Array.from(secretKey.split(',').map(num => parseInt(num.trim())));
|
||||||
|
fromKeypair = Keypair.fromSecretKey(decoded);
|
||||||
|
} catch {
|
||||||
|
fromKeypair = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(secretKey)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const lamports = amount * LAMPORTS_PER_SOL;
|
||||||
|
|
||||||
|
const transaction = new Transaction().add(
|
||||||
|
SystemProgram.transfer({
|
||||||
|
fromPubkey: fromKeypair.publicKey,
|
||||||
|
toPubkey: new PublicKey(toAddress),
|
||||||
|
lamports,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const signature = await sendAndConfirmTransaction(
|
||||||
|
solanaConnection,
|
||||||
|
transaction,
|
||||||
|
[fromKeypair]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Transfer successful!
|
||||||
|
From: ${fromKeypair.publicKey.toBase58()}
|
||||||
|
To: ${toAddress}
|
||||||
|
Amount: ${amount} SOL
|
||||||
|
Transaction signature: ${signature}
|
||||||
|
Explorer URL: https://explorer.solana.com/tx/${signature}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error;
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Failed to transfer SOL: ${error.message}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
"getSplTokenBalances",
|
||||||
|
"Get SPL token balances for a Solana address",
|
||||||
|
{
|
||||||
|
address: z.string().describe("Solana account address"),
|
||||||
|
},
|
||||||
|
async ({ address }) => {
|
||||||
|
try {
|
||||||
|
const tokenAccounts = await getTokenAccounts(address);
|
||||||
|
|
||||||
|
if (tokenAccounts.length === 0) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `No token accounts found for address: ${address}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const balancesList = tokenAccounts
|
||||||
|
.filter(account => account.amount > 0)
|
||||||
|
.map(account => `Mint: ${account.mint.toString()}\nBalance: ${account.amount}\nDecimals: ${account.decimals}\nToken Account: ${account.tokenAccount}`)
|
||||||
|
.join('\n\n');
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Token Balances for ${address}:\n\n${balancesList}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error;
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Failed to retrieve token balances: ${error.message}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
"getSplTokenInfo",
|
||||||
|
"Get detailed information about a specific SPL token account",
|
||||||
|
{
|
||||||
|
tokenMint: z.string().describe("Token mint address"),
|
||||||
|
ownerAddress: z.string().describe("Token account owner address"),
|
||||||
|
},
|
||||||
|
async ({ tokenMint, ownerAddress }) => {
|
||||||
|
try {
|
||||||
|
const mint = new PublicKey(tokenMint);
|
||||||
|
const owner = new PublicKey(ownerAddress);
|
||||||
|
|
||||||
|
const tokenAccounts = await solanaConnection.getParsedTokenAccountsByOwner(
|
||||||
|
owner,
|
||||||
|
{
|
||||||
|
mint: mint,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tokenAccounts.value.length === 0) {
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `No token account found for mint ${tokenMint} owned by ${ownerAddress}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountInfo = tokenAccounts.value[0].account;
|
||||||
|
const parsedInfo = accountInfo.data.parsed.info;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Token Account Information:
|
||||||
|
Token Account: ${tokenAccounts.value[0].pubkey.toString()}
|
||||||
|
Mint: ${parsedInfo.mint}
|
||||||
|
Owner: ${parsedInfo.owner}
|
||||||
|
Balance: ${parsedInfo.tokenAmount.uiAmount}
|
||||||
|
Decimals: ${parsedInfo.tokenAmount.decimals}
|
||||||
|
State: ${parsedInfo.state}
|
||||||
|
Is Native: ${parsedInfo.isNative}
|
||||||
|
Delegation: ${parsedInfo.delegate || 'None'}
|
||||||
|
Close Authority: ${parsedInfo.closeAuthority || 'None'}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as Error;
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text",
|
||||||
|
text: `Failed to retrieve token information: ${error.message}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
502
src/index.ts
502
src/index.ts
@@ -1,31 +1,7 @@
|
|||||||
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
import {
|
import { registerSolanaTools } from "./chains/solana.js";
|
||||||
Connection,
|
import { registerEthereumTools } from "./chains/ethereum.js";
|
||||||
PublicKey,
|
|
||||||
Keypair,
|
|
||||||
Transaction,
|
|
||||||
SystemProgram,
|
|
||||||
sendAndConfirmTransaction,
|
|
||||||
LAMPORTS_PER_SOL,
|
|
||||||
} from '@solana/web3.js';
|
|
||||||
import {
|
|
||||||
TOKEN_PROGRAM_ID,
|
|
||||||
AccountLayout,
|
|
||||||
} from '@solana/spl-token';
|
|
||||||
import { z } from "zod";
|
|
||||||
import bs58 from 'bs58';
|
|
||||||
import { JsonRpcProvider, formatEther, formatUnits, Contract, Interface } from 'ethers';
|
|
||||||
|
|
||||||
const SOLANA_RPC = "https://api.mainnet-beta.solana.com";
|
|
||||||
const ETH_RPC = "https://eth-mainnet.g.alchemy.com/v2/demo";
|
|
||||||
|
|
||||||
// ERC-20 minimal ABI for balance checking
|
|
||||||
const ERC20_ABI = [
|
|
||||||
"function balanceOf(address owner) view returns (uint256)",
|
|
||||||
"function decimals() view returns (uint8)",
|
|
||||||
"function symbol() view returns (string)"
|
|
||||||
];
|
|
||||||
|
|
||||||
// Create server instance
|
// Create server instance
|
||||||
const server = new McpServer({
|
const server = new McpServer({
|
||||||
@@ -33,450 +9,9 @@ const server = new McpServer({
|
|||||||
version: "1.0.0",
|
version: "1.0.0",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize blockchain connections
|
// Register chain-specific tools
|
||||||
const solanaConnection = new Connection(SOLANA_RPC, 'confirmed');
|
registerSolanaTools(server);
|
||||||
const ethProvider = new JsonRpcProvider(ETH_RPC);
|
registerEthereumTools(server);
|
||||||
|
|
||||||
// Register Solana tools
|
|
||||||
server.tool(
|
|
||||||
"getSlot",
|
|
||||||
"Get the current slot",
|
|
||||||
{},
|
|
||||||
async () => {
|
|
||||||
try {
|
|
||||||
const slot = await solanaConnection.getSlot();
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Current slot: ${slot}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Failed to retrieve current slot: ${error.message}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
server.tool(
|
|
||||||
"getBalance",
|
|
||||||
"Get balance for a Solana address",
|
|
||||||
{
|
|
||||||
address: z.string().describe("Solana account address"),
|
|
||||||
},
|
|
||||||
async ({ address }) => {
|
|
||||||
try {
|
|
||||||
const publicKey = new PublicKey(address);
|
|
||||||
const balance = await solanaConnection.getBalance(publicKey);
|
|
||||||
const solBalance = balance / LAMPORTS_PER_SOL;
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Balance for ${address}:\n${solBalance} SOL`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Failed to retrieve balance for address: ${error.message}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
server.tool(
|
|
||||||
"getKeypairInfo",
|
|
||||||
"Get information about a keypair from its secret key",
|
|
||||||
{
|
|
||||||
secretKey: z.string().describe("Base58 encoded secret key or array of bytes"),
|
|
||||||
},
|
|
||||||
async ({ secretKey }) => {
|
|
||||||
try {
|
|
||||||
// Handle both base58 encoded strings and byte arrays
|
|
||||||
let keypair: Keypair;
|
|
||||||
try {
|
|
||||||
// First try parsing as comma-separated string
|
|
||||||
const decoded = Uint8Array.from(secretKey.split(',').map(num => parseInt(num.trim())));
|
|
||||||
keypair = Keypair.fromSecretKey(decoded);
|
|
||||||
} catch {
|
|
||||||
// If that fails, try as a byte array string
|
|
||||||
keypair = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(secretKey)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get account info and balance
|
|
||||||
const publicKey = keypair.publicKey;
|
|
||||||
const balance = await solanaConnection.getBalance(publicKey);
|
|
||||||
const accountInfo = await solanaConnection.getAccountInfo(publicKey);
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Keypair Information:
|
|
||||||
Public Key: ${publicKey.toBase58()}
|
|
||||||
Balance: ${balance / LAMPORTS_PER_SOL} SOL
|
|
||||||
Account Program Owner: ${accountInfo?.owner?.toBase58() || 'N/A'}
|
|
||||||
Account Size: ${accountInfo?.data.length || 0} bytes
|
|
||||||
Is Executable: ${accountInfo?.executable || false}
|
|
||||||
Rent Epoch: ${accountInfo?.rentEpoch || 0}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Failed to retrieve keypair information: ${error.message}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
server.tool(
|
|
||||||
"getAccountInfo",
|
|
||||||
"Get detailed account information for a Solana address",
|
|
||||||
{
|
|
||||||
address: z.string().describe("Solana account address"),
|
|
||||||
encoding: z.enum(['base58', 'base64', 'jsonParsed']).optional().describe("Data encoding format"),
|
|
||||||
},
|
|
||||||
async ({ address, encoding = 'base64' }) => {
|
|
||||||
try {
|
|
||||||
const publicKey = new PublicKey(address);
|
|
||||||
const accountInfo = await solanaConnection.getAccountInfo(
|
|
||||||
publicKey,
|
|
||||||
'confirmed'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!accountInfo) {
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `No account found for address: ${address}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format the data based on encoding
|
|
||||||
let formattedData: string;
|
|
||||||
if (encoding === 'base58') {
|
|
||||||
formattedData = bs58.encode(accountInfo.data);
|
|
||||||
} else if (encoding === 'base64') {
|
|
||||||
formattedData = Buffer.from(accountInfo.data).toString('base64');
|
|
||||||
} else {
|
|
||||||
// For jsonParsed, we'll still return base64 but note that it's not parsed
|
|
||||||
formattedData = Buffer.from(accountInfo.data).toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Account Information for ${address}:
|
|
||||||
Lamports: ${accountInfo.lamports} (${accountInfo.lamports / LAMPORTS_PER_SOL} SOL)
|
|
||||||
Owner: ${accountInfo.owner.toBase58()}
|
|
||||||
Executable: ${accountInfo.executable}
|
|
||||||
Rent Epoch: ${accountInfo.rentEpoch}
|
|
||||||
Data Length: ${accountInfo.data.length} bytes
|
|
||||||
Data (${encoding}): ${formattedData}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Failed to retrieve account information: ${error.message}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
server.tool(
|
|
||||||
"transfer",
|
|
||||||
"Transfer SOL from your keypair to another address",
|
|
||||||
{
|
|
||||||
secretKey: z.string().describe("Your keypair's secret key (as comma-separated numbers or JSON array)"),
|
|
||||||
toAddress: z.string().describe("Destination wallet address"),
|
|
||||||
amount: z.number().positive().describe("Amount of SOL to send"),
|
|
||||||
},
|
|
||||||
async ({ secretKey, toAddress, amount }) => {
|
|
||||||
try {
|
|
||||||
// Parse the secret key and create keypair
|
|
||||||
let fromKeypair: Keypair;
|
|
||||||
try {
|
|
||||||
// First try parsing as comma-separated string
|
|
||||||
const decoded = Uint8Array.from(secretKey.split(',').map(num => parseInt(num.trim())));
|
|
||||||
fromKeypair = Keypair.fromSecretKey(decoded);
|
|
||||||
} catch {
|
|
||||||
// If that fails, try as a JSON array string
|
|
||||||
fromKeypair = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(secretKey)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert SOL amount to lamports
|
|
||||||
const lamports = amount * LAMPORTS_PER_SOL;
|
|
||||||
|
|
||||||
// Create transfer instruction
|
|
||||||
const transaction = new Transaction().add(
|
|
||||||
SystemProgram.transfer({
|
|
||||||
fromPubkey: fromKeypair.publicKey,
|
|
||||||
toPubkey: new PublicKey(toAddress),
|
|
||||||
lamports,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Send and confirm transaction
|
|
||||||
const signature = await sendAndConfirmTransaction(
|
|
||||||
solanaConnection,
|
|
||||||
transaction,
|
|
||||||
[fromKeypair]
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Transfer successful!
|
|
||||||
From: ${fromKeypair.publicKey.toBase58()}
|
|
||||||
To: ${toAddress}
|
|
||||||
Amount: ${amount} SOL
|
|
||||||
Transaction signature: ${signature}
|
|
||||||
Explorer URL: https://explorer.solana.com/tx/${signature}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Failed to transfer SOL: ${error.message}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register Ethereum tools
|
|
||||||
server.tool(
|
|
||||||
"getEthBalance",
|
|
||||||
"Get balance for an Ethereum address",
|
|
||||||
{
|
|
||||||
address: z.string().describe("Ethereum account address"),
|
|
||||||
},
|
|
||||||
async ({ address }) => {
|
|
||||||
try {
|
|
||||||
const balance = await ethProvider.getBalance(address);
|
|
||||||
const ethBalance = formatEther(balance); // Convert from wei to ETH
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Balance for ${address}:\n${ethBalance} ETH`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Failed to retrieve ETH balance: ${error.message}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register token balance checking tool
|
|
||||||
server.tool(
|
|
||||||
"getTokenBalance",
|
|
||||||
"Get ERC-20 token balance for an Ethereum address",
|
|
||||||
{
|
|
||||||
address: z.string().describe("Ethereum account address"),
|
|
||||||
tokenAddress: z.string().describe("ERC-20 token contract address")
|
|
||||||
},
|
|
||||||
async ({ address, tokenAddress }) => {
|
|
||||||
try {
|
|
||||||
// Create contract interface
|
|
||||||
const contract = new Contract(tokenAddress, ERC20_ABI, ethProvider);
|
|
||||||
|
|
||||||
// Get token details and balance
|
|
||||||
const [balance, decimals, symbol] = await Promise.all([
|
|
||||||
contract.balanceOf(address),
|
|
||||||
contract.decimals(),
|
|
||||||
contract.symbol()
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Format the balance using the correct number of decimals
|
|
||||||
const formattedBalance = formatUnits(balance, decimals);
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Token Balance for ${address}:\n${formattedBalance} ${symbol} (${tokenAddress})`
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Failed to retrieve token balance: ${error.message}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
server.tool(
|
|
||||||
"getSplTokenBalances",
|
|
||||||
"Get SPL token balances for a Solana address",
|
|
||||||
{
|
|
||||||
address: z.string().describe("Solana account address"),
|
|
||||||
},
|
|
||||||
async ({ address }) => {
|
|
||||||
try {
|
|
||||||
const tokenAccounts = await getTokenAccounts(address);
|
|
||||||
|
|
||||||
if (tokenAccounts.length === 0) {
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `No token accounts found for address: ${address}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create formatted output of all token balances
|
|
||||||
const balancesList = tokenAccounts
|
|
||||||
.filter(account => account.amount > 0) // Only show non-zero balances
|
|
||||||
.map(account => `Mint: ${account.mint.toString()}\nBalance: ${account.amount}\nDecimals: ${account.decimals}\nToken Account: ${account.tokenAccount}`)
|
|
||||||
.join('\n\n');
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Token Balances for ${address}:\n\n${balancesList}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Failed to retrieve token balances: ${error.message}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
server.tool(
|
|
||||||
"getSplTokenInfo",
|
|
||||||
"Get detailed information about a specific SPL token account",
|
|
||||||
{
|
|
||||||
tokenMint: z.string().describe("Token mint address"),
|
|
||||||
ownerAddress: z.string().describe("Token account owner address"),
|
|
||||||
},
|
|
||||||
async ({ tokenMint, ownerAddress }) => {
|
|
||||||
try {
|
|
||||||
const mint = new PublicKey(tokenMint);
|
|
||||||
const owner = new PublicKey(ownerAddress);
|
|
||||||
|
|
||||||
// Find the token account
|
|
||||||
const tokenAccounts = await solanaConnection.getParsedTokenAccountsByOwner(
|
|
||||||
owner,
|
|
||||||
{
|
|
||||||
mint: mint,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tokenAccounts.value.length === 0) {
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `No token account found for mint ${tokenMint} owned by ${ownerAddress}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountInfo = tokenAccounts.value[0].account;
|
|
||||||
const parsedInfo = accountInfo.data.parsed.info;
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Token Account Information:
|
|
||||||
Token Account: ${tokenAccounts.value[0].pubkey.toString()}
|
|
||||||
Mint: ${parsedInfo.mint}
|
|
||||||
Owner: ${parsedInfo.owner}
|
|
||||||
Balance: ${parsedInfo.tokenAmount.uiAmount}
|
|
||||||
Decimals: ${parsedInfo.tokenAmount.decimals}
|
|
||||||
State: ${parsedInfo.state}
|
|
||||||
Is Native: ${parsedInfo.isNative}
|
|
||||||
Delegation: ${parsedInfo.delegate || 'None'}
|
|
||||||
Close Authority: ${parsedInfo.closeAuthority || 'None'}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} catch (err) {
|
|
||||||
const error = err as Error;
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: "text",
|
|
||||||
text: `Failed to retrieve token information: ${error.message}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const transport = new StdioServerTransport();
|
const transport = new StdioServerTransport();
|
||||||
@@ -489,30 +24,3 @@ main().catch((err: unknown) => {
|
|||||||
console.error("Fatal error in main():", error.message);
|
console.error("Fatal error in main():", error.message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// Helper function to get token accounts for a wallet
|
|
||||||
async function getTokenAccounts(walletAddress: string) {
|
|
||||||
try {
|
|
||||||
const owner = new PublicKey(walletAddress);
|
|
||||||
const tokenAccounts = await solanaConnection.getParsedTokenAccountsByOwner(
|
|
||||||
owner,
|
|
||||||
{
|
|
||||||
programId: TOKEN_PROGRAM_ID
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return tokenAccounts.value.map(account => {
|
|
||||||
const parsedAccountInfo = account.account.data.parsed.info;
|
|
||||||
return {
|
|
||||||
mint: new PublicKey(parsedAccountInfo.mint),
|
|
||||||
amount: parsedAccountInfo.tokenAmount.uiAmount,
|
|
||||||
decimals: parsedAccountInfo.tokenAmount.decimals,
|
|
||||||
tokenAccount: account.pubkey.toString()
|
|
||||||
};
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching token accounts:', error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "es2020",
|
||||||
"module": "ES2020",
|
"module": "es2020",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"outDir": "./build",
|
"allowSyntheticDefaultImports": true,
|
||||||
"rootDir": "./src",
|
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"strict": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"outDir": "build",
|
||||||
"resolveJsonModule": true,
|
"rootDir": "src"
|
||||||
"allowJs": true
|
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules", "build"]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user