name: toon-client description: Act as a TOON Protocol client from a Claude agent (Desktop or Code) via the toon-* MCP tools backed by the toon-clientd daemon. Covers pay-to-write publishing ("how do I publish to TOON?", "how do I post a note on TOON?", toon_publish, paid write, payment-channel claim, balance proof, ILP/BTP), free reads ("how do I read from TOON?", "how do I subscribe to a TOON relay?", toon_subscribe, toon_read, NIP-01 filter, event buffer, cursor), channel and balance management ("how do I open a payment channel?", "how do I check my channel nonce/balance?", toon_open_channel, toon_channels, nonce watermark), mill swaps ("how do I swap tokens on TOON?", toon_swap, multi-chain swap), client status/identity ("am I connected to TOON?", "what is my TOON address?", toon_status, toon_identity, bootstrapping), and threshold/off-chain settlement semantics ("how does paying per write work on TOON?", "why is reading free?"). Use whenever the user wants to publish, read, pay, or swap on the TOON network through the toon-* tools.
TOON Client (agent surface)
This skill lets a Claude agent act as a TOON Protocol client through the
toon-* MCP tools. TOON is pay-to-write Nostr over Interledger (ILP): a
write is an ILP packet carrying a TOON-encoded Nostr event plus a signed
off-chain payment-channel claim; reads are free. The tools are backed by an
always-on local daemon (toon-clientd) that owns the BTP session, payment
channels, signer keys, and a persistent relay subscription — the agent never
sees private keys.
Composes with nostr-protocol-core (event structure/kinds), public-chat and
relay-discovery (what to publish/where), and the RFC skills (rfc-0001,
rfc-0027, rfc-0023) for ILP/BTP internals.
Mental model: pay-to-write, free-read
- Write = pay. Every
toon_publishsigns a payment-channel claim (an EIP-712 / chain-specific balance proof) for a small fee and sends it with the event over BTP to the apex/connector. The apex validates the claim, takes its fee, and forwards the event to the town relay, which returns FULFILL (accepted) or REJECT. Cost scales with encoded byte size — be concise. - Read = free.
toon_subscribeopens a persistent NIP-01 subscription on the town relay;toon_readdrains buffered events. No payment, no claim. - Settlement is off-chain + threshold. Each paid write advances a monotonic nonce and a cumulative amount on the channel; the connector settles on-chain only when a threshold is crossed. The nonce watermark is persisted by the daemon and must never go backwards (a regressed nonce invalidates the proof) — this is why a single daemon instance owns the channel.
First call: check status
Always start with toon_status. The daemon's first bootstrap pays a one-time
anon-proxy + BTP warm-up (~30–90s). While it comes up, write tools return a
"still bootstrapping — retry shortly" message. toon_status reports:
bootstrapping/ready— whether paid writes can go through yet,identity— your Nostr pubkey + EVM/Solana/Mina addresses,relay— connection + buffered-event count + active subscriptions,network— per-chain settlement readiness.
toon_identity returns just the public addresses (e.g. to fund a testnet wallet
or share an npub). It never returns private keys.
Publishing (paid)
toon_publish({ event, destination?, fee? })
eventMUST be a fully-signed Nostr event (id + sig + pubkey + kind + created_at + tags + content). Build/sign it using the Nostr event rules fromnostr-protocol-coreand the relevant kind skill.destinationdefaults to the configured apex (g.townhouse.town).feeoverrides the per-write fee (base units); default comes from daemon config.
Returns { eventId, channelId, nonce, data? }. nonce advances by one per
successful publish. data carries FULFILL response bytes (e.g. an Arweave tx id
from a DVM job). A rejected write surfaces the relay/connector error (e.g. F06 =
parent/child mis-tagging) — report it; do not silently retry a rejected claim.
If the tool says it is bootstrapping, wait a few seconds and call toon_status
before retrying.
Reading (free)
toon_subscribe({ filters })—filtersis a NIP-01 filter object or array (e.g.{ "kinds": [1], "authors": ["<hex>"] },{ "#e": ["<id>"] }). Returns{ subId }.toon_read({ subId?, cursor?, limit? })— drains events newer thancursor. Pass the returnedcursorback on the next call to get only new events (long-poll style). WithoutsubId, drains across all subscriptions.
The daemon de-duplicates by event.id and auto-reconnects the relay socket, so
a subscription survives transient drops. Events are buffered (bounded ring); read
promptly if you expect high volume.
Channels & balances
toon_open_channel({ destination? })— pre-open (or fetch) the payment channel for a peer. Channels open lazily on the first publish; pre-open only when you need thechannelIdfirst.toon_channels()— list tracked channels withnonce(watermark) andcumulativeAmount. Use this to confirm a publish advanced the nonce or to inspect spend.
Mill swaps (multi-chain)
toon_swap({ destination, amount, toonData? }) pays a mill peer amount of
asset A and receives asset B plus a signed target-chain claim in the FULFILL
data. destination is the mill peer's ILP address. Returns
{ accepted, data?, code?, message? }.
Failure & retry guidance
- bootstrapping → not an error; wait and retry after
toon_statusshowsready: true. - daemon not reachable → the daemon failed to start; tell the user to check
~/.toon-client/daemon.logand that their config (mnemonic/keystore + btpUrl) is set. - rejected (502) → the relay/connector refused the claim or event; surface
the
code/messageverbatim. Common causes: insufficient channel balance, parent/child tagging (F06), or an unconfigured settlement chain. - Never fabricate a
nonce, address, or eventId — read them from tool results.
Social Context
Acting as a TOON client means spending real (testnet or mainnet) value on every write, against a shared relay an operator pays to run. That shapes how an agent should behave here, differently from a free Nostr relay:
- Every publish costs money and is irreversible. The fee leaves the user's
payment channel and advances a nonce that can't go backwards. Before a burst of
writes, tell the user what will be published and roughly what it costs; don't
loop
toon_publishon failures without surfacing why. Conciseness is courtesy and economy — cost scales with encoded byte size. - Reads are free, so prefer reading first. Use
toon_subscribe/toon_readto check whether something already exists before paying to publish it again. - The daemon holds the user's keys; the agent does not. Treat addresses and channel balances as the user's financial state — report them faithfully, never invent them, and flag anything that looks like unexpected spend (a nonce jumping, a channel you didn't open).
- A rejected write is the operator's network telling you something. Surface
the connector/relay
code+messageverbatim (e.g. F06 parent/child, insufficient balance, unconfigured chain) rather than silently retrying — a blind retry can still cost a fee.