thirdweb 5.56.0-nightly-07b949dd8c07ffdeda40a5549c31ad4b09abbbf1-20240916000437 → 5.56.0-nightly-a5e605c65e360a9d3d2e553d6783e58582b50a70-20240917000331

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 (63) hide show
  1. package/dist/cjs/contract/deployment/publisher.js +4 -4
  2. package/dist/cjs/contract/deployment/publisher.js.map +1 -1
  3. package/dist/cjs/exports/extensions/thirdweb.js +4 -3
  4. package/dist/cjs/exports/extensions/thirdweb.js.map +1 -1
  5. package/dist/cjs/exports/modules.js +4 -1
  6. package/dist/cjs/exports/modules.js.map +1 -1
  7. package/dist/cjs/extensions/modules/common/checkModulesCompatibility.js +95 -0
  8. package/dist/cjs/extensions/modules/common/checkModulesCompatibility.js.map +1 -0
  9. package/dist/cjs/extensions/thirdweb/write/publish.js +106 -0
  10. package/dist/cjs/extensions/thirdweb/write/publish.js.map +1 -0
  11. package/dist/cjs/utils/arrays.js +23 -0
  12. package/dist/cjs/utils/arrays.js.map +1 -0
  13. package/dist/cjs/utils/semver.js +67 -0
  14. package/dist/cjs/utils/semver.js.map +1 -0
  15. package/dist/cjs/version.js +1 -1
  16. package/dist/esm/contract/deployment/publisher.js +1 -1
  17. package/dist/esm/contract/deployment/publisher.js.map +1 -1
  18. package/dist/esm/exports/extensions/thirdweb.js +1 -1
  19. package/dist/esm/exports/extensions/thirdweb.js.map +1 -1
  20. package/dist/esm/exports/modules.js +2 -1
  21. package/dist/esm/exports/modules.js.map +1 -1
  22. package/dist/esm/extensions/modules/common/checkModulesCompatibility.js +93 -0
  23. package/dist/esm/extensions/modules/common/checkModulesCompatibility.js.map +1 -0
  24. package/dist/esm/extensions/thirdweb/write/publish.js +102 -0
  25. package/dist/esm/extensions/thirdweb/write/publish.js.map +1 -0
  26. package/dist/esm/utils/arrays.js +20 -0
  27. package/dist/esm/utils/arrays.js.map +1 -0
  28. package/dist/esm/utils/semver.js +62 -0
  29. package/dist/esm/utils/semver.js.map +1 -0
  30. package/dist/esm/version.js +1 -1
  31. package/dist/types/contract/deployment/publisher.d.ts +1 -0
  32. package/dist/types/contract/deployment/publisher.d.ts.map +1 -1
  33. package/dist/types/exports/extensions/thirdweb.d.ts +1 -1
  34. package/dist/types/exports/extensions/thirdweb.d.ts.map +1 -1
  35. package/dist/types/exports/modules.d.ts +2 -1
  36. package/dist/types/exports/modules.d.ts.map +1 -1
  37. package/dist/types/extensions/modules/common/checkModulesCompatibility.d.ts +9 -0
  38. package/dist/types/extensions/modules/common/checkModulesCompatibility.d.ts.map +1 -0
  39. package/dist/types/extensions/thirdweb/write/publish.d.ts +29 -0
  40. package/dist/types/extensions/thirdweb/write/publish.d.ts.map +1 -0
  41. package/dist/types/utils/any-evm/deploy-metadata.d.ts +18 -1
  42. package/dist/types/utils/any-evm/deploy-metadata.d.ts.map +1 -1
  43. package/dist/types/utils/arrays.d.ts +5 -0
  44. package/dist/types/utils/arrays.d.ts.map +1 -0
  45. package/dist/types/utils/semver.d.ts +25 -0
  46. package/dist/types/utils/semver.d.ts.map +1 -0
  47. package/dist/types/version.d.ts +1 -1
  48. package/package.json +23 -23
  49. package/src/contract/deployment/publisher.ts +2 -1
  50. package/src/exports/extensions/thirdweb.ts +6 -4
  51. package/src/exports/modules.ts +5 -1
  52. package/src/extensions/erc721/lazyMinting/write/createAndReveal.test.ts +1 -1
  53. package/src/extensions/modules/MintableERC1155/mintableERC1155.test.ts +1 -1
  54. package/src/extensions/modules/MintableERC20/mintableERC20.test.ts +1 -1
  55. package/src/extensions/modules/MintableERC721/mintableERC721.test.ts +1 -1
  56. package/src/extensions/modules/common/checkModulesCompatibility.test.ts +46 -0
  57. package/src/extensions/modules/common/checkModulesCompatibility.ts +123 -0
  58. package/src/extensions/thirdweb/write/publish.test.ts +162 -0
  59. package/src/extensions/thirdweb/write/publish.ts +131 -0
  60. package/src/utils/any-evm/deploy-metadata.ts +18 -4
  61. package/src/utils/arrays.ts +23 -0
  62. package/src/utils/semver.ts +76 -0
  63. package/src/version.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thirdweb",
3
- "version": "5.56.0-nightly-07b949dd8c07ffdeda40a5549c31ad4b09abbbf1-20240916000437",
3
+ "version": "5.56.0-nightly-a5e605c65e360a9d3d2e553d6783e58582b50a70-20240917000331",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/thirdweb-dev/js.git#main"
@@ -210,7 +210,7 @@
210
210
  "@radix-ui/react-focus-scope": "1.1.0",
211
211
  "@radix-ui/react-icons": "1.3.0",
212
212
  "@radix-ui/react-tooltip": "1.1.2",
213
- "@tanstack/react-query": "5.55.4",
213
+ "@tanstack/react-query": "5.56.2",
214
214
  "@walletconnect/ethereum-provider": "2.16.1",
215
215
  "@walletconnect/sign-client": "2.16.1",
216
216
  "abitype": "1.0.5",
@@ -220,13 +220,13 @@
220
220
  "mipd": "0.0.7",
221
221
  "node-libs-browser": "2.2.1",
222
222
  "uqr": "0.1.2",
223
- "viem": "2.21.4"
223
+ "viem": "2.21.7"
224
224
  },
225
225
  "peerDependencies": {
226
226
  "@aws-sdk/client-lambda": "^3",
227
227
  "@aws-sdk/credential-providers": "^3",
228
228
  "@coinbase/wallet-mobile-sdk": "^1",
229
- "@mobile-wallet-protocol/client": "^0.0.2",
229
+ "@mobile-wallet-protocol/client": "0.0.3",
230
230
  "@react-native-async-storage/async-storage": "^1 || ^2",
231
231
  "amazon-cognito-identity-js": "^6",
232
232
  "aws-amplify": "^5",
@@ -297,28 +297,28 @@
297
297
  "node": ">=18"
298
298
  },
299
299
  "devDependencies": {
300
- "@aws-sdk/client-lambda": "3.592.0",
301
- "@aws-sdk/credential-providers": "3.592.0",
302
- "@chromatic-com/storybook": "^1.5.0",
300
+ "@aws-sdk/client-lambda": "3.651.1",
301
+ "@aws-sdk/credential-providers": "3.651.1",
302
+ "@chromatic-com/storybook": "2.0.2",
303
303
  "@codspeed/vitest-plugin": "3.1.1",
304
- "@coinbase/wallet-mobile-sdk": "1.0.13",
304
+ "@coinbase/wallet-mobile-sdk": "1.1.2",
305
305
  "@mobile-wallet-protocol/client": "0.0.2",
306
306
  "@react-native-async-storage/async-storage": "1.24.0",
307
- "@storybook/addon-essentials": "^8.2.9",
308
- "@storybook/addon-interactions": "^8.2.9",
309
- "@storybook/addon-links": "^8.2.9",
310
- "@storybook/addon-onboarding": "^8.2.9",
311
- "@storybook/blocks": "^8.2.9",
312
- "@storybook/react": "^8.2.9",
313
- "@storybook/react-vite": "^8.2.9",
314
- "@storybook/test": "^8.2.9",
307
+ "@storybook/addon-essentials": "8.3.0",
308
+ "@storybook/addon-interactions": "8.3.0",
309
+ "@storybook/addon-links": "8.3.0",
310
+ "@storybook/addon-onboarding": "8.3.0",
311
+ "@storybook/blocks": "8.3.0",
312
+ "@storybook/react": "8.3.0",
313
+ "@storybook/react-vite": "8.3.0",
314
+ "@storybook/test": "8.3.0",
315
315
  "@testing-library/jest-dom": "^6.4.7",
316
316
  "@testing-library/react": "^16.0.0",
317
317
  "@testing-library/user-event": "^14.5.2",
318
318
  "@types/cross-spawn": "^6.0.6",
319
319
  "@types/react": "^18.3.5",
320
320
  "@vitejs/plugin-react": "^4.3.1",
321
- "@vitest/ui": "2.0.5",
321
+ "@vitest/ui": "2.1.1",
322
322
  "amazon-cognito-identity-js": "6.3.12",
323
323
  "aws-amplify": "5.3.19",
324
324
  "cross-spawn": "7.0.3",
@@ -327,15 +327,15 @@
327
327
  "expo-linking": "6.3.1",
328
328
  "expo-web-browser": "13.0.3",
329
329
  "happy-dom": "^14.12.0",
330
- "msw": "2.4.4",
331
- "react-native": "0.75.2",
330
+ "msw": "2.4.7",
331
+ "react-native": "0.75.3",
332
332
  "react-native-aes-gcm-crypto": "0.2.2",
333
333
  "react-native-passkey": "3.0.0-beta2",
334
- "react-native-quick-crypto": "0.7.0-rc.6",
335
- "react-native-svg": "15.3.0",
336
- "storybook": "^8.2.9",
334
+ "react-native-quick-crypto": "0.7.4",
335
+ "react-native-svg": "15.6.0",
336
+ "storybook": "8.3.0",
337
337
  "typescript": "5.6.2",
338
- "vite": "5.4.3"
338
+ "vite": "5.4.5"
339
339
  },
340
340
  "scripts": {
341
341
  "bench:compare": "bun run ./benchmarks/run.ts",
@@ -12,7 +12,8 @@ import { withCache } from "../../utils/promise/withCache.js";
12
12
 
13
13
  import { type ThirdwebContract, getContract } from "../contract.js";
14
14
 
15
- const CONTRACT_PUBLISHER_ADDRESS = "0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808"; // Polygon only
15
+ export const CONTRACT_PUBLISHER_ADDRESS =
16
+ "0xf5b896Ddb5146D5dA77efF4efBb3Eae36E300808"; // Polygon only
16
17
  export const THIRDWEB_DEPLOYER = "0xdd99b75f095d0c4d5112aCe938e4e6ed962fb024";
17
18
 
18
19
  /**
@@ -35,10 +35,6 @@ export {
35
35
  } from "../../extensions/thirdweb/__generated__/IContractPublisher/read/getPublishedContractVersions.js";
36
36
 
37
37
  // Write
38
- export {
39
- publishContract,
40
- type PublishContractParams,
41
- } from "../../extensions/thirdweb/__generated__/IContractPublisher/write/publishContract.js";
42
38
  export {
43
39
  setPublisherProfileUri,
44
40
  type SetPublisherProfileUriParams,
@@ -48,6 +44,12 @@ export {
48
44
  type UnpublishContractParams,
49
45
  } from "../../extensions/thirdweb/__generated__/IContractPublisher/write/unpublishContract.js";
50
46
 
47
+ export {
48
+ publishContract,
49
+ type PublishContractParams,
50
+ getContractPublisher,
51
+ } from "../../extensions/thirdweb/write/publish.js";
52
+
51
53
  // --------------------------------------------------------
52
54
  // Multichain Registry
53
55
  // --------------------------------------------------------
@@ -121,6 +121,7 @@ export {
121
121
  rolesOf,
122
122
  type RolesOfParams,
123
123
  } from "../extensions/modules/__generated__/OwnableRoles/read/rolesOf.js";
124
+ export { checkModulesCompatibility } from "../extensions/modules/common/checkModulesCompatibility.js";
124
125
 
125
126
  /**
126
127
  * Write
@@ -158,7 +159,10 @@ export {
158
159
  isUninstallModuleSupported,
159
160
  type UninstallModuleParams,
160
161
  } from "../extensions/modules/__generated__/IModularCore/write/uninstallModule.js";
161
- export { getModuleConfig } from "../extensions/modules/__generated__/IModule/read/getModuleConfig.js";
162
+ export {
163
+ getModuleConfig,
164
+ isGetModuleConfigSupported,
165
+ } from "../extensions/modules/__generated__/IModule/read/getModuleConfig.js";
162
166
  export {
163
167
  installPublishedModule,
164
168
  type InstallPublishedModuleOptions,
@@ -29,7 +29,7 @@ const realNFTs = [
29
29
  },
30
30
  ];
31
31
 
32
- describe("createAndReveal", () => {
32
+ describe.runIf(process.env.TW_SECRET_KEY)("createAndReveal", () => {
33
33
  let contract: ThirdwebContract;
34
34
  beforeAll(async () => {
35
35
  const address = await deployERC721Contract({
@@ -21,7 +21,7 @@ import { getInstalledModules } from "../__generated__/IModularCore/read/getInsta
21
21
  import { grantMinterRole } from "../common/grantMinterRole.js";
22
22
  import * as MintableERC1155 from "./index.js";
23
23
 
24
- describe("ModularTokenERC1155", () => {
24
+ describe.runIf(process.env.TW_SECRET_KEY)("ModularTokenERC1155", () => {
25
25
  let contract: ThirdwebContract;
26
26
  beforeAll(async () => {
27
27
  const address = await deployModularContract({
@@ -16,7 +16,7 @@ import { getInstalledModules } from "../__generated__/IModularCore/read/getInsta
16
16
  import { grantMinterRole } from "../common/grantMinterRole.js";
17
17
  import * as MintableERC20 from "./index.js";
18
18
 
19
- describe("ModularTokenERC20", () => {
19
+ describe.runIf(process.env.TW_SECRET_KEY)("ModularTokenERC20", () => {
20
20
  let contract: ThirdwebContract;
21
21
  beforeAll(async () => {
22
22
  const address = await deployModularContract({
@@ -20,7 +20,7 @@ import { getInstalledModules } from "../__generated__/IModularCore/read/getInsta
20
20
  import { grantMinterRole } from "../common/grantMinterRole.js";
21
21
  import * as MintableERC721 from "./index.js";
22
22
 
23
- describe("ModularTokenERC721", () => {
23
+ describe.runIf(process.env.TW_SECRET_KEY)("ModularTokenERC721", () => {
24
24
  let contract: ThirdwebContract;
25
25
  beforeAll(async () => {
26
26
  const address = await deployModularContract({
@@ -0,0 +1,46 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { CLAIMABLE_ERC20_MODULE_BYTECODE } from "../../../../test/src/bytecode/claimable-erc20.js";
3
+ import { CLAIMABLE_ERC721_BYTECODE } from "../../../../test/src/bytecode/claimable-erc721.js";
4
+ import { ERC20_CORE_BYTECODE } from "../../../../test/src/bytecode/erc20core.js";
5
+ import { MINTABLE_ERC20_BYTECODE } from "../../../../test/src/bytecode/mintable-erc20.js";
6
+ import { ANVIL_CHAIN } from "../../../../test/src/chains.js";
7
+ import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
8
+ import { checkModulesCompatibility } from "./checkModulesCompatibility.js";
9
+
10
+ describe("compatibleModules", () => {
11
+ it("should return true for compatible modules", async () => {
12
+ const result = await checkModulesCompatibility({
13
+ coreBytecode: ERC20_CORE_BYTECODE,
14
+ moduleBytecodes: [CLAIMABLE_ERC20_MODULE_BYTECODE],
15
+ chain: ANVIL_CHAIN,
16
+ client: TEST_CLIENT,
17
+ });
18
+
19
+ expect(result).toBe(true);
20
+ });
21
+
22
+ it("should return false for incompatible modules", async () => {
23
+ const result = await checkModulesCompatibility({
24
+ coreBytecode: ERC20_CORE_BYTECODE,
25
+ moduleBytecodes: [CLAIMABLE_ERC721_BYTECODE],
26
+ chain: ANVIL_CHAIN,
27
+ client: TEST_CLIENT,
28
+ });
29
+
30
+ expect(result).toBe(false);
31
+ });
32
+
33
+ it("should return false for overlapping modules", async () => {
34
+ const result = await checkModulesCompatibility({
35
+ coreBytecode: ERC20_CORE_BYTECODE,
36
+ moduleBytecodes: [
37
+ CLAIMABLE_ERC20_MODULE_BYTECODE,
38
+ MINTABLE_ERC20_BYTECODE,
39
+ ],
40
+ chain: ANVIL_CHAIN,
41
+ client: TEST_CLIENT,
42
+ });
43
+
44
+ expect(result).toBe(false);
45
+ });
46
+ });
@@ -0,0 +1,123 @@
1
+ import {} from "abitype";
2
+ import type { Chain } from "../../../chains/types.js";
3
+ import type { ThirdwebClient } from "../../../client/client.js";
4
+ import { eth_call } from "../../../rpc/actions/eth_call.js";
5
+ import { getRpcClient } from "../../../rpc/rpc.js";
6
+ import { hasDuplicates } from "../../../utils/arrays.js";
7
+ import { ensureBytecodePrefix } from "../../../utils/bytecode/prefix.js";
8
+ import type { Hex } from "../../../utils/encoding/hex.js";
9
+ import {
10
+ decodeSupportsInterfaceResult,
11
+ encodeSupportsInterface,
12
+ } from "../../erc165/__generated__/IERC165/read/supportsInterface.js";
13
+ import {
14
+ decodeGetSupportedCallbackFunctionsResult,
15
+ FN_SELECTOR as getSupportedCallbackFunctionsSelector,
16
+ } from "../__generated__/IModularCore/read/getSupportedCallbackFunctions.js";
17
+ import {
18
+ decodeGetModuleConfigResult,
19
+ FN_SELECTOR as getModuleConfigSelector,
20
+ } from "../__generated__/IModule/read/getModuleConfig.js";
21
+
22
+ export async function checkModulesCompatibility(options: {
23
+ coreBytecode: string;
24
+ moduleBytecodes: string[];
25
+ chain: Chain;
26
+ client: ThirdwebClient;
27
+ }): Promise<boolean> {
28
+ const addr = "0x0000000000000000000000000000000000000124"; // arbitrary address
29
+ let _coreBytecode = ensureBytecodePrefix(options.coreBytecode);
30
+ if (!_coreBytecode.startsWith("0x6080604052")) {
31
+ const index = _coreBytecode.indexOf("6080604052");
32
+ _coreBytecode = `0x${_coreBytecode.substring(index)}`;
33
+ } else if (_coreBytecode.lastIndexOf("6080604052") > 0) {
34
+ const index = _coreBytecode.lastIndexOf("6080604052");
35
+ _coreBytecode = `0x${_coreBytecode.substring(index)}`;
36
+ }
37
+ const rpcClient = getRpcClient({
38
+ client: options.client,
39
+ chain: options.chain,
40
+ });
41
+
42
+ // get the core's supported callback functions
43
+ const coreCallResult = await eth_call(rpcClient, {
44
+ data: getSupportedCallbackFunctionsSelector,
45
+ to: addr,
46
+ stateOverrides: {
47
+ [addr]: {
48
+ code: _coreBytecode,
49
+ },
50
+ },
51
+ });
52
+
53
+ const decodedCallResult =
54
+ decodeGetSupportedCallbackFunctionsResult(coreCallResult);
55
+ const coreCallbackSelectors = decodedCallResult.flat().map((c) => c.selector);
56
+
57
+ // get the module config for each module
58
+ const modules = await Promise.all(
59
+ options.moduleBytecodes.map(async (b: string) => {
60
+ // TODO: Upload deployed bytecode on publish metadata
61
+ let moduleBytecode = ensureBytecodePrefix(b);
62
+ if (!moduleBytecode.startsWith("0x6080604052")) {
63
+ const index = moduleBytecode.indexOf("6080604052");
64
+ moduleBytecode = `0x${moduleBytecode.substring(index)}`;
65
+ } else if (moduleBytecode.lastIndexOf("6080604052") > 0) {
66
+ const index = moduleBytecode.lastIndexOf("6080604052");
67
+ moduleBytecode = `0x${moduleBytecode.substring(index)}`;
68
+ }
69
+
70
+ const callResult = await eth_call(rpcClient, {
71
+ data: getModuleConfigSelector,
72
+ to: addr,
73
+ stateOverrides: {
74
+ [addr]: {
75
+ code: moduleBytecode,
76
+ },
77
+ },
78
+ });
79
+ return decodeGetModuleConfigResult(callResult);
80
+ }),
81
+ );
82
+
83
+ // check if callback selectors are supported
84
+ for (const module of modules) {
85
+ for (const callback of module.callbackFunctions) {
86
+ if (!coreCallbackSelectors.includes(callback.selector)) {
87
+ return false;
88
+ }
89
+ }
90
+ }
91
+
92
+ // check if the core contract supports required interfaces by modules above
93
+ const requiredInterfaces = modules.flatMap((m) => m.requiredInterfaces);
94
+ if (requiredInterfaces.length > 0) {
95
+ const supportsInterfaceResult = await Promise.all(
96
+ requiredInterfaces.map(async (r) => {
97
+ const callResult = await eth_call(rpcClient, {
98
+ data: encodeSupportsInterface({
99
+ interfaceId: r,
100
+ }),
101
+ to: addr,
102
+ stateOverrides: {
103
+ [addr]: {
104
+ code: _coreBytecode,
105
+ },
106
+ },
107
+ });
108
+ return decodeSupportsInterfaceResult(callResult);
109
+ }),
110
+ );
111
+
112
+ if (supportsInterfaceResult.flat().some((element) => element === false)) {
113
+ return false;
114
+ }
115
+ }
116
+ return !hasDuplicates(
117
+ [
118
+ ...modules.flatMap((m) => m.callbackFunctions.map((c) => c.selector)),
119
+ ...modules.flatMap((m) => m.fallbackFunctions.map((f) => f.selector)),
120
+ ],
121
+ (a: Hex | undefined, b: Hex | undefined): boolean => a === b,
122
+ );
123
+ }
@@ -0,0 +1,162 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { FORKED_POLYGON_CHAIN } from "../../../../test/src/chains.js";
3
+ import { TEST_CLIENT } from "../../../../test/src/test-clients.js";
4
+ import { TEST_ACCOUNT_D } from "../../../../test/src/test-wallets.js";
5
+ import { getContract } from "../../../contract/contract.js";
6
+ import { CONTRACT_PUBLISHER_ADDRESS } from "../../../contract/deployment/publisher.js";
7
+ import { parseEventLogs } from "../../../event/actions/parse-logs.js";
8
+ import { download } from "../../../storage/download.js";
9
+ import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
10
+ import { fetchDeployMetadata } from "../../../utils/any-evm/deploy-metadata.js";
11
+ import { contractPublishedEvent } from "../__generated__/IContractPublisher/events/ContractPublished.js";
12
+ import { getAllPublishedContracts } from "../__generated__/IContractPublisher/read/getAllPublishedContracts.js";
13
+ import { getPublishedContractVersions } from "../__generated__/IContractPublisher/read/getPublishedContractVersions.js";
14
+ import { publishContract } from "./publish.js";
15
+
16
+ describe.runIf(process.env.TW_SECRET_KEY)("publishContract", () => {
17
+ it("should publish a contract successfully", async () => {
18
+ const publisherContract = getContract({
19
+ client: TEST_CLIENT,
20
+ chain: FORKED_POLYGON_CHAIN,
21
+ address: CONTRACT_PUBLISHER_ADDRESS,
22
+ });
23
+
24
+ let publishedContracts = await getAllPublishedContracts({
25
+ contract: publisherContract,
26
+ publisher: TEST_ACCOUNT_D.address,
27
+ });
28
+
29
+ expect(publishedContracts.length).toBe(0);
30
+
31
+ const catAttackDeployMetadata = await fetchDeployMetadata({
32
+ client: TEST_CLIENT,
33
+ uri: "ipfs://QmWcAMvBy49WRrzZeK4EQeVnkdmyb5H4STz4gUQwnt1kzC",
34
+ });
35
+
36
+ const tx = publishContract({
37
+ contract: publisherContract,
38
+ account: TEST_ACCOUNT_D,
39
+ metadata: {
40
+ ...catAttackDeployMetadata,
41
+ version: "0.0.1",
42
+ description: "Cat Attack NFT",
43
+ changelog: "Initial release",
44
+ },
45
+ });
46
+ const result = await sendAndConfirmTransaction({
47
+ transaction: tx,
48
+ account: TEST_ACCOUNT_D,
49
+ });
50
+ expect(result.transactionHash.length).toBeGreaterThan(0);
51
+ const logs = parseEventLogs({
52
+ events: [contractPublishedEvent()],
53
+ logs: result.logs,
54
+ });
55
+ expect(logs?.[0]?.args.publishedContract.contractId).toBe("CatAttackNFT");
56
+ expect(logs?.[0]?.args.publishedContract.publishMetadataUri).toBeDefined();
57
+ const rawMeta = await download({
58
+ client: TEST_CLIENT,
59
+ uri: logs?.[0]?.args.publishedContract.publishMetadataUri ?? "",
60
+ }).then((r) => r.json());
61
+ expect(rawMeta).toMatchInlineSnapshot(`
62
+ {
63
+ "bytecodeUri": "ipfs://QmVyB9qAs7XdZYNGPcNbff43BX1tyZFJkqdfp1eXiNS8AG/0",
64
+ "changelog": "Initial release",
65
+ "compilers": {
66
+ "solc": [
67
+ {
68
+ "bytecodeUri": "ipfs://QmVyB9qAs7XdZYNGPcNbff43BX1tyZFJkqdfp1eXiNS8AG/0",
69
+ "compilerVersion": "",
70
+ "evmVersion": "",
71
+ "metadataUri": "ipfs://Qmd2Ef29NzCjomqYXZbWa8ZdF1AESDAS1HDAonmAnTgHPs",
72
+ },
73
+ ],
74
+ },
75
+ "description": "Cat Attack NFT",
76
+ "metadataUri": "ipfs://Qmd2Ef29NzCjomqYXZbWa8ZdF1AESDAS1HDAonmAnTgHPs",
77
+ "name": "CatAttackNFT",
78
+ "publisher": "0x90F79bf6EB2c4f870365E785982E1f101E93b906",
79
+ "routerType": "none",
80
+ "version": "0.0.1",
81
+ }
82
+ `);
83
+ const publishedData = await fetchDeployMetadata({
84
+ client: TEST_CLIENT,
85
+ uri: logs?.[0]?.args.publishedContract.publishMetadataUri ?? "",
86
+ });
87
+ expect(publishedData.abi).toBeDefined();
88
+ expect(publishedData.bytecode).toBeDefined();
89
+ expect(publishedData.version).toBe("0.0.1");
90
+ expect(publishedData.changelog).toBe("Initial release");
91
+ expect(publishedData.name).toBe("CatAttackNFT");
92
+ expect(publishedData.description).toBe("Cat Attack NFT");
93
+ expect(publishedData.publisher).toBe(TEST_ACCOUNT_D.address);
94
+ expect(publishedData.routerType).toBe("none");
95
+
96
+ publishedContracts = await getAllPublishedContracts({
97
+ contract: publisherContract,
98
+ publisher: TEST_ACCOUNT_D.address,
99
+ });
100
+
101
+ expect(publishedContracts.length).toBe(1);
102
+
103
+ expect(
104
+ sendAndConfirmTransaction({
105
+ account: TEST_ACCOUNT_D,
106
+ transaction: publishContract({
107
+ contract: publisherContract,
108
+ account: TEST_ACCOUNT_D,
109
+ previousMetadata: publishedData,
110
+ metadata: {
111
+ ...publishedData,
112
+ version: "0.0.1",
113
+ changelog: "Initial release 2",
114
+ },
115
+ }),
116
+ }),
117
+ ).rejects.toThrow("Version 0.0.1 is not greater than 0.0.1");
118
+
119
+ const tx2 = publishContract({
120
+ contract: publisherContract,
121
+ account: TEST_ACCOUNT_D,
122
+ previousMetadata: publishedData,
123
+ metadata: {
124
+ ...publishedData,
125
+ version: "0.0.2",
126
+ changelog: "Initial release 2",
127
+ },
128
+ });
129
+ const result2 = await sendAndConfirmTransaction({
130
+ transaction: tx2,
131
+ account: TEST_ACCOUNT_D,
132
+ });
133
+
134
+ expect(result2.transactionHash.length).toBeGreaterThan(0);
135
+ const logs2 = parseEventLogs({
136
+ events: [contractPublishedEvent()],
137
+ logs: result2.logs,
138
+ });
139
+ expect(logs2?.[0]?.args.publishedContract.contractId).toBe("CatAttackNFT");
140
+ expect(logs2?.[0]?.args.publishedContract.publishMetadataUri).toBeDefined();
141
+ const publishedData2 = await fetchDeployMetadata({
142
+ client: TEST_CLIENT,
143
+ uri: logs2?.[0]?.args.publishedContract.publishMetadataUri ?? "",
144
+ });
145
+ expect(publishedData2.version).toBe("0.0.2");
146
+
147
+ publishedContracts = await getAllPublishedContracts({
148
+ contract: publisherContract,
149
+ publisher: TEST_ACCOUNT_D.address,
150
+ });
151
+
152
+ expect(publishedContracts.length).toBe(1);
153
+
154
+ const versions = await getPublishedContractVersions({
155
+ contract: publisherContract,
156
+ contractId: "CatAttackNFT",
157
+ publisher: TEST_ACCOUNT_D.address,
158
+ });
159
+
160
+ expect(versions.length).toEqual(2);
161
+ }, 120000);
162
+ });
@@ -0,0 +1,131 @@
1
+ import type { Abi } from "abitype";
2
+ import { encodePacked, keccak256, toFunctionSelector } from "viem/utils";
3
+ import { polygon } from "../../../chains/chain-definitions/polygon.js";
4
+ import type { ThirdwebClient } from "../../../client/client.js";
5
+ import { ZERO_ADDRESS } from "../../../constants/addresses.js";
6
+ import { getContract } from "../../../contract/contract.js";
7
+ import { CONTRACT_PUBLISHER_ADDRESS } from "../../../contract/deployment/publisher.js";
8
+ import { download } from "../../../storage/download.js";
9
+ import { upload } from "../../../storage/upload.js";
10
+ import type { BaseTransactionOptions } from "../../../transaction/types.js";
11
+ import type {
12
+ ExtendedMetadata,
13
+ FetchDeployMetadataResult,
14
+ } from "../../../utils/any-evm/deploy-metadata.js";
15
+ import { ensureBytecodePrefix } from "../../../utils/bytecode/prefix.js";
16
+ import { isIncrementalVersion } from "../../../utils/semver.js";
17
+ import type { Account } from "../../../wallets/interfaces/wallet.js";
18
+ import { isGetInstalledModulesSupported } from "../../modules/__generated__/IModularCore/read/getInstalledModules.js";
19
+ import { publishContract as generatedPublishContract } from "../__generated__/IContractPublisher/write/publishContract.js";
20
+
21
+ export type PublishContractParams = {
22
+ account: Account;
23
+ metadata: FetchDeployMetadataResult & {
24
+ version: string;
25
+ };
26
+ previousMetadata?: FetchDeployMetadataResult;
27
+ };
28
+
29
+ /**
30
+ * Publish a contract to the contract publisher.
31
+ *
32
+ * @param options - The options for publishing the contract.
33
+ * @returns The transaction to publish the contract.
34
+ * @example
35
+ * ```ts
36
+ * const tx = publishContract({
37
+ * contract,
38
+ * account,
39
+ * metadata,
40
+ * });
41
+ * ```
42
+ * @extension thirdweb
43
+ */
44
+ export function publishContract(
45
+ options: BaseTransactionOptions<PublishContractParams>,
46
+ ) {
47
+ return generatedPublishContract({
48
+ contract: options.contract,
49
+ async asyncParams() {
50
+ const currentVersion = options.previousMetadata?.version;
51
+ // check if the version is greater than the current version
52
+ if (
53
+ currentVersion &&
54
+ !isIncrementalVersion(currentVersion, options.metadata.version)
55
+ ) {
56
+ throw Error(
57
+ `Version ${options.metadata.version} is not greater than ${currentVersion}`,
58
+ );
59
+ }
60
+ // hash the bytecode
61
+ const bytecode = await download({
62
+ client: options.contract.client,
63
+ uri: options.metadata.bytecodeUri,
64
+ }).then((r) => r.text());
65
+ const bytecodeHash = keccak256(
66
+ encodePacked(["bytes"], [ensureBytecodePrefix(bytecode)]),
67
+ );
68
+
69
+ const abi = options.metadata.abi;
70
+ const routerType = getRouterType(abi);
71
+ // not spreading here, we don't want to re-upload the fetched data like bytecode
72
+ const newMetadata: ExtendedMetadata = {
73
+ bytecodeUri: options.metadata.bytecodeUri,
74
+ metadataUri: options.metadata.metadataUri,
75
+ name: options.metadata.name,
76
+ version: options.metadata.version,
77
+ audit: options.metadata.audit,
78
+ changelog: options.metadata.changelog,
79
+ compositeAbi: options.metadata.compositeAbi,
80
+ constructorParams: options.metadata.constructorParams,
81
+ defaultExtensions: options.metadata.defaultExtensions,
82
+ defaultModules: options.metadata.defaultModules,
83
+ deployType: options.metadata.deployType,
84
+ description: options.metadata.description,
85
+ displayName: options.metadata.displayName,
86
+ factoryDeploymentData: options.metadata.factoryDeploymentData,
87
+ isDeployableViaFactory: options.metadata.isDeployableViaFactory,
88
+ isDeployableViaProxy: options.metadata.isDeployableViaProxy,
89
+ logo: options.metadata.logo,
90
+ networksForDeployment: options.metadata.networksForDeployment,
91
+ readme: options.metadata.readme,
92
+ tags: options.metadata.tags,
93
+ compilers: options.metadata.compilers,
94
+ publisher: options.account.address,
95
+ routerType,
96
+ };
97
+
98
+ // upload the new metadata
99
+ const newMetadataUri = await upload({
100
+ client: options.contract.client,
101
+ files: [newMetadata],
102
+ });
103
+
104
+ return {
105
+ publisher: options.account.address,
106
+ contractId: options.metadata.name,
107
+ publishMetadataUri: newMetadataUri,
108
+ compilerMetadataUri: options.metadata.metadataUri,
109
+ bytecodeHash,
110
+ implementation: ZERO_ADDRESS,
111
+ };
112
+ },
113
+ });
114
+ }
115
+
116
+ export function getContractPublisher(client: ThirdwebClient) {
117
+ return getContract({
118
+ client,
119
+ chain: polygon,
120
+ address: CONTRACT_PUBLISHER_ADDRESS,
121
+ });
122
+ }
123
+
124
+ function getRouterType(abi: Abi) {
125
+ const fnSelectors = abi
126
+ .filter((f) => f.type === "function")
127
+ .map((f) => toFunctionSelector(f));
128
+ const isModule = isGetInstalledModulesSupported(fnSelectors);
129
+ // TODO add dynamic detection
130
+ return isModule ? "modular" : "none";
131
+ }