Security
Hosted simple wallet mainnet bar (recoverability + fund safety, IdentityStore operator, CI/audit roadmap): Simple wallet mainnet assurance.
- Sweepers never open the product DB; all updates go through the signed API
- Admin routes require
ADMIN_API_KEY - Admin UI is not in the public nav; still requires a valid key
- nginx + app rate limits (default 1 invoice create/s per IP)
- CORS allowlist via server YAML /
CORS_ORIGINS - Claim leases prevent double-sweep races between workers
- Follow-up: merchant API keys / captcha on create; Redis rate limits for multi-replica
Wallet client HMAC
- Partner
hmacSecretis shown once at create/rotate; store server-side only (never in frontend) - Requests use timestamp skew (±5 min), nonce replay table, and body SHA-256 binding
- WebAuthn assertions must match the client
rpId/origins; wallets are scoped to(clientId, email) - Recovery trusts the partner as IdP (
identityVerified: true); Commerce does not send verification email - Challenges expire (~5 min) and are single-use
- Cross-client list isolation: client B cannot see client A’s wallets for the same email
- Guardian private key lives on the wallet-deployer worker, not in the HTTP API process
Hosted identity wallet (certified simple path)
New hosted /wallet users use IdentityWallet + IdentityStore (not Wallet.sol owners). Program of record: Simple wallet mainnet assurance.
- Spend: EntryPoint v0.9 UserOp →
IdentityStore.verifymust return the wallet’sidentityId - Methods: WebAuthn, YubiKey, EOA on the identity.
disableRestoreis permanent and EOA-gated - Email OTP starts a recovery request only; on-chain restore is
recoveryOperator(initiateRestore→ delay →executeRestore). Operator is a reco 2-of-3 Super Wallet (operator infrastructure; user Super remains beta) - Existing methods can
cancelRestoreduring the delay and canaddMethodeven afterdisableRestore - Counterfactual address salt:
keccak256(abi.encode("TC-IDENTITY-WALLET-V1", identityId, index)) IDENTITY_RESTORE_ENABLEDis a test override of on-chainrestoreEnabled; do not set it on mainnet- Super Wallet (
enableSuper) is beta / not fully audited. It does not calldisableRestore(unlike legacyenableAdvanced)
Hosted wallet recovery (legacy AdminGuardian)
Distinct from IdentityStore operator restore and from the HMAC partner flow (identityVerified IdP attestation). Applies to legacy Wallet.sol clones with AdminGuardianRecovery:
- Hosted
/api/wallet/email*+/api/wallet/recovery*use email OTP (Resend whenRESEND_API_KEYis set; otherwise logged in dev) - Email-first recovery: OTP, then list wallets; mutating routes require Turnstile when
TURNSTILE_SECRETis set - Recovery without email is allowed (wallet address + new key). Ops is emailed
recovery requestedwhenWALLET_RECOVERY_NOTIFY_EMAILis set - Recover-without-email EOA proof is EIP-712
Recover(address wallet, string challenge, address signer)(same domain as Connect wallet). Leftover EIP-191personal_signrecovery messages are rejected - If that EOA is already an on-chain owner (simple sentinel or Super Wallet
KEY_EOA), the client self-enrolls a new passkey with an EOA-signed UserOp (addOwner/addKey). New EOAs still go to guardian review and are not initiated on-chain (P256-onlyAdminGuardianRecovery) - Passkey assertions use the hosted WebAuthn
rpId(API/public hostname), not a partner domain - Guardian dashboard
/guardian(not in nav): MetaMask signs an EIP-191 login; only the on-chainAdminGuardianRecovery.guardianEOA (orWALLET_ADMIN_GUARDIANfallback) may approve/reject - Approve enqueues a
wallet_recovery_jobsinitiate; the deployer still holdsguardianPrivateKey - OTP codes are SHA-256 hashed at rest; never returned in HTTP JSON
Identity wallets reuse the same hosted request rows; initiation is IdentityStore (recoveryOperator / reco Super), not initiateOwnerRecovery.
Wallet EOA owners (Connect wallet)
Identity wallets add an EOA as an IdentityStore method (METHOD_EOA) and sign EIP-712 Verify / AddMethod against the store. Legacy Wallet.sol still uses the owner sentinels below.
- Simple legacy wallets add a connected EOA via
addOwnerEoa(EIP-712AddOwner); the address is stored as a sentinelqx/qyowner and can sign UserOps with EIP-712UserOp(bytes32 userOpHash) - Super Wallets add
KEY_EOAon the current identity viaaddKeyEoa(EIP-712AddKey) - Domain: name
Trustless Commerce Wallet, version1,chainId,verifyingContract= wallet WalletFactory.walletImplementationis immutable. Existing clones keep the bytecode they were created with (P256-only / EIP-191 EOA). New EOA-owner / EIP-712 validation requires a new implementation and factory; updateWALLET_FACTORY_ADDRESSafter redeploy- Deploy
WalletEip712first and link it when compiling/deployingWallet(scripts/deploy-wallet.ts)