Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Integration FAQ

Info

Toncoin → Gram rename. As of June 15, 2026, the native token is displayed as Gram (GRAM) instead of Toncoin (TON). This is a display-only change: same asset, same contracts, same addresses, and 10 TON = 10 GRAM. The TON blockchain itself is not renamed, so network-level identifiers (endpoints, contract addresses, field names such as derivedTON) are unaffected. Do not create new asset listings or pools — existing ones are simply relabeled.

Questions

Indexer and SDK

SDK

Github - https://github.com/cryptoalgebra/tonco-sdk/

The current mainnet SDK version used by the examples is @toncodex/[email protected]:

npm install @toncodex/[email protected]

Contracts are selected through DEX_VERSION. In SDK 1.3.0:

  • DEX_VERSION.v1 selects the V1 contracts.
  • DEX_VERSION.v1_6 selects the current V2 deployment.

The same keys are used with versioned SDK constants and contracts, for example pTON_MINTER.v1_6, ROUTER.v1_6, and PoolContract[DEX_VERSION.v1_6].

Examples and references

Github - https://github.com/cryptoalgebra/tonco-demo

Mainnet

  • Explorer page: https://indexer.tonco.io
  • GraphQL endpoint: https://indexer.tonco.io
  • Farming Backend: https://api-farming.tonco.io
  • ProxyTON/ProxyGRAM
    • V1 - EQCUnExmdgwAKADi-j2KPKThyQqTc7U650cgM0g78UzZXn9J
    • V2 - EQBwGRAMjTY_CMNWKhzBeHA-VPjp2rD9vB3rwepG2co5eScs
  • Router Contract V1 - EQC_-t0nCnOFMdp7E7qPxAOCbCWGFz-e3pwxb6tTvFmshjt5
  • Router Contract V1.6 - EQBt0NCO16Ng6QqOb8YaRcnV_vzIAfbLKhMMBBZpYNUbl3sB - this is a valid but demosissioned router
  • Router Contract V2 - EQAT0nCO20vC6gzpyUJbWI7ELnRvAAB3VHJ3Z1jXQsw6KGJ4

Testnet

Retrieve pool data

The best way to get all the pools and general information about them is to use our indexer.

// Some code
import { ApolloClient, InMemoryCache, gql } from "@apollo/client/core";
import { Command, OptionValues } from "commander";

export const POOLS_QUERY = gql`
    query PoolsQuery {
        pools {
            name
            address
            jetton0 {
                address
                symbol
                decimals
            }
            jetton1 {
                address
                symbol
                decimals
            }
        }
    }
`;
async function queryPools(options: OptionValues) {
    const appoloClient = new ApolloClient({
        uri: "https://indexer.tonco.io/",
        credentials: "same-origin",
        cache: new InMemoryCache(),
    });
    const response = await appoloClient.query({ query: POOLS_QUERY });
    const appoloPoolList = response.data.pools;
    console.log(appoloPoolList);
}


Indexer is critical for our system, and we keep it highly available, however, we encourage caching the pool list.

Alternatively, if you don’t want to depend on our infrastructure, you can rescan the blockchain in search of messages POOL_INIT sent by the router

Getting pool APR

For information about the APR of the pool in farming, you need to refer to the URL:

GET api-farming.tonco.io/apr?pool=<pool address>

The answer has “apr” as the base apr and an array of farmings. Farming is considered active if rewardsLeft is not equal to zero. Farming has a property - multiplier, it is a coefficient that denotes how many times farming increases the base apr.

In most cases, a pool has only one active farming, but it is possible that it will have several in the future.

Example

Request:

https://api-farming.tonco.io/apr?pool=EQD25vStEwc-h1QT1qlsYPQwqU5IiOhox5II0C_xsDNpMVo7

Response:

{
    "apr": 65.96151567163085,
    "farmings": [
        {
            "pool": "0:f6e6f4ad13073e875413d6a96c60f430a94e4888e868c79208d02ff1b0336931",
            "rewardsLeft": "0",
            "rewardToken": "0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe",
            "rewardRate": "208334",
            "id": 5,
            "multiplier": 1.3277778973971204
        }
    ]
}

Retrieving positions

There are several ways to get all positions and positions per person.

Getting positions by index

If you want to manually scan and enumerate all the positions for a particular pool you can first use the method getPoolStateAndConfiguration and get "Number of active NFT positions" from it. Then iterate from 0 as the index of NFT and call get_nft_address_by_index()

Getting positions with NFT API's

Position NFT is a real NFT so you can use TonConsole Api and TonCenter API to get NFT address info and metadata. For position parameters, however, you would need to call the NFT get-method - GetPositionInfo()

Getting positions from TONCO indexer

Please address the GraphQL schema documents for more details - GraphQL Schema

Getting collected fees - Indexer — Position Data
Here is a small snippet that uses our indexer

import { ApolloClient, InMemoryCache, gql } from "@apollo/client/core";
import { Address, TonClient4 } from "@ton/ton";
import { getHttpV4Endpoint } from "@orbs-network/ton-access";
import { PoolV3Contract } from "../wrappers/PoolV3Contract";

export const POSITION_QUERY = gql`
    query PositionQuery($where: PositionWhere) {
        positions(where: $where) {
            id
            owner
            pool
            nftAddress
            tickLower
            tickUpper
            liquidity
            feeGrowthInside0LastX128
            feeGrowthInside1LastX128
        }
    }
`;

async function queryPositions(options: OptionValues) {
    const appoloClient = new ApolloClient({
        uri: "https://indexer.tonco.io/", // Replace with your GraphQL endpoint
        credentials: "same-origin",
        cache: new InMemoryCache(),
    });

    const poolAddress = Address.parse("EQD25vStEwc-h1QT1qlsYPQwqU5IiOhox5II0C_xsDNpMVo7");
    const ownerAddress = Address.parse("EQC2nUFN69DWcdgiuvSKXI6P3vHF9Gu_zW3OnQf0s5DgYBmJ");
    console.log(poolAddress.toRawString());
    console.log(ownerAddress.toString({ bounceable: true }));

    const response = await appoloClient.query({
        query: POSITION_QUERY,
        variables: {
            where: {
                pool: poolAddress.toRawString(),
                owner: ownerAddress.toString({ bounceable: true }),
            },
        },
    });
    const appoloPositionsList = response.data.positions;
    const client = new TonClient4({ endpoint: await getHttpV4Endpoint() });
    const poolOpened = client.open(new PoolV3Contract(poolAddress));

    for (let [i, positionInfo] of appoloPositionsList.entries()) {
        console.log(`# ${i} :`);
        console.log(positionInfo);

        const fees = await poolOpened.getCollectedFees(
            positionInfo.tickLower,
            positionInfo.tickUpper,
            positionInfo.liquidity,
            positionInfo.feeGrowthInside0LastX128,
            positionInfo.feeGrowthInside1LastX128,
        );
        console.log(`  Fees Jetton0 : ${fees.amount0}`);
        console.log(`  Fees Jetton1 : ${fees.amount1}`);

        const reserves = await poolOpened.getMintEstimate(positionInfo.tickLower, positionInfo.tickUpper, positionInfo.liquidity);
        console.log(`  Reserves Jetton0 : ${reserves.amount0}`);
        console.log(`  Reserves Jetton1 : ${reserves.amount1}`);
    }
}

Retrieving position data using SDK

Use the contract class that corresponds to the pool version. The current GRAM/USDT V2.0 pool is exposed as DEX_VERSION.v1_6 in SDK 1.3.0. The Position entity calculates the current token amounts, while accumulated fees can be read with getCollectedFees.

import { TonClient } from "@ton/ton";
import { Address } from "@ton/core";
import { DEX_VERSION, Jetton, Pool, PoolContract, Position, PositionNFTContract, pTON_MINTER } from "@toncodex/sdk";

const client = new TonClient({
    endpoint: "https://toncenter.com/api/v2/jsonRPC",
});

const poolAddress = "EQBPTSLamspzALssCkevAbqS2J3GtH7E5Z0tm6XI0DaPyWzE"; // GRAM - USDT V2.0

const poolContract = client.open(new PoolContract[DEX_VERSION.v1_6](Address.parse(poolAddress)));
const poolData = await poolContract.getPoolStateAndConfiguration();

const jetton0 = new Jetton(pTON_MINTER.v1_6, 9, "GRAM");
const jetton1 = new Jetton("0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe", 6, "USD₮");

const pool = new Pool(
    jetton0,
    jetton1,
    poolData.lp_fee_current,
    poolData.price_sqrt.toString(),
    poolData.liquidity.toString(),
    poolData.tick,
    poolData.tick_spacing,
);

// Replace 0n with the required position index.
const positionNFTAddress = await poolContract.getNFTAddressByIndex(0n);
const positionContract = client.open(new PositionNFTContract[DEX_VERSION.v1_6](positionNFTAddress));

const positionInfo = await positionContract.getPositionInfo();

const liquidity = positionInfo.liquidity.toString();
const tickLower = positionInfo.tickLow;
const tickUpper = positionInfo.tickHigh;
const feeGrowthInside0LastX128 = positionInfo.feeGrowthInside0LastX128;
const feeGrowthInside1LastX128 = positionInfo.feeGrowthInside1LastX128;

const position = new Position({
    pool, // pool instance
    tickLower,
    tickUpper,
    liquidity,
});

const { amount0, amount1 } = position;

const { amount0: feeAmount0, amount1: feeAmount1 } = await poolContract.getCollectedFees(
    tickLower,
    tickUpper,
    BigInt(liquidity),
    feeGrowthInside0LastX128,
    feeGrowthInside1LastX128,
);

Forming messages for the swap

Swap messages are constructed with PoolMessageManager. Select the constants and SwapType that match the pool version.

Single-hop GRAM → USD₮

import { Address, toNano } from "@ton/core";
import { TonClient } from "@ton/ton";
import { Jetton, JettonMinter, PoolMessageManager, pTON_MINTER, ROUTER, SwapType } from "@toncodex/sdk";

const client = new TonClient({
    endpoint: "https://toncenter.com/api/v2/jsonRPC",
});

const recipient = Address.parse("<user wallet address>");
const gram = new Jetton(pTON_MINTER.v1_6, 9, "GRAM");
const usdt = new Jetton("0:b113a994b5024a16719f69139328eb759596c38a25f59028b146fecdc3621dfe", 6, "USD₮");

const gramMinter = client.open(new JettonMinter(Address.parse(gram.address)));
const usdtMinter = client.open(new JettonMinter(Address.parse(usdt.address)));

const userGramWallet = await gramMinter.getWalletAddress(recipient);
const routerUsdtWallet = await usdtMinter.getWalletAddress(Address.parse(ROUTER.v1_6));

const message = PoolMessageManager.createSwapExactInMessage(
    userGramWallet,
    routerUsdtWallet,
    recipient,
    toNano(1),
    0n, // minimumAmountOut
    0n, // priceLimitSqrt
    SwapType.TON_TO_JETTON_V1_6,
    0, // queryId
);

Set minimumAmountOut from a fresh estimate and your slippage tolerance before sending a production transaction. 0n is used in the example only to focus on message construction.

Full example: createSwapMessage.ts.

Multihop GRAM → USD₮ → EVAA

A multihop route may cross different router versions. Pass one output router wallet and one SwapType per hop, in route order. Continuing the single-hop setup above:

const evaa = new Jetton("EQBKMfjX_a_dsOLm-juxyVZytFP7_KKnzGv6J01kGc72gVBp", 9, "EVAA");

const evaaMinter = client.open(new JettonMinter(Address.parse(evaa.address)));

const routerUsdtWalletV1_6 = await usdtMinter.getWalletAddress(Address.parse(ROUTER.v1_6));
const routerEvaaWalletV1 = await evaaMinter.getWalletAddress(Address.parse(ROUTER.v1));

const message = PoolMessageManager.createSwapExactInMultihopMessage(
    userGramWallet,
    [routerUsdtWalletV1_6, routerEvaaWalletV1],
    recipient,
    toNano(1),
    [0n, 0n], // minimumAmountsOut
    [0n, 0n], // priceLimitsSqrt
    [SwapType.TON_TO_JETTON_V1_6, SwapType.JETTON_TO_JETTON_V1],
);

For this route, the first pool is V2.0 (v1_6 in the SDK) and the second pool is V1. Apply the estimate and slippage tolerance to every minimumAmountsOut entry before sending the transaction. Full example: createSwapMultihopMessage.ts.

Swap estimate

For an exact-input single-hop swap, the simplest and most precise option is the pool get-method exposed by the SDK:

import { Address, toNano } from "@ton/core";
import { TonClient } from "@ton/ton";
import { DEX_VERSION, getSwapEstimate, PoolContract, pTON_MINTER } from "@toncodex/sdk";

const POOL_ADDRESS = "EQBPTSLamspzALssCkevAbqS2J3GtH7E5Z0tm6XI0DaPyWzE"; // GRAM - USDT V2.0

const client = new TonClient({
    endpoint: "https://toncenter.com/api/v2/jsonRPC",
});

const poolContract = client.open(new PoolContract[DEX_VERSION.v1_6](Address.parse(POOL_ADDRESS)));
const { jetton0_minter } = await poolContract.getPoolStateAndConfiguration();

const zeroToOne = Address.parse(pTON_MINTER.v1_6).equals(jetton0_minter);
const amountOut = await getSwapEstimate(toNano(1), POOL_ADDRESS, zeroToOne, client, DEX_VERSION.v1_6);

Exact-output estimation requires initialized ticks and is simulated with SwapSimulator. See the complete examples:

Multihop estimate

Estimate each pool in route order for exact-input, or in reverse order for exact-output. Determine the pool version for every hop separately: the example route combines the GRAM/USDT V2.0 pool (DEX_VERSION.v1_6) with the EVAA/USDT V1 pool (DEX_VERSION.v1).

The resulting swapTypes must remain in forward route order because the same array can be passed to createSwapExactInMultihopMessage.

Complete examples:

Marking transactions for referral tracking

With TONCO v1 It is possible to mark transactions to be able to index them for needs of referral tracking

  • You have 64 bits of query_id at your disposal. We don't alter it and copy it in the outgoing messages
  • A swap request is created as a payload for TRANSFER_NOTIFICATION. The current version of the swap request - Swap Cell Building uses at most 850bits and 1ref. In the current version, all remaining cell part is ignored. However, to be future-proof proof we recommend to occupy second maybe_ref (1 ref and 1 bit). This cell starts with 4 byte of your service id and any content you need as remaining data.

Forming Messages for Mint

Messages can be constructed using @toncodex/[email protected] and PoolMessageManager.createMintMessage.

The example prepares a position in the GRAM/USDT V2.0 pool within the price range [1, 2], based on an input amount of 1 GRAM. For this deployment, open PoolContract[DEX_VERSION.v1_6], use pTON_MINTER.v1_6, and pass DEX_VERSION.v1_6 as the last argument to createMintMessage.

In the V2.0 mint message, the position amounts are the maximum token inputs. The minimum acceptable liquidity is calculated from the selected slippage.

Complete example: createMintMessage.ts.