This guide explains how to create a new transaction builder for the swap API system. Transaction builders are responsible for creating swap transactions for different DEX protocols on Solana.
The swap API uses a builder pattern to handle different DEX protocols. Each protocol has its own builder that implements the ITransactionBuilder interface and extends the BaseTransactionBuilder abstract class.
ITransactionBuilder: Base interface that all builders must implementBaseTransactionBuilder: Abstract base class providing common functionalityTransactionBuilderRegistry: Registry that manages and provides access to all builders
// Input parameters for swap transactions
interface SwapParams {
mint: string; // Token mint address
signer: string; // User's wallet address
type: 'buy' | 'sell'; // Transaction type
inputAmount?: number; // Input amount in lamports/tokens
outputAmount?: number; // Expected output amount
slippageBps: number; // Slippage tolerance in basis points
trade: {
mint: string; // Token mint (same as above)
pool: string; // Pool address
avgPrice: number; // Average price
programId: string; // DEX program ID
slot: string; // Slot information
};
}
// Output transaction structure
interface SwapTransaction {
transactionId: string;
status: 'pending' | 'confirmed' | 'failed';
instructions: SwapInstruction[];
}
// Individual instruction format
interface SwapInstruction {
programId: string;
accounts: Array<{
pubkey: string;
isSigner: boolean;
isWritable: boolean;
}>;
data: string; // Base64 encoded instruction data
}Create a new file in the src/builders/ directory:
// src/builders/YourProtocolTransactionBuilder.ts
import { BaseTransactionBuilder, SwapParams, SwapTransaction, SwapInstruction } from '../TransactionBuilder';
import { Connection, PublicKey, TransactionInstruction } from '@solana/web3.js';
// Import your protocol's SDK
export class YourProtocolTransactionBuilder extends BaseTransactionBuilder {
// Define your protocol's program ID
programId = 'YourProgramIdHere';
private connection: Connection;
constructor(connection: Connection) {
super();
this.connection = connection;
}
async buildSwapTransaction(params: SwapParams): Promise<SwapTransaction> {
const timestamp = Date.now();
const random = Math.random().toString(36).substring(2, 15);
const transactionId = `tx_${timestamp}_${random}`;
const instructions = await this.createSwapInstructions(params);
return {
transactionId,
status: 'pending',
instructions
};
}
private async createSwapInstructions(params: SwapParams): Promise<SwapInstruction[]> {
// Implement your protocol-specific logic here
// Return array of SwapInstruction objects
}
private convertToSwapInstruction(instruction: TransactionInstruction): SwapInstruction {
return {
programId: instruction.programId.toString(),
accounts: instruction.keys.map(key => ({
pubkey: key.pubkey.toString(),
isSigner: key.isSigner,
isWritable: key.isWritable
})),
data: Buffer.from(instruction.data).toString('base64')
};
}
}The createSwapInstructions method should:
-
Parse input parameters:
const mint = new PublicKey(params.mint); const user = new PublicKey(params.signer); const pool = new PublicKey(params.trade.pool);
-
Handle token accounts:
// Get or create associated token accounts const userTokenAccount = await getAssociatedTokenAddress(mint, user); // Check if account exists and create if needed const tokenAccountInfo = await this.connection.getAccountInfo(userTokenAccount); if (!tokenAccountInfo) { const createInstruction = createAssociatedTokenAccountInstruction( user, userTokenAccount, user, mint ); instructions.push(createInstruction); }
-
Create protocol-specific swap instructions:
// Use your protocol's SDK to create swap instructions const swapInstruction = await yourProtocolSdk.createSwapInstruction({ pool, user, inputAmount: params.inputAmount, outputAmount: params.outputAmount, slippage: params.slippageBps }); instructions.push(swapInstruction);
-
Convert and return instructions:
return instructions.map(ix => this.convertToSwapInstruction(ix));
Add your builder to the registry in src/builders/TransactionBuilderRegistry.ts:
// Import your builder
import { YourProtocolTransactionBuilder } from './YourProtocolTransactionBuilder';
// In the registerBuilders() method:
private registerBuilders(): void {
// ... existing builders ...
// Register your builder
const yourProtocolBuilder = new YourProtocolTransactionBuilder(this.connection);
this.builders.set(yourProtocolBuilder.programId, yourProtocolBuilder);
}
// Update getBuilderInfo() method to include your protocol:
case 'YourProgramIdHere':
name = 'YourProtocolName';
break;Add protocol-specific helper methods:
// Static method to check if a program ID belongs to your protocol
public static isYourProtocolProgram(programId: string): boolean {
return programId === 'YourProgramIdHere';
}
// Helper methods for your protocol
public getPoolAddress(mint: string): string {
// Implementation specific to your protocol
}
public calculateSlippage(amount: number, slippageBps: number): number {
return amount * (10000 - slippageBps) / 10000;
}private async createSwapInstructions(params: SwapParams): Promise<SwapInstruction[]> {
try {
// Your implementation
} catch (error) {
console.error(`Error creating ${this.constructor.name} instructions:`, error);
throw new Error(`Failed to create swap instructions: ${error.message}`);
}
}private validateParams(params: SwapParams): void {
if (!params.mint || !PublicKey.isOnCurve(params.mint)) {
throw new Error('Invalid mint address');
}
if (!params.signer || !PublicKey.isOnCurve(params.signer)) {
throw new Error('Invalid signer address');
}
if (params.slippageBps < 0 || params.slippageBps > 10000) {
throw new Error('Invalid slippage value');
}
}// Cache SDK instances
private protocolSdk: YourProtocolSdk | null = null;
private async getProtocolSdk(): Promise<YourProtocolSdk> {
if (!this.protocolSdk) {
this.protocolSdk = new YourProtocolSdk(this.connection);
}
return this.protocolSdk;
}- Unit Tests: Create tests for your builder's methods
- Integration Tests: Test with actual Solana devnet
- Manual Testing: Use the API endpoints to test your builder
// Example test
const builder = new YourProtocolTransactionBuilder(connection);
const params: SwapParams = {
mint: 'TokenMintAddress',
signer: 'UserWalletAddress',
type: 'buy',
inputAmount: 1000000, // 1 SOL in lamports
slippageBps: 100, // 1% slippage
trade: {
mint: 'TokenMintAddress',
pool: 'PoolAddress',
avgPrice: 0.001,
programId: 'YourProgramIdHere',
slot: '12345'
}
};
const transaction = await builder.buildSwapTransaction(params);
console.log('Transaction created:', transaction);Refer to existing builders for implementation examples:
- PumpFunTransactionBuilder: Simple bonding curve implementation
- DBCTransactionBuilder: Dynamic bonding curve with complex account management
- LaunchpadTransactionBuilder: Raydium launchpad integration
- PumpSwapTransactionBuilder: AMM-style swaps
Most builders need to handle associated token account creation:
const userTokenAccount = await getAssociatedTokenAddress(mint, user);
const accountInfo = await this.connection.getAccountInfo(userTokenAccount);
if (!accountInfo) {
instructions.push(createAssociatedTokenAccountInstruction(
user, userTokenAccount, user, mint
));
}For SOL-based swaps, you'll often need to wrap/unwrap SOL:
// Create WSOL account
const wsolAccount = await getAssociatedTokenAddress(NATIVE_MINT, user);
// Add wrap/unwrap instructions as neededconst minAmountOut = expectedAmount * (10000 - slippageBps) / 10000;- Account not found: Ensure all required accounts are created
- Insufficient funds: Validate input amounts and account balances
- Invalid instruction data: Check SDK usage and parameter formatting
- Program errors: Verify program IDs and account permissions
When contributing a new builder:
- Follow the existing code style and patterns
- Add comprehensive error handling
- Include helper methods for common operations
- Update the registry to include your builder
- Add tests for your implementation
- Document any protocol-specific requirements
Your builder will automatically be available through the API once registered in the TransactionBuilderRegistry.