Skip to main content
Glama

tokens_send_ft

Send fungible tokens (FT) like USDC, USDT, or WNEAR between accounts on the NEAR blockchain. Specify sender, receiver, contract ID, and amount for secure token transfers following NEP-141 and NEP-148 standards.

Instructions

Send Fungible Tokens (FT) like USDC native, USDT, WNEAR, etc. based on the NEP-141 and NEP-148 standards to an account. The signer account is the sender of the tokens, and the receiver account is the recipient of the tokens. Ensure the contract account id exists and is in the same network as the signer and receiver accounts.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
amountYesThe amount of tokens to send in the fungible token contract. e.g. 1 USDC, 0.33 USDT, 1.5 WNEAR, etc.
fungibleTokenContractAccountIdYesThe account id of the fungible token contract. Ensure the contract account id exists and is in the same network as the signer and receiver accounts.
networkIdNomainnet
receiverAccountIdYesThe account that will receive the tokens.
signerAccountIdYesThe account that will send the tokens.

Implementation Reference

  • Handler function that performs the FT transfer: validates FT contract metadata, adjusts amount for decimals, calls ft_transfer on the token contract from the signer account.
    async (args, _) => {
      const connection = await connect({
        networkId: args.networkId,
        keyStore: keystore,
        nodeUrl: getEndpointsByNetwork(args.networkId)[0]!,
      });
    
      // check that the fungible token contract exists by getting
      // the metadata of the contract
      const fungibleTokenContractMetadataResult: Result<
        FungibleTokenMetadata,
        Error
      > = await getFungibleTokenContractMetadataResult(
        args.fungibleTokenContractAccountId,
        connection,
      );
      if (!fungibleTokenContractMetadataResult.ok) {
        return {
          content: [
            {
              type: 'text',
              text: `Error: ${fungibleTokenContractMetadataResult.error}`,
            },
          ],
        };
      }
    
      // convert the amount into the decimals of the fungible token
      const amountInDecimals = BigInt(
        args.amount * 10 ** fungibleTokenContractMetadataResult.value.decimals,
      );
    
      // call the transfer function of the fungible token contract
      const transferResult: Result<FinalExecutionOutcome, Error> =
        await (async () => {
          try {
            const fungibleTokenContractResult = await getAccount(
              args.fungibleTokenContractAccountId,
              connection,
            );
            if (!fungibleTokenContractResult.ok) {
              return fungibleTokenContractResult;
            }
            const fungibleTokenContract = fungibleTokenContractResult.value;
    
            const senderAccount = await connection.account(
              args.signerAccountId,
            );
            const receiverAccount = await connection.account(
              args.receiverAccountId,
            );
    
            return {
              ok: true,
              value: await senderAccount.functionCall({
                contractId: fungibleTokenContract.accountId,
                methodName: 'ft_transfer',
                args: {
                  receiver_id: receiverAccount.accountId,
                  amount: amountInDecimals.toString(),
                },
                gas: DEFAULT_GAS,
                attachedDeposit:
                  NearToken.parse_yocto_near('1').as_yocto_near(),
              }),
            };
          } catch (e) {
            return { ok: false, error: new Error(e as string) };
          }
        })();
      if (!transferResult.ok) {
        return {
          content: [{ type: 'text', text: `Error: ${transferResult.error}` }],
        };
      }
    
      return {
        content: [
          {
            type: 'text',
            text: `Transaction sent: ${stringify_bigint(transferResult.value)}`,
          },
        ],
      };
    },
  • Registration of the 'tokens_send_ft' MCP tool with description, input schema, and handler.
    mcp.tool(
      'tokens_send_ft',
      noLeadingWhitespace`
      Send Fungible Tokens (FT) like USDC native, USDT, WNEAR, etc. based on the NEP-141 and NEP-148 standards to an account.
      The signer account is the sender of the tokens, and the receiver account is the
      recipient of the tokens. Ensure the contract account id exists and is in the same network as the signer and receiver accounts.`,
      {
        signerAccountId: z
          .string()
          .describe('The account that will send the tokens.'),
        receiverAccountId: z
          .string()
          .describe('The account that will receive the tokens.'),
        networkId: z.enum(['mainnet']).default('mainnet'),
        fungibleTokenContractAccountId: z
          .string()
          .describe(
            'The account id of the fungible token contract. Ensure the contract account id exists and is in the same network as the signer and receiver accounts.',
          ),
        amount: z
          .number()
          .describe(
            'The amount of tokens to send in the fungible token contract. e.g. 1 USDC, 0.33 USDT, 1.5 WNEAR, etc.',
          ),
      },
      async (args, _) => {
        const connection = await connect({
          networkId: args.networkId,
          keyStore: keystore,
          nodeUrl: getEndpointsByNetwork(args.networkId)[0]!,
        });
    
        // check that the fungible token contract exists by getting
        // the metadata of the contract
        const fungibleTokenContractMetadataResult: Result<
          FungibleTokenMetadata,
          Error
        > = await getFungibleTokenContractMetadataResult(
          args.fungibleTokenContractAccountId,
          connection,
        );
        if (!fungibleTokenContractMetadataResult.ok) {
          return {
            content: [
              {
                type: 'text',
                text: `Error: ${fungibleTokenContractMetadataResult.error}`,
              },
            ],
          };
        }
    
        // convert the amount into the decimals of the fungible token
        const amountInDecimals = BigInt(
          args.amount * 10 ** fungibleTokenContractMetadataResult.value.decimals,
        );
    
        // call the transfer function of the fungible token contract
        const transferResult: Result<FinalExecutionOutcome, Error> =
          await (async () => {
            try {
              const fungibleTokenContractResult = await getAccount(
                args.fungibleTokenContractAccountId,
                connection,
              );
              if (!fungibleTokenContractResult.ok) {
                return fungibleTokenContractResult;
              }
              const fungibleTokenContract = fungibleTokenContractResult.value;
    
              const senderAccount = await connection.account(
                args.signerAccountId,
              );
              const receiverAccount = await connection.account(
                args.receiverAccountId,
              );
    
              return {
                ok: true,
                value: await senderAccount.functionCall({
                  contractId: fungibleTokenContract.accountId,
                  methodName: 'ft_transfer',
                  args: {
                    receiver_id: receiverAccount.accountId,
                    amount: amountInDecimals.toString(),
                  },
                  gas: DEFAULT_GAS,
                  attachedDeposit:
                    NearToken.parse_yocto_near('1').as_yocto_near(),
                }),
              };
            } catch (e) {
              return { ok: false, error: new Error(e as string) };
            }
          })();
        if (!transferResult.ok) {
          return {
            content: [{ type: 'text', text: `Error: ${transferResult.error}` }],
          };
        }
    
        return {
          content: [
            {
              type: 'text',
              text: `Transaction sent: ${stringify_bigint(transferResult.value)}`,
            },
          ],
        };
      },
    );
  • Zod input schema for tokens_send_ft tool defining parameters: signerAccountId, receiverAccountId, networkId (mainnet only), fungibleTokenContractAccountId, amount (number).
    {
      signerAccountId: z
        .string()
        .describe('The account that will send the tokens.'),
      receiverAccountId: z
        .string()
        .describe('The account that will receive the tokens.'),
      networkId: z.enum(['mainnet']).default('mainnet'),
      fungibleTokenContractAccountId: z
        .string()
        .describe(
          'The account id of the fungible token contract. Ensure the contract account id exists and is in the same network as the signer and receiver accounts.',
        ),
      amount: z
        .number()
        .describe(
          'The amount of tokens to send in the fungible token contract. e.g. 1 USDC, 0.33 USDT, 1.5 WNEAR, etc.',
        ),
    },
  • Helper function to fetch and parse ft_metadata from FT contract, used to get decimals for amount conversion.
    export const getFungibleTokenContractMetadataResult = async (
      fungibleTokenContractId: string,
      connection: Near,
    ): Promise<Result<FungibleTokenMetadata, Error>> => {
      try {
        const contractAccountResult = await getAccount(
          fungibleTokenContractId,
          connection,
        );
        if (!contractAccountResult.ok) {
          return contractAccountResult;
        }
        const contractAccount = contractAccountResult.value;
    
        // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
        const metadata = await contractAccount.viewFunction({
          contractId: fungibleTokenContractId,
          methodName: 'ft_metadata',
          args: {},
          gas: DEFAULT_GAS,
          attachedDeposit: NearToken.parse_yocto_near('1').as_yocto_near(),
        });
        const parsedMetadata = FungibleTokenMetadataSchema.parse(metadata);
        return { ok: true, value: parsedMetadata };
      } catch (e) {
        return { ok: false, error: new Error(e as string) };
      }
    };
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries full burden. It states the action ('Send') implying a write/mutation operation, but doesn't disclose critical behavioral traits: whether this requires authentication, transaction fees, confirmation time, error conditions (e.g., insufficient balance), or idempotency. The network constraint is mentioned, but other operational aspects are missing.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately sized (three sentences) and front-loaded with the core purpose. Each sentence adds value: first states action and standards, second clarifies sender/receiver roles, third adds a constraint. There's minor repetition of the network constraint across sentences, but overall it's efficient with zero fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (financial transaction tool with no annotations and no output schema), the description is incomplete. It doesn't explain return values, error handling, security requirements, or side effects. For a tool that sends tokens—a sensitive operation—more context on behavior, success criteria, and risks is needed to be fully helpful to an AI agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 80%, providing a high baseline. The description adds minimal parameter semantics beyond the schema: it clarifies that 'signer account is the sender' and 'receiver account is the recipient', which the schema already implies. It repeats the network constraint for 'fungibleTokenContractAccountId'. No additional syntax, format, or constraints are provided beyond what's in the schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Send Fungible Tokens') and resource (FT like USDC, USDT, WNEAR), with specific standards (NEP-141, NEP-148) mentioned. It distinguishes from sibling 'tokens_send_near' by specifying fungible tokens vs. NEAR tokens. However, it doesn't explicitly contrast with other token-related tools like 'search_near_fungible_tokens'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for sending fungible tokens, with a requirement that 'contract account id exists and is in the same network as the signer and receiver accounts.' It doesn't provide explicit when-not-to-use guidance or name alternatives (e.g., when to use 'tokens_send_near' instead). The context is clear but lacks explicit exclusions or comparison to siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

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/nearai/near-mcp'

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