thirdweb 5.76.1 → 5.77.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 (110) hide show
  1. package/dist/cjs/contract/deployment/deploy-with-abi.js +1 -1
  2. package/dist/cjs/contract/deployment/deploy-with-abi.js.map +1 -1
  3. package/dist/cjs/exports/react.js +4 -1
  4. package/dist/cjs/exports/react.js.map +1 -1
  5. package/dist/cjs/extensions/prebuilts/deploy-published.js +25 -6
  6. package/dist/cjs/extensions/prebuilts/deploy-published.js.map +1 -1
  7. package/dist/cjs/extensions/prebuilts/process-ref-deployments.js +117 -0
  8. package/dist/cjs/extensions/prebuilts/process-ref-deployments.js.map +1 -0
  9. package/dist/cjs/extensions/thirdweb/write/publish.js +1 -0
  10. package/dist/cjs/extensions/thirdweb/write/publish.js.map +1 -1
  11. package/dist/cjs/react/core/utils/storage.js +4 -3
  12. package/dist/cjs/react/core/utils/storage.js.map +1 -1
  13. package/dist/cjs/react/web/ui/prebuilt/Chain/icon.js +12 -1
  14. package/dist/cjs/react/web/ui/prebuilt/Chain/icon.js.map +1 -1
  15. package/dist/cjs/react/web/ui/prebuilt/Chain/name.js +30 -13
  16. package/dist/cjs/react/web/ui/prebuilt/Chain/name.js.map +1 -1
  17. package/dist/cjs/react/web/ui/prebuilt/NFT/description.js +2 -1
  18. package/dist/cjs/react/web/ui/prebuilt/NFT/description.js.map +1 -1
  19. package/dist/cjs/react/web/ui/prebuilt/NFT/media.js +2 -1
  20. package/dist/cjs/react/web/ui/prebuilt/NFT/media.js.map +1 -1
  21. package/dist/cjs/react/web/ui/prebuilt/NFT/name.js +2 -1
  22. package/dist/cjs/react/web/ui/prebuilt/NFT/name.js.map +1 -1
  23. package/dist/cjs/react/web/ui/prebuilt/Token/icon.js +13 -1
  24. package/dist/cjs/react/web/ui/prebuilt/Token/icon.js.map +1 -1
  25. package/dist/cjs/react/web/ui/prebuilt/Token/name.js +57 -26
  26. package/dist/cjs/react/web/ui/prebuilt/Token/name.js.map +1 -1
  27. package/dist/cjs/react/web/ui/prebuilt/Token/symbol.js +57 -26
  28. package/dist/cjs/react/web/ui/prebuilt/Token/symbol.js.map +1 -1
  29. package/dist/cjs/react/web/utils/storage.js +26 -0
  30. package/dist/cjs/react/web/utils/storage.js.map +1 -0
  31. package/dist/cjs/version.js +1 -1
  32. package/dist/esm/contract/deployment/deploy-with-abi.js +1 -1
  33. package/dist/esm/contract/deployment/deploy-with-abi.js.map +1 -1
  34. package/dist/esm/exports/react.js +2 -0
  35. package/dist/esm/exports/react.js.map +1 -1
  36. package/dist/esm/extensions/prebuilts/deploy-published.js +25 -6
  37. package/dist/esm/extensions/prebuilts/deploy-published.js.map +1 -1
  38. package/dist/esm/extensions/prebuilts/process-ref-deployments.js +114 -0
  39. package/dist/esm/extensions/prebuilts/process-ref-deployments.js.map +1 -0
  40. package/dist/esm/extensions/thirdweb/write/publish.js +1 -0
  41. package/dist/esm/extensions/thirdweb/write/publish.js.map +1 -1
  42. package/dist/esm/react/core/utils/storage.js +1 -1
  43. package/dist/esm/react/core/utils/storage.js.map +1 -1
  44. package/dist/esm/react/web/ui/prebuilt/Chain/icon.js +12 -1
  45. package/dist/esm/react/web/ui/prebuilt/Chain/icon.js.map +1 -1
  46. package/dist/esm/react/web/ui/prebuilt/Chain/name.js +29 -13
  47. package/dist/esm/react/web/ui/prebuilt/Chain/name.js.map +1 -1
  48. package/dist/esm/react/web/ui/prebuilt/NFT/description.js +2 -1
  49. package/dist/esm/react/web/ui/prebuilt/NFT/description.js.map +1 -1
  50. package/dist/esm/react/web/ui/prebuilt/NFT/media.js +2 -1
  51. package/dist/esm/react/web/ui/prebuilt/NFT/media.js.map +1 -1
  52. package/dist/esm/react/web/ui/prebuilt/NFT/name.js +2 -1
  53. package/dist/esm/react/web/ui/prebuilt/NFT/name.js.map +1 -1
  54. package/dist/esm/react/web/ui/prebuilt/Token/icon.js +13 -1
  55. package/dist/esm/react/web/ui/prebuilt/Token/icon.js.map +1 -1
  56. package/dist/esm/react/web/ui/prebuilt/Token/name.js +55 -26
  57. package/dist/esm/react/web/ui/prebuilt/Token/name.js.map +1 -1
  58. package/dist/esm/react/web/ui/prebuilt/Token/symbol.js +55 -26
  59. package/dist/esm/react/web/ui/prebuilt/Token/symbol.js.map +1 -1
  60. package/dist/esm/react/web/utils/storage.js +23 -0
  61. package/dist/esm/react/web/utils/storage.js.map +1 -0
  62. package/dist/esm/version.js +1 -1
  63. package/dist/types/exports/react.d.ts +1 -0
  64. package/dist/types/exports/react.d.ts.map +1 -1
  65. package/dist/types/extensions/prebuilts/deploy-published.d.ts.map +1 -1
  66. package/dist/types/extensions/prebuilts/process-ref-deployments.d.ts +17 -0
  67. package/dist/types/extensions/prebuilts/process-ref-deployments.d.ts.map +1 -0
  68. package/dist/types/extensions/thirdweb/write/publish.d.ts.map +1 -1
  69. package/dist/types/react/core/utils/storage.d.ts +1 -0
  70. package/dist/types/react/core/utils/storage.d.ts.map +1 -1
  71. package/dist/types/react/web/ui/prebuilt/Chain/icon.d.ts.map +1 -1
  72. package/dist/types/react/web/ui/prebuilt/Chain/name.d.ts +8 -0
  73. package/dist/types/react/web/ui/prebuilt/Chain/name.d.ts.map +1 -1
  74. package/dist/types/react/web/ui/prebuilt/NFT/description.d.ts.map +1 -1
  75. package/dist/types/react/web/ui/prebuilt/NFT/media.d.ts.map +1 -1
  76. package/dist/types/react/web/ui/prebuilt/NFT/name.d.ts.map +1 -1
  77. package/dist/types/react/web/ui/prebuilt/Token/icon.d.ts.map +1 -1
  78. package/dist/types/react/web/ui/prebuilt/Token/name.d.ts +21 -0
  79. package/dist/types/react/web/ui/prebuilt/Token/name.d.ts.map +1 -1
  80. package/dist/types/react/web/ui/prebuilt/Token/symbol.d.ts +21 -0
  81. package/dist/types/react/web/ui/prebuilt/Token/symbol.d.ts.map +1 -1
  82. package/dist/types/react/web/utils/storage.d.ts +19 -0
  83. package/dist/types/react/web/utils/storage.d.ts.map +1 -0
  84. package/dist/types/utils/any-evm/deploy-metadata.d.ts +19 -0
  85. package/dist/types/utils/any-evm/deploy-metadata.d.ts.map +1 -1
  86. package/dist/types/version.d.ts +1 -1
  87. package/package.json +8 -8
  88. package/src/contract/deployment/deploy-with-abi.ts +1 -1
  89. package/src/exports/react.ts +3 -0
  90. package/src/extensions/prebuilts/deploy-published.ts +36 -6
  91. package/src/extensions/prebuilts/process-ref-deployments.test.ts +196 -0
  92. package/src/extensions/prebuilts/process-ref-deployments.ts +167 -0
  93. package/src/extensions/thirdweb/write/publish.ts +1 -0
  94. package/src/react/core/utils/storage.ts +1 -1
  95. package/src/react/web/ui/prebuilt/Chain/icon.tsx +13 -1
  96. package/src/react/web/ui/prebuilt/Chain/name.test.tsx +36 -9
  97. package/src/react/web/ui/prebuilt/Chain/name.tsx +35 -13
  98. package/src/react/web/ui/prebuilt/NFT/description.tsx +2 -1
  99. package/src/react/web/ui/prebuilt/NFT/media.tsx +2 -1
  100. package/src/react/web/ui/prebuilt/NFT/name.tsx +2 -1
  101. package/src/react/web/ui/prebuilt/Token/icon.tsx +14 -1
  102. package/src/react/web/ui/prebuilt/Token/name.test.tsx +148 -0
  103. package/src/react/web/ui/prebuilt/Token/name.tsx +73 -29
  104. package/src/react/web/ui/prebuilt/Token/provider.test.tsx +11 -0
  105. package/src/react/web/ui/prebuilt/Token/symbol.test.tsx +138 -20
  106. package/src/react/web/ui/prebuilt/Token/symbol.tsx +73 -32
  107. package/src/react/web/utils/storage.test.ts +34 -0
  108. package/src/react/web/utils/storage.ts +23 -0
  109. package/src/utils/any-evm/deploy-metadata.ts +29 -0
  110. package/src/version.ts +1 -1
@@ -0,0 +1,167 @@
1
+ import type { Chain } from "../../chains/types.js";
2
+ import type { ThirdwebClient } from "../../client/client.js";
3
+ import { encodeAbiParameters } from "../../utils/abi/encodeAbiParameters.js";
4
+ import type { DynamicParams } from "../../utils/any-evm/deploy-metadata.js";
5
+ import type { Account } from "../../wallets/interfaces/wallet.js";
6
+ import { deployPublishedContract } from "./deploy-published.js";
7
+
8
+ export type ImplementationConstructorParam = {
9
+ defaultValue?: string;
10
+ dynamicValue?: DynamicParams;
11
+ };
12
+
13
+ type ProcessRefDeploymentsOptions = {
14
+ client: ThirdwebClient;
15
+ chain: Chain;
16
+ account: Account;
17
+ paramValue: string | ImplementationConstructorParam;
18
+ };
19
+
20
+ export async function processRefDeployments(
21
+ options: ProcessRefDeploymentsOptions,
22
+ ): Promise<string | string[]> {
23
+ const { client, account, chain, paramValue } = options;
24
+
25
+ if (typeof paramValue === "object") {
26
+ if (
27
+ "defaultValue" in paramValue &&
28
+ paramValue.defaultValue &&
29
+ paramValue.defaultValue.length > 0
30
+ ) {
31
+ return paramValue.defaultValue;
32
+ }
33
+
34
+ if ("dynamicValue" in paramValue && paramValue.dynamicValue) {
35
+ const dynamicValue = paramValue.dynamicValue;
36
+ const contracts = dynamicValue.refContracts;
37
+
38
+ if (dynamicValue.type === "address") {
39
+ if (!contracts || contracts.length === 0 || !contracts[0]?.contractId) {
40
+ throw new Error("Invalid or empty param value");
41
+ }
42
+ const salt =
43
+ contracts[0]?.salt && contracts[0]?.salt.length > 0
44
+ ? contracts[0]?.salt
45
+ : "thirdweb";
46
+
47
+ const addr = await deployPublishedContract({
48
+ client,
49
+ chain,
50
+ account,
51
+ contractId: contracts[0]?.contractId,
52
+ publisher: contracts[0]?.publisherAddress,
53
+ version: contracts[0]?.version,
54
+ salt,
55
+ });
56
+
57
+ return addr;
58
+ }
59
+
60
+ if (dynamicValue.type === "address[]") {
61
+ if (!contracts || contracts.length === 0) {
62
+ throw new Error("Invalid or empty param value");
63
+ }
64
+ const addressArray = [];
65
+
66
+ for (const c of contracts) {
67
+ const salt = c?.salt && c?.salt.length > 0 ? c?.salt : "thirdweb";
68
+
69
+ addressArray.push(
70
+ await deployPublishedContract({
71
+ client,
72
+ chain,
73
+ account,
74
+ contractId: c.contractId,
75
+ publisher: c.publisherAddress,
76
+ version: c.version,
77
+ salt,
78
+ }),
79
+ );
80
+ }
81
+
82
+ return addressArray;
83
+ }
84
+
85
+ if (dynamicValue.type === "bytes") {
86
+ if (!dynamicValue.paramsToEncode) {
87
+ throw new Error("Invalid or empty param value");
88
+ }
89
+ const paramsToEncode = dynamicValue.paramsToEncode[0];
90
+
91
+ if (paramsToEncode) {
92
+ const types = [];
93
+ const values = [];
94
+ for (const v of paramsToEncode) {
95
+ types.push(v.type);
96
+
97
+ if (v.defaultValue) {
98
+ values.push(v.defaultValue);
99
+ } else if (v.dynamicValue) {
100
+ values.push(
101
+ await processRefDeployments({
102
+ client,
103
+ account,
104
+ chain,
105
+ paramValue: v,
106
+ }),
107
+ );
108
+ }
109
+ }
110
+
111
+ return encodeAbiParameters(
112
+ types.map((t) => {
113
+ return { type: t };
114
+ }),
115
+ values,
116
+ );
117
+ }
118
+ }
119
+
120
+ if (dynamicValue.type === "bytes[]") {
121
+ if (!dynamicValue.paramsToEncode) {
122
+ throw new Error("Invalid or empty param value");
123
+ }
124
+ const bytesArray = [];
125
+ const paramArray = dynamicValue.paramsToEncode;
126
+
127
+ for (const a of paramArray) {
128
+ const paramsToEncode = a;
129
+
130
+ if (paramsToEncode) {
131
+ const types = [];
132
+ const values = [];
133
+ for (const v of paramsToEncode) {
134
+ types.push(v.type);
135
+
136
+ if (v.defaultValue) {
137
+ values.push(v.defaultValue);
138
+ } else if (v.dynamicValue) {
139
+ values.push(
140
+ await processRefDeployments({
141
+ client,
142
+ account,
143
+ chain,
144
+ paramValue: v,
145
+ }),
146
+ );
147
+ }
148
+ }
149
+
150
+ bytesArray.push(
151
+ encodeAbiParameters(
152
+ types.map((t) => {
153
+ return { type: t };
154
+ }),
155
+ values,
156
+ ),
157
+ );
158
+ }
159
+ }
160
+
161
+ return bytesArray;
162
+ }
163
+ }
164
+ }
165
+
166
+ return paramValue as string;
167
+ }
@@ -78,6 +78,7 @@ export function publishContract(
78
78
  changelog: options.metadata.changelog,
79
79
  compositeAbi: options.metadata.compositeAbi,
80
80
  constructorParams: options.metadata.constructorParams,
81
+ implConstructorParams: options.metadata.implConstructorParams,
81
82
  defaultExtensions: options.metadata.defaultExtensions,
82
83
  defaultModules: options.metadata.defaultModules,
83
84
  deployType: options.metadata.deployType,
@@ -1,7 +1,7 @@
1
1
  import type { AsyncStorage } from "../../../utils/storage/AsyncStorage.js";
2
2
  import type { AuthArgsType } from "../../../wallets/in-app/core/authentication/types.js";
3
3
 
4
- const LAST_AUTH_PROVIDER_STORAGE_KEY = "lastAuthProvider";
4
+ export const LAST_AUTH_PROVIDER_STORAGE_KEY = "lastAuthProvider";
5
5
 
6
6
  export async function setLastAuthProvider(
7
7
  authProvider: AuthArgsType["strategy"],
@@ -4,6 +4,7 @@ import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
4
4
  import type { JSX } from "react";
5
5
  import { getChainMetadata } from "../../../../../chains/utils.js";
6
6
  import type { ThirdwebClient } from "../../../../../client/client.js";
7
+ import { getFunctionId } from "../../../../../utils/function-id.js";
7
8
  import { resolveScheme } from "../../../../../utils/ipfs.js";
8
9
  import { useChainContext } from "./provider.js";
9
10
 
@@ -121,7 +122,18 @@ export function ChainIcon({
121
122
  }: ChainIconProps) {
122
123
  const { chain } = useChainContext();
123
124
  const iconQuery = useQuery({
124
- queryKey: ["_internal_chain_icon_", chain.id] as const,
125
+ queryKey: [
126
+ "_internal_chain_icon_",
127
+ chain.id,
128
+ {
129
+ resolver:
130
+ typeof iconResolver === "string"
131
+ ? iconResolver
132
+ : typeof iconResolver === "function"
133
+ ? getFunctionId(iconResolver)
134
+ : undefined,
135
+ },
136
+ ] as const,
125
137
  queryFn: async () => {
126
138
  if (typeof iconResolver === "string") {
127
139
  return iconResolver;
@@ -2,17 +2,17 @@ import { describe, expect, it } from "vitest";
2
2
  import { render, screen, waitFor } from "~test/react-render.js";
3
3
  import { ethereum } from "../../../../../chains/chain-definitions/ethereum.js";
4
4
  import { defineChain } from "../../../../../chains/utils.js";
5
- import { ChainName } from "./name.js";
5
+ import { ChainName, fetchChainName } from "./name.js";
6
6
  import { ChainProvider } from "./provider.js";
7
7
 
8
8
  describe.runIf(process.env.TW_SECRET_KEY)("ChainName component", () => {
9
- it("should return the correct chain name, if the name exists in the chain object", () => {
9
+ it("should return the correct chain name, if the name exists in the chain object", async () => {
10
10
  render(
11
11
  <ChainProvider chain={ethereum}>
12
12
  <ChainName />
13
13
  </ChainProvider>,
14
14
  );
15
- waitFor(() =>
15
+ await waitFor(() =>
16
16
  expect(
17
17
  screen.getByText("Ethereum", {
18
18
  exact: true,
@@ -22,13 +22,13 @@ describe.runIf(process.env.TW_SECRET_KEY)("ChainName component", () => {
22
22
  );
23
23
  });
24
24
 
25
- it("should return the correct chain name, if the name is loaded from the server", () => {
25
+ it("should return the correct chain name, if the name is loaded from the server", async () => {
26
26
  render(
27
27
  <ChainProvider chain={defineChain(1)}>
28
28
  <ChainName />
29
29
  </ChainProvider>,
30
30
  );
31
- waitFor(() =>
31
+ await waitFor(() =>
32
32
  expect(
33
33
  screen.getByText("Ethereum Mainnet", {
34
34
  exact: true,
@@ -38,13 +38,13 @@ describe.runIf(process.env.TW_SECRET_KEY)("ChainName component", () => {
38
38
  );
39
39
  });
40
40
 
41
- it("should return the correct FORMATTED chain name", () => {
41
+ it("should return the correct FORMATTED chain name", async () => {
42
42
  render(
43
43
  <ChainProvider chain={ethereum}>
44
44
  <ChainName formatFn={(str: string) => `${str}-formatted`} />
45
45
  </ChainProvider>,
46
46
  );
47
- waitFor(() =>
47
+ await waitFor(() =>
48
48
  expect(
49
49
  screen.getByText("Ethereum-formatted", {
50
50
  exact: true,
@@ -54,14 +54,14 @@ describe.runIf(process.env.TW_SECRET_KEY)("ChainName component", () => {
54
54
  );
55
55
  });
56
56
 
57
- it("should fallback properly when fail to resolve chain name", () => {
57
+ it("should fallback properly when fail to resolve chain name", async () => {
58
58
  render(
59
59
  <ChainProvider chain={defineChain(-1)}>
60
60
  <ChainName fallbackComponent={<span>oops</span>} />
61
61
  </ChainProvider>,
62
62
  );
63
63
 
64
- waitFor(() =>
64
+ await waitFor(() =>
65
65
  expect(
66
66
  screen.getByText("oops", {
67
67
  exact: true,
@@ -70,4 +70,31 @@ describe.runIf(process.env.TW_SECRET_KEY)("ChainName component", () => {
70
70
  ).toBeInTheDocument(),
71
71
  );
72
72
  });
73
+
74
+ it("fetchChainName should respect nameResolver as a string", async () => {
75
+ const res = await fetchChainName({
76
+ chain: ethereum,
77
+ nameResolver: "eth_mainnet",
78
+ });
79
+ expect(res).toBe("eth_mainnet");
80
+ });
81
+
82
+ it("fetchChainName should respect nameResolver as a non-async function", async () => {
83
+ const res = await fetchChainName({
84
+ chain: ethereum,
85
+ nameResolver: () => "eth_mainnet",
86
+ });
87
+ expect(res).toBe("eth_mainnet");
88
+ });
89
+
90
+ it("fetchChainName should respect nameResolver as an async function", async () => {
91
+ const res = await fetchChainName({
92
+ chain: ethereum,
93
+ nameResolver: async () => {
94
+ await new Promise((resolve) => setTimeout(resolve, 2000));
95
+ return "eth_mainnet";
96
+ },
97
+ });
98
+ expect(res).toBe("eth_mainnet");
99
+ });
73
100
  });
@@ -3,7 +3,9 @@
3
3
  import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
4
4
  import type React from "react";
5
5
  import type { JSX } from "react";
6
+ import type { Chain } from "../../../../../chains/types.js";
6
7
  import { getChainMetadata } from "../../../../../chains/utils.js";
8
+ import { getFunctionId } from "../../../../../utils/function-id.js";
7
9
  import { useChainContext } from "./provider.js";
8
10
 
9
11
  /**
@@ -155,19 +157,19 @@ export function ChainName({
155
157
  }: ChainNameProps) {
156
158
  const { chain } = useChainContext();
157
159
  const nameQuery = useQuery({
158
- queryKey: ["_internal_chain_name_", chain.id] as const,
159
- queryFn: async () => {
160
- if (typeof nameResolver === "string") {
161
- return nameResolver;
162
- }
163
- if (typeof nameResolver === "function") {
164
- return nameResolver();
165
- }
166
- if (chain.name) {
167
- return chain.name;
168
- }
169
- return getChainMetadata(chain).then((data) => data.name);
170
- },
160
+ queryKey: [
161
+ "_internal_chain_name_",
162
+ chain.id,
163
+ {
164
+ resolver:
165
+ typeof nameResolver === "string"
166
+ ? nameResolver
167
+ : typeof nameResolver === "function"
168
+ ? getFunctionId(nameResolver)
169
+ : undefined,
170
+ },
171
+ ] as const,
172
+ queryFn: async () => fetchChainName({ chain, nameResolver }),
171
173
  ...queryOptions,
172
174
  });
173
175
 
@@ -183,3 +185,23 @@ export function ChainName({
183
185
 
184
186
  return <span {...restProps}>{displayValue}</span>;
185
187
  }
188
+
189
+ /**
190
+ * @internal Exported for tests only
191
+ */
192
+ export async function fetchChainName(props: {
193
+ chain: Chain;
194
+ nameResolver?: string | (() => string) | (() => Promise<string>);
195
+ }) {
196
+ const { nameResolver, chain } = props;
197
+ if (typeof nameResolver === "string") {
198
+ return nameResolver;
199
+ }
200
+ if (typeof nameResolver === "function") {
201
+ return nameResolver();
202
+ }
203
+ if (chain.name) {
204
+ return chain.name;
205
+ }
206
+ return getChainMetadata(chain).then((data) => data.name);
207
+ }
@@ -3,6 +3,7 @@
3
3
  import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
4
4
  import type { JSX } from "react";
5
5
  import type { ThirdwebContract } from "../../../../../contract/contract.js";
6
+ import { getFunctionId } from "../../../../../utils/function-id.js";
6
7
  import { useNFTContext } from "./provider.js";
7
8
  import { getNFTInfo } from "./utils.js";
8
9
 
@@ -100,7 +101,7 @@ export function NFTDescription({
100
101
  typeof descriptionResolver === "string"
101
102
  ? descriptionResolver
102
103
  : typeof descriptionResolver === "function"
103
- ? descriptionResolver.toString()
104
+ ? getFunctionId(descriptionResolver)
104
105
  : undefined,
105
106
  },
106
107
  ],
@@ -3,6 +3,7 @@
3
3
  import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
4
4
  import type { JSX } from "react";
5
5
  import type { ThirdwebContract } from "../../../../../contract/contract.js";
6
+ import { getFunctionId } from "../../../../../utils/function-id.js";
6
7
  import { MediaRenderer } from "../../MediaRenderer/MediaRenderer.js";
7
8
  import type { MediaRendererProps } from "../../MediaRenderer/types.js";
8
9
  import { useNFTContext } from "./provider.js";
@@ -140,7 +141,7 @@ export function NFTMedia({
140
141
  typeof mediaResolver === "object"
141
142
  ? mediaResolver
142
143
  : typeof mediaResolver === "function"
143
- ? mediaResolver.toString()
144
+ ? getFunctionId(mediaResolver)
144
145
  : undefined,
145
146
  },
146
147
  ],
@@ -3,6 +3,7 @@
3
3
  import { type UseQueryOptions, useQuery } from "@tanstack/react-query";
4
4
  import type { JSX } from "react";
5
5
  import type { ThirdwebContract } from "../../../../../contract/contract.js";
6
+ import { getFunctionId } from "../../../../../utils/function-id.js";
6
7
  import { useNFTContext } from "./provider.js";
7
8
  import { getNFTInfo } from "./utils.js";
8
9
 
@@ -102,7 +103,7 @@ export function NFTName({
102
103
  typeof nameResolver === "string"
103
104
  ? nameResolver
104
105
  : typeof nameResolver === "function"
105
- ? nameResolver.toString()
106
+ ? getFunctionId(nameResolver)
106
107
  : undefined,
107
108
  },
108
109
  ],
@@ -6,6 +6,7 @@ import { getChainMetadata } from "../../../../../chains/utils.js";
6
6
  import { NATIVE_TOKEN_ADDRESS } from "../../../../../constants/addresses.js";
7
7
  import { getContract } from "../../../../../contract/contract.js";
8
8
  import { getContractMetadata } from "../../../../../extensions/common/read/getContractMetadata.js";
9
+ import { getFunctionId } from "../../../../../utils/function-id.js";
9
10
  import { resolveScheme } from "../../../../../utils/ipfs.js";
10
11
  import { useTokenContext } from "./provider.js";
11
12
 
@@ -117,7 +118,19 @@ export function TokenIcon({
117
118
  }: TokenIconProps) {
118
119
  const { address, client, chain } = useTokenContext();
119
120
  const iconQuery = useQuery({
120
- queryKey: ["_internal_token_icon_", chain.id, address] as const,
121
+ queryKey: [
122
+ "_internal_token_icon_",
123
+ chain.id,
124
+ address,
125
+ {
126
+ resolver:
127
+ typeof iconResolver === "string"
128
+ ? iconResolver
129
+ : typeof iconResolver === "function"
130
+ ? getFunctionId(iconResolver)
131
+ : undefined,
132
+ },
133
+ ] as const,
121
134
  queryFn: async () => {
122
135
  if (typeof iconResolver === "string") {
123
136
  return iconResolver;
@@ -0,0 +1,148 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { ANVIL_CHAIN } from "~test/chains.js";
3
+ import {} from "~test/react-render.js";
4
+ import { TEST_CLIENT } from "~test/test-clients.js";
5
+ import {
6
+ UNISWAPV3_FACTORY_CONTRACT,
7
+ USDT_CONTRACT,
8
+ } from "~test/test-contracts.js";
9
+ import { ethereum } from "../../../../../chains/chain-definitions/ethereum.js";
10
+ import { NATIVE_TOKEN_ADDRESS } from "../../../../../constants/addresses.js";
11
+ import { getFunctionId } from "../../../../../utils/function-id.js";
12
+ import { fetchTokenName, getQueryKeys } from "./name.js";
13
+
14
+ const client = TEST_CLIENT;
15
+
16
+ describe.runIf(process.env.TW_SECRET_KEY)("TokenName component", () => {
17
+ it("fetchTokenName should respect the nameResolver being a string", async () => {
18
+ const res = await fetchTokenName({
19
+ address: "thing",
20
+ client,
21
+ chain: ANVIL_CHAIN,
22
+ nameResolver: "tw",
23
+ });
24
+ expect(res).toBe("tw");
25
+ });
26
+
27
+ it("fetchTokenName should respect the nameResolver being a non-async function", async () => {
28
+ const res = await fetchTokenName({
29
+ address: "thing",
30
+ client,
31
+ chain: ANVIL_CHAIN,
32
+ nameResolver: () => "tw",
33
+ });
34
+
35
+ expect(res).toBe("tw");
36
+ });
37
+
38
+ it("fetchTokenName should respect the nameResolver being an async function", async () => {
39
+ const res = await fetchTokenName({
40
+ address: "thing",
41
+ client,
42
+ chain: ANVIL_CHAIN,
43
+ nameResolver: async () => {
44
+ await new Promise((resolve) => setTimeout(resolve, 2000));
45
+ return "tw";
46
+ },
47
+ });
48
+
49
+ expect(res).toBe("tw");
50
+ });
51
+
52
+ it("fetchTokenName should work for contract with `name` function", async () => {
53
+ const res = await fetchTokenName({
54
+ address: USDT_CONTRACT.address,
55
+ client,
56
+ chain: USDT_CONTRACT.chain,
57
+ });
58
+
59
+ expect(res).toBe("Tether USD");
60
+ });
61
+
62
+ it("fetchTokenName should work for native token", async () => {
63
+ const res = await fetchTokenName({
64
+ address: NATIVE_TOKEN_ADDRESS,
65
+ client,
66
+ chain: ethereum,
67
+ });
68
+
69
+ expect(res).toBe("Ether");
70
+ });
71
+
72
+ it("fetchTokenName should try to fallback to the contract metadata if fails to resolves from `name()`", async () => {
73
+ // todo: find a contract with name in contractMetadata, but does not have a name function
74
+ });
75
+
76
+ it("fetchTokenName should throw in the end where all fallback solutions failed to resolve to any name", async () => {
77
+ await expect(() =>
78
+ fetchTokenName({
79
+ address: UNISWAPV3_FACTORY_CONTRACT.address,
80
+ client,
81
+ chain: UNISWAPV3_FACTORY_CONTRACT.chain,
82
+ }),
83
+ ).rejects.toThrowError(
84
+ "Failed to resolve name from both name() and contract metadata",
85
+ );
86
+ });
87
+
88
+ it("getQueryKeys should work without resolver", () => {
89
+ expect(getQueryKeys({ chainId: 1, address: "0x" })).toStrictEqual([
90
+ "_internal_token_name_",
91
+ 1,
92
+ "0x",
93
+ {
94
+ resolver: undefined,
95
+ },
96
+ ]);
97
+ });
98
+
99
+ it("getQueryKeys should work with resolver being a string", () => {
100
+ expect(
101
+ getQueryKeys({ chainId: 1, address: "0x", nameResolver: "tw" }),
102
+ ).toStrictEqual([
103
+ "_internal_token_name_",
104
+ 1,
105
+ "0x",
106
+ {
107
+ resolver: "tw",
108
+ },
109
+ ]);
110
+ });
111
+
112
+ it("getQueryKeys should work with resolver being a non-async fn that returns a string", () => {
113
+ const fn = () => "tw";
114
+ const fnId = getFunctionId(fn);
115
+ expect(
116
+ getQueryKeys({ chainId: 1, address: "0x", nameResolver: fn }),
117
+ ).toStrictEqual([
118
+ "_internal_token_name_",
119
+ 1,
120
+ "0x",
121
+ {
122
+ resolver: fnId,
123
+ },
124
+ ]);
125
+ });
126
+
127
+ it("getQueryKeys should work with resolver being an async fn that returns a string", () => {
128
+ const fn = async () => {
129
+ await new Promise((resolve) => setTimeout(resolve, 2000));
130
+ return "tw";
131
+ };
132
+ const fnId = getFunctionId(fn);
133
+ expect(
134
+ getQueryKeys({
135
+ chainId: 1,
136
+ address: "0x",
137
+ nameResolver: fn,
138
+ }),
139
+ ).toStrictEqual([
140
+ "_internal_token_name_",
141
+ 1,
142
+ "0x",
143
+ {
144
+ resolver: fnId,
145
+ },
146
+ ]);
147
+ });
148
+ });