# Vrismcost Cost basis and realised P&L for **Robinhood Chain** (chain 4663), computed on your machine. You give it an address. It reads the chain, works out what each transaction actually was, matches every disposal to the lots it consumed, and writes a schedule your accountant can read. Anything it cannot prove goes in a queue with the reason — it is never filled in with a plausible guess. Version 0.2.6 · Python 3.10+ · one dependency --- ## What it contacts One address, and nothing else: https://rpc.mainnet.chain.robinhood.com That is Robinhood Chain's public RPC. There is no account, no API key, no analytics, no telemetry, no server of ours. Check it yourself before you run anything — it takes one command: ```bash grep -rn "https://" *.py | grep -v "^test_" ``` The only thing that ever leaves your computer is the address you asked about, and it goes to that node. Your cost basis, your notes, your corrections and your reports stay in a file on your disk. **"Private" is not the same as anonymous.** The node can see which addresses you looked up, and it can see your IP. If that matters to you, use a VPN or run your own node and point Vrismcost at it with `--rpc=URL`. --- ## Install ```bash pip install -r requirements.txt python test_vrismcost.py ``` The test suite runs offline in a few seconds. If it is not green, do not trust the numbers. `cryptography` is the only dependency, and only the encrypted vault needs it. AES-GCM has to come from an audited implementation; composing one out of `hashlib` would be the kind of cleverness that loses somebody their records. --- ## Use ```bash # one wallet, full history python vrismcost.py 0xabc... # a wallet set, netted together — moves between them are not disposals python vrismcost.py 0xabc... 0xdef... # write the schedule python vrismcost.py 0xabc... --csv=2026 --method=HIFO ``` | Flag | What it does | | --- | --- | | `--recent=N` | Only scan the last N blocks. A sale of anything bought before that window has no lot to match, so it is marked `UNMATCHED` and the report stays INCOMPLETE. | | `--method=` | `FIFO` (default), `LIFO`, `HIFO`, `AVERAGE` | | `--quote=` | Name the quote asset. Detected from your tokens otherwise. | | `--eth=3455.20` | Fix the ETH rate yourself instead of deriving it | | `--eth=off` | Leave ETH legs unpriced | | `--csv=PREFIX` | Write the four sheets | | `--json=FILE` | Write the whole report as JSON | | `--vault` | Use the wallets and answers in your vault | | `--rpc=URL` | Read from another node, such as your own, instead of the public RPC | ### The vault The chain cannot tell you which transfers were your own wallet moves, what an untraceable acquisition cost, or whether an arrival was income or a gift. You answer once, and the answer is kept — encrypted, on your machine. ```bash python vrismcost.py vault init # writes ~/.vrismcost/vault.json python vrismcost.py vault add 0xabc... main python vrismcost.py vault answer 0xTX own 2568.72 2026-07-01 python vrismcost.py --vault ``` | Answer | What it does | | --- | --- | | `own VALUE [DATE]` | It came from a wallet of yours: a lot at that basis, dated DATE (YYYY-MM-DD) or, without one, the day it arrived | | `offchain VALUE [DATE]` | You bought it elsewhere: a lot at the basis you paid | | `income VALUE` | Income on arrival, on the income sheet. A token also becomes a lot at that value; USDG needs no value | | `sold VALUE` | It left and was sold off-chain: a disposal at those proceeds, matched against your lots | | `manual VALUE` | An unpriced trade: the proceeds of what left and the basis of what arrived | | `eth_price RATE` | The ETH rate for that one transaction | | `exclude` | Left out of the figures, and listed in the summary so the omission is visible | An answer that cannot apply — a value that is missing, an ETH rate on a transaction with no ETH leg — answers nothing, and the transaction stays open. Write the recovery code down on paper. It is not stored anywhere and it is shown once. Lose both it and the passphrase and the contents are gone — that trade is deliberate, and it is the same trade that means nobody else can read the file either. --- ## What comes out Four sheets, because an accountant works in a spreadsheet: | File | What it holds | | --- | --- | | `PREFIX-disposals.csv` | The capital-gains schedule, one row per matched leg | | `PREFIX-income.csv` | Distributions, kept out of the lot inventory | | `PREFIX-open-lots.csv` | What is still held, with its basis and dates | | `PREFIX-summary.csv` | Whether any of it is complete, on the first line, then the metadata | Every row carries **both** transaction hashes, the acquiring and the disposing one, because a leg is a claim about two moments and one hash proves half of it. Every row carries a `source` column: `chain` for figures the blockchain states, `derived` for values at an ETH rate read off the chain, `user` for anything you supplied, and `UNRESOLVED` for what is still open. A row is only as proven as the least proven figure on it. Each leg carries its own share of the disposal's proceeds and gas, so the gains down the sheet add up to the realised gain in the summary. Rows it could not price are exported marked `UNPRICED`, not dropped. A schedule that quietly omits what the tool could not read looks complete and is not. While anything is open, the summary's first line says INCOMPLETE. --- ## How the numbers are found **A trade is a transaction, not a log.** On this chain one trade emits 5.2 Transfer logs on average and 70.9% emit more than two — router hops, fee sinks, mint and burn. One real transaction carried 2,000 legs, of which one touched the wallet being scanned. Legs are netted per asset per transaction, and the net is what gets classified. **Native ETH emits no log at all.** One transaction in five moves some. The amount comes from the transaction's own `value` field, which is the only record there is. **Prices come out of the chain, not a feed.** A trade against USDG states its own price exactly. Most real trades here settle in ETH, so the ETH rate is derived: WETH and USDG legs matched **across a shared pool** — one contract taking one token and paying the other inside a single transaction — then the median of the matched swaps. Inside a five-second window they agree to 0.03% at the median and within 0.1% nine times in ten (measured 12–13 September 2026). A window whose swaps disagree by more than 3% leaves those legs unpriced rather than filled in. Historical state is pruned after about ten minutes on this chain, so a pool's reserves at an old block cannot be read back. Logs survive; that is why the rate is reconstructed from them. **A USDG mint is asked about, not booked.** Its amount is exact, but a dividend, a bridge deposit and an issuance against your own dollars all log the same Transfer from the zero address, and the quote asset is recognised by its symbol, which any token can claim. So it waits in the queue as *not confirmed as income*. Answer `income` and it is booked on the income sheet at the minted amount; answer `own` and it is not income at all. **What left is gone, priced or not.** When an asset leaves in a transaction nothing prices — a token-for-token swap, a transfer out — it still leaves your inventory. The disposal is written with the lots it consumed and their basis, and `UNPRICED` where the proceeds and gain would be, until you answer it. **A sale the history cannot cover is a gap, not a gain.** If a sale is larger than every lot the scan found before it, the uncovered quantity is marked `UNMATCHED`, earns no gain, and holds the report open. Selling what the scanned history never bought is not a sale at zero cost. **Corporate actions are not disposals.** A split runs as burn-and-mint here, which to a log reader looks like selling everything and rebuying at no cost. Quantity and cost-per-unit are adjusted; total cost basis and the acquisition date are preserved. It is applied once, to the lots still open when it lands; a lot bought afterwards is already in the new units. **Every method keeps its own lots.** The history is walked once, in chain order. A sale is matched only against lots bought before it, and FIFO, LIFO, HIFO and average each sell from their own copy of the inventory, so a second LIFO sale sees what the first LIFO sale left. The open-lots sheet follows the method you export under. **Gas is yours when you sent the transaction.** Each receipt states `gasUsed × effectiveGasPrice`. On a buy or a sale one of your wallets sent, that joins the basis of what it bought or comes off the proceeds of what it sold. A move between your wallets records its gas on the lots it carried. A transfer someone sent you, an issuer's split or distribution, and a bundler's transaction were paid for by their senders and add nothing. Gas is valued at the ETH rate, so with `--eth=off`, or in a window with no rate, it counts as zero. --- ## What it will not do - Hold your keys, sign anything, or move anything. It only reads. - Guess a price it cannot derive. - Book a transfer between your own wallets as a sale. - Read a split as a disposal. - Quietly drop a transaction it could not understand. - Tell you what you owe. It computes figures; rates and eligibility are yours to confirm against current regulation where you live. **This is not tax advice, and it is not a substitute for an accountant.** --- ## Known limits These are real and currently unsolved. They are listed because a tool that hides its edges is worse than one that names them. - **Corporate actions are untested against a real event.** The detection is written and covered by tests, but no split has been observed on this chain yet, so the code path has never met a live one. - **ETH moved by a contract mid-transaction is not read.** It leaves no Transfer log and no `tx.value`, and the public RPC offers no `debug_` or `trace_`. That node can re-simulate a transaction with `eth_simulateV1` for about ten minutes, which would recover it; Vrismcost does not do this yet, and after those ten minutes the public RPC cannot recover it at all. - **Rebases leave nothing to detect.** A balance that changes with no Transfer log cannot be seen. Splits and migrations are caught by their burn-and-mint pattern; a silent rebase is not. - **Very active addresses hit a wall.** `eth_getLogs` refuses more than 10,000 matching logs. Below a 500-block window there is nowhere left to narrow to, and the scan stops and says so rather than truncating. - **A full-history scan takes a couple of minutes** — 134 seconds for one active wallet when last measured. The slowest stage is reading ETH rates off the pools. - **One value across several assets is split evenly.** When an answered transaction acquired more than one token, nothing on chain states their relative worth. The split is recorded as an assumption on the lot. - **A smart account's gas is not read.** Its operations are sent by a bundler, which recovers the fee from the account inside the same transaction. Gas counts only on transactions your wallets sent, so a smart account's basis carries none of it. - **An ETH leg is priced from its window's centre.** The rate is derived once per 100,000 blocks, at the middle of that range, so the rate applied to a trade can come from up to about 1.4 hours away. On a day ETH moves 2%, that gap is visible in the basis. Smaller windows cost more queries against a rate-limited node; this is the current trade. - **No tax-year bucketing.** Robinhood Chain reached production on 30 June 2026, so 2026 is its only tax year and "since the chain went live" is the same figure. This becomes necessary in January 2027. --- ## Releases **A published version never changes.** If the code needs fixing, the version number moves with it. The site's build refuses to publish different bytes under a version that is already out, because from the outside that is indistinguishable from tampering, whatever the reason for it. Every release is appended to `RELEASES` on the site and never edited, and every file in this archive is listed with its hash in `SHA256SUMS` there. --- ## Layout vrismcost.py the command line, and run(): the whole pipeline in one function fetcher.py reads the chain — logs, timestamps, tx.value, gas engine.py classification, lots, matching, corporate actions price.py derives the ETH rate from WETH/USDG pools report.py the CSV export vault.py the encrypted local store test_vrismcost.py the suite README.md this file requirements.txt the one dependency Run the suite after any change: ```bash python test_vrismcost.py -v ``` Every test runs offline, in a few seconds. They exist because a tax tool does not crash when it is wrong — it returns a number, confidently, and the number is wrong. --- ## Measurements Everything above is derived from the chain as measured on **12 September 2026**. Chain facts change; re-measure before relying on them. | | | | --- | --- | | Production since | 30 June 2026 (block 505,859) | | Block time | 0.100s steady | | State retention | ~10 minutes — not an archive node | | Log retention | Readable where checked, 30,000,000 blocks back | | `eth_getLogs` cap | 10,000 matched logs per query | | Legs per transaction | 5.20 mean, 70.9% above two (884 transactions over 200 blocks) | | Transactions moving ETH | 20% (same sample) | | Indexed API | None — Blockscout returns 403 | | Browser access | `access-control-allow-origin: *` | Block 1 carries a stamp of 30 April 2026, but the chain crawled at up to 195s per block until it reached production cadence. That stamp is a bring-up artefact, not a launch date, and using it would overstate every holding period by two months.