Skip to content

Commit 2ce8840

Browse files
feat: zksync deploys (#4346)
## Problem solved Short description of the bug fixed or feature added <!-- start pr-codex --> --- ## PR-Codex overview This PR increases the size limit for `thirdweb (cjs)` to 105 kB, adds an event `ContractDeployed`, and improves error handling and EIP712 transaction support. ### Detailed summary - Increased size limit for `thirdweb (cjs)` to 105 kB - Added `ContractDeployed` event - Improved error messages with detailed information - Added EIP712 transaction support for zksync - Refactored deployment address computation and metadata handling > The following files were skipped due to too many changes: `packages/thirdweb/src/contract/deployment/zksync/zkDeployContract.ts`, `packages/thirdweb/src/transaction/actions/zksync/send-eip712-transaction.test.ts`, `packages/thirdweb/src/contract/deployment/zksync/zkDeployProxy.ts`, `packages/thirdweb/package.json`, `packages/thirdweb/src/contract/deployment/zksync/zkDeployCreate2Factory.ts`, `packages/thirdweb/src/contract/deployment/utils/bootstrap.ts`, `packages/thirdweb/src/contract/deployment/zksync/zkDeployDeterministic.ts`, `packages/thirdweb/src/extensions/prebuilts/deploy-published.ts`, `packages/thirdweb/src/utils/any-evm/zksync/constants.ts`, `pnpm-lock.yaml` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 4ad5f7f commit 2ce8840

27 files changed

+920
-358
lines changed

packages/thirdweb/.size-limit.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
{
99
"name": "thirdweb (cjs)",
1010
"path": "./dist/cjs/exports/thirdweb.js",
11-
"limit": "100 kB"
11+
"limit": "105 kB"
1212
},
1313
{
1414
"name": "thirdweb (minimal + tree-shaking)",

packages/thirdweb/package.json

Lines changed: 16 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -117,54 +117,22 @@
117117
},
118118
"typesVersions": {
119119
"*": {
120-
"adapters/*": [
121-
"./dist/types/exports/adapters/*.d.ts"
122-
],
123-
"auth": [
124-
"./dist/types/exports/auth.d.ts"
125-
],
126-
"chains": [
127-
"./dist/types/exports/chains.d.ts"
128-
],
129-
"contract": [
130-
"./dist/types/exports/contract.d.ts"
131-
],
132-
"deploys": [
133-
"./dist/types/exports/deploys.d.ts"
134-
],
135-
"event": [
136-
"./dist/types/exports/event.d.ts"
137-
],
138-
"extensions/*": [
139-
"./dist/types/exports/extensions/*.d.ts"
140-
],
141-
"pay": [
142-
"./dist/types/exports/pay.d.ts"
143-
],
144-
"react": [
145-
"./dist/types/exports/react.d.ts"
146-
],
147-
"react-native": [
148-
"./dist/types/exports/react-native.d.ts"
149-
],
150-
"rpc": [
151-
"./dist/types/exports/rpc.d.ts"
152-
],
153-
"storage": [
154-
"./dist/types/exports/storage.d.ts"
155-
],
156-
"transaction": [
157-
"./dist/types/exports/transaction.d.ts"
158-
],
159-
"utils": [
160-
"./dist/types/exports/utils.d.ts"
161-
],
162-
"wallets": [
163-
"./dist/types/exports/wallets.d.ts"
164-
],
165-
"wallets/*": [
166-
"./dist/types/exports/wallets/*.d.ts"
167-
]
120+
"adapters/*": ["./dist/types/exports/adapters/*.d.ts"],
121+
"auth": ["./dist/types/exports/auth.d.ts"],
122+
"chains": ["./dist/types/exports/chains.d.ts"],
123+
"contract": ["./dist/types/exports/contract.d.ts"],
124+
"deploys": ["./dist/types/exports/deploys.d.ts"],
125+
"event": ["./dist/types/exports/event.d.ts"],
126+
"extensions/*": ["./dist/types/exports/extensions/*.d.ts"],
127+
"pay": ["./dist/types/exports/pay.d.ts"],
128+
"react": ["./dist/types/exports/react.d.ts"],
129+
"react-native": ["./dist/types/exports/react-native.d.ts"],
130+
"rpc": ["./dist/types/exports/rpc.d.ts"],
131+
"storage": ["./dist/types/exports/storage.d.ts"],
132+
"transaction": ["./dist/types/exports/transaction.d.ts"],
133+
"utils": ["./dist/types/exports/utils.d.ts"],
134+
"wallets": ["./dist/types/exports/wallets.d.ts"],
135+
"wallets/*": ["./dist/types/exports/wallets/*.d.ts"]
168136
}
169137
},
170138
"browser": {
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[
2+
"event ContractDeployed(address indexed deployerAddress, bytes32 indexed bytecodeHash, address indexed contractAddress)"
3+
]

packages/thirdweb/src/contract/deployment/deploy-via-autofactory.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import { encode } from "../../transaction/actions/encode.js";
77
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
88
import type { PreparedTransaction } from "../../transaction/prepare-transaction.js";
99
import { keccakId } from "../../utils/any-evm/keccak-id.js";
10+
import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js";
1011
import { toHex } from "../../utils/encoding/hex.js";
1112
import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js";
1213
import type {
1314
ClientAndChain,
1415
ClientAndChainAndAccount,
1516
} from "../../utils/types.js";
1617
import type { ThirdwebContract } from "../contract.js";
18+
import { zkDeployProxy } from "./zksync/zkDeployProxy.js";
1719

1820
/**
1921
* @internal
@@ -69,6 +71,17 @@ export async function deployViaAutoFactory(
6971
cloneFactoryContract,
7072
initializeTransaction,
7173
} = options;
74+
75+
if (isZkSyncChain(chain)) {
76+
return zkDeployProxy({
77+
chain,
78+
client,
79+
account,
80+
cloneFactoryContract,
81+
initializeTransaction,
82+
});
83+
}
84+
7285
const tx = prepareAutoFactoryDeployTransaction({
7386
chain,
7487
client,

packages/thirdweb/src/contract/deployment/publisher.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ export async function fetchPublishedContractMetadata(options: {
3030
contractId: options.contractId,
3131
version: options.version,
3232
});
33+
if (!publishedContract.publishMetadataUri) {
34+
throw new Error(
35+
`No published metadata URI found for ${options.contractId}`,
36+
);
37+
}
3338
return fetchDeployMetadata({
3439
client: options.client,
3540
uri: publishedContract.publishMetadataUri,

packages/thirdweb/src/contract/deployment/utils/bootstrap.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
2+
import { isZkSyncChain } from "../../../utils/any-evm/zksync/isZkSyncChain.js";
23
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
4+
import { type ThirdwebContract, getContract } from "../../contract.js";
5+
import { fetchPublishedContractMetadata } from "../publisher.js";
6+
import { zkDeployCreate2Factory } from "../zksync/zkDeployCreate2Factory.js";
7+
import { zkDeployContractDeterministic } from "../zksync/zkDeployDeterministic.js";
38
import { getDeployedCloneFactoryContract } from "./clone-factory.js";
49
import {
510
deployCreate2Factory,
@@ -19,10 +24,56 @@ export async function getOrDeployInfraForPublishedContract(
1924
contractId: string;
2025
constructorParams: unknown[];
2126
publisher?: string;
27+
version?: string;
2228
},
23-
) {
24-
const { chain, client, account, contractId, constructorParams, publisher } =
25-
args;
29+
): Promise<{
30+
cloneFactoryContract: ThirdwebContract;
31+
implementationContract: ThirdwebContract;
32+
}> {
33+
const {
34+
chain,
35+
client,
36+
account,
37+
contractId,
38+
constructorParams,
39+
publisher,
40+
version,
41+
} = args;
42+
43+
if (isZkSyncChain(chain)) {
44+
const cloneFactoryContract = await zkDeployCreate2Factory({
45+
chain,
46+
client,
47+
account,
48+
});
49+
const { compilerMetadata } = await fetchPublishedContractMetadata({
50+
client,
51+
contractId: `${contractId}_ZkSync`, // different contract id for zkSync
52+
publisher,
53+
version,
54+
});
55+
const implementationContract = await zkDeployContractDeterministic({
56+
chain,
57+
client,
58+
account,
59+
abi: compilerMetadata.abi,
60+
bytecode: compilerMetadata.bytecode,
61+
params: constructorParams,
62+
});
63+
return {
64+
cloneFactoryContract: getContract({
65+
address: cloneFactoryContract,
66+
chain,
67+
client,
68+
}),
69+
implementationContract: getContract({
70+
address: implementationContract,
71+
chain,
72+
client,
73+
}),
74+
};
75+
}
76+
2677
let [cloneFactoryContract, implementationContract] = await Promise.all([
2778
getDeployedCloneFactoryContract({
2879
chain,
@@ -34,6 +85,7 @@ export async function getOrDeployInfraForPublishedContract(
3485
contractId,
3586
constructorParams,
3687
publisher,
88+
version,
3789
}),
3890
]);
3991

@@ -51,6 +103,7 @@ export async function getOrDeployInfraForPublishedContract(
51103
contractId,
52104
constructorParams,
53105
publisher,
106+
version,
54107
});
55108
}
56109
return { cloneFactoryContract, implementationContract };
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { Abi } from "abitype";
2+
import { encodeDeployData } from "viem/zksync";
3+
import { parseEventLogs } from "../../../event/actions/parse-logs.js";
4+
import { contractDeployedEvent } from "../../../extensions/zksync/__generated__/ContractDeployer/events/ContractDeployed.js";
5+
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
6+
import { prepareTransaction } from "../../../transaction/prepare-transaction.js";
7+
import { CONTRACT_DEPLOYER_ADDRESS } from "../../../utils/any-evm/zksync/constants.js";
8+
import type { Hex } from "../../../utils/encoding/hex.js";
9+
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
10+
11+
/**
12+
* @internal
13+
*/
14+
export async function zkDeployContract(
15+
options: ClientAndChainAndAccount & {
16+
abi: Abi;
17+
bytecode: Hex;
18+
params?: unknown[];
19+
},
20+
) {
21+
const data = encodeDeployData({
22+
abi: options.abi,
23+
bytecode: options.bytecode,
24+
deploymentType: "create",
25+
args: options.params,
26+
});
27+
28+
const receipt = await sendAndConfirmTransaction({
29+
account: options.account,
30+
transaction: prepareTransaction({
31+
chain: options.chain,
32+
client: options.client,
33+
to: CONTRACT_DEPLOYER_ADDRESS,
34+
data,
35+
eip712: {
36+
factoryDeps: [options.bytecode],
37+
// TODO (zksync): allow passing in a paymaster
38+
},
39+
}),
40+
});
41+
42+
const events = parseEventLogs({
43+
logs: receipt.logs,
44+
events: [contractDeployedEvent()],
45+
});
46+
47+
const contractAddress = events[0]?.args.contractAddress;
48+
if (!contractAddress) {
49+
throw new Error("Contract creation failed");
50+
}
51+
return contractAddress;
52+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { parseAbi } from "abitype";
2+
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
3+
import { prepareTransaction } from "../../../transaction/prepare-transaction.js";
4+
import {
5+
PUBLISHED_PRIVATE_KEY,
6+
ZKSYNC_SINGLETON_FACTORY,
7+
singletonFactoryAbi,
8+
singletonFactoryBytecode,
9+
} from "../../../utils/any-evm/zksync/constants.js";
10+
import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js";
11+
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
12+
import { toWei } from "../../../utils/units.js";
13+
import { privateKeyToAccount } from "../../../wallets/private-key.js";
14+
import { getWalletBalance } from "../../../wallets/utils/getWalletBalance.js";
15+
import { zkDeployContract } from "./zkDeployContract.js";
16+
17+
/**
18+
* @internal
19+
*/
20+
export async function zkDeployCreate2Factory(
21+
options: ClientAndChainAndAccount,
22+
) {
23+
const isDeployed = await isContractDeployed({
24+
address: ZKSYNC_SINGLETON_FACTORY,
25+
chain: options.chain,
26+
client: options.client,
27+
});
28+
29+
if (isDeployed) {
30+
return ZKSYNC_SINGLETON_FACTORY;
31+
}
32+
33+
const create2Signer = privateKeyToAccount({
34+
client: options.client,
35+
privateKey: PUBLISHED_PRIVATE_KEY,
36+
});
37+
const valueToSend = toWei("0.01");
38+
const balance = await getWalletBalance({
39+
address: create2Signer.address,
40+
chain: options.chain,
41+
client: options.client,
42+
});
43+
44+
if (balance.value < valueToSend) {
45+
await sendAndConfirmTransaction({
46+
account: options.account,
47+
transaction: prepareTransaction({
48+
chain: options.chain,
49+
client: options.client,
50+
to: create2Signer.address,
51+
value: valueToSend,
52+
}),
53+
});
54+
}
55+
56+
await zkDeployContract({
57+
client: options.client,
58+
chain: options.chain,
59+
account: options.account,
60+
abi: parseAbi(singletonFactoryAbi),
61+
bytecode: singletonFactoryBytecode,
62+
});
63+
64+
return ZKSYNC_SINGLETON_FACTORY;
65+
}

0 commit comments

Comments
 (0)