5. Swap Tracking – Absinthe Docs
Skip to content

5. Swap Tracking

Purpose

Track swaps and emit priceable action events. This lets the enrichment pipeline price the swap in USD, enabling trading volume rewards, fee sharing, and other swap-based incentives.

Implementation

if (params.trackSwaps && log.topics[0] === swapTopic) {
  const { amount0In, amount1In, amount0Out, amount1Out } = univ2Abi.events.Swap.decode(log);
 
  // Determine swap direction
  const isToken0ToToken1 = amount0In > 0n;
 
  // Extract amounts and tokens
  const fromAmount = isToken0ToToken1 ? amount0In : amount1In;
  const toAmount = isToken0ToToken1 ? amount1Out : amount0Out;
  const fromTokenAddress = isToken0ToToken1 ? tk0Addr : tk1Addr;
  const toTokenAddress = isToken0ToToken1 ? tk1Addr : tk0Addr;
 
  // Get transaction sender
  const user = log.transaction?.from;
  if (!user) throw new Error('transaction.from is not found in the log.');
 
  // Prepare metadata
  const swapMeta = {
    fromTkAddress: fromTokenAddress,
    toTkAddress: toTokenAddress,
    fromTkAmount: fromAmount.toString(),
    toTkAmount: toAmount.toString(),
  };
 
  // Generate deterministic key for deduplication
  const key = md5Hash(`${log.transactionHash}${log.logIndex}`);
 
  // Emit both legs of the swap
  await emit.swap({
    key, priceable: true, activity: 'swap', user,
    amount: { asset: fromTokenAddress, amount: new Big(fromAmount.toString()) },
    meta: swapMeta,
  });
 
  await emit.swap({
    key, priceable: true, activity: 'swap', user,
    amount: { asset: toTokenAddress, amount: new Big(toAmount.toString()) },
    meta: swapMeta,
  });
}

Uniswap V2 Swap Event

Event Structure

event Swap(
    address indexed sender,
    uint256 amount0In,
    uint256 amount1In,
    uint256 amount0Out,
    uint256 amount1Out,
    address indexed to
);
Swap Logic:
  • Token0 → Token1: amount0In > 0 and amount1Out > 0
  • Token1 → Token0: amount1In > 0 and amount0Out > 0

Direction Detection

const isToken0ToToken1 = amount0In > 0n;
 
// Example swap: USDC → WETH
// amount0In = 1000000 (1 USDC, assuming USDC is token0)
// amount1In = 0
// amount0Out = 0
// amount1Out = 300000000000000000 (0.3 WETH)
// Result: isToken0ToToken1 = true

Dual Event Emission

Why Two Events?

// For a single swap, we emit TWO events:
 
// Event 1: Token being sold (outflow)
await emit.swap({
  key: 'same-key-for-both',
  amount: { asset: fromTokenAddress, amount: new Big(fromAmount.toString()).neg() },
  // ... other fields
});
 
// Event 2: Token being bought (inflow)
await emit.swap({
  key: 'same-key-for-both',
  amount: { asset: toTokenAddress, amount: new Big(toAmount.toString()) },
  // ... other fields
});
Why this approach:
  1. Complete Trade Representation: Captures both sides of the exchange
  2. Flexible Reward Calculation: Can reward based on volume, fees, or both tokens
  3. Enrichment Pipeline: Allows pricing both input and output tokens

Deterministic Keys

MD5 Hash Generation

const key = md5Hash(`${log.transactionHash}${log.logIndex}`);
 
// Example:
// transactionHash: 0x123abc...
// logIndex: 42
// key: md5('0x123abc...42') = 'a1b2c3d4...'
Why deterministic keys:
  • Deduplication: Same swap always generates same key
  • Idempotency: Reprocessing won't create duplicates
  • Consistency: Same event across different runs

Collision Prevention

// ✅ Good: Include both tx hash and log index
md5Hash(`${log.transactionHash}${log.logIndex}`)
 
// ❌ Bad: Only transaction hash (multiple events per tx)
md5Hash(log.transactionHash)
 
// ❌ Bad: Random generation (non-deterministic)
crypto.randomBytes(16).toString('hex')

Priceable Actions

priceable: true

await emit.swap({
  key,
  priceable: true,  // This is CRITICAL
  activity: 'swap',
  user,
  amount: { asset: fromTokenAddress, amount: amount },
  meta: swapMeta,
});
What happens when priceable: true:
  1. Enrichment Pipeline: Automatically fetches USD prices for the token
  2. Volume Calculation: Converts token amounts to USD values
  3. Reward Computation: Enables volume-based or fee-based rewards
Without priceable: true:
  • Events are logged but not priced
  • No USD volume calculations
  • Limited to token-specific rewards only

Metadata Structure

Complete Swap Context

const swapMeta = {
  fromTkAddress: fromTokenAddress,    // '0xa0b86a33e6e6c8c8a6e6f6e6e6e6e6e6e6e6e6e6'
  toTkAddress: toTokenAddress,        // '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
  fromTkAmount: fromAmount.toString(), // '1000000' (1 USDC)
  toTkAmount: toAmount.toString(),     // '300000000000000000' (0.3 WETH)
};
Why full metadata:
  • Debugging: Complete context for troubleshooting
  • Analysis: Rich data for volume and fee calculations
  • Audit Trail: Full transaction reconstruction

Error Handling

Transaction Sender Validation

const user = log.transaction?.from;
if (!user) throw new Error('transaction.from is not found in the log.');
Why required:
  • User Attribution: Must know who performed the swap
  • Reward Distribution: Rewards go to the transaction initiator
  • Audit Compliance: Complete user activity tracking

Amount Validation

// Ensure we have valid amounts
if (fromAmount === 0n || toAmount === 0n) {
  console.warn(`Invalid swap amounts: ${fromAmount} -> ${toAmount}`);
  return; // Skip invalid swaps
}