OS Trading Engine
Technical Documentation
Integrations
DexScreener

DexScreener

Nexgent integrates with DexScreener as an alternative price feed provider. DexScreener provides token prices, liquidity data, and trading pair information.

Overview

FeatureDescription
Token PricesPrice in SOL and USD
Liquidity DataPool liquidity for pairs
Multi-token BatchUp to 30 tokens per request
Rug DetectionIdentify low-liquidity tokens

Configuration

# Select price provider (default: jupiter)
PRICE_PROVIDER=dexscreener

The price provider can be switched at runtime:

const priceFeedService = new PriceFeedService();
priceFeedService.setProvider(new DexScreenerPriceProvider());

Price Provider

Single Token Price

// infrastructure/external/dexscreener/dexscreener-price-provider.ts
 
async getTokenPrice(tokenAddress: string): Promise<TokenPrice> {
  // Rate limit check
  await this.waitForRateLimit();
  
  const url = `https://api.dexscreener.com/tokens/v1/solana/${tokenAddress}`;
  
  const response = await fetch(url);
  const data = await response.json();
  
  // DexScreener returns array of pairs
  if (!Array.isArray(data) || data.length === 0) {
    throw new PriceFeedServiceError('No price data found', 'TOKEN_NOT_FOUND');
  }
  
  // Parse to get best SOL pair
  const tokenPrice = this.parseTokenPrice(data, tokenAddress);
  
  if (!tokenPrice) {
    throw new PriceFeedServiceError('No SOL pair found', 'NO_SOL_PAIR');
  }
  
  return tokenPrice;
}

Batch Token Prices

async getMultipleTokenPrices(tokenAddresses: string[]): Promise<TokenPrice[]> {
  const results: TokenPrice[] = [];
  const batchSize = 30; // Max addresses per request
  
  for (let i = 0; i < tokenAddresses.length; i += batchSize) {
    const batch = tokenAddresses.slice(i, i + batchSize);
    const addressesString = batch.join(',');
    
    await this.waitForRateLimit();
    
    const url = `https://api.dexscreener.com/tokens/v1/solana/${addressesString}`;
    const response = await fetch(url);
    const data = await response.json();
    
    // Group pairs by base token
    const pairsByToken = new Map<string, DexScreenerPair[]>();
    for (const pair of data) {
      const baseAddress = pair.baseToken.address.toLowerCase();
      if (!pairsByToken.has(baseAddress)) {
        pairsByToken.set(baseAddress, []);
      }
      pairsByToken.get(baseAddress)!.push(pair);
    }
    
    // Parse best price for each token
    for (const address of batch) {
      const pairs = pairsByToken.get(address.toLowerCase());
      if (pairs?.length > 0) {
        const price = this.parseTokenPrice(pairs, address);
        if (price) results.push(price);
      }
    }
  }
  
  return results;
}

Pair Selection

DexScreener returns all trading pairs for a token. We select the best SOL pair:

private parseTokenPrice(pairs: DexScreenerPair[], tokenAddress: string): TokenPrice | null {
  // Filter SOL pairs where our token is the base token
  const solPairs = pairs.filter(
    (pair) =>
      pair.quoteToken.address.toLowerCase() === SOL_MINT_ADDRESS.toLowerCase() &&
      pair.baseToken.address.toLowerCase() === tokenAddress.toLowerCase()
  );
  
  if (solPairs.length === 0) {
    return null;
  }
  
  // Select pair with highest liquidity
  const bestPair = solPairs.reduce((best, current) => {
    const bestLiquidity = best.liquidity?.usd || 0;
    const currentLiquidity = current.liquidity?.usd || 0;
    return currentLiquidity > bestLiquidity ? current : best;
  });
  
  return {
    tokenAddress: tokenAddress.toLowerCase(),
    priceSol: parseFloat(bestPair.priceNative),
    priceUsd: parseFloat(bestPair.priceUsd),
    liquidity: bestPair.liquidity?.usd || 0,
    priceChange24h: bestPair.priceChange?.h24 || 0,
    lastUpdated: new Date(),
    pairAddress: bestPair.pairAddress,
  };
}

Rate Limiting

DexScreener has a rate limit of 300 requests per minute. We use a token bucket algorithm:

const RATE_LIMIT_REQUESTS = 300;
const RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute
 
interface TokenBucket {
  tokens: number;
  lastRefill: number;
}
 
private rateLimitBucket: TokenBucket = {
  tokens: RATE_LIMIT_REQUESTS,
  lastRefill: Date.now(),
};
 
private refillBucket(): void {
  const now = Date.now();
  const elapsed = now - this.rateLimitBucket.lastRefill;
  const tokensToAdd = Math.floor(
    (elapsed / RATE_LIMIT_WINDOW_MS) * RATE_LIMIT_REQUESTS
  );
  
  if (tokensToAdd > 0) {
    this.rateLimitBucket.tokens = Math.min(
      RATE_LIMIT_REQUESTS,
      this.rateLimitBucket.tokens + tokensToAdd
    );
    this.rateLimitBucket.lastRefill = now;
  }
}
 
private async waitForRateLimit(): Promise<void> {
  while (!this.checkRateLimit()) {
    await new Promise(resolve => setTimeout(resolve, 10));
    this.refillBucket();
  }
}

Liquidity Check Service

The LiquidityCheckService detects rug pulls by checking liquidity:

// infrastructure/external/dexscreener/liquidity-check-service.ts
 
interface LiquidityCheckResult {
  tokenAddress: string;
  hasLiquidity: boolean;
  liquiditySol: number;
  liquidityUsd: number;
  hasPairs: boolean;
  pairCount: number;
  isRugPulled: boolean;
  error?: string;
}
 
async checkLiquidity(tokenAddress: string): Promise<LiquidityCheckResult> {
  const url = `https://api.dexscreener.com/tokens/v1/solana/${tokenAddress}`;
  const response = await fetch(url);
  const pairs = await response.json();
  
  // Filter SOL pairs
  const solPairs = pairs.filter(
    p => p.quoteToken.address.toLowerCase() === SOL_MINT_ADDRESS.toLowerCase()
  );
  
  // Sum liquidity across all SOL pairs
  const totalLiquiditySol = solPairs.reduce(
    (sum, pair) => sum + (pair.liquidity?.quote || 0),
    0
  );
  
  const totalLiquidityUsd = solPairs.reduce(
    (sum, pair) => sum + (pair.liquidity?.usd || 0),
    0
  );
  
  // Token is considered rug pulled if SOL liquidity < 10 SOL
  const isRugPulled = solPairs.length > 0 && totalLiquiditySol < 10;
  
  return {
    tokenAddress,
    hasLiquidity: totalLiquiditySol >= 10,
    liquiditySol: totalLiquiditySol,
    liquidityUsd: totalLiquidityUsd,
    hasPairs: pairs.length > 0,
    pairCount: solPairs.length,
    isRugPulled,
  };
}

Rug Pull Handling

When a token is detected as rug pulled, positions are automatically closed:

// In PriceUpdateManager
for (const result of liquidityResults) {
  if (result.isRugPulled) {
    logger.error({
      tokenAddress: result.tokenAddress,
      liquiditySol: result.liquiditySol,
    }, 'Token identified as rug pulled');
    
    // Create burn transactions for positions
    await liquidityCheckService.createBurnTransactionsForRugPulledToken(
      result.tokenAddress
    );
  }
}

API Reference

DexScreener Response

interface DexScreenerPair {
  chainId: string;
  dexId: string;
  pairAddress: string;
  baseToken: {
    address: string;
    name: string;
    symbol: string;
  };
  quoteToken: {
    address: string;
    name: string;
    symbol: string;
  };
  priceNative: string;    // Price in quote token (SOL)
  priceUsd: string;       // Price in USD
  liquidity: {
    usd?: number;
    base?: number;        // Token liquidity
    quote?: number;       // SOL liquidity
  };
  priceChange: {
    h24?: number;         // 24h change %
    h6?: number;
    h1?: number;
    m5?: number;
  };
  volume: {
    h24?: number;
    h6?: number;
    h1?: number;
    m5?: number;
  };
  txns: {
    h24?: { buys: number; sells: number };
  };
}

Endpoints

EndpointMethodDescription
/tokens/v1/solana/{address}GETSingle token pairs
/tokens/v1/solana/{addr1,addr2}GETMultiple tokens (max 30)

Jupiter vs DexScreener

FeatureJupiterDexScreener
Price SourceAggregated from swapsTrading pairs
Liquidity DataNoYes
Pair InfoNoYes
Rate LimitHigher (with API key)300/min
DefaultYesNo
💡

Jupiter is the default price provider because it provides more accurate execution prices. DexScreener is used for liquidity checks and as a fallback.