thirdweb 5.105.45 → 5.105.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/dist/cjs/react/core/hooks/transaction/useSendTransaction.js +4 -0
  2. package/dist/cjs/react/core/hooks/transaction/useSendTransaction.js.map +1 -1
  3. package/dist/cjs/react/core/hooks/useStepExecutor.js +68 -51
  4. package/dist/cjs/react/core/hooks/useStepExecutor.js.map +1 -1
  5. package/dist/cjs/react/web/ui/Bridge/BridgeOrchestrator.js +1 -2
  6. package/dist/cjs/react/web/ui/Bridge/BridgeOrchestrator.js.map +1 -1
  7. package/dist/cjs/react/web/ui/Bridge/BuyWidget.js +1 -1
  8. package/dist/cjs/react/web/ui/Bridge/BuyWidget.js.map +1 -1
  9. package/dist/cjs/react/web/ui/Bridge/QuoteLoader.js +1 -0
  10. package/dist/cjs/react/web/ui/Bridge/QuoteLoader.js.map +1 -1
  11. package/dist/cjs/react/web/ui/Bridge/payment-details/PaymentDetails.js +2 -1
  12. package/dist/cjs/react/web/ui/Bridge/payment-details/PaymentDetails.js.map +1 -1
  13. package/dist/cjs/react/web/ui/Bridge/payment-selection/PaymentSelection.js +6 -5
  14. package/dist/cjs/react/web/ui/Bridge/payment-selection/PaymentSelection.js.map +1 -1
  15. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/swap/FiatValue.js +1 -0
  16. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/swap/FiatValue.js.map +1 -1
  17. package/dist/cjs/version.js +1 -1
  18. package/dist/cjs/wallets/create-wallet.js +2 -0
  19. package/dist/cjs/wallets/create-wallet.js.map +1 -1
  20. package/dist/cjs/wallets/in-app/core/authentication/siwe.js +5 -3
  21. package/dist/cjs/wallets/in-app/core/authentication/siwe.js.map +1 -1
  22. package/dist/cjs/wallets/in-app/native/native-connector.js +0 -1
  23. package/dist/cjs/wallets/in-app/native/native-connector.js.map +1 -1
  24. package/dist/cjs/wallets/in-app/web/lib/web-connector.js +0 -1
  25. package/dist/cjs/wallets/in-app/web/lib/web-connector.js.map +1 -1
  26. package/dist/cjs/wallets/wallet-connect/controller.js +103 -11
  27. package/dist/cjs/wallets/wallet-connect/controller.js.map +1 -1
  28. package/dist/esm/react/core/hooks/transaction/useSendTransaction.js +4 -0
  29. package/dist/esm/react/core/hooks/transaction/useSendTransaction.js.map +1 -1
  30. package/dist/esm/react/core/hooks/useStepExecutor.js +68 -51
  31. package/dist/esm/react/core/hooks/useStepExecutor.js.map +1 -1
  32. package/dist/esm/react/web/ui/Bridge/BridgeOrchestrator.js +1 -2
  33. package/dist/esm/react/web/ui/Bridge/BridgeOrchestrator.js.map +1 -1
  34. package/dist/esm/react/web/ui/Bridge/BuyWidget.js +1 -1
  35. package/dist/esm/react/web/ui/Bridge/BuyWidget.js.map +1 -1
  36. package/dist/esm/react/web/ui/Bridge/QuoteLoader.js +1 -0
  37. package/dist/esm/react/web/ui/Bridge/QuoteLoader.js.map +1 -1
  38. package/dist/esm/react/web/ui/Bridge/payment-details/PaymentDetails.js +2 -1
  39. package/dist/esm/react/web/ui/Bridge/payment-details/PaymentDetails.js.map +1 -1
  40. package/dist/esm/react/web/ui/Bridge/payment-selection/PaymentSelection.js +6 -5
  41. package/dist/esm/react/web/ui/Bridge/payment-selection/PaymentSelection.js.map +1 -1
  42. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/swap/FiatValue.js +1 -0
  43. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/swap/FiatValue.js.map +1 -1
  44. package/dist/esm/version.js +1 -1
  45. package/dist/esm/wallets/create-wallet.js +2 -0
  46. package/dist/esm/wallets/create-wallet.js.map +1 -1
  47. package/dist/esm/wallets/in-app/core/authentication/siwe.js +5 -3
  48. package/dist/esm/wallets/in-app/core/authentication/siwe.js.map +1 -1
  49. package/dist/esm/wallets/in-app/native/native-connector.js +0 -1
  50. package/dist/esm/wallets/in-app/native/native-connector.js.map +1 -1
  51. package/dist/esm/wallets/in-app/web/lib/web-connector.js +0 -1
  52. package/dist/esm/wallets/in-app/web/lib/web-connector.js.map +1 -1
  53. package/dist/esm/wallets/wallet-connect/controller.js +103 -11
  54. package/dist/esm/wallets/wallet-connect/controller.js.map +1 -1
  55. package/dist/types/react/core/hooks/transaction/useSendTransaction.d.ts.map +1 -1
  56. package/dist/types/react/core/hooks/useStepExecutor.d.ts +1 -1
  57. package/dist/types/react/core/hooks/useStepExecutor.d.ts.map +1 -1
  58. package/dist/types/react/core/machines/paymentMachine.d.ts +1 -1
  59. package/dist/types/react/core/machines/paymentMachine.d.ts.map +1 -1
  60. package/dist/types/react/web/ui/Bridge/BridgeOrchestrator.d.ts.map +1 -1
  61. package/dist/types/react/web/ui/Bridge/BuyWidget.d.ts +4 -0
  62. package/dist/types/react/web/ui/Bridge/BuyWidget.d.ts.map +1 -1
  63. package/dist/types/react/web/ui/Bridge/QuoteLoader.d.ts.map +1 -1
  64. package/dist/types/react/web/ui/Bridge/StepRunner.d.ts +1 -1
  65. package/dist/types/react/web/ui/Bridge/StepRunner.d.ts.map +1 -1
  66. package/dist/types/react/web/ui/Bridge/payment-details/PaymentDetails.d.ts.map +1 -1
  67. package/dist/types/react/web/ui/Bridge/payment-selection/PaymentSelection.d.ts.map +1 -1
  68. package/dist/types/react/web/ui/ConnectWallet/screens/Buy/swap/FiatValue.d.ts.map +1 -1
  69. package/dist/types/version.d.ts +1 -1
  70. package/dist/types/wallets/create-wallet.d.ts.map +1 -1
  71. package/dist/types/wallets/in-app/core/authentication/siwe.d.ts +0 -2
  72. package/dist/types/wallets/in-app/core/authentication/siwe.d.ts.map +1 -1
  73. package/dist/types/wallets/in-app/native/native-connector.d.ts.map +1 -1
  74. package/dist/types/wallets/in-app/web/lib/web-connector.d.ts.map +1 -1
  75. package/dist/types/wallets/wallet-connect/controller.d.ts.map +1 -1
  76. package/package.json +3 -3
  77. package/src/extensions/erc721/read/getNFT.test.ts +1 -1
  78. package/src/react/core/hooks/transaction/useSendTransaction.ts +5 -0
  79. package/src/react/core/hooks/useStepExecutor.ts +88 -71
  80. package/src/react/core/machines/paymentMachine.ts +1 -1
  81. package/src/react/web/ui/Bridge/BridgeOrchestrator.tsx +1 -2
  82. package/src/react/web/ui/Bridge/BuyWidget.tsx +6 -1
  83. package/src/react/web/ui/Bridge/QuoteLoader.tsx +1 -0
  84. package/src/react/web/ui/Bridge/StepRunner.tsx +1 -1
  85. package/src/react/web/ui/Bridge/payment-details/PaymentDetails.tsx +2 -1
  86. package/src/react/web/ui/Bridge/payment-selection/PaymentSelection.tsx +9 -5
  87. package/src/react/web/ui/ConnectWallet/screens/Buy/swap/FiatValue.tsx +1 -0
  88. package/src/version.ts +1 -1
  89. package/src/wallets/create-wallet.ts +2 -0
  90. package/src/wallets/in-app/core/authentication/siwe.ts +5 -5
  91. package/src/wallets/in-app/native/native-connector.ts +0 -1
  92. package/src/wallets/in-app/web/lib/web-connector.test.ts +0 -1
  93. package/src/wallets/in-app/web/lib/web-connector.ts +0 -1
  94. package/src/wallets/wallet-connect/controller.ts +117 -14
@@ -38,7 +38,7 @@ interface StepExecutorOptions {
38
38
  /** Prepared quote returned by Bridge.prepare */
39
39
  request: BridgePrepareRequest;
40
40
  /** Wallet instance providing getAccount() & sendTransaction */
41
- wallet: Wallet;
41
+ wallet?: Wallet;
42
42
  /** Window adapter for opening on-ramp URLs (web / RN) */
43
43
  windowAdapter: WindowAdapter;
44
44
  /** Thirdweb client for API calls */
@@ -408,6 +408,14 @@ export function useStepExecutor(
408
408
  abortControllerRef.current = abortController;
409
409
 
410
410
  try {
411
+ if (flatTxs.length > 0 && !wallet) {
412
+ throw new ApiError({
413
+ code: "INVALID_INPUT",
414
+ message: "No wallet provided to execute transactions",
415
+ statusCode: 400,
416
+ });
417
+ }
418
+
411
419
  // Execute onramp first if configured and not already completed
412
420
  if (preparedQuote.type === "onramp" && onrampStatus === "pending") {
413
421
  await executeOnramp(
@@ -417,89 +425,98 @@ export function useStepExecutor(
417
425
  );
418
426
  }
419
427
 
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;
428
+ if (flatTxs.length > 0) {
429
+ // Then execute transactions
430
+ if (!wallet) {
431
+ throw new ApiError({
432
+ code: "INVALID_INPUT",
433
+ message: "No wallet provided to execute transactions",
434
+ statusCode: 400,
435
+ });
436
436
  }
437
-
438
- const currentTx = flatTxs[i];
439
- if (!currentTx) {
440
- continue; // Skip invalid index
437
+ const account = wallet.getAccount();
438
+ if (!account) {
439
+ throw new ApiError({
440
+ code: "INVALID_INPUT",
441
+ message: "Wallet not connected",
442
+ statusCode: 400,
443
+ });
441
444
  }
442
445
 
443
- setCurrentTxIndex(i);
444
- const currentStepData = preparedQuote.steps[currentTx._stepIndex];
445
- if (!currentStepData) {
446
- throw new Error(`Invalid step index: ${currentTx._stepIndex}`);
447
- }
446
+ // Start from where we left off, or from the beginning
447
+ const startIndex = currentTxIndex ?? 0;
448
448
 
449
- // switch chain if needed
450
- if (currentTx.chainId !== wallet.getChain()?.id) {
451
- await wallet.switchChain(getCachedChain(currentTx.chainId));
452
- }
449
+ for (let i = startIndex; i < flatTxs.length; i++) {
450
+ if (abortController.signal.aborted) {
451
+ break;
452
+ }
453
453
 
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++;
454
+ const currentTx = flatTxs[i];
455
+ if (!currentTx) {
456
+ continue; // Skip invalid index
457
+ }
458
+
459
+ setCurrentTxIndex(i);
460
+ const currentStepData = preparedQuote.steps[currentTx._stepIndex];
461
+ if (!currentStepData) {
462
+ throw new Error(`Invalid step index: ${currentTx._stepIndex}`);
463
+ }
464
+
465
+ // switch chain if needed
466
+ if (currentTx.chainId !== wallet.getChain()?.id) {
467
+ await wallet.switchChain(getCachedChain(currentTx.chainId));
469
468
  }
470
469
 
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));
470
+ // Check if we can batch transactions
471
+ const canBatch =
472
+ account.sendBatchTransaction !== undefined &&
473
+ i < flatTxs.length - 1; // Not the last transaction
474
+
475
+ if (canBatch) {
476
+ // Find consecutive transactions on the same chain
477
+ const batchTxs: FlattenedTx[] = [currentTx];
478
+ let j = i + 1;
479
+ while (j < flatTxs.length) {
480
+ const nextTx = flatTxs[j];
481
+ if (!nextTx || nextTx.chainId !== currentTx.chainId) {
482
+ break;
483
+ }
484
+ batchTxs.push(nextTx);
485
+ j++;
483
486
  }
484
487
 
485
- // Skip ahead
486
- i = j - 1;
487
- continue;
488
+ // Execute batch if we have multiple transactions
489
+ if (batchTxs.length > 1) {
490
+ await executeBatch(
491
+ batchTxs,
492
+ account,
493
+ completedStatusResults,
494
+ abortController.signal,
495
+ );
496
+
497
+ // Mark all batched transactions as completed
498
+ for (const tx of batchTxs) {
499
+ setCompletedTxs((prev) => new Set(prev).add(tx._index));
500
+ }
501
+
502
+ // Skip ahead
503
+ i = j - 1;
504
+ continue;
505
+ }
488
506
  }
489
- }
490
507
 
491
- // Execute single transaction
492
- await executeSingleTx(
493
- currentTx,
494
- account,
495
- completedStatusResults,
496
- abortController.signal,
497
- );
508
+ // Execute single transaction
509
+ await executeSingleTx(
510
+ currentTx,
511
+ account,
512
+ completedStatusResults,
513
+ abortController.signal,
514
+ );
498
515
 
499
- // Mark transaction as completed
500
- setCompletedTxs((prev) => new Set(prev).add(currentTx._index));
516
+ // Mark transaction as completed
517
+ setCompletedTxs((prev) => new Set(prev).add(currentTx._index));
518
+ }
501
519
  }
502
-
503
520
  // All done - check if we actually completed everything
504
521
  if (!abortController.signal.aborted) {
505
522
  setCurrentTxIndex(undefined);
@@ -29,7 +29,7 @@ export type PaymentMethod =
29
29
  }
30
30
  | {
31
31
  type: "fiat";
32
- payerWallet: Wallet;
32
+ payerWallet?: Wallet;
33
33
  currency: string;
34
34
  onramp: "stripe" | "coinbase" | "transak";
35
35
  };
@@ -369,8 +369,7 @@ export function BridgeOrchestrator({
369
369
 
370
370
  {state.value === "execute" &&
371
371
  state.context.quote &&
372
- state.context.request &&
373
- state.context.selectedPaymentMethod?.payerWallet && (
372
+ state.context.request && (
374
373
  <StepRunner
375
374
  autoStart={true}
376
375
  client={client}
@@ -193,6 +193,11 @@ export type BuyWidgetProps = {
193
193
  * Custom label for the main action button.
194
194
  */
195
195
  buttonLabel?: string;
196
+
197
+ /**
198
+ * The receiver address for the purchased funds.
199
+ */
200
+ receiverAddress?: Address;
196
201
  };
197
202
 
198
203
  // Enhanced UIOptions to handle unsupported token state
@@ -455,7 +460,7 @@ export function BuyWidget(props: BuyWidgetProps) {
455
460
  paymentMethods={props.paymentMethods}
456
461
  presetOptions={props.presetOptions}
457
462
  purchaseData={props.purchaseData}
458
- receiverAddress={undefined}
463
+ receiverAddress={props.receiverAddress}
459
464
  uiOptions={bridgeDataQuery.data.data}
460
465
  showThirdwebBranding={props.showThirdwebBranding}
461
466
  />
@@ -133,6 +133,7 @@ export function QuoteLoader({
133
133
  toChainId: destinationToken.chainId,
134
134
  toToken: destinationToken.address,
135
135
  });
136
+ return true;
136
137
  },
137
138
  queryKey: ["loading_quote", paymentMethod.type],
138
139
  });
@@ -30,7 +30,7 @@ interface StepRunnerProps {
30
30
  /**
31
31
  * Wallet instance for executing transactions
32
32
  */
33
- wallet: Wallet;
33
+ wallet?: Wallet;
34
34
 
35
35
  /**
36
36
  * Thirdweb client for API calls
@@ -101,6 +101,7 @@ export function PaymentDetails({
101
101
  : preparedQuote.intent.destinationTokenAddress,
102
102
  });
103
103
  }
104
+ return true;
104
105
  },
105
106
  queryKey: ["payment_details", preparedQuote.type],
106
107
  });
@@ -239,7 +240,7 @@ export function PaymentDetails({
239
240
  receiver={preparedQuote.intent.receiver}
240
241
  sender={
241
242
  preparedQuote.intent.sender ||
242
- paymentMethod.payerWallet.getAccount()?.address
243
+ paymentMethod.payerWallet?.getAccount()?.address
243
244
  }
244
245
  toAmount={displayData.destinationAmount}
245
246
  toToken={displayData.destinationToken}
@@ -196,15 +196,17 @@ export function PaymentSelection({
196
196
  const handleOnrampProviderSelected = (
197
197
  provider: "coinbase" | "stripe" | "transak",
198
198
  ) => {
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"));
201
203
  return;
202
204
  }
203
205
 
204
206
  const fiatPaymentMethod: PaymentMethod = {
205
- currency: "USD",
207
+ currency: currency || "USD",
206
208
  onramp: provider,
207
- payerWallet, // Default to USD for now
209
+ payerWallet,
208
210
  type: "fiat",
209
211
  };
210
212
  handlePaymentMethodSelected(fiatPaymentMethod);
@@ -307,7 +309,9 @@ export function PaymentSelection({
307
309
  country={country}
308
310
  client={client}
309
311
  onProviderSelected={handleOnrampProviderSelected}
310
- toAddress={receiverAddress || ""}
312
+ toAddress={
313
+ receiverAddress || payerWallet?.getAccount()?.address || ""
314
+ }
311
315
  toAmount={destinationAmount}
312
316
  toChainId={destinationToken.chainId}
313
317
  toTokenAddress={destinationToken.address}
@@ -36,6 +36,7 @@ export function FiatValue(
36
36
  props.chain.id,
37
37
  getTokenAddress(props.token),
38
38
  deferredTokenAmount,
39
+ props.currency,
39
40
  ],
40
41
  });
41
42
 
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = "5.105.45";
1
+ export const version = "5.105.47";
@@ -483,6 +483,8 @@ export function createWallet<const ID extends WalletId>(
483
483
  id,
484
484
  subscribe: emitter.subscribe,
485
485
  switchChain: async (c) => {
486
+ // TODO: this should actually throw an error if the chain switch fails
487
+ // but our useSwitchActiveWalletChain hook currently doesn't handle this
486
488
  try {
487
489
  await handleSwitchChain(c);
488
490
  chain = c;
@@ -1,6 +1,6 @@
1
1
  import { signLoginPayload } from "../../../../auth/core/sign-login-payload.js";
2
2
  import type { LoginPayload } from "../../../../auth/core/types.js";
3
- import type { Chain } from "../../../../chains/types.js";
3
+ import { getCachedChain } from "../../../../chains/utils.js";
4
4
  import type { ThirdwebClient } from "../../../../client/client.js";
5
5
  import { getClientFetch } from "../../../../utils/fetch.js";
6
6
  import { stringify } from "../../../../utils/json.js";
@@ -14,14 +14,14 @@ import type { AuthStoredTokenWithCookieReturnType } from "./types.js";
14
14
  */
15
15
  export async function siweAuthenticate(args: {
16
16
  wallet: Wallet;
17
- chain: Chain;
18
17
  client: ThirdwebClient;
19
18
  ecosystem?: Ecosystem;
20
19
  }): Promise<AuthStoredTokenWithCookieReturnType> {
21
- const { wallet, chain, client, ecosystem } = args;
20
+ const { wallet, client, ecosystem } = args;
21
+ const siweChain = getCachedChain(1); // always use mainnet for SIWE for wide wallet compatibility
22
22
  // only connect if the wallet doesn't already have an account
23
23
  const account =
24
- wallet.getAccount() || (await wallet.connect({ chain, client }));
24
+ wallet.getAccount() || (await wallet.connect({ chain: siweChain, client }));
25
25
  const clientFetch = getClientFetch(client, ecosystem);
26
26
 
27
27
  const payload = await (async () => {
@@ -31,7 +31,7 @@ export async function siweAuthenticate(args: {
31
31
  ecosystem: args.ecosystem,
32
32
  });
33
33
  const res = await clientFetch(
34
- `${path}&address=${account.address}&chainId=${chain.id}`,
34
+ `${path}&address=${account.address}&chainId=${siweChain.id}`,
35
35
  );
36
36
 
37
37
  if (!res.ok) throw new Error("Failed to generate SIWE login payload");
@@ -183,7 +183,6 @@ export class InAppNativeConnector implements InAppConnector {
183
183
  "../core/authentication/siwe.js"
184
184
  );
185
185
  return siweAuthenticate({
186
- chain: params.chain,
187
186
  client: this.client,
188
187
  ecosystem: params.ecosystem,
189
188
  wallet: params.wallet,
@@ -104,7 +104,6 @@ describe("InAppWebConnector.connect", () => {
104
104
  });
105
105
 
106
106
  expect(siweAuthenticate).toHaveBeenCalledWith({
107
- chain: ethereum,
108
107
  client: TEST_CLIENT,
109
108
  ecosystem: undefined,
110
109
  wallet: mockWallet,
@@ -365,7 +365,6 @@ export class InAppWebConnector implements InAppConnector {
365
365
  }
366
366
  case "wallet": {
367
367
  return siweAuthenticate({
368
- chain: args.chain,
369
368
  client: this.client,
370
369
  ecosystem: this.ecosystem,
371
370
  wallet: args.wallet,
@@ -132,7 +132,7 @@ export async function connectWC(
132
132
  ...(wcOptions?.pairingTopic
133
133
  ? { pairingTopic: wcOptions?.pairingTopic }
134
134
  : {}),
135
- namespaces: {
135
+ optionalNamespaces: {
136
136
  [NAMESPACE]: {
137
137
  chains: chainsToRequest,
138
138
  events: ["chainChanged", "accountsChanged"],
@@ -157,14 +157,8 @@ export async function connectWC(
157
157
  );
158
158
  const currentChainId = chainsToRequest[0]?.split(":")[1] || 1;
159
159
  const providerChainId = normalizeChainId(currentChainId);
160
- const accounts: string[] = await provider.request(
161
- {
162
- method: "eth_requestAccounts",
163
- params: [],
164
- },
165
- `eip155:${providerChainId}`,
166
- );
167
- const address = accounts[0];
160
+ const account = firstAccountOn(provider.session, `eip155:1`); // grab the address from mainnet
161
+ const address = account;
168
162
  if (!address) {
169
163
  throw new Error("No accounts found on provider.");
170
164
  }
@@ -202,6 +196,109 @@ export async function connectWC(
202
196
  );
203
197
  }
204
198
 
199
+ async function ensureTargetChain(
200
+ provider: Awaited<ReturnType<typeof initProvider>>,
201
+ chain: Chain,
202
+ walletInfo: WalletInfo,
203
+ ) {
204
+ if (!provider.session) {
205
+ throw new Error("No session found on provider.");
206
+ }
207
+ const TARGET_CAIP = `eip155:${chain.id}`;
208
+ const TARGET_HEX = numberToHex(chain.id);
209
+
210
+ // Fast path: already enabled
211
+ if (hasChainEnabled(provider.session, TARGET_CAIP)) {
212
+ provider.setDefaultChain(TARGET_CAIP);
213
+ return;
214
+ }
215
+
216
+ // 1) Try switch
217
+ try {
218
+ await requestAndOpenWallet({
219
+ provider,
220
+ payload: {
221
+ method: "wallet_switchEthereumChain",
222
+ params: [{ chainId: TARGET_HEX }],
223
+ },
224
+ chain: TARGET_CAIP, // route to target
225
+ walletInfo,
226
+ });
227
+ provider.setDefaultChain(TARGET_CAIP);
228
+ return;
229
+ } catch (err: any) {
230
+ const code = err?.code ?? err?.data?.originalError?.code;
231
+ // 4001 user rejected; stop
232
+ if (code === 4001) throw new Error("User rejected chain switch");
233
+ // fall through on 4902 or unknown -> try add
234
+ }
235
+
236
+ // 2) Add the chain via any chain we already have
237
+ const routeChain = anyRoutableChain(provider.session);
238
+ if (!routeChain)
239
+ throw new Error("No routable chain to send wallet_addEthereumChain");
240
+
241
+ try {
242
+ await requestAndOpenWallet({
243
+ provider,
244
+ payload: {
245
+ method: "wallet_addEthereumChain",
246
+ params: [
247
+ {
248
+ chainId: TARGET_HEX,
249
+ chainName: chain.name,
250
+ nativeCurrency: chain.nativeCurrency,
251
+ rpcUrls: [chain.rpc],
252
+ blockExplorerUrls: [chain.blockExplorers?.[0]?.url ?? ""],
253
+ },
254
+ ],
255
+ },
256
+ chain: routeChain, // route via known-good chain, not the target
257
+ walletInfo,
258
+ });
259
+ } catch (err: any) {
260
+ const code = err?.code ?? err?.data?.originalError?.code;
261
+ if (code === 4001) throw new Error("User rejected add chain");
262
+ throw new Error(`Add chain failed: ${err?.message || String(err)}`);
263
+ }
264
+
265
+ // 3) Re-try switch after add
266
+ await requestAndOpenWallet({
267
+ provider,
268
+ payload: {
269
+ method: "wallet_switchEthereumChain",
270
+ params: [{ chainId: TARGET_HEX }],
271
+ },
272
+ chain: TARGET_CAIP,
273
+ walletInfo,
274
+ });
275
+ provider.setDefaultChain(TARGET_CAIP);
276
+
277
+ // 4) Verify enablement
278
+ if (!hasChainEnabled(provider.session, TARGET_CAIP)) {
279
+ throw new Error("Target chain still not enabled by wallet");
280
+ }
281
+ }
282
+
283
+ type WCSession = Awaited<ReturnType<typeof UniversalProvider.init>>["session"];
284
+
285
+ function getNS(session: WCSession) {
286
+ return session?.namespaces?.eip155;
287
+ }
288
+ function hasChainEnabled(session: WCSession, caip: string) {
289
+ const ns = getNS(session);
290
+ return !!ns?.accounts?.some((a) => a.startsWith(`${caip}:`));
291
+ }
292
+ function firstAccountOn(session: WCSession, caip: string): string | null {
293
+ const ns = getNS(session);
294
+ const hit = ns?.accounts?.find((a) => a.startsWith(`${caip}:`));
295
+ return hit ? (hit.split(":")[2] ?? null) : null;
296
+ }
297
+ function anyRoutableChain(session: WCSession): string | null {
298
+ const ns = getNS(session);
299
+ return ns?.accounts?.[0]?.split(":")?.slice(0, 2)?.join(":") ?? null; // e.g. "eip155:1"
300
+ }
301
+
205
302
  /**
206
303
  * Auto connect to already connected wallet connect session.
207
304
  * @internal
@@ -545,14 +642,17 @@ function onConnect(
545
642
  account,
546
643
  chain,
547
644
  disconnect,
548
- (newChain) => switchChainWC(provider, newChain),
645
+ (newChain) => switchChainWC(provider, newChain, walletInfo),
549
646
  ];
550
647
  }
551
648
 
552
- async function switchChainWC(provider: WCProvider, chain: Chain) {
553
- const chainId = chain.id;
649
+ async function switchChainWC(
650
+ provider: WCProvider,
651
+ chain: Chain,
652
+ walletInfo: WalletInfo,
653
+ ) {
554
654
  try {
555
- provider.setDefaultChain(`eip155:${chainId}`);
655
+ await ensureTargetChain(provider, chain, walletInfo);
556
656
  } catch (error) {
557
657
  const message =
558
658
  typeof error === "string" ? error : (error as ProviderRpcError)?.message;
@@ -605,7 +705,10 @@ function getChainsToRequest(options: {
605
705
  chainIds.push(chain.id);
606
706
  }
607
707
 
608
- if (!options.chain && optionalChains.length === 0) {
708
+ // always include mainnet
709
+ // many wallets only support a handful of chains, but mainnet is always supported
710
+ // we will add additional chains in switchChain if needed
711
+ if (!chainIds.includes(1)) {
609
712
  rpcMap[1] = getCachedChain(1).rpc;
610
713
  chainIds.push(1);
611
714
  }