Skip to content

feat(card): /fix-card-signature — guided repair for enable-bricked kernel accounts#2412

Merged
Hugo0 merged 4 commits into
mainfrom
hotfix/fix-card-signature-repair
Jul 14, 2026
Merged

feat(card): /fix-card-signature — guided repair for enable-bricked kernel accounts#2412
Hugo0 merged 4 commits into
mainfrom
hotfix/fix-card-signature-repair

Conversation

@jjramirezn

@jjramirezn jjramirezn commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Three users' card auto-balance fails hourly forever with AA23 reverted 0x756688fe (InvalidNonce): their kernel accounts have validNonceFrom (3) > currentNonce (2) — state left behind by the 2025-09-18 root-validator migration wave. Kernel v3.1 rejects every enable-mode validation installed below the validNonceFrom floor, so no approval — however correctly signed (one victim re-granted post-#2313 and failed 3 seconds later) — can ever validate. Root-passkey ops are exempt, which is why everything else works for them. A fourth user's account is undeployed and pre-cutoff, so his stored approval's v0.0.3 initCode derives the wrong CREATE2 address (AA14).

This adds a hidden, auth-gated page /fix-card-signature (registered in DEDICATED_ROUTES, not in any public regex, linked from nowhere — support DMs the URL):

  1. Diagnose — reads getCode / currentNonce / validNonceFrom via peanutPublicClient.
  2. Repair (one passkey tap) — nonce-bricked: root userOp invalidateNonce(validNonceFrom + 1) on the user's own kernel (the deployed v3.1 impl syncs currentNonce up); undeployed: the existing migration no-op (buildMigrationNoopCall) which deploys + migrates. Confirmation is by re-reading chain state, never the bundle receipt (same rationale as ensureRootValidatorMigrated), then rebuildClientForChain so the re-grant signs fresh.
  3. Re-grant (one passkey tap) — existing useGrantSessionKey().grant(); the backend session-approve route stores the approval and scheduleRebalances immediately, so the user leaves with a funded card.

Fleet scan of all 227 active-card wallets: exactly 3 bricked + 1 undeployed-AA14; the state can't recur (current migration path leaves nonces untouched — verified on-chain against healthy migrated accounts), hence a hidden page over grant-preflight plumbing.

Risks / breaking changes

  • New isolated route + hook; no existing flow touched (only a DEDICATED_ROUTES entry).
  • invalidateNonce(floor+1) also revokes any not-yet-installed enable signature below the new floor — for these accounts that's exactly the already-dead approval being replaced; healthy accounts never see the button (diagnose gates it).
  • Cross-repo: companion peanut-api-ts PR [TASK-14408] Add ISO2 and ISO3 code for all countries #1179 (AA14 → permanent classification) stops the retry hammering; independent, no deploy-order constraint.

QA

  • useCardSignatureRepair unit tests: diagnosis classification (undeployed / bricked / healthy), repair call selection (invalidateNonce encoded to self vs migration no-op), on-chain confirmation polling incl. never-confirms path (7 tests).
  • Typecheck + full jest green (1 pre-existing add-money failure on main, unrelated).
  • Affected prod users (for reviewer context): 71d2d847… (papilomal), 1e432995… (freddy), ead23114… (arianotfound) — cn=2/vnf=3; c7d77186… (lotito) — undeployed/AA14.

Hotfix to main (prod incident remediation) — back-merge to dev needed after merge.

Design notes / accepted trade-offs (from review passes)

  • Post-repair grant could theoretically bind a stale nonce if the grant's currentNonce read hits a lagging fallback RPC that hasn't seen the repair op the confirm poll observed. Accepted: no client-side fix is airtight (provider stickiness isn't controllable here); the confirm poll requires a healthy read first, and we'll verify all 4 affected users' next sweep in prod DB after they run the flow.
  • repair() re-diagnoses live state before sending — a retry after a confirm-poll timeout converges instead of re-sending an already-consumed invalidateNonce.
  • Kernel invalidation cap guarded: a floor more than MAX_NONCE_INCREMENT_SIZE (10) beyond currentNonce has no valid invalidation target (nonce must be > validNonceFrom and ≤ currentNonce+10), so the page fails with clear copy instead of sending a deterministically-reverting op. No account in the fleet scan is in that state.
  • Local delay/poll helper duplicated from kernelMigration.utils (declined dedup): it isn't exported there, and widening a shared util's API inside a hotfix isn't worth two lines.
  • Follow-up idea (not this PR): server-side detection of doomed approvals at session-approve time (read validNonceFrom/currentNonce and reject with a typed error pointing at this page).

…rnels

Three accounts carry validNonceFrom > currentNonce (left by the 2025-09-18
root-validator migration wave); Kernel v3.1 rejects every enable-mode card
approval below that floor with InvalidNonce, so auto-balance fails hourly
forever and no re-grant can help — the account state itself is the poison.
A fourth account is undeployed with a v0.0.3-initCode approval (AA14).

Hidden support page (not linked anywhere): diagnoses the account on-chain,
sends the matching repair userOp (invalidateNonce(floor+1), or the migration
no-op deploy), confirms against re-read chain state, then re-grants — the
existing session-approve route stores the fresh approval and kicks off a
funding run immediately.
@vercel

vercel Bot commented Jul 14, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Jul 14, 2026 12:52pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds wallet diagnosis and repair logic for nonce-bricked or undeployed accounts, confirms repairs on-chain, rebuilds the client, and provides a dedicated repair and funding page with route registration and hook tests.

Changes

Card signature repair

Layer / File(s) Summary
Wallet diagnosis contract
src/hooks/wallet/useCardSignatureRepair.ts, src/hooks/wallet/__tests__/useCardSignatureRepair.test.ts
Defines diagnosis states, reads deployment and nonce data, and tests undeployed, nonce-bricked, and healthy classifications.
Wallet repair execution
src/hooks/wallet/useCardSignatureRepair.ts, src/hooks/wallet/__tests__/useCardSignatureRepair.test.ts
Submits nonce invalidation or migration operations, polls for confirmation, rebuilds the kernel client, and tests successful, skipped, and unconfirmed repairs.
Dedicated repair page integration
src/app/(mobile-ui)/fix-card-signature/page.tsx, src/constants/routes.ts
Adds the two-step repair and funding UI with status, error, and nonce messaging, and reserves the dedicated route.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested labels: enhancement

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the new /fix-card-signature guided repair flow for bricked kernel accounts.
Description check ✅ Passed The description accurately describes the hidden repair page, diagnosis/repair flow, and related risks and QA.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6093.56 → 6109.87 (+16.31)
Findings: +9 net (+10 new, -1 resolved)

🆕 New findings (10)

  • high complexity — src/app/(mobile-ui)/fix-card-signature/page.tsx — CC 39, MI 60.5, SLOC 53
  • high complexity — src/hooks/wallet/useCardSignatureRepair.ts — CC 35, MI 58.64, SLOC 171
  • high method-complexity — src/app/(mobile-ui)/fix-card-signature/page.tsx:23 — FixCardSignaturePage CC 30 SLOC 35
  • medium high-mdd — src/app/(mobile-ui)/fix-card-signature/page.tsx:23 — FixCardSignaturePage: MDD 51.8 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/wallet/useCardSignatureRepair.ts:75 — useCardSignatureRepair: MDD 36.9 (uses across many lines from declarations)
  • medium complexity — src/constants/routes.ts — CC 15, MI 60.99, SLOC 47
  • medium nextjs-missing-use-client — src/hooks/wallet/useCardSignatureRepair.ts:1 — Hooks used without use client directive
  • low missing-return-type — src/app/(mobile-ui)/fix-card-signature/page.tsx:23 — FixCardSignaturePage: exported fn missing return type annotation
  • low any-usage — src/hooks/wallet/useCardSignatureRepair.ts — 1 any annotation(s)
  • low missing-return-type — src/hooks/wallet/useCardSignatureRepair.ts:75 — useCardSignatureRepair: exported fn missing return type annotation

✅ Resolved (1)

  • src/constants/routes.ts — CC 15, MI 61, SLOC 47

📈 Painscore deltas (top movers)

File Before After Δ
src/app/(mobile-ui)/fix-card-signature/page.tsx 0.0 8.3 +8.3
src/hooks/wallet/useCardSignatureRepair.ts 0.0 7.9 +7.9

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 1830 ran, 0 failed, 0 skipped, 29.8s

📊 Coverage (unit)

metric %
statements 57.9%
branches 42.0%
functions 46.5%
lines 58.0%
⏱ 10 slowest test cases
time test
3.8s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
0.6s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.4s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in updateUserById body
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in updateUserById
0.3s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › keeps stickers off the username pill (final pass respects the keep-out)
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid 9-digit US account
0.1s src/components/Kyc/states/__tests__/KycStates.test.tsx › does not pass the click event to action-required resume
0.1s src/components/Global/SupportDrawer/__tests__/SupportDrawer.test.tsx › shows the mailto fallback + retry when the proxy reports CRISP_FAILED
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle valid ETH address with surrounding spaces
0.1s src/components/Global/GeneralRecipientInput/__tests__/GeneralRecipientInput.test.tsx › should handle invalid ETH address (invalid characters)
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/hooks/wallet/useCardSignatureRepair.ts (1)

46-48: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Confirm-poll budget is tight (~6s total).

If the RPC lags the bundler beyond ~6s, a successfully-landed repair will surface as "did not confirm" even though it worked, forcing a manual retry. Given this is a support-guided hotfix flow, consider a slightly larger budget or backoff (e.g., 8–10 retries) to reduce false negatives.

[reliability_and_availability]

Also applies to: 115-131

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/wallet/useCardSignatureRepair.ts` around lines 46 - 48, Increase
the confirmation polling budget used by the card signature repair flow from the
current five attempts to roughly 8–10 attempts, or otherwise extend the total
wait to about 8–10 seconds. Update the retry logic associated with
CONFIRM_RETRIES while preserving the existing confirmation interval and
successful-repair handling.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/`(mobile-ui)/fix-card-signature/page.tsx:
- Line 23: Update the fix-card-signature page’s useRainCardOverview
destructuring to include isLoading, then gate the disabled={!card} fallback
behavior and “No active card found...” message on the overview query being
settled. Preserve the existing card flow once loading completes.
- Around line 31-34: Update the useEffect that invokes diagnose so it re-runs
when the useZeroDev().address value changes, allowing diagnosis after the
initially undefined address is populated. Add address to the effect dependency
list while preserving the existing diagnose invocation.

---

Nitpick comments:
In `@src/hooks/wallet/useCardSignatureRepair.ts`:
- Around line 46-48: Increase the confirmation polling budget used by the card
signature repair flow from the current five attempts to roughly 8–10 attempts,
or otherwise extend the total wait to about 8–10 seconds. Update the retry logic
associated with CONFIRM_RETRIES while preserving the existing confirmation
interval and successful-repair handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fa14351f-75d9-4450-b6f2-cf318390c253

📥 Commits

Reviewing files that changed from the base of the PR and between 8316bdd and 0adb18f.

📒 Files selected for processing (4)
  • src/app/(mobile-ui)/fix-card-signature/page.tsx
  • src/constants/routes.ts
  • src/hooks/wallet/__tests__/useCardSignatureRepair.test.ts
  • src/hooks/wallet/useCardSignatureRepair.ts

Comment thread src/app/(mobile-ui)/fix-card-signature/page.tsx Outdated
Comment thread src/app/(mobile-ui)/fix-card-signature/page.tsx Outdated
- diagnose keyed on the async zerodev address (mount-only effect left the
  page dead on a cold load from a support DM — the exact target flow)
- repair() re-diagnoses live state before sending, so a retry after a
  confirm-poll timeout can't re-send an already-consumed invalidateNonce
- guard the Kernel v3.1 invalidation cap (floor > currentNonce+10 has no
  valid target — fail with clear copy instead of burning doomed passkey taps)
- step 2 respects the card-overview loading state instead of flashing
  'no active card — contact support' at users on the success path
- 'Check again' affordance after a failed diagnosis; dismissed passkey
  sheets are quiet no-ops, matching the grant path; drop the dead lastError
  render condition and the type export from the hook file
@jjramirezn
jjramirezn marked this pull request as ready for review July 14, 2026 12:57
@Hugo0
Hugo0 merged commit a5cc747 into main Jul 14, 2026
24 of 26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants