w3-kitdocs

provide-liquidity

Deposit token pairs into on-chain liquidity pools and receive LP tokens representing your share of the pool.

evmsolana

Dependencies

viem@solana/web3.js
provide-liquidity/evm.tsx
import { useState } from "react";
import { useWalletClient, usePublicClient } from "wagmi";
import { parseUnits } from "viem";

// Generic liquidity router ABI — compatible with any Uniswap V2-style router
const ROUTER_ABI = [
  {
    name: "addLiquidity",
    type: "function",
    inputs: [
      { name: "tokenA", type: "address" },
      { name: "tokenB", type: "address" },
      { name: "amountADesired", type: "uint256" },
      { name: "amountBDesired", type: "uint256" },
      { name: "amountAMin", type: "uint256" },
      { name: "amountBMin", type: "uint256" },
      { name: "to", type: "address" },
      { name: "deadline", type: "uint256" },
    ],
    outputs: [
      { name: "amountA", type: "uint256" },
      { name: "amountB", type: "uint256" },
      { name: "liquidity", type: "uint256" },
    ],
  },
] as const;

const ERC20_APPROVE_ABI = [
  {
    name: "approve",
    type: "function",
    inputs: [
      { name: "spender", type: "address" },
      { name: "amount", type: "uint256" },
    ],
    outputs: [{ name: "", type: "bool" }],
  },
] as const;

interface AddLiquidityParams {
  routerAddress: `0x${string}`;
  tokenA: `0x${string}`;
  tokenB: `0x${string}`;
  amountA: string;
  amountB: string;
  slippageBps?: number; // basis points, default 50 = 0.5%
  // ★ Use separate decimals for each token — they can differ (e.g. USDC=6, WETH=18)
  decimalsA?: number;
  decimalsB?: number;
}

export function useProvideLiquidity() {
  const { data: walletClient } = useWalletClient();
  const publicClient = usePublicClient();
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  async function addLiquidity(params: AddLiquidityParams) {
    if (!walletClient || !publicClient) throw new Error("Wallet not connected");
    setLoading(true);
    setError(null);
    try {
      const decimalsA = params.decimalsA ?? 18;
      const decimalsB = params.decimalsB ?? 18;
      const slippageBps = params.slippageBps ?? 50;
      const amountA = parseUnits(params.amountA, decimalsA);
      const amountB = parseUnits(params.amountB, decimalsB);
      const amountAMin = (amountA * BigInt(10000 - slippageBps)) / BigInt(10000);
      const amountBMin = (amountB * BigInt(10000 - slippageBps)) / BigInt(10000);
      const deadline = BigInt(Math.floor(Date.now() / 1000) + 60 * 20);

      // Approve router to spend both tokens
      for (const [token, amount] of [[params.tokenA, amountA], [params.tokenB, amountB]] as const) {
        const approveTx = await walletClient.writeContract({
          address: token,
          abi: ERC20_APPROVE_ABI,
          functionName: "approve",
          args: [params.routerAddress, amount],
        });
        await publicClient.waitForTransactionReceipt({ hash: approveTx });
      }

      // Add liquidity
      const tx = await walletClient.writeContract({
        address: params.routerAddress,
        abi: ROUTER_ABI,
        functionName: "addLiquidity",
        args: [
          params.tokenA, params.tokenB,
          amountA, amountB,
          amountAMin, amountBMin,
          walletClient.account.address,
          deadline,
        ],
      });
      return await publicClient.waitForTransactionReceipt({ hash: tx });
    } catch (e) {
      setError(e instanceof Error ? e.message : String(e));
      throw e;
    } finally {
      setLoading(false);
    }
  }

  return { addLiquidity, loading, error };
}

How Liquidity Provision Works

Liquidity Pools

A liquidity pool is a smart contract holding reserves of two (or more) tokens. Traders swap against these reserves; liquidity providers (LPs) deposit the tokens and earn a share of trading fees.

Pool share = your deposit / total pool liquidity × 100%

LP Tokens

When you deposit, the pool mints LP tokens proportional to your share. LP tokens are redeemable: burn them to withdraw your share of the current reserves plus accumulated fees.

Example: pool has 100 ETH + 200,000 USDC. You deposit 10 ETH + 20,000 USDC (10% of the pool). You receive 10% of LP token supply. Later, fees have grown the pool to 110 ETH + 220,000 USDC. Your 10% is now worth 11 ETH + 22,000 USDC.

Impermanent Loss (IL)

IL is the opportunity cost of providing liquidity vs simply holding both tokens.

It occurs when the price ratio between the two tokens changes. The AMM formula rebalances the pool automatically — which means you end up holding more of the cheaper token and less of the more expensive one.

IL is "impermanent" because it disappears if prices return to the entry ratio. It becomes permanent when you withdraw while prices are still diverged.

Price change (one token)Approximate IL
1.25x0.6%
1.5x2.0%
2x5.7%
4x20%
10x42.5%

Concentrated Liquidity

V3-style AMMs let LPs choose a price range. Liquidity is only active (and earning fees) when the market price is within that range. This improves capital efficiency but requires active management — if price leaves the range, you earn no fees.

Fee Tiers

Pools come in different fee tiers (e.g., 0.01%, 0.05%, 0.3%, 1%).

  • Stable pairs (USDC/USDT): use low fee tiers (0.01–0.05%)
  • Blue-chip volatile pairs (ETH/USDC): 0.3% is most liquid
  • Exotic pairs: 1% compensates LPs for higher IL risk

Key Terms

  • TVL: Total Value Locked — total dollar value of assets in a pool
  • APR/APY: Annualized fee return for LPs (varies by volume and pool size)
  • Rebalancing: The AMM automatically adjusting reserves to maintain k = x*y
  • PDA: Program Derived Address (Solana) — deterministic address for pool state
  • Tick: Discrete price point used in concentrated liquidity (V3-style) AMMs