> For the complete documentation index, see [llms.txt](https://docs.ston.fi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.ston.fi/developer-section/dex/sdk/provide-liquidity.md).

# Add liquidity

Simulate a deposit, preserve its message legs, and check issued LP tokens.

Deposit assets into a STON.fi pool to receive LP tokens representing your share. This guide uses DEX v2 contracts and SDK 2.x. See [Setup and environments](/developer-section/dex/sdk/v2.md) for client setup and router selection.

{% hint style="info" %}
**Learn More**

* **Blog**: [STON.fi Liquidity Pools and TON Integration](https://blog.ston.fi/ston-fi-liquidity-pools-ton-integration/)
  {% endhint %}

{% hint style="danger" %}
Before [simulating a liquidity provision](https://api.ston.fi/swagger-ui/#/Dex/dex_simulate_liquidity_provision), [refund pending LP-account tokens](/developer-section/dex/sdk/refund-liquidity.md). Existing balances in `lp_account_token_a_balance` or `lp_account_token_b_balance` can produce an inaccurate `min_lp_units` when simulating a new deposit, creating a risk of **permanent loss of funds**.
{% endhint %}

## Mainnet-first workflow

STON.fi’s REST API (`api.ston.fi`) serves mainnet data. For a mainnet liquidity deposit:

1. Simulate the provision to obtain routing metadata (pool address, token amounts, and the full router object).
2. Feed `simulationResult.router` directly into `dexFactory()` to build contracts dynamically.
3. Generate the liquidity provision transaction parameters with the router helpers.

```typescript
import { Client, dexFactory, toUnits } from "@ston-fi/sdk";
import { StonApiClient } from "@ston-fi/api";

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

const apiClient = new StonApiClient();

const userWalletAddress = "<your wallet address>";
const tokenA = await apiClient.getAsset("<asset A address or 'ton'>");
const tokenB = await apiClient.getAsset("<asset B address>");
const amountA = "10"; // human-readable amount (e.g. "10")
const amountB = "5";  // human-readable amount for the second token
const slippageTolerance = "0.001";

// Discover existing pools for the pair. If none are found we are creating a new pool.
const [poolInfo] = await apiClient.getPoolsByAssetPair({
  asset0Address: tokenA.contractAddress,
  asset1Address: tokenB.contractAddress,
});

const baseInput = {
  tokenA: tokenA.contractAddress,
  tokenB: tokenB.contractAddress,
  slippageTolerance,
  walletAddress: userWalletAddress,
};

const simulationInput: Parameters<StonApiClient["simulateLiquidityProvision"]>[0] =
  poolInfo
    ? {
        ...baseInput,
        provisionType: "Balanced",
        poolAddress: poolInfo.address,
        // Supply one side; the API calculates the matching amount.
        tokenAUnits: toUnits(amountA, tokenA.decimals).toString(),
      }
    : {
        ...baseInput,
        provisionType: "Initial",
        tokenAUnits: toUnits(amountA, tokenA.decimals).toString(),
        tokenBUnits: toUnits(amountB, tokenB.decimals).toString(),
      };

const simulationResult = await apiClient.simulateLiquidityProvision(simulationInput);

// 2. Router metadata ships with the simulation result
const { router: routerInfo } = simulationResult;
const dexContracts = dexFactory(routerInfo);

// 3. Open the router contract
const router = tonClient.open(
  dexContracts.Router.create(routerInfo.address)
);

// Optional helper when TON is part of the provision
const proxyTon = dexContracts.pTON.create(routerInfo.ptonMasterAddress);
```

The `simulationResult` object contains `provisionType`, `tokenA`, `tokenB`, `tokenAUnits`, `tokenBUnits`, `minLpUnits`, and `router`. Reuse those values when building the actual provision transaction to ensure the signed payload matches the simulated path.

### Interpreting simulation results

* **Initial**: no pool exists yet. Provide positive amounts for both assets; the router will deploy the pool and mint the first LP tokens.
* **Balanced**: an existing pool is topped up at the current ratio. Provide *one* token amount (the other is computed by the API). The result always contains two positive `token*Units` values.
* **Arbitrary**: to use this mode, replace the existing-pool input above with `provisionType: "Arbitrary"`, the `poolAddress`, and both `tokenAUnits` and `tokenBUnits`. To perform a single-sided deposit, set one of the amounts to `"0"`; build the corresponding single-sided v2 provision message. Pending deposits belong to LP accounts, not referral-fee Vaults.

## Generating transaction messages

Define the TON address and convert each non-zero asset amount in the simulation result into a message. Single-sided `Arbitrary` provisions use one non-zero amount.

```typescript
const TON_ADDRESS = "EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c";

const legs = [
  {
    userWalletAddress,
    minLpOut: simulationResult.minLpUnits,
    sendTokenAddress: simulationResult.tokenA,
    sendAmount: simulationResult.tokenAUnits,
    otherTokenAddress: simulationResult.tokenB,
  },
  {
    userWalletAddress,
    minLpOut: simulationResult.minLpUnits,
    sendTokenAddress: simulationResult.tokenB,
    sendAmount: simulationResult.tokenBUnits,
    otherTokenAddress: simulationResult.tokenA,
  },
].filter(({ sendAmount }) => BigInt(sendAmount) > 0n);

const isSingleSide =
  simulationResult.provisionType === "Arbitrary" && legs.length === 1;

if (!("getSingleSideProvideLiquidityTonTxParams" in router)) {
  throw new Error("Select a DEX v2 router supporting this provision flow");
}

const txParams = await Promise.all(
  legs.map((leg) => {
    if (leg.sendTokenAddress === TON_ADDRESS) {
      const tonLeg = {
        ...leg,
        proxyTon,
      };

      return isSingleSide
        ? router.getSingleSideProvideLiquidityTonTxParams(tonLeg)
        : router.getProvideLiquidityTonTxParams(tonLeg);
    }

    const jettonLeg = {
      ...leg,
      otherTokenAddress:
        leg.otherTokenAddress === TON_ADDRESS
          ? proxyTon.address.toString()
          : leg.otherTokenAddress,
    };

    return isSingleSide
      ? router.getSingleSideProvideLiquidityJettonTxParams(jettonLeg)
      : router.getProvideLiquidityJettonTxParams(jettonLeg);
  }),
);
```

Send the resulting `txParams` array in a single transaction. See our [transaction sending guide](/developer-section/common/transaction-sending.md) for wallet-specific examples.

## Testnet provisions (manual setup)

To add liquidity on TON testnet, configure testnet contracts and obtain the required testnet jettons. The `api.ston.fi` endpoint serves mainnet data.

To mirror the examples above on testnet, testers often use TesREED/TestBlue and run both single-sided and balanced deposits.

```typescript
import { TonClient, toNano } from "@ton/ton";
import { DEX, pTON } from "@ston-fi/sdk";

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

const router = client.open(
  DEX.v2_1.Router.CPI.create("kQALh-JBBIKK7gr0o4AVf9JZnEsFndqO0qTCyT-D-yBsWk0v") // CPI Router v2.1.0 (testnet)
);

const proxyTon = pTON.v2_1.create("kQACS30DNoUQ7NfApPvzh7eBmSZ9L4ygJ-lkNWtba8TQT-Px"); // pTON v2.1.0 (testnet)

const txParams = await router.getProvideLiquidityJettonTxParams({
  userWalletAddress: "<your testnet wallet>",
  sendTokenAddress: "kQDLvsZol3juZyOAVG8tWsJntOxeEZWEaWCbbSjYakQpuYN5", // TesREED jetton (testnet)
  sendAmount: toNano("1"),
  otherTokenAddress: "kQB_TOJSB7q3-Jm1O8s0jKFtqLElZDPjATs5uJGsujcjznq3", // TestBlue jetton (testnet)
  minLpOut: "1",
  queryId: 12345,
});
```

This manual approach is strictly **for testing**. Switch back to the API-driven workflow for anything mainnet facing.

## Check the result

An implementation reference is available in the [liquidity demo](https://sdk-demo-app.ston.fi/liquidity/provide) and [SDK example application](https://github.com/ston-fi/sdk/tree/main/examples/next-js-app).

Inspect the submitted operation and the wallet’s LP position. A completed deposit mints LP tokens. To reclaim tokens remaining in the LP account, use [Recover pending liquidity](/developer-section/dex/sdk/refund-liquidity.md). A two-sided deposit requires both non-empty messages; verify that the wallet can send both before submission.

Use the [REST transaction action tree](/developer-section/dex/api/reference.md#transaction-action-tree) to inspect the submitted operation.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.ston.fi/developer-section/dex/sdk/provide-liquidity.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
