Solana 示例#
创建兑换指令#
// swap.ts
import { client } from './DexClient';
/**
* Example: Execute a swap from SOL to USDC
*/
async function executeSwap() {
try {
if (!process.env.SOLANA_PRIVATE_KEY) {
throw new Error('Missing SOLANA_PRIVATE_KEY in .env file');
}
// Get quote to fetch token information
console.log("Getting token information...");
const quote = await client.dex.getQuote({
chainId: '501',
fromTokenAddress: '11111111111111111111111111111111', // SOL
toTokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
amount: '1000000', // Small amount for quote
slippage: '0.5'
});
const tokenInfo = {
fromToken: {
symbol: quote.data[0].fromToken.tokenSymbol,
decimals: parseInt(quote.data[0].fromToken.decimal),
price: quote.data[0].fromToken.tokenUnitPrice
},
toToken: {
symbol: quote.data[0].toToken.tokenSymbol,
decimals: parseInt(quote.data[0].toToken.decimal),
price: quote.data[0].toToken.tokenUnitPrice
}
};
// Convert amount to base units (for display purposes)
const humanReadableAmount = 0.1; // 0.1 SOL
const rawAmount = (humanReadableAmount * Math.pow(10, tokenInfo.fromToken.decimals)).toString();
console.log("\nSwap Details:");
console.log("--------------------");
console.log(`From: ${tokenInfo.fromToken.symbol}`);
console.log(`To: ${tokenInfo.toToken.symbol}`);
console.log(`Amount: ${humanReadableAmount} ${tokenInfo.fromToken.symbol}`);
console.log(`Amount in base units: ${rawAmount}`);
console.log(`Approximate USD value: $${(humanReadableAmount * parseFloat(tokenInfo.fromToken.price)).toFixed(2)}`);
// Execute the swap
console.log("\nExecuting swap...");
const swapResult = await client.dex.executeSwap({
chainId: '501', // Solana chain ID
fromTokenAddress: '11111111111111111111111111111111', // SOL
toTokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
amount: rawAmount,
slippage: '0.5', // 0.5% slippage
userWalletAddress: process.env.SOLANA_WALLET_ADDRESS!
});
console.log('Swap executed successfully:');
console.log(JSON.stringify(swapResult, null, 2));
return swapResult;
} catch (error) {
if (error instanceof Error) {
console.error('Error executing swap:', error.message);
// API errors include details in the message
if (error.message.includes('API Error:')) {
const match = error.message.match(/API Error: (.*)/);
if (match) console.error('API Error Details:', match[1]);
}
}
throw error;
}
}
// Run if this file is executed directly
if (require.main === module) {
executeSwap()
.then(() => process.exit(0))
.catch((error) => {
console.error('Error:', error);
process.exit(1);
});
}
export { executeSwap };
获取报价#
const quote = await client.dex.getQuote({
chainId: '501', // Solana
fromTokenAddress: '11111111111111111111111111111111', // SOL
toTokenAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC
amount: '100000000', // 0.1 SOL (in lamports)
slippage: '0.5' // 0.5%
});
执行兑换指令#
导入以下代码库
// 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
连线和钱包初始化
// 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))
);
设置兑换参数#
// 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
};
兑换指令#
// 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));
}
地址查找表#
// 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);
}
创建并签署交易#
// 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]);
执行交易#
// 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!");