Skip to main content
Glama
index.ts6.61 kB
import { type Address, isAddress, encodeFunctionData, erc20Abi, parseUnits } from "viem"; import type { ToolRegistration } from "@/types/tools.js"; import { bigint, z } from "zod"; import { createTextResponse } from "@/types/tools.js"; import { getPublicClient } from "../../utils/evm/viem"; import { ERC20_TOKENS } from "../../utils/evm/supportedErc20Tokens"; import { getErc20TokenSchema, type GetErc20TokenSchema } from "./schema"; // import { flowEVMAccountABI } from '../abis/flowEVMAccount'; /** * Formats a token balance according to its decimals * @param balance The raw balance as a bigint * @param decimals The number of decimals for the token * @returns A formatted string representation of the balance */ function formatTokenBalance(balance: bigint, decimals: number): string { if (balance === 0n) return "0"; const balanceStr = balance.toString(); // If the balance is less than 10^decimals, we need to pad with leading zeros if (balanceStr.length <= decimals) { const paddedBalance = balanceStr.padStart(decimals + 1, "0"); const integerPart = paddedBalance.slice(0, -decimals) || "0"; const fractionalPart = paddedBalance.slice(-decimals).replace(/0+$/, ""); return fractionalPart ? `${integerPart}.${fractionalPart}` : integerPart; } else { const integerPart = balanceStr.slice(0, balanceStr.length - decimals); const fractionalPart = balanceStr.slice(balanceStr.length - decimals).replace(/0+$/, ""); return fractionalPart ? `${integerPart}.${fractionalPart}` : integerPart; } } /** * Tool for creating a serialized transaction to send FLOW tokens */ export const getErc20TokensTool = { name: "get_erc20_tokens", description: ` Get all erc20 tokens for an EVM address, the information includes the token name, symbol, contract address, decimals and balance. Only Wrapped Flow(WFLOW), Trump(TRUMP), HotCocoa, Gwendolion, Pawderick, Catseye are supported. Note: FLOW is not ERC20 token, you should use evm_tool to get the balance of FLOW`, inputSchema: getErc20TokenSchema, handler: async (params: GetErc20TokenSchema) => { try { const { address } = params; // Validate address format if (!isAddress(address)) { return createTextResponse(`Invalid address format: ${address}. Must be a valid 0x format address.`); } const publicClient = getPublicClient(); // Fetch balances for all tokens const tokenResults = await Promise.all( Object.entries(ERC20_TOKENS).map(async ([name, tokenInfo]) => { try { const balance = (await publicClient.readContract({ address: tokenInfo.contractAddress, abi: [ { name: "balanceOf", type: "function", inputs: [{ name: "owner", type: "address" }], outputs: [{ name: "balance", type: "uint256" }], stateMutability: "view", }, ], functionName: "balanceOf", args: [address as Address], })) as bigint; // Format balance according to decimals const formattedBalance = formatTokenBalance(balance, tokenInfo.decimals); return { ...tokenInfo, name, rawBalance: balance.toString(), balance: formattedBalance, }; } catch (error) { console.error(`Error fetching balance for ${name}:`, error); return { ...tokenInfo, name, rawBalance: "0", balance: "0", error: error instanceof Error ? error.message : String(error), }; } }), ); return createTextResponse(JSON.stringify(tokenResults, null, 2)); } catch (error) { return createTextResponse( `Error fetching ERC20 tokens: ${error instanceof Error ? error.message : String(error)}`, ); } }, }; export const transferErc20TokenTool = { name: "transfer_erc20_token", description: "Transfer an erc20 token to an address. You can query the address book to get the recipient address.", inputSchema: z.object({ token: z .string() .refine( (input) => { // Check if the input matches any token's name, symbol, or contract address return Object.entries(ERC20_TOKENS).some( ([key, token]) => key === input || token.symbol === input || token.name === input || token.contractAddress === input, ); }, { message: "Invalid token. Please provide a valid token name, symbol, or contract address", path: ["token"], }, ) .describe("The token to send (name, symbol, or contract address)"), to: z .string() .refine((addr) => isAddress(addr), { message: "Invalid address format", path: ["to"], }) .describe("The address to send the token to"), amount: z.string().describe("The amount of tokens to send"), }), handler: async (params: { token: string; to: string; amount: string }) => { try { const { token, to, amount } = params; // Validate address format const addressRegex = /^0x[a-fA-F0-9]{40}$/; if (!addressRegex.test(to)) { return createTextResponse(`Invalid recipient address format: ${to}. Must be a valid 0x format address.`); } // Find token info by checking name, symbol, or contract address let tokenInfo; for (const [key, info] of Object.entries(ERC20_TOKENS)) { if (key === token || info.symbol === token || info.name === token || info.contractAddress === token) { tokenInfo = info; break; } } if (!tokenInfo) { return createTextResponse( `Invalid token: ${token}. Supported tokens: WFLOW, TRUMP, HotCocoa, Gwendolion, Pawderick, Catseye, USDF`, ); } const transactionRequest = { to: tokenInfo.contractAddress, data: encodeFunctionData({ abi: erc20Abi, functionName: "transfer", args: [to as `0x${string}`, parseUnits(amount, tokenInfo.decimals)], }), }; const response = { tokenInfo, transactionRequest, type: "transfer_erc20_token", }; return createTextResponse(JSON.stringify(response)); } catch (error) { return createTextResponse(`Error sending erc20 token: ${error instanceof Error ? error.message : String(error)}`); } }, };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Outblock/flow-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server