Documentation
Everything you need to understand and run Onca, on one page. The custody model first, then how to install the tools, then the notes on building Solana tooling for wasm.
Custody & the threat model
An agent joins a private key to a language model, and that model reads text you do not control: chat messages, mail, web pages. An attacker can hide an instruction in that text. If the agent can sign and send, one hidden instruction can move your funds. That attack is prompt injection.
You cannot remove prompt injection from a model. So Onca limits what a successful injection can reach. The rule is simple: the agent proposes, and a person, a multisig, or a limited session key disposes.
The custody ladder
The bounty defines four tiers. Onca ships only the two safe ones.
| Tier | Name | The tool can | Secrets held |
|---|---|---|---|
| T0 | Read | read the chain and report | an RPC key at most |
| T1 | Build | return an unsigned request for a person to sign | none |
| T2 | Sign & send | sign and submit | a scoped session key |
A T2 tool is the tier where one successful injection empties a wallet. It is possible to build one safely, with hard spend caps, a mint allowlist in the plugin, a session key that holds little, and an approval gate. But none of the Onca tools need that risk to do their job, so none of them take it. This is a deliberate boundary, not an unfinished feature.
Where the guardrails live
Each guardrail lives in the pure Rust core of its plugin, not in the prompt, and runs on every call. The model never sees the config that limits it and cannot turn it off. To pass a guardrail you would have to change the source and rebuild the component.
| Plugin | Guardrail |
|---|---|
| solana-pay-request | max amount, mint allowlist, address check, memo encoding |
| token-risk-check | verdict from chain facts only; refuse a malformed mint |
| payment-watch | amount checked in base units; scan every signature |
| depin-attest | reading bounds + monotonic replay guard; unsigned tx only |
How each tool fails closed
Every tool ships a test that sends a hostile input and checks that the tool refuses. The unsafe path returns “no,” never “maybe.”
- solana-pay-request refuses an amount over the cap, a token off the allowlist, and an invalid recipient.
- token-risk-check returns red for a honeypot, whatever the message claims about it.
- payment-watch returns underpaid for dust and pending for a failed transaction. It never returns paid from a message alone.
- depin-attest refuses a reading outside the configured bounds and a sequence that does not move forward, so a fabricated or replayed reading is never attested.
What a secret can reach
The worst case for every secret in the suite. None of them can spend funds.
| Secret | Held by | Worst case if leaked |
|---|---|---|
| RPC URL with API key | token-risk-check, payment-watch, depin-attest | read access to a node, plus RPC quota use |
| none | solana-pay-request | nothing to leak |
The RPC URL can carry an API key, so no tool writes it to a log or an error message. An error reports the HTTP status code, never the URL.
Install & run
For an operator running a ZeroClaw agent who wants to add the Onca tools.
Before you start
- A Rust toolchain, 1.80 or later, and the wasm32-wasip2 target.
- A ZeroClaw build with a wasm plugin backend.
- A Solana JSON-RPC endpoint of your own; the two read tools need it.
rustup target add wasm32-wasip2
Build a component
Each build writes a .wasm file under the plugin’s target directory.
cd plugins/solana-pay-request cargo build --target wasm32-wasip2 --release # → target/wasm32-wasip2/release/solana_pay_request.wasm
Do the same for token-risk-check and payment-watch.
Place it and configure
Copy the .wasm next to its manifest.toml in your ZeroClaw plugins directory, then enable plugins and add a section per tool. The host hands each plugin only its own section, because the manifest asks for config_read.
[plugins] enabled = true [[plugins.entries.solana-pay-request]] label = "Bar do Zé" allowed_mints = "USDC" max_amount = "100" [[plugins.entries.token-risk-check]] rpc_url = "https://your-rpc-endpoint/?api-key=…" [[plugins.entries.payment-watch]] rpc_url = "https://your-rpc-endpoint/?api-key=…"
Run it, use it
Start ZeroClaw with a wasm plugin backend. Then solana-pay-request and token-risk-check answer a chat request directly — “charge table 4 for 25 USDC,” “is this mint safe?” payment-watch works best on a cron SOP that polls an open invoice and posts to your channel when it clears.
zeroclaw --features plugins-wasm,plugins-wasm-cranelift
Building for wasm32-wasip2
The hard part was never the Solana logic. It was compiling anything Solana-shaped into a WIT component. The notes that cost the most time:
solana-sdk does not belong here
solana-sdk and solana-client expect a full operating system: sockets, threads, getrandom, a socket RPC client. A sandboxed component has none of that, and forcing them to build bloats the artifact. So onca-core hand-writes the small surface the plugins need — address parsing, a JSON-RPC message, amount math — on serde, serde_json, and bs58, all pure Rust that builds for the target unchanged.
Keep HTTP out of the core
waki, the blocking wasi:http client, exists only on wasm; if the core imported it, host tests would fail to build. So the core declares an RpcTransport trait and makes no call itself. The wasm shim supplies waki; the tests supply a mock. That one seam is what puts the whole RPC layer under cargo test with no network.
[target.'cfg(target_family = "wasm")'.dependencies]
waki = { version = "0.5.1", features = ["json"] }The output is a component, not a module
Built with wit-bindgen and export!, the binary starts 00 61 73 6d 0d 00 01 00 — the 0d is the component-model layer, so no cargo component post-step is needed. The release profile uses opt-level = "s", lto, and strip to keep each component small.
Per plugin
Each plugin has its own README with a threat model and a fail-closed transcript.
This page is the summary. The full source, every test, the per-plugin READMEs, and the raw docs live in the repository on GitHub.