Skip to main content
If your wallet is a SHHH V8.4 wallet — the default since v14.5.0 — use X402ShhhClient. The existing X402Client only works for CHIPI v29 and Argent X wallets; it signs payments with a raw {r, s} pair that V8.4 rejects on chain. X402ShhhClient is in @chipi-stack/backend (and re-exported from @chipi-stack/x402 for convenience). Same fetch(url, init) surface as the regular client.

Which buyer client do I need?

Not sure which? Read wallet.walletType off any wallet returned from getWallet / createWallet / useChipiWallet.

Install

@chipi-stack/x402 re-exports X402ShhhClient so a single import covers facilitator + client surfaces.

Construct the client

Required:
  • wallet — the wallet’s publicKey (deployed account address) and encryptedPrivateKey (ciphertext). Same WalletData-derived shape every other SDK call uses.
  • encryptKey — passkey-derived key or PIN that decrypts the wallet’s STARK key client-side at sign time.
  • chipiClient — Chipi HTTP client (the same one your server uses elsewhere).
  • bearerToken — your chipi-back auth token. Server-side only; never crosses the wire to the x402 seller.
Optional config:
PIN is weak — not recommended for production.A user-typed PIN is a short, low-entropy string. Anyone who shoulder-surfs the PIN, observes a phishing form, or compromises the browser at typing time can decrypt the wallet’s private key. PIN remains in the SDK only as a fallback recovery surface for users who lose access to their platform authenticator.Production embedded-wallet apps should default to a platform passkey (Touch ID, Face ID, Windows Hello, Android biometrics) via the @chipi-stack/chipi-passkey package. For SHHH V8.4 wallets, signerKind: "WEBAUTHN_P256" keeps the private key inside the platform authenticator — it never leaves the device, never reaches Chipi servers, and is never derived from a user-typed secret.Only prompt for a PIN as the encryption key when:
  • The user explicitly opted into a PIN-only flow (e.g. cold-storage / paper-backup recovery), or
  • The platform genuinely has no WebAuthn / biometric support available.
If you are migrating an existing PIN-based wallet to a passkey, look up useMigrateWalletToPasskey in your framework’s hook docs.

Pay for a 402-protected request

fetch is a drop-in replacement for the global fetch. If the seller returns 200, it passes through. If they return 402 Payment Required, the client signs the payment locally and retries:
The client adds the signed X-PAYMENT header to the retry. No paymaster call from your side — the facilitator (the seller’s side) settles by forwarding the signed calldata to chipi-back’s /transactions/execute-sponsored-raw.

What gets signed

  1. Build a USDC.transfer(payTo, amount) call.
  2. Build a SHHH V8.4 execute_from_outside_v2 outside-execution wrapping that call, with caller = ANY_CALLER (paymaster sponsorship).
  3. Compute the V8.4 SNIP-12 hash against the wallet’s address + chain id.
  4. Decrypt the STARK key client-side, sign the hash, wrap into a V2_SNIP12 envelope.
  5. Serialize the full execute_from_outside_v2 calldata into payload.shhh.oeCalldata.
  6. Ship that as the X-PAYMENT header.
The cleartext STARK key never crosses the wire. Chipi-back receives only the pre-signed calldata span; the wallet’s owner is the only party that could have produced it.

Signer kind support

For browser-rooted kinds (WEBAUTHN_P256, EIP191_SECP256K1), the right pattern is to call your wallet adapter’s signMessage directly and build the envelope via buildWebAuthnEnvelopeFromAssertion / buildEip191EnvelopeFromSignature from @chipi-stack/backend, then submit through the paymaster yourself. The drop-in fetch(url) flow is STARK-only today.

Safety: caps + allowlists

Both the original X402Client and X402ShhhClient enforce the same two safety nets at sign time:
  • maxPaymentAmount — hard cap per request in human USDC (“1.00” = 1 USDC). Throws before signing if a 402 asks for more.
  • allowedRecipients — whitelist of seller addresses. Throws before signing if a 402’s payTo isn’t on the list.
Both errors surface before any signature is produced, so the user is never asked to sign a payment that would have been rejected anyway.

Facilitator side (sellers)

You don’t need a different facilitator for SHHH buyers — X402Facilitator from PR #298 onward auto-detects payload.shhh on the inbound payment and routes through the SHHH dispatch path. See x402 server — monetizing APIs for the seller-side setup; that page works as-is for SHHH buyers.