Skip to content

Commit 97e5080

Browse files
[SDK] Fix fiat payments with no wallet connected (#8048)
1 parent 9f9fadf commit 97e5080

File tree

10 files changed

+107
-82
lines changed

10 files changed

+107
-82
lines changed

.changeset/fuzzy-suits-wear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Fix fiat payments with no wallets connected

packages/thirdweb/src/react/core/hooks/useStepExecutor.ts

Lines changed: 80 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ interface StepExecutorOptions {
3838
/** Prepared quote returned by Bridge.prepare */
3939
request: BridgePrepareRequest;
4040
/** Wallet instance providing getAccount() & sendTransaction */
41-
wallet: Wallet;
41+
wallet?: Wallet;
4242
/** Window adapter for opening on-ramp URLs (web / RN) */
4343
windowAdapter: WindowAdapter;
4444
/** Thirdweb client for API calls */
@@ -417,89 +417,98 @@ export function useStepExecutor(
417417
);
418418
}
419419

420-
// Then execute transactions
421-
const account = wallet.getAccount();
422-
if (!account) {
423-
throw new ApiError({
424-
code: "INVALID_INPUT",
425-
message: "Wallet not connected",
426-
statusCode: 400,
427-
});
428-
}
429-
430-
// Start from where we left off, or from the beginning
431-
const startIndex = currentTxIndex ?? 0;
432-
433-
for (let i = startIndex; i < flatTxs.length; i++) {
434-
if (abortController.signal.aborted) {
435-
break;
420+
if (flatTxs.length > 0) {
421+
// Then execute transactions
422+
if (!wallet) {
423+
throw new ApiError({
424+
code: "INVALID_INPUT",
425+
message: "No wallet provided to execute transactions",
426+
statusCode: 400,
427+
});
436428
}
437-
438-
const currentTx = flatTxs[i];
439-
if (!currentTx) {
440-
continue; // Skip invalid index
429+
const account = wallet.getAccount();
430+
if (!account) {
431+
throw new ApiError({
432+
code: "INVALID_INPUT",
433+
message: "Wallet not connected",
434+
statusCode: 400,
435+
});
441436
}
442437

443-
setCurrentTxIndex(i);
444-
const currentStepData = preparedQuote.steps[currentTx._stepIndex];
445-
if (!currentStepData) {
446-
throw new Error(`Invalid step index: ${currentTx._stepIndex}`);
447-
}
438+
// Start from where we left off, or from the beginning
439+
const startIndex = currentTxIndex ?? 0;
448440

449-
// switch chain if needed
450-
if (currentTx.chainId !== wallet.getChain()?.id) {
451-
await wallet.switchChain(getCachedChain(currentTx.chainId));
452-
}
441+
for (let i = startIndex; i < flatTxs.length; i++) {
442+
if (abortController.signal.aborted) {
443+
break;
444+
}
453445

454-
// Check if we can batch transactions
455-
const canBatch =
456-
account.sendBatchTransaction !== undefined && i < flatTxs.length - 1; // Not the last transaction
457-
458-
if (canBatch) {
459-
// Find consecutive transactions on the same chain
460-
const batchTxs: FlattenedTx[] = [currentTx];
461-
let j = i + 1;
462-
while (j < flatTxs.length) {
463-
const nextTx = flatTxs[j];
464-
if (!nextTx || nextTx.chainId !== currentTx.chainId) {
465-
break;
466-
}
467-
batchTxs.push(nextTx);
468-
j++;
446+
const currentTx = flatTxs[i];
447+
if (!currentTx) {
448+
continue; // Skip invalid index
469449
}
470450

471-
// Execute batch if we have multiple transactions
472-
if (batchTxs.length > 1) {
473-
await executeBatch(
474-
batchTxs,
475-
account,
476-
completedStatusResults,
477-
abortController.signal,
478-
);
479-
480-
// Mark all batched transactions as completed
481-
for (const tx of batchTxs) {
482-
setCompletedTxs((prev) => new Set(prev).add(tx._index));
451+
setCurrentTxIndex(i);
452+
const currentStepData = preparedQuote.steps[currentTx._stepIndex];
453+
if (!currentStepData) {
454+
throw new Error(`Invalid step index: ${currentTx._stepIndex}`);
455+
}
456+
457+
// switch chain if needed
458+
if (currentTx.chainId !== wallet.getChain()?.id) {
459+
await wallet.switchChain(getCachedChain(currentTx.chainId));
460+
}
461+
462+
// Check if we can batch transactions
463+
const canBatch =
464+
account.sendBatchTransaction !== undefined &&
465+
i < flatTxs.length - 1; // Not the last transaction
466+
467+
if (canBatch) {
468+
// Find consecutive transactions on the same chain
469+
const batchTxs: FlattenedTx[] = [currentTx];
470+
let j = i + 1;
471+
while (j < flatTxs.length) {
472+
const nextTx = flatTxs[j];
473+
if (!nextTx || nextTx.chainId !== currentTx.chainId) {
474+
break;
475+
}
476+
batchTxs.push(nextTx);
477+
j++;
483478
}
484479

485-
// Skip ahead
486-
i = j - 1;
487-
continue;
480+
// Execute batch if we have multiple transactions
481+
if (batchTxs.length > 1) {
482+
await executeBatch(
483+
batchTxs,
484+
account,
485+
completedStatusResults,
486+
abortController.signal,
487+
);
488+
489+
// Mark all batched transactions as completed
490+
for (const tx of batchTxs) {
491+
setCompletedTxs((prev) => new Set(prev).add(tx._index));
492+
}
493+
494+
// Skip ahead
495+
i = j - 1;
496+
continue;
497+
}
488498
}
489-
}
490499

491-
// Execute single transaction
492-
await executeSingleTx(
493-
currentTx,
494-
account,
495-
completedStatusResults,
496-
abortController.signal,
497-
);
500+
// Execute single transaction
501+
await executeSingleTx(
502+
currentTx,
503+
account,
504+
completedStatusResults,
505+
abortController.signal,
506+
);
498507

499-
// Mark transaction as completed
500-
setCompletedTxs((prev) => new Set(prev).add(currentTx._index));
508+
// Mark transaction as completed
509+
setCompletedTxs((prev) => new Set(prev).add(currentTx._index));
510+
}
501511
}
502-
503512
// All done - check if we actually completed everything
504513
if (!abortController.signal.aborted) {
505514
setCurrentTxIndex(undefined);

packages/thirdweb/src/react/core/machines/paymentMachine.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export type PaymentMethod =
2929
}
3030
| {
3131
type: "fiat";
32-
payerWallet: Wallet;
32+
payerWallet?: Wallet;
3333
currency: string;
3434
onramp: "stripe" | "coinbase" | "transak";
3535
};

packages/thirdweb/src/react/web/ui/Bridge/BridgeOrchestrator.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,8 +369,7 @@ export function BridgeOrchestrator({
369369

370370
{state.value === "execute" &&
371371
state.context.quote &&
372-
state.context.request &&
373-
state.context.selectedPaymentMethod?.payerWallet && (
372+
state.context.request && (
374373
<StepRunner
375374
autoStart={true}
376375
client={client}

packages/thirdweb/src/react/web/ui/Bridge/BuyWidget.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,11 @@ export type BuyWidgetProps = {
193193
* Custom label for the main action button.
194194
*/
195195
buttonLabel?: string;
196+
197+
/**
198+
* The receiver address for the purchased funds.
199+
*/
200+
receiverAddress?: Address;
196201
};
197202

198203
// Enhanced UIOptions to handle unsupported token state
@@ -455,7 +460,7 @@ export function BuyWidget(props: BuyWidgetProps) {
455460
paymentMethods={props.paymentMethods}
456461
presetOptions={props.presetOptions}
457462
purchaseData={props.purchaseData}
458-
receiverAddress={undefined}
463+
receiverAddress={props.receiverAddress}
459464
uiOptions={bridgeDataQuery.data.data}
460465
showThirdwebBranding={props.showThirdwebBranding}
461466
/>

packages/thirdweb/src/react/web/ui/Bridge/QuoteLoader.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export function QuoteLoader({
133133
toChainId: destinationToken.chainId,
134134
toToken: destinationToken.address,
135135
});
136+
return true;
136137
},
137138
queryKey: ["loading_quote", paymentMethod.type],
138139
});

packages/thirdweb/src/react/web/ui/Bridge/StepRunner.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ interface StepRunnerProps {
3030
/**
3131
* Wallet instance for executing transactions
3232
*/
33-
wallet: Wallet;
33+
wallet?: Wallet;
3434

3535
/**
3636
* Thirdweb client for API calls

packages/thirdweb/src/react/web/ui/Bridge/payment-details/PaymentDetails.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export function PaymentDetails({
101101
: preparedQuote.intent.destinationTokenAddress,
102102
});
103103
}
104+
return true;
104105
},
105106
queryKey: ["payment_details", preparedQuote.type],
106107
});
@@ -239,7 +240,7 @@ export function PaymentDetails({
239240
receiver={preparedQuote.intent.receiver}
240241
sender={
241242
preparedQuote.intent.sender ||
242-
paymentMethod.payerWallet.getAccount()?.address
243+
paymentMethod.payerWallet?.getAccount()?.address
243244
}
244245
toAmount={displayData.destinationAmount}
245246
toToken={displayData.destinationToken}

packages/thirdweb/src/react/web/ui/Bridge/payment-selection/PaymentSelection.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -196,15 +196,17 @@ export function PaymentSelection({
196196
const handleOnrampProviderSelected = (
197197
provider: "coinbase" | "stripe" | "transak",
198198
) => {
199-
if (!payerWallet) {
200-
onError(new Error("No wallet available for fiat payment"));
199+
const recipientAddress =
200+
receiverAddress || payerWallet?.getAccount()?.address;
201+
if (!recipientAddress) {
202+
onError(new Error("No recipient address available for fiat payment"));
201203
return;
202204
}
203205

204206
const fiatPaymentMethod: PaymentMethod = {
205-
currency: "USD",
207+
currency: currency || "USD",
206208
onramp: provider,
207-
payerWallet, // Default to USD for now
209+
payerWallet,
208210
type: "fiat",
209211
};
210212
handlePaymentMethodSelected(fiatPaymentMethod);
@@ -307,7 +309,9 @@ export function PaymentSelection({
307309
country={country}
308310
client={client}
309311
onProviderSelected={handleOnrampProviderSelected}
310-
toAddress={receiverAddress || ""}
312+
toAddress={
313+
receiverAddress || payerWallet?.getAccount()?.address || ""
314+
}
311315
toAmount={destinationAmount}
312316
toChainId={destinationToken.chainId}
313317
toTokenAddress={destinationToken.address}

packages/thirdweb/src/react/web/ui/ConnectWallet/screens/Buy/swap/FiatValue.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export function FiatValue(
3636
props.chain.id,
3737
getTokenAddress(props.token),
3838
deferredTokenAmount,
39+
props.currency,
3940
],
4041
});
4142

0 commit comments

Comments
 (0)