Skip to main content
Glama

ref_finance_get_swap_estimate

Get a swap estimate for exchanging tokens on Ref Finance via NEAR, using either the smart router for best price or a specific pool ID.

Instructions

Get a swap estimate from the Ref Finance exchange contract based on two tokens and a pool id.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
tokenInYes
tokenOutYes
amountYesThe amount of the input tokens to swap
estimateTypeNoThe type of estimate to get. Defaults to the ref finance smart router to find the best price over all available pools
networkIdNomainnet

Implementation Reference

  • The tool 'ref_finance_get_swap_estimate' is registered via mcp.tool() with its schema (input validation via Zod) and handler function. This is the primary registration and handler location.
    mcp.tool(
      'ref_finance_get_swap_estimate',
      noLeadingWhitespace`
      Get a swap estimate from the Ref Finance exchange contract based on two tokens and a pool id.`,
      {
        tokenIn: z.object({
          contractId: z
            .string()
            .describe('The contract id of the input token to be swapped'),
          symbol: z.string().describe('The symbol of the input token'),
        }),
        tokenOut: z.object({
          contractId: z
            .string()
            .describe('The contract id of the output token to be swapped'),
          symbol: z.string().describe('The symbol of the output token'),
        }),
        amount: z
          .union([
            z
              .number()
              .describe(
                'The amount of input tokens with decimal formatting (e.g., 1.5 wNEAR)',
              ),
            z
              .bigint()
              .describe(
                'The amount in smallest denomination (e.g., yoctowNEAR for wNEAR, or equivalent for other tokens based on their decimals)',
              ),
          ])
          .describe('The amount of the input tokens to swap'),
        estimateType: z
          .union([
            z.object({
              type: z
                .literal('bySmartRoute')
                .describe(
                  'Get an estimate using the ref finance smart router to find the best pool',
                ),
              pathDepth: z
                .number()
                .default(3)
                .describe('The depth of the path to search for the best pool'),
              slippagePercent: z
                .number()
                .default(0.001)
                .describe(
                  'The slippage to use for the estimate. Only use 0.001, 0.005, or 0.01',
                ),
            }),
            z.object({
              type: z
                .literal('byPoolId')
                .describe('Get an estimate using a specific pool id'),
              poolId: z.number().describe('The pool id (e.g. 1)'),
            }),
          ])
          .default({ type: 'bySmartRoute' })
          .describe(
            'The type of estimate to get. Defaults to the ref finance smart router to find the best price over all available pools',
          ),
        networkId: z.enum(['testnet', 'mainnet']).default('mainnet'),
      },
      async (args, _) => {
        if (args.tokenIn === args.tokenOut) {
          return {
            content: [
              {
                type: 'text',
                text: `Error: Token in and token out cannot be the same`,
              },
            ],
          };
        }
    
        const connection = await connect({
          networkId: args.networkId,
          keyStore: keystore,
          nodeUrl: getEndpointsByNetwork(args.networkId)[0]!,
        });
    
        const tokenInContractAccountResult = await getAccount(
          args.tokenIn.contractId,
          connection,
        );
        if (!tokenInContractAccountResult.ok) {
          return {
            content: [
              {
                type: 'text',
                text: `Error: ${tokenInContractAccountResult.error}`,
              },
            ],
          };
        }
        const tokenIn = tokenInContractAccountResult.value;
    
        const tokenOutContractAccountResult = await getAccount(
          args.tokenOut.contractId,
          connection,
        );
        if (!tokenOutContractAccountResult.ok) {
          return {
            content: [
              {
                type: 'text',
                text: `Error: ${tokenOutContractAccountResult.error}`,
              },
            ],
          };
        }
        const tokenOut = tokenOutContractAccountResult.value;
    
        const tokenInMetadata = await getFungibleTokenContractMetadataResult(
          tokenIn.accountId,
          connection,
        );
        if (!tokenInMetadata.ok) {
          return {
            content: [{ type: 'text', text: `Error: ${tokenInMetadata.error}` }],
          };
        }
        const tokenOutMetadata = await getFungibleTokenContractMetadataResult(
          tokenOut.accountId,
          connection,
        );
        if (!tokenOutMetadata.ok) {
          return {
            content: [{ type: 'text', text: `Error: ${tokenOutMetadata.error}` }],
          };
        }
    
        const estimateType = args.estimateType;
        if (estimateType.type === 'byPoolId') {
          const poolResult = await refFinanceGetPoolFromId(
            connection,
            estimateType.poolId,
          );
          if (!poolResult.ok) {
            return {
              content: [{ type: 'text', text: `Error: ${poolResult.error}` }],
            };
          }
          const poolInfo = poolResult.value;
    
          if (
            !poolInfo.tokenIds.includes(tokenIn.accountId) ||
            !poolInfo.tokenIds.includes(tokenOut.accountId)
          ) {
            return {
              content: [
                {
                  type: 'text',
                  text: `Error: Pool tokens [${poolInfo.tokenIds.join(', ')}] do not include ${args.tokenIn.contractId} or ${args.tokenOut.contractId}`,
                },
              ],
            };
          }
    
          // calculate the pool estimate
          const poolEstimate = await refFinanceGetEstimate(
            {
              id: tokenIn.accountId,
              metadata: tokenInMetadata.value,
            },
            {
              id: tokenOut.accountId,
              metadata: tokenOutMetadata.value,
            },
            poolInfo,
            args.amount.toString(),
          );
          if (!poolEstimate.ok) {
            return {
              content: [{ type: 'text', text: `Error: ${poolEstimate.error}` }],
            };
          }
    
          return {
            content: [
              {
                type: 'text',
                text: `Pool info: ${stringify_bigint(poolEstimate.value)}`,
              },
            ],
          };
        } else {
          // convert the amount into the decimals of the fungible token
          const amountInDecimals =
            typeof args.amount === 'number'
              ? BigInt(
                  Math.floor(args.amount * 10 ** tokenInMetadata.value.decimals),
                )
              : args.amount;
    
          const smartRouteEstimate = await getSmartRouteRefSwapEstimate(
            amountInDecimals.toString(),
            tokenIn.accountId,
            tokenOut.accountId,
            estimateType.pathDepth,
            estimateType.slippagePercent,
          );
          if (!smartRouteEstimate.ok) {
            return {
              content: [
                { type: 'text', text: `Error: ${smartRouteEstimate.error}` },
              ],
            };
          }
    
          return {
            content: [
              {
                type: 'text',
                text: `Smart route estimate: ${stringify_bigint(
                  smartRouteEstimate.value,
                )}`,
              },
            ],
          };
        }
      },
    );
  • The handler function that executes the swap estimate logic. It validates tokens, fetches metadata, and dispatches to either 'byPoolId' estimate (using refFinanceGetEstimate helper) or 'bySmartRoute' estimate (using getSmartRouteRefSwapEstimate from utils).
    async (args, _) => {
      if (args.tokenIn === args.tokenOut) {
        return {
          content: [
            {
              type: 'text',
              text: `Error: Token in and token out cannot be the same`,
            },
          ],
        };
      }
    
      const connection = await connect({
        networkId: args.networkId,
        keyStore: keystore,
        nodeUrl: getEndpointsByNetwork(args.networkId)[0]!,
      });
    
      const tokenInContractAccountResult = await getAccount(
        args.tokenIn.contractId,
        connection,
      );
      if (!tokenInContractAccountResult.ok) {
        return {
          content: [
            {
              type: 'text',
              text: `Error: ${tokenInContractAccountResult.error}`,
            },
          ],
        };
      }
      const tokenIn = tokenInContractAccountResult.value;
    
      const tokenOutContractAccountResult = await getAccount(
        args.tokenOut.contractId,
        connection,
      );
      if (!tokenOutContractAccountResult.ok) {
        return {
          content: [
            {
              type: 'text',
              text: `Error: ${tokenOutContractAccountResult.error}`,
            },
          ],
        };
      }
      const tokenOut = tokenOutContractAccountResult.value;
    
      const tokenInMetadata = await getFungibleTokenContractMetadataResult(
        tokenIn.accountId,
        connection,
      );
      if (!tokenInMetadata.ok) {
        return {
          content: [{ type: 'text', text: `Error: ${tokenInMetadata.error}` }],
        };
      }
      const tokenOutMetadata = await getFungibleTokenContractMetadataResult(
        tokenOut.accountId,
        connection,
      );
      if (!tokenOutMetadata.ok) {
        return {
          content: [{ type: 'text', text: `Error: ${tokenOutMetadata.error}` }],
        };
      }
    
      const estimateType = args.estimateType;
      if (estimateType.type === 'byPoolId') {
        const poolResult = await refFinanceGetPoolFromId(
          connection,
          estimateType.poolId,
        );
        if (!poolResult.ok) {
          return {
            content: [{ type: 'text', text: `Error: ${poolResult.error}` }],
          };
        }
        const poolInfo = poolResult.value;
    
        if (
          !poolInfo.tokenIds.includes(tokenIn.accountId) ||
          !poolInfo.tokenIds.includes(tokenOut.accountId)
        ) {
          return {
            content: [
              {
                type: 'text',
                text: `Error: Pool tokens [${poolInfo.tokenIds.join(', ')}] do not include ${args.tokenIn.contractId} or ${args.tokenOut.contractId}`,
              },
            ],
          };
        }
    
        // calculate the pool estimate
        const poolEstimate = await refFinanceGetEstimate(
          {
            id: tokenIn.accountId,
            metadata: tokenInMetadata.value,
          },
          {
            id: tokenOut.accountId,
            metadata: tokenOutMetadata.value,
          },
          poolInfo,
          args.amount.toString(),
        );
        if (!poolEstimate.ok) {
          return {
            content: [{ type: 'text', text: `Error: ${poolEstimate.error}` }],
          };
        }
    
        return {
          content: [
            {
              type: 'text',
              text: `Pool info: ${stringify_bigint(poolEstimate.value)}`,
            },
          ],
        };
      } else {
        // convert the amount into the decimals of the fungible token
        const amountInDecimals =
          typeof args.amount === 'number'
            ? BigInt(
                Math.floor(args.amount * 10 ** tokenInMetadata.value.decimals),
              )
            : args.amount;
    
        const smartRouteEstimate = await getSmartRouteRefSwapEstimate(
          amountInDecimals.toString(),
          tokenIn.accountId,
          tokenOut.accountId,
          estimateType.pathDepth,
          estimateType.slippagePercent,
        );
        if (!smartRouteEstimate.ok) {
          return {
            content: [
              { type: 'text', text: `Error: ${smartRouteEstimate.error}` },
            ],
          };
        }
    
        return {
          content: [
            {
              type: 'text',
              text: `Smart route estimate: ${stringify_bigint(
                smartRouteEstimate.value,
              )}`,
            },
          ],
        };
      }
    },
  • The input schema using Zod defining all parameters: tokenIn, tokenOut, amount, estimateType (bySmartRoute or byPoolId with pathDepth/slippagePercent or poolId), and networkId.
    {
      tokenIn: z.object({
        contractId: z
          .string()
          .describe('The contract id of the input token to be swapped'),
        symbol: z.string().describe('The symbol of the input token'),
      }),
      tokenOut: z.object({
        contractId: z
          .string()
          .describe('The contract id of the output token to be swapped'),
        symbol: z.string().describe('The symbol of the output token'),
      }),
      amount: z
        .union([
          z
            .number()
            .describe(
              'The amount of input tokens with decimal formatting (e.g., 1.5 wNEAR)',
            ),
          z
            .bigint()
            .describe(
              'The amount in smallest denomination (e.g., yoctowNEAR for wNEAR, or equivalent for other tokens based on their decimals)',
            ),
        ])
        .describe('The amount of the input tokens to swap'),
      estimateType: z
        .union([
          z.object({
            type: z
              .literal('bySmartRoute')
              .describe(
                'Get an estimate using the ref finance smart router to find the best pool',
              ),
            pathDepth: z
              .number()
              .default(3)
              .describe('The depth of the path to search for the best pool'),
            slippagePercent: z
              .number()
              .default(0.001)
              .describe(
                'The slippage to use for the estimate. Only use 0.001, 0.005, or 0.01',
              ),
          }),
          z.object({
            type: z
              .literal('byPoolId')
              .describe('Get an estimate using a specific pool id'),
            poolId: z.number().describe('The pool id (e.g. 1)'),
          }),
        ])
        .default({ type: 'bySmartRoute' })
        .describe(
          'The type of estimate to get. Defaults to the ref finance smart router to find the best price over all available pools',
        ),
      networkId: z.enum(['testnet', 'mainnet']).default('mainnet'),
    },
  • The refFinanceGetEstimate helper function that calculates the swap estimate for a specific pool using constant product AMM formula.
    export const refFinanceGetEstimate = async (
      tokenIn: TokenMetadata,
      tokenOut: TokenMetadata,
      pool: Pool,
      amountIn: string,
    ): Promise<Result<RefFinanceEstimate, Error>> => {
      try {
        const amountInBigInt = BigInt(amountIn);
        const feeDivisorBigInt = BigInt(FEE_DIVISOR);
        const poolFeeBigInt = BigInt(pool.fee);
    
        const amount_with_fee = amountInBigInt * (feeDivisorBigInt - poolFeeBigInt);
    
        // Use the raw supply values which should be strings representing integers
        const in_balance = BigInt(
          toReadableNumber(tokenIn.metadata.decimals, pool.supplies[tokenIn.id]),
        );
        const out_balance = BigInt(
          toReadableNumber(tokenOut.metadata.decimals, pool.supplies[tokenOut.id]),
        );
    
        // Perform calculation using BigInt division
        // Note: BigInt division truncates the result (floor division)
        const numerator = amount_with_fee * out_balance;
        const denominator = feeDivisorBigInt * in_balance + amount_with_fee;
    
        // Avoid division by zero
        if (denominator === 0n) {
          return {
            ok: false,
            error: new Error('Division by zero in estimate calculation'),
          };
        }
    
        const estimate = (numerator / denominator).toString();
    
        return {
          ok: true,
          value: {
            estimate,
            pool,
            outputToken: tokenOut.id,
            inputToken: tokenIn.id,
          },
        };
      } catch (e) {
        return { ok: false, error: new Error(e as string) };
      }
    };
  • The getSmartRouteRefSwapEstimate helper function that calls the Ref Finance smart router API to find the best swap route across pools.
    export const getSmartRouteRefSwapEstimate = async (
      amountIn: string,
      tokenIn: string,
      tokenOut: string,
      pathDepth: number,
      slippagePercent: number,
    ): Promise<Result<SwapEstimate, Error>> => {
      try {
        const params = new URLSearchParams({
          amountIn,
          tokenIn,
          tokenOut,
          pathDeep: pathDepth.toString(),
          slippage: slippagePercent.toString(),
        });
    
        const url = `https://smartrouter.ref.finance/findPath?${params.toString()}`;
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`Failed to fetch swap estimate: ${response.statusText}`);
        }
    
        const data = (await response.json()) as SwapEstimate;
        return { ok: true, value: data };
      } catch (error) {
        return { ok: false, error: error as Error };
      }
    };
Behavior2/5

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

No annotations provided, so description must carry full burden. It only says 'Get a swap estimate' with no mention of side effects (should be read-only), permissions, rate limits, or return format. Minimal behavioral context.

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?

Single sentence, no wasted words. It is concise, though could be slightly more informative without losing brevity.

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 (5 parameters, nested objects, two estimate types), the description is too brief. No mention of output (estimated amount), network selection, or the smart route vs pool id distinction. Schema fills some gaps but description should summarize.

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?

The input schema has rich descriptions for most parameters, so the description adds little extra meaning. It mentions 'two tokens and a pool id' which maps to tokenIn, tokenOut, and optional poolId, but schema already covers these.

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 tool gets a swap estimate, which is a specific verb+resource. It mentions two tokens and a pool id, differentiating it from sibling tools like execute_swap (execution) and get_pools (listing). However, it does not explicitly contrast with these siblings.

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

Usage Guidelines2/5

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

No guidance on when to use this tool vs. alternatives (e.g., when to use execute_swap instead). The context of 'estimate' implies a preview before swap, but this is not stated.

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

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