/** * Drift Market Discovery Script * * Queries the Drift SDK to list all available perpetual markets with their * complete specifications including market index, oracle address, and order sizes. * * Usage: * npm run discover-markets * npx tsx scripts/discover-drift-markets.ts * npx tsx scripts/discover-drift-markets.ts FARTCOIN # Search for specific symbol */ import { initializeDriftService, getDriftService } from '../lib/drift/client' import { OracleSource } from '@drift-labs/sdk' // Helper to decode market name from bytes (Drift stores names as 32-byte arrays) function decodeMarketName(nameBytes: number[] | Uint8Array | undefined): string { if (!nameBytes) return '' // Convert to string and trim null bytes const bytes = Array.isArray(nameBytes) ? nameBytes : Array.from(nameBytes) const name = String.fromCharCode(...bytes.filter(b => b !== 0)) return name.trim() } // Map oracle source enum to human-readable string function getOracleSourceName(oracleSource: OracleSource | number): string { // OracleSource enum values from Drift SDK const sourceMap: Record = { 0: 'Pyth', 1: 'Switchboard', 2: 'QuoteAsset', 3: 'Pyth1K', 4: 'Pyth1M', 5: 'PythStableCoin', 6: 'Prelaunch', 7: 'PythPull', 8: 'Pyth1KPull', 9: 'Pyth1MPull', 10: 'PythStableCoinPull', 11: 'SwitchboardOnDemand', } // Handle both enum and number types const sourceValue = typeof oracleSource === 'number' ? oracleSource : Object.values(oracleSource)[0] return sourceMap[sourceValue as number] || `Unknown(${sourceValue})` } // Format number with appropriate decimals function formatNumber(value: number, decimals: number = 6): string { if (value === 0) return '0' if (value < 0.0001) return value.toExponential(2) return value.toFixed(decimals).replace(/\.?0+$/, '') } interface MarketInfo { index: number symbol: string oracleSource: string minOrderSize: number orderStepSize: number tickSize: number oracleAddress: string contractType: string imfFactor: number marginRatioInitial: number marginRatioMaintenance: number status: string } async function discoverMarkets(searchSymbol?: string): Promise { console.log('\nšŸ” Discovering Drift Protocol Markets...\n') try { // Initialize Drift service await initializeDriftService() const driftService = getDriftService() const driftClient = driftService.getClient() console.log('āœ… Connected to Drift Protocol\n') // Get all perp market accounts const perpMarketAccounts = driftClient.getPerpMarketAccounts() if (!perpMarketAccounts || perpMarketAccounts.length === 0) { console.log('āŒ No perpetual markets found') return } console.log(`šŸ“Š Found ${perpMarketAccounts.length} Perpetual Markets:\n`) // Collect market information const markets: MarketInfo[] = [] let foundSearchSymbol: MarketInfo | null = null for (let i = 0; i < perpMarketAccounts.length; i++) { const market = perpMarketAccounts[i] if (!market) continue // Extract market name from the name field (stored as bytes) let symbol = decodeMarketName(market.name as unknown as number[]) if (!symbol) { symbol = `Market-${i}` } // Get oracle information const oracleAddress = market.amm?.oracle?.toString() || 'N/A' const oracleSource = market.amm?.oracleSource // Get order sizing (values are stored with different precision) // Base asset precision is typically 9 decimals const minOrderSize = market.amm?.minOrderSize ? Number(market.amm.minOrderSize) / 1e9 : 0 const orderStepSize = market.amm?.orderStepSize ? Number(market.amm.orderStepSize) / 1e9 : 0 // Tick size for price (quote asset precision is 6 decimals for USDC) const tickSize = market.amm?.orderTickSize ? Number(market.amm.orderTickSize) / 1e6 : 0.0001 // Get margin requirements (stored as percentages with 4 decimal precision) const marginRatioInitial = market.marginRatioInitial ? Number(market.marginRatioInitial) / 10000 : 0 const marginRatioMaintenance = market.marginRatioMaintenance ? Number(market.marginRatioMaintenance) / 10000 : 0 // IMF factor (insurance margin fraction) const imfFactor = market.imfFactor ? Number(market.imfFactor) / 1e6 : 0 // Market status const statusMap: Record = { 0: 'Initialized', 1: 'Active', 2: 'FundingPaused', 3: 'AmmPaused', 4: 'FillPaused', 5: 'WithdrawPaused', 6: 'ReduceOnly', 7: 'Settlement', 8: 'Delisted', } const status = statusMap[Object.values(market.status)[0] as number] || 'Unknown' // Contract type const contractTypeMap: Record = { 0: 'Perpetual', 1: 'Future', } const contractType = contractTypeMap[Object.values(market.contractType)[0] as number] || 'Unknown' const marketInfo: MarketInfo = { index: i, symbol, oracleSource: getOracleSourceName(oracleSource), minOrderSize, orderStepSize, tickSize, oracleAddress, contractType, imfFactor, marginRatioInitial, marginRatioMaintenance, status, } markets.push(marketInfo) // Check if this matches our search if (searchSymbol && symbol.toUpperCase().includes(searchSymbol.toUpperCase())) { foundSearchSymbol = marketInfo } } // Print table header console.log('Index | Symbol | Oracle | Min Order | Step Size | Tick Size | Status | Oracle Address') console.log('------|----------------------|-------------|------------|------------|------------|-----------|' + '-'.repeat(50)) // Print each market for (const market of markets) { const isHighlighted = searchSymbol && market.symbol.toUpperCase().includes(searchSymbol.toUpperCase()) const highlight = isHighlighted ? ' ← FOUND!' : '' const row = [ market.index.toString().padStart(5), market.symbol.padEnd(20), market.oracleSource.padEnd(11), formatNumber(market.minOrderSize).padStart(10), formatNumber(market.orderStepSize).padStart(10), formatNumber(market.tickSize).padStart(10), market.status.padEnd(9), market.oracleAddress.substring(0, 44) + '...' + highlight, ].join(' | ') if (isHighlighted) { console.log(`\x1b[32m${row}\x1b[0m`) // Green highlight } else { console.log(row) } } // If searching for a specific symbol if (searchSymbol) { console.log('\n' + '='.repeat(100)) if (foundSearchSymbol) { console.log(`\nšŸŽÆ ${foundSearchSymbol.symbol} Configuration:\n`) // Generate copy-paste config for config/trading.ts console.log('Copy this into config/trading.ts (SUPPORTED_MARKETS):') console.log('```typescript') console.log(`'${foundSearchSymbol.symbol}': {`) console.log(` symbol: '${foundSearchSymbol.symbol}',`) console.log(` driftMarketIndex: ${foundSearchSymbol.index},`) console.log(` pythPriceFeedId: '${foundSearchSymbol.oracleAddress}',`) console.log(` minOrderSize: ${foundSearchSymbol.minOrderSize},`) console.log(` tickSize: ${foundSearchSymbol.tickSize},`) console.log(` positionSize: 50, // Customize as needed`) console.log(` leverage: 15, // Customize as needed`) console.log(`},`) console.log('```') // Generate symbol normalizer snippet const symbolBase = foundSearchSymbol.symbol.replace('-PERP', '').replace('-SPOT', '') console.log('\nAlso add to normalizeTradingViewSymbol():') console.log('```typescript') console.log(`if (upper.includes('${symbolBase}')) return '${foundSearchSymbol.symbol}'`) console.log('```') // Print detailed market info console.log('\nšŸ“‹ Full Market Details:') console.log(' Market Index:', foundSearchSymbol.index) console.log(' Symbol:', foundSearchSymbol.symbol) console.log(' Contract Type:', foundSearchSymbol.contractType) console.log(' Status:', foundSearchSymbol.status) console.log(' Oracle Source:', foundSearchSymbol.oracleSource) console.log(' Oracle Address:', foundSearchSymbol.oracleAddress) console.log(' Min Order Size:', foundSearchSymbol.minOrderSize, 'units') console.log(' Order Step Size:', foundSearchSymbol.orderStepSize, 'units') console.log(' Tick Size (Price):', foundSearchSymbol.tickSize, 'USD') console.log(' Initial Margin Ratio:', (foundSearchSymbol.marginRatioInitial * 100).toFixed(2) + '%') console.log(' Maintenance Margin Ratio:', (foundSearchSymbol.marginRatioMaintenance * 100).toFixed(2) + '%') console.log(' IMF Factor:', foundSearchSymbol.imfFactor) // Calculate max leverage from margin requirement if (foundSearchSymbol.marginRatioInitial > 0) { const maxLeverage = Math.floor(1 / foundSearchSymbol.marginRatioInitial) console.log(' Max Leverage (calculated):', maxLeverage + 'x') } } else { console.log(`\nāš ļø ${searchSymbol.toUpperCase()} not found in available markets.`) console.log('\nPossible reasons:') console.log(' 1. The market may not exist on Drift yet') console.log(' 2. The symbol name might be different (try variations)') console.log(' 3. The market might be delisted or not yet active') console.log('\nTip: Check Drift app manually: https://app.drift.trade') // Suggest similar symbols const similar = markets.filter(m => m.symbol.toUpperCase().includes(searchSymbol.substring(0, 3).toUpperCase()) ) if (similar.length > 0) { console.log('\nSimilar symbols found:') similar.forEach(m => console.log(` - ${m.symbol} (index: ${m.index})`)) } } } // Summary console.log('\n' + '='.repeat(100)) console.log(`\nšŸ“Š Summary: ${markets.length} perpetual markets available`) const activeMarkets = markets.filter(m => m.status === 'Active') console.log(` Active markets: ${activeMarkets.length}`) const pythMarkets = markets.filter(m => m.oracleSource.includes('Pyth')) console.log(` Pyth oracle markets: ${pythMarkets.length}`) // Cleanup await driftService.disconnect() console.log('\nāœ… Discovery complete\n') } catch (error) { console.error('āŒ Error discovering markets:', error) throw error } } // Main entry point const searchArg = process.argv[2] discoverMarkets(searchArg) .then(() => { process.exit(0) }) .catch((error) => { console.error('\nāŒ Discovery failed:', error) process.exit(1) })