# Provide Liquidity (v2)

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

* **Quickstart Guide**: See our [Liquidity Quickstart](https://docs.ston.fi/developer-section/quickstart/liquidity) for a complete React application example
* **Blog**: [STON.fi Liquidity Pools and TON Integration](https://blog.ston.fi/ston-fi-liquidity-pools-ton-integration/)
  {% endhint %}

{% hint style="danger" %}
If you are **simulating a liquidity provision** with the [simulate liquidity provision](https://api.ston.fi/swagger-ui/#/Dex/dex_simulate_liquidity_provision) endpoint and your LP account **already holds some tokens** (e.g., `"lp_account_token_a_balance": "more than 0"` or `"lp_account_token_b_balance": "more than 0"`), setting `token_a_units` or `token_b_units` in request might lead to an inaccurate `min_lp_units` in the simulation result. This could result in **permanent loss of funds**. We strongly encourage you to [refund any old LP account tokens](https://docs.ston.fi/developer-section/dex/sdk/v2/refund-liquidity) before proceeding.
{% endhint %}

> The production-ready pattern is **API-driven**. Simulate the provision first, let the STON.fi API tell you which router to hit, and then build contract instances dynamically. This keeps your integration compatible with future router upgrades and avoids hardcoding contract addresses.

## Mainnet-first workflow

STON.fi’s REST API (`api.ston.fi`) only serves mainnet data, so every production liquidity provision should follow this flow:

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 = { address: "<asset A address or 'ton'>", decimals: 9 };
const tokenB = { address: "<asset B address>", decimals: 9 };
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.address,
  asset1Address: tokenB.address,
});

const provisionType = poolInfo ? "Balanced" : "Initial"; // or "Arbitrary" for manual ratios

const simulationInput: Parameters<StonApiClient["simulateLiquidityProvision"]>[0] = {
  tokenA: tokenA.address,
  tokenB: tokenB.address,
  provisionType,
  slippageTolerance,
  walletAddress: userWalletAddress,
  ...(poolInfo ? { poolAddress: poolInfo.address } : {}),
};

if (provisionType === "Initial") {
  simulationInput.tokenAUnits = toUnits(amountA, tokenA.decimals).toString();
  simulationInput.tokenBUnits = toUnits(amountB, tokenB.decimals).toString();
} else if (provisionType === "Balanced") {
  // Supply **one** side; the API calculates the matching amount for the other token.
  simulationInput.tokenAUnits = toUnits(amountA, tokenA.decimals).toString();
} else {
  // "Arbitrary" – provide any ratio. To go single-sided, set one of the amounts to "0".
  simulationInput.tokenAUnits = toUnits(amountA, tokenA.decimals).toString();
  simulationInput.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** – add liquidity to an existing pool using any ratio. Provide explicit amounts for both tokens. To perform a single-sided deposit, set one of the amounts to `"0"`; the SDK will produce a single message that leans on the vault mechanics.

## Generating transaction messages

Define the canonical TON address and map the simulation result to message “legs”. Filter out empty amounts so that single-sided `Arbitrary` provisions naturally collapse to one message.

```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;

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](https://docs.ston.fi/developer-section/common/transaction-sending) for wallet-specific examples.

Execute the transaction using your preferred wallet integration. TonConnect, Tonkeeper SDK, custodial signers, and other libraries all accept the `txParams` generated above.

## Testnet provisions (manual setup)

If you really need to exercise liquidity provision on the TON testnet, you must fall back to hardcoded contracts because `api.ston.fi` serves mainnet only. Liquidity is scarce, so plan to source or mint the jettons yourself, create and fund the required pools, and only then attempt the provision.

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.
