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
This commit is contained in:
2
.env
2
.env
@@ -35,6 +35,8 @@ API_SECRET_KEY=2a344f0149442c857fb56c038c0c7d1b113883b830bec792c76f1e0efa15d6bb
|
|||||||
# Drift SDK REQUIRES WebSocket subscriptions - Alchemy doesn't support this
|
# Drift SDK REQUIRES WebSocket subscriptions - Alchemy doesn't support this
|
||||||
# Alchemy "working" state was temporary - always breaks after first trade or shortly after init
|
# 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
|
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):
|
# Alternative RPC providers (reference):
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -17,12 +17,14 @@ interface ManualWallet {
|
|||||||
|
|
||||||
export interface DriftConfig {
|
export interface DriftConfig {
|
||||||
rpcUrl: string
|
rpcUrl: string
|
||||||
|
alchemyRpcUrl?: string // Optional: Use Alchemy for trade operations (better sustained rate limits)
|
||||||
walletPrivateKey: string
|
walletPrivateKey: string
|
||||||
env: 'mainnet-beta' | 'devnet'
|
env: 'mainnet-beta' | 'devnet'
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DriftService {
|
export class DriftService {
|
||||||
private connection: Connection
|
private connection: Connection
|
||||||
|
private tradeConnection: Connection // Separate connection for trade operations (uses Alchemy if available)
|
||||||
private wallet: ManualWallet
|
private wallet: ManualWallet
|
||||||
private keypair: Keypair
|
private keypair: Keypair
|
||||||
private driftClient: DriftClient | null = null
|
private driftClient: DriftClient | null = null
|
||||||
@@ -32,8 +34,21 @@ export class DriftService {
|
|||||||
private reconnectIntervalMs: number = 4 * 60 * 60 * 1000 // 4 hours (prevent memory leak)
|
private reconnectIntervalMs: number = 4 * 60 * 60 * 1000 // 4 hours (prevent memory leak)
|
||||||
|
|
||||||
constructor(private config: DriftConfig) {
|
constructor(private config: DriftConfig) {
|
||||||
|
// Helius connection for Drift SDK initialization (handles burst subscriptions well)
|
||||||
this.connection = new Connection(config.rpcUrl, 'confirmed')
|
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
|
// Create wallet from private key
|
||||||
// Support both formats:
|
// Support both formats:
|
||||||
// 1. JSON array: [91,24,199,...] (from Phantom export as array)
|
// 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
|
* Get current USDC balance
|
||||||
*/
|
*/
|
||||||
@@ -465,6 +495,7 @@ export function getDriftService(): DriftService {
|
|||||||
if (!driftServiceInstance) {
|
if (!driftServiceInstance) {
|
||||||
const config: DriftConfig = {
|
const config: DriftConfig = {
|
||||||
rpcUrl: process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com',
|
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 || '',
|
walletPrivateKey: process.env.DRIFT_WALLET_PRIVATE_KEY || '',
|
||||||
env: (process.env.DRIFT_ENV as 'mainnet-beta' | 'devnet') || 'mainnet-beta',
|
env: (process.env.DRIFT_ENV as 'mainnet-beta' | 'devnet') || 'mainnet-beta',
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export async function openPosition(
|
|||||||
|
|
||||||
// CRITICAL: Confirm transaction actually executed on-chain
|
// CRITICAL: Confirm transaction actually executed on-chain
|
||||||
console.log('⏳ Confirming transaction on-chain...')
|
console.log('⏳ Confirming transaction on-chain...')
|
||||||
const connection = driftService.getConnection()
|
const connection = driftService.getTradeConnection() // Use Alchemy for trade operations
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const confirmation = await connection.confirmTransaction(txSig, 'confirmed')
|
const confirmation = await connection.confirmTransaction(txSig, 'confirmed')
|
||||||
@@ -559,7 +559,7 @@ export async function closePosition(
|
|||||||
// CRITICAL: Confirm transaction on-chain to prevent phantom closes
|
// CRITICAL: Confirm transaction on-chain to prevent phantom closes
|
||||||
// BUT: Use timeout to prevent API hangs during network congestion
|
// BUT: Use timeout to prevent API hangs during network congestion
|
||||||
console.log('⏳ Confirming transaction on-chain (30s timeout)...')
|
console.log('⏳ Confirming transaction on-chain (30s timeout)...')
|
||||||
const connection = driftService.getConnection()
|
const connection = driftService.getTradeConnection() // Use Alchemy for trade operations
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const confirmationPromise = connection.confirmTransaction(txSig, 'confirmed')
|
const confirmationPromise = connection.confirmTransaction(txSig, 'confirmed')
|
||||||
|
|||||||
Reference in New Issue
Block a user