feat(spend): one-tap mixed spend for the sign-only engine, dark behind its own flag - #2969
Conversation
…d its own flag QR pay, Manteca withdraw and the card lock/cancel modals sign a mixed spend through useSignSpendBundle and hand the UserOp to the backend to broadcast. That engine still costs two passkey sheets (Rain admin EIP-712 + UserOp); ui#2959 only covers the broadcasting engine. signMixedEphemeralSpend is the sign-only twin of tryMixedEphemeralSpend: the same single enable-signature tap, then the ephemeral key signs the admin EIP-712 and the UserOp silently, uninstall last, and the unbroadcast artifact goes back in the same SignedSpendArtifact shape. Nothing is sent from the client, so a signing failure falls back to the two-tap path with nothing at stake. Own flag (session_key_spend_sign) rather than the broadcasting engine's: a permission that fails on-chain surfaces here as the backend's broadcast reverting, with no client-side retry. Stays off until ui#2959 has proven the ERC-1271 ordering on production contracts. The permission lifetime is 10 min for this path (backend broadcasts after Manteca settles, with a receipt re-poll on timeout); the op stays single-use through its nonce.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…y-spend-sign-engine
…or cannot be resolved Same gap Chip found on the broadcasting engine (ui#2959): the validator lookup sat outside the helper's catch, so a rejection escaped as a failed spend after /prepare had run. It now yields ok:false and the two-tap path signs against the same preparation.
Why
QR pay, Manteca withdraw and the card lock/cancel modals sign a mixed spend through
useSignSpendBundleand hand the UserOp to the backend to broadcast. That engine still costs two passkey sheets (Rain admin EIP-712, then the UserOp). #2959 covers only the broadcasting engine (useSpendBundle). Item 3 ofmono/engineering/passkey-prompt-pairs-2026-09-03.md.What
signMixedEphemeralSpend(src/hooks/wallet/mixedEphemeralSign.ts): sign-only twin oftryMixedEphemeralSpend. Same single enable-signature tap insidecreateEphemeralSpendSession; the ephemeral key then signs the Rain admin EIP-712 and the UserOp silently, self-uninstall as the last call, and returns the unbroadcast op in the existingSignedUserOpDatashape.signUserOperationfrom the ZeroDev SDK, exactly whatuseSignUserOpuses.useSignSpendBundlemixed path tries it first whensessionKeySignEnabled(); on any failure it falls through to the two-tap path with the same prep (nothing was broadcast, nothing at stake) and reportssession_key_spend_fallbackwithflow: 'sign-only'.session_key_spend_sign(build gate shared with feat(spend): one-tap mixed spend for everyone (broadcasting engine) #2959). Why not reusesession_key_spend: here a permission that does not validate on-chain surfaces later, as the backend's broadcast reverting, with no client-side retry. So this flag stays off until feat(spend): one-tap mixed spend for everyone (broadcasting engine) #2959 has proven the ERC-1271 ordering on production contracts, then it is one click.createEphemeralSpendSessiontakes an optionalttlSeconds; this path uses 10 min (default stays 180 s). The backend broadcasts after Manteca settles inside the same request, with a receipt re-poll on timeout, so 3 min was tight. The op stays single-use through its nonce and the batch still ends in the self-uninstall./dev/session-key-spendshows the second flag.buildWithdrawCallexported frommixedEphemeralSpend.ts(shared with the twin).Rollout
session_key_spendproves one real mixed send on prod (one sheet, no fallback).session_key_spend_sign, internal cohort; one real QR pay on a mixed balance; expect one sheet andsession_key_spend_attempted{flow:'sign-only'}with no fallback and a successful backend broadcast.Verification
mixedEphemeralSign(artifact shape, TTL, silent admin sig, uninstall last, signed through the session client and never sent, dispose on every exit; preflight and signing failures asok:false),sessionKeySignEnabledgate matrix, and threeuseSignSpendBundlecases (flag off unchanged, flag on returns the ephemeral artifact with no passkey admin signature, fallback reuses the same prep and reports the reason).pnpm typecheckclean; ephemeralSpendKey, useGrantSessionKey, session-key-spend.consts, useSignSpendBundle suites green; prettier clean.