Two ZeroClaw tool plugins let an agent charge a customer from any chat channel on Solana or Base L2 — the plugin builds the payment-request URL, the customer's wallet signs, and the agent confirms payment on-chain. Zero private keys held.
The full payment-terminal flow on camera: real plugin · real RPC · a real finalized devnet transaction — no mocked receipts. Solana Pay charge → customer QR → on-chain confirmation, plus the Base L2 (EIP-681) counterpart.
▶ PLAY THE DEMOA self-hosted ZeroClaw agent acts as a payment terminal on a chat channel (WhatsApp/Telegram). Staff message the agent to charge a customer; the agent generates a payment-request URL (Solana Pay or EIP-681 on Base), the customer's wallet scans the QR and pays, and the agent confirms payment on-chain — all with zero private keys held (custody tier T1).
Small businesses and family shops that accept USDC or SOL, freelancers who want a payment-link bot, and any operator who wants "charge X, confirm paid" without exposing a hot wallet.
The plugins never sign and never broadcast. A prompt-injected agent cannot move funds — it has no keys to move. The worst case is an extra payment request a human still has to approve.
Two Rust WIT components (wasm32-wasip2): solana_pay_tool.wasm (350 KB) + base_usdc_tool.wasm (371 KB). Pure logic cores with 84 passing tests total, no wasm or HTTP dependency in the test path.
| op | What it does | Custody |
|---|---|---|
create_payment | Builds a solana: transfer-request URL with a unique reference key | T1 — no keys held |
check_payment | Polls getSignaturesForAddress on the reference key to confirm paid | T0 — RPC read only |
The reference key is the trick that makes payment detectable without a server: the payer's wallet includes it in the transfer instruction, and a cron SOP polls for a confirmed signature on that key every 10 seconds.
The base-usdc-tool plugin is the Base L2 counterpart to solana-pay-tool — same terminal pattern, same custody tier, different chain. Together they give a ZeroClaw agent two-chain payment coverage with one mental model:
| Chain | Plugin | Payment URL format | Verification method |
|---|---|---|---|
| Solana | solana-pay-tool | solana:<recipient>?amount=…&reference=… | getSignaturesForAddress on the reference key |
| Base L2 | base-usdc-tool | ethereum:<usdc>@8453/transfer?address=<to>&uint256=<raw> | eth_getTransactionReceipt / chunked eth_getLogs on USDC Transfer events |
Staff message: "charge customer, 25 USDC." The agent calls base_usdc with op=create_payment; the plugin returns an EIP-681 URL targeting the native USDC contract (0x833589fC…A02913) on chain 8453. The agent posts it as a deep link / QR; the customer's wallet (MetaMask, Rainbow, …) opens, previews the USDC transfer, and signs. A cron SOP polls check_payment every 30s: eth_getTransactionReceipt confirms status, and an eth_getLogs scan (4500-block chunks — free RPCs reject wider ranges) matches the Transfer(address,address,uint256) event to the shop wallet with the exact 6-decimal amount. Paid → "Invoice #412 paid ✓."
eth_getLogs, topic-padded recipient match, receipt status check (0x1 = success, 0x0 = reverted).base_usdc_tool.wasm = 370,995 bytes for wasm32-wasip2; and a live end-to-end host test — 4/4 PASS against Base mainnet (see the full transcript in the evidence section below), 8/8 deadline-day re-verified across both chains (Solana devnet 4/4 + Base mainnet 4/4, 2026-08-07).base-usdc-tool buildsSame builder logic as the plugin's create_payment (rendered in your browser; no keys involved, nothing is signed). Scan with any EVM wallet to see the USDC transfer preview on Base. Demo shop wallet — do not send real funds.
The plugin also converts the decimal amount to raw uint256 (6 decimals) exactly as shown below — the same conversion its unit tests pin down.
solana-pay-tool buildsThis is the same Solana Pay transfer-request format create_payment produces (here rendered in your browser; no keys involved, nothing is signed). Scan it with Phantom or Solflare on devnet to see the wallet preview.
cargo test on the Solana plugin: 14 unit + 14 integration tests, all passing. Logic lives in a pure Rust module (src/solana_pay.rs) so the full suite runs natively.
solana_pay_tool.wasm, 350,929 bytes, built for wasm32-wasip2.
cargo test on the Base plugin, re-run 2026-07-30: 32 unit + 24 integration tests, all passing — URL construction, raw-amount conversion, topic padding, receipt/log parsing, config overrides, and rejection of bad addresses / zero amounts.
base_usdc_tool.wasm, 370,995 bytes, built for wasm32-wasip2.The compiled component was loaded into a real ZeroClaw host (wasmtime + cranelift) and run end-to-end against live Base mainnet: EIP-681 URL construction, live eth_blockNumber/eth_getLogs payment check (correctly unpaid), and both error paths (invalid / missing recipient) — 4/4 tests passed. Full transcript: e2e_base_usdc_transcript.txt.
Deadline-day regression re-verified 2026-08-07: 8/8 PASS — the full end-to-end suite was re-run on the submission deadline day against live RPCs on both chains: Solana devnet 4/4 (URL construction, unpaid poll, error paths) + Base mainnet 4/4 (suite above). Nothing regressed after the base-usdc-tool vendoring; both plugins are submission-ready.
Tier: T1 (Build). The plugin constructs unsigned Solana Pay URLs and reads on-chain state. It never holds a private key, never signs, never broadcasts.
refund tool and no signing capability. The injection is inert.# 1. Clone the canonical repo (repo root IS the solana-pay plugin) git clone https://github.com/jakesullivan77/zeroclaw-solana-pay cd zeroclaw-solana-pay # 2. Build the component rustup target add wasm32-wasip2 cargo build --target wasm32-wasip2 --release # 3. Install into ZeroClaw (source-built host, plugins-wasm-cranelift) zeroclaw plugin install ./solana-pay-tool/ zeroclaw config set plugins.enabled true # 4. Configure (optional: custom RPC, USDC mint) zeroclaw config set plugins.entries.solana-pay-tool.rpc_url https://api.devnet.solana.com zeroclaw config set plugins.entries.solana-pay-tool.spl_token EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v # 5. Tell the agent > Charge table 4, 25 USDC
# Vendored in the same canonical repo: plugins/base-usdc-tool/ git clone https://github.com/jakesullivan77/zeroclaw-solana-pay cd zeroclaw-solana-pay/plugins/base-usdc-tool # Same toolchain pattern — build, install, configure rustup target add wasm32-wasip2 cargo build --target wasm32-wasip2 --release # → target/wasm32-wasip2/release/base_usdc_tool.wasm (370,995 bytes) zeroclaw plugin install ./base-usdc-tool/ # Optional config (defaults shown; operator-set, encrypted at rest) zeroclaw config set plugins.entries.base-usdc-tool.rpc_url https://mainnet.base.org zeroclaw config set plugins.entries.base-usdc-tool.usdc_address 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 # Tell the agent > Charge customer, 25 USDC on Base
Also in the repo: the skill markdown that teaches the agent the payment flow, and the cron SOP that polls check_payment (every 10s on Solana, every 30s on Base). The base-usdc-tool plugin ships with its own skill (skills/base-usdc-terminal.md) and cron SOP (sops/payment-watch.cron.md).