Skip to content

tx-preflight: simulate a Solana transaction and report its observed effect - #137

Open
ace-coderr wants to merge 6 commits into
zeroclaw-labs:mainfrom
ace-coderr:cupel-plugins
Open

tx-preflight: simulate a Solana transaction and report its observed effect#137
ace-coderr wants to merge 6 commits into
zeroclaw-labs:mainfrom
ace-coderr:cupel-plugins

Conversation

@ace-coderr

@ace-coderr ace-coderr commented Jul 22, 2026

Copy link
Copy Markdown

tx-preflight — simulate a Solana transaction and report its observed effect before a human approves it.

📹 Demo (2 min): https://youtu.be/X8s-na4zFj8


What this is

A T1 tool plugin that answers one question before a human approves a transaction: what will this actually do?

An agent that builds a transaction and asks for approval hands the human a description the language model wrote. Poison the model and the approval card reads "refund the customer 25 USDC" while the bytes underneath move 2,140 and install a delegate. tx-preflight simulates against the operator's own RPC and reports the observed effect — net amounts, authority grants, accounts closed — checked against limits declared in config.

Holds no key. Signs nothing. Submits nothing.

Verified on the real runtime

Not just unit-tested. Installed and driven by a live agent on ZeroClaw 0.8.3 against devnet:

  • A legitimate transfer returns PASS (25 tokens out, 50-token cap, no grants).
  • A delegate grant framed as "routine refund authorisation" returns FAIL — the transaction moves zero tokens, so a balance check sees nothing wrong, but it hands a stranger standing authority over the whole account. Given the block instead of the description, the model wrote its own "do not sign" warning.

The full transcript is in the README; the demo video shows both live.

Notes for reviewers

First tool plugin to declare http_client. Every HTTP plugin in the repo is a channel, and redact-text makes no network calls, so this linker path had never been exercised. It works — runtime.rs::create_plugin calls with_granted_http() and selects tool_linker_http() — flagging in case that was unintentional rather than deliberate.

A wasi:http bug found by running it. An https:// URL with no explicit port fails from inside a plugin: the scheme's default port doesn't survive wakiwasi:httpdefault-send-request, so the request dials 80 and is refused before TLS, surfacing as ErrorCode::ConnectionRefused (the catch-all — looks exactly like the endpoint being down). Bisected against the host's own http_request tool, which works either way, so it's specific to the plugin sandbox. Reported in #solana-bounty; tx-preflight normalises the URL so operators don't hit it.

Two undocumented install prerequisites, also found the hard way and now in the README: the standard host build has no plugin subcommand (needs --features plugins-wasm-cranelift), and plugins.enabled defaults to false so an installed plugin's tools never reach the agent.

Guardrails live in __config, deliberately. The protected wallet, the RPC endpoint, and every spending limit are read from the host-injected config, never from execute args — the runtime strips caller-supplied __config first, so a poisoned agent can't name its own wallet and collect a clean PASS on a drain. A mistyped owner_pubkey fails closed rather than passing everything. Both have tests.

solana-sdk doesn't build for wasm32-wasip2 inside a WIT component, so the wire format is decoded by hand in cupel-core (MIT/Apache-2.0, published, no path deps): legacy and v0 messages, address lookup tables, SPL Token and Token-2022 layouts. 85 tests, all offline. Reusable by any other Solana plugin in this repo.

Fails closed everywhere. Decode failure, unreachable RPC, unresolvable lookup, malformed config, a transaction that wouldn't land, and an owner mismatch all produce the same verdict word. Output is capped at ~160 tokens.

Scope: one component, not three

The brief suggests 1–3 components. This is one, on purpose. spl-transfer-build and solana-pay-request are both in the brief and both would have been straightforward, but neither strengthens the argument this PR makes. A verifier that a stranger can install, that catches a real attack on a live chain, and that fails closed everywhere is worth more than three plugins that demo once. Depth over breadth was the deliberate call.

Checklist

  • tx-preflight — host tests (16), both clippy gates, wasm32-wasip2 release build, all --locked
  • cupel-core published to crates.io (85 tests)
  • README with config keys, custody tier, threat model, and a real injection transcript
  • Verified end-to-end on ZeroClaw 0.8.3 against devnet
  • Demo video

Built against wit/v0 at e112ce6. Happy to rebase if the ABI moves.

@ace-coderr
ace-coderr marked this pull request as ready for review July 25, 2026 02:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant