Skip to content
LitVMSwapDocs
Open app
Developers / Quickstart
Developers

Quickstart: your first swap

We'll swap 0.5 zkLTC for ZKUSDC using the Swap API and viem. The API finds the route and builds the transaction, you just sign and send it.

Install viem

Terminal
npm install viem

Any library that can send a transaction works (ethers, web3.py, a raw eth_sendTransaction). We use viem because it's small and typed.

Describe the chain

viem needs to know about LitVM once. Save this as litvm.ts:

TypeScript
import { defineChain } from 'viem'

export const litvm = defineChain({
  id: 4441,
  name: 'LitVM LiteForge',
  nativeCurrency: { name: 'zkLTC', symbol: 'zkLTC', decimals: 18 },
  rpcUrls: { default: { http: ['https://liteforge.rpc.caldera.xyz/http'] } },
  blockExplorers: {
    default: { name: 'LiteForge Explorer', url: 'https://liteforge.explorer.caldera.xyz' },
  },
  contracts: {
    multicall3: { address: '0xcA11bde05977b3631167028862bE2a173976CA11' },
  },
})

Get a quote

A quote is a free read. Great for showing a price while the user is still typing.

TypeScript
const API = 'https://www.litvmswap.com/api'
const NATIVE = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' // native zkLTC
const ZKUSDC = '0xdf69970B2fE416339187aA41D39882e864984CE9'

const params = new URLSearchParams({
  fromToken: NATIVE,
  toToken: ZKUSDC,
  amount: '0.5', // human units, not wei
  partnerFeeBps: '0', // no integrator fee for this example
})

const quote = await fetch(`${API}/partner/quote?${params}`).then((r) => r.json())
if (quote.error) throw new Error(quote.error)

console.log(`0.5 zkLTC ≈ ${quote.toAmount} ${quote.toToken.symbol} via ${quote.route.dexName}`)

Build the transaction

When the user hits the button, ask for a real transaction. This one is built for their address and their slippage, so it's the number to show right before they sign.

TypeScript
const account = '0xYourWalletAddress'

const swap = await fetch(`${API}/partner/swap`, {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    fromToken: NATIVE,
    toToken: ZKUSDC,
    amount: '0.5',
    userAddress: account,
    slippage: 0.5, // percent
    partnerFeeBps: 0,
  }),
}).then((r) => r.json())
if (swap.error) throw new Error(swap.error)

console.log(`You get about ${swap.estimate.toAmount}, at least ${swap.estimate.minAmountOut}`)

Approve (only when selling an ERC-20)

Selling native zkLTC? Skip this step, the value rides along with the transaction. Selling a token? The swap contract needs an allowance first. Approve the exact amount and only the spender the API returned.

TypeScript
import { createPublicClient, createWalletClient, custom, erc20Abi, http, parseUnits } from 'viem'
import 'viem/window' // types for window.ethereum
import { litvm } from './litvm'

const publicClient = createPublicClient({ chain: litvm, transport: http() })
const wallet = createWalletClient({ chain: litvm, transport: custom(window.ethereum!) })

async function ensureAllowance(token: `0x${string}`, owner: `0x${string}`, spender: `0x${string}`, amount: string, decimals: number) {
  const needed = parseUnits(amount, decimals)
  const current = await publicClient.readContract({
    address: token,
    abi: erc20Abi,
    functionName: 'allowance',
    args: [owner, spender],
  })
  if (current >= needed) return

  const hash = await wallet.writeContract({
    account: owner,
    address: token,
    abi: erc20Abi,
    functionName: 'approve',
    args: [spender, needed],
  })
  await publicClient.waitForTransactionReceipt({ hash })
}

Send it

TypeScript
const hash = await wallet.sendTransaction({
  account,
  to: swap.transaction.to,
  data: swap.transaction.data,
  value: BigInt(swap.transaction.value), // "0" for token sells
})

const receipt = await publicClient.waitForTransactionReceipt({ hash })
console.log(receipt.status === 'success' ? 'Swapped 🎉' : 'Reverted, see the explorer')
console.log(`https://liteforge.explorer.caldera.xyz/tx/${hash}`)

That's it

You just did a routed swap on LitVM. A few things worth doing next:

  • Show users minAmountOut, not only toAmount. That's the worst case they agreed to.
  • If the transaction reverts because the price moved, fetch a fresh /partner/swap and try again. Don't just resend the old calldata.
  • Want to earn a little on the swaps you send? See integrator fees.
Test with small amounts

Start with tiny swaps while you wire things up. A wrong token address or decimals bug costs real tokens.