Skip to content

Classifier Rules

Griffen Fargo edited this page May 4, 2026 · 2 revisions

Classifier Rules

The classifier lives in packages/classifier/. It transforms RawEvent[] into LedgerEntry[] using an 8-rule chain. Rules run in order — the first rule that claims an event wins.

Rule Chain

Rule 1: Coinbase Pair Merger

File: rules/01-cb-pair-merger.ts

Safety net for Coinbase internal move pairs (Retail Staking Transfer, Retail Eth2 Deprecation). Groups by (timestamp, abs(quantity)) and produces internal_move ledger entries. Most pairing is already handled by the Coinbase adapter, but this rule catches any that slip through.

Rule 2: Coinbase Self-Transfer Detection

File: rules/02-cb-self-transfer.ts

For every Coinbase Send event, parses the destination address from the Notes field. If the address matches any known wallet of the user, marks as transfer_self. Otherwise leaves for later rules.

Rule 3: Cross-Source Self-Transfer Matching

File: rules/03-cross-source-match.ts

Matches transfers across sources using fuzzy criteria:

  • Same direction (one outbound, one inbound)
  • Amount within ±0.5% (gas tolerance)
  • Timestamp within ±10 minutes

When matched, both raw events are merged into one internal_move ledger entry. This handles the common case of a Coinbase withdrawal appearing as both a Coinbase Send and an on-chain incoming transfer.

Rule 4: DEX Swap Collapse

File: rules/04-dex-swap-collapse.ts

Groups on-chain events by txHash. If the to address matches a known DEX router (from dex-routers.json), collapses the multiple transfer legs into one synthetic trade event with 2 legs + 1 gas-fee leg.

Supported routers: Uniswap V2/V3, Universal Router, MetaMask Swap Router, QuickSwap (Polygon).

Rule 5: Bridge Detection

File: rules/05-bridge-detection.ts

Marks transfers to/from known bridge contracts as internal_move if a corresponding receive on the destination chain exists within 24 hours. Otherwise leaves as unknown for user review.

Supported bridges: Celer cBridge V2, Polygon PoS Bridge.

Rule 6: Approval Gas Accounting

File: rules/06-approval-gas.ts

ERC-20 approve calls have no asset movement but cost gas. This rule produces fee_disposal entries for the gas spent. Also handles crypto_out events where all legs have feeFlag: true (failed transaction gas).

Rule 8: NFT Classification

File: rules/08-nft-classification.ts

Detects NFT acquisition and disposal patterns by grouping nft_event raw events by txHash and pairing them with fungible counterpart legs in the same transaction.

Classification logic:

NFT Direction Fungible Counterpart Result Reason
NFT in + fungible out Yes nft_acquisition NFT purchase
NFT in from null address + fungible out Yes nft_acquisition NFT mint
NFT in alone No nft_acquisition NFT airdrop
NFT out + fungible in Yes nft_disposal NFT sale
NFT out alone No nft_disposal NFT transfer out
NFT in + NFT out (same tx) Either Both nft_disposal + nft_acquisition NFT-for-NFT trade

Key behaviors:

  • Mint detection: checks if the NFT's counterparty is the null address (0x0000000000000000000000000000000000000000)
  • Preserves contractAddress and tokenId on all NFT legs
  • Sets NFT leg amount to '1' for acquisitions, '-1' for disposals
  • Consumes both NFT events and their paired fungible events (prevents double-counting by later rules)
  • Only claims nft_event type events — all other types pass through

Rule 7: Default Passthrough

Override System

User overrides take precedence over all rules. Before the rule chain runs, the classifier checks for ClassifierOverride records matching the event's raw event IDs. If found, the override's type is used directly.

Overrides are created via:

  • daybook classify --review (interactive Ink UI)
  • Direct database insertion

Overrides survive re-sync and re-classification.

Catalogs

DEX Routers

File: dex-routers.json

A curated list of known DEX router contract addresses used by Rule 4 for swap detection.

Bridge Contracts

File: bridges.json

A curated list of known bridge contract addresses used by Rule 5 for bridge detection.

Dry Run

daybook classify --dry-run runs the full rule chain without writing to the database. It displays:

  • Total events processed
  • Ledger entries per type
  • A diff against the current database state (added, removed, unchanged)

Interactive Review

daybook classify --review launches an Ink-based interface after classification. It shows all unclassified entries and lets you assign types interactively. Each override is persisted as a ClassifierOverride record.

The review is automatically skipped when:

  • There are no unclassified entries
  • stdout is not a TTY (piped output)
  • --no-review flag is passed

Clone this wiki locally