- Co-owner: signs anything the passkey can, such as approving or revoking an agent session, or replacing a lost passkey.
- Guardian: can only start a recovery. The recovery completes after 7 days unless an owner cancels it.
@chipi-stack/* 14.17.0+ ships the pieces: the STARK_SNIP12 signer kind, sdk.executeWithExternalSigner / useExternalSignerExecute, and the governance builders and readers used below.
The
STARK_SNIP12 verifier class (0x017884125977631bee198b34cd3e3fcf975a543e453c8b996a818910a964b5e) is declared on Starknet mainnet (2026-10-14, tx 0x00ba3329…59e6, block 16377651). Step 2 still checks isShhhVerifierDeclared first, so the same code refuses to propose it on a network where it isn’t.Why a new signer kind
Bramble uses a STARK key, the same curve as the wallet’s ownSTARK signer. But a browser wallet never signs a raw hash. Its only signing method, wallet_signTypedData, signs a SNIP-12 message hash that always includes the signing wallet’s own address. So Bramble can’t produce the signature the STARK verifier checks.
STARK_SNIP12 wraps the Chipi wallet’s transaction hash (its SNIP-12 outside-execution hash) in a typed data Bramble signs as-is:
wallet_signTypedData. The SDK accepts all three and sends the verifier [r, s]:
Not supported: multisig accounts, Ready accounts with a guardian, and Braavos hardware or passkey signers.
A co-owner has full control of the wallet. If the user only wants a backup, make Bramble a guardian. You pick the role in step 2.
1. Read Bramble’s key (browser)
The verifier is bound to Bramble’s STARK key and account address. Read them from one signature: connect Bramble from a click, have it sign anApproval, then resolve the key.
resolveStarkSnip12Signer reads the key from the signature (Ready), the account (get_public_key for Bramble’s OpenZeppelin account, get_signers for Braavos), the deployment data (a new account), or the owner record (a Chipi wallet). It throws unless that key verifies the signature.
2. Propose (passkey, one prompt)
A Chipi wallet starts with only theSTARK verifier. Propose the new kind and the new owner in the same transaction. Both have a 48-hour timelock. role is "OWNER" for a co-owner or "GUARDIAN" for recovery only; role and label must be the same in step 3, because the timelock commits to them.
proposeCalls with the passkey, like any other call on the wallet (useCallAnyContract with usePasskey: true, contractAddress set to the wallet). Wait for the transaction, then read the op ids from its events: they include the block time, so they can’t be computed in advance.
3. Execute (after 48 hours)
Execute calls are permissionless, so anyone may send them once the timelock is over: the passkey again, or Bramble itself withwallet_addInvokeTransaction (Bramble pays its own gas in STRK). Register the verifier first: execute_add_owner checks the new key through it.
4. Sign with Bramble (co-owner)
Only when Bramble was added withrole: "OWNER". Bramble signs an Approval for the transaction; Chipi’s paymaster relays it gaslessly. Here Bramble approves a capped agent session:
useExternalSignerExecute() from @chipi-stack/nextjs (or @chipi-stack/chipi-react) takes the same input. Wait for the transaction before treating it as done.
Bramble shows the typed data with the wallet address and a hash, not the decoded calls. Show the calls in your own UI before asking for the signature.
5. Recover
Both recovery paths below work on-chain today. What Chipi does not support yet is pointing the wallet record at a new passkey: the record keeps the old encrypted key and passkey id, and the SDK has no call to replace both. So after a recovery, the recovered key signs withexecuteShhhWithStarkKey, not with the passkey hooks. Keep that key in memory, or encrypt it yourself, until record updates ship.
The user can still sign in to your app with Bramble (Sign in with Starknet), which gives you the bearer token these calls need.
With a co-owner. Nothing is locked: Bramble keeps signing everything. To replace the lost passkey key, Bramble signs buildProposeRotateOwnerCall({ walletAddress, proposer: brambleId, ownerId: 0, newPubkeyBytes: [newKey] }) through executeWithExternalSigner, with a fresh STARK key generated in the browser. After 24 hours anyone executes the rotation (buildExecuteRotateOwnerCall with the op id from the propose events). newKey then signs as owner 0: executeShhhWithStarkKey({ privateKey, ownerIndex: 0, ... }).
With a guardian (7 days).
- Generate a fresh STARK key K in the browser. Bramble signs
buildInitiateRecoveryCall({ walletAddress, proposer: brambleId, newOwner: { kind: "STARK", pubkeyBytes: [K], role: "OWNER", weight: 1, label: "recovered" } })throughexecuteWithExternalSigner. A guardian’s signature is accepted for this one call and nothing else. - During the 7 days, any owner can cancel with
buildCancelRecoveryCall, passing the cancelling owner’s id. Guardians can’t cancel. WatchreadPendingRecoveryand alert the user, so a recovery they didn’t start gets cancelled. - After 7 days anyone sends
buildFinalizeRecoveryCallwith the same new-owner fields, for example Bramble withwallet_addInvokeTransaction(it pays its own gas in STRK). Recovery adds K as a new owner; it never removes or replaces one. - K signs with
executeShhhWithStarkKey({ privateKey: K, ownerIndex: <K's id from readShhhOwners>, ... }). Remove the lost owner withbuildProposeRemoveOwnerCall(24 hours) if it may be compromised.
STARK always is), the key isn’t already an owner, and the weight is above 0. Only an owner can clear a recovery that can’t finalize, and the user who needs it has none.
Bramble rules to follow
From Bramble’s integration guide (v0.10.5):window.starknet_bramblecan appear after page load. Wait for thestarknet#initializedevent before deciding it is not installed.- Prompts open only from a click. Don’t auto-retry: Bramble allows 5 pending prompts per site.
- Every params object is checked strictly. Never send
undefinedvalues or extra keys. - The typed data’s
chainIdmust match Bramble’s active network, or it answers114before any prompt. Checkwallet_requestChainIdis0x534e5f4d41494e(mainnet) first. 113means the user declined.163covers a locked wallet, a site that isn’t connected, and other refusals: ask the user to unlock Bramble and retry.
Related
✅ Verified against the SDK and contract source on 2026-10-14.
