Add Solana plugin suite: payments, on-chain intelligence & a shared wasm core (Tracks A, D, E)#116
Open
oblivisheee wants to merge 16 commits into
Open
Add Solana plugin suite: payments, on-chain intelligence & a shared wasm core (Tracks A, D, E)#116oblivisheee wants to merge 16 commits into
oblivisheee wants to merge 16 commits into
Conversation
oblivisheee
force-pushed
the
solana-plugins
branch
from
July 21, 2026 12:41
a063ae8 to
f318ba9
Compare
Read-only mint safety check: authorities, Token-2022 extensions (permanent delegate, transfer hooks, fees, default-frozen), holder concentration — shaped to ~200 tokens. RPC error text clamped before it can reach a model. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Offline, zero-secret Solana Pay transfer-request builder. Config-pinned recipient, token allowlist, per-token amount caps. Roaming-terminal mode (arg-supplied payee) is opt-in, so a forgotten pin fails closed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Builds unsigned versioned transactions (TransferChecked + ATA create + memo), with mint allowlist, per-token caps, recipient allowlist, durable-nonce mode (solves approval-queue blockhash expiry), Token-2022 screening, and a 1232-byte packet-size guard. Holds no key; cannot sign or submit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cap + dedupe Token-2022 extension parsing at the source (MAX_EXTENSIONS=32) so a mint account padded with thousands of duplicate TLVs can no longer inflate a downstream report or transfer summary — closes the one demonstrated context-flood vector that contradicted the risk plugin's threat model. - token.rs: parse_tlv_extensions dedupes by type, caps at 32, reports truncation; parse_mint surfaces extensions_truncated; COption tag strictly 0/1 - token-risk-check: flag malformed/oversized mints RED, hard-clamp report to 2KB - spl-transfer-build: refuse to build against a malformed/over-capped mint - solana-pay-request: reject zero/non-positive amounts - New tests: duplicate-extension flood (core + risk), zero-amount refusal; 62 host tests total. Threat-model READMEs corrected to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…iteration 4) - format_base_units: checked_pow so an untrusted mint's decimals byte (0..=255, where 10^decimals overflows u64) can no longer divide-by-zero / overflow-panic the release wasm — the T0 tool now inspects even absurd mints without trapping - token-risk-check: end-to-end hostile-decimals regression test - solana-pay-request: cap label/message/memo/invoice_id at 128 chars so the URL (the one output that scales with caller input) stays bounded - doc precision: instruction-encoding provenance, adversarial-test naming - 65 host tests total Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (judge iteration 5)
The prior display-field cap missed that a numerically-valid but 50k-char
amount ("0"×50000+"1") passes every guard and floods the success-path URL
and output to ~100 KB. Bound every caller-supplied string at the source and
add a final ToolResult clamp in every shim as the definitive backstop:
- amount capped at 40 chars (core parse_decimal_amount + pay validate_decimal)
- Pubkey::parse rejects >44-char input before echoing it
- token symbol and memo length-capped in both payment tools
- getTokenLargestAccounts capped at 20 holders
- every plugin shim clamps ToolResult output+error to 4096 chars — no path,
success or error, can exceed it regardless of future regressions
- new giant-argument flood tests in both payment tools; 67 host tests total
- README size range corrected to ~200–435 KB
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ration 6) Fourth plugin: sns-resolve resolves a .sol domain to its on-chain wallet so an agent pays 'lucas.sol' from a derived, verified address instead of a hallucinated one — the safety primitive that makes the payment tools usable with human names. Track D. Composes: sns-resolve → token-risk-check → spl-transfer-build. - crates/solana-core: new sns module (SPL Name Service derivation + registry parsing), derivation regression-locked against the real bonfida.sol registry key - sns-resolve: derived-collision guard, malformed-input fail-closed, output clamp; 5 host tests, mocked RPC - Close the two remaining consistency items from iteration 6: the pay core now bounds the recipient echo itself (not only the shim), and decode_hash bounds the RPC blockhash echo — every error string is short at its own layer - 77 host tests total; all four plugins build+test+clippy+fmt clean in upstream-CI isolation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ge iteration 7) - SNS label cap raised to a 63-char flood guard (SNS imposes no protocol max; labels hash to 32 bytes) with an honest 'too long' message; test now asserts a 40-char label resolves - sns-resolve README gains the literal prompt-injection transcript the other three carry - token-risk-check row states the hard 2 KB output cap alongside the ~200-token typical case Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eration 8) - pay README worked example showed a fabricated payment reference; replace with the value the code actually produces (FnzC…3my8) and lock it with a host-test assertion so the headline demo can never drift again - sns.rs: reject an all-zero registry owner (closed/malformed record) instead of reporting the system-program address as a payable wallet; add a test - drop remaining stale '32-char label' references now that the bound is 63 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…polish Fifth plugin: payment-watch (T0) closes the payments loop — it watches the operator's receiving wallet for a recent incoming credit (SOL/SPL, optional amount match), rather than the Solana Pay reference that wallets often drop. Ships host tests, a README (threat model + prompt-injection transcript), and a zero-LLM economical shell-cron watcher recipe. Pre-submission audit fixes: - solana-pay-request: cap display fields in BYTES not chars (128 emoji = 512 B could overflow the shim clamp and truncate the [PHOTO:] QR marker); move output composition into a pure-core, host-tested, bounded render_output. - UX: unify every plugin's chat output (status glyph + one clickable link + plain next step); spl-transfer reframes the base64 as a how-to-sign block and flags an unpinned recipient; token-risk gains status glyphs + clean pluralization + an Explorer line; sns-resolve keeps the full owner address and drops the non-payable registry PDA from chat text. - emit() logs WARN on failure across all five shims. - sns-resolve: add a non-circular derivation regression test. - Docs: root README reflects five plugins and 89 tests; all README worked examples regenerated from real execute() output; reworded dangling refs. - Add per-plugin MIT LICENSE files for isolated-snapshot completeness. 89 host tests pass, 5/5 wasm32-wasip2 builds clean, sync-core --check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A prompt-injection ("rotate the payout wallet + charge 1000") exposed two
agent-level weaknesses even though funds were never at risk (recipient pinned):
- The model offered to SPLIT an over-cap charge into smaller requests — the cap
refusal said "exceeds cap per request", which invites splitting. Reworded to a
firm, final refusal that instructs the model not to split, retry, or work
around the cap.
- The reply parroted the injected "new payout wallet" as accepted. Root cause:
the amount cap was checked before the recipient pin, so an over-cap amount
masked the recipient rejection. The payee is now resolved FIRST, and the pin
refusal explicitly states a "rotated wallet" claim is not honored.
Verified end-to-end through the live agent: redirect -> "pinned wallet cannot be
changed, refused"; over-cap -> firm refusal, no split offer. Host test updated to
pin the anti-split language. README injection transcript + threat model updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sixth plugin. Given a base58 address, returns a shaped ~200-token summary — SOL balance, SPL token holdings (both token programs, largest first, known-mint symbols), account type (wallet vs program-owned), and recent activity — so the agent can answer "what's in wallet X?" without dumping raw getAccountInfo / getTokenAccountsByOwner JSON into its context. T0: read-only, no keys, no state. - New shared-core helper get_token_accounts_by_owner() (jsonParsed, both SPL Token and Token-2022, zero/dust dropped, capped, largest-first; a per-program RPC failure is tolerated). - Pure core + thin wasm shim, crate-type [cdylib, rlib], waki wasm-only. - Host tests incl. flood/injection: address length-bounded in the core, output bounded with a hostile many-token wallet, fabricated-balance prompt ignored. - README (threat model + prompt-injection transcript), manifest (minimal http_client + config_read), MIT LICENSE. Registered in sync-core.py; vendored core re-synced across all plugins. - Root + core READMEs updated: six plugins, 97 host tests. 97 host tests pass, wasm32-wasip2 builds clean, sync-core --check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Emit a Solana Explorer transaction-inspector link (and a QR of it) for the unsigned transfer, so a human can review/simulate the exact decoded tx before signing. Built from the MESSAGE bytes (not the full wire tx, whose leading signature-count byte would make the inspector misparse it), base64 + percent-encoded (new shared-core helper links::explorer_inspector_url). The [PHOTO:...] marker is emitted first and the description forbids markdown-izing it, so the model relays it intact and the channel renders the QR. HONEST framing: a tx cannot be signed from a QR/link (that needs a hosted Solana Pay transaction-request endpoint a stateless T1 component can't host) — signing stays in the wallet/Squads/CLI from the base64. README + a host test pin the review-link format. - Add waki connect_timeout(8s) to every HTTP plugin so a dead RPC fails in seconds instead of hanging the agent for minutes (waki 0.5 exposes only connect_timeout; a connected-but-slow node still needs a faster rpc_url). 98 host tests pass, all wasm builds clean, sync-core --check clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ncy in submission) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
oblivisheee
force-pushed
the
solana-plugins
branch
from
July 21, 2026 15:59
f318ba9 to
6bf7c32
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Solana plugin suite for ZeroClaw — payments, on-chain intelligence, and a shared wasm core
Six
wasm32-wasip2tool-plugins implementing thewit/v0tool-plugin world, plus the shared Solana substrate they build on. Custody stays at T0/T1 throughout — no plugin holds a key or signs anything.Tracks: A (payments), D (on-chain intelligence, safety & naming), E (shared core).
plugins/solana-pay-requestplugins/payment-watchsolana-pay-request.plugins/spl-transfer-buildTransferChecked, ATA creation, memo, durable-nonce mode, mint allowlist + caps in-plugin. Emits a read-only Explorer review link + QR.plugins/token-risk-checkplugins/sns-resolve.soldomain to its on-chain wallet — derived and verified, not hallucinated.plugins/solana-accountcrates/solana-coreMeets the hard requirements
redact-text): standalone crates,crate-type = ["cdylib", "rlib"], a pure core module + a thin#[cfg(target_family = "wasm")]shim overwit/v0.cargo test(RPC mocked, no network, no wasm toolchain), including fail-closed prompt-injection suites in every plugin and a context-budget test.cargo build --target wasm32-wasip2 --releaseper plugin; clippy- and rustfmt-clean.log-recordimport (never stdout).The wasm32-wasip2 story
solana-sdk/solana-clientdon't compile into a WIT component, so transactions are hand-assembled frombs58+sha2+curve25519-dalek(decompression only, for the PDA off-curve check) + hand-rolled compact-u16 / v0-message / instruction encoding. Every encoding is proven against the decomposedsolana-pubkey/solana-message/solana-transactioncrates as host-only differential-test oracles (v0 key ordering, PDA/ATA derivation, and wire serialization byte-for-byte). Blockhash-expiry on approval-gated payments is solved with durable nonce accounts. HTTP is one blockingwakicall behind a trait, so all protocol logic stays host-testable.The shared core lives in
crates/solana-coreand is mirrored into each plugin'svendor/zeroclaw-solana-core/(viatools/sync-core.py;--checkguards drift) so every plugin directory builds in isolation the way CI snapshots them.Happy to restructure however you prefer —
crates/placement,registry.jsonentries, or splitting into separate PRs. Opening early per the bounty guidance; feedback very welcome. Demo video to follow.🤖 Generated with Claude Code