From a38e299da120119d4a90c4f7150dceda47f3004a Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Sun, 14 Sep 2025 21:34:59 +1200 Subject: [PATCH] Fix fiat payments with no wallet connected --- .changeset/fuzzy-suits-wear.md | 5 + .../src/react/core/hooks/useStepExecutor.ts | 151 ++++++++++-------- .../src/react/core/machines/paymentMachine.ts | 2 +- .../web/ui/Bridge/BridgeOrchestrator.tsx | 3 +- .../src/react/web/ui/Bridge/BuyWidget.tsx | 7 +- .../src/react/web/ui/Bridge/QuoteLoader.tsx | 1 + .../src/react/web/ui/Bridge/StepRunner.tsx | 2 +- .../Bridge/payment-details/PaymentDetails.tsx | 3 +- .../payment-selection/PaymentSelection.tsx | 14 +- .../screens/Buy/swap/FiatValue.tsx | 1 + 10 files changed, 107 insertions(+), 82 deletions(-) create mode 100644 .changeset/fuzzy-suits-wear.md diff --git a/.changeset/fuzzy-suits-wear.md b/.changeset/fuzzy-suits-wear.md new file mode 100644 index 00000000000..572eef3e508 --- /dev/null +++ b/.changeset/fuzzy-suits-wear.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Fix fiat payments with no wallets connected diff --git a/packages/thirdweb/src/react/core/hooks/useStepExecutor.ts b/packages/thirdweb/src/react/core/hooks/useStepExecutor.ts index 7bb59520f0f..0163da2e62c 100644 --- a/packages/thirdweb/src/react/core/hooks/useStepExecutor.ts +++ b/packages/thirdweb/src/react/core/hooks/useStepExecutor.ts @@ -38,7 +38,7 @@ interface StepExecutorOptions { /** Prepared quote returned by Bridge.prepare */ request: BridgePrepareRequest; /** Wallet instance providing getAccount() & sendTransaction */ - wallet: Wallet; + wallet?: Wallet; /** Window adapter for opening on-ramp URLs (web / RN) */ windowAdapter: WindowAdapter; /** Thirdweb client for API calls */ @@ -417,89 +417,98 @@ export function useStepExecutor( ); } - // Then execute transactions - const account = wallet.getAccount(); - if (!account) { - throw new ApiError({ - code: "INVALID_INPUT", - message: "Wallet not connected", - statusCode: 400, - }); - } - - // Start from where we left off, or from the beginning - const startIndex = currentTxIndex ?? 0; - - for (let i = startIndex; i < flatTxs.length; i++) { - if (abortController.signal.aborted) { - break; + if (flatTxs.length > 0) { + // Then execute transactions + if (!wallet) { + throw new ApiError({ + code: "INVALID_INPUT", + message: "No wallet provided to execute transactions", + statusCode: 400, + }); } - - const currentTx = flatTxs[i]; - if (!currentTx) { - continue; // Skip invalid index + const account = wallet.getAccount(); + if (!account) { + throw new ApiError({ + code: "INVALID_INPUT", + message: "Wallet not connected", + statusCode: 400, + }); } - setCurrentTxIndex(i); - const currentStepData = preparedQuote.steps[currentTx._stepIndex]; - if (!currentStepData) { - throw new Error(`Invalid step index: ${currentTx._stepIndex}`); - } + // Start from where we left off, or from the beginning + const startIndex = currentTxIndex ?? 0; - // switch chain if needed - if (currentTx.chainId !== wallet.getChain()?.id) { - await wallet.switchChain(getCachedChain(currentTx.chainId)); - } + for (let i = startIndex; i < flatTxs.length; i++) { + if (abortController.signal.aborted) { + break; + } - // Check if we can batch transactions - const canBatch = - account.sendBatchTransaction !== undefined && i < flatTxs.length - 1; // Not the last transaction - - if (canBatch) { - // Find consecutive transactions on the same chain - const batchTxs: FlattenedTx[] = [currentTx]; - let j = i + 1; - while (j < flatTxs.length) { - const nextTx = flatTxs[j]; - if (!nextTx || nextTx.chainId !== currentTx.chainId) { - break; - } - batchTxs.push(nextTx); - j++; + const currentTx = flatTxs[i]; + if (!currentTx) { + continue; // Skip invalid index } - // Execute batch if we have multiple transactions - if (batchTxs.length > 1) { - await executeBatch( - batchTxs, - account, - completedStatusResults, - abortController.signal, - ); - - // Mark all batched transactions as completed - for (const tx of batchTxs) { - setCompletedTxs((prev) => new Set(prev).add(tx._index)); + setCurrentTxIndex(i); + const currentStepData = preparedQuote.steps[currentTx._stepIndex]; + if (!currentStepData) { + throw new Error(`Invalid step index: ${currentTx._stepIndex}`); + } + + // switch chain if needed + if (currentTx.chainId !== wallet.getChain()?.id) { + await wallet.switchChain(getCachedChain(currentTx.chainId)); + } + + // Check if we can batch transactions + const canBatch = + account.sendBatchTransaction !== undefined && + i < flatTxs.length - 1; // Not the last transaction + + if (canBatch) { + // Find consecutive transactions on the same chain + const batchTxs: FlattenedTx[] = [currentTx]; + let j = i + 1; + while (j < flatTxs.length) { + const nextTx = flatTxs[j]; + if (!nextTx || nextTx.chainId !== currentTx.chainId) { + break; + } + batchTxs.push(nextTx); + j++; } - // Skip ahead - i = j - 1; - continue; + // Execute batch if we have multiple transactions + if (batchTxs.length > 1) { + await executeBatch( + batchTxs, + account, + completedStatusResults, + abortController.signal, + ); + + // Mark all batched transactions as completed + for (const tx of batchTxs) { + setCompletedTxs((prev) => new Set(prev).add(tx._index)); + } + + // Skip ahead + i = j - 1; + continue; + } } - } - // Execute single transaction - await executeSingleTx( - currentTx, - account, - completedStatusResults, - abortController.signal, - ); + // Execute single transaction + await executeSingleTx( + currentTx, + account, + completedStatusResults, + abortController.signal, + ); - // Mark transaction as completed - setCompletedTxs((prev) => new Set(prev).add(currentTx._index)); + // Mark transaction as completed + setCompletedTxs((prev) => new Set(prev).add(currentTx._index)); + } } - // All done - check if we actually completed everything if (!abortController.signal.aborted) { setCurrentTxIndex(undefined); diff --git a/packages/thirdweb/src/react/core/machines/paymentMachine.ts b/packages/thirdweb/src/react/core/machines/paymentMachine.ts index b3633459125..624cef48f0f 100644 --- a/packages/thirdweb/src/react/core/machines/paymentMachine.ts +++ b/packages/thirdweb/src/react/core/machines/paymentMachine.ts @@ -29,7 +29,7 @@ export type PaymentMethod = } | { type: "fiat"; - payerWallet: Wallet; + payerWallet?: Wallet; currency: string; onramp: "stripe" | "coinbase" | "transak"; }; diff --git a/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx b/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx index 9f25370345e..aba0fe404d5 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx @@ -369,8 +369,7 @@ export function BridgeOrchestrator({ {state.value === "execute" && state.context.quote && - state.context.request && - state.context.selectedPaymentMethod?.payerWallet && ( + state.context.request && ( diff --git a/packages/thirdweb/src/react/web/ui/Bridge/QuoteLoader.tsx b/packages/thirdweb/src/react/web/ui/Bridge/QuoteLoader.tsx index 8f740fd36f7..6186c9526dc 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/QuoteLoader.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/QuoteLoader.tsx @@ -133,6 +133,7 @@ export function QuoteLoader({ toChainId: destinationToken.chainId, toToken: destinationToken.address, }); + return true; }, queryKey: ["loading_quote", paymentMethod.type], }); diff --git a/packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx b/packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx index c0c5266271a..fa8a9c6c8bb 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx @@ -30,7 +30,7 @@ interface StepRunnerProps { /** * Wallet instance for executing transactions */ - wallet: Wallet; + wallet?: Wallet; /** * Thirdweb client for API calls diff --git a/packages/thirdweb/src/react/web/ui/Bridge/payment-details/PaymentDetails.tsx b/packages/thirdweb/src/react/web/ui/Bridge/payment-details/PaymentDetails.tsx index 008495f6f85..1ae362cafcc 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/payment-details/PaymentDetails.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/payment-details/PaymentDetails.tsx @@ -101,6 +101,7 @@ export function PaymentDetails({ : preparedQuote.intent.destinationTokenAddress, }); } + return true; }, queryKey: ["payment_details", preparedQuote.type], }); @@ -239,7 +240,7 @@ export function PaymentDetails({ receiver={preparedQuote.intent.receiver} sender={ preparedQuote.intent.sender || - paymentMethod.payerWallet.getAccount()?.address + paymentMethod.payerWallet?.getAccount()?.address } toAmount={displayData.destinationAmount} toToken={displayData.destinationToken} diff --git a/packages/thirdweb/src/react/web/ui/Bridge/payment-selection/PaymentSelection.tsx b/packages/thirdweb/src/react/web/ui/Bridge/payment-selection/PaymentSelection.tsx index 1dcf9c302db..37e420659f7 100644 --- a/packages/thirdweb/src/react/web/ui/Bridge/payment-selection/PaymentSelection.tsx +++ b/packages/thirdweb/src/react/web/ui/Bridge/payment-selection/PaymentSelection.tsx @@ -196,15 +196,17 @@ export function PaymentSelection({ const handleOnrampProviderSelected = ( provider: "coinbase" | "stripe" | "transak", ) => { - if (!payerWallet) { - onError(new Error("No wallet available for fiat payment")); + const recipientAddress = + receiverAddress || payerWallet?.getAccount()?.address; + if (!recipientAddress) { + onError(new Error("No recipient address available for fiat payment")); return; } const fiatPaymentMethod: PaymentMethod = { - currency: "USD", + currency: currency || "USD", onramp: provider, - payerWallet, // Default to USD for now + payerWallet, type: "fiat", }; handlePaymentMethodSelected(fiatPaymentMethod); @@ -307,7 +309,9 @@ export function PaymentSelection({ country={country} client={client} onProviderSelected={handleOnrampProviderSelected} - toAddress={receiverAddress || ""} + toAddress={ + receiverAddress || payerWallet?.getAccount()?.address || "" + } toAmount={destinationAmount} toChainId={destinationToken.chainId} toTokenAddress={destinationToken.address} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/FiatValue.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/FiatValue.tsx index 721eb7999dc..23fdb24f664 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/FiatValue.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/FiatValue.tsx @@ -36,6 +36,7 @@ export function FiatValue( props.chain.id, getTokenAddress(props.token), deferredTokenAmount, + props.currency, ], });