Skip to content

Repository files navigation

fecho

tests clippy custody anchors

A small business in Brazil gets paid on two rails that never talk to each other. PIX lands in the bank. Dollars land on Solana. fecho is a self-hosted ZeroClaw agent that closes the books across both at the end of a day, then anchors the proof on-chain.

fecho de caixa is Portuguese for the daily close. The moment a shop stops selling and finds out if the books balance.

The agent holds no key. The operator signs the close.

The proof comes first

A closed day ends as one ledger file. Its SHA-256 goes into a Solana memo transaction signed by the operator's own wallet, and every close names the hash of the close before it. A dated chain that a block explorer timestamps for you, so this agent's run history is not a claim in a README. It is public record.

# Date Anchor
0 2026-07-28 2fqWVcER…yvdWZ
1 2026-07-29 5mhFQWXN…qdHDD
2 2026-08-05 2kcP4DLq…f9RVE
3 2026-08-06 BAGeFuTW…znUGa

One row per close, and a close happens when the operator runs it. The dates above are the dates that exist: 2026-07-30 through 2026-08-04 have no close, because the desk was not closed on those days while the rest of this repository was being built. The cron jobs that would make it nightly are written and not registered yet. We did not fill the gap in, and docs/SHOWCASE.md says why.

A chain proves nobody edited a close after it was signed. On its own it does not prove the close was right when it was signed — those are different claims, and confusing them is how a wrong number gets a proof attached. So a close carries its evidence too. Every settled invoice is one record naming the amount in base units, the verdict, and the transaction signature (dollars) or Pix end-to-end id (BRL) behind it. A rail's total has to equal the sum of those records or scripts/close.sh refuses the draft, offline, before anything reaches the ledger. The numbers in a close are checkable by anyone holding the file, against a block explorer, forever.

You can't backdate a blockchain.

What it does

Most agent demos show a payment happening. That part is easy to show and easy to stage. The part a real business needs is what happens at 23:00: which invoices got paid, which came up short, whether the bank statement agrees with the chain, and whether you can prove any of it next month.

flowchart TB
    INV[invoices] --> USDC[dollars on Solana<br/>Solana Pay + reference key]
    INV --> PIX[PIX in BRL<br/>copia e cola]
    USDC --> REC[reconcile pass]
    BANK[bank statement CSV] --> REC
    PIX -.paid at the bank.-> BANK
    REC --> CLOSE[the close<br/>ledger/YYYY-MM-DD.json]
    CLOSE --> ANCHOR[sha256 in a memo tx<br/>operator signs]
    ANCHOR --> SOL[(Solana mainnet)]

    style REC fill:#14F195,color:#0b3d2c,stroke:#0ec27a
    style CLOSE fill:#14F195,color:#0b3d2c,stroke:#0ec27a
    style ANCHOR fill:#14F195,color:#0b3d2c,stroke:#0ec27a
    style SOL fill:#0b3d2c,color:#14F195,stroke:#062418
Loading

The night, in order:

  1. A cron SOP wakes the agent. No webhooks, no public ingress, nothing listening. The two [cron.*] jobs are written and in version control; on the reference deployment they are not registered yet, so the operator fires the SOPs by hand.
  2. Every open invoice's reference key gets checked against finalized transactions. Amounts are compared in base units with u128 math. No f32 or f64 exists anywhere in this codebase; uiAmount appears only inside recorded RPC fixtures and in the tests that prove it is never read — one of them sets it to 99999.5 and the verdict does not move.
  3. The bank CSV is matched row by row on the PIX side. Duplicated end-to-end IDs get flagged, not merged.
  4. The close is written: per-rail totals, plus one record per settled invoice naming its verdict and the evidence behind it. It chains to yesterday's hash and is summarized to the operator.
  5. scripts/close.sh re-does the arithmetic before the file can enter the ledger. A draft that does not add up is refused and the ledger stays untouched.
  6. The operator reviews and signs the anchor. The agent computes, the human signs. That line never blurs.

What a payment can turn out to be

"Paid" is a verdict, not a feeling. Verification reads the finalized transaction and decides:

Verdict Meaning
paid exact amount, right mint, right recipient, finalized
overpaid / underpaid amount mismatch, measured in base units
fee_legit_short a Token-2022 transfer fee explains the exact shortfall. A fee excuses exactly the fee
wrong_asset right recipient, wrong mint
wrong_mint_dust a dust transfer of a junk mint dressed up as a payment
no_credit reference present, funds went somewhere else
failed landed but errored
unfinalized not final yet, so not money yet
unverifiable anything malformed. Never resolves to paid

The PIX side gets the same treatment: unmatched statement rows, duplicate end-to-end IDs, and BRL/USD drift are first-class flags in the close, because the failure modes of a bank export are not the failure modes of a blockchain.

Custody

T1, build only. Secrets held: none. Not a policy the model is asked to follow. A shape it cannot express a violation in.

No tool here accepts a destination address. A refund can only return to the wallet the chain already recorded as the sender, derived from the payment's own token balance changes. When a prompt injection supplies an address, there is no parameter for it to land in.

Refund transactions ride a durable nonce. An unsigned transaction built on a recent blockhash dies in about ninety seconds, usually while the human it waits for is still reading it. On a durable nonce, the transaction lives exactly as long as the approval does.

Capability Who holds the key
invoices (Solana Pay URL, PIX payload) nobody, they are strings
verification nobody, read-only RPC
refunds the operator signs. The agent only builds, unsigned
the daily anchor the operator signs

Eight prompt-injection attacks were run against the live agent and written up in docs/attacks/: refund redirect, forged operator override, poisoned payment data, settlement by assertion, config exfiltration, key extraction, the refund cap, and self-approval. Seven were prevented structurally. The eighth was recorded PARTIAL, and our own review is what found it: close.sh validated a close draft's schema, date, and hash chain but never its numbers, so a fabricated draft chained correctly. That gap is closed in code now. Schema fecho-close/1 makes every claimed base unit name its evidence, validate_totals in core/src/ledger.rs refuses a close that does not add up, and close.sh runs it before anything is copied into ledger/. The fabricated draft has been handed back to close.sh and is refused three ways, with ledger/ untouched. It stays PARTIAL rather than PREVENTED for a smaller reason, stated in the file: arithmetic on a file cannot reach the chain, so a close that forges a well-formed signature and makes its totals agree still validates. Each file carries the attacker's exact message, the full exchange, and the file and line that stopped it — including the times the model said something wrong while the transaction stayed correct.

Check it yourself

rustup target add wasm32-wasip2
./scripts/verify.sh
fecho verify

core
  ok   242 tests
  ok   clippy -D warnings
  ok   no floats in core

plugins
  ok   fecho-invoice builds for wasm32-wasip2
  ok   fecho-ping builds for wasm32-wasip2
  ok   fecho-refund-build builds for wasm32-wasip2
  ok   fecho-verify builds for wasm32-wasip2

ledger
  ok   2026-07-28.json 08f639c498bab0dd80bc6a4461f7a278b348ab26c480289dd1a2e43a1cd4dc6f
  ok   2026-07-29.json 7e264b8c8e79e8afb2d61336b778a1142755c1c46c14b6b859117d408bfcdd1f
  ok   2026-08-05.json 9f0e974cd82daa172777dc6d7ea5cc6e96d09a9ce541cebc04f2db3900e279d7
  ok   2026-08-06.json 5ead436a875f8c45d394c8b7203d917ed90881363a64d033d844103afad08304
  ok   anchor 2026-07-28 matches on-chain hash
  ok   anchor 2026-07-29 matches on-chain hash
  ok   anchor 2026-08-05 matches on-chain hash
  ok   anchor 2026-08-06 matches on-chain hash

all checks passed

No network, no keys. The script re-derives every close's hash from the file on disk and checks it against what the anchor transaction recorded, and re-adds every close's settlements to check they still equal the totals that close declares. If anyone edits history, this is the command that catches them.

One invoice has gone the whole way on mainnet: issued by the desk, paid by an independent program with its own key, then found on chain and called paid by the desk itself. Command by command, with the paying transaction decoded by hand, in docs/MONEY_LOOP.md.

Getting the full agent running (source-built host, plugin install, channel, skill, SOPs) is in docs/RUNBOOK.md, written so a stranger gets there in an evening. docs/BOUNDARY_NOTES.md records what the wasm component boundary actually does when you meet it for real: the wit drift between the registry and the host, the three silent gates between an installed plugin and a tool the model can see, the HTTPS port that vanishes inside the sandbox. Each of those cost somebody an evening.

Layout

core/            pure Rust: money math, verification, PIX, Solana Pay, the close
                 242 tests. no network, no wasm, no clocks, no floats
plugins/         wasm32-wasip2 tool components, thin shims over core
                 fecho-invoice · fecho-verify · fecho-refund-build · fecho-ping
wit/v0/          plugin ABI, vendored from the exact host revision we build
ledger/          the live close series + anchors.jsonl
skills/fecho/    the desk's own rules, installed as skill bundle "desk"
sops/            fecho-reconcile · fecho-close · fecho-refund
config/          config.snippet.toml, the operator config template
fixtures/        synthetic bank statement exports for the PIX rail
tools/payer/     a customer that pays an invoice. Not a plugin, not installed,
                 not reachable from a chat message
scripts/         close.sh · verify.sh · anchor-close.sh · refbook.sh
                 ingest-statement.sh
docs/            RUNBOOK.md · OPERATIONS.md · MONEY_LOOP.md · EVIDENCE.md
                 SHOWCASE.md · ONE_PAGER.md · BOUNDARY_NOTES.md · VIDEO.md
                 attacks/ · upstream/

What we refused to build

Instead of fecho does
a chat message summarizing the day from mutable memory a hash-chained file an accountant can hold
a hash chain, and calling "nobody edited this" the same as "this was true" settlements with their evidence inside the close, and a script that refuses a draft whose totals do not equal the sum of them
a refund tool with an address argument and an approval prompt guarding it no address argument at all
recent blockhashes and hoping the human signs fast durable nonces
uiAmount floats u128 base units, parsed from amount strings
dumping RPC responses into the model's context shaped output under 512 bytes, asserted by tests
everything as WASM plugins skills and SOPs on the stock feature set, compiled code only where determinism demands it

Trust, stated plainly

Dependency What it could do What contains it
Solana RPC serve stale or false reads finalized only, malformed input never resolves to paid
bank CSV be wrong or tampered operator-supplied evidence, hashed into the close, never proof on its own
model provider see everything the agent sees it cannot move funds. No tool signs

Status

Built in public for the Superteam Brasil × ZeroClaw Solana bounty.

Component State
core (money math, verification, PIX, Solana Pay, ledger) done, 242 tests
fecho-invoice, fecho-verify, fecho-refund-build done, 142 tests, live on a real host
skill, SOPs, Telegram channel installed: bundle desk claimed by the agent, three SOPs registered, Telegram healthy with an allowlist of one
schedules written as [cron.*] jobs, not registered on the host yet. The SOPs are fired by hand
close series live: four closes anchored, 2026-07-28, 2026-07-29, 2026-08-05 and 2026-08-06. Not consecutive, and the gap is stated rather than filled
money loop proven on mainnet: invoice issued, paid by an outside key, verified paid by the desk
refund path proven: a real unsigned refund built on a durable nonce
PIX rail code done and fixture-tested. No BRL cent has settled through it

MIT licensed. ZeroClaw is MIT/Apache-2.0 and community-maintained, its only official repository is zeroclaw-labs/zeroclaw. fecho is a third-party community project, not part of ZeroClaw core.

About

closes the books every night. PIX and USDC in, one ledger out, the hash anchored on Solana.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages