thirdweb 5.96.7 → 5.96.8-nightly-80bf795760052e83dd056cf5ef3ea54723d47775-20250503000343

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 (42) hide show
  1. package/dist/cjs/insight/common.js +7 -2
  2. package/dist/cjs/insight/common.js.map +1 -1
  3. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js +3 -0
  4. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js.map +1 -1
  5. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/swap/TokenSelectorScreen.js +25 -76
  6. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/swap/TokenSelectorScreen.js.map +1 -1
  7. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/swap/fetchBalancesForWallet.js +133 -0
  8. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/swap/fetchBalancesForWallet.js.map +1 -0
  9. package/dist/cjs/react/web/ui/ConnectWallet/screens/nativeToken.js +2 -1
  10. package/dist/cjs/react/web/ui/ConnectWallet/screens/nativeToken.js.map +1 -1
  11. package/dist/cjs/version.js +1 -1
  12. package/dist/cjs/version.js.map +1 -1
  13. package/dist/esm/insight/common.js +6 -2
  14. package/dist/esm/insight/common.js.map +1 -1
  15. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js +4 -1
  16. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js.map +1 -1
  17. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/swap/TokenSelectorScreen.js +25 -76
  18. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/swap/TokenSelectorScreen.js.map +1 -1
  19. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/swap/fetchBalancesForWallet.js +130 -0
  20. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/swap/fetchBalancesForWallet.js.map +1 -0
  21. package/dist/esm/react/web/ui/ConnectWallet/screens/nativeToken.js +3 -2
  22. package/dist/esm/react/web/ui/ConnectWallet/screens/nativeToken.js.map +1 -1
  23. package/dist/esm/version.js +1 -1
  24. package/dist/esm/version.js.map +1 -1
  25. package/dist/types/insight/common.d.ts +1 -0
  26. package/dist/types/insight/common.d.ts.map +1 -1
  27. package/dist/types/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.d.ts.map +1 -1
  28. package/dist/types/react/web/ui/ConnectWallet/screens/Buy/swap/TokenSelectorScreen.d.ts.map +1 -1
  29. package/dist/types/react/web/ui/ConnectWallet/screens/Buy/swap/fetchBalancesForWallet.d.ts +25 -0
  30. package/dist/types/react/web/ui/ConnectWallet/screens/Buy/swap/fetchBalancesForWallet.d.ts.map +1 -0
  31. package/dist/types/react/web/ui/ConnectWallet/screens/nativeToken.d.ts.map +1 -1
  32. package/dist/types/version.d.ts +1 -1
  33. package/dist/types/version.d.ts.map +1 -1
  34. package/package.json +3 -3
  35. package/src/adapters/viem-legacy.test.ts +1 -6
  36. package/src/bridge/Status.test.ts +2 -1
  37. package/src/insight/common.ts +7 -2
  38. package/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx +7 -1
  39. package/src/react/web/ui/ConnectWallet/screens/Buy/swap/TokenSelectorScreen.tsx +30 -96
  40. package/src/react/web/ui/ConnectWallet/screens/Buy/swap/fetchBalancesForWallet.ts +196 -0
  41. package/src/react/web/ui/ConnectWallet/screens/nativeToken.ts +6 -2
  42. package/src/version.ts +1 -1
@@ -4,7 +4,10 @@ import { trackPayEvent } from "../../../../../../analytics/track/pay.js";
4
4
  import type { Chain } from "../../../../../../chains/types.js";
5
5
  import { getCachedChain } from "../../../../../../chains/utils.js";
6
6
  import type { ThirdwebClient } from "../../../../../../client/client.js";
7
- import { NATIVE_TOKEN_ADDRESS } from "../../../../../../constants/addresses.js";
7
+ import {
8
+ NATIVE_TOKEN_ADDRESS,
9
+ ZERO_ADDRESS,
10
+ } from "../../../../../../constants/addresses.js";
8
11
  import type { BuyWithCryptoStatus } from "../../../../../../pay/buyWithCrypto/getStatus.js";
9
12
  import type { BuyWithFiatStatus } from "../../../../../../pay/buyWithFiat/getStatus.js";
10
13
  import { formatNumber } from "../../../../../../utils/formatNumber.js";
@@ -982,6 +985,9 @@ function createSupportedTokens(
982
985
 
983
986
  for (const x of data) {
984
987
  tokens[x.chain.id] = x.tokens.filter((t) => {
988
+ if (t.address === ZERO_ADDRESS) {
989
+ return false;
990
+ }
985
991
  // for source tokens, data is not provided, so we include all of them
986
992
  if (
987
993
  t.buyWithCryptoEnabled === undefined &&
@@ -7,14 +7,8 @@ import {
7
7
  import { useQuery } from "@tanstack/react-query";
8
8
  import { trackPayEvent } from "../../../../../../../analytics/track/pay.js";
9
9
  import type { Chain } from "../../../../../../../chains/types.js";
10
- import { getCachedChain } from "../../../../../../../chains/utils.js";
11
10
  import type { ThirdwebClient } from "../../../../../../../client/client.js";
12
- import { NATIVE_TOKEN_ADDRESS } from "../../../../../../../constants/addresses.js";
13
11
  import type { Wallet } from "../../../../../../../wallets/interfaces/wallet.js";
14
- import {
15
- type GetWalletBalanceResult,
16
- getWalletBalance,
17
- } from "../../../../../../../wallets/utils/getWalletBalance.js";
18
12
  import type { WalletId } from "../../../../../../../wallets/wallet-types.js";
19
13
  import { useCustomTheme } from "../../../../../../core/design-system/CustomThemeProvider.js";
20
14
  import {
@@ -47,12 +41,10 @@ import { formatTokenBalance } from "../../formatTokenBalance.js";
47
41
  import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js";
48
42
  import { FiatValue } from "./FiatValue.js";
49
43
  import { WalletRow } from "./WalletRow.js";
50
-
51
- type TokenBalance = {
52
- balance: GetWalletBalanceResult;
53
- chain: Chain;
54
- token: TokenInfo;
55
- };
44
+ import {
45
+ type TokenBalance,
46
+ fetchBalancesForWallet,
47
+ } from "./fetchBalancesForWallet.js";
56
48
 
57
49
  type WalletKey = {
58
50
  id: WalletId;
@@ -90,93 +82,35 @@ export function TokenSelectorScreen(props: {
90
82
  activeAccount?.address,
91
83
  connectedWallets.map((w) => w.getAccount()?.address),
92
84
  ],
85
+ enabled: !!props.sourceSupportedTokens && !!chainInfo.data,
93
86
  queryFn: async () => {
94
- // in parallel, get the balances of all the wallets on each of the sourceSupportedTokens
95
- const walletBalanceMap = new Map<WalletKey, TokenBalance[]>();
96
-
97
- const balancePromises = connectedWallets.flatMap((wallet) => {
98
- const account = wallet.getAccount();
99
- if (!account) return [];
100
- const walletKey: WalletKey = {
101
- id: wallet.id,
102
- address: account.address,
103
- };
104
- walletBalanceMap.set(walletKey, []);
105
-
106
- // inject the destination token too since it can be used as well to pay/transfer
107
- const toToken = isNativeToken(props.toToken)
108
- ? {
109
- address: NATIVE_TOKEN_ADDRESS,
110
- name: chainInfo.data?.nativeCurrency.name || "",
111
- symbol: chainInfo.data?.nativeCurrency.symbol || "",
112
- icon: chainInfo.data?.icon?.url,
113
- }
114
- : props.toToken;
115
-
116
- const tokens = {
117
- ...props.sourceSupportedTokens,
118
- [props.toChain.id]: [
119
- toToken,
120
- ...(props.sourceSupportedTokens?.[props.toChain.id] || []),
121
- ],
122
- };
123
-
124
- return Object.entries(tokens).flatMap(([chainId, tokens]) => {
125
- return tokens.map(async (token) => {
126
- try {
127
- const chain = getCachedChain(Number(chainId));
128
- const balance = await getWalletBalance({
129
- address: account.address,
130
- chain,
131
- tokenAddress: isNativeToken(token) ? undefined : token.address,
132
- client: props.client,
133
- });
134
-
135
- // show the token if:
136
- // - its not the destination token and balance is greater than 0
137
- // - its the destination token and balance is greater than the token amount AND we the account is not the default account in fund_wallet mode
138
- const shouldInclude =
139
- token.address === toToken.address &&
140
- chain.id === props.toChain.id
141
- ? props.mode === "fund_wallet" &&
142
- account.address === activeAccount?.address
143
- ? false
144
- : Number(balance.displayValue) > Number(props.tokenAmount)
145
- : balance.value > 0n;
146
-
147
- if (shouldInclude) {
148
- const existingBalances = walletBalanceMap.get(walletKey) || [];
149
- existingBalances.push({ balance, chain, token });
150
- existingBalances.sort((a, b) => {
151
- if (
152
- a.chain.id === props.toChain.id &&
153
- a.token.address === toToken.address
154
- )
155
- return -1;
156
- if (
157
- b.chain.id === props.toChain.id &&
158
- b.token.address === toToken.address
159
- )
160
- return 1;
161
- if (a.chain.id === props.toChain.id) return -1;
162
- if (b.chain.id === props.toChain.id) return 1;
163
- return a.chain.id > b.chain.id ? 1 : -1;
164
- });
165
- }
166
- } catch (error) {
167
- console.error(
168
- `Failed to fetch balance for wallet ${wallet.id} on chain ${chainId} for token ${token.symbol}:`,
169
- error,
170
- );
171
- }
87
+ const entries = await Promise.all(
88
+ connectedWallets.map(async (wallet) => {
89
+ const balances = await fetchBalancesForWallet({
90
+ wallet,
91
+ accountAddress: activeAccount?.address,
92
+ sourceSupportedTokens: props.sourceSupportedTokens || [],
93
+ toChain: props.toChain,
94
+ toToken: props.toToken,
95
+ tokenAmount: props.tokenAmount,
96
+ mode: props.mode,
97
+ client: props.client,
172
98
  });
173
- });
174
- });
175
-
176
- await Promise.all(balancePromises);
177
- return walletBalanceMap;
99
+ return [
100
+ {
101
+ id: wallet.id,
102
+ address: wallet.getAccount()?.address || "",
103
+ } as WalletKey,
104
+ balances,
105
+ ] as const;
106
+ }),
107
+ );
108
+ const map = new Map<WalletKey, TokenBalance[]>();
109
+ for (const entry of entries) {
110
+ map.set(entry[0], entry[1]);
111
+ }
112
+ return map;
178
113
  },
179
- enabled: !!props.sourceSupportedTokens && !!chainInfo.data,
180
114
  });
181
115
 
182
116
  if (
@@ -0,0 +1,196 @@
1
+ import type { Chain } from "../../../../../../../chains/types.js";
2
+ import { getCachedChain } from "../../../../../../../chains/utils.js";
3
+ import type { ThirdwebClient } from "../../../../../../../client/client.js";
4
+ import { NATIVE_TOKEN_ADDRESS } from "../../../../../../../constants/addresses.js";
5
+ import { isInsightEnabled } from "../../../../../../../insight/common.js";
6
+ import { getOwnedTokens } from "../../../../../../../insight/get-tokens.js";
7
+ import type { Wallet } from "../../../../../../../wallets/interfaces/wallet.js";
8
+ import {
9
+ type GetWalletBalanceResult,
10
+ getWalletBalance,
11
+ } from "../../../../../../../wallets/utils/getWalletBalance.js";
12
+ import type { PayUIOptions } from "../../../../../../core/hooks/connection/ConnectButtonProps.js";
13
+ import type {
14
+ SupportedTokens,
15
+ TokenInfo,
16
+ } from "../../../../../../core/utils/defaultTokens.js";
17
+ import { type ERC20OrNativeToken, isNativeToken } from "../../nativeToken.js";
18
+
19
+ const CHUNK_SIZE = 5;
20
+
21
+ function chunkChains<T>(chains: T[]): T[][] {
22
+ const chunks: T[][] = [];
23
+ for (let i = 0; i < chains.length; i += CHUNK_SIZE) {
24
+ chunks.push(chains.slice(i, i + CHUNK_SIZE));
25
+ }
26
+ return chunks;
27
+ }
28
+
29
+ type FetchBalancesParams = {
30
+ wallet: Wallet;
31
+ accountAddress: string | undefined;
32
+ sourceSupportedTokens: SupportedTokens;
33
+ toChain: Chain;
34
+ toToken: ERC20OrNativeToken;
35
+ tokenAmount: string;
36
+ mode: PayUIOptions["mode"];
37
+ client: ThirdwebClient;
38
+ };
39
+
40
+ export type TokenBalance = {
41
+ balance: GetWalletBalanceResult;
42
+ chain: Chain;
43
+ token: TokenInfo;
44
+ };
45
+
46
+ export async function fetchBalancesForWallet({
47
+ wallet,
48
+ accountAddress,
49
+ sourceSupportedTokens,
50
+ toChain,
51
+ toToken,
52
+ tokenAmount,
53
+ mode,
54
+ client,
55
+ }: FetchBalancesParams): Promise<TokenBalance[]> {
56
+ const account = wallet.getAccount();
57
+ if (!account) {
58
+ return [];
59
+ }
60
+
61
+ const balances: TokenBalance[] = [];
62
+
63
+ // 1. Resolve all unique chains in the supported token map
64
+ const uniqueChains = Object.keys(sourceSupportedTokens).map((id) =>
65
+ getCachedChain(Number(id)),
66
+ );
67
+
68
+ // 2. Check insight availability once per chain
69
+ const insightSupport = await Promise.all(
70
+ uniqueChains.map(async (c) => ({
71
+ chain: c,
72
+ enabled: await isInsightEnabled(c),
73
+ })),
74
+ );
75
+ const insightEnabledChains = insightSupport
76
+ .filter((c) => c.enabled)
77
+ .map((c) => c.chain);
78
+
79
+ // 3. ERC-20 balances for insight-enabled chains (batched 5 chains / call)
80
+ const insightChunks = chunkChains(insightEnabledChains);
81
+ await Promise.all(
82
+ insightChunks.map(async (chunk) => {
83
+ const owned = await getOwnedTokens({
84
+ ownerAddress: account.address,
85
+ chains: chunk,
86
+ client,
87
+ });
88
+
89
+ for (const b of owned) {
90
+ const matching = sourceSupportedTokens[b.chainId]?.find(
91
+ (t) => t.address.toLowerCase() === b.tokenAddress.toLowerCase(),
92
+ );
93
+ if (matching) {
94
+ balances.push({
95
+ balance: b,
96
+ chain: getCachedChain(b.chainId),
97
+ token: matching,
98
+ });
99
+ }
100
+ }
101
+ }),
102
+ );
103
+
104
+ // 4. Build a token map that also includes the destination token so it can be used to pay
105
+ const destinationToken = isNativeToken(toToken)
106
+ ? {
107
+ address: NATIVE_TOKEN_ADDRESS,
108
+ name: toChain.nativeCurrency?.name || "",
109
+ symbol: toChain.nativeCurrency?.symbol || "",
110
+ icon: toChain.icon?.url,
111
+ }
112
+ : toToken;
113
+
114
+ const tokenMap: Record<number, TokenInfo[]> = {
115
+ ...sourceSupportedTokens,
116
+ [toChain.id]: [
117
+ destinationToken,
118
+ ...(sourceSupportedTokens[toChain.id] || []),
119
+ ],
120
+ };
121
+
122
+ // 5. Fallback RPC balances (native currency & ERC-20 that we couldn't fetch from insight)
123
+ const rpcCalls: Promise<void>[] = [];
124
+
125
+ for (const [chainIdStr, tokens] of Object.entries(tokenMap)) {
126
+ const chainId = Number(chainIdStr);
127
+ const chain = getCachedChain(chainId);
128
+
129
+ for (const token of tokens) {
130
+ const isNative = isNativeToken(token);
131
+ const isAlreadyFetched = balances.some(
132
+ (b) =>
133
+ b.chain.id === chainId &&
134
+ b.token.address.toLowerCase() === token.address.toLowerCase(),
135
+ );
136
+ if (!isNative && !isAlreadyFetched) {
137
+ // ERC20 on insight-enabled chain already handled by insight call
138
+ continue;
139
+ }
140
+ rpcCalls.push(
141
+ (async () => {
142
+ try {
143
+ const balance = await getWalletBalance({
144
+ address: account.address,
145
+ chain,
146
+ tokenAddress: isNative ? undefined : token.address,
147
+ client,
148
+ });
149
+
150
+ const include =
151
+ token.address === destinationToken.address &&
152
+ chain.id === toChain.id
153
+ ? mode === "fund_wallet" && account.address === accountAddress
154
+ ? false
155
+ : Number(balance.displayValue) > Number(tokenAmount)
156
+ : balance.value > 0n;
157
+
158
+ if (include) {
159
+ balances.push({ balance, chain, token });
160
+ }
161
+ } catch (err) {
162
+ console.warn(
163
+ `Failed to fetch balance for ${token.symbol} on chain ${chainId}`,
164
+ err,
165
+ );
166
+ }
167
+ })(),
168
+ );
169
+ }
170
+ }
171
+
172
+ await Promise.all(rpcCalls);
173
+
174
+ // Remove duplicates (same chainId + token address)
175
+ {
176
+ const uniq: Record<string, TokenBalance> = {};
177
+ for (const b of balances) {
178
+ const k = `${b.chain.id}-${b.token.address.toLowerCase()}`;
179
+ if (!uniq[k]) {
180
+ uniq[k] = b;
181
+ }
182
+ }
183
+ balances.splice(0, balances.length, ...Object.values(uniq));
184
+ }
185
+ // 6. Sort so that the destination token always appears first, then tokens on the destination chain, then by chain id
186
+ balances.sort((a, b) => {
187
+ const destAddress = destinationToken.address;
188
+ if (a.chain.id === toChain.id && a.token.address === destAddress) return -1;
189
+ if (b.chain.id === toChain.id && b.token.address === destAddress) return 1;
190
+ if (a.chain.id === toChain.id) return -1;
191
+ if (b.chain.id === toChain.id) return 1;
192
+ return a.chain.id - b.chain.id;
193
+ });
194
+
195
+ return balances;
196
+ }
@@ -1,4 +1,7 @@
1
- import { NATIVE_TOKEN_ADDRESS } from "../../../../../constants/addresses.js";
1
+ import {
2
+ NATIVE_TOKEN_ADDRESS,
3
+ ZERO_ADDRESS,
4
+ } from "../../../../../constants/addresses.js";
2
5
  import { type Address, getAddress } from "../../../../../utils/address.js";
3
6
  import type { TokenInfo } from "../../../../core/utils/defaultTokens.js";
4
7
 
@@ -15,7 +18,8 @@ export function isNativeToken(
15
18
  return (
16
19
  (token &&
17
20
  ("nativeToken" in token ||
18
- token.address?.toLowerCase() === NATIVE_TOKEN_ADDRESS.toLowerCase())) ||
21
+ token.address?.toLowerCase() === NATIVE_TOKEN_ADDRESS.toLowerCase() ||
22
+ token?.address === ZERO_ADDRESS)) ||
19
23
  false
20
24
  );
21
25
  }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = "5.96.7";
1
+ export const version = "5.96.8-nightly-80bf795760052e83dd056cf5ef3ea54723d47775-20250503000343";