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:
// 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);| Method | Instruction 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:
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:
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:
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:
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:
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:
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():
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;
}