Trade API

Advanced Control With Swap-Instructions On Solana#

The swap-instruction endpoint offers more control over the swap process than the standard /swap endpoint. While /swap gives you a pre-built transaction ready to sign, swap-instruction lets you:

  • Build custom transaction signing flows
  • Handle instruction processing your own way
  • Add your own instructions to the transaction if needed
  • Work with lookup tables directly for optimizing transaction size

1. Set Up Your Environment#

Import the necessary libraries:

// Required Solana dependencies for DEX interaction
import {
    Connection,          // Handles RPC connections to Solana network
    Keypair,             // Manages wallet keypairs for signing
    PublicKey,           // Handles Solana public key conversion and validation
    TransactionInstruction,    // Core transaction instruction type
    TransactionMessage,        // Builds transaction messages (v0 format)
    VersionedTransaction,      // Supports newer transaction format with lookup tables
    RpcResponseAndContext,     // RPC response wrapper type
    SimulatedTransactionResponse,  // Simulation result type
    AddressLookupTableAccount,     // For transaction size optimization
    PublicKeyInitData              // Public key input type
} from "@solana/web3.js";
import base58 from "bs58";    // Required for private key decoding

2. Initialize Your Connection and Wallet#

Set up your connection and wallet instance:

// Note: Consider using a reliable RPC endpoint with high rate limits for production
const connection = new Connection(
    process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com"
);
// Initialize wallet for signing
// This wallet will be the fee payer and transaction signer
const wallet = Keypair.fromSecretKey(
    Uint8Array.from(base58.decode(userPrivateKey))
);

3. Configure Swap Parameters#

Set up the parameters for your swap:

// Configure swap parameters
const baseUrl = "https://web3.okx.com/api/v5/dex/aggregator/swap-instruction";
const params = {
    chainId: "501",              // Solana mainnet chain ID
    feePercent: "1",             // Platform fee percentage
    amount: "1000000",           // Amount in smallest denomination (lamports for SOL)
    fromTokenAddress: "11111111111111111111111111111111",  // SOL mint address
    toTokenAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",  // USDC mint address
    slippage: "0.1",             // Slippage tolerance in percentage
    userWalletAddress: userAddress,   // Wallet performing the swap
    priceTolerance: "0",         // Maximum allowed price impact
    autoSlippage: "false",       // Use fixed slippage instead of auto
    pathNum: "3"                 // Maximum routes to consider
};

4. Process Swap Instructions#

Fetch and process the swap instructions:

// Helper function to convert DEX API instructions to Solana format
function createTransactionInstruction(instruction) {
    return new TransactionInstruction({
        programId: new PublicKey(instruction.programId),  // DEX program ID
        keys: instruction.accounts.map((key) => ({
            pubkey: new PublicKey(key.pubkey),    // Account address
            isSigner: key.isSigner,     // True if account must sign tx
            isWritable: key.isWritable  // True if instruction modifies account
        })),
        data: Buffer.from(instruction.data, 'base64')  // Instruction parameters
    });
}
// Fetch optimal swap route and instructions from DEX
const timestamp = new Date().toISOString();
const requestPath = "/api/v5/dex/aggregator/swap-instruction";
const queryString = "?" + new URLSearchParams(params).toString();
const headers = getHeaders(timestamp, "GET", requestPath, queryString);
const response = await fetch(
    `https://web3.okx.com${requestPath}${queryString}`,
    { method: 'GET', headers }
);
const { data } = await response.json();
const { instructionLists, addressLookupTableAccount } = data;
// Process DEX instructions into Solana-compatible format
const instructions = [];
// Remove duplicate lookup table addresses returned by DEX
const uniqueLookupTables = Array.from(new Set(addressLookupTableAccount));
console.log("Lookup tables to load:", uniqueLookupTables);
// Convert each DEX instruction to Solana format
if (instructionLists?.length) {
    instructions.push(...instructionLists.map(createTransactionInstruction));
}

5. Handle Address Lookup Tables#

Process the address lookup tables for transaction optimization:

// Process lookup tables for transaction optimization
// Lookup tables are crucial for complex swaps that interact with many accounts
// They significantly reduce transaction size and cost
const addressLookupTableAccounts = [];
if (uniqueLookupTables?.length > 0) {
    console.log("Loading address lookup tables...");
    // Fetch all lookup tables in parallel for better performance
    const lookupTableAccounts = await Promise.all(
        uniqueLookupTables.map(async (address) => {
            const pubkey = new PublicKey(address);
            // Get lookup table account data from Solana
            const account = await connection
                .getAddressLookupTable(pubkey)
                .then((res) => res.value);
            if (!account) {
                throw new Error(`Could not fetch lookup table account ${address}`);
            }
            return account;
        })
    );
    addressLookupTableAccounts.push(...lookupTableAccounts);
}

6. Create and Sign Transaction#

Create the transaction message and sign it:

// Get recent blockhash for transaction timing and uniqueness
const latestBlockhash = await connection.getLatestBlockhash('finalized');
// Create versioned transaction message (V0 format required for lookup table support)
const messageV0 = new TransactionMessage({
    payerKey: wallet.publicKey,     // Fee payer address
    recentBlockhash: latestBlockhash.blockhash,  // Transaction timing
    instructions                     // Swap instructions from DEX
}).compileToV0Message(addressLookupTableAccounts);  // Include lookup tables
// Create new versioned transaction with optimizations
const transaction = new VersionedTransaction(messageV0);
// Simulate transaction to check for errors
// This helps catch issues before paying fees
const result = await connection.simulateTransaction(transaction);
// Sign transaction with fee payer wallet
transaction.sign([wallet]);

7. Execute Transaction#

Finally, simulate and send the transaction:

// Send transaction to Solana
// skipPreflight=false ensures additional validation
// maxRetries helps handle network issues
const txId = await connection.sendRawTransaction(transaction.serialize(), {
    skipPreflight: false,  // Run preflight validation
    maxRetries: 5         // Retry on failure
});
// Log transaction results
console.log("Transaction ID:", txId);
console.log("Explorer URL:", `https://solscan.io/tx/${txId}`);
// Wait for confirmation
await connection.confirmTransaction({
    signature: txId,
    blockhash: latestBlockhash.blockhash,
    lastValidBlockHeight: latestBlockhash.lastValidBlockHeight
});
console.log("Transaction confirmed!");

Best Practices and Considerations#

When implementing swap instructions, keep these key points in mind:

  • Error Handling: Always implement proper error handling for API responses and transaction simulation results.
  • Slippage Protection: Choose appropriate slippage parameters based on your use case and market conditions.
  • Gas Optimization: Use address lookup tables when available to reduce transaction size and costs.
  • Transaction Simulation: Always simulate transactions before sending them to catch potential issues early.
  • Retry Logic: Implement proper retry mechanisms for failed transactions with appropriate backoff strategies. MEV Protection Trading on Solana comes with MEV (Maximal Extractable Value) risks. While the MEV protection is not directly included in the SDK, you can implement it yourself using the API-first approach.