Selo is a pure-Rust, transport-agnostic cryptographic accounting engine built for stablecoin income reporting, multi-wallet FIFO tax lot tracking, air-gapped auditor verification, and deterministic daily closes on Solana. It ships as a single stock release binary -- no WASM, no plugins, no compiler required.
Strict Dependency Inversion: pure business logic, tax lot state machines, and ZK commitment math live exclusively inside selo-core with zero network or I/O dependencies. Network transport, storage drivers, and CLI interaction reside strictly in selo-tool.
Brain and Hands Pattern: the Brain (selo-core) processes identification, net deltas across any DeFi protocol, PTAX fiat valuations, and Poseidon Merkle trees. The Hands (selo-tool) handle Solana JSON-RPC I/O via ureq, local disk persistence, and monochrome split-seal report rendering.
A user installs the stock ZeroClaw binary, adds config and skills, and the selo binary runs the money path. Reading and matching are custody T0 with no key. The period anchor and refunds render an unsigned transaction that a human signs (T1), using a durable nonce so it never expires while waiting. One implementation, one threat model, one place to audit.
The full comparison is in notes/architecture-comparison.html.
flowchart TD
subgraph Input["PODA · Input Layer"]
RPC["Solana RPC Nodes<br/>(Helius / mainnet-beta)"]
PTAX["BCB PTAX Feed<br/>(series 10813, USD/BRL)"]
TX["Transaction Streams<br/>(getSignaturesForAddress)"]
end
subgraph Processing["ALICERCE · Processing Layer"]
STORE["Local JSON Stores<br/>(.selo_store / .selo_rules / .selo_ledger)"]
TRANSPORT["RPC Transports<br/>(ureq JSON-RPC, RpcSeam trait)"]
REFMATCH["Reference Key Matching<br/>(getSignaturesForAddress against active quotes)"]
end
subgraph Logic["VIRGILIA · Business Logic Layer"]
FIFO["Pure FIFO / Multi-Wallet<br/>State Machines"]
PTAXCONV["PTAX Conversion Engine<br/>(USD to BRL at block time)"]
CP["Counterparty Resolution<br/>& Identity Shield"]
SETTLE["Settlement Loop<br/>(match open quotes against on-chain activity)"]
end
subgraph Crypto["SELO · Cryptographic Closing Layer"]
POSEIDON["Poseidon BN254<br/>Merkle Root Commitment"]
CLOSE["Daily Close Compaction<br/>(byte-identical canonical records)"]
NONCE["Durable-Nonce Anchor<br/>Preparation"]
HTML["Self-Verifying HTML<br/>Report Export Suite"]
end
subgraph Output["Output Layer"]
BROWSER["Standalone Offline<br/>HTML Reports"]
VERIFY["Air-Gapped Auditor<br/>Verification"]
ZEROCLAW["ZeroClaw Adapter<br/>(WhatsApp / Telegram)"]
CRON["Background Daemon Cron<br/>(recurring reconciliation)"]
end
RPC --> TRANSPORT
PTAX --> PTAXCONV
TX --> TRANSPORT
TRANSPORT --> STORE
STORE --> FIFO
REFMATCH --> SETTLE
FIFO --> PTAXCONV
PTAXCONV --> CP
CP --> SETTLE
SETTLE --> CLOSE
CLOSE --> POSEIDON
POSEIDON --> NONCE
NONCE --> HTML
HTML --> BROWSER
HTML --> VERIFY
CLOSE --> ZEROCLAW
SETTLE --> CRON
ZEROCLAW --> CRON
The ingestion engine reads every transaction through net deltas: it computes
Post-Balance - Pre-Balance at the transaction boundary and ignores
everything that happened inside. This is protocol-agnostic. A Meteora DLMM
yield harvest, a Jupiter Perps PnL settlement, a Drift funding payment, an
Orca Whirlpool rebalance -- all of them collapse to a single net delta per
mint per transaction. No instruction-level parsing, no protocol-specific code.
Combined with the ingest checkpointing (each signature is saved to disk as it is processed), a wallet with thousands of DeFi transactions can be ingested in sessions. If it crashes at transaction 7,000 of 12,000, the next run resumes at 7,001 rather than restarting from zero.
The tax lot book is integer-exact. Ingest persists the raw ledger events and
rebuilds the FIFO/HIFO LotBook from them on every run, so an interrupted
ingest resumes by re-deriving the same book rather than mutating a partial
one: positions are never destroyed by a re-run, disposals are atomic, and
the oldest acquisition is always consumed first regardless of arrival order.
Resolved PTAX and SOL/USD rates are persisted alongside the events, so a live
feed that succeeds today and is rate-limited tomorrow cannot quietly change
the ledger; the book is deterministic and offline after the first ingest.
A stored rate that is missing or zero is treated as a miss and re-resolved,
and every rate is floored at the documented historical PTAX, so a broken
feed can never multiply cost basis by zero.
By default every outbound transfer to a classified counterparty is booked as
a capital disposal, and a payment with no same-transaction income realizes a
full loss. Run selo-tool ingest <pubkey> --all --payments-as-expenses to
adopt the alternative policy: payments to counterparties are treated as
operating expenses. The position is still reduced, but no capital loss is
booked and nothing reaches the gains report or the tax calculation. Only
swaps (an expense with income in the same transaction) remain capital
disposals. The choice is persisted on the ledger.
- Phase 1: Poda (Stages 1-3) -- Workspace cleanup and crate separation (selo-core and selo-tool)
- Phase 2: Alicerce (Stages 4-5) -- Foundational RPC infrastructure (RpcSeam and ureq) and persistence
- Phase 3: Virgilia (Stages 6-9) -- Ledger Intelligence, Solana Pay URIs, PDA derivation, and counterparty auto-labeling
- Phase 4: Selo (Stages 10-12) -- Deterministic Daily Closes, Poseidon BN254 Commitments, Identity Shield, Self-Verifying HTML Audit Reports, Ingest Checkpointing, Per-Wallet Date-Ranged Reports
- Phase 5: Country Support -- Reporting-currency selection beyond BRL (US, Canada, and others; the exact country set is not finalized). The operator picks a country once at merchant setup, stored in
.selo_merchant.json, and the valuation layer reads it at ingest time. Changing country later requires a re-ingest so cost basis stays deterministic. USD-style currencies: stablecoins value at 1:1 and SOL uses the existing SOL/USD cascade without the BRL hop. Tax columns differ per country, so the applicable regime is researched and codified per country as it is added; Brazil keeps its flat 15% capital-gains column, and a US mode would split gains into short-term (held under 1 year) and long-term (held 1+ years) with no single flat rate.
cargo test --workspace296 tests, all in selo-core, plus 5 in selo-tool. No test requires a network connection or a running node.
Selo supports human-readable counterparty names across all wallet inputs:
cargo run -p selo-tool -- balance "Relayer"
cargo run -p selo-tool -- balance 7Xw19aK4mQ2vB8pY3zN6jR5wL8kQ9tM4sP2vX1yZ3kL9# Issue a payment intent quote with single-use reference key:
cargo run -p selo-tool -- issue --amount 500000000 --recipient <PUBKEY> --label "Design Work"
# Scan cluster for settlements matching stored reference keys:
cargo run -p selo-tool -- confirm# Manage counterparty mapping rules:
cargo run -p selo-tool -- rules --add <PUBKEY> --name "Client Escrow"
# Ingest transaction history with automatic checkpointing.
# If interrupted, the next run resumes from the last processed signature:
cargo run -p selo-tool -- ingest <PUBKEY> --all
# Filter by date range:
cargo run -p selo-tool -- ingest <PUBKEY> --since 2026-01-01 --before 2026-12-31
# Surface unclassified counterparty addresses needing review:
cargo run -p selo-tool -- review <PUBKEY>The ingest command saves after every transaction. A wallet with thousands of DLMM, perp DEX, or AMM transactions can be ingested across sessions. Already processed signatures are skipped on resume.
cargo run -p selo-tool -- close --merchant <PUBKEY> --start 1750000000 --end 1750086400 --output daily_audit.txt# Export self-verifying HTML audit report for a fiscal year:
cargo run -p selo-tool -- export-html --year 2026 --output audit_statement.html
# Per-wallet report:
cargo run -p selo-tool -- export-html --year 2026 --wallet <PUBKEY> --output wallet_audit.html
# Date-range scoped report:
cargo run -p selo-tool -- export-html --year 2026 --from 2026-01-01 --to 2026-06-30 --output h1_audit.html
# Verify local tax ledger against a cryptographic Poseidon BN254 root:
cargo run -p selo-tool -- verify --root 0x09be3021160dce395ebe3617c382a8adba...# Fetch live BCB PTAX rate and historical baseline:
cargo run -p selo-tool -- ptaxThe BCB SGS API (series 10813) is queried for the current USD/BRL rate. For
stablecoins (USDC, USDT, PYUSD) the cost basis formula amount * PTAX is
correct because 1 stablecoin is approximately 1 USD. For volatile assets like
SOL, fetch_sol_brl_price() in ptax.rs combines a live SOL/USD price from
Jupiter's price API (https://lite-api.jup.ag/price/v3) with the BCB USD/BRL
PTAX rate to produce a SOL/BRL price for oracle-derived cost basis entries.
When either feed is unreachable, it falls back to historical defaults
(DEFAULT_HISTORICAL_SOL_USD = 20.00 and DEFAULT_HISTORICAL_PTAX = 5.0500),
and the is_live flag in the return value lets the caller distinguish a live
price from a fallback.
Historical SOL/USD for backfilled dates is resolved from two independent
sources in selo-tool: CoinGecko's daily history endpoint first, then
Binance's daily kline close for the same UTC day. Resolved rates are persisted
on the ledger alongside the events, so a feed that is rate-limited or offline
on one run cannot silently change the cost basis on a later one; the book is
deterministic and offline after the first ingest. SOL historical prices in a
ledger that was built before this cascade (or under the fallback constant) can
be corrected by re-resolving the affected dates against Binance and re-running
ingest.
Selo runs through ZeroClaw 0.8.4 as the selo agent with the selo skill
(see zeroclaw.toml for the canonical wiring and skills/selo/SKILL.toml for
the skill definition). The skill carries [[tools]] shell entries, one per
selo-tool subcommand, so the agent's model brain dispatches to the selo-tool
release binary through registered tool calls. A SKILL.md alone would load as
documentation with zero tools; the tool definitions must live in SKILL.toml.
The agent workspace is set to this repository root, because the skill shell
tools run selo-tool from that working directory where the state files
(.selo_store.json, .selo_rules.json, .selo_ledger_*.json, .env) live.
The security model is enforced in two layers. First, the agent runs under a
narrow risk profile (selo-safe): it may only run selo-tool, and only the
read/query/close-prep subcommands. The money-moving and mutation commands
(issue, refund, expire) are excluded from the agent entirely and stay
human-only behind confirm-token gates. Second, selo-tool itself never signs:
a daily close or anchor produces an unsigned transaction that requires the
human T1 signature before broadcast, so prompt injection cannot reach a
destination-changing command or a private key. The close and anchor tools
are registered under their composed names selo__close / selo__anchor, so
the risk profile's always_ask lists those exact names to force a human
approval prompt before either runs.
Telegram (primary surface). The Telegram channel is enabled and bound to
the agent. The standalone adapter at adapters/telegram/main.py remains the
deterministic operations harness (admin gating, cron scheduling, settlement
watcher, confirm-token gates, progress streaming) and is LLM-free by design;
it is an alternative to the agent for operators who want no model in the
control path.
WhatsApp (webhook bridge). Configured via the selo skill. Note that
the stock ZeroClaw 0.8.4 binary does not compile in the WhatsApp channel; a
source build with the WhatsApp channel feature is required.
The agent's model brain runs on the opencode-go provider (opencode.go,
model deepseek-v4-flash). Live credentials (Telegram bot token, opencode API
key) are resolved at launch from the gitignored .env file: the launcher
tools/start-zeroclaw.ps1 loads every ZEROCLAW_* variable into the process
environment, and ZeroClaw resolves them as config overrides. The key variables
are ZEROCLAW_channels__telegram__primary__bot_token and
ZEROCLAW_providers__models__opencode__go__api_key. Never commit .env; it
is already gitignored. Full setup instructions are in adapters/README.md.
Licensed under Apache-2.0
