Skip to content

Components

Architecture: the AI client talks OAuth and MCP to the MCP worker; the app with your passkey talks to the API worker; the MCP worker makes internal calls to the API and probes and retries x402 sellers; the API forwards stamps unchanged to your Turnkey sub-org and uses Neon Postgres and the chain providers.Architecture: the AI client talks OAuth and MCP to the MCP worker; the app with your passkey talks to the API worker; the MCP worker makes internal calls to the API and probes and retries x402 sellers; the API forwards stamps unchanged to your Turnkey sub-org and uses Neon Postgres and the chain providers.
ZyroPay's parts and who talks to whom. The two ZyroPay workers are outlined.
  1. The MCP server never signs. It holds no Turnkey credential and no database URL. Its only credential is the internal service token, and it only calls the API’s /internal/v1/*. On every call the API checks that the agent is still active and not revoked.
  2. The API never holds your root credential. Your passkey is the only root of your sub-org. The parent organization can read sub-orgs but cannot write to them.
  3. An approval binds to payloadHash, the SHA-256 of the payment’s canonical terms. Any material change makes a new hash, and the old approval no longer applies.
  4. Amounts are integer strings in base units, arithmetic is bigint. Lint rules ban floating-point parsing of money.
  5. Fail closed. No valuation, a stale price, a malformed amount, a policy error or an unexpected Turnkey result never auto-allows a payment.
  6. Mainnet and testnet never mix. Production serves mainnets only; development and staging serve testnets only. Mock adapters are refused in production.
  7. Mocks are labelled. Every fake implementation is named Mock* and returns simulated: true, and the UI shows a badge for it.
Part What it gives you
Passkeys Sign in and approve with Face ID, Touch ID or your device PIN. No password, no seed phrase on screen.
Turnkey secure enclaves Keys are generated and used inside hardware-isolated enclaves. ZyroPay never holds them and cannot move your money.
Server-side policy engine Dollar limits per payment, day and month, recipient allowlists, escrow above a threshold and expiry, checked before your phone ever buzzes.
Model Context Protocol Any assistant that speaks MCP can connect with OAuth, read balances and ask for a payment. You can revoke it at any time.
x402 on Cardano Paid HTTP resources quote a price in a 402. ZyroPay builds the payment, direct or into Masumi escrow, and checks the seller’s signed delivery receipt.
Component Where Runs on Responsibility
App apps/mobile, packages/ui, apps/web-host Expo (iOS, Android), static web on Workers Sign-up with a passkey, portfolio, send and receive, approvals, agents and their policies, escrow and refunds
API apps/api, packages/wallet Cloudflare Worker (Hono) System of record: sessions, policy, state machine, reservations, Turnkey forwarding, broadcast, escrow, audit
MCP server apps/mcp Cloudflare Worker OAuth for agent clients, the ten tools, the x402 probe and paid retry; never signs
Chain layer packages/blockchain library Builders and independent decoders for Solana, EVM and Cardano (own CBOR codec), broadcasters, portfolio, prices
Integrations packages/integrations library Plugins (x402, Masumi escrow and registry, Jupiter), on-ramp, push
Contracts packages/shared library Schemas, money, networks, state machine, canonical payload and hash, policy engine, REST and MCP contracts
Database infrastructure/database Drizzle on Neon Postgres Schema and forward-only migrations
Demo sellers apps/seller, apps/specialist Cloudflare Workers (separate parties) x402 sellers on Cardano preprod: Address Intelligence (direct and escrow) and Token Intelligence
Masumi apps/sokosumi-agent, infrastructure/masumi Worker and a local Payment Service Registering the seller as a Masumi agent and answering Sokosumi tasks

Each card below is a real part of the system: what it does, why it exists, what it guarantees, and where in the engineering documents the claim comes from.

AI side

AI agent

An AI assistant such as Claude, ChatGPT or Grok, connected as an MCP client. It can read balances and ask for payments, but it can never sign anything.

Why: ZyroPay is built so agents can pay on a user's behalf, while every payment still goes through the policy engine.

Guarantee: The agent's token only ever maps to its own agent id, and its tools can only create requests, never signatures.

Talks to: OAuth 2.1 + PKCE → Agent sign-in (OAuth) · tool calls → MCP server

Built with: MCP client · Streamable HTTP · OAuth 2.1 + PKCE

Sources: ARCHITECTURE.md §1 · ARCHITECTURE.md §2.4 · MCP.md Server guide §1 · SECURITY.md T1 · SECURITY.md T5

Your device

ZyroPay app

The iOS and Android app (with a web build) where people sign up, see their portfolio, send and receive, approve agent payments and manage agents.

Why: It is the only place where the user's passkey is used, so every human approval happens here.

Guarantee: Approvals are signed with the passkey on the device, and the review shown must match the exact transaction the server checks.

Talks to: asks you to confirm → Your passkey · sign up with passkey → Turnkey Auth Proxy · session, reviews, signed approvals → ZyroPay API · scrubbed errors → Error monitoring

Built with: Expo SDK 57 · Expo Router · React Native · Turnkey RN Wallet Kit · Expo push

Sources: ARCHITECTURE.md §1 Components · ARCHITECTURE.md §2.1 · ARCHITECTURE.md §2.3 · MONITORING.md §1 · SECURITY.md T9

Your passkey

A passkey on the user's device that proves it is really them. It is the only root of their Turnkey sub-organization.

Why: Being non-custodial means only the user can move their funds on their own, so the passkey is the one root credential and ZyroPay holds none.

Guarantee: Every approval needs a fresh passkey stamp; a session key stamp is accepted only for read-only queries.

Built with: WebAuthn · platform authenticator · StamperType.Passkey · X-Stamp-Webauthn

Sources: ARCHITECTURE.md §3 · ADR-001 D1 · ADR-001 D2 · SECURITY.md C7 · SECURITY.md T15

ZyroPay edge

MCP server

A Cloudflare Worker that exposes 10 tools to AI clients (8 wallet and payment tools plus 2 read-only Masumi registry tools) and turns each tool call into one internal API call.

Why: It keeps agents away from keys and data: it holds no Turnkey credentials, no database URL and no user session.

Guarantee: It never signs. Results are sanitized and wrapped as untrusted data, and a compromised MCP worker can only create requests that still need approval or stay within limits.

Talks to: internal calls only → ZyroPay API · scrubbed errors → Error monitoring

Built with: Cloudflare Workers · @modelcontextprotocol/sdk 1.32.1 · Streamable HTTP (stateless) · INTERNAL_SERVICE_TOKEN

Sources: ARCHITECTURE.md §1 rule 1 · MCP.md Server guide §1 · MCP.md Server guide §4 · MCP.md Server guide §5 · SECURITY.md C9 · SECURITY.md T4

Agent sign-in (OAuth)

The OAuth 2.1 authorization server inside the MCP worker. It shows a short code that the user approves in the app.

Why: Agents need their own revocable access that is tied to one agent and approved by the user's passkey session.

Guarantee: PKCE is required for every client, redirect URIs must match exactly, tokens are opaque and stored only as hashes, access tokens last 15 minutes by default, and revocation cuts off every token for an agent.

Talks to: pending connection + user code → ZyroPay API

Built with: OAuth 2.1 · PKCE S256 · CIMD (Client ID Metadata Documents) · DCR RFC 7591 (fallback) · RFC 9728 · RFC 8414 · RFC 8707 · RFC 9207 · RFC 7009 · @cloudflare/workers-oauth-provider 1.2.3 · Workers KV

Sources: MCP.md Server guide §2 · MCP.md Server guide §3 · MCP.md Server guide §5 · SECURITY.md C10 · SECURITY.md T5

Provider webhooks

Incoming notifications from Helius, Alchemy and Transak about chain activity and on-ramp orders.

Why: They speed up updates, but are never the only source of truth.

Guarantee: Signatures are checked on the raw body, duplicates are ignored, and events are reconciled with ZyroPay's own RPC reads before any state change.

Talks to: signed events → ZyroPay API

Built with: HMAC-SHA256 · webhook_events · UNIQUE(provider, event_id)

Sources: ARCHITECTURE.md §2.8 · API.md Implementation notes: Webhooks · SECURITY.md C11 · SECURITY.md C12 · SECURITY.md T11

ZyroPay core

ZyroPay API

The system of record, running on Cloudflare Workers. It handles sessions, policy checks, the request state machine, spending reservations, Turnkey forwarding, broadcast, webhooks and audit.

Why: One place enforces every rule, and it forwards the user's signed requests to Turnkey without being able to change them.

Guarantee: The API never holds a user's root credential, validates every input with Zod, and fails closed when something is missing or unexpected.

Talks to: reviews, status → ZyroPay app · forwards stamped requests unchanged → Your Turnkey sub-org · reads root quorum → Turnkey parent org · autonomous signing only → Delegated signer · revoke agent grants → MCP server · hashes payment terms → Payload hash binding · evaluates agent request → Policy engine · every state change → Request state machine · records of truth → Neon Postgres · USD valuation → Price sources · build unsigned tx → Transaction builders · check before and after signing → Independent decoder · swap and x402 quotes → Payment plugins · background jobs → Job queue · notify user → Push notifications · scrubbed errors → Error monitoring

Built with: Cloudflare Workers · Hono · Zod · opaque access tokens (zp_at_) · rotating refresh tokens (zp_rt_)

Sources: ARCHITECTURE.md §1 Components · ARCHITECTURE.md §1 rules 2 and 5 · ARCHITECTURE.md §3 · API.md Implementation notes · SECURITY.md C16

Policy engine

Checks every agent payment against the agent's rules and returns allow, require approval or deny.

Why: Turnkey cannot enforce USD limits, daily or monthly totals or per-agent rules, so those live here.

Guarantee: New agents start in Always Ask with spending disabled and zero limits. A missing price, or an error during evaluation, never auto-allows a payment. Swaps and Cardano are never autonomous.

Talks to: spent + reserved under row lock → Neon Postgres

Built with: evaluatePolicy · defaultAgentPolicy · AUTONOMY_ELIGIBLE_TOOLS · AUTONOMY_ELIGIBLE_FAMILIES · x402EscrowAboveUsdMinor · requireRegisteredAgent

Sources: ARCHITECTURE.md §2.5 · ADR-001 D7 · ADR-002 C5 · SECURITY.md C1 · SECURITY.md C2 · SECURITY.md C6 · SECURITY.md C20 · SECURITY.md T1

Request state machine

Tracks every payment request from creation to its final outcome through a fixed set of states.

Why: An explicit transition table makes it impossible to skip a step, such as signing without a policy check.

Guarantee: Illegal transitions throw, and an approval moves to SIGNING before Turnkey is called, so a duplicate approve can never sign twice.

Built with: transition() · optimistic locking (version) · ILLEGAL_STATE_TRANSITION · VERSION_CONFLICT

Sources: ARCHITECTURE.md §2.5 · API.md Implementation notes: Transaction requests · SECURITY.md C15

Payload hash binding

A SHA-256 fingerprint of the payment's terms: network, from, to, asset, amount, fee cap, plugin, tool, nonce, expiry and idempotency key.

Why: It ties an approval to exactly what the user saw, so nothing material can change after they approve.

Guarantee: Any material change produces a new hash and the old approval no longer applies. The stamped body must also be byte-equal to the reviewed transaction.

Built with: CanonicalTransactionPayload · canonical JSON · SHA-256 · payloadHash · PAYLOAD_HASH_MISMATCH

Sources: ARCHITECTURE.md §1 rule 3 · ADR-001 D2 · ADR-001 D4 · SECURITY.md C7 · SECURITY.md T2

Job queue

A Cloudflare Queue that runs background steps: autonomous signing, broadcast, confirmation polling, push notifications, webhook processing and revocation retries.

Why: Slow or retryable work runs outside the request, with retries and a dead-letter queue.

Talks to: broadcast signed bytes → Broadcasters · poll confirmations → Confirmation tracker

Built with: Cloudflare Queues · zyropay-api-jobs · zyropay-api-jobs-dlq

Sources: API.md Implementation notes: Async stages

Delivery receipt verifier

Checks the seller's Ed25519 signature over the exact bytes the agent received, the paid URL, payTo and the payment transaction.

Why: A paid response alone proves nothing about who sent it or whether it was altered.

Guarantee: Only the holder of the paid address's key can produce a receipt that verifies; the MCP worker hashes the body itself, never trusting the seller's hash alone.

Built with: X-Delivery-Receipt v1 · verifyX402Receipt · blake2b-224 · Ed25519

Sources: CARDANO_X402.md Delivery receipt

Key custody (Turnkey)

Turnkey Auth Proxy

A Turnkey service that creates the passkey, the user's sub-organization and a default wallet at sign-up, with no ZyroPay backend involved.

Why: It lets sign-up happen directly between the app and Turnkey. Turnkey documents that it has no access to funds.

Guarantee: Its root layout is not documented, so the API checks the sub-org's root quorum on first login and refuses anything other than the user alone.

Talks to: creates sub-org + wallet → Your Turnkey sub-org

Built with: Turnkey Auth Proxy · signUpWithPasskey · authProxyConfigId

Sources: ARCHITECTURE.md §2.1 · ADR-001 Facts 3 · ADR-001 D1 · SECURITY.md T14

Turnkey parent org

ZyroPay's organization in Turnkey. It sits above every user's sub-organization.

Why: The API needs to read a user's sub-org (for example to check its root quorum and mirror wallets), but should never be able to change it.

Guarantee: The parent organization can read sub-orgs but cannot write to them.

Talks to: read-only access → Your Turnkey sub-org

Built with: Turnkey · parent-org read key · get_organization_configs

Sources: ARCHITECTURE.md §1 rule 2 · ADR-001 Facts 1 · SECURITY.md C8

Your Turnkey sub-org

A separate Turnkey organization for each user that holds their wallets and keys inside Turnkey. The user's passkey is its only root user.

Why: Root actions in Turnkey bypass policies, so ZyroPay keeps itself out of the root quorum entirely.

Guarantee: There is no ZyroPay root user, so no ZyroPay system can move funds on its own. ZyroPay also has no recovery override.

Talks to: holds the envelope → Turnkey policy envelope

Built with: Turnkey (TEE) · sub-organization · root quorum threshold 1 · SIGN_TRANSACTION_V2 · SIGN_RAW_PAYLOAD_V2

Sources: ARCHITECTURE.md §1 · ADR-001 Context · ADR-001 Facts 2 · ADR-001 D1 · SECURITY.md Assets

Delegated signer

An optional non-root key user inside the sub-org, used only for Autonomous mode. The user's own passkey creates it.

Why: Autonomous payments need a signer without a human in the loop. It does not exist until the user turns on Autonomous for an agent.

Guarantee: Its private key is stored encrypted, it is never in the root quorum, and revoking it destroys the key at once. In Always Ask, ZyroPay can sign nothing.

Built with: Turnkey non-root API-key user · P-256 · AES-256-GCM · DELEGATED_KEY_ENCRYPTION_KEY · fetchOrCreateP256ApiKeyUser

Sources: ADR-001 D5 · ADR-001 D6 · ADR-001 D8 · ARCHITECTURE.md §2.6 · SECURITY.md C8 · SECURITY.md T3

Turnkey policy envelope

Rules inside Turnkey that limit what the delegated signer may sign: allowed chains, allowlisted recipients, per-transaction caps and allowed programs.

Why: A second, coarser line of defence that holds even if the ZyroPay API itself is compromised.

Guarantee: A fully compromised API still cannot exceed the per-transaction envelope or pay a non-allowlisted recipient. A live Turnkey org accepted the envelope on staging on 2026-10-20 (Solana devnet, Sepolia, Base Sepolia).

Talks to: bounds what it signs → Delegated signer

Built with: Turnkey policies · ACTIVITY_TYPE_CREATE_POLICIES · buildDelegatedPolicyEnvelope · P1 EVM ALLOW · P2 Solana ALLOW · P3 DENY lookup tables

Sources: ADR-001 D6 · ADR-001 Consequence · ADR-001 Open items · SECURITY.md C3

Data and prices

Neon Postgres

The database for users, wallets, agents, policies, payment requests, approvals, spending reservations, the spending ledger, webhook events and audit logs.

Why: Spending limits need an exact, shared record of what has been spent and what is reserved, even under concurrent requests.

Guarantee: Agent requests run under a row lock and limits count spent plus reserved, so many requests at once can never jointly exceed a limit. Tokens are stored only as hashes.

Built with: Neon Postgres · Drizzle · SELECT ... FOR UPDATE · spending_reservation · spending_ledger · UNIQUE(user_id, idempotency_key)

Sources: ARCHITECTURE.md §1 Components · ARCHITECTURE.md §2.5 Concurrency · SECURITY.md C4 · SECURITY.md C11 · SECURITY.md C13 · SECURITY.md T6 · SECURITY.md T7

Price sources

Provide USD prices used to value payments and portfolios.

Why: USD limits need a valuation, and Turnkey has no price oracle.

Guarantee: Values are rounded up, and a stale, unknown or failed price counts as no valuation, which never auto-allows a payment.

Built with: Jupiter Price API v3 (Solana) · CoinGecko (Ethereum, Base, Cardano) · PriceProvider · maxAgeSeconds

Sources: INTEGRATIONS.md §2 · ARCHITECTURE.md §2.2 · SECURITY.md C5 · SECURITY.md T8

Chain layer

Transaction builders

Build the unsigned transaction for Solana, EVM chains and Cardano from the payment terms, and simulate it before review.

Why: ZyroPay builds the exact bytes the user reviews, using Turnkey sign-only so nothing is filled in after review.

Guarantee: Fees are capped by the fee cap inside the payload hash, and a Solana transaction that is about to expire is rebuilt without changing the payload hash.

Talks to: self-check → Independent decoder

Built with: TransactionBuilder · @solana/kit · viem · own Cardano CBOR codec · Blockfrost

Sources: ARCHITECTURE.md §1 Components · ADR-001 D3 · ADR-001 D4 · INTEGRATIONS.md §3 · INTEGRATIONS.md §7

Independent decoder

Decodes the transaction bytes on its own, without network access, and checks they match the approved payment.

Why: It catches any mismatch between what was approved and what is about to be signed or sent.

Guarantee: It fails closed. A signed transaction that does not match the reviewed bytes fails the request before broadcast.

Built with: verifyMatchesPayload · assertSignedMatchesUnsigned · verifyJupiterSwap · verifyX402Authorization

Sources: INTEGRATIONS.md §3 · INTEGRATIONS.md Exported factories · API.md Implementation notes: Transaction requests · SECURITY.md C7

Broadcasters

Send the signed transaction to the network through ZyroPay's own RPC providers.

Why: One broadcast and confirmation path for every chain family, instead of Turnkey sign-and-send.

Guarantee: The transaction hash is stored before sending, and a rebroadcast re-sends the same bytes.

Talks to: send transaction → Chain RPC providers

Built with: Broadcaster · createSolanaBroadcaster · createEvmBroadcaster · createCardanoBroadcaster

Sources: ADR-001 D3 · INTEGRATIONS.md §4 · API.md Implementation notes: Transaction requests

Confirmation tracker

Checks on chain whether a transaction is pending, confirmed, failed, expired or replaced.

Why: The final outcome of a payment is decided by the chain, read through ZyroPay's own RPC.

Guarantee: On confirmation the reserved spend is settled into the ledger once; on failure it is released.

Talks to: read status → Chain RPC providers

Built with: createConfirmationTracker · SOLANA_COMMITMENT · EVM_CONFIRMATIONS · CARDANO_CONFIRMATIONS

Sources: INTEGRATIONS.md §4 · API.md Implementation notes: Transaction requests · ARCHITECTURE.md §2.3

Chain RPC providers

The node providers ZyroPay uses to read balances and history and to broadcast: Helius for Solana, Alchemy for Ethereum and Base, Blockfrost for Cardano.

Why: Each provider is bound to one network, so mainnet and testnet are never mixed.

Guarantee: Providers refuse any other network, the API checks genesis hash or chain id, and it never falls back to a public RPC.

Built with: Helius · Alchemy · Blockfrost · rpcUrlFor

Sources: INTEGRATIONS.md Wiring · INTEGRATIONS.md §1 · INTEGRATIONS.md §7 · SECURITY.md C14 · SECURITY.md T13

Payment plugins

Adapters for swaps and paid web requests: Jupiter swaps on Solana mainnet, and x402 payments (Cardano preprod end to end; EVM and Solana signed but not handed off).

Why: They let agents do more than plain transfers, with the same quote, binding and decode checks.

Guarantee: Swaps are never autonomous. x402 on EVM and every Cardano payment always need the passkey.

Built with: PluginAdapter · Jupiter Swap API v1 · x402 v1 and v2 · @x402/cardano 2.29.0 · createPluginRegistry

Sources: INTEGRATIONS.md §5 · ADR-001 D7 · API.md Implementation notes: Transaction requests (x402)

Third parties on Cardano

x402 seller (example)

Cardano Address Intelligence: a paid data API on Cloudflare Workers that answers 402 with x402 v2 requirements (exact scheme, cardano:preprod) and runs the official @x402/cardano facilitator in-process. A second one, Token Intelligence, sells token reports.

Why: ZyroPay needs a real third-party seller to pay. It is a separate party: it never sees a ZyroPay credential.

Guarantee: Settlement happens only when the handler answers below 400, so a Blockfrost failure returns 503 and the buyer is not charged.

Built with: @x402/hono 2.29.0 · @x402/cardano 2.29.0 · X-Delivery-Receipt · Evolution SDK

Sources: apps/seller/README.md · apps/specialist/README.md

Masumi escrow

The canonical Masumi payment-v2 vested_pay script (Plutus V3) on Cardano. It holds a payment until the seller submits a result hash and the unlock time passes, or refunds it.

Why: Paying a stranger up front needs a way to get the money back if nothing, or the wrong thing, is delivered.

Guarantee: ZyroPay locks only into the canonical deployment and binds the exact datum bytes into the payload hash; the user's refund spends are passkey-signed.

Built with: vested_pay (Plutus V3) · inline datum · SubmitResult · SetRefundRequested · WithdrawRefund · 2-of-3 admins

Sources: CARDANO_X402.md Masumi escrow

Masumi registry

A permissionless registry of agents: each is a CIP-25 NFT under the V2 registry policy whose metadata names its endpoint and price.

Why: An agent can find sellers, and ZyroPay can check that a seller claiming an identity really holds it.

Guarantee: Registration proves identity binding, not trustworthiness. ZyroPay verifies a claimed identity on chain at quote time and refuses on any failure.

Built with: CIP-25 · agentIdentifier (120 hex) · Blockfrost

Sources: CARDANO_X402.md Discover and identify

Operations

Scheduled sweeps

A Cron Trigger that runs every minute to expire old requests and re-check stuck ones.

Why: A request waiting for approval should not stay open forever, and stuck broadcasts need a retry path.

Guarantee: Requests past their expiry that have not started signing move to EXPIRED and their reserved spend is released.

Talks to: expire and re-poll → ZyroPay API

Built with: Cloudflare Cron Triggers · * * * * *

Sources: ARCHITECTURE.md §2.5 Expiry · API.md Implementation notes: Transaction requests

Push notifications

Expo push messages that tell the user an approval is waiting or a payment landed.

Why: Always Ask needs to reach the user quickly, without leaking payment details.

Guarantee: Pushes carry no amounts or addresses and deep link only to a fixed set of screens in the user's own app, which then reloads everything from the API.

Talks to: deep link → ZyroPay app

Built with: Expo push · Notifier · PushData · buildPushData

Sources: ARCHITECTURE.md §2.7 · API.md Implementation notes: Async stages · SECURITY.md T10

Error monitoring

Optional Sentry error reporting for the API, the MCP worker and the app. Nothing is sent until a DSN is configured.

Why: Debugging needs error types and stack traces, but never secrets or personal data.

Guarantee: Every event is scrubbed and fails closed: bodies, headers, user data, tokens, keys, recovery phrases, emails and IP addresses are removed, and no traces are sent.

Built with: Sentry · @sentry/cloudflare · @sentry/react-native · @sentry/react · scrubTelemetryEvent

Sources: MONITORING.md §1 · SECURITY.md C13 · SECURITY.md T12