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
- See complete Examples
- Review API Reference