DEV Community

Kaminari Research
Kaminari Research

Posted on

Export any crypto wallet to tax-ready CSV without paying per transaction

Most crypto tax tools charge you more as your wallet gets busier. Koinly and CoinLedger start around $49–199 per tax year, and per-transaction exporters like exportmywallet charge $4.99 per 1,000 transactions. If you have ever moved funds across a few chains, that adds up fast.

Try it without installing anything: ChainLedger Web runs entirely in your browser — paste an address, get the CSV, nothing is uploaded. The CLI below is for full history across many wallets.

But the data you actually need is not secret. Every transfer is already on-chain. Here is how to pull it yourself, and what the real engineering problems are.

What a tax import actually needs

A tax app needs one row per movement:

  • timestamp
  • asset (symbol + contract/mint)
  • amount
  • from address
  • to address
  • a rough classification (transfer / swap / fee)

Everything else is the tax app's job. So the problem is: enumerate every transfer for an address, across every chain you used, and normalize it.

EVM chains: eth_getLogs for ERC-20

ERC-20 transfers emit a Transfer(address,address,uint256) event. You can query them directly:

const topic = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
// chunk blocks — public RPCs cap ranges (often 5k–10k blocks)
const logs = await rpc('eth_getLogs', [{
  fromBlock: '0x...', toBlock: '0x...',
  topics: [topic, null, paddedAddress]
}])
Enter fullscreen mode Exit fullscreen mode

Two traps:

  1. Chunking. Public nodes reject large ranges. Loop in 5,000-block windows and back off on errors.
  2. Decimals. The raw value is an integer. You need decimals() from the token contract, or your CSV will be off by orders of magnitude.

Native transfers (ETH, BNB, MATIC) are not in logs. Where a public Blockscout instance exists (Ethereum, Base), its API gives you native + token transfers without a key. Elsewhere you need an archive node or a paid indexer.

Solana: reconstruct from balances

Solana has no Transfer event log. Use getSignaturesForAddress, then getTransaction for each signature. The reliable way to get amounts is preBalances / postBalances and preTokenBalances / postTokenBalances — not parsing instructions. That way swaps, closed token accounts, and fee payments all reconcile.

Classification is heuristic — say so

A single token move is a transfer. A move with two sides in one transaction is probably a swap. Anything ambiguous should be flagged needsReview rather than guessed. Every honest exporter does this; the dishonest ones silently mislabel and hand you a wrong cost basis.

The boring part is the product

The code is not hard. The hard part is doing it for seven chains, handling RPC limits, token decimals, and Solana's balance model without a server, an account, or an API key.

I built ChainLedger for exactly this: a zero-dependency Node CLI that exports any wallet to Koinly, CoinTracker, CoinLedger, or raw CSV, plus JSON. It reads only from the public RPC you point it at — your addresses never touch a third-party server.

node chainledger.mjs --chain base --address 0xYOUR_WALLET
node chainledger.mjs --chain solana --address YOUR_SOLANA_WALLET
Enter fullscreen mode Exit fullscreen mode

It is not tax advice, and classification is heuristic — always reconcile totals. But it turns a weekend of CSV wrangling into one command.

Get it

ChainLedger is a one-time $19 download — no subscription, no account, no per-transaction fee:

https://ko-fi.com/s/f8dcae9582

The zip contains the CLI, the chain table, and a README with the honest limitations. If it does not export your wallet correctly, message me on X (@kaminariouji) and I will fix it or refund you.

Top comments (0)