-
Notifications
You must be signed in to change notification settings - Fork 0
Pricing
The pricing module lives in packages/tax/src/pricing/. It resolves USD prices for assets at specific timestamps using a priority chain of providers.
Providers are tried in order. The first provider that returns a price wins:
-
Source-reported — prices the source itself reported (e.g., Coinbase's
Price at Transactioncolumn) - CoinGecko — historical price lookup by symbol or contract address
-
Manual override — user-provided prices in the
price_overridestable
File: pricing/providers/source-reported.ts
Extracts prices from RawEvent legs where the source provided a USD value (the usdReportedBySource field). This is the most defensible price source for tax purposes — it's what the exchange used at the moment of the transaction.
Available for: Coinbase transactions (all types), generic CSV rows with USD value columns, and exchange rows where the source export includes explicit USD values.
File: pricing/providers/coingecko.ts
Fetches historical prices from the CoinGecko API:
- Major assets:
GET /coins/{id}/history?date=DD-MM-YYYY - ERC-20 tokens:
GET /coins/ethereum/contract/{address}/market_chart/range
Handles rate limits with exponential backoff (max 3 retries on 429 responses). The free tier allows approximately 30 requests per minute.
File: pricing/providers/manual-override.ts
Reads from the price_overrides SQLite table. Use this for tokens that have no reliable price feed.
# Set a manual price
daybook overrides set SOMETOKEN 2024-03-15 0.50
# List all overrides
daybook overrides list
# Remove an override
daybook overrides remove <id>File: pricing/cache.ts
Resolved prices are cached in the prices SQLite table, keyed by (asset, date). Cache hits skip the provider chain entirely. The cache is populated automatically as prices are resolved.
File: pricing/asset-aliases.ts
Some assets have multiple names that should be treated as equivalent for pricing:
| Alias | Canonical |
|---|---|
| POL | MATIC |
| MATIC | POL |
| ETH2 | ETH |
When looking up a price, the alias map is consulted so that POL and MATIC resolve to the same price.
When no provider returns a price:
- Assets worth less than $1 USD-equivalent are auto-zeroed (handles spam airdrops)
- Assets above $1 prompt for manual override
The export flags unpriced events so they're visible in the output.
Implement the PricingProvider interface:
interface PricingProvider {
name: string;
getPrice(asset: string, timestamp: Date): Promise<PriceResult | null>;
}Then add it to the priority chain in the pricing configuration. The chain runner handles fallthrough automatically.
Getting Started
Usage
Architecture