From f09c7fc3a9bff8fa81afeff5991c310a3a64bb80 Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Thu, 30 Oct 2025 18:38:05 +0530 Subject: [PATCH 1/2] revert balance fix --- src/components/Payment/PaymentForm/index.tsx | 43 +++++++++---------- src/hooks/usePaymentInitiator.ts | 45 ++++++-------------- 2 files changed, 32 insertions(+), 56 deletions(-) diff --git a/src/components/Payment/PaymentForm/index.tsx b/src/components/Payment/PaymentForm/index.tsx index 3d6175b6f..fda2274a2 100644 --- a/src/components/Payment/PaymentForm/index.tsx +++ b/src/components/Payment/PaymentForm/index.tsx @@ -25,9 +25,7 @@ import { useAppDispatch, usePaymentStore } from '@/redux/hooks' import { paymentActions } from '@/redux/slices/payment-slice' import { walletActions } from '@/redux/slices/wallet-slice' import { areEvmAddressesEqual, ErrorHandler, formatAmount, formatCurrency, getContributorsFromCharge } from '@/utils' -import { initializeAppKit } from '@/config/wagmi.config' import { useAppKit, useDisconnect } from '@reown/appkit/react' -import * as Sentry from '@sentry/nextjs' import Image from 'next/image' import { useRouter, useSearchParams } from 'next/navigation' import { useCallback, useContext, useEffect, useMemo, useState } from 'react' @@ -237,8 +235,12 @@ export const PaymentForm = ({ } } else { // regular send/pay - if (isActivePeanutWallet && areEvmAddressesEqual(selectedTokenAddress, PEANUT_WALLET_TOKEN)) { - // peanut wallet payment - ALWAYS check balance (including request pots) + if ( + !showRequestPotInitialView && // don't apply balance check on request pot payment initial view + isActivePeanutWallet && + areEvmAddressesEqual(selectedTokenAddress, PEANUT_WALLET_TOKEN) + ) { + // peanut wallet payment const walletNumeric = parseFloat(String(peanutWalletBalance).replace(/,/g, '')) if (walletNumeric < parsedInputAmount) { dispatch(paymentActions.setError('Insufficient balance')) @@ -365,13 +367,15 @@ export const PaymentForm = ({ } } + console.log(inputTokenAmount, 'inputTokenAmount') + const handleInitiatePayment = useCallback(async () => { // clear invite error if (inviteError) { setInviteError(false) } - // Handle insufficient balance - redirect to add money - if (isActivePeanutWallet && isInsufficientBalanceError && !isExternalWalletFlow) { + // Invites will be handled in the payment page, skip this step for request pots initial view + if (!showRequestPotInitialView && isActivePeanutWallet && isInsufficientBalanceError && !isExternalWalletFlow) { // If the user doesn't have app access, accept the invite before claiming the link if (recipient.recipientType === 'USERNAME' && !user?.user.hasAppAccess) { const isAccepted = await handleAcceptInvite() @@ -383,16 +387,7 @@ export const PaymentForm = ({ // skip this step for request pots initial view if (!showRequestPotInitialView && !isExternalWalletConnected && isExternalWalletFlow) { - try { - await initializeAppKit() - openReownModal() - } catch (error) { - console.error('Failed to initialize AppKit:', error) - Sentry.captureException(error, { - tags: { context: 'payment_form_external_wallet' }, - extra: { flow: 'external_wallet_payment' }, - }) - } + openReownModal() return } @@ -521,7 +516,10 @@ export const PaymentForm = ({ return 'Send' } - // Check insufficient balance BEFORE other conditions + if (showRequestPotInitialView) { + return 'Pay' + } + if (isActivePeanutWallet && isInsufficientBalanceError && !isExternalWalletFlow) { return (
@@ -534,10 +532,6 @@ export const PaymentForm = ({ ) } - if (showRequestPotInitialView) { - return 'Pay' - } - if (isActivePeanutWallet) { return (
@@ -554,11 +548,12 @@ export const PaymentForm = ({ } const getButtonIcon = (): IconName | undefined => { - if (!isExternalWalletConnected && isExternalWalletFlow) return 'wallet-outline' + if (!showRequestPotInitialView && !isExternalWalletConnected && isExternalWalletFlow) return 'wallet-outline' - if (isActivePeanutWallet && isInsufficientBalanceError && !isExternalWalletFlow) return 'arrow-down' + if (!showRequestPotInitialView && isActivePeanutWallet && isInsufficientBalanceError && !isExternalWalletFlow) + return 'arrow-down' - if (!isProcessing && isActivePeanutWallet && !isExternalWalletFlow && !showRequestPotInitialView) + if (!showRequestPotInitialView && !isProcessing && isActivePeanutWallet && !isExternalWalletFlow) return 'arrow-up-right' return undefined diff --git a/src/hooks/usePaymentInitiator.ts b/src/hooks/usePaymentInitiator.ts index 16570630e..b2646ff8a 100644 --- a/src/hooks/usePaymentInitiator.ts +++ b/src/hooks/usePaymentInitiator.ts @@ -1,5 +1,4 @@ import { PEANUT_WALLET_CHAIN, PEANUT_WALLET_TOKEN } from '@/constants' -import { BALANCE_DECREASE, INITIATE_PAYMENT } from '@/constants/query.consts' import { tokenSelectorContext } from '@/context' import { useWallet } from '@/hooks/wallet/useWallet' import { type ParsedURL } from '@/lib/url-parser/types/payment' @@ -27,7 +26,6 @@ import { getRoute, type PeanutCrossChainRoute } from '@/services/swap' import { estimateTransactionCostUsd } from '@/app/actions/tokens' import { captureException } from '@sentry/nextjs' import { useRouter } from 'next/navigation' -import { useQueryClient } from '@tanstack/react-query' enum ELoadingStep { IDLE = 'Idle', @@ -84,7 +82,6 @@ export const usePaymentInitiator = () => { const router = useRouter() const config = useConfig() const { chain: connectedWalletChain } = useWagmiAccount() - const queryClient = useQueryClient() const [slippagePercentage, setSlippagePercentage] = useState(undefined) const [unsignedTx, setUnsignedTx] = useState(null) @@ -605,22 +602,12 @@ export const usePaymentInitiator = () => { ] ) - // @dev Architecture Note: initiatePayment flow and mutation tracking - // - // Current: This async function uses state-based lifecycle (isProcessing, loadingStep) - // rather than TanStack Query mutations. This is INTENTIONAL because: - // 1. It has two phases: charge preparation (no balance change) + payment execution (balance decrease) - // 2. Only the payment execution phase triggers balance-decreasing mutations (via sendMoney/sendTransactions) - // 3. sendMoney already properly wraps mutations with mutationKey: [BALANCE_DECREASE, SEND_MONEY] - // 4. usePendingTransactions tracks ALL balance-decreasing operations globally - // - // Bug fix (2025-10): Added guard at line 648 to prevent premature payment execution when - // fetching existing charges. Previously, fetching an existing charge (chargeCreated=false) - // without skipChargeCreation=true would fall through and trigger sendMoney() prematurely, - // causing optimistic balance updates before user confirmed payment. - // - // Future consideration: Could wrap entire initiatePayment in useMutation, but complexity is HIGH - // due to two-phase flow, Redux integration, and multiple return points. Current architecture works. + // @dev TODO: Refactor to TanStack Query mutation for architectural consistency + // Current: This async function works correctly (protected by isProcessing state) + // but is NOT tracked by usePendingTransactions mutation system. + // Future improvement: Wrap in useMutation for consistency with other balance-decreasing ops. + // mutationKey: [BALANCE_DECREASE, INITIATE_PAYMENT] + // Complexity: HIGH - complex state/Redux integration. Low priority. // // initiate and process payments const initiatePayment = useCallback( @@ -641,25 +628,19 @@ export const usePaymentInitiator = () => { console.log('Proceeding with charge details:', determinedChargeDetails.uuid) // 2. handle charge state - // Return early if: - // a) Explicitly told to return after charge creation (request pot initial view) - // b) Charge was just created AND needs special handling (external wallet/cross-chain) - // c) Fetching existing charge WITHOUT explicit skipChargeCreation (not from CONFIRM view) - const shouldReturnAfterCharge = - payload.returnAfterChargeCreation || + if ( + payload.returnAfterChargeCreation || // For request pot payment, return after charge creation (chargeCreated && (payload.isExternalWalletFlow || !isPeanutWallet || (isPeanutWallet && (!areEvmAddressesEqual(determinedChargeDetails.tokenAddress, PEANUT_WALLET_TOKEN) || - determinedChargeDetails.chainId !== PEANUT_WALLET_CHAIN.id.toString())))) || - // NEW: If charge exists (not created) and we're NOT explicitly skipping charge creation, - // then we're in "prepare" mode and shouldn't execute payment yet - (!chargeCreated && payload.chargeId && !payload.skipChargeCreation) - - if (shouldReturnAfterCharge) { + determinedChargeDetails.chainId !== PEANUT_WALLET_CHAIN.id.toString())))) + ) { console.log( - `Charge ready. Returning without payment execution. (chargeCreated: ${chargeCreated}, skipChargeCreation: ${payload.skipChargeCreation})` + `Charge created. Transitioning to Confirm view for: ${ + payload.isExternalWalletFlow ? 'Add Money Flow' : 'External Wallet' + }.` ) setLoadingStep('Charge Created') return { status: 'Charge Created', charge: determinedChargeDetails, success: false } From d617aa43474a9e2b6a69b6f63d5c13aca14d2c54 Mon Sep 17 00:00:00 2001 From: Zishan Mohd Date: Thu, 30 Oct 2025 18:43:38 +0530 Subject: [PATCH 2/2] feat: initialize AppKit for external wallet flow in PaymentForm and add error handling with Sentry --- src/components/Payment/PaymentForm/index.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/Payment/PaymentForm/index.tsx b/src/components/Payment/PaymentForm/index.tsx index fda2274a2..e9ff34ba2 100644 --- a/src/components/Payment/PaymentForm/index.tsx +++ b/src/components/Payment/PaymentForm/index.tsx @@ -26,6 +26,7 @@ import { paymentActions } from '@/redux/slices/payment-slice' import { walletActions } from '@/redux/slices/wallet-slice' import { areEvmAddressesEqual, ErrorHandler, formatAmount, formatCurrency, getContributorsFromCharge } from '@/utils' import { useAppKit, useDisconnect } from '@reown/appkit/react' +import { initializeAppKit } from '@/config/wagmi.config' import Image from 'next/image' import { useRouter, useSearchParams } from 'next/navigation' import { useCallback, useContext, useEffect, useMemo, useState } from 'react' @@ -39,6 +40,7 @@ import { invitesApi } from '@/services/invites' import { EInviteType } from '@/services/services.types' import ContributorCard from '@/components/Global/Contributors/ContributorCard' import { getCardPosition } from '@/components/Global/Card' +import * as Sentry from '@sentry/nextjs' export type PaymentFlowProps = { isExternalWalletFlow?: boolean @@ -367,8 +369,6 @@ export const PaymentForm = ({ } } - console.log(inputTokenAmount, 'inputTokenAmount') - const handleInitiatePayment = useCallback(async () => { // clear invite error if (inviteError) { @@ -387,8 +387,16 @@ export const PaymentForm = ({ // skip this step for request pots initial view if (!showRequestPotInitialView && !isExternalWalletConnected && isExternalWalletFlow) { - openReownModal() - return + try { + await initializeAppKit() + openReownModal() + } catch (error) { + console.error('Failed to initialize AppKit:', error) + Sentry.captureException(error, { + tags: { context: 'payment_form_external_wallet' }, + extra: { flow: 'external_wallet_payment' }, + }) + } } // skip this step for request pots initial view