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
);
- Token0 → Token1:
amount0In > 0
andamount1Out > 0
- Token1 → Token0:
amount1In > 0
andamount0Out > 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
});
- Complete Trade Representation: Captures both sides of the exchange
- Flexible Reward Calculation: Can reward based on volume, fees, or both tokens
- 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...'
- 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,
});
priceable: true
:
- Enrichment Pipeline: Automatically fetches USD prices for the token
- Volume Calculation: Converts token amounts to USD values
- Reward Computation: Enables volume-based or fee-based rewards
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)
};
- 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.');
- 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
}