Skip to main content
Production-proven engine (Chipi’s consumer wallet “Modo privado”, mainnet). Mental model for your users: checking/savings — visible money is for spending, private money is what they keep, invisible to everyone but them.

The five rules

  1. Key handling: privacy keys derive client-side from the user’s anchor (passkey encryptKey / PRF) and are never stored server-side. Cache the companion address, never the keys. Note the proving trust model below — it bounds what “private” may honestly mean in your copy.
  2. One passkey gesture per shield/unshield — that IS the security model. Set the expectation: “You confirm with your fingerprint or Face ID — the same as when you send money.”
  3. Browsers need a gateway proxy (gatewaySubmitUrl): the Starknet sequencer gateway has no CORS. Install the private-balance-card registry item to get the proxy route, or copy it from the example.
  4. Capture the StarkWare pool ToS at the user’s first shield.
  5. Fees: fixed 4 STRK per op, fronted by Chipi’s gas tank (metered per API key) — your user never holds STRK. Show it in USD via getShieldFee.

The trust model, honestly

Keys derive client-side and are never stored server-side — but STRK20’s delegated proving requires the pool spending key inside each proof request, so Chipi’s proving service (a private prover Chipi operates; the same model as StarkWare’s hosted prover) sees it per operation and must be trusted not to retain or use it. Privacy holds against the public — chain observers, other users — not against the proving operator. Do not tell your users “nobody, including Chipi, can see your balance.” Local proving is the roadmap item that removes this trust.

What is the “companion”?

Each user gets a lightweight standard account (the companion) as their pool identity, derived deterministically from their anchor and deployed automatically on first shield. You only supply a standard SNIP-6 account class hash (classHash, e.g. OpenZeppelin 0x061dac03…); everything else is handled. Why a companion instead of changing the account class: see our design write-up on the Starknet forum, and the Core SDK privacy page for the architecture.

Quickstart

import { useShield, usePrivateBalance, derivePrivacyAccount } from "@chipi-stack/nextjs";

const keys = await derivePrivacyAccount({
  anchorSecret,                  // 32 bytes from your passkey rail
  seedDomainTag: wallet.address, // per-wallet domain separation
  classHash: COMPANION_CLASS_HASH,
});

const { shieldAsync } = useShield();
await shieldAsync({
  config: {
    apiPublicKey: process.env.NEXT_PUBLIC_CHIPI_API_KEY!,
    getBearerToken: () => getToken(),          // a FUNCTION: tokens die mid-flow
    gatewaySubmitUrl: "/api/starknet-gateway", // your CORS proxy
  },
  keys,
  token: USDC_ADDRESS,
  amount: 1_000_000n, // base units
  fundDeposit: (companion, amount) => transferGasless(companion, amount),
  onStep: setPhase,   // prepare | fund | deposit | prove | finish — drive a REAL loader
});

const { byToken } = usePrivateBalance({ mode: "ledger", events: recordedEvents });
Record every settled op in your own store — your ledger is the display source of truth (the chain won’t cleanly surface confidential moves). Failures never strand money: a failed shield parks funds in the companion and the next attempt sweeps them in; say “your money is safe” in error copy. Full headless example (also the release smoke): examples/private-balance in the SDK repo. Agent skill: chipi-private-balances.