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 -->
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
{
|
||||
"name": "thirdweb (cjs)",
|
||||
"path": "./dist/cjs/exports/thirdweb.js",
|
||||
"limit": "100 kB"
|
||||
"limit": "105 kB"
|
||||
},
|
||||
{
|
||||
"name": "thirdweb (minimal + tree-shaking)",
|
||||
|
||||
@@ -117,54 +117,22 @@
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"adapters/*": [
|
||||
"./dist/types/exports/adapters/*.d.ts"
|
||||
],
|
||||
"auth": [
|
||||
"./dist/types/exports/auth.d.ts"
|
||||
],
|
||||
"chains": [
|
||||
"./dist/types/exports/chains.d.ts"
|
||||
],
|
||||
"contract": [
|
||||
"./dist/types/exports/contract.d.ts"
|
||||
],
|
||||
"deploys": [
|
||||
"./dist/types/exports/deploys.d.ts"
|
||||
],
|
||||
"event": [
|
||||
"./dist/types/exports/event.d.ts"
|
||||
],
|
||||
"extensions/*": [
|
||||
"./dist/types/exports/extensions/*.d.ts"
|
||||
],
|
||||
"pay": [
|
||||
"./dist/types/exports/pay.d.ts"
|
||||
],
|
||||
"react": [
|
||||
"./dist/types/exports/react.d.ts"
|
||||
],
|
||||
"react-native": [
|
||||
"./dist/types/exports/react-native.d.ts"
|
||||
],
|
||||
"rpc": [
|
||||
"./dist/types/exports/rpc.d.ts"
|
||||
],
|
||||
"storage": [
|
||||
"./dist/types/exports/storage.d.ts"
|
||||
],
|
||||
"transaction": [
|
||||
"./dist/types/exports/transaction.d.ts"
|
||||
],
|
||||
"utils": [
|
||||
"./dist/types/exports/utils.d.ts"
|
||||
],
|
||||
"wallets": [
|
||||
"./dist/types/exports/wallets.d.ts"
|
||||
],
|
||||
"wallets/*": [
|
||||
"./dist/types/exports/wallets/*.d.ts"
|
||||
]
|
||||
"adapters/*": ["./dist/types/exports/adapters/*.d.ts"],
|
||||
"auth": ["./dist/types/exports/auth.d.ts"],
|
||||
"chains": ["./dist/types/exports/chains.d.ts"],
|
||||
"contract": ["./dist/types/exports/contract.d.ts"],
|
||||
"deploys": ["./dist/types/exports/deploys.d.ts"],
|
||||
"event": ["./dist/types/exports/event.d.ts"],
|
||||
"extensions/*": ["./dist/types/exports/extensions/*.d.ts"],
|
||||
"pay": ["./dist/types/exports/pay.d.ts"],
|
||||
"react": ["./dist/types/exports/react.d.ts"],
|
||||
"react-native": ["./dist/types/exports/react-native.d.ts"],
|
||||
"rpc": ["./dist/types/exports/rpc.d.ts"],
|
||||
"storage": ["./dist/types/exports/storage.d.ts"],
|
||||
"transaction": ["./dist/types/exports/transaction.d.ts"],
|
||||
"utils": ["./dist/types/exports/utils.d.ts"],
|
||||
"wallets": ["./dist/types/exports/wallets.d.ts"],
|
||||
"wallets/*": ["./dist/types/exports/wallets/*.d.ts"]
|
||||
}
|
||||
},
|
||||
"browser": {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[
|
||||
"event ContractDeployed(address indexed deployerAddress, bytes32 indexed bytecodeHash, address indexed contractAddress)"
|
||||
]
|
||||
@@ -7,6 +7,7 @@ import { encode } from "../../transaction/actions/encode.js";
|
||||
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import type { PreparedTransaction } from "../../transaction/prepare-transaction.js";
|
||||
import { keccakId } from "../../utils/any-evm/keccak-id.js";
|
||||
import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js";
|
||||
import { toHex } from "../../utils/encoding/hex.js";
|
||||
import { resolvePromisedValue } from "../../utils/promise/resolve-promised-value.js";
|
||||
import type {
|
||||
@@ -14,6 +15,7 @@ import type {
|
||||
ClientAndChainAndAccount,
|
||||
} from "../../utils/types.js";
|
||||
import type { ThirdwebContract } from "../contract.js";
|
||||
import { zkDeployProxy } from "./zksync/zkDeployProxy.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -69,6 +71,17 @@ export async function deployViaAutoFactory(
|
||||
cloneFactoryContract,
|
||||
initializeTransaction,
|
||||
} = options;
|
||||
|
||||
if (isZkSyncChain(chain)) {
|
||||
return zkDeployProxy({
|
||||
chain,
|
||||
client,
|
||||
account,
|
||||
cloneFactoryContract,
|
||||
initializeTransaction,
|
||||
});
|
||||
}
|
||||
|
||||
const tx = prepareAutoFactoryDeployTransaction({
|
||||
chain,
|
||||
client,
|
||||
|
||||
@@ -30,6 +30,11 @@ export async function fetchPublishedContractMetadata(options: {
|
||||
contractId: options.contractId,
|
||||
version: options.version,
|
||||
});
|
||||
if (!publishedContract.publishMetadataUri) {
|
||||
throw new Error(
|
||||
`No published metadata URI found for ${options.contractId}`,
|
||||
);
|
||||
}
|
||||
return fetchDeployMetadata({
|
||||
client: options.client,
|
||||
uri: publishedContract.publishMetadataUri,
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { isZkSyncChain } from "../../../utils/any-evm/zksync/isZkSyncChain.js";
|
||||
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
|
||||
import { type ThirdwebContract, getContract } from "../../contract.js";
|
||||
import { fetchPublishedContractMetadata } from "../publisher.js";
|
||||
import { zkDeployCreate2Factory } from "../zksync/zkDeployCreate2Factory.js";
|
||||
import { zkDeployContractDeterministic } from "../zksync/zkDeployDeterministic.js";
|
||||
import { getDeployedCloneFactoryContract } from "./clone-factory.js";
|
||||
import {
|
||||
deployCreate2Factory,
|
||||
@@ -19,10 +24,56 @@ export async function getOrDeployInfraForPublishedContract(
|
||||
contractId: string;
|
||||
constructorParams: unknown[];
|
||||
publisher?: string;
|
||||
version?: string;
|
||||
},
|
||||
) {
|
||||
const { chain, client, account, contractId, constructorParams, publisher } =
|
||||
args;
|
||||
): Promise<{
|
||||
cloneFactoryContract: ThirdwebContract;
|
||||
implementationContract: ThirdwebContract;
|
||||
}> {
|
||||
const {
|
||||
chain,
|
||||
client,
|
||||
account,
|
||||
contractId,
|
||||
constructorParams,
|
||||
publisher,
|
||||
version,
|
||||
} = args;
|
||||
|
||||
if (isZkSyncChain(chain)) {
|
||||
const cloneFactoryContract = await zkDeployCreate2Factory({
|
||||
chain,
|
||||
client,
|
||||
account,
|
||||
});
|
||||
const { compilerMetadata } = await fetchPublishedContractMetadata({
|
||||
client,
|
||||
contractId: `${contractId}_ZkSync`, // different contract id for zkSync
|
||||
publisher,
|
||||
version,
|
||||
});
|
||||
const implementationContract = await zkDeployContractDeterministic({
|
||||
chain,
|
||||
client,
|
||||
account,
|
||||
abi: compilerMetadata.abi,
|
||||
bytecode: compilerMetadata.bytecode,
|
||||
params: constructorParams,
|
||||
});
|
||||
return {
|
||||
cloneFactoryContract: getContract({
|
||||
address: cloneFactoryContract,
|
||||
chain,
|
||||
client,
|
||||
}),
|
||||
implementationContract: getContract({
|
||||
address: implementationContract,
|
||||
chain,
|
||||
client,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
let [cloneFactoryContract, implementationContract] = await Promise.all([
|
||||
getDeployedCloneFactoryContract({
|
||||
chain,
|
||||
@@ -34,6 +85,7 @@ export async function getOrDeployInfraForPublishedContract(
|
||||
contractId,
|
||||
constructorParams,
|
||||
publisher,
|
||||
version,
|
||||
}),
|
||||
]);
|
||||
|
||||
@@ -51,6 +103,7 @@ export async function getOrDeployInfraForPublishedContract(
|
||||
contractId,
|
||||
constructorParams,
|
||||
publisher,
|
||||
version,
|
||||
});
|
||||
}
|
||||
return { cloneFactoryContract, implementationContract };
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { Abi } from "abitype";
|
||||
import { encodeDeployData } from "viem/zksync";
|
||||
import { parseEventLogs } from "../../../event/actions/parse-logs.js";
|
||||
import { contractDeployedEvent } from "../../../extensions/zksync/__generated__/ContractDeployer/events/ContractDeployed.js";
|
||||
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { prepareTransaction } from "../../../transaction/prepare-transaction.js";
|
||||
import { CONTRACT_DEPLOYER_ADDRESS } from "../../../utils/any-evm/zksync/constants.js";
|
||||
import type { Hex } from "../../../utils/encoding/hex.js";
|
||||
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function zkDeployContract(
|
||||
options: ClientAndChainAndAccount & {
|
||||
abi: Abi;
|
||||
bytecode: Hex;
|
||||
params?: unknown[];
|
||||
},
|
||||
) {
|
||||
const data = encodeDeployData({
|
||||
abi: options.abi,
|
||||
bytecode: options.bytecode,
|
||||
deploymentType: "create",
|
||||
args: options.params,
|
||||
});
|
||||
|
||||
const receipt = await sendAndConfirmTransaction({
|
||||
account: options.account,
|
||||
transaction: prepareTransaction({
|
||||
chain: options.chain,
|
||||
client: options.client,
|
||||
to: CONTRACT_DEPLOYER_ADDRESS,
|
||||
data,
|
||||
eip712: {
|
||||
factoryDeps: [options.bytecode],
|
||||
// TODO (zksync): allow passing in a paymaster
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const events = parseEventLogs({
|
||||
logs: receipt.logs,
|
||||
events: [contractDeployedEvent()],
|
||||
});
|
||||
|
||||
const contractAddress = events[0]?.args.contractAddress;
|
||||
if (!contractAddress) {
|
||||
throw new Error("Contract creation failed");
|
||||
}
|
||||
return contractAddress;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { parseAbi } from "abitype";
|
||||
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { prepareTransaction } from "../../../transaction/prepare-transaction.js";
|
||||
import {
|
||||
PUBLISHED_PRIVATE_KEY,
|
||||
ZKSYNC_SINGLETON_FACTORY,
|
||||
singletonFactoryAbi,
|
||||
singletonFactoryBytecode,
|
||||
} from "../../../utils/any-evm/zksync/constants.js";
|
||||
import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js";
|
||||
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
|
||||
import { toWei } from "../../../utils/units.js";
|
||||
import { privateKeyToAccount } from "../../../wallets/private-key.js";
|
||||
import { getWalletBalance } from "../../../wallets/utils/getWalletBalance.js";
|
||||
import { zkDeployContract } from "./zkDeployContract.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function zkDeployCreate2Factory(
|
||||
options: ClientAndChainAndAccount,
|
||||
) {
|
||||
const isDeployed = await isContractDeployed({
|
||||
address: ZKSYNC_SINGLETON_FACTORY,
|
||||
chain: options.chain,
|
||||
client: options.client,
|
||||
});
|
||||
|
||||
if (isDeployed) {
|
||||
return ZKSYNC_SINGLETON_FACTORY;
|
||||
}
|
||||
|
||||
const create2Signer = privateKeyToAccount({
|
||||
client: options.client,
|
||||
privateKey: PUBLISHED_PRIVATE_KEY,
|
||||
});
|
||||
const valueToSend = toWei("0.01");
|
||||
const balance = await getWalletBalance({
|
||||
address: create2Signer.address,
|
||||
chain: options.chain,
|
||||
client: options.client,
|
||||
});
|
||||
|
||||
if (balance.value < valueToSend) {
|
||||
await sendAndConfirmTransaction({
|
||||
account: options.account,
|
||||
transaction: prepareTransaction({
|
||||
chain: options.chain,
|
||||
client: options.client,
|
||||
to: create2Signer.address,
|
||||
value: valueToSend,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
await zkDeployContract({
|
||||
client: options.client,
|
||||
chain: options.chain,
|
||||
account: options.account,
|
||||
abi: parseAbi(singletonFactoryAbi),
|
||||
bytecode: singletonFactoryBytecode,
|
||||
});
|
||||
|
||||
return ZKSYNC_SINGLETON_FACTORY;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
import { type Abi, parseAbi } from "abitype";
|
||||
import { hashBytecode } from "viem/zksync";
|
||||
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { prepareContractCall } from "../../../transaction/prepare-contract-call.js";
|
||||
import { readContract } from "../../../transaction/read-contract.js";
|
||||
import { encodeAbiParameters } from "../../../utils/abi/encodeAbiParameters.js";
|
||||
import { keccakId } from "../../../utils/any-evm/keccak-id.js";
|
||||
import { computeDeploymentAddress } from "../../../utils/any-evm/zksync/computeDeploymentAddress.js";
|
||||
import {
|
||||
KNOWN_CODES_STORAGE,
|
||||
singletonFactoryAbi,
|
||||
} from "../../../utils/any-evm/zksync/constants.js";
|
||||
import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js";
|
||||
import { ensureBytecodePrefix } from "../../../utils/bytecode/prefix.js";
|
||||
import { type Hex, uint8ArrayToHex } from "../../../utils/encoding/hex.js";
|
||||
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
|
||||
import { getContract } from "../../contract.js";
|
||||
import { zkDeployContract } from "./zkDeployContract.js";
|
||||
import { zkDeployCreate2Factory } from "./zkDeployCreate2Factory.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function zkDeployContractDeterministic(
|
||||
options: ClientAndChainAndAccount & {
|
||||
abi: Abi;
|
||||
bytecode: Hex;
|
||||
params: unknown[];
|
||||
salt?: string;
|
||||
},
|
||||
) {
|
||||
const constructorAbi = options.abi.find(
|
||||
(x) => "type" in x && x.type === "constructor",
|
||||
) || { inputs: [] };
|
||||
const encodedArgs = encodeAbiParameters(
|
||||
constructorAbi.inputs,
|
||||
options.params || [],
|
||||
);
|
||||
const create2FactoryAddress = await zkDeployCreate2Factory({
|
||||
client: options.client,
|
||||
chain: options.chain,
|
||||
account: options.account,
|
||||
});
|
||||
const bytecode = ensureBytecodePrefix(options.bytecode);
|
||||
const bytecodeHash = uint8ArrayToHex(hashBytecode(bytecode));
|
||||
const predictedAddress = computeDeploymentAddress({
|
||||
bytecodeHash,
|
||||
encodedArgs,
|
||||
create2FactoryAddress,
|
||||
salt: options.salt,
|
||||
});
|
||||
const deployed = await isContractDeployed({
|
||||
address: predictedAddress,
|
||||
chain: options.chain,
|
||||
client: options.client,
|
||||
});
|
||||
if (!deployed) {
|
||||
// check if bytecodehash is known
|
||||
const knownCodesStorageContract = getContract({
|
||||
address: KNOWN_CODES_STORAGE,
|
||||
chain: options.chain,
|
||||
client: options.client,
|
||||
});
|
||||
const marker = await readContract({
|
||||
contract: knownCodesStorageContract,
|
||||
method: "function getMarker(bytes32 _hash) view returns (uint256 marker)",
|
||||
params: [bytecodeHash],
|
||||
});
|
||||
// if not known, publish the bytecodehash
|
||||
if (marker !== 1n) {
|
||||
await zkDeployContract({
|
||||
client: options.client,
|
||||
chain: options.chain,
|
||||
account: options.account,
|
||||
abi: options.abi,
|
||||
bytecode,
|
||||
params: options.params,
|
||||
});
|
||||
}
|
||||
|
||||
console.log(
|
||||
`deploying contract via create2 factory at: ${predictedAddress}`,
|
||||
);
|
||||
|
||||
// deploy with create2 factory
|
||||
const factory = getContract({
|
||||
address: create2FactoryAddress,
|
||||
chain: options.chain,
|
||||
client: options.client,
|
||||
abi: parseAbi(singletonFactoryAbi),
|
||||
});
|
||||
|
||||
const salt = options?.salt ? keccakId(options.salt) : keccakId("thirdweb");
|
||||
|
||||
await sendAndConfirmTransaction({
|
||||
account: options.account,
|
||||
transaction: prepareContractCall({
|
||||
contract: factory,
|
||||
method: "deploy",
|
||||
params: [salt, bytecodeHash, encodedArgs],
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return predictedAddress;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { encode } from "../../../transaction/actions/encode.js";
|
||||
import type { PreparedTransaction } from "../../../transaction/prepare-transaction.js";
|
||||
import {
|
||||
twProxyAbi,
|
||||
twProxyBytecode,
|
||||
} from "../../../utils/any-evm/zksync/constants.js";
|
||||
import { isContractDeployed } from "../../../utils/bytecode/is-contract-deployed.js";
|
||||
import { resolvePromisedValue } from "../../../utils/promise/resolve-promised-value.js";
|
||||
import type { ClientAndChainAndAccount } from "../../../utils/types.js";
|
||||
import type { ThirdwebContract } from "../../contract.js";
|
||||
import { zkDeployContract } from "./zkDeployContract.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export async function zkDeployProxy(
|
||||
options: ClientAndChainAndAccount & {
|
||||
cloneFactoryContract: ThirdwebContract;
|
||||
initializeTransaction: PreparedTransaction;
|
||||
salt?: string;
|
||||
},
|
||||
) {
|
||||
// TODO ensure implementation is deployed
|
||||
const implementationAddress = await resolvePromisedValue(
|
||||
options.initializeTransaction.to,
|
||||
);
|
||||
if (!implementationAddress) {
|
||||
throw new Error("initializeTransaction must have a 'to' field set");
|
||||
}
|
||||
const deployed = await isContractDeployed({
|
||||
address: implementationAddress,
|
||||
chain: options.chain,
|
||||
client: options.client,
|
||||
});
|
||||
if (!deployed) {
|
||||
throw new Error(
|
||||
`Implementation contract at ${implementationAddress} is not deployed`,
|
||||
);
|
||||
}
|
||||
// deploy tw proxy of the implementation
|
||||
const proxyAddress = await zkDeployContract({
|
||||
client: options.client,
|
||||
chain: options.chain,
|
||||
account: options.account,
|
||||
abi: twProxyAbi,
|
||||
bytecode: twProxyBytecode,
|
||||
params: [
|
||||
implementationAddress,
|
||||
await encode(options.initializeTransaction),
|
||||
],
|
||||
});
|
||||
|
||||
// return address of proxy
|
||||
return proxyAddress;
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
import type { AbiConstructor } from "abitype";
|
||||
import type { AbiConstructor, AbiFunction } from "abitype";
|
||||
import type { Chain } from "../../chains/types.js";
|
||||
import type { ThirdwebClient } from "../../client/client.js";
|
||||
import { getContract } from "../../contract/contract.js";
|
||||
import { fetchPublishedContractMetadata } from "../../contract/deployment/publisher.js";
|
||||
import { zkDeployContract } from "../../contract/deployment/zksync/zkDeployContract.js";
|
||||
import { sendAndConfirmTransaction } from "../../transaction/actions/send-and-confirm-transaction.js";
|
||||
import { simulateTransaction } from "../../transaction/actions/simulate.js";
|
||||
import { prepareContractCall } from "../../transaction/prepare-contract-call.js";
|
||||
import { resolveMethod } from "../../transaction/resolve-method.js";
|
||||
import type { CompilerMetadata } from "../../utils/any-evm/deploy-metadata.js";
|
||||
import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js";
|
||||
import type { Account } from "../../wallets/interfaces/wallet.js";
|
||||
|
||||
/**
|
||||
@@ -58,26 +61,19 @@ export async function deployPublishedContract(
|
||||
const { compilerMetadata, extendedMetadata } =
|
||||
await fetchPublishedContractMetadata({
|
||||
client,
|
||||
contractId,
|
||||
contractId: isZkSyncChain(chain) ? `${contractId}_ZkSync` : contractId,
|
||||
publisher,
|
||||
version,
|
||||
});
|
||||
|
||||
switch (extendedMetadata?.deployType) {
|
||||
case "standard": {
|
||||
const { deployContract } = await import(
|
||||
"../../contract/deployment/deploy-with-abi.js"
|
||||
);
|
||||
return deployContract({
|
||||
return directDeploy({
|
||||
account,
|
||||
client,
|
||||
chain,
|
||||
bytecode: compilerMetadata.bytecode,
|
||||
constructorAbi:
|
||||
(compilerMetadata.abi.find(
|
||||
(i) => i.type === "constructor",
|
||||
) as AbiConstructor) || [],
|
||||
constructorParams: contractParams,
|
||||
compilerMetadata,
|
||||
contractParams,
|
||||
});
|
||||
}
|
||||
case "autoFactory": {
|
||||
@@ -97,16 +93,23 @@ export async function deployPublishedContract(
|
||||
constructorParams: implementationConstructorParams || [],
|
||||
publisher,
|
||||
});
|
||||
const initializeFunction = compilerMetadata.abi.find(
|
||||
(i) =>
|
||||
i.type === "function" &&
|
||||
i.name ===
|
||||
(extendedMetadata.factoryDeploymentData
|
||||
?.implementationInitializerFunction || "initialize"),
|
||||
) as AbiFunction;
|
||||
if (!initializeFunction) {
|
||||
throw new Error(`Could not find initialize function for ${contractId}`);
|
||||
}
|
||||
const initializeTransaction = prepareContractCall({
|
||||
contract: getContract({
|
||||
client,
|
||||
chain,
|
||||
address: implementationContract.address,
|
||||
}),
|
||||
method: resolveMethod(
|
||||
extendedMetadata.factoryDeploymentData
|
||||
?.implementationInitializerFunction || "initialize",
|
||||
),
|
||||
method: initializeFunction,
|
||||
params: contractParams,
|
||||
});
|
||||
|
||||
@@ -154,19 +157,12 @@ export async function deployPublishedContract(
|
||||
}
|
||||
case undefined: {
|
||||
// Default to standard deployment if none was specified
|
||||
const { deployContract } = await import(
|
||||
"../../contract/deployment/deploy-with-abi.js"
|
||||
);
|
||||
return deployContract({
|
||||
return directDeploy({
|
||||
account,
|
||||
client,
|
||||
chain,
|
||||
bytecode: compilerMetadata.bytecode,
|
||||
constructorAbi:
|
||||
(compilerMetadata.abi.find(
|
||||
(i) => i.type === "constructor",
|
||||
) as AbiConstructor) || [],
|
||||
constructorParams: contractParams,
|
||||
compilerMetadata,
|
||||
contractParams,
|
||||
});
|
||||
}
|
||||
default:
|
||||
@@ -176,3 +172,39 @@ export async function deployPublishedContract(
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function directDeploy(options: {
|
||||
account: Account;
|
||||
client: ThirdwebClient;
|
||||
chain: Chain;
|
||||
compilerMetadata: CompilerMetadata;
|
||||
contractParams: unknown[];
|
||||
}) {
|
||||
const { account, client, chain, compilerMetadata, contractParams } = options;
|
||||
|
||||
if (isZkSyncChain(chain)) {
|
||||
return zkDeployContract({
|
||||
account,
|
||||
client,
|
||||
chain,
|
||||
bytecode: compilerMetadata.bytecode,
|
||||
abi: compilerMetadata.abi,
|
||||
params: contractParams,
|
||||
});
|
||||
}
|
||||
|
||||
const { deployContract } = await import(
|
||||
"../../contract/deployment/deploy-with-abi.js"
|
||||
);
|
||||
return deployContract({
|
||||
account,
|
||||
client,
|
||||
chain,
|
||||
bytecode: compilerMetadata.bytecode,
|
||||
constructorAbi:
|
||||
(compilerMetadata.abi.find(
|
||||
(i) => i.type === "constructor",
|
||||
) as AbiConstructor) || [],
|
||||
constructorParams: contractParams,
|
||||
});
|
||||
}
|
||||
|
||||
Generated
+55
@@ -0,0 +1,55 @@
|
||||
import { prepareEvent } from "../../../../../event/prepare-event.js";
|
||||
import type { AbiParameterToPrimitiveType } from "abitype";
|
||||
|
||||
/**
|
||||
* Represents the filters for the "ContractDeployed" event.
|
||||
*/
|
||||
export type ContractDeployedEventFilters = Partial<{
|
||||
deployerAddress: AbiParameterToPrimitiveType<{
|
||||
type: "address";
|
||||
name: "deployerAddress";
|
||||
indexed: true;
|
||||
}>;
|
||||
bytecodeHash: AbiParameterToPrimitiveType<{
|
||||
type: "bytes32";
|
||||
name: "bytecodeHash";
|
||||
indexed: true;
|
||||
}>;
|
||||
contractAddress: AbiParameterToPrimitiveType<{
|
||||
type: "address";
|
||||
name: "contractAddress";
|
||||
indexed: true;
|
||||
}>;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Creates an event object for the ContractDeployed event.
|
||||
* @param filters - Optional filters to apply to the event.
|
||||
* @returns The prepared event object.
|
||||
* @extension ZKSYNC
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getContractEvents } from "thirdweb";
|
||||
* import { contractDeployedEvent } from "thirdweb/extensions/zksync";
|
||||
*
|
||||
* const events = await getContractEvents({
|
||||
* contract,
|
||||
* events: [
|
||||
* contractDeployedEvent({
|
||||
* deployerAddress: ...,
|
||||
* bytecodeHash: ...,
|
||||
* contractAddress: ...,
|
||||
* })
|
||||
* ],
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function contractDeployedEvent(
|
||||
filters: ContractDeployedEventFilters = {},
|
||||
) {
|
||||
return prepareEvent({
|
||||
signature:
|
||||
"event ContractDeployed(address indexed deployerAddress, bytes32 indexed bytecodeHash, address indexed contractAddress)",
|
||||
filters,
|
||||
});
|
||||
}
|
||||
@@ -144,6 +144,17 @@ export async function sendTransaction(
|
||||
await account.onTransactionRequested(transaction);
|
||||
}
|
||||
|
||||
// if zksync transaction params are set, send with eip712
|
||||
if (options.transaction.eip712) {
|
||||
const { sendEip712Transaction } = await import(
|
||||
"./zksync/send-eip712-transaction.js"
|
||||
);
|
||||
return sendEip712Transaction({
|
||||
account,
|
||||
transaction,
|
||||
});
|
||||
}
|
||||
|
||||
const serializableTransaction = await toSerializableTransaction({
|
||||
transaction: transaction,
|
||||
from: account.address,
|
||||
|
||||
@@ -3,9 +3,7 @@ import { beforeAll, describe, expect, test } from "vitest";
|
||||
import { TEST_WALLET_B } from "../../../test/src/addresses.js";
|
||||
import { FORKED_ETHEREUM_CHAIN } from "../../../test/src/chains.js";
|
||||
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
|
||||
|
||||
import { arbitrumSepolia } from "../../chains/chain-definitions/arbitrum-sepolia.js";
|
||||
import { ZERO_ADDRESS } from "../../constants/addresses.js";
|
||||
import { toWei } from "../../utils/units.js";
|
||||
import {
|
||||
type PreparedTransaction,
|
||||
@@ -403,15 +401,16 @@ describe.runIf(process.env.TW_SECRET_KEY)("toSerializableTransaction", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// skipping this test for now, it fails when batching eth_estimateGas with eth_maxFeePerGas together on arbitrumSepolia
|
||||
// works if you run it individually, but when run in parallel the batching kicks in and it fails
|
||||
test("should respect 0 maxPriorityFeePerGas chains", async () => {
|
||||
const serializableTransaction = await toSerializableTransaction({
|
||||
transaction: prepareTransaction({
|
||||
to: TEST_WALLET_B,
|
||||
chain: arbitrumSepolia,
|
||||
value: toWei("0.000001"),
|
||||
value: 0n,
|
||||
client: TEST_CLIENT,
|
||||
}),
|
||||
from: ZERO_ADDRESS,
|
||||
});
|
||||
|
||||
// gasPrice should be undefined for arbSepolia which has 0 maxPriorityFeePerGas
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { TransactionSerializable } from "viem";
|
||||
import { hashBytecode } from "viem/zksync";
|
||||
import type { Address } from "../../../utils/address.js";
|
||||
import { toHex } from "../../../utils/encoding/hex.js";
|
||||
import type {
|
||||
EIP712SerializedTransaction,
|
||||
EIP712TransactionOptions,
|
||||
@@ -56,6 +58,7 @@ function transactionToMessage(
|
||||
paymasterInput,
|
||||
gasPerPubdata,
|
||||
data,
|
||||
factoryDeps,
|
||||
} = transaction;
|
||||
|
||||
return {
|
||||
@@ -70,8 +73,7 @@ function transactionToMessage(
|
||||
nonce: nonce ? BigInt(nonce) : 0n,
|
||||
value: value ?? 0n,
|
||||
data: data ? data : "0x0",
|
||||
// TODO suport factoryDeps
|
||||
factoryDeps: [],
|
||||
factoryDeps: factoryDeps?.map((dep) => toHex(hashBytecode(dep))) ?? [],
|
||||
paymasterInput: paymasterInput ? paymasterInput : "0x",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
TEST_ACCOUNT_A,
|
||||
TEST_ACCOUNT_B,
|
||||
} from "../../../../test/src/test-wallets.js";
|
||||
import { defineChain } from "../../../chains/utils.js";
|
||||
import { zkSyncSepolia } from "../../../chains/chain-definitions/zksync-sepolia.js";
|
||||
import { deployPublishedContract } from "../../../extensions/prebuilts/deploy-published.js";
|
||||
import { prepareTransaction } from "../../prepare-transaction.js";
|
||||
import { sendEip712Transaction } from "./send-eip712-transaction.js";
|
||||
|
||||
@@ -12,7 +13,7 @@ describe("sendEip712Transaction", () => {
|
||||
// re-enable for testing, but disable for CI since it requires testnet funds
|
||||
it.skip("should send eip712 transaction", async () => {
|
||||
const transaction = prepareTransaction({
|
||||
chain: defineChain(300),
|
||||
chain: zkSyncSepolia, // TODO make zksync fork chain work
|
||||
client: TEST_CLIENT,
|
||||
value: 0n,
|
||||
to: TEST_ACCOUNT_B.address,
|
||||
@@ -28,4 +29,28 @@ describe("sendEip712Transaction", () => {
|
||||
});
|
||||
expect(res.transactionHash.length).toBe(66);
|
||||
});
|
||||
|
||||
it.skip("should deploy a published autofactory contract on zksync", async () => {
|
||||
const address = await deployPublishedContract({
|
||||
client: TEST_CLIENT,
|
||||
chain: zkSyncSepolia, // TODO make zksync fork chain work
|
||||
account: TEST_ACCOUNT_A,
|
||||
contractId: "DropERC1155",
|
||||
contractParams: [
|
||||
TEST_ACCOUNT_A.address, // defaultAdmin
|
||||
"test", // name
|
||||
"", // symbol
|
||||
"", // contractURI
|
||||
[], // trustedForwarders
|
||||
TEST_ACCOUNT_A.address, // saleRecipient
|
||||
TEST_ACCOUNT_A.address, // royaltyRecipient
|
||||
0n, // royaltyBps
|
||||
0n, // platformFeeBps
|
||||
TEST_ACCOUNT_A.address, // platformFeeRecipient
|
||||
],
|
||||
});
|
||||
console.log("deployed address", address);
|
||||
expect(address).toBeDefined();
|
||||
expect(address.length).toBe(42);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@ import type { Abi } from "abitype";
|
||||
import { type Hex, decodeErrorResult } from "viem";
|
||||
import { resolveContractAbi } from "../contract/actions/resolve-abi.js";
|
||||
import type { ThirdwebContract } from "../contract/contract.js";
|
||||
import { isHex } from "../utils/encoding/hex.js";
|
||||
import { stringify } from "../utils/json.js";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@@ -19,7 +21,7 @@ export async function extractError<abi extends Abi>(args: {
|
||||
data?: Hex;
|
||||
};
|
||||
if (errorObj.data) {
|
||||
if (errorObj.data !== "0x") {
|
||||
if (errorObj.data !== "0x" && isHex(errorObj.data)) {
|
||||
let abi = contract?.abi;
|
||||
if (contract && !abi) {
|
||||
abi = await resolveContractAbi(contract).catch(() => undefined);
|
||||
@@ -35,7 +37,10 @@ export async function extractError<abi extends Abi>(args: {
|
||||
contract,
|
||||
);
|
||||
}
|
||||
return new TransactionError("Execution Reverted", contract);
|
||||
return new TransactionError(
|
||||
`Execution Reverted: ${stringify(errorObj)}`,
|
||||
contract,
|
||||
);
|
||||
}
|
||||
}
|
||||
return error;
|
||||
|
||||
@@ -30,7 +30,7 @@ describe("encodeAbiParameters", () => {
|
||||
it("should throw error when encoding invalid address", () => {
|
||||
const invalidAddress = "0x_hello_world";
|
||||
expect(() => encodeAddress(invalidAddress)).toThrowError(
|
||||
"Invalid address.",
|
||||
`Invalid address: ${invalidAddress}`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -160,7 +160,7 @@ function encodeParams(preparedParams: PreparedParam[]): Hex {
|
||||
export function encodeAddress(value: Hex): PreparedParam {
|
||||
// We allow empty strings for deployment transactions where there is no to address
|
||||
if ((value as string) !== "" && value !== undefined && !isAddress(value)) {
|
||||
throw new Error("Invalid address.");
|
||||
throw new Error(`Invalid address: ${value}`);
|
||||
}
|
||||
return { dynamic: false, encoded: padHex(value.toLowerCase() as Hex) };
|
||||
}
|
||||
|
||||
@@ -53,13 +53,22 @@ async function fetchCompilerMetadata(
|
||||
uri: options.uri,
|
||||
client: options.client,
|
||||
}).then((r) => r.json());
|
||||
// TODO: proper handling of different compiler metadata types
|
||||
const metadataUri =
|
||||
rawMeta.compilers?.zksolc?.length > 0 && rawMeta.name.endsWith("_ZkSync")
|
||||
? rawMeta.compilers.zksolc[0].metadataUri
|
||||
: rawMeta.metadataUri;
|
||||
const bytecodeUri =
|
||||
rawMeta.compilers?.zksolc?.length > 0 && rawMeta.name.endsWith("_ZkSync")
|
||||
? rawMeta.compilers.zksolc[0].bytecodeUri
|
||||
: rawMeta.bytecodeUri;
|
||||
const [deployBytecode, parsedMeta] = await Promise.all([
|
||||
download({ uri: rawMeta.bytecodeUri, client: options.client }).then(
|
||||
download({ uri: bytecodeUri, client: options.client }).then(
|
||||
(res) => res.text() as Promise<Hex>,
|
||||
),
|
||||
fetchAndParseCompilerMetadata({
|
||||
client: options.client,
|
||||
uri: rawMeta.metadataUri,
|
||||
uri: metadataUri,
|
||||
}),
|
||||
]);
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { Address } from "../../address.js";
|
||||
import type { Hex } from "../../encoding/hex.js";
|
||||
import { keccakId } from "../keccak-id.js";
|
||||
import { create2Address } from "./create2Address.js";
|
||||
|
||||
type ComputeDeploymentAddressOptions = {
|
||||
bytecodeHash: Hex;
|
||||
encodedArgs: Hex;
|
||||
create2FactoryAddress: Address;
|
||||
salt?: string;
|
||||
};
|
||||
|
||||
export function computeDeploymentAddress(
|
||||
options: ComputeDeploymentAddressOptions,
|
||||
) {
|
||||
const saltHash = options.salt ? keccakId(options.salt) : keccakId("thirdweb");
|
||||
|
||||
return create2Address({
|
||||
sender: options.create2FactoryAddress,
|
||||
bytecodeHash: options.bytecodeHash,
|
||||
salt: saltHash,
|
||||
input: options.encodedArgs,
|
||||
});
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,26 @@
|
||||
import { concat, padHex } from "viem";
|
||||
import { type Address, getAddress } from "../../address.js";
|
||||
import type { Hex } from "../../encoding/hex.js";
|
||||
import { stringToBytes } from "../../encoding/to-bytes.js";
|
||||
import { keccak256 } from "../../hashing/keccak256.js";
|
||||
|
||||
export function create2Address(options: {
|
||||
sender: Address;
|
||||
bytecodeHash: Hex;
|
||||
salt: Hex;
|
||||
input?: Hex;
|
||||
}): string {
|
||||
const { sender, bytecodeHash, salt, input } = options;
|
||||
const prefix = keccak256(stringToBytes("zksyncCreate2"));
|
||||
const inputHash = input ? keccak256(input) : "0x";
|
||||
const addressBytes = keccak256(
|
||||
concat([
|
||||
prefix,
|
||||
padHex(sender, { size: 32 }),
|
||||
salt,
|
||||
bytecodeHash,
|
||||
inputHash,
|
||||
]),
|
||||
).slice(26);
|
||||
return getAddress(`0x${addressBytes}`);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { Chain } from "../../../chains/types.js";
|
||||
|
||||
export function isZkSyncChain(chain: Chain) {
|
||||
return (
|
||||
chain.id === 324 ||
|
||||
chain.id === 300 ||
|
||||
chain.id === 302 ||
|
||||
chain.id === 11124
|
||||
);
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from "../../transaction/actions/zksync/send-eip712-transaction.js";
|
||||
import type { PreparedTransaction } from "../../transaction/prepare-transaction.js";
|
||||
import { getAddress } from "../../utils/address.js";
|
||||
import { isZkSyncChain } from "../../utils/any-evm/zksync/isZkSyncChain.js";
|
||||
import { concatHex } from "../../utils/encoding/helpers/concat-hex.js";
|
||||
import type { Hex } from "../../utils/encoding/hex.js";
|
||||
import { parseTypedData } from "../../utils/signatures/helpers/parseTypedData.js";
|
||||
@@ -48,7 +49,6 @@ import {
|
||||
signUserOp,
|
||||
waitForUserOpReceipt,
|
||||
} from "./lib/userop.js";
|
||||
import { isNativeAAChain } from "./lib/utils.js";
|
||||
import type {
|
||||
BundlerOptions,
|
||||
PaymasterResult,
|
||||
@@ -58,7 +58,6 @@ import type {
|
||||
UserOperationV06,
|
||||
UserOperationV07,
|
||||
} from "./types.js";
|
||||
|
||||
/**
|
||||
* Checks if the provided wallet is a smart wallet.
|
||||
*
|
||||
@@ -104,7 +103,7 @@ export async function connectSmartWallet(
|
||||
const sponsorGas =
|
||||
"gasless" in options ? options.gasless : options.sponsorGas;
|
||||
|
||||
if (isNativeAAChain(chain)) {
|
||||
if (isZkSyncChain(chain)) {
|
||||
return [
|
||||
createZkSyncAccount({
|
||||
creationOptions,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { concat } from "viem";
|
||||
import type { Chain } from "../../../chains/types.js";
|
||||
import { isHex, numberToHex, toHex } from "../../../utils/encoding/hex.js";
|
||||
import type {
|
||||
UserOperationV06,
|
||||
@@ -48,12 +47,3 @@ export function hexlifyUserOp(
|
||||
]),
|
||||
) as UserOperationV06Hexed | UserOperationV07Hexed;
|
||||
}
|
||||
|
||||
export function isNativeAAChain(chain: Chain) {
|
||||
return (
|
||||
chain.id === 324 ||
|
||||
chain.id === 300 ||
|
||||
chain.id === 302 ||
|
||||
chain.id === 11124
|
||||
);
|
||||
}
|
||||
|
||||
Generated
+259
-253
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user