Skip to content

Repository files navigation

store

TOON Protocol store — NIP-90 kind:5094 Arweave blob storage, plus three optional job kinds: kind:5095 ArNS brokered buy (gated by ARNS_DVM_SOLANA_SECRET_KEY), kind:5096 Solana gas-station co-sign/broadcast (gated by GAS_STATION_SOLANA_SECRET_KEY), and kind:5098 EVM gas-station meta-transaction relayer (gated by EVM_GAS_STATION_CONFIG_JSON). It uploads the blob to Arweave via Turbo and returns the tx id. Built from Dockerfile.store over src/entrypoint-store.ts, which wraps @toon-protocol/sdk's createArweaveDvmHandler. It runs as a payment-oblivious POST /store backend (RouteTermination) behind the connector, which is the front-of-app payment proxy — see deploy/.

Status / follow-ups

  • Trimmed to store-only: the repo is now Dockerfile.store + src/entrypoint-store.ts + src/store-backend.ts. The other images' carried-over build contexts (Dockerfile.{town,mill,townhouse-api,akash-*,oyster,nix,sdk-e2e,toon-client,…}, their src/entrypoint-* files, and the configs/, dev-fixtures/, akash-ator-probe/, townhouse-ator-sidecar/ dirs) have been removed.
  • Image-publish workflows: publish-store-image.yml (the store app → ghcr.io/toon-protocol/store) and publish-store-connector-image.yml (the connector-with-config payment proxy → ghcr.io/toon-protocol/store-connector). Consumers pinning the old …/dvm image must move to …/store.
  • Publishes no npm package (it's a container); kept private.

Extracted from the TOON monorepo with full git history preserved.

Getting started with Devbox

Devbox pins the local toolchain to the exact versions CI uses — Node 22 and pnpm 8.15.x — so pnpm build, pnpm test, and pnpm typecheck run in a reproducible shell without touching your system packages.

Prerequisites: Install devbox (one-liner).

# Enter the pinned shell (downloads packages on first run via Nix)
devbox shell

# Inside the devbox shell, all tools are on PATH:
node --version    # v22.x
pnpm --version    # 8.15.x

# Run the standard targets (defined as devbox scripts)
devbox run build     # pnpm install --frozen-lockfile && pnpm build
devbox run typecheck # pnpm typecheck
devbox run test      # pnpm test

.devbox/ (the Nix symlink/cache dir) is gitignored; devbox.json and devbox.lock are committed.

Public network ids — zkApps / programs the store's jobs touch

The store's paid jobs execute against public, third-party on-chain programs. These ids are network-scoped; misconfiguring them silently targets the wrong registry. Canonical machine-readable source: @ar.io/sdk ≥ 4.0.3 exports (DEVNET_PROGRAM_IDS, ARIO_*_PROGRAM_ID); this table is the human-readable snapshot (verified live 2026-07-17).

ar.io Solana programs (kind:5095 arns-buy + kind:5096 whitelist)

Program Solana mainnet Solana devnet (ARNS_NETWORK=devnet, default)
ario-core 73YoECm6NKXpVRoe5f1Q9BcP5DJGPFUjnFy6AxBE5Nvh 8Njx9wPkXiNzDCgjwVsJFRjpAEV34gGW3n8DzX3V23m1
ario-gar 89fNiiwgpFSPHKuqfNUkgYTYjtAJAhyqHjXmgXeppGpf 7WsDTrtZBsfKtnP33XkjuqXCY69JE7n4QVYpynqJCFxz
ario-arns (name registry) 2yCUx5edFvUrkibYaUa2ZXWyx9kuJkS8CwyzsgHPWdZZ 6EZNezcg4rc5hnh8HG34vGquT3WpW5xXypzPb24uyEpp
ario-ant (ANT state) 2MWexMHfMhGJwMHv9Qm9YAVCqjUFUJwDJAysW4oCUGk5 DbHbRwUD1oAn1mrDSqtWtvwGcNrmhWdD2g8L4xmeQ7NX
ario-ant-escrow — (not exported for mainnet by the SDK) bttco5oAnBwCucG63iKokBJCZmNr493f3Ewe9LM3oTx
  • ar.io has NO deployment on Solana's testnet clusterdevnet and mainnet are the only valid networks (toon-client#376/#381).
  • RPC per network: https://api.mainnet-beta.solana.com / https://api.devnet.solana.com.

Cluster-invariant programs (kind:5096 gas-station whitelist)

Program Id (same on every cluster)
Metaplex Core (MPL_CORE_PROGRAM) CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d
System Program 11111111111111111111111111111111
ComputeBudget ComputeBudget111111111111111111111111111111

The kind:5096 gas-station program whitelist = these three + the ar.io programs for the configured ARNS_NETWORK row above (assembled at runtime in src/gas-station-handler.ts from the SDK exports — the table is documentation, not the source of truth), plus, optionally, the TOON payment-channel program (issue #67) when GAS_STATION_CHANNEL_PROGRAM_ID is set — see the next section. Unlike the three above, that program id is NOT a hardcoded preset: instructions against it are further restricted to deposit / close / settle (TOON_CHANNEL_DISCRIMINATORS in src/gas-station-handler.ts) so an agent can fund or reclaim its own channel without holding SOL, without the gas station ever co-signing an open or a claim.

kind:5098 — EVM gas-station meta-transaction relayer (issue #68, toon-meta#261 decision 9)

The EVM half of gasless settle for agent payment channels: a client-signed ERC-2771 forward request (OpenZeppelin ERC2771Forwarder) for TokenNetwork deposit/close/settle is validated, submitted through the trusted forwarder, and paid for by a dedicated relayer wallet — the caller needs no native gas. Deliberately a real relayer, not native-gas dispensing, so every new EVM chain gets gasless settle at deploy time (connector#694, the contract half, merged the ERC-2771 TokenNetwork + forwarder deployment support).

Mirrors the kind:5096 security model (src/evm-gas-station-handler.ts doc comment has the full mapping) — no second security posture invented for the EVM leg:

kind:5096 (Solana) kind:5098 (EVM)
(a) dedicated fee-payer wallet (a) dedicated relayer wallet, one per configured chain
(b) static inspection (fee-payer slot rules) (b) static inspection (to = configured TokenNetwork, value/gas/deadline caps)
(c) simulation + balance-delta cap (c) estimateGas on forwarder.execute(request) + gas cap
(d) program whitelist (d) function-selector whitelist: setTotalDeposit/closeChannel/settleChannel only — openChannel/claimFromChannel excluded, same rationale as kind:5096's INITIALIZE_CHANNEL/CLAIM_FROM_CHANNEL exclusion
quote → execute + idempotency key quote → execute + idempotency key
machine-readable failure reasons machine-readable failure reasons

Signature/nonce/deadline validity is delegated to the forwarder's own verify(request) view call rather than reimplemented offline — it is the one contract that actually knows its EIP-712 domain and the signer's current nonce.

Chain portability is the point: adding a new EVM chain is one entry in EVM_GAS_STATION_CONFIG_JSON — a JSON array of {chainId, rpcUrl, forwarderAddress, tokenNetworkAddress, relayerPrivateKey} — not a code change. Optional EVM_GAS_STATION_QUOTE_TTL_MS / EVM_GAS_STATION_MAX_GAS override the default 120s quote TTL / 300,000 gas cap. All three env vars are treated as secret (the config JSON carries relayer private keys) — never logged, deleted from process.env after boot.

Proven against a local chain (src/evm-gas-station-handler.test.ts) — no live-chain calls in tests, ever, same hard-safety rule as the Solana suite. connector#695 (deploy the ERC-2771 TokenNetwork to devnet and repoint the advertised addresses) landed and broadcast 2026-08-06; the "TOON payment-channel contracts" table below carries the resulting TokenNetwork and forwarder addresses, not the pre-ERC-2771 deployment.

kind:5094 confirmed for encrypted increment artifacts (issue #70, toon-meta#262 decision 13)

toon-meta#262 decision 13 routes every factory increment (a plan, a built artifact, a pack of git objects) through this store's existing kind:5094 — uploaded encrypted, keyed by a per-increment key never known to this repo (hashlock delivery lives entirely in the buyer/provider clients; see CLAUDE.md). Confirmed end to end with no code changes — the existing handler already suffices:

  • Size. The single-packet path (src/store-backend.ts → SDK createArweaveDvmHandler) has no store-side byte ceiling of its own — it is bounded only by the configured Arweave/Turbo credit (unauthenticated free tier: ≤100KB per upload; a funded STORE_ARWEAVE_JWK_B64 wallet: effectively uncapped). The chunked path (uploadId/chunkIndex/ totalChunks params, reassembled by the SDK's ChunkManager) caps at 50MB per upload by default (ChunkManagerConfig.maxBytesPerUpload, not currently overridden in src/entrypoint-store.ts); the SDK's uploadBlobChunked() client helper chunks at 500KB/packet, under the ILP packet threshold. A plan (KB-scale) fits the single-packet path trivially; a built artifact or git-object pack fits comfortably inside the 50MB chunked ceiling for realistic increment sizes — raise maxBytesPerUpload if a future increment needs more headroom.
  • Opaque bytes. parseBlobStorageRequest (@toon-protocol/core) base64-decodes the event's i tag straight into a Buffer — it never parses, sniffs, or otherwise assumes the bytes are readable. Content-type sanitization (sanitizeContentType) falls back to application/octet-stream for anything that isn't a well-formed MIME token, which is exactly what an encrypted blob (no meaningful type to declare) hits. The receipt returned to the caller (data = base64(txId)) is passed through store-backend.ts unchanged. Verified end to end (real event build/parse, real HTTP transport, real signature verification, a stubbed-only-at-the-network-edge Turbo adapter) in src/kind-5094-encrypted-artifact.test.ts with crypto.randomBytes payloads — including a multi-chunk reassembly — asserting the uploaded bytes are byte-for-byte identical to the input and the receipt round-trips unmodified.
  • Retrieval. A buyer who did not upload the artifact fetches it by txId — content-addressing means any gateway serving Arweave data serves the same bytes. Retrieval is buyer-side, not this repo's concern, and already goes through an ar.io-first ordered gateway list rather than a single gateway: @toon-protocol/arweave (toon-client repo, packages/arweave/src/gateways.ts) exports ARWEAVE_GATEWAYS = ['https://ar-io.dev', 'https://arweave.net', 'https://permagate.io'] plus arweaveUrls() / arweaveGatewayCandidates() helpers used by both the upload path (stamps primary + fallback mirror URLs) and the render path (fails over to the next gateway on error).
  • Signature verification stays integrity-only. store-backend.ts's verifyEvent check (skipped only in devMode) is unchanged by this confirmation — it proves the event wasn't tampered with in transit, not who may upload; payment already authorized the request upstream.

TOON payment-channel contracts (the connector in FRONT of this store)

The store itself is payment-oblivious; the channel contracts belong to the connector deployment. Since the 2026-07-19 public-chain cutover the devnet settles on public networks, and since the 2026-08-06 ERC-2771 cutover (connector#695) EVM settlement runs through the meta-tx-aware TokenNetwork + trusted forwarder, not the pre-ERC-2771 deployment. Canonical machine-readable source: the apex's kind:10032 announce on the relay; human-readable: toon-client packages/rig/README.md § "Devnet reference (public chains)" + toon-meta docs/deployment.md. Snapshot (devnet/testnet only — TOON has no mainnet deployments):

Chain / network What Id
Solana devnet payment-channel program (fixed public deployment) 2aEVJ8koKD8LTZrLRSGtAtU7LBt4e7QjjCgf1kzQ7Rip
Solana devnet mock USDC SPL mint (6dp) xyc5J8MgKFiEN13PnfftdXxUzYH34FEvw1LCrFwN7in
Mina public devnet canonical USDC token (6dp) token B62qqN1Pu3kF2KGmqLA8EwpqfWrnFTVZJGDSDHQuQRoVt5BCFjhNz3d · tokenId 9497120696276615621907376728658022802954262638363646162765282600447713419198
Mina public devnet PaymentChannel zkApp B62qmgPhv2Xo6QVEtwjLja8UZJUtu8yapRFAR6gaoGtbM9zE5hG7Tkf
Base Sepolia (evm:84532) TokenNetworkRegistry / TokenNetwork / ERC2771Forwarder / USDC registry 0x8263BdD4eB4862395Cb4ef5dA5d637F4b047Eea1 · TokenNetwork 0xa79C3b1dbcEA00a6d84735a134395D8eF6D6a478 · forwarder 0xf1b0B8BA9CA90A0779C382Fe4212a3D4C5646Ee9 · USDC 0x49beE1Bca5d15Fb0963117923403F9498119a9Ce

To let the kind:5096 gas station co-sign a channel deposit/close/settle for that Solana program, set GAS_STATION_CHANNEL_PROGRAM_ID to the current row above — read it fresh from the live kind:10032 announce at deploy time, since this address belongs to the connector's deployment and rotates with it (see e.g. commits #64/#66 rotating other announce-derived addresses in this repo).

About

TOON Protocol Arweave DVM node (NIP-90 kind:5094 blob storage)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages