Superteam · ZeroClaw on Solana

An agent bounded by the chain, and a node that pays its own gas

Two things run, both self-hosted, both auditable by a stranger in an evening. A DePIN node that signs its own readings on-chain and sells them per request, and a merchant terminal that takes an order in Portuguese and settles in USDC. The reading comes from a keyless public weather API on that host rather than from a physical probe; a Raspberry Pi with a DHT11 is the hardware path, and the on-chain half is identical either way, because what is signed is the value and the device key rather than the enclosure.

Every claim on this page is checkable with one command or one link. Nothing here is a screenshot of something that used to work. Start with the shortest one:

curl -i https://x402.perfpilot.dev/price

A live node answering HTTP/1.1 402 Payment Required with a single-use nonce that changes on every request. That is the whole machine-commerce claim, in one request, against a node that is running right now. The node is an Ampere ARM instance on Oracle's free tier; its signing key was generated there and has never left it.

On Windows that can fail with curl: (35) schannel: … CRYPT_E_REVOCATION_OFFLINE. The node is fine; Schannel could not reach a certificate-revocation responder and treats that as fatal for every HTTPS host. Add --ssl-revoke-best-effort, which downgrades an unreachable responder from fatal to non-fatal and still validates the certificate.

Two minutes fifteen, one take, no slides. Also on YouTube in 4K. Everything it shows is exercisable below without a wallet.

01It has been running, not demoed

2,292+publishes on chain
0failed
2independent devices
20.5mmedian gap

Read at 2026-08-15T19:41Z, and carried as a floor because the ARM node publishes every twenty minutes or so; the second device's count is final. Re-derive both rather than believing this page. It is an unauthenticated call against a public devnet RPC, no key and no account:

python3 scripts/verify-proof.py    # stdlib only, no install, no key

Two devices, not one, and that is the DePIN claim rather than a gadget claim. The same on-chain oracle program serves both, each with its own device key, each on its own machine. Neither can sign for the other.

To count either history directly, the underlying call is getSignaturesForAddress on the feed account.

Zero failures is exact and covers both devices: no transaction has ever errored. Continuity is where they differ. The ARM node's largest gap is 61.5 minutes across the run since 25 July, so "every twenty minutes" is true at the median with one hour-long interruption. The second device was laptop-hosted, so it slept, and its largest gap inside its run was 36 hours. It has since stopped for good, and the command cannot tell you that: it measures gaps between consecutive publishes, so once a feed stops its largest gap freezes at the last value it reached instead of growing to reflect the silence. Re-run it in a month and it still reports 36 hours, with nothing in the number marking the device as gone. Its job was to show the same program accepting signed readings from a second independent device with a second key, and a completed run of 778 publishes at zero failures did that. Continuity rests on the node alone. The command above shows you both anyway, which is the only reason either is worth writing down: a number you can check beats a rounder one you cannot.

02The custody argument does not depend on trusting us

An approval prompt is not a boundary. The sentence a human reads before approving was written by the model, so influencing the model influences the description. An attacker needs no key, only an operator who reads one plausible sentence and says yes.

Being key-free is what every careful entry in this space says, and it is a claim about what our own code declines to do, which is only ever as good as our code. The load-bearing difference is where the limit is enforced. Ours is not in the plugin, not in the host, and not in the prompt: it is a deployed, audited on-chain program.

Demonstrated on mainnet, and the program that does the refusing is not ours. Solana Foundation's audited Allowances program (De1egAFMk…, on mainnet since June 2026, audited by Cantina) rejected an over-cap transfer with custom error 0x12c, signed by a freshly generated agent session key. The same key moved a within-cap transfer normally, settling 0.4 USDC. Three mainnet transactions in all, captured as raw bytes in the repo, verifying offline with no network and no dependencies:

python3 scripts/verify_proof_offline.py

Prompt-inject every layer we wrote and the ceiling is still there, because it was never ours to remove.

The selling side is capped too, which the brief makes mandatory in both directions. The node sells its readings behind an x402 paywall, and a per-payer per-day ceiling is enforced in the gate's own code rather than by the protocol: a payer who drains the cap in many small buys is refused the same as one who tries it in a single large one. The ledger is durable, so restarting the process does not re-open a spent allowance. An earlier version restarted the day on every boot, which is a cap in name only. Check the fix:

curl -s https://x402.perfpilot.dev/health | jq '.ledger'

That paywall has taken real money, and the precise claim is a split one. Reading the feed and settling the payment are separate concerns on separate RPC endpoints, so the gate settled a genuine mainnet payment of 1.000000 USDC while serving a reading from our devnet feed. The settlement is 3gSg3mQE9vA5X9CmFBxGEY2EFSAMXGhaC1HrUDbH8uA3MQhuaVjCdHjb1kshyzTqWKRALa9EQPeKja2Hk2rWcF2f, finalized on mainnet-beta. The goods stay on devnet because a DeviceFeed account is owned by our zeroclaw_oracle program, deployed on devnet only, so a mainnet feed is a program deployment rather than a config flag. The hosted endpoint above runs the devnet default.

The exchange rate is off the model as well. A Brazilian order carries one money-touching number a language model could simply state, with nothing checking it: the rate. It is now fetched in code from Brazil's central bank (BCB PTAX) and refused unless the ECB's published figure agrees within a stated band. Both sources are keyless, and there is no last-known value to fall back on, so an unreachable source stops the quote instead of ageing one. On 2026-08-14 the two sat 0.91% apart, which is the margin a single source would have carried silently.

python3 scripts/rate_crosscheck.py                  # the corroborated rate, or a refusal
python3 scripts/check-pay-link-rate-agreement.py   # the pay path's copy still matches

What this does not close: the order value is still supplied by the caller. An implausible one is refused in code, so "table 4, R$ 0.05" no longer produces a link, but a plausible wrong amount still does. That band is wide on purpose, because this shop has no catalog and a narrow one would refuse legitimate orders. One free parameter of two is gone and the second is narrowed. The shop on the node has not picked either change up yet, so the enforcement is in the repo and the deploy is what remains.

03The customer's own page refuses, in their language

Every layer above is still something the agent composes, and an agent talked into a different recipient composes a perfectly well-formed link to it. So the checkout page pins the one address it will ever pay and does not trust the link that opened it. Change a single character of the recipient and the card is replaced by RECUSADO, the pay button is gone rather than disabled, and both addresses print in full, because a truncated C331…iLHJ is exactly what lets a swapped address survive a glance.

python demo/verify-merchant-invariant.py

That harness covers the swapped recipient. Six more cover the rest of the page, each written after a failure we actually hit rather than one we imagined, and each drives the live page:

python demo/verify_wallet_picker.py       # the customer can choose a wallet, at no layout cost
python demo/verify_qr_scannable.py        # the QR decodes, under two independent decoders
python demo/verify_no_cdn_dependency.py   # the pay path loads its code from this origin, never a CDN
python demo/verify_paid_link_refused.py   # a reference that already settled is refused, both directions
python demo/verify_pay_path_rpc.py        # the desktop path reaches its RPC from a browser
python demo/verify_settlement_poll.py     # the page polls for settlement, not only on load

One more covers a different surface. Those checks decode the QR off the page; what a viewer actually scans is a frame of the shipped video, and an h264 encode is precisely what degrades a QR's sharp module boundaries through chroma subsampling and deblocking. A code that decodes off the page can fail off the tape, and only the tape ships:

python demo/verify_qr_in_encoded_cut.py    # the pay QR still decodes after the lossy encode

Or exercise it yourself, right now, with no wallet and no funds. These are the live checkout page, opened with three different orders. Nothing is sent by loading any of them.

The control is the point. A page that refused everything would produce an identical screenshot, so the harness drives both directions: the pinned address must stay payable while a one-character variant is refused. It reads the pinned address out of the shipped page rather than restating it, so the two cannot drift, and neutering its tampering makes it exit non-zero instead of quietly passing.

The page localises from navigator.language and renders pt-BR end to end. The refusal a Brazilian customer actually reads: "Este link paga um endereço que não é desta loja. Nada foi enviado."

04Reproduce it: clone, then three commands

git clone https://github.com/belumume/zeroclaw-solana && cd zeroclaw-solana
python3 scripts/verify-proof.py           # 10 static + up to 5 live claims, stdlib only
python3 scripts/verify_proof_offline.py   # the custody proofs, no network at all
python3 scripts/certify_publish_tx.py     # seven injection shapes, all refused

No install step, no virtualenv, no credentials. The offline verifier refuses to report at all if its own positive control stops passing, so a green result cannot come from a broken checker.

05Four things in the repo

These are not extras bolted on for a demo. Each one exists because a specific claim elsewhere on this page would otherwise rest on our word, and each is exercisable by a stranger.

Where the cap's boundary sits today, on mainnet, with no key and no funds. The captured refusal above proves the program said no once. This finds where it says no today: it reads the remaining allowance off the delegation account and replays the captured message at amounts either side of it. It requires at least one amount to be refused and at least one to be accepted, so a program that had stopped working and a check that had stopped checking both fail it rather than printing a clean result over nothing.

python3 scripts/replay_allowance_probe.py

A second program reads the feed on chain, which is the difference between an oracle and a memo. Publishing signed bytes proves only that bytes were published. A separate deployed program, consumer_example, CPI-reads the feed account, checks the owner and gates on freshness, so the data is consumable by something other than a human squinting at an explorer. It last read the live ARM feed in transaction 4CRapo3AEFBFLh7Y7byJR9XDYZEa95MEioUQMzUhJVxTB9HaDTRtX2X47pVgxaSu8KNfYsPyugeQ6FjN8hBzi54L.

The sanitizer, in your browser, compiled from the code that ships. Untrusted on-chain text reaches a model through our plugins, so it is sanitized and labelled first. The page below is not a JavaScript reimplementation demonstrating the idea. It compiles solana_core::sanitize itself to wasm, so what you are typing at is the same function the plugins call. Paste a right-to-left override or a zero-width joiner and watch what the model would actually receive.

Open the sanitizer microworld — it runs in this browser, no clone and no build. Six presets, and one of them strips zero invisible characters and still labels the input, which is the case a canned demo would not think to include.

sanitizer-microworld/index.html    # or open it from a clone, no build step

A fuzzer graded by someone else's decoder. Every other correctness gate here tests a property we thought to write down. The differential fuzzer seeds from real serialized transactions, mutates them with correlated perturbations rather than uniform byte flips, and scores each result against solana-sdk's own deserializer as an external oracle so it can find disagreement we did not predict. It is deliberately isolated from the plugin workspace, because that oracle cannot compile to wasm and must never be reachable from the shipped crates.

06Everything else