From 0ef6b8210663412eeb358b4c780d8de1e80d221d Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Sat, 15 Nov 2025 12:00:57 +0100 Subject: [PATCH] feat: Hybrid RPC strategy (Helius init + Alchemy trades) CRITICAL: Fix rate limiting by using dual RPC approach Problem: - Helius RPC gets overwhelmed during trade execution (429 errors) - Exit orders fail to place, leaving positions UNPROTECTED - No on-chain TP/SL orders = unlimited risk if container crashes Solution: Hybrid RPC Strategy - Helius for Drift SDK initialization (handles burst subscriptions well) - Alchemy for trade operations (better sustained rate limits) - Falls back to Helius if Alchemy not configured Implementation: - DriftService now has two connections: connection (Helius) + tradeConnection (Alchemy) - Added getTradeConnection() method for trade operations - Updated openPosition() and closePosition() to use trade connection - Added ALCHEMY_RPC_URL to .env (optional, falls back to Helius) Benefits: - Helius: 0 subscription errors during init (proven reliable for SDK setup) - Alchemy: 300M compute units/month for sustained trade operations - Best of both worlds: reliable init + reliable trades Files: - lib/drift/client.ts: Dual connection support - lib/drift/orders.ts: Use getTradeConnection() for confirmations - .env: Added ALCHEMY_RPC_URL Testing: Deploy and execute test trade to verify orders place successfully --- .env | 2 ++ lib/drift/client.ts | 31 +++++++++++++++++++++++++++++++ lib/drift/orders.ts | 4 ++-- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/.env b/.env index 741f1fd..940f490 100644 --- a/.env +++ b/.env @@ -35,6 +35,8 @@ API_SECRET_KEY=2a344f0149442c857fb56c038c0c7d1b113883b830bec792c76f1e0efa15d6bb # Drift SDK REQUIRES WebSocket subscriptions - Alchemy doesn't support this # Alchemy "working" state was temporary - always breaks after first trade or shortly after init SOLANA_RPC_URL=https://mainnet.helius-rpc.com/?api-key=5e236449-f936-4af7-ae38-f15e2f1a3757 +# Alchemy RPC URL for trade operations (better sustained rate limits, optional) +ALCHEMY_RPC_URL=https://solana-mainnet.g.alchemy.com/v2/fDKYNe7eL83HRH5Y4xW54qg6tTk0L7y0 # Alternative RPC providers (reference): # diff --git a/lib/drift/client.ts b/lib/drift/client.ts index 50e8586..1a6eeb7 100644 --- a/lib/drift/client.ts +++ b/lib/drift/client.ts @@ -17,12 +17,14 @@ interface ManualWallet { export interface DriftConfig { rpcUrl: string + alchemyRpcUrl?: string // Optional: Use Alchemy for trade operations (better sustained rate limits) walletPrivateKey: string env: 'mainnet-beta' | 'devnet' } export class DriftService { private connection: Connection + private tradeConnection: Connection // Separate connection for trade operations (uses Alchemy if available) private wallet: ManualWallet private keypair: Keypair private driftClient: DriftClient | null = null @@ -32,8 +34,21 @@ export class DriftService { private reconnectIntervalMs: number = 4 * 60 * 60 * 1000 // 4 hours (prevent memory leak) constructor(private config: DriftConfig) { + // Helius connection for Drift SDK initialization (handles burst subscriptions well) this.connection = new Connection(config.rpcUrl, 'confirmed') + // Alchemy connection for trade operations (better sustained rate limits) + // Falls back to Helius if Alchemy not configured + this.tradeConnection = config.alchemyRpcUrl + ? new Connection(config.alchemyRpcUrl, 'confirmed') + : this.connection + + if (config.alchemyRpcUrl) { + console.log('🔀 Hybrid RPC mode: Helius for init, Alchemy for trades') + } else { + console.log('📡 Single RPC mode: Helius for all operations') + } + // Create wallet from private key // Support both formats: // 1. JSON array: [91,24,199,...] (from Phantom export as array) @@ -233,6 +248,21 @@ export class DriftService { } } + /** + * Get the Solana connection (Helius - for SDK operations) + */ + getConnection(): Connection { + return this.connection + } + + /** + * Get the trade connection (Alchemy if configured, otherwise Helius) + * Use this for all trade operations (open, close, place orders) + */ + getTradeConnection(): Connection { + return this.tradeConnection + } + /** * Get current USDC balance */ @@ -465,6 +495,7 @@ export function getDriftService(): DriftService { if (!driftServiceInstance) { const config: DriftConfig = { rpcUrl: process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com', + alchemyRpcUrl: process.env.ALCHEMY_RPC_URL, // Optional: Alchemy for trade operations walletPrivateKey: process.env.DRIFT_WALLET_PRIVATE_KEY || '', env: (process.env.DRIFT_ENV as 'mainnet-beta' | 'devnet') || 'mainnet-beta', } diff --git a/lib/drift/orders.ts b/lib/drift/orders.ts index b22ebc2..e2b2431 100644 --- a/lib/drift/orders.ts +++ b/lib/drift/orders.ts @@ -147,7 +147,7 @@ export async function openPosition( // CRITICAL: Confirm transaction actually executed on-chain console.log('⏳ Confirming transaction on-chain...') - const connection = driftService.getConnection() + const connection = driftService.getTradeConnection() // Use Alchemy for trade operations try { const confirmation = await connection.confirmTransaction(txSig, 'confirmed') @@ -559,7 +559,7 @@ export async function closePosition( // CRITICAL: Confirm transaction on-chain to prevent phantom closes // BUT: Use timeout to prevent API hangs during network congestion console.log('⏳ Confirming transaction on-chain (30s timeout)...') - const connection = driftService.getConnection() + const connection = driftService.getTradeConnection() // Use Alchemy for trade operations try { const confirmationPromise = connection.confirmTransaction(txSig, 'confirmed')