Wallet Modes
WizPay supports two signing models. The mode determines who holds the signing key and where transaction construction happens.Mode Selection
The wallet mode is read fromtask.payload.walletMode by ExecutionRouterService:
walletMode is absent (all tasks created before the field was introduced), the system defaults to W3S. Zero breaking changes.
W3S (Custodial)
Circle Wallet-as-a-Service. The backend has signing authority. Signing model:- Backend holds a Circle API key and entity secret.
- The entity secret allows the backend to sign transactions on behalf of developer-controlled wallets.
- The user does not approve individual transactions.
- User authenticates (Google/Email) → receives Circle
userToken+encryptionKey. - Frontend sends
userTokento backend with each wallet operation. - Agent calls
CircleService.transfer()→ Circle signs and submits on-chain.
- Backend signs. No client-side signing.
userTokenrequired for wallet identification.walletIdrequired per operation.- Supports EVM (ARC-TESTNET, ETH-SEPOLIA) and Solana (SOLANA-DEVNET).
useMobileRecoverylistens tovisibilitychange,focus,pageshow, andonlineevents. On each trigger (throttled to prevent flooding) it callsrearmSdkForSessionto re-attach the current auth token to the SDK instance.ensureSessionReady()is called before every Circle-mode operation (transaction execution, bridge, typed data signing). If the session is stale, it re-arms the SDK and refreshes the wallet list before the operation proceeds.- If a Circle operation returns a recoverable session error (code
155706or an invalid-device code),withRecoveredSessioncallsensureSessionReadyand retries the operation exactly once. The retry is transparent to the caller.
| Endpoint | Purpose |
|---|---|
POST /wallets/initialize | Create wallet set + wallets for a user |
POST /wallets/sync | Sync existing wallets from Circle |
POST /wallets/ensure | Get or create wallet for a chain (EVM/SOLANA) |
PASSKEY (Client-Controlled)
Circle modular Account Abstraction wallet. The user holds the signing key. Signing model:- User authenticates with a WebAuthn passkey (biometric or hardware key).
- The backend has no signing authority over the user’s wallet.
- For operations requiring the user’s signature, the backend returns unsigned intents.
| Operation | Backend Action | Client Action |
|---|---|---|
| Bridge | Records CCTP intent, returns parameters | User signs and submits burn tx via AA wallet |
| Payroll (EVM) | Submits ERC-20 transfers from backend treasury (BACKEND_PRIVATE_KEY) | None — treasury pre-funded by company |
| Payroll (Solana) | Builds unsigned SPL transfer intents | User signs and broadcasts each intent |
| Swap | Prepares swap payload via DexService | User submits via AA wallet |
- No Circle
userToken,tokenId, or W3S session credentials. - No
walletIdrequired. - Passkey AA wallets are EVM-only (ARC-TESTNET, ETH-SEPOLIA).
- Solana operations require client-side signing.
Comparison
| Aspect | W3S | PASSKEY |
|---|---|---|
| Key holder | Backend (Circle entity secret) | User (passkey) |
| Client signing | Never | Bridge, Solana payroll, swap |
| Circle session | Required (userToken) | Not used |
walletId | Required | Not required |
| Chains | EVM + Solana | EVM (AA) + Solana (client-sign) |
| Bridge execution | Backend calls Circle Bridge Kit | Frontend executes CCTP directly |
| Payroll execution | CircleService.transfer() | Treasury key (EVM) / unsigned intents (Solana) |
| Default | Yes | Must be explicitly set |
External Signer Bridge
External browser wallets are not a thirdwalletMode. They are a bridge execution mode used when the source wallet is a connected EVM wallet or an injected Solana wallet.
- The browser executes the burn, attestation, and mint flow with public Circle bridge tooling.
- The backend still accepts a best-effort
POST /tasksaudit record for traceability. - These audit tasks require
walletAddressbut do not requirewalletId. - Solana support is provider-agnostic: any compatible injected Solana wallet can be used, not just Phantom.
NEXT_PUBLIC_CIRCLE_API_PROXY_ENABLED=trueenables the same-origin/api/circle/proxyfallback when the deployed Next.js runtime serves that route. When the flag is unset, bridge clients use direct Circle API requests only.- Production reverse proxies must route
/api/circle/proxyto the frontend Next.js app before any generic backend/api/rule; otherwise externalETH-SEPOLIA -> ARC-TESTNETtransfers can silently fall back to slow full-finality burns.
Isolation
TheExecutionRouterService is the only component aware of wallet modes.
- Agents do not check
walletMode. They receive tasks through the router and execute. - The orchestrator does not check
walletMode. It calls the execution router. - Workers do not check
walletMode. They call the orchestrator.
- Extend the
WalletModetype union intask.types.ts. - Add a case in
ExecutionRouterService.resolveWalletMode(). - Implement the engine service.