Skip to content

TASK-22692: Prepare QR payments early and measure Pay-to-success - #3221

Merged
jjramirezn merged 5 commits into
devfrom
codex/TASK-22692-qr-latency
Sep 17, 2026
Merged

jjramirezn merged 5 commits into
devfrom
codex/TASK-22692-qr-latency

Conversation

@jjramirezn

@jjramirezn jjramirezn commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

QR Pay now prepares the unsigned smart-wallet operation while the user reads a fixed-amount payment. At Pay it still checks the live strategy, client, nonce, lock expiry and sponsorship before signing. Stale or failed warmup falls back to a fresh operation; mixed, collateral and open-amount paths retain their live preparation.

  • Bind preparation and signing to the same kernel client. Warmup creates no signature, Rain draft or broadcast. Preview sponsorship does not consume the policy. Pay consumes once after validation; an uncertain consuming response fails without a second request.
  • Keep reward confetti after durable issuance while the API transfers the reward in the background. Suppress the reward card and celebration for explicitly failed payouts, including status changes during a hold. Payment success remains visible.
  • Measure Pay, preparation, signature, request and committed success with one attempt UUID. Report failed, pending, cancelled and unknown attempts separately. A failure before signing starts finishes the attempt without reporting a signature stage.

Validation: full UI suite, 624 suites / 7,705 passing tests (7 skipped); 237 focused wallet, context and QR state tests after sponsorship review; 74 focused QR flow tests after telemetry review. Typecheck, generated API drift check, full Prettier and ESLint (0 errors, 68 warnings, identical to dev). Production build passes with LOCAL_BUILD=true (Sentry uploads disabled). Hook tests cover no early signature/draft, stale nonce/client, live routing, sponsorship failure, telemetry and confetti. Normal payment and pending-reward layouts and copy stay the same. Failed reward payouts use the existing plain payment-success layout.

Design notes: the candidate is best-effort and only helps when preparation completes before Pay. Sponsorship is refreshed at Pay after the live nonce check. The shared paymaster retains its existing single callback, so this adds no paymaster round trip to normal wallet preparation. Tests exercise the installed viem preparation path and check the consuming request count. The success timing ends at committed client state; it is not screen paint or merchant bank credit. The generated snapshot also catches up two existing backend contracts (residence refusal and username check).

Release with API PR #1617 for TASK-22692. Both changes operate on release without rollout flags. The API request and reward fields are additive. No merge or deployment from this task.

Tracking: TASK-22692. Measurement: QR latency dashboard; new charts await release. Separate rewards/privacy documentation follow-up is recorded on the API PR.

Screenshots: ⚠️ No capture of the failed-payout state. Existing screenshot fixtures cannot enter QR payment completion; the separate QR claim-success fixture does not cover this view. Real-view state tests cover failed, failed-after-reveal and pending confetti.

Screenshot capture and ds-shots checks pass. Publishing the screen-library report failed during Cloudflare token verification (job log); this PR does not change the publishing scripts or workflows. Required CI and the Vercel preview pass.

@notion-workspace

Copy link
Copy Markdown

@vercel

vercel Bot commented Sep 17, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
peanut-wallet Ready Ready Preview Sep 17, 2026 6:12am UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: fc21d66a-2526-4572-95f6-14b494edc73b

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 7997.19 → 8021.13 (+23.94)
Findings: +21 net (+402 new, -381 resolved)

🆕 New findings (402)

  • critical complexity — src/features/payments/flows/qr-pay/useQrPayFlow.ts — CC 184, MI 52.33, SLOC 717
  • critical complexity — src/context/kernelClient.context.tsx — CC 116, MI 56.85, SLOC 526
  • critical complexity — src/features/payments/flows/qr-pay/usePerkHoldToClaim.ts — CC 50, MI 57.24, SLOC 198
  • high structural-dup — types/api.generated.ts:9894 — 83 duplicate lines / 392 tokens with types/api.generated.ts:10692
  • high structural-dup — types/api.generated.ts:9904 — 74 duplicate lines / 352 tokens with types/api.generated.ts:10702
  • high hotspot — src/constants/analytics.consts.ts — 73 commits, +389/-72 lines since 6 months ago
  • high hotspot — src/types/api.generated.ts — 50 commits, +36086/-23869 lines since 6 months ago
  • high complexity — src/features/payments/flows/qr-pay/views/QrPaySuccessView.tsx — CC 43, MI 58.87, SLOC 135
  • high complexity — src/services/manteca.ts — CC 43, MI 53.27, SLOC 192
  • high complexity — src/hooks/wallet/useSignUserOp.ts — CC 40, MI 53.36, SLOC 184
  • high method-complexity — src/features/payments/flows/qr-pay/useQrPayFlow.ts:536 — CC 32 SLOC 219
  • high complexity — src/hooks/wallet/useSignSpendBundle.ts — CC 23, MI 49.99, SLOC 260
  • high complexity — src/constants/analytics.consts.ts — CC 1, MI 30.88, SLOC 206
  • medium high-mdd — src/features/payments/flows/qr-pay/useQrPayFlow.ts:95 — useQrPayFlowController: MDD 234.3 (uses across many lines from declarations)
  • medium high-mdd — src/context/kernelClient.context.tsx:316 — KernelClientProvider: MDD 112.1 (uses across many lines from declarations)
  • medium high-dlt — src/features/payments/flows/qr-pay/useQrPayFlow.ts:95 — useQrPayFlowController: DLT 86 (calls 86 distinct functions — high context load)
  • medium high-mdd — src/hooks/wallet/useSignSpendBundle.ts:119 — useSignSpendBundle: MDD 84.6 (uses across many lines from declarations)
  • medium high-mdd — src/features/payments/flows/qr-pay/useQrPayFlow.ts:536 — : MDD 84.1 (uses across many lines from declarations)
  • medium high-mdd — src/features/payments/flows/qr-pay/views/QrPaySuccessView.tsx:32 — QrPaySuccessView: MDD 81.1 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/wallet/useSignSpendBundle.ts:129 — : MDD 65.0 (uses across many lines from declarations)

…and 382 more.

✅ Resolved (381)

  • src/features/payments/flows/qr-pay/useQrPayFlow.ts — CC 157, MI 52.82, SLOC 610
  • src/context/kernelClient.context.tsx — CC 116, MI 56.8, SLOC 528
  • types/api.generated.ts:9891 — 83 duplicate lines / 392 tokens with types/api.generated.ts:10689
  • types/api.generated.ts:9901 — 74 duplicate lines / 352 tokens with types/api.generated.ts:10699
  • src/constants/analytics.consts.ts — 72 commits, +386/-72 lines since 6 months ago
  • src/types/api.generated.ts — 49 commits, +36015/-23867 lines since 6 months ago
  • src/features/payments/flows/qr-pay/views/QrPaySuccessView.tsx — CC 45, MI 58.89, SLOC 133
  • src/services/manteca.ts — CC 43, MI 53.24, SLOC 192
  • src/features/payments/flows/qr-pay/usePerkHoldToClaim.ts — CC 40, MI 57.24, SLOC 169
  • src/hooks/wallet/useSignSpendBundle.ts — CC 22, MI 49.33, SLOC 251
  • src/constants/analytics.consts.ts — CC 1, MI 30.94, SLOC 205
  • src/features/payments/flows/qr-pay/useQrPayFlow.ts:69 — useQrPayFlowController: MDD 224.3 (uses across many lines from declarations)
  • src/context/kernelClient.context.tsx:312 — KernelClientProvider: MDD 112.1 (uses across many lines from declarations)
  • src/features/payments/flows/qr-pay/views/QrPaySuccessView.tsx:32 — QrPaySuccessView: MDD 82.1 (uses across many lines from declarations)
  • src/hooks/wallet/useSignSpendBundle.ts:109 — useSignSpendBundle: MDD 81.4 (uses across many lines from declarations)
  • src/features/payments/flows/qr-pay/useQrPayFlow.ts:69 — useQrPayFlowController: DLT 78 (calls 78 distinct functions — high context load)
  • src/hooks/wallet/useSignSpendBundle.ts:119 — : MDD 62.5 (uses across many lines from declarations)
  • src/context/kernelClient.context.tsx:312 — KernelClientProvider: DLT 61 (calls 61 distinct functions — high context load)
  • src/features/payments/flows/qr-pay/useQrPayFlow.ts:461 — : MDD 57.2 (uses across many lines from declarations)
  • src/features/payments/flows/qr-pay/usePerkHoldToClaim.ts:26 — usePerkHoldToClaim: MDD 50.8 (uses across many lines from declarations)

…and 361 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/features/payments/flows/qr-pay/qr-payment-telemetry.ts 0.0 6.6 +6.6
src/hooks/wallet/useSmartSpendPreparation.ts 0.0 6.2 +6.2
src/hooks/wallet/smartSpendPreparation.ts 0.0 4.1 +4.1
src/hooks/wallet/paymasterSponsorship.ts 0.0 2.9 +2.9
src/features/payments/flows/qr-pay/useQrPayFlow.ts 14.7 16.3 +1.7
src/hooks/wallet/useSignUserOp.ts 7.2 8.7 +1.5
src/features/payments/flows/qr-pay/usePerkHoldToClaim.ts 7.8 8.4 +0.6

@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 7712 ran, 0 failed, 0 skipped, 2.9m

📊 Coverage (unit)

metric %
statements 78.8%
branches 66.1%
functions 73.0%
lines 79.9%
⏱ 10 slowest test cases
time test
🐢 9.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Network failure keeps loading while retries remain, then shows the generic error
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › User KYC not approved fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_RECENT_REFUND fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_SOURCE_OVER_MONTHLY_CAP fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_MERCHANT_VOLUME_NEAR_CAP fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › MANTECA_USER_NOT_PROVISIONED fails fast with copy that names the real cause
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › routes the KYC rejection on its wire code, and does not retry it
4.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › a refused idempotency key tells the user to scan again, not to contact support
3.1s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
3.0s src/app/(mobile-ui)/qr-pay/__tests__/qr-pay-states.test.tsx › Going offline blames the connection, and reconnecting clears it for the recovered scan
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@jjramirezn

Copy link
Copy Markdown
Contributor Author

/chip review

@github-actions

github-actions Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

🖼 Visual diff — 1 screen moved

2 of 96 shots changed · 94 identical · baseline 404f40e → head 2d3db30

worst % screen widths
15.49% avatar-picker 320, 430

job summary · before/after/diff images — artifact

Fixture screenshots, no backend. Advisory — this check never blocks a merge. Posted from the default branch by ds-shots-comment.yml; the report it renders is untrusted data.

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

No findings.

Findings

  • MINOR · src/features/payments/flows/qr-pay/usePerkHoldToClaim.ts:93 · [claude-opus] Reward is celebrated even when the API reports the payout failed
    This pair moves the reward transfer off the completion response: the API now returns as soon as the usage is issued and reserved, with payoutStatus: 'pending' | 'completed' | 'failed' (new field, declared in src/services/manteca.ts:59). The UI reads eligible only — usePerkHoldToClaim.ts:93 gates on !qrPayment.perk?.eligible, and QrPaySuccessView.tsx:71/117 render 'You earned a reward!' / 'You earned ${amount}! Hold to claim your reward.' on eligible alone — so a response carrying payoutStatus: 'failed' (reachable on a replayed completion for an intent whose payout job already failed terminally) still produces the card, the hold gesture, confetti and a reward_claimed capture. The code comment states this deliberately: 'which this hook neither reads nor requests'.

Product truth says the reward is real money that lands: /home/chip/mono/product/rewards.md — 'Tap to claim, and the reward moves to your spendable balance.' And /home/chip/mono/product/feedback/problems/cashback-wrong-or-missing.md records 'saw cashback on screen but it never credited' as a top support cluster (2026-02-16 discord: 'users report seeing they received cashback on screen but it never reflects in their wallet balance'). Before this change the API only returned a perk after claimPerk succeeded, so the celebration implied the money had already moved; now it does not. The code is the side that is wrong here.

The pending case is fine and is correctly tested — the job pays shortly. The narrow fix: treat perk.payoutStatus === 'failed' as not claimable (same branch as eligible: false), and add the mirror of the existing 'payout still pending' test for a failed payout. Optionally soften the post-reveal copy for pending so the screen does not assert the balance already moved.

Checked clean

  • Confirmed the detached worktree head and merge base match the supplied SHAs.
  • Reviewed fixed-amount warmup, live strategy routing, nonce/client/lock validation, sponsorship refresh, fallback preparation, and one-shot candidate lifecycle.
  • Reviewed QR completion outcomes, attempt telemetry ordering and privacy bounds, durable reward reveal semantics, and the paired generated API contract.
  • Core CI gates succeeded at this head; visual capture and ds-shots jobs were still in progress when checked.

Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.

Third opinion by claude-opus: 1 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

Exact head: 378a6592f400 · Context: repo, product · Took 23m

@jjramirezn

Copy link
Copy Markdown
Contributor Author

/chip review

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

Found one major issue: fixed-amount QR warm-up consumes ZeroDev sponsorship policy before Pay, and the Pay-time refresh consumes it again. The previously raised failed-reward celebration defect is fixed at this head.

Findings

  • MAJOR · src/hooks/wallet/useSignUserOp.ts:134 · Warm-up consumes sponsorship before Pay
    prepareWithClient runs the real client's full prepareUserOperation, whose configured paymaster callback invokes ZeroDev sponsorUserOperation without shouldConsume; the pinned SDK defaults that flag to true. Merely opening a fixed-amount QR therefore consumes one sponsorship-policy use, and reusePreparedUserOp consumes a second during its Pay-time sponsorship refresh. With per-account or budget policies, abandoned scans drain quota and a later valid payment can be rejected before signing. Prepare with a non-consuming paymaster request (or without final sponsorship), then consume exactly once during the Pay-time refresh.

Checked clean

  • Exact detached head, supplied base SHA, merge base, author, and PR metadata
  • Failed-reward success UI and hold lifecycle, including pending-to-failed cancellation
  • Smart-only, mixed, and collateral routing plus prepared UserOp staleness, nonce, and sponsorship refresh
  • QR payment telemetry bounds and terminal outcome paths
  • Exact-head CI: unit, typecheck, lint, format, screen tests, native export, and preview passed; screenshot publication alone failed Cloudflare token verification

Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

Exact head: d8c41fec9dc8 · Context: repo, dependency · Took 26m

Comment thread src/hooks/wallet/useSignUserOp.ts Outdated
@jjramirezn

Copy link
Copy Markdown
Contributor Author

/chip review

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

Both prior findings are fixed at this head; one minor telemetry correctness issue remains.

Findings

  • MINOR · src/features/payments/flows/qr-pay/useQrPayFlow.ts:643 · Do not mark pre-signing failures as signature-ready
    This catch covers failures before any signature work, but it always emits signature_ready. For example, an insufficient live balance can make strategy resolution throw before strategy_ready, and a session-key grant can fail during preflight; both attempts are then recorded as reaching the signature stage even though they did not. That contradicts the telemetry contract that absent stages are not reported and corrupts the stage funnel. Emit this stage only after signing actually starts, or have the lower layer report the precise failed stage and finish the attempt without signature_ready for earlier failures.

Checked clean

  • Pinned head, supplied base SHA, merge base, trusted author, and dev base all match the request.
  • Warm-up sponsorship now carries the preview context through real viem preparation, and ZeroDev SDK 5.5.7 forwards shouldConsume=false without serializing the context into the UserOperation.
  • Prepared UserOperations are rejected on client, chain, call, lock-expiry, or nonce drift; the Pay path consumes sponsorship once and propagates uncertain consuming failures without retrying.
  • Failed reward payouts suppress the reward offer, hold gesture, confetti, reveal UI, and reward analytics, including a status change during an armed hold.
  • Smart-only, mixed, and collateral-only routing, API request shapes, generated contract updates, and payment outcome telemetry were reviewed.
  • Completed CI at this head has no failures: unit, typecheck, lint, format, native export, screen tests, preview deployment, and the aggregate ci-success gate passed; visual capture jobs were still running at review time.

Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

Exact head: ffecd05c47ee · Context: repo, sdk-source · Took 14m

Comment thread src/features/payments/flows/qr-pay/useQrPayFlow.ts Outdated
@jjramirezn

Copy link
Copy Markdown
Contributor Author

/chip review

@chip-peanut-bot chip-peanut-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chip review — no blocking findings — this is not an approval

No findings at the supplied head.

Checked clean

  • P1 is fixed: warm-up uses a per-request non-consuming ZeroDev sponsorship preview, while Pay performs one consuming refresh and propagates uncertain consuming failures.
  • P2 is fixed: signature_ready is emitted only after signing_preparation_ready; routing, collateral preflight, and Rain draft failures do not invent a signature stage.
  • Reviewed prepared-candidate identity and staleness, live strategy and nonce validation, sponsorship refresh and signing, reward payout-status UI, telemetry stages and attempt IDs, request types, and focused regression tests.
  • Verified the split prepare/sign and paymaster-context assumptions against the exact @zerodev/sdk 5.5.7 and viem 2.55.0 package sources.
  • Exact-head unit, typecheck, lint, format, native-export, screen, preview, and aggregate CI checks passed; visual capture jobs were still running when checked.
  • Checked the canonical Lexicon and Funnels & Rewards v2; the linked Notion task was not shared with the integration and returned object_not_found.

Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

Exact head: 2d3db3060417 · Context: repo, notion, provider-sdk · Took 14m

@jjramirezn
jjramirezn marked this pull request as ready for review September 17, 2026 10:54
@jjramirezn
jjramirezn merged commit b8e2f8c into dev Sep 17, 2026
34 of 37 checks passed
@chip-peanut-bot

Copy link
Copy Markdown
Contributor

This pull request was already closed when the review finished, so these findings are follow-up work rather than a gate.

Chip review — no blocking findings — this is not an approval

No actionable defects found; both prior findings are fixed at this head.

Checked clean

  • Exact detached head and merge base; smart-only, mixed, and collateral-only signing and submission paths
  • Warm-up preview sponsorship, Pay-time client/call/expiry/nonce validation, and single consuming sponsorship refresh
  • QR attempt telemetry stage ordering across preparation, cancellation, deterministic failure, pending, unknown, and committed success outcomes
  • Reward UI and hold behavior for pending, completed, failed, and legacy payout responses
  • Exact-head CI; required checks pass, while screenshot-report publishing failed independently at Cloudflare token verification

Security review by moonshotai/kimi-k3: 0 finding(s), marked with the model name. It reads the diff only and answers only security, privacy and money, so treat its findings as advice.

Third opinion by claude-opus: 0 finding(s), marked with the model name. It answers only product truth, missing tests and the cross-repo contract, so treat its findings as advice.

Exact head: 2d3db3060417 · Context: repo, ci, provider-sdk · Took 15m

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant