Skip to main content

Building Transactions

How to build and send transactions using the Seesaw SDK.

Transaction Basics

Transaction Structure

Basic Pattern

import {
  Connection,
  Transaction,
  sendAndConfirmTransaction,
} from '@solana/web3.js';

// 1. Build instruction
const instruction = createPlaceOrderInstruction({...});

// 2. Create transaction
const transaction = new Transaction().add(instruction);

// 3. Set recent blockhash
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
transaction.feePayer = wallet.publicKey;

// 4. Sign and send
const signature = await sendAndConfirmTransaction(
  connection,
  transaction,
  [wallet]
);

Trading Instructions

Place Order

import {
  findMarketPda,
  findOrderbookPda,
  findVaultPda,
  findPositionPda,
  findConfigPda,
  createPlaceOrderInstruction,
  OrderSide,
  OrderType,
} from '@seesaw/sdk';
import { getAssociatedTokenAddress, TOKEN_PROGRAM_ID } from '@solana/spl-token';

async function placeOrder(
  connection: Connection,
  wallet: Keypair,
  programId: PublicKey,
  marketId: bigint,
  side: OrderSide,
  priceBps: number,
  quantity: bigint
) {
  // 1. Derive PDAs
  const [configPda] = findConfigPda(programId);
  const [marketPda] = findMarketPda(marketId, programId);
  const [orderbookPda] = findOrderbookPda(marketPda, programId);
  const [vaultPda] = findVaultPda(marketPda, programId);
  const [positionPda] = findPositionPda(marketPda, wallet.publicKey, programId);

  // 2. Get market to find settlement mint
  const marketAccount = await connection.getAccountInfo(marketPda);
  const market = parseMarket(marketAccount!.data);

  // 3. Get token accounts
  const userTokenAccount = await getAssociatedTokenAddress(market.settlementMint, wallet.publicKey);

  const treasuryTokenAccount = await getAssociatedTokenAddress(
    market.settlementMint,
    configPda, // Treasury is config PDA
    true // Allow PDA owner
  );

  // 4. Build instruction
  const placeOrderIx = createPlaceOrderInstruction(
    {
      market: marketPda,
      orderbook: orderbookPda,
      position: positionPda,
      userTokenAccount,
      vault: vaultPda,
      user: wallet.publicKey,
      config: configPda,
      treasuryTokenAccount,
      tokenProgram: TOKEN_PROGRAM_ID,
      systemProgram: SystemProgram.programId,
    },
    {
      side,
      priceBps,
      quantity,
      orderType: OrderType.Limit,
    },
    programId
  );

  // 5. Send transaction
  const tx = new Transaction().add(placeOrderIx);
  const signature = await sendAndConfirmTransaction(connection, tx, [wallet]);

  return signature;
}

// Usage
await placeOrder(
  connection,
  wallet,
  programId,
  marketId,
  OrderSide.BuyYes,
  6000, // 60%
  BigInt(100)
);

Cancel Order

import { createCancelOrderInstruction } from '@seesaw/sdk';

async function cancelOrder(
  connection: Connection,
  wallet: Keypair,
  programId: PublicKey,
  marketPda: PublicKey,
  orderId: bigint
) {
  // 1. Derive PDAs
  const [orderbookPda] = findOrderbookPda(marketPda, programId);
  const [positionPda] = findPositionPda(marketPda, wallet.publicKey, programId);
  const [vaultPda] = findVaultPda(marketPda, programId);

  // 2. Get market for mint
  const marketAccount = await connection.getAccountInfo(marketPda);
  const market = parseMarket(marketAccount!.data);

  // 3. Get user token account
  const userTokenAccount = await getAssociatedTokenAddress(market.settlementMint, wallet.publicKey);

  // 4. Build instruction
  const cancelOrderIx = createCancelOrderInstruction(
    {
      market: marketPda,
      orderbook: orderbookPda,
      position: positionPda,
      userTokenAccount,
      vault: vaultPda,
      user: wallet.publicKey,
      tokenProgram: TOKEN_PROGRAM_ID,
    },
    {
      orderId,
    },
    programId
  );

  // 5. Send transaction
  const tx = new Transaction().add(cancelOrderIx);
  const signature = await sendAndConfirmTransaction(connection, tx, [wallet]);

  return signature;
}

Settlement Instructions

Settle Position

import { createSettlePositionInstruction } from '@seesaw/sdk';

async function settlePosition(
  connection: Connection,
  cranker: Keypair, // Or any signer
  programId: PublicKey,
  marketPda: PublicKey,
  userToSettle: PublicKey
) {
  // 1. Derive PDAs
  const [configPda] = findConfigPda(programId);
  const [positionPda] = findPositionPda(marketPda, userToSettle, programId);
  const [vaultPda] = findVaultPda(marketPda, programId);

  // 2. Get market for mint
  const marketAccount = await connection.getAccountInfo(marketPda);
  const market = parseMarket(marketAccount!.data);

  // 3. Get user token account
  const userTokenAccount = await getAssociatedTokenAddress(market.settlementMint, userToSettle);

  // 4. Get treasury
  const treasury = await connection.getAccountInfo(configPda);
  const config = parseConfig(treasury!.data);

  // 5. Build instruction
  const settleIx = createSettlePositionInstruction(
    {
      market: marketPda,
      position: positionPda,
      userTokenAccount,
      vault: vaultPda,
      user: userToSettle,
      cranker: cranker.publicKey,
      config: configPda,
      treasury: config.treasury,
      tokenProgram: TOKEN_PROGRAM_ID,
    },
    programId
  );

  // 6. Send transaction
  const tx = new Transaction().add(settleIx);
  const signature = await sendAndConfirmTransaction(connection, tx, [cranker]);

  return signature;
}

Crank Instructions

Snapshot Start

import { createSnapshotStartInstruction } from '@seesaw/sdk';

async function snapshotStart(
  connection: Connection,
  cranker: Keypair,
  programId: PublicKey,
  marketPda: PublicKey
) {
  // 1. Get market
  const marketAccount = await connection.getAccountInfo(marketPda);
  const market = parseMarket(marketAccount!.data);

  // 2. Derive PDAs
  const [configPda] = findConfigPda(programId);
  const config = parseConfig((await connection.getAccountInfo(configPda))!.data);

  // 3. Build instruction
  const snapshotIx = createSnapshotStartInstruction(
    {
      market: marketPda,
      pythFeed: market.pythFeed,
      cranker: cranker.publicKey,
      config: configPda,
      treasury: config.treasury,
      systemProgram: SystemProgram.programId,
    },
    programId
  );

  // 4. Send
  const tx = new Transaction().add(snapshotIx);
  return sendAndConfirmTransaction(connection, tx, [cranker]);
}

Resolve Market

import { createResolveMarketInstruction } from '@seesaw/sdk';

async function resolveMarket(
  connection: Connection,
  cranker: Keypair,
  programId: PublicKey,
  marketPda: PublicKey
) {
  const [configPda] = findConfigPda(programId);
  const config = parseConfig((await connection.getAccountInfo(configPda))!.data);

  const resolveIx = createResolveMarketInstruction(
    {
      market: marketPda,
      cranker: cranker.publicKey,
      config: configPda,
      treasury: config.treasury,
      systemProgram: SystemProgram.programId,
    },
    programId
  );

  const tx = new Transaction().add(resolveIx);
  return sendAndConfirmTransaction(connection, tx, [cranker]);
}

Transaction Options

Compute Budget

For complex transactions, request more compute:

import { ComputeBudgetProgram } from '@solana/web3.js';

const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
  units: 200_000,
});

const tx = new Transaction().add(modifyComputeUnits).add(placeOrderIx);

Priority Fees

During congestion, add priority fees:

const addPriorityFee = ComputeBudgetProgram.setComputeUnitPrice({
  microLamports: 10_000, // Adjust based on network conditions
});

const tx = new Transaction().add(addPriorityFee).add(placeOrderIx);

Confirmation Options

// Wait for specific confirmation level
const signature = await sendAndConfirmTransaction(connection, transaction, [wallet], {
  commitment: 'finalized', // or 'confirmed', 'processed'
  preflightCommitment: 'confirmed',
});

Error Handling

Handling Transaction Errors

import { SendTransactionError } from '@solana/web3.js';

try {
  const signature = await sendAndConfirmTransaction(connection, transaction, [wallet]);
} catch (error) {
  if (error instanceof SendTransactionError) {
    console.error('Transaction failed:', error.logs);

    // Parse program error
    const logs = error.logs?.join('\n') || '';

    if (logs.includes('TradingEnded')) {
      console.error('Market trading has ended');
    } else if (logs.includes('InsufficientCollateral')) {
      console.error('Not enough USDC for this order');
    } else if (logs.includes('InsufficientShares')) {
      console.error('Not enough shares to sell');
    }
  }
  throw error;
}

Retry Logic

async function sendWithRetry(
  connection: Connection,
  transaction: Transaction,
  signers: Keypair[],
  maxRetries = 3
): Promise<string> {
  let lastError: Error | null = null;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      // Refresh blockhash on retry
      if (attempt > 0) {
        const { blockhash } = await connection.getLatestBlockhash();
        transaction.recentBlockhash = blockhash;
      }

      return await sendAndConfirmTransaction(connection, transaction, signers);
    } catch (error) {
      lastError = error as Error;

      // Don't retry program errors
      if (error instanceof SendTransactionError) {
        throw error;
      }

      // Wait before retry
      await new Promise((r) => setTimeout(r, 1000 * (attempt + 1)));
    }
  }

  throw lastError;
}

Batching Transactions

Multiple Instructions

// Combine related operations in one transaction
const tx = new Transaction()
  .add(cancelOrderIx) // Cancel existing order
  .add(placeOrderIx); // Place new order

const signature = await sendAndConfirmTransaction(connection, tx, [wallet]);

Transaction Size Limits

A single transaction has limits:

  • 1232 bytes for signatures and instructions
  • 200,000 compute units default (can request up to 1.4M)

For large operations, split across transactions:

async function settleAllPositions(
  positions: PublicKey[],
  connection: Connection,
  cranker: Keypair
) {
  const BATCH_SIZE = 5; // Positions per transaction

  for (let i = 0; i < positions.length; i += BATCH_SIZE) {
    const batch = positions.slice(i, i + BATCH_SIZE);
    const tx = new Transaction();

    for (const position of batch) {
      const settleIx = createSettlePositionInstruction({...});
      tx.add(settleIx);
    }

    await sendAndConfirmTransaction(connection, tx, [cranker]);
  }
}

Versioned Transactions

For advanced use cases, use versioned transactions:

import { TransactionMessage, VersionedTransaction } from '@solana/web3.js';

// Build message
const message = new TransactionMessage({
  payerKey: wallet.publicKey,
  recentBlockhash: blockhash,
  instructions: [placeOrderIx],
}).compileToV0Message();

// Create versioned transaction
const versionedTx = new VersionedTransaction(message);
versionedTx.sign([wallet]);

// Send
const signature = await connection.sendTransaction(versionedTx);
await connection.confirmTransaction(signature);

Next Steps