Onca
← Home

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.

TierNameThe tool canSecrets held
T0Readread the chain and reportan RPC key at most
T1Buildreturn an unsigned request for a person to signnone
T2Sign & sendsign and submita 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.

PluginGuardrail
solana-pay-requestmax amount, mint allowlist, address check, memo encoding
token-risk-checkverdict from chain facts only; refuse a malformed mint
payment-watchamount checked in base units; scan every signature
depin-attestreading 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.”

What a secret can reach

The worst case for every secret in the suite. None of them can spend funds.

SecretHeld byWorst case if leaked
RPC URL with API keytoken-risk-check, payment-watch, depin-attestread access to a node, plus RPC quota use
nonesolana-pay-requestnothing 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

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.