Executing Swaps

The SDK builds unsigned algosdk.Transaction[] arrays. You sign them with whatever wallet your users have connected, then submit to Algod. The SDK never touches private keys.

High-Level: client.buildSwapTxns

import { TaurusClient } from '@taurusswap/sdk';
import algosdk from 'algosdk';

const client = new TaurusClient();

const txns = await client.buildSwapTxns({
  sender:      'SENDER_ADDRESS',
  fromIndex:   0,          // sell token 0 (USDC)
  toIndex:     1,          // buy  token 1 (USDT)
  amountIn:    10_000_000n, // 10 USDC
  slippageBps: 50,         // 0.5% — default if omitted
});

// txns is algosdk.Transaction[]
// Sign and submit (see Signing section below)

Low-Level: buildSwapTxns

If you manage your own Algod client and pool state:

import { readPoolState, buildSwapTxns } from '@taurusswap/sdk';

const pool = await readPoolState(algod, POOL_APP_ID);
const txns = await buildSwapTxns(
  algod,
  POOL_APP_ID,
  'SENDER_ADDRESS',
  pool,           // pass the pool state you already have
  0,              // tokenInIdx
  1,              // tokenOutIdx
  10_000_000n,    // amountInRaw
  50,             // slippageBps
);

Transaction Group Layout

The group structure depends on whether the swap crosses any ticks:

Simple swap (no tick crossings)

┌──────────────────────────────────────────┐
│ Tx 0: Budget (optional opcode top-up)    │
│ Tx 1: ASA Transfer  user → pool          │
│   amount: amountInRaw                    │
│ Tx 2: App call  swap()                   │
│   args: [tokenInIdx, tokenOutIdx,        │
│          amountInRaw, claimedOut,        │
│          minAmountOut]                   │
│   inner tx: pool → user  (output tokens) │
└──────────────────────────────────────────┘

Crossing swap (≥1 tick boundary)

┌──────────────────────────────────────────┐
│ Tx 0–k: Budget top-up (k depends on      │
│         number of crossings)             │
│ Tx k+1: ASA Transfer  user → pool        │
│ Tx k+2: App call  swap_with_crossings()  │
│   args: [tokenInIdx, tokenOutIdx,        │
│          amountInRaw, effectiveAmountIn, │
│          minAmountOut, trade_recipe]     │
│   inner tx: pool → user  (output tokens) │
└──────────────────────────────────────────┘

The SDK selects the right path automatically based on quote.ticksCrossed.

Signing: Pera Wallet

import { PeraWalletConnect } from '@perawallet/connect';

const pera = new PeraWalletConnect();

async function swap() {
  const txns = await client.buildSwapTxns({
    sender:    activeAddress,
    fromIndex: 0,
    toIndex:   1,
    amountIn:  10_000_000n,
  });

  // Pera expects an array of { txn, signers? } objects per group
  const signedTxns = await pera.signTransaction([
    txns.map((txn) => ({ txn })),
  ]);
  // signedTxns is Uint8Array[]

  const { txid } = await client.algod.sendRawTransaction(signedTxns).do();
  await algosdk.waitForConfirmation(client.algod, txid, 4);
  console.log('Confirmed:', txid);
}

Signing: use-wallet-react

import { useWallet } from '@txnlab/use-wallet-react';

function SwapButton() {
  const { activeAddress, signTransactions, algodClient } = useWallet();

  const handleSwap = async () => {
    const txns = await client.buildSwapTxns({
      sender:    activeAddress!,
      fromIndex: 0,
      toIndex:   1,
      amountIn:  10_000_000n,
    });

    // Encode to msgpack bytes for signing
    const encoded = txns.map((t) => algosdk.encodeUnsignedTransaction(t));
    const signedTxns = await signTransactions([encoded]);

    const { txid } = await algodClient.sendRawTransaction(signedTxns).do();
    await algosdk.waitForConfirmation(algodClient, txid, 4);
  };

  return <button onClick={handleSwap}>Swap</button>;
}

Fully Atomic: executeSwap

The low-level executeSwap function combines quote + build + sign + submit into one call. Useful for server-side scripts or bots where you hold the key directly:

import { executeSwap } from '@taurusswap/sdk';
import algosdk from 'algosdk';

const account = algosdk.mnemonicToSecretKey('your mnemonic here ...');

const { txId, amountOut } = await executeSwap(
  algod,
  POOL_APP_ID,
  account.addr,
  0,              // tokenInIdx
  1,              // tokenOutIdx
  10_000_000n,    // amountInRaw
  50,             // slippageBps
  async (txns) => {
    // signer callback — sign all txns and return Uint8Array[]
    return txns.map((t) => t.signTxn(account.sk));
  },
);

console.log('TX ID:', txId);
console.log('Received:', Number(amountOut) / 1e6, 'USDT');

Error Handling

import {
  TaurusClient,
  SwapTooSmallError,
  InsufficientLiquidityError,
  InvalidSlippageError,
} from '@taurusswap/sdk';

try {
  const txns = await client.buildSwapTxns({ sender, fromIndex: 0, toIndex: 1, amountIn });
  // ...sign and submit
} catch (err) {
  if (err instanceof SwapTooSmallError) {
    // amountIn is below ~1000 microunits effective after fee
    showToast('Amount too small. Try a larger trade.');
  } else if (err instanceof InsufficientLiquidityError) {
    // Trade exceeds pool depth
    showToast('Insufficient liquidity. Try a smaller amount.');
  } else if (err instanceof InvalidSlippageError) {
    // slippageBps was outside 0–10 000
    console.error('Bad slippage value');
  } else if (String(err).includes('app call rejection')) {
    // On-chain invariant check failed — pool state changed between quote and submit
    showToast('Price moved. Please refresh the quote and try again.');
  }
}

Confirmation Pattern

async function swapAndWait(txns: algosdk.Transaction[], signedTxns: Uint8Array[]) {
  const { txid } = await client.algod.sendRawTransaction(signedTxns).do();

  // waitForConfirmation polls /v2/transactions/{txid}/pending
  const result = await algosdk.waitForConfirmation(client.algod, txid, 4 /* rounds to wait */);

  console.log('Confirmed in round:', result['confirmed-round']);
  console.log('TX ID:', txid);

  // Invalidate the cache so the next quote uses fresh state
  client.invalidateCache();

  return txid;
}