Skip to content
LitVMSwapDocs
Open app
Developers / V2 pools
Developers · On chain

V2 pools

Good old constant-product pairs. If you've integrated a Uniswap V2 style DEX before, you already know this page. The router speaks the Router02 interface and pairs use the classic 0.3% fee math.

Do you even need this?

The Swap API already routes through V2 when V2 gives the best price. Come here when you specifically want one pool: LP tooling, arbitrage, a bot, or a contract that swaps on chain.

Addresses

ContractAddress
Factory0x4680BCe1632824d30D2F53656dD610736c3e312e
Router0xF456737D17C2Bbb348fd4F7D1b000D62A46FB3b5
WETH() on the routerwzkLTC 0x3153…A77e
Pair init code hash0x01888feb01db41d97ad6fb1883d7e286650d46c410b82338aeb4a37554c28bcd

The router's ETH functions (swapExactETHForTokens, addLiquidityETH and so on) mean zkLTC here. Paths always use wzkLTC.

The ABI bits you'll need

TypeScript
import { parseAbi } from 'viem'

export const ROUTER = '0xF456737D17C2Bbb348fd4F7D1b000D62A46FB3b5'
export const WZKLTC = '0x315374AA9b5536037Cc1Efeea2439CCC0913A77e'

export const routerAbi = parseAbi([
  'function getAmountsOut(uint256 amountIn, address[] path) view returns (uint256[] amounts)',
  'function getAmountsIn(uint256 amountOut, address[] path) view returns (uint256[] amounts)',
  'function swapExactETHForTokens(uint256 amountOutMin, address[] path, address to, uint256 deadline) payable returns (uint256[] amounts)',
  'function swapExactTokensForETH(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline) returns (uint256[] amounts)',
  'function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline) returns (uint256[] amounts)',
  'function addLiquidity(address tokenA, address tokenB, uint256 amountADesired, uint256 amountBDesired, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline) returns (uint256 amountA, uint256 amountB, uint256 liquidity)',
  'function addLiquidityETH(address token, uint256 amountTokenDesired, uint256 amountTokenMin, uint256 amountETHMin, address to, uint256 deadline) payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity)',
  'function removeLiquidity(address tokenA, address tokenB, uint256 liquidity, uint256 amountAMin, uint256 amountBMin, address to, uint256 deadline) returns (uint256 amountA, uint256 amountB)',
  'function removeLiquidityETH(address token, uint256 liquidity, uint256 amountTokenMin, uint256 amountETHMin, address to, uint256 deadline) returns (uint256 amountToken, uint256 amountETH)',
])

export const pairAbi = parseAbi([
  'function token0() view returns (address)',
  'function token1() view returns (address)',
  'function getReserves() view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)',
  'function totalSupply() view returns (uint256)',
  'function balanceOf(address) view returns (uint256)',
])

Get a price

getAmountsOut runs the pool math for a path. It's a free read.

TypeScript
import { createPublicClient, formatUnits, http, parseEther } from 'viem'
import { litvm } from './litvm'
import { ROUTER, WZKLTC, routerAbi } from './v2'

const client = createPublicClient({ chain: litvm, transport: http() })
const ZKUSDC = '0xdf69970B2fE416339187aA41D39882e864984CE9'

const amounts = await client.readContract({
  address: ROUTER,
  abi: routerAbi,
  functionName: 'getAmountsOut',
  args: [parseEther('0.01'), [WZKLTC, ZKUSDC]],
})

console.log(`0.01 zkLTC → ${formatUnits(amounts[1]!, 18)} ZKUSDC`)

Swap zkLTC for a token

TypeScript
const amountIn = parseEther('0.01')
const [, quoted] = await client.readContract({
  address: ROUTER,
  abi: routerAbi,
  functionName: 'getAmountsOut',
  args: [amountIn, [WZKLTC, ZKUSDC]],
})

const minOut = (quoted! * 995n) / 1000n // 0.5% slippage
const deadline = BigInt(Math.floor(Date.now() / 1000) + 10 * 60) // 10 minutes

const hash = await wallet.writeContract({
  account,
  address: ROUTER,
  abi: routerAbi,
  functionName: 'swapExactETHForTokens',
  args: [minOut, [WZKLTC, ZKUSDC], account, deadline],
  value: amountIn,
})

Selling a token instead? Approve the router for amountIn, then call swapExactTokensForETH or swapExactTokensForTokens with the same minOut and deadline idea.

Add liquidity

TypeScript
// approve the router for the token side first
const tokenAmount = parseUnits('100', 18)
const zkltcAmount = parseEther('0.4')

await wallet.writeContract({
  account,
  address: ROUTER,
  abi: routerAbi,
  functionName: 'addLiquidityETH',
  args: [
    ZKUSDC,
    tokenAmount,
    (tokenAmount * 99n) / 100n, // min token, 1% wiggle room
    (zkltcAmount * 99n) / 100n, // min zkLTC
    account, // LP tokens go here
    deadline,
  ],
  value: zkltcAmount,
})

The router adds at the pool's current ratio and refunds any extra zkLTC. To remove, approve the pair (the LP token) to the router and call removeLiquidityETH or removeLiquidity.

Reading a pair

TypeScript
import { v2PairAddress } from './addresses' // see Contract addresses

const pair = v2PairAddress(WZKLTC, ZKUSDC)
const [token0, [reserve0, reserve1]] = await Promise.all([
  client.readContract({ address: pair, abi: pairAbi, functionName: 'token0' }),
  client.readContract({ address: pair, abi: pairAbi, functionName: 'getReserves' }),
])

The math is the classic one, if you'd rather do it yourself:

TypeScript
function getAmountOut(amountIn: bigint, reserveIn: bigint, reserveOut: bigint) {
  const withFee = amountIn * 997n
  return (withFee * reserveOut) / (reserveIn * 1000n + withFee)
}

From a contract

Same calls, from Solidity. Remember the pair, not the router, is where the reserves live.

Solidity
interface IRouter02 {
    function getAmountsOut(uint256 amountIn, address[] calldata path) external view returns (uint256[] memory);
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory);
}
Use a real minimum

amountOutMin = 0 means "I accept any price", and bots will happily give you a terrible one. Always quote first and set a minimum.