Transaction Handling

Advanced patterns for building, submitting, and monitoring Solana transactions with the SDK.

Overview

SDK methods handle transaction building, signing, and confirmation automatically. For advanced flows, you can access the underlying instruction builders to compose custom transactions with full control.

Instruction builders

Every transaction method has a corresponding instruction builder under sdk.instructions. These return a TransactionInstruction without broadcasting:

typescript
// Get raw instruction
const wagerIx = await sdk.instructions.placeWagerV2({
  bet: betAddress,
  amount: 25,
  side: Outcome.For,
});

// Build your own transaction
const tx = new Transaction().add(wagerIx);
MethodInstruction builder
createUser()sdk.instructions.createUser()
initializeBetV2()sdk.instructions.initializeBetV2Ix()
placeWagerV2()sdk.instructions.placeWagerV2()
initiateVoteV2()sdk.instructions.initiateVoteV2()
placeVoteV2()sdk.instructions.placeVoteV2()
settleBetBatchV2()sdk.instructions.settleBetBatchV2()

Custom transactions

Compose multiple instructions with compute budget adjustments:

typescript
import { Transaction, ComputeBudgetProgram } from "@solana/web3.js";

async function buildCustomTransaction() {
  const computeIx = ComputeBudgetProgram.setComputeUnitLimit({
    units: 400_000,
  });

  const priorityFeeIx = ComputeBudgetProgram.setComputeUnitPrice({
    microLamports: 1000,
  });

  const wagerIx = await sdk.instructions.placeWagerV2({
    bet: betAddress,
    amount: 50,
    side: Outcome.For,
  });

  const tx = new Transaction()
    .add(computeIx)
    .add(priorityFeeIx)
    .add(wagerIx);

  const { blockhash } = await connection.getLatestBlockhash();
  tx.recentBlockhash = blockhash;
  tx.feePayer = wallet.publicKey;

  return tx;
}

Pre-instructions

Pass instructions to run before the main operation using preinstructions:

typescript
await sdk.placeWagerV2({
  bet: betAddress,
  amount: 25,
  side: Outcome.For,
  preinstructions: [
    createAssociatedTokenAccountInstruction(
      wallet.publicKey, // payer
      ata,              // ata
      wallet.publicKey, // owner
      usdcMint,         // mint
    ),
  ],
  signers: [wallet],
});

Fee payer override

Sponsor transaction fees for your users with feePayerOverride:

typescript
await sdk.placeWagerV2({
  bet: betAddress,
  amount: 25,
  side: Outcome.For,
  feePayerOverride: sponsorWallet.publicKey,
  signers: [wallet, sponsorWallet],
});

Confirmation options

Control commitment level and retry behavior per transaction:

typescript
await sdk.placeWagerV2({
  bet: betAddress,
  amount: 25,
  side: Outcome.For,
  confirmOptions: {
    commitment: "finalized",
    maxRetries: 10,
    skipPreflight: false,
    preflightCommitment: "processed",
  },
  signers: [wallet],
});

Retrying failures

Wrap SDK calls in a retry loop to handle transient network issues:

typescript
async function sendWithRetry(
  fn: () => Promise<string>,
  maxRetries = 3
): Promise<string> {
  let lastError: Error | undefined;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;

      if ((error as Error).message.includes("blockhash not found")) {
        await new Promise((r) => setTimeout(r, 1000 * (attempt + 1)));
        continue;
      }
      throw error;
    }
  }

  throw lastError;
}

// Usage
const txHash = await sendWithRetry(() =>
  sdk.placeWagerV2({ bet: betAddress, amount: 25, side: Outcome.For, signers: [wallet] })
);

Simulation

Simulate a transaction before broadcasting to catch errors early:

typescript
async function simulateTransaction(tx: Transaction) {
  const simulation = await connection.simulateTransaction(tx);

  if (simulation.value.err) {
    console.error("Simulation failed:", simulation.value.err);
    console.log("Logs:", simulation.value.logs);
    throw new Error("Transaction would fail");
  }

  console.log("Compute units used:", simulation.value.unitsConsumed);
  return simulation;
}

Batching

Send multiple wagers in parallel using Promise.allSettled():

typescript
async function batchWagers(
  bets: { address: PublicKey; amount: number; side: Outcome }[]
) {
  const results = await Promise.allSettled(
    bets.map((bet) =>
      sdk.placeWagerV2({
        bet: bet.address,
        amount: bet.amount,
        side: bet.side,
        signers: [wallet],
      })
    )
  );

  const succeeded = results.filter((r) => r.status === "fulfilled").length;
  const failed = results.filter((r) => r.status === "rejected").length;

  console.log(`${succeeded} succeeded, ${failed} failed`);
  return results;
}