From aeb0bc3b036b06e607098e5a27250d26add2ffe7 Mon Sep 17 00:00:00 2001 From: Hugo Montenegro Date: Tue, 12 May 2026 21:09:44 +0100 Subject: [PATCH 1/5] chore: enable full maintenance mode for prod release migrations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flips enableFullMaintenance to true so all routes redirect to /maintenance while we run the decomplexify ledger cutover migrations on prod DB. Merge this PR immediately before kicking off migrations. To exit maintenance, either revert this PR or wait for the dev->main release PR to merge (note: dev still has enableFullMaintenance: false, so the release merge will conflict on this file — resolve to false). --- src/config/underMaintenance.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/underMaintenance.config.ts b/src/config/underMaintenance.config.ts index e09b6cb7c..a9f3232b1 100644 --- a/src/config/underMaintenance.config.ts +++ b/src/config/underMaintenance.config.ts @@ -52,7 +52,7 @@ interface MaintenanceConfig { } const underMaintenanceConfig: MaintenanceConfig = { - enableFullMaintenance: false, // set to true to redirect all pages to /maintenance + enableFullMaintenance: true, // set to true to redirect all pages to /maintenance enableMaintenanceBanner: false, // set to true to show maintenance banner on all pages disabledPaymentProviders: [], // set to ['MANTECA'] to disable Manteca QR payments disableSquidWithdraw: true, // set to true to disable cross-chain withdrawals (only allows USDC on Arbitrum) From 8ef9bb5cb7cce6927cfd25f9b30b45fa3f3a8e50 Mon Sep 17 00:00:00 2001 From: Hugo Montenegro Date: Thu, 14 May 2026 21:04:55 +0100 Subject: [PATCH 2/5] chore: disable maintenance mode post-release The decomplexify cutover migrations + backfill completed successfully at 2026-05-14 19:50 UTC. BE PR #747 + FE PR #1984 merged. Flipping maint off so users come out of the /maintenance redirect. Why this needs a separate PR: PR #1985 (enable maint) was merged into main but never synced to dev. When PR #1984 merged dev -> main, the 3-way merge kept main's true (dev didn't touch the line). --- src/config/underMaintenance.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/underMaintenance.config.ts b/src/config/underMaintenance.config.ts index ba717ab07..34f8643ec 100644 --- a/src/config/underMaintenance.config.ts +++ b/src/config/underMaintenance.config.ts @@ -52,7 +52,7 @@ interface MaintenanceConfig { } const underMaintenanceConfig: MaintenanceConfig = { - enableFullMaintenance: true, // set to true to redirect all pages to /maintenance + enableFullMaintenance: false, // set to true to redirect all pages to /maintenance enableMaintenanceBanner: false, // set to true to show maintenance banner on all pages disabledPaymentProviders: [], // set to ['MANTECA'] to disable Manteca QR payments disableXchainWithdraw: false, // set to true to disable cross-chain withdrawals (only allows USDC on Arbitrum) From df32ee052c88ba89463dd7f1c8548bd488b1a8fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Ram=C3=ADrez?= Date: Thu, 14 May 2026 18:18:00 -0300 Subject: [PATCH 3/5] fix(card): bind session-key approval to user's real wallet address MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pre-2025-09-18 users sit at a legacy V0_0_2-derived smart-account address (migrated in place to V0_0_3 by `createKernelMigrationAccount`, which forces the historical address). Their actual on-chain wallet and USDC live at that address. The grant flow built the session-key kernel via plain `createKernelAccount({plugins: {sudo: newValidator}})` with no address override, so the resulting counterfactual address was the natural V0_0_3-of-new-validator hash — a brand-new, never-funded address that the rebalancer would later try to deploy and transfer from. Simulation reverted with `ERC20: transfer amount exceeds balance` and auto-balance was broken for every pre-migration user. Forcing `address: kernelClient.account!.address` binds the approval to the real wallet for both cohorts (post-migration users already had address == natural counterfactual, so no behavior change there). --- src/hooks/wallet/useGrantSessionKey.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/hooks/wallet/useGrantSessionKey.ts b/src/hooks/wallet/useGrantSessionKey.ts index 9228405b4..a5eb330f2 100644 --- a/src/hooks/wallet/useGrantSessionKey.ts +++ b/src/hooks/wallet/useGrantSessionKey.ts @@ -153,7 +153,14 @@ export const useGrantSessionKey = (): GrantSessionKeyResult => { // produce a passkey prompt. posthog.capture(ANALYTICS_EVENTS.CARD_SESSION_KEY_PROMPTED) // Triggers the passkey prompt — this is the one-time install. + // `address` is forced to the user's actual wallet so the approval + // binds to the deployed kernel. Pre-2025-09-18 users sit at a + // legacy V0_0_2-derived address (migrated in place to V0_0_3); the + // natural counterfactual of `createKernelAccount({sudo: newValidator})` + // is a different, never-funded address. Forcing the address here makes + // the grant work for both legacy and post-migration users. const sessionKernelAccount = await createKernelAccount(peanutPublicClient, { + address: kernelClient.account!.address, entryPoint: getEntryPoint('0.7'), kernelVersion: KERNEL_V3_1, plugins: { From e288397710969aeb9cec969b6c0e7a159737218d Mon Sep 17 00:00:00 2001 From: Hugo Montenegro Date: Thu, 14 May 2026 22:25:25 +0100 Subject: [PATCH 4/5] hotfix(carousel): hide bug-bounty CTA once SUPPORT_SURVIVOR badge earned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The server enforces a lifetime cap of one grant per user. Without this gate the CTA stays visible after a successful claim and the next tap just bounces off 'already_granted' — confusing for the user and noisy for support. --- src/hooks/useHomeCarouselCTAs.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/hooks/useHomeCarouselCTAs.tsx b/src/hooks/useHomeCarouselCTAs.tsx index 1dea3402e..87acbbfa2 100644 --- a/src/hooks/useHomeCarouselCTAs.tsx +++ b/src/hooks/useHomeCarouselCTAs.tsx @@ -109,6 +109,7 @@ export const useHomeCarouselCTAs = () => { [latestHistory] ) const hasSentInvites = (user?.invitesSent?.length ?? 0) > 0 + const hasSupportSurvivorBadge = user?.user?.badges?.some((b) => b.code === 'SUPPORT_SURVIVOR') ?? false const dismissCTA = useCallback( (ctaId: string) => { @@ -261,11 +262,12 @@ export const useHomeCarouselCTAs = () => { }) } - // Bug bounty — shown to activated users only. Server enforces the real - // eligibility (email verified, ≥1 payment OR KYC approved), lifetime - // caps, and the daily budget. This gate just keeps the CTA off cold - // accounts where the reward would be denied anyway. - if (isActivated) { + // Bug bounty — shown to activated users who haven't already claimed. + // Server enforces lifetime cap of 1 grant per user, so re-pinging the + // CTA after a successful claim would just bounce off `already_granted`. + // Hide once the SUPPORT_SURVIVOR badge is on the user — that's the + // server-side dedup marker, so it's the authoritative signal. + if (isActivated && !hasSupportSurvivorBadge) { _carouselCTAs.push({ id: 'bug-bounty', title: ( @@ -329,6 +331,7 @@ export const useHomeCarouselCTAs = () => { isActivated, hasMadeQrPayment, hasSentInvites, + hasSupportSurvivorBadge, oneSignalInitialized, setIsIosPwaInstallModalOpen, toast, From 78d9ccf78099844bd263eca4aeeb903671ef3998 Mon Sep 17 00:00:00 2001 From: Hugo Montenegro Date: Thu, 14 May 2026 22:58:41 +0100 Subject: [PATCH 5/5] fix(history): route SEND_LINK_CLAIM through sendLink strategy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Post-decomplexify the BE splits a single SEND_LINK into two intents — SEND_LINK (sender's deposit) and SEND_LINK_CLAIM (recipient's claim). The registry was never updated to map the new kind, so claimed-link entries fell through to intentFallback, emitting an "unhandled TRANSACTION_INTENT kind" Sentry warn (PEANUT-UI-QCX, 3 events / 2 users since cutover) and rendering as a degraded "Transaction" card. The existing sendLink strategy already handles the RECIPIENT branch correctly (`direction: 'receive'`, sender identifier as nameForDetails), so the fix is a one-line mapping. Baseline snapshot fixture for send_link_claim-peanut-completed-recipient updated — the old expected values captured the broken fallback output, not the correct receive-side rendering. --- .../__tests__/fixtures/render-baseline.json | 20 +++++++++---------- .../TransactionDetails/strategies/registry.ts | 2 ++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/components/TransactionDetails/__tests__/fixtures/render-baseline.json b/src/components/TransactionDetails/__tests__/fixtures/render-baseline.json index b3b2a0014..cc3796610 100644 --- a/src/components/TransactionDetails/__tests__/fixtures/render-baseline.json +++ b/src/components/TransactionDetails/__tests__/fixtures/render-baseline.json @@ -2697,7 +2697,7 @@ "kind": "PERK_REWARD", "txHash": "", "perkReward": { - "reason": "✨ Peanut Mystery Rewards", + "reason": "\u2728 Peanut Mystery Rewards", "discountPercentage": 100, "originatingTxId": null, "originatingTxType": "MANUAL", @@ -3167,9 +3167,9 @@ "price_details": { "rate": 4597916.4, "currency": "ARS", - "base_amount": 0.000026751248, + "base_amount": 2.6751248e-05, "paid_amount": 0, - "final_amount": 0.000026751248, + "final_amount": 2.6751248e-05, "discount_rate": 0, "currency_amount": 123, "currency_final_amount": 123 @@ -3190,9 +3190,9 @@ "price_details": { "rate": 4597916.4, "currency": "ARS", - "base_amount": 0.000026751248, + "base_amount": 2.6751248e-05, "paid_amount": 0, - "final_amount": 0.000026751248, + "final_amount": 2.6751248e-05, "discount_rate": 0, "currency_amount": 123, "currency_final_amount": 123 @@ -3236,9 +3236,9 @@ "price_details": { "rate": 138734817.45, "currency": "ARS", - "base_amount": 8.9e-7, + "base_amount": 8.9e-07, "paid_amount": 0, - "final_amount": 8.9e-7, + "final_amount": 8.9e-07, "discount_rate": 0, "currency_amount": 123, "currency_final_amount": 123 @@ -3540,9 +3540,9 @@ "createdAt": "" }, "expected": { - "direction": "send", - "userName": "", - "transactionCardType": "send", + "direction": "receive", + "userName": "", + "transactionCardType": "receive", "provider": "PEANUT", "cardPaymentDefined": false, "bankAccountDetailsDefined": false diff --git a/src/components/TransactionDetails/strategies/registry.ts b/src/components/TransactionDetails/strategies/registry.ts index 50a57a907..035622ea1 100644 --- a/src/components/TransactionDetails/strategies/registry.ts +++ b/src/components/TransactionDetails/strategies/registry.ts @@ -24,6 +24,7 @@ import { intentFallback } from './fallback' export type IntentKind = | 'DIRECT_TRANSFER' | 'SEND_LINK' + | 'SEND_LINK_CLAIM' | 'P2P_REQUEST_FULFILL' | 'QR_PAY' | 'CRYPTO_DEPOSIT' @@ -39,6 +40,7 @@ const STRATEGIES: Record = { DIRECT_TRANSFER: p2pSendOrRequestFulfill, P2P_REQUEST_FULFILL: p2pSendOrRequestFulfill, SEND_LINK: sendLink, + SEND_LINK_CLAIM: sendLink, QR_PAY: qrPay, CRYPTO_DEPOSIT: cryptoDeposit, CRYPTO_WITHDRAW: cryptoWithdraw,