3. Redis Token Caching – Absinthe Docs
Skip to content

3. Redis Token Caching

Purpose

Before we can decode swap events, we need to know which tokens are in the pool. Uniswap V2 pools have token0 and token1 addresses that we need to fetch from the contract. To avoid expensive RPC calls on every swap, we cache these addresses in Redis.

Implementation

const token0Key = `univ2:${params.poolAddress}:token0`;
const token1Key = `univ2:${params.poolAddress}:token1`;
 
let tk0Addr = await redis.get(token0Key);
let tk1Addr = await redis.get(token1Key);
 
if (!tk0Addr || !tk1Addr) {
  const poolContract = new univ2Abi.Contract(rpc, params.poolAddress);
  tk0Addr = (await poolContract.token0()).toLowerCase();
  tk1Addr = (await poolContract.token1()).toLowerCase();
 
  await redis.set(token0Key, tk0Addr);
  await redis.set(token1Key, tk1Addr);
}

Why This Matters

The Problem

Without caching, every swap event would require:

  1. RPC Call to get token0 (~$0.0001-0.001 per call)
  2. RPC Call to get token1 (~$0.0001-0.001 per call)
  3. For 1000 swaps: ~$0.20-2.00 in RPC costs

The Solution

With Redis caching:

  1. First event: Fetch from contract and cache (~$0.0002)
  2. All subsequent events: Read from Redis (~$0.000001)
  3. For 1000 swaps: ~$0.001 + negligible Redis costs

Cache Key Strategy

Naming Convention

const token0Key = `univ2:${params.poolAddress}:token0`;
const token1Key = `univ2:${params.poolAddress}:token1`;
Why this format?
  • univ2: - Namespacing to avoid collisions
  • ${poolAddress}: - Pool-specific data
  • :token0/:token1 - Clear field identification

Collision Prevention

Automatic Config Prefixing: All Redis keys are automatically prefixed with a hash of your adapter configuration, ensuring that different adapters or different configurations cannot collide with each other.

// Your actual Redis key structure:
// [configHash]:univ2:0x123...:token0
// [configHash]:univ3:0x456...:token0

Protocol Namespacing (Recommended): While not mandatory due to automatic config prefixing, adding protocol prefixes like univ2: is highly recommended for easier debugging and key organization.

// ✅ Recommended: Clear protocol namespacing
`univ2:${params.poolAddress}:token0`
`univ3:${params.poolAddress}:token0` // Different protocol
 
// ⚠️ Possible but less clear: Protocol prefix not required
`${params.poolAddress}:token0` // Still collision-safe due to config hash
Why protocol prefixes help:
  • Debugging: Easier to identify which adapter/protocol owns the data
  • Maintenance: Clear separation between different protocols
  • Readability: Self-documenting keys in Redis

Address Normalization

Why Lowercase?

tk0Addr = (await poolContract.token0()).toLowerCase();
tk1Addr = (await poolContract.token1()).toLowerCase();
Consistency Benefits:
  • Ethereum addresses are case-insensitive but case-significant for checksums
  • Lowercase ensures consistent storage and comparison
  • Prevents duplicate cache entries for the same address
Example:
// These are the same address but different strings:
'0x742d35Cc6634C0532925a3b8F6f5c6fF1c4a5F1'  // Mixed case
'0x742d35cc6634c0532925a3b8f6f5c6ff1c4a5f1'  // Lowercase

Error Handling

Network Failures

try {
  const poolContract = new univ2Abi.Contract(rpc, params.poolAddress);
  tk0Addr = (await poolContract.token0()).toLowerCase();
} catch (error) {
  console.error(`Failed to fetch token0 for pool ${params.poolAddress}:`, error);
  throw error; // Re-throw to fail the adapter
}

Best Practices

Cache Key Patterns

// Pool metadata
`univ2:${pool}:token0`
`univ2:${pool}:token1`
`univ2:${pool}:fee` // Could cache fee tier too
 
// User data (if needed)
`univ2:${pool}:${user}:balance`
 
// Global data
`univ2:factory` // Factory contract address

Monitoring Cache Hit Rates

// Log cache performance
if (tk0Addr && tk1Addr) {
  console.log(`Cache hit for pool ${params.poolAddress}`);
} else {
  console.log(`Cache miss for pool ${params.poolAddress}`);
}