thirdweb 5.52.0-nightly-3a32b11a80cb28f22575219ab8f80e703793ec10-20240906000338 → 5.52.0

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 (147) hide show
  1. package/dist/cjs/exports/react.js +4 -1
  2. package/dist/cjs/exports/react.js.map +1 -1
  3. package/dist/cjs/exports/social.js +6 -0
  4. package/dist/cjs/exports/social.js.map +1 -0
  5. package/dist/cjs/react/core/hooks/transaction/useSendTransaction.js.map +1 -1
  6. package/dist/cjs/react/core/providers/thirdweb-provider.js +2 -0
  7. package/dist/cjs/react/core/providers/thirdweb-provider.js.map +1 -1
  8. package/dist/cjs/react/core/social/useSocialProfiles.js +37 -0
  9. package/dist/cjs/react/core/social/useSocialProfiles.js.map +1 -0
  10. package/dist/cjs/react/core/utils/structuralSharing.js +54 -0
  11. package/dist/cjs/react/core/utils/structuralSharing.js.map +1 -0
  12. package/dist/cjs/react/core/utils/wallet.js +24 -0
  13. package/dist/cjs/react/core/utils/wallet.js.map +1 -1
  14. package/dist/cjs/react/native/ui/components/WalletImage.js +3 -3
  15. package/dist/cjs/react/native/ui/components/WalletImage.js.map +1 -1
  16. package/dist/cjs/react/native/ui/connect/ConnectedButton.js +3 -3
  17. package/dist/cjs/react/native/ui/connect/ConnectedButton.js.map +1 -1
  18. package/dist/cjs/react/native/ui/connect/ConnectedModal.js +2 -2
  19. package/dist/cjs/react/native/ui/connect/ConnectedModal.js.map +1 -1
  20. package/dist/cjs/react/web/ui/ConnectWallet/ConnectButton.js +0 -3
  21. package/dist/cjs/react/web/ui/ConnectWallet/ConnectButton.js.map +1 -1
  22. package/dist/cjs/react/web/ui/ConnectWallet/Details.js +9 -6
  23. package/dist/cjs/react/web/ui/ConnectWallet/Details.js.map +1 -1
  24. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js +4 -1
  25. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js.map +1 -1
  26. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.js +2 -1
  27. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.js.map +1 -1
  28. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.js +4 -4
  29. package/dist/cjs/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.js.map +1 -1
  30. package/dist/cjs/social/profiles.js +38 -0
  31. package/dist/cjs/social/profiles.js.map +1 -0
  32. package/dist/cjs/social/types.js +3 -0
  33. package/dist/cjs/social/types.js.map +1 -0
  34. package/dist/cjs/utils/domains.js +3 -0
  35. package/dist/cjs/utils/domains.js.map +1 -1
  36. package/dist/cjs/utils/function-id.js +3 -5
  37. package/dist/cjs/utils/function-id.js.map +1 -1
  38. package/dist/cjs/utils/ipfs.js +19 -10
  39. package/dist/cjs/utils/ipfs.js.map +1 -1
  40. package/dist/cjs/version.js +1 -1
  41. package/dist/cjs/version.js.map +1 -1
  42. package/dist/cjs/wallets/smart/lib/userop.js +2 -1
  43. package/dist/cjs/wallets/smart/lib/userop.js.map +1 -1
  44. package/dist/esm/exports/react.js +2 -0
  45. package/dist/esm/exports/react.js.map +1 -1
  46. package/dist/esm/exports/social.js +3 -0
  47. package/dist/esm/exports/social.js.map +1 -0
  48. package/dist/esm/react/core/hooks/transaction/useSendTransaction.js.map +1 -1
  49. package/dist/esm/react/core/providers/thirdweb-provider.js +2 -0
  50. package/dist/esm/react/core/providers/thirdweb-provider.js.map +1 -1
  51. package/dist/esm/react/core/social/useSocialProfiles.js +34 -0
  52. package/dist/esm/react/core/social/useSocialProfiles.js.map +1 -0
  53. package/dist/esm/react/core/utils/structuralSharing.js +50 -0
  54. package/dist/esm/react/core/utils/structuralSharing.js.map +1 -0
  55. package/dist/esm/react/core/utils/wallet.js +24 -0
  56. package/dist/esm/react/core/utils/wallet.js.map +1 -1
  57. package/dist/esm/react/native/ui/components/WalletImage.js +3 -3
  58. package/dist/esm/react/native/ui/components/WalletImage.js.map +1 -1
  59. package/dist/esm/react/native/ui/connect/ConnectedButton.js +3 -3
  60. package/dist/esm/react/native/ui/connect/ConnectedButton.js.map +1 -1
  61. package/dist/esm/react/native/ui/connect/ConnectedModal.js +2 -2
  62. package/dist/esm/react/native/ui/connect/ConnectedModal.js.map +1 -1
  63. package/dist/esm/react/web/ui/ConnectWallet/ConnectButton.js +0 -3
  64. package/dist/esm/react/web/ui/ConnectWallet/ConnectButton.js.map +1 -1
  65. package/dist/esm/react/web/ui/ConnectWallet/Details.js +9 -6
  66. package/dist/esm/react/web/ui/ConnectWallet/Details.js.map +1 -1
  67. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js +4 -1
  68. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.js.map +1 -1
  69. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.js +2 -1
  70. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.js.map +1 -1
  71. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.js +4 -4
  72. package/dist/esm/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.js.map +1 -1
  73. package/dist/esm/social/profiles.js +35 -0
  74. package/dist/esm/social/profiles.js.map +1 -0
  75. package/dist/esm/social/types.js +2 -0
  76. package/dist/esm/social/types.js.map +1 -0
  77. package/dist/esm/utils/domains.js +3 -0
  78. package/dist/esm/utils/domains.js.map +1 -1
  79. package/dist/esm/utils/function-id.js +3 -5
  80. package/dist/esm/utils/function-id.js.map +1 -1
  81. package/dist/esm/utils/ipfs.js +19 -10
  82. package/dist/esm/utils/ipfs.js.map +1 -1
  83. package/dist/esm/version.js +1 -1
  84. package/dist/esm/version.js.map +1 -1
  85. package/dist/esm/wallets/smart/lib/userop.js +2 -1
  86. package/dist/esm/wallets/smart/lib/userop.js.map +1 -1
  87. package/dist/types/exports/react.d.ts +1 -0
  88. package/dist/types/exports/react.d.ts.map +1 -1
  89. package/dist/types/exports/social.d.ts +3 -0
  90. package/dist/types/exports/social.d.ts.map +1 -0
  91. package/dist/types/react/core/hooks/connection/ConnectButtonProps.d.ts +1 -0
  92. package/dist/types/react/core/hooks/connection/ConnectButtonProps.d.ts.map +1 -1
  93. package/dist/types/react/core/hooks/transaction/useSendTransaction.d.ts +3 -1
  94. package/dist/types/react/core/hooks/transaction/useSendTransaction.d.ts.map +1 -1
  95. package/dist/types/react/core/providers/thirdweb-provider.d.ts.map +1 -1
  96. package/dist/types/react/core/social/useSocialProfiles.d.ts +23 -0
  97. package/dist/types/react/core/social/useSocialProfiles.d.ts.map +1 -0
  98. package/dist/types/react/core/utils/structuralSharing.d.ts +4 -0
  99. package/dist/types/react/core/utils/structuralSharing.d.ts.map +1 -0
  100. package/dist/types/react/core/utils/wallet.d.ts +3 -0
  101. package/dist/types/react/core/utils/wallet.d.ts.map +1 -1
  102. package/dist/types/react/native/ui/components/WalletImage.d.ts +1 -1
  103. package/dist/types/react/native/ui/components/WalletImage.d.ts.map +1 -1
  104. package/dist/types/react/native/ui/connect/ConnectedButton.d.ts.map +1 -1
  105. package/dist/types/react/web/ui/ConnectWallet/Details.d.ts +18 -3
  106. package/dist/types/react/web/ui/ConnectWallet/Details.d.ts.map +1 -1
  107. package/dist/types/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.d.ts.map +1 -1
  108. package/dist/types/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.d.ts.map +1 -1
  109. package/dist/types/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.d.ts +2 -2
  110. package/dist/types/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.d.ts.map +1 -1
  111. package/dist/types/social/profiles.d.ts +24 -0
  112. package/dist/types/social/profiles.d.ts.map +1 -0
  113. package/dist/types/social/types.d.ts +41 -0
  114. package/dist/types/social/types.d.ts.map +1 -0
  115. package/dist/types/utils/domains.d.ts +6 -0
  116. package/dist/types/utils/domains.d.ts.map +1 -1
  117. package/dist/types/utils/function-id.d.ts.map +1 -1
  118. package/dist/types/utils/ipfs.d.ts.map +1 -1
  119. package/dist/types/version.d.ts +1 -1
  120. package/dist/types/version.d.ts.map +1 -1
  121. package/dist/types/wallets/smart/lib/userop.d.ts +2 -1
  122. package/dist/types/wallets/smart/lib/userop.d.ts.map +1 -1
  123. package/package.json +1 -1
  124. package/src/exports/react.ts +3 -0
  125. package/src/exports/social.ts +2 -0
  126. package/src/react/core/hooks/connection/ConnectButtonProps.ts +1 -0
  127. package/src/react/core/hooks/transaction/useSendTransaction.ts +5 -1
  128. package/src/react/core/providers/thirdweb-provider.tsx +2 -0
  129. package/src/react/core/social/useSocialProfiles.ts +40 -0
  130. package/src/react/core/utils/structuralSharing.ts +53 -0
  131. package/src/react/core/utils/wallet.ts +27 -0
  132. package/src/react/native/ui/components/WalletImage.tsx +4 -4
  133. package/src/react/native/ui/connect/ConnectedButton.tsx +8 -14
  134. package/src/react/native/ui/connect/ConnectedModal.tsx +8 -14
  135. package/src/react/web/ui/ConnectWallet/ConnectButton.tsx +0 -3
  136. package/src/react/web/ui/ConnectWallet/Details.tsx +40 -23
  137. package/src/react/web/ui/ConnectWallet/screens/Buy/BuyScreen.tsx +7 -1
  138. package/src/react/web/ui/ConnectWallet/screens/Buy/main/useUISelectionStates.ts +2 -1
  139. package/src/react/web/ui/ConnectWallet/screens/Buy/swap/useSwapSupportedChains.ts +9 -3
  140. package/src/social/profiles.ts +46 -0
  141. package/src/social/types.ts +43 -0
  142. package/src/utils/domains.ts +8 -0
  143. package/src/utils/function-id.ts +3 -5
  144. package/src/utils/ipfs.test.ts +57 -1
  145. package/src/utils/ipfs.ts +22 -14
  146. package/src/version.ts +1 -1
  147. package/src/wallets/smart/lib/userop.ts +2 -1
@@ -0,0 +1,53 @@
1
+ import { replaceEqualDeep } from "@tanstack/react-query";
2
+
3
+ /** Forked from https://github.com/epoberezkin/fast-deep-equal */
4
+ // biome-ignore lint/suspicious/noExplicitAny: This function by nature takes any object
5
+ export function deepEqual(a: any, b: any) {
6
+ if (a === b) return true;
7
+
8
+ if (a && b && typeof a === "object" && typeof b === "object") {
9
+ if (a.constructor !== b.constructor) return false;
10
+
11
+ let length: number;
12
+ let i: number;
13
+
14
+ if (Array.isArray(a) && Array.isArray(b)) {
15
+ length = a.length;
16
+ if (length !== b.length) return false;
17
+ for (i = length; i-- !== 0; ) if (!deepEqual(a[i], b[i])) return false;
18
+ return true;
19
+ }
20
+
21
+ if (a.valueOf !== Object.prototype.valueOf)
22
+ return a.valueOf() === b.valueOf();
23
+ if (a.toString !== Object.prototype.toString)
24
+ return a.toString() === b.toString();
25
+
26
+ const keys = Object.keys(a);
27
+ length = keys.length;
28
+ if (length !== Object.keys(b).length) return false;
29
+
30
+ for (i = length; i-- !== 0; )
31
+ // biome-ignore lint/style/noNonNullAssertion: We know its there
32
+ if (!Object.prototype.hasOwnProperty.call(b, keys[i]!)) return false;
33
+
34
+ for (i = length; i-- !== 0; ) {
35
+ const key = keys[i];
36
+
37
+ if (key && !deepEqual(a[key], b[key])) return false;
38
+ }
39
+
40
+ return true;
41
+ }
42
+
43
+ // true if both NaN, false otherwise
44
+ // biome-ignore lint/suspicious/noSelfCompare: <explanation>
45
+ return a !== a && b !== b;
46
+ }
47
+
48
+ export function structuralSharing<T>(oldData: T | undefined, newData: T) {
49
+ if (deepEqual(oldData, newData)) {
50
+ return oldData as T;
51
+ }
52
+ return replaceEqualDeep(oldData, newData) as T;
53
+ }
@@ -5,11 +5,13 @@ import type { ThirdwebClient } from "../../../client/client.js";
5
5
  import { resolveAvatar } from "../../../extensions/ens/resolve-avatar.js";
6
6
  import { resolveName } from "../../../extensions/ens/resolve-name.js";
7
7
  import { shortenAddress } from "../../../utils/address.js";
8
+ import { parseAvatarRecord } from "../../../utils/ens/avatar.js";
8
9
  import { getWalletInfo } from "../../../wallets/__generated__/getWalletInfo.js";
9
10
  import type { Account, Wallet } from "../../../wallets/interfaces/wallet.js";
10
11
  import type { WalletInfo } from "../../../wallets/wallet-info.js";
11
12
  import type { WalletId } from "../../../wallets/wallet-types.js";
12
13
  import { useWalletBalance } from "../hooks/others/useWalletBalance.js";
14
+ import { useSocialProfiles } from "../social/useSocialProfiles.js";
13
15
 
14
16
  /**
15
17
  * Get the ENS name and avatar for an address
@@ -98,6 +100,11 @@ export function useConnectedWalletDetails(
98
100
  ensName: ensNameQuery.data,
99
101
  });
100
102
 
103
+ const socialProfileQuery = useSocialProfiles({
104
+ client,
105
+ address: activeAccount?.address,
106
+ });
107
+
101
108
  const shortAddress = activeAccount?.address
102
109
  ? shortenAddress(activeAccount.address, 4)
103
110
  : "";
@@ -110,11 +117,31 @@ export function useConnectedWalletDetails(
110
117
  });
111
118
 
112
119
  const addressOrENS = ensNameQuery.data || shortAddress;
120
+ const pfpUnresolved = socialProfileQuery.data?.filter((p) => p.avatar)[0]
121
+ ?.avatar;
122
+
123
+ const { data: pfp } = useQuery({
124
+ queryKey: ["ens-avatar", pfpUnresolved],
125
+ queryFn: async () => {
126
+ if (!pfpUnresolved) {
127
+ return undefined;
128
+ }
129
+ return parseAvatarRecord({ client, uri: pfpUnresolved });
130
+ },
131
+ enabled: !!pfpUnresolved,
132
+ refetchOnWindowFocus: false,
133
+ refetchOnMount: false,
134
+ });
135
+ const name =
136
+ socialProfileQuery.data?.filter((p) => p.name)[0]?.name || addressOrENS;
113
137
 
114
138
  return {
139
+ socialProfileQuery,
115
140
  ensNameQuery,
116
141
  ensAvatarQuery,
117
142
  addressOrENS,
143
+ pfp,
144
+ name,
118
145
  shortAddress,
119
146
  balanceQuery,
120
147
  };
@@ -21,9 +21,9 @@ export const WalletImage = (props: {
21
21
  theme: Theme;
22
22
  wallet: Wallet;
23
23
  size: number;
24
- ensAvatar?: string | null;
24
+ avatar?: string | null;
25
25
  }) => {
26
- const { wallet, ensAvatar, size } = props;
26
+ const { wallet, avatar, size } = props;
27
27
 
28
28
  const { data: imageData } = useQuery({
29
29
  queryKey: ["wallet-image", wallet.id, wallet.getAccount()?.address],
@@ -56,10 +56,10 @@ export const WalletImage = (props: {
56
56
 
57
57
  return WALLET_ICON;
58
58
  },
59
- enabled: !ensAvatar,
59
+ enabled: !avatar,
60
60
  });
61
61
 
62
- const data = ensAvatar || imageData || WALLET_ICON;
62
+ const data = avatar || imageData || WALLET_ICON;
63
63
  return <RNImage theme={props.theme} data={data} size={size} />;
64
64
  };
65
65
 
@@ -22,13 +22,12 @@ export function ConnectedButton(
22
22
  const theme = parseTheme(props.theme);
23
23
  const { account, wallet } = props;
24
24
  const walletChain = useActiveWalletChain();
25
- const { ensAvatarQuery, addressOrENS, balanceQuery } =
26
- useConnectedWalletDetails(
27
- props.client,
28
- walletChain,
29
- account,
30
- props.detailsButton?.displayBalanceToken,
31
- );
25
+ const { pfp, name, balanceQuery } = useConnectedWalletDetails(
26
+ props.client,
27
+ walletChain,
28
+ account,
29
+ props.detailsButton?.displayBalanceToken,
30
+ );
32
31
  return (
33
32
  <ThemedButton
34
33
  theme={theme}
@@ -41,12 +40,7 @@ export function ConnectedButton(
41
40
  }}
42
41
  >
43
42
  <View style={styles.row}>
44
- <WalletImage
45
- theme={theme}
46
- size={40}
47
- wallet={wallet}
48
- ensAvatar={ensAvatarQuery.data}
49
- />
43
+ <WalletImage theme={theme} size={40} wallet={wallet} avatar={pfp} />
50
44
  <View style={styles.col}>
51
45
  <ThemedText
52
46
  theme={theme}
@@ -55,7 +49,7 @@ export function ConnectedButton(
55
49
  color: theme.colors.primaryButtonText,
56
50
  }}
57
51
  >
58
- {addressOrENS}
52
+ {name}
59
53
  </ThemedText>
60
54
  {balanceQuery.data ? (
61
55
  <ThemedText
@@ -163,24 +163,18 @@ export function ConnectedModal(props: ConnectedModalProps) {
163
163
  const AccountHeader = (props: ConnectedModalProps) => {
164
164
  const { account, wallet, theme } = props;
165
165
  const walletChain = useActiveWalletChain();
166
- const { ensAvatarQuery, addressOrENS, balanceQuery } =
167
- useConnectedWalletDetails(
168
- props.client,
169
- walletChain,
170
- account,
171
- props.detailsButton?.displayBalanceToken,
172
- );
166
+ const { pfp, name, balanceQuery } = useConnectedWalletDetails(
167
+ props.client,
168
+ walletChain,
169
+ account,
170
+ props.detailsButton?.displayBalanceToken,
171
+ );
173
172
  return (
174
173
  <View style={styles.accountHeaderContainer}>
175
- <WalletImage
176
- theme={theme}
177
- size={70}
178
- wallet={wallet}
179
- ensAvatar={ensAvatarQuery.data}
180
- />
174
+ <WalletImage theme={theme} size={70} wallet={wallet} avatar={pfp} />
181
175
  <SmartAccountBadge client={props.client} theme={theme} />
182
176
  <Spacer size="smd" />
183
- <Address account={account} theme={theme} addressOrENS={addressOrENS} />
177
+ <Address account={account} theme={theme} addressOrENS={name} />
184
178
  <Spacer size="xxs" />
185
179
  {balanceQuery.data ? (
186
180
  <ThemedText
@@ -544,9 +544,6 @@ function ConnectButtonInner(
544
544
  showAllWallets: props.showAllWallets,
545
545
  walletConnect: props.walletConnect,
546
546
  wallets: props.wallets,
547
- hideReceiveFunds: props.detailsModal?.hideReceiveFunds,
548
- hideSendFunds: props.detailsModal?.hideSendFunds,
549
- hideBuyFunds: props.detailsModal?.hideBuyFunds,
550
547
  }}
551
548
  />
552
549
  );
@@ -131,13 +131,12 @@ export const ConnectedWalletDetails: React.FC<{
131
131
  const activeAccount = useActiveAccount();
132
132
  const walletChain = useActiveWalletChain();
133
133
 
134
- const { ensAvatarQuery, addressOrENS, balanceQuery } =
135
- useConnectedWalletDetails(
136
- client,
137
- walletChain,
138
- activeAccount,
139
- props.detailsButton?.displayBalanceToken,
140
- );
134
+ const { pfp, name, balanceQuery } = useConnectedWalletDetails(
135
+ client,
136
+ walletChain,
137
+ activeAccount,
138
+ props.detailsButton?.displayBalanceToken,
139
+ );
141
140
 
142
141
  function closeModal() {
143
142
  setRootEl(null);
@@ -185,8 +184,7 @@ export const ConnectedWalletDetails: React.FC<{
185
184
  );
186
185
  }
187
186
 
188
- const avatarSrc =
189
- props.detailsButton?.connectedAccountAvatarUrl ?? ensAvatarQuery.data;
187
+ const avatarSrc = props.detailsButton?.connectedAccountAvatarUrl || pfp;
190
188
 
191
189
  return (
192
190
  <WalletInfoButton
@@ -235,7 +233,7 @@ export const ConnectedWalletDetails: React.FC<{
235
233
  weight={500}
236
234
  className={`${TW_CONNECTED_WALLET}__address`}
237
235
  >
238
- {props.detailsButton?.connectedAccountName ?? addressOrENS}
236
+ {props.detailsButton?.connectedAccountName ?? name}
239
237
  </Text>
240
238
 
241
239
  {/* Balance */}
@@ -280,13 +278,12 @@ function DetailsModal(props: {
280
278
  const { client, locale } = props;
281
279
  const walletChain = useActiveWalletChain();
282
280
  const activeAccount = useActiveAccount();
283
- const { ensAvatarQuery, addressOrENS, balanceQuery } =
284
- useConnectedWalletDetails(
285
- client,
286
- walletChain,
287
- activeAccount,
288
- props.displayBalanceToken,
289
- );
281
+ const { pfp, name, balanceQuery } = useConnectedWalletDetails(
282
+ client,
283
+ walletChain,
284
+ activeAccount,
285
+ props.displayBalanceToken,
286
+ );
290
287
  const theme = parseTheme(props.theme);
291
288
 
292
289
  const activeWallet = useActiveWallet();
@@ -370,8 +367,7 @@ function DetailsModal(props: {
370
367
  </MenuButton>
371
368
  );
372
369
 
373
- const avatarSrc =
374
- props.detailsModal?.connectedAccountAvatarUrl ?? ensAvatarQuery.data;
370
+ const avatarSrc = props.detailsModal?.connectedAccountAvatarUrl ?? pfp;
375
371
 
376
372
  const { hideSendFunds, hideReceiveFunds, hideBuyFunds } =
377
373
  props.detailsModal || {};
@@ -483,7 +479,7 @@ function DetailsModal(props: {
483
479
  }}
484
480
  >
485
481
  <Text color="primaryText" weight={500} size="md">
486
- {props.detailsModal?.connectedAccountName ?? addressOrENS}
482
+ {props.detailsModal?.connectedAccountName ?? name}
487
483
  </Text>
488
484
  <IconButton>
489
485
  <CopyIcon
@@ -1154,9 +1150,6 @@ export type DetailsModalConnectOptions = {
1154
1150
  chains?: Chain[];
1155
1151
  recommendedWallets?: Wallet[];
1156
1152
  showAllWallets?: boolean;
1157
- hideSendFunds?: boolean;
1158
- hideReceiveFunds?: boolean;
1159
- hideBuyFunds?: boolean;
1160
1153
  };
1161
1154
 
1162
1155
  export type UseWalletDetailsModalOptions = {
@@ -1380,6 +1373,27 @@ export type UseWalletDetailsModalOptions = {
1380
1373
  * Use custom avatar URL for the connected wallet image in the `ConnectButton` Details Modal, overriding ENS avatar or Blobbie icon.
1381
1374
  */
1382
1375
  connectedAccountAvatarUrl?: string;
1376
+
1377
+ /**
1378
+ * Hide the "Send Funds" button in the Details Modal.
1379
+ *
1380
+ * By default the "Send Funds" button is shown.
1381
+ */
1382
+ hideSendFunds?: boolean;
1383
+
1384
+ /**
1385
+ * Hide the "Receive Funds" button in the Details Modal.
1386
+ *
1387
+ * By default the "Receive Funds" button is shown.
1388
+ */
1389
+ hideReceiveFunds?: boolean;
1390
+
1391
+ /**
1392
+ * Hide the "Buy Funds" button in the Details Modal.
1393
+ *
1394
+ * By default the "Buy Funds" button is shown.
1395
+ */
1396
+ hideBuyFunds?: boolean;
1383
1397
  };
1384
1398
 
1385
1399
  /**
@@ -1434,6 +1448,9 @@ export function useWalletDetailsModal() {
1434
1448
  showTestnetFaucet: props.showTestnetFaucet,
1435
1449
  connectedAccountName: props.connectedAccountName,
1436
1450
  connectedAccountAvatarUrl: props.connectedAccountAvatarUrl,
1451
+ hideBuyFunds: props.hideBuyFunds,
1452
+ hideReceiveFunds: props.hideReceiveFunds,
1453
+ hideSendFunds: props.hideSendFunds,
1437
1454
  }}
1438
1455
  displayBalanceToken={props.displayBalanceToken}
1439
1456
  theme={props.theme || "dark"}
@@ -97,7 +97,13 @@ export type BuyScreenProps = {
97
97
  * @internal
98
98
  */
99
99
  export default function BuyScreen(props: BuyScreenProps) {
100
- const supportedDestinationsQuery = useBuySupportedDestinations(props.client);
100
+ const isTestMode = props.payOptions.buyWithCrypto
101
+ ? props.payOptions.buyWithCrypto.testMode
102
+ : undefined;
103
+ const supportedDestinationsQuery = useBuySupportedDestinations(
104
+ props.client,
105
+ isTestMode,
106
+ );
101
107
 
102
108
  if (!supportedDestinationsQuery.data) {
103
109
  return <LoadingScreen />;
@@ -50,7 +50,8 @@ export function useToTokenSelectionStates(options: {
50
50
  // use active chain if its supported as destination
51
51
  supportedDestinations.find((x) => x.chain.id === activeChain?.id)
52
52
  ?.chain ||
53
- // default to polygon
53
+ // default to the first chain in supportedDestinations, or polygon if nothing is found at all
54
+ supportedDestinations[0]?.chain ||
54
55
  polygon,
55
56
  );
56
57
 
@@ -36,11 +36,14 @@ export type SupportedChainAndTokens = Array<{
36
36
 
37
37
  export async function fetchBuySupportedDestinations(
38
38
  client: ThirdwebClient,
39
+ isTestMode?: boolean,
39
40
  ): Promise<SupportedChainAndTokens> {
40
41
  return withCache(
41
42
  async () => {
42
43
  const fetchWithHeaders = getClientFetch(client);
43
- const res = await fetchWithHeaders(getPaySupportedDestinations());
44
+ const res = await fetchWithHeaders(
45
+ `${getPaySupportedDestinations()}?isTestMode=${isTestMode}`,
46
+ );
44
47
  const data = (await res.json()) as Response;
45
48
  return data.result.map((item) => ({
46
49
  chain: defineChain({
@@ -59,11 +62,14 @@ export async function fetchBuySupportedDestinations(
59
62
  /**
60
63
  * @internal
61
64
  */
62
- export function useBuySupportedDestinations(client: ThirdwebClient) {
65
+ export function useBuySupportedDestinations(
66
+ client: ThirdwebClient,
67
+ isTestMode?: boolean,
68
+ ) {
63
69
  return useQuery({
64
70
  queryKey: ["destination-tokens", client],
65
71
  queryFn: async () => {
66
- return fetchBuySupportedDestinations(client);
72
+ return fetchBuySupportedDestinations(client, isTestMode);
67
73
  },
68
74
  });
69
75
  }
@@ -0,0 +1,46 @@
1
+ import type { ThirdwebClient } from "../client/client.js";
2
+ import { getThirdwebBaseUrl } from "../utils/domains.js";
3
+ import { getClientFetch } from "../utils/fetch.js";
4
+ import type { SocialProfiles } from "./types.js";
5
+
6
+ /**
7
+ * Fetches the wallet's available social profiles.
8
+ * @param args - The arguments to use when fetching the social profiles.
9
+ * @param args.address - The wallet address to fetch the social profiles for.
10
+ * @param args.client - The Thirdweb client.
11
+ * @returns A promise resolving to the retrieved social profiles for different protocols. If a profile is not available for a protocol, the value will be `null`.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * import { getProfiles } from "thirdweb/social";
16
+ * const profiles = await getProfiles({
17
+ * address: "0x...",
18
+ * client,
19
+ * });
20
+ * ```
21
+ * @beta
22
+ */
23
+ export async function getSocialProfiles(args: {
24
+ address: string;
25
+ client: ThirdwebClient;
26
+ }): Promise<SocialProfiles> {
27
+ const { address, client } = args;
28
+
29
+ const clientFetch = getClientFetch(client);
30
+ const response = await clientFetch(
31
+ `${getThirdwebBaseUrl("social")}/v1/profiles/${address}`,
32
+ );
33
+
34
+ if (response.status !== 200) {
35
+ try {
36
+ const errorBody = await response.json();
37
+ throw new Error(`Failed to fetch profile: ${errorBody.message}`);
38
+ } catch {
39
+ throw new Error(
40
+ `Failed to fetch profile: ${response.status}\n${await response.text()}`,
41
+ );
42
+ }
43
+ }
44
+
45
+ return (await response.json()).data as SocialProfiles;
46
+ }
@@ -0,0 +1,43 @@
1
+ export type FarcasterProfile = {
2
+ fid?: number;
3
+ bio?: string;
4
+ pfp?: string;
5
+ display?: string;
6
+ username?: string;
7
+ custodyAddress?: string;
8
+ addresses?: string[];
9
+ };
10
+
11
+ export type LensProfile = {
12
+ name?: string;
13
+ bio?: string;
14
+ picture?: string;
15
+ coverPicture?: string;
16
+ };
17
+
18
+ export type EnsProfile = {
19
+ name?: string;
20
+ address?: string;
21
+ avatar?: string;
22
+ display?: string;
23
+ description?: string;
24
+ keywords?: string[];
25
+ email?: string;
26
+ mail?: string;
27
+ notice?: string;
28
+ location?: string;
29
+ phone?: string;
30
+ url?: string;
31
+ twitter?: string;
32
+ github?: string;
33
+ discord?: string;
34
+ telegram?: string;
35
+ };
36
+
37
+ export type SocialProfiles = {
38
+ type: "farcaster" | "lens" | "ens";
39
+ name?: string;
40
+ avatar?: string;
41
+ bio?: string;
42
+ metadata?: FarcasterProfile | LensProfile | EnsProfile;
43
+ }[];
@@ -4,6 +4,11 @@ type DomainOverrides = {
4
4
  * @default "rpc.thirdweb.com"
5
5
  */
6
6
  rpc?: string;
7
+ /**
8
+ * The base URL for the social service.
9
+ * @default "social.thirdweb.com"
10
+ */
11
+ social?: string;
7
12
  /**
8
13
  * The base URL for the in-app wallet service
9
14
  * @default "embedded-wallet.thirdweb.com"
@@ -28,6 +33,7 @@ type DomainOverrides = {
28
33
 
29
34
  export const DEFAULT_RPC_URL = "rpc.thirdweb.com";
30
35
  const DEFAULT_IN_APP_WALLET_URL = "embedded-wallet.thirdweb.com";
36
+ const DEFAULT_SOCIAL_URL = "social.thirdweb.com";
31
37
  const DEFAULT_PAY_URL = "pay.thirdweb.com";
32
38
  const DEFAULT_STORAGE_URL = "storage.thirdweb.com";
33
39
  const DEFAULT_BUNDLER_URL = "bundler.thirdweb.com";
@@ -35,6 +41,7 @@ const DEFAULT_BUNDLER_URL = "bundler.thirdweb.com";
35
41
  let domains: { [k in keyof DomainOverrides]-?: string } = {
36
42
  rpc: DEFAULT_RPC_URL,
37
43
  inAppWallet: DEFAULT_IN_APP_WALLET_URL,
44
+ social: DEFAULT_SOCIAL_URL,
38
45
  pay: DEFAULT_PAY_URL,
39
46
  storage: DEFAULT_STORAGE_URL,
40
47
  bundler: DEFAULT_BUNDLER_URL,
@@ -47,6 +54,7 @@ export const setThirdwebDomains = (DomainOverrides: DomainOverrides) => {
47
54
  domains = {
48
55
  rpc: DomainOverrides.rpc ?? DEFAULT_RPC_URL,
49
56
  inAppWallet: DomainOverrides.inAppWallet ?? DEFAULT_IN_APP_WALLET_URL,
57
+ social: DomainOverrides.social ?? DEFAULT_SOCIAL_URL,
50
58
  pay: DomainOverrides.pay ?? DEFAULT_PAY_URL,
51
59
  storage: DomainOverrides.storage ?? DEFAULT_STORAGE_URL,
52
60
  bundler: DomainOverrides.bundler ?? DEFAULT_BUNDLER_URL,
@@ -1,11 +1,9 @@
1
- import { sha256 } from "@noble/hashes/sha256";
2
- import { uint8ArrayToHex } from "./encoding/hex.js";
1
+ import { randomBytesHex } from "./random.js";
3
2
 
4
3
  // biome-ignore lint/suspicious/noExplicitAny: the whoel point here is to accept anything
5
4
  type AnyFunction = (...args: any[]) => any;
6
5
 
7
- // WeakMap should be fine, if we de-reference the function, it should be garbage collected
8
- const functionIdCache = new WeakMap<AnyFunction, string>();
6
+ const functionIdCache = new Map<AnyFunction, string>();
9
7
 
10
8
  /**
11
9
  * Retrieves the unique identifier for a given function.
@@ -20,7 +18,7 @@ export function getFunctionId(fn: AnyFunction) {
20
18
  // biome-ignore lint/style/noNonNullAssertion: the `has` above ensures that this will always be set
21
19
  return functionIdCache.get(fn)!;
22
20
  }
23
- const id = uint8ArrayToHex(sha256(fn.toString()));
21
+ const id = randomBytesHex();
24
22
  functionIdCache.set(fn, id);
25
23
  return id;
26
24
  }
@@ -1,6 +1,10 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import { createThirdwebClient } from "../client/client.js";
3
- import { findIPFSCidFromUri, resolveScheme } from "./ipfs.js";
3
+ import {
4
+ findIPFSCidFromUri,
5
+ getBaseUriFromBatch,
6
+ resolveScheme,
7
+ } from "./ipfs.js";
4
8
 
5
9
  describe("resolveScheme", () => {
6
10
  it("should resolve ipfs scheme when not passing a gateway override", () => {
@@ -70,3 +74,55 @@ describe("resolveScheme", () => {
70
74
  expect(findIPFSCidFromUri(uri)).toBe(cid);
71
75
  });
72
76
  });
77
+
78
+ describe("getBaseUriFromBatch", () => {
79
+ it("should return the base uri", () => {
80
+ const batchOfUris = ["ipfs://Qm.../0", "ipfs://Qm.../1", "ipfs://Qm.../2"];
81
+ const baseUri = getBaseUriFromBatch(batchOfUris);
82
+ expect(baseUri).toMatchInlineSnapshot(`"ipfs://Qm.../"`);
83
+ });
84
+ it("should throw if the batch is empty", () => {
85
+ expect(() => getBaseUriFromBatch([])).toThrowErrorMatchingInlineSnapshot(
86
+ "[Error: Batch of URIs is empty]",
87
+ );
88
+ });
89
+
90
+ it("should throw if an element of the array does not have the same base", () => {
91
+ const batchOfUris = ["ipfs://Qm.../0", "ipfs://Qm.../1", "ipfs://Qm2.../2"];
92
+ expect(() =>
93
+ getBaseUriFromBatch(batchOfUris),
94
+ ).toThrowErrorMatchingInlineSnapshot(
95
+ "[Error: All URIs in the batch must have the same base URI]",
96
+ );
97
+ });
98
+
99
+ it("should work with a custom domain", () => {
100
+ const batchOfUris = [
101
+ "https://example.com/0",
102
+ "https://example.com/1",
103
+ "https://example.com/2",
104
+ ];
105
+ const baseUri = getBaseUriFromBatch(batchOfUris);
106
+ expect(baseUri).toMatchInlineSnapshot(`"https://example.com/"`);
107
+ });
108
+
109
+ it("should work with a custom domain and path", () => {
110
+ const batchOfUris = [
111
+ "https://example.com/path/0",
112
+ "https://example.com/path/1",
113
+ "https://example.com/path/2",
114
+ ];
115
+ const baseUri = getBaseUriFromBatch(batchOfUris);
116
+ expect(baseUri).toMatchInlineSnapshot(`"https://example.com/path/"`);
117
+ });
118
+
119
+ it("should work with a custom domain and path with trailing slash", () => {
120
+ const batchOfUris = [
121
+ "https://example.com/path/0/",
122
+ "https://example.com/path/1/",
123
+ "https://example.com/path/2/",
124
+ ];
125
+ const baseUri = getBaseUriFromBatch(batchOfUris);
126
+ expect(baseUri).toMatchInlineSnapshot(`"https://example.com/path/"`);
127
+ });
128
+ });
package/src/utils/ipfs.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import type { ThirdwebClient } from "../client/client.js";
2
-
3
2
  import type { FileOrBufferOrString } from "../storage/upload/types.js";
4
3
 
5
4
  export type ResolveSchemeOptions = {
@@ -109,23 +108,32 @@ export async function uploadOrExtractURIs<
109
108
  * @internal
110
109
  */
111
110
  export function getBaseUriFromBatch(uris: string[]): string {
112
- const urisWithSlashes = uris.map((uri) => new URL(uri));
113
- const baseUri = urisWithSlashes[0];
114
- for (let i = 0; i < urisWithSlashes.length; i++) {
115
- const uri = urisWithSlashes[i];
116
- if (baseUri?.host !== uri?.host) {
117
- throw new Error(
118
- `Can only create batches with the same base URI for every entry in the batch. Expected '${baseUri}' but got '${uri}'`,
119
- );
111
+ const [base, ...rest] = uris.map((uri) => {
112
+ // remove query parameters
113
+ // biome-ignore lint/style/noParameterAssign: lemme do my stuff
114
+ [uri] = uri.split("?") as [string];
115
+ // remove fragments
116
+ // biome-ignore lint/style/noParameterAssign: lemme do my stuff
117
+ [uri] = uri.split("#") as [string];
118
+
119
+ // if the URI ends with a `/`, remove it
120
+ if (uri.endsWith("/")) {
121
+ // biome-ignore lint/style/noParameterAssign: lemme do my stuff
122
+ uri = uri.slice(0, -1);
120
123
  }
121
- }
122
124
 
123
- if (!baseUri) {
124
- throw new Error("No base URI found in the batch");
125
+ // remove the last part of the URI & add the trailing `/`
126
+ return `${uri.split("/").slice(0, -1).join("/")}/`;
127
+ });
128
+
129
+ if (!base) {
130
+ throw new Error("Batch of URIs is empty");
125
131
  }
126
132
 
127
- // Ensure that baseUri ends with trailing slash
128
- return `${baseUri.protocol}//${baseUri.host}/`;
133
+ if (rest.some((uri) => uri !== base)) {
134
+ throw new Error("All URIs in the batch must have the same base URI");
135
+ }
136
+ return base;
129
137
  }
130
138
 
131
139
  function isUriList<T extends FileOrBufferOrString | Record<string, unknown>>(