A minimal, end-to-end on-ramp for kawasekit: an AI agent pays a metered HTTP API in testnet JPYC — and it pays through a cryptographic 2-of-2 co-signer, not a raw private key. The agent holds only half the signing key; no valid payment exists unless the owner backend re-checks its policy and contributes its share.
⚠️ Testnet-only, UNAUDITED. This demo runs on Polygon Amoy with no real value. The DKLs23 threshold-signature crypto is unaudited — a third-party cryptographic audit is the standing prerequisite before any mainnet / real-value use. Do not put real funds behind this.
┌─────────┐ GET /weather/Tokyo ┌──────────────────┐
│ agent │ ──────────────────────────► │ metered API │
│ (payer) │ ◄───── 402 + JPYC reqs ───── │ (x402 server) │
└────┬────┘ └────────┬─────────┘
│ co-sign an EIP-3009 authorization │ verify + settle
│ for the GROUP EOA (2-of-2) │ via self-facilitator
▼ ▼
┌──────────────────────────────┐ Polygon Amoy (JPYC)
│ cryptographic co-signer │
│ • public adapter (kawasekit) │ ── wss + mTLS ──► owner backend
│ • private glue (@kawasekit/ │ (cosign_server):
│ mpc-2p): WASM share + │ • owner half-share
│ transport + A3 HMAC │ • the spending policy
└──────────────────────────────┘ • re-derives digest, re-checks
policy, then co-signs
The agent retries the 402 with an X-PAYMENT header carrying the co-signed EIP-3009
transferWithAuthorization; the server's facilitator settles it on-chain and returns the data
plus the settlement tx.
| Piece | Package | Open / private |
|---|---|---|
The x402 client/server + the PolicyGatedSigner adapter (createMpc2pPolicyGatedSigner) |
kawasekit |
public (Apache-2.0, npm) |
| The injected implementations — the WASM DKLs agent share, the wss/mTLS transport, the A3 HMAC authenticator | @kawasekit/mpc-2p |
private (the client you receive with your owner-hosted co-signer) |
| The owner backend that holds the other share + the policy | cosign_server (kawasekit-mpc-2p) |
private, self-hosted by the owner |
The public SDK ships only the protocol; the crypto, the socket, and the key are injected. This
example wires the two together — see lib/cosigner.ts, the whole seam in one
file.
To make the demo runnable by one person, you run BOTH MPC parties — the agent (one share) and the owner backend (the other share + policy) — on your own testnet infrastructure. That is not the production non-custodial topology. In production:
- the owner self-hosts the
cosign_server(their share + their policy), and - the agent is the client's, holding only its own share;
so neither party is a custodian, and the owner's policy is cryptographically non-bypassable for a counterparty it does not control. Co-locating both parties (as here) collapses that separation — fine for a demo, never how you'd run real value. This is testnet-only and unaudited; treat the result as an integration proof, not a security guarantee.
- A deployed owner backend — the
cosign_serverfromkawasekit-mpc-2p(see itsdocs/DEPLOY-RUNBOOK.md), reachable over wss + mTLS, holding the owner share + the spending policy on a persistent volume. - A DKG-provisioned 2-of-2 key — run the distributed key generation so the agent holds
one share (
AGENT_SHARE_HEX) and the backend holds the other; both agree on the group EOA. Back up both shares before funding (B5). The shared A3 key and the mTLS PKI come from the same provisioning step. - A funded group EOA — fund the group EOA with testnet JPYC on Polygon Amoy, and a separate facilitator EOA with Amoy MATIC for gas.
- Node ≥ 22 and
pnpm(ornpm).
This repo depends on the two published packages — the open-core seam in your own package.json:
kawasekit(public, npm) — the x402 client/server + thecreateMpc2pPolicyGatedSigneradapter. Installs for anyone.@kawasekit/mpc-2p(private, GitHub Packages) — the injected glue (WASM share + transport + A3). It's anoptionalDependency: the metered-API server installs and runs for anyone; only the paying agent needs this private client, which ships with your owner-hosted co-signer.
The committed .npmrc routes the @kawasekit scope to GitHub Packages. A token-less
install is clean — the optional private client is simply excluded and the server still runs. To
install the private client, authenticate once with a GitHub token that has read:packages (+
access to the kawasekit org packages):
npm config set //npm.pkg.github.com/:_authToken "$GITHUB_TOKEN" # read:packages — for the agent
cp .env.example .env # fill in the facilitator key, recipient, and the co-signer config
pnpm install # no token → server-only (the optional client is excluded)
pnpm typecheck # optional — verifies the wiring against the SDK types
# terminal 1 — the metered API (no token needed)
pnpm dev:server
# terminal 2 — the paying agent (needs @kawasekit/mpc-2p installed)
pnpm dev:agentLocal dev against sibling checkouts. Working on
kawasekit/kawasekit-mpc-2pthemselves? Override the published deps without touchingpackage.json:pnpm link ../kawasekit && pnpm link ../kawasekit-mpc-2p/agent-ts(build the siblings first:pnpm -C ../kawasekit buildandpnpm -C ../kawasekit-mpc-2p/agent-ts build).pnpm unlinkto revert.
Expected: the agent prints the weather for the city plus a Polygon Amoy settlement tx hash. The
co-signer enforces the owner's policy on every payment — an over-policy request comes back as a
typed rejection (no signature), and an unreachable co-signer throws (CoSignUnavailableError)
rather than silently falling back to an advisory signature.
| Path | What |
|---|---|
server/index.ts |
The metered API — a Hono x402 paywall + self-facilitator on Amoy. |
agent/index.ts |
The paying agent — wrapFetch + createX402PaymentSigner({ signer }) over the cryptographic co-signer, with a budget guard. |
lib/cosigner.ts |
The open-core seam — the public adapter driven by the private glue, over mTLS. |
.env.example |
Every variable, with the secret/maintainer-hand ones flagged. |
Apache-2.0. The crypto remains unaudited and testnet-only; see the caveat above.