An agent harvests tax losses on Robinhood tokenized stocks, bounded by an onchain mandate it can't exceed. Stylus HIFO ledger, contract-enforced wash-sale wall, slashable bond. Robinhood Chain.




HarvestBot is an agent that harvests tax losses on Robinhood tokenized equities, bounded by an onchain mandate it cannot exceed, and slashed when it lies. It runs on Robinhood Chain testnet (chain 46630, an Arbitrum Orbit chain): the tax-lot ledger is a Stylus contract (Rust compiled to WASM), and the assets are Robinhood's own testnet Stock Tokens.
- Live site: https://harvestbot.edycu.dev
- Verify it yourself, no wallet (your browser asks the Robinhood Chain RPC): https://harvestbot.edycu.dev/verify/
- Pitch deck: https://harvestbot.edycu.dev/deck/
- Code: https://github.com/edycutjong/harvestbot
- Demo video (2:39): https://youtu.be/Q9FVTu7iuCM
INSPIRATION
In December, Maya sees her tokenized-equity position down $3,140 on paper and does nothing. Banking that loss properly means picking which of 64 tax lots to sell, then policing a 30-day calendar so she doesn't trip the wash-sale rule and void the deduction. Wealthfront and Betterment sell exactly this service ("direct indexing") for 0.25%/yr. On chain, where Robinhood now issues tokenized stocks, none of the plumbing exists: no cost-basis ledger, no specific-lot identification, no wash-sale enforcement, and no safe way to hand the job to an agent.
WHAT IT DOES
1. Onchain HIFO tax-lot ledger — TaxLotLedger, a Stylus contract. Records every lot, selects the highest-basis lots for a sale, prorates the last one and computes the realized loss deterministically computeHarvest, realize).
2. A contract-enforced wash-sale wall — WashSaleGuard. For the 30 days after a harvest, any rebuy of the same asset or its identical cluster reverts with WashSaleViolation. The agent cannot route around it; the chain says no.
3. A mandate the agent cannot exceed — HarvestMandate + ExecutionRouter. The agent key reaches one target, one selector, value 0, with an expiry and a daily rate limit. Before anything moves, the mandate re-verifies the EIP-712 signature, nonce, deadline, substitute map, wash-sale window, HIFO selection and the realized loss at the oracle mark. Custody and withdrawal are owner-only; no path exists from the agent key to a withdrawal.
4. A slashable bond — AgentBond.challenge(). Anyone holding an envelope the agent signed that the mandate must reject can slash it: 20% of the bond, a tenth of that as bounty to the challenger, the rest to the owner.
THE DEMO: THREE BEATS, EVERY ONE A REAL TRANSACTION ON ROBINHOOD CHAIN TESTNET
- Beat 1, HARVEST (block 119,852,570): agent-signed, router-gated, HIFO recomputed on the Stylus ledger, lots 63 to 55, −$24.531251 realized on 9 lots, real AMZN rotated into real NFLX. https://explorer.testnet.chain.robinhood.com/tx/0x689c4d345c492c08ef06aed9341de941cb131835cce52886d700207925f26123
- Beat 2, BLOCKED REBUY: 40 seconds later the same agent key tries NFLX back into AMZN. Reverted on chain by the contract: WashSaleViolation, the way back walled until 15 Oct 2026. https://explorer.testnet.chain.robinhood.com/tx/0xd486468ea84c6d419b28401386ce832483f809f96a35ea94e9c4a92037fecf10
- Beat 3, SLASHED: the agent signs AMZN into PLTR, a trade the substitute map never sanctioned. The attempt reverts OffMapSubstitute, and the signed envelope becomes evidence: challenge() takes the bond from 1,000 to 800 mUSDC at the Slashed event (20 bounty, 180 restitution). Attempt: https://explorer.testnet.chain.robinhood.com/tx/0xd8b6e5779f53a1cc157a9962c8546e0e5bdef3cc1e62f441458a19359c5994c8 · Challenge: https://explorer.testnet.chain.robinhood.com/tx/0x6b896b6e5aa1895b8084c1000997fcb4daa219de24f2d0219e8debfdc51e7dcb (called by a third-party key, neither owner nor agent; challenge is permissionless).
Why the on-chain loss is $24.53: the faucet hands out 5 of each Stock Token per day, so the on-chain portfolio is the 64-lot shape at 1/128 scale (0.078125 AMZN per lot). The same engine at 10 shares per lot realizes exactly −$3,140.000000 — the deterministic seed scenario asserted by forge test and cargo test, not an on-chain amount. Real small numbers on chain, the big number in the deterministic test.
HOW WE BUILT IT
- Ledger — Rust to WASM on Arbitrum Stylus stylus/ledger), deployed with cargo stylus deploy; ArbWasm.stylusVersion() = 3 on chain 46630. Ledger address: https://explorer.testnet.chain.robinhood.com/address/0xEff7B46049fC677F58264e0ebb19dF1a39195a21
- Ledger twin — Solidity, same ABI and integer semantics: the benchmark control.
- Mandate, router, guard, map, bond — Solidity 0.8.28 + OpenZeppelin 5.4 Ownable, SafeERC20, ReentrancyGuard, EIP712, ECDSA).
- Assets — real Robinhood testnet Stock Tokens (AMZN, NFLX, PLTR) from the official faucet.
- MOCK, labeled in name and symbol — the oracle mark, the fixed-rate swap venue and the bond's USDC. There is no Stock-Token AMM or USDC on the testnet. The tokens are real; the venue is not.
- Verify page — about 150 lines of plain JavaScript: one JSON-RPC batch from the judge's browser to the public Robinhood Chain RPC, events decoded by hand.
THE NUMBER: STYLUS VS SOLIDITY, SAME CHAIN, SAME INPUTS
Both ledgers deployed side by side on chain 46630, identical output at every size, measured with Arbitrum's own NodeInterface.gasEstimateComponents:
- 64 open lots: 1,456,905 L2 gas on Solidity vs 507,993 on Stylus — 2.87× less
- 128 open lots: 3.1× less. 8 open lots: 1.05× (near parity).
The Stylus program is uncached (this testnet has no ArbOS cache manager), so these are worst-case Stylus numbers. Our spec had guessed ~8×; the chain says 2.87×, and that is the number we report.
SMART CONTRACT QUALITY
- 81 Foundry tests + 6 Stylus native tests; 100% line, 100% function, 98.3% statement, 89.2% branch coverage on src/
- 3 fuzz properties: HIFO never worse than lowest-basis-first; the window is exactly 30 days for any timestamps; a breach is negative-EV for any bond
- Seven invariants (INV-1 custody … INV-7 determinism), each with a named test
- Slither: 0 high, 0 medium; every remaining finding triaged in docs/SLITHER.md
- CI: forge fmt, forge test, forge coverage, cargo clippy -D warnings, Slither SARIF, CodeQL, TruffleHog, gitleaks over full history, Dependabot
- A 2026-09-27 self-audit hardened AgentBond in source (live-envelope-only challenges, no bond exit while the agent key is active; 8 regression tests). The hardened AgentBond was redeployed the same day and beat 3 re-run against it; on chain it refuses the expired beat-2 envelope with EnvelopeNotLive (https://explorer.testnet.chain.robinhood.com/tx/0x673f8459a6a8205e318676cda4e25ec3c728c4d22ee44ca3d682ce8e9d5eed51). The retired pre-fix V1 is disclosed in the README.
PRODUCT-MARKET FIT, HONESTLY
Tax-loss harvesting is a feature retail investors already pay for (0.25%/yr at Wealthfront and Betterment). Robinhood Chain puts the stocks on chain but not the accounting: a holder of tokenized equities has no onchain way to prove which lots were sold or to enforce the 30-day window. The user is that holder; the retention loop is that harvesting runs all year, not once. Status: no users yet. As of 27 Sep, every transaction to these contracts was sent by our own owner or agent key (checked on the explorer), and we won't claim otherwise. What it takes to reach users is listed under What's next.
THE JUDGING CRITERIA, ONE BY ONE
- Smart contract quality: see SMART CONTRACT QUALITY above. 81 + 6 tests, 100% line coverage, fuzz and invariant tests, Slither 0 high / 0 medium, a self-audit with its fixes and its one open deployment gap disclosed.
- Product-Market Fit: see above. The demand is proven off chain; the on-chain plumbing is missing; we have no users yet.
- Innovation and Creativity: specific-lot (HIFO) identification and a wash-sale wall written as contracts, and an agent whose own signature becomes the evidence that slashes it. The compute-heavy ledger runs as WASM on Stylus, because that is where WASM measurably beats EVM opcodes.
- Real Problem Solving: tokenized stocks now exist on Robinhood Chain, but a holder cannot bank a loss there without hand-picking lots and policing a 30-day calendar. HarvestBot does both, and the contracts refuse the mistakes (beat 2) and punish the lie (beat 3).
WHY ONLY ARBITRUM + ROBINHOOD CHAIN
- The ledger is WASM. Remove Stylus and the HIFO scan costs 2.87× more L2 gas at 64 lots, 3.1× at 128.
- The assets are Robinhood's. A tokenized-equity tax ledger only makes sense on the chain that issues the tokenized equities.
- Arbitrum precompiles answered "is WASM live on this Orbit chain?" in one RPC call ArbWasm.stylusVersion()) before any code was written.
CHALLENGES WE RAN INTO
1. Stylus on Robinhood Chain was an open question. Day-0 spike: probe ArbWasm, then deploy, activate and call a Stylus counter before writing the ledger. It worked, so the real ledger went to Robinhood Chain, not a side chain.
2. Forge cannot simulate WASM, so seeding, the three beats and the benchmark are cast-driven scripts seed.sh, beats.sh, bench.py).
3. Our own audit found that challenge() accepted stale or foreign envelopes. Fixed in source with regression tests that fail on the old contract, plus a one-day challenge grace so a same-block deadline cannot make a rogue signature unchallengeable.
WHAT WE LEARNED
The bound matters more than the brain. The agent is rule-v1 (deterministic HIFO plus the first allowed substitute); there is no LLM in the loop, and we say "agent", not "AI". Whatever decides, the contracts recompute it, so the decision-maker can be swapped without being trusted.
HONEST LIMITATIONS
- Testnet only. No mainnet deploy (gas budget, unaudited code).
- Oracle mark, swap venue and bond USDC are MOCK contracts, labeled.
- Wash-sale scope: re-buys are walled for the 30 days after a harvest; the 30-day look-back before the sale is not enforced yet.
- The substitute map is owner-curated; map and oracle changes are not timelocked yet.
- There is no end-user app, SDK or CLI yet: today the product is the contracts, the verify page and reproducible scripts.
WHAT'S NEXT
- Enforce the wash-sale look-back (the ledger already records acquiredAt per lot) and record executed envelope hashes in the mandate.
- A real venue and price feed with a minOut bound, timelocked owner levers, then an external audit.
- An owner-facing app and a mainnet launch on Robinhood Chain.
Built solo by Edy Cu (github.com/edycutjong) for the Arbitrum Open House Singapore Online Buildathon. MIT licensed.
Everything judged was built during the Buildathon (event period from 14 Sep 2026). The only earlier commit is a .gitignore (8 Aug). Third-party libraries (OpenZeppelin 5.4, forge-std, the Stylus SDK) are dependencies, not our code.
15 SEP — DAY 0
- Stylus spike: ArbWasm.stylusVersion() = 3 on Robinhood Chain; a Stylus counter deployed, activated and called; first real tx.
- The six system contracts and their Foundry suite; Slither to 0 high / 0 medium.
- TaxLotLedger in Rust with an ABI-identical Solidity twin and native Stylus tests.
- Deployed on Robinhood Chain testnet; all three beats executed as real transactions.
- Stylus vs Solidity benchmark on chain at 8 to 128 lots (2.87× at 64, 3.1× at 128); README, DEMO, JUDGE and ARCHITECTURE written from the code.
27 SEP — AUDIT AND SURFACES
- Self-audit: fixed a stack-too-deep that broke forge build/coverage in CI; hardened AgentBond in source (live-envelope-only challenges, no bond exit while the agent is active, one-day challenge grace) with regression tests; now 81 Foundry + 6 Stylus tests, 100% line coverage on src/.
- Every judged number re-checked against executed output (test count, coverage, beat timing, benchmark pick counts) and corrected where it had drifted.
- Live site, a /verify page that reads chain 46630 from the judge's browser, and a 12-slide deck.
The full history is in the git log: https://github.com/edycutjong/harvestbot/commits/main
Not raising. Bootstrapped, solo. No users, revenue or grants yet. The next milestone is a mainnet launch on Robinhood Chain after an external audit.