Skip to content

Commit 664082e

Browse files
feat: prepareDeployDeterministicTransaction (#2915)
1 parent 24a3ee4 commit 664082e

File tree

15 files changed

+148
-18
lines changed

15 files changed

+148
-18
lines changed

.changeset/selfish-tools-double.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
New `prepareDeterministicDeployTransaction` extension for deploying published contracts with the same address on multiple chains
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { describe, expect, it } from "vitest";
2+
import {
3+
FORKED_ETHEREUM_CHAIN,
4+
FORKED_OPTIMISM_CHAIN,
5+
} from "../../../test/src/chains.js";
6+
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
7+
import { TEST_ACCOUNT_A } from "../../../test/src/test-wallets.js";
8+
import { simulateTransaction } from "../../transaction/actions/simulate.js";
9+
import { ENTRYPOINT_ADDRESS_v0_6 } from "../../wallets/smart/lib/constants.js";
10+
import { prepareDeterministicDeployTransaction } from "./deploy-deterministic.js";
11+
12+
// skip this test suite if there is no secret key available to test with
13+
// TODO: remove reliance on secret key during unit tests entirely
14+
describe.runIf(process.env.TW_SECRET_KEY)("deployFromMetadata", () => {
15+
it("should deploy contracts at the same address", async () => {
16+
const tx1 = prepareDeterministicDeployTransaction({
17+
chain: FORKED_ETHEREUM_CHAIN,
18+
client: TEST_CLIENT,
19+
contractId: "AccountFactory",
20+
constructorParams: [TEST_ACCOUNT_A.address, ENTRYPOINT_ADDRESS_v0_6],
21+
});
22+
const tx2 = prepareDeterministicDeployTransaction({
23+
chain: FORKED_OPTIMISM_CHAIN,
24+
client: TEST_CLIENT,
25+
contractId: "AccountFactory",
26+
constructorParams: [TEST_ACCOUNT_A.address, ENTRYPOINT_ADDRESS_v0_6],
27+
});
28+
const [tx1Result, tx2Result] = await Promise.all([
29+
simulateTransaction({ transaction: tx1 }),
30+
simulateTransaction({ transaction: tx2 }),
31+
]);
32+
expect(tx1Result).toEqual(tx2Result);
33+
});
34+
35+
it("should respect salt param", async () => {
36+
const tx1 = prepareDeterministicDeployTransaction({
37+
chain: FORKED_ETHEREUM_CHAIN,
38+
client: TEST_CLIENT,
39+
contractId: "AccountFactory",
40+
constructorParams: [TEST_ACCOUNT_A.address, ENTRYPOINT_ADDRESS_v0_6],
41+
salt: "some-salt",
42+
});
43+
const tx2 = prepareDeterministicDeployTransaction({
44+
chain: FORKED_OPTIMISM_CHAIN,
45+
client: TEST_CLIENT,
46+
contractId: "AccountFactory",
47+
constructorParams: [TEST_ACCOUNT_A.address, ENTRYPOINT_ADDRESS_v0_6],
48+
});
49+
const [tx1Result, tx2Result] = await Promise.all([
50+
simulateTransaction({ transaction: tx1 }),
51+
simulateTransaction({ transaction: tx2 }),
52+
]);
53+
expect(tx1Result !== tx2Result).toBe(true);
54+
});
55+
});
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { prepareTransaction } from "../../transaction/prepare-transaction.js";
2+
import { computeDeploymentInfoFromContractId } from "../../utils/any-evm/compute-published-contract-deploy-info.js";
3+
import type { Prettify } from "../../utils/type-utils.js";
4+
import type { ClientAndChain } from "../../utils/types.js";
5+
import { computeCreate2FactoryAddress } from "./utils/create-2-factory.js";
6+
7+
export type DeployDetemisiticParams = Prettify<
8+
ClientAndChain & {
9+
contractId: string;
10+
constructorParams: unknown[];
11+
publisher?: string;
12+
version?: string;
13+
salt?: string;
14+
}
15+
>;
16+
17+
/**
18+
* Deploy a contract deterministically - will maintain the same address across chains.
19+
* This is meant to be used with published contracts configured with the 'direct deploy' method.
20+
* Under the hood, this uses a keyless transaction with a common create2 factory.
21+
* @param options - the options to deploy the contract
22+
* @returns - the transaction to deploy the contract
23+
* @extension DEPLOY
24+
* @example
25+
* ```ts
26+
* import { prepareDeterministicDeployTransaction } from "thirdweb/deploys";
27+
* import { sepolia } from "thirdweb/chains";
28+
*
29+
* const tx = prepareDeterministicDeployTransaction({
30+
* client,
31+
* chain: sepolia,
32+
* contractId: "AccountFactory",
33+
* constructorParams: [123],
34+
* });
35+
* ```
36+
*/
37+
export function prepareDeterministicDeployTransaction(
38+
options: DeployDetemisiticParams,
39+
) {
40+
const { client, chain } = options;
41+
return prepareTransaction({
42+
client,
43+
chain,
44+
to: () =>
45+
computeCreate2FactoryAddress({
46+
client,
47+
chain,
48+
}),
49+
data: async () => {
50+
const infraContractInfo =
51+
await computeDeploymentInfoFromContractId(options);
52+
return infraContractInfo.initBytecodeWithsalt;
53+
},
54+
});
55+
}

packages/thirdweb/src/contract/deployment/deploy-with-abi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export type PrepareDirectDeployTransactionOptions<
3333
* @returns - The prepared transaction.
3434
* @example
3535
* ```ts
36-
* import { prepareDirectDeployTransaction } from "thirdweb/contract";
36+
* import { prepareDirectDeployTransaction } from "thirdweb/deploys";
3737
* import { ethereum } from "thirdweb/chains";
3838
* const tx = prepareDirectDeployTransaction({
3939
* client,

packages/thirdweb/src/exports/deploys.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ export {
2626

2727
export { prepareDirectDeployTransaction } from "../contract/deployment/deploy-with-abi.js";
2828
export { prepareAutoFactoryDeployTransaction } from "../contract/deployment/deploy-via-autofactory.js";
29+
export { prepareDeterministicDeployTransaction } from "../contract/deployment/deploy-deterministic.js";
2930
export { deployViaAutoFactory } from "../contract/deployment/deploy-via-autofactory.js";
3031
export {
3132
deployContract,
3233
type PrepareDirectDeployTransactionOptions,
3334
} from "../contract/deployment/deploy-with-abi.js";
35+
export { computePublishedContractAddress } from "../utils/any-evm/compute-published-contract-address.js";

packages/thirdweb/src/exports/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,8 @@ export { resolvePromisedValue } from "../utils/promise/resolve-promised-value.js
141141
// json
142142
// ------------------------------------------------
143143
export { stringify } from "../utils/json.js";
144+
145+
// ------------------------------------------------
146+
// values
147+
// ------------------------------------------------
148+
export { maxUint256 } from "viem";

packages/thirdweb/src/exports/wallets/smart.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ export type {
77
PaymasterResult,
88
} from "../../wallets/smart/types.js";
99

10-
// TODO: add exports for smart wallet utils here
10+
export { ENTRYPOINT_ADDRESS_v0_6 } from "../../wallets/smart/lib/constants.js";

packages/thirdweb/src/extensions/erc4337/account/permissions.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { getOrDeployInfraContract } from "../../../contract/deployment/utils/boo
1414
import { parseEventLogs } from "../../../event/actions/parse-logs.js";
1515
import { sendAndConfirmTransaction } from "../../../transaction/actions/send-and-confirm-transaction.js";
1616
import { simulateTransaction } from "../../../transaction/actions/simulate.js";
17-
import { ENTRYPOINT_ADDRESS } from "../../../wallets/smart/lib/constants.js";
17+
import { ENTRYPOINT_ADDRESS_v0_6 } from "../../../wallets/smart/lib/constants.js";
1818
import { createAccount } from "../__generated__/IAccountFactory/write/createAccount.js";
1919
import { adminUpdatedEvent } from "../__generated__/IAccountPermissions/events/AdminUpdated.js";
2020
import { signerPermissionsUpdatedEvent } from "../__generated__/IAccountPermissions/events/SignerPermissionsUpdated.js";
@@ -34,7 +34,7 @@ describe.runIf(process.env.TW_SECRET_KEY)("Account Permissions", () => {
3434
chain: ANVIL_CHAIN,
3535
client: TEST_CLIENT,
3636
contractId: "AccountFactory",
37-
constructorParams: [TEST_ACCOUNT_A.address, ENTRYPOINT_ADDRESS],
37+
constructorParams: [TEST_ACCOUNT_A.address, ENTRYPOINT_ADDRESS_v0_6],
3838
});
3939
const transaction = createAccount({
4040
contract: accountFactoryContract,

packages/thirdweb/src/extensions/prebuilts/deploy-published.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
22
import { ANVIL_CHAIN } from "../../../test/src/chains.js";
33
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
44
import { TEST_ACCOUNT_A } from "../../../test/src/test-wallets.js";
5-
import { ENTRYPOINT_ADDRESS } from "../../wallets/smart/lib/constants.js";
5+
import { ENTRYPOINT_ADDRESS_v0_6 } from "../../wallets/smart/lib/constants.js";
66
import { deployPublishedContract } from "./deploy-published.js";
77

88
describe.runIf(process.env.TW_SECRET_KEY)(
@@ -17,7 +17,7 @@ describe.runIf(process.env.TW_SECRET_KEY)(
1717
chain: ANVIL_CHAIN,
1818
account: TEST_ACCOUNT_A,
1919
contractId: "AccountFactory",
20-
contractParams: [TEST_ACCOUNT_A.address, ENTRYPOINT_ADDRESS],
20+
contractParams: [TEST_ACCOUNT_A.address, ENTRYPOINT_ADDRESS_v0_6],
2121
});
2222
expect(address).toBeDefined();
2323
expect(address.length).toBe(42);

packages/thirdweb/src/utils/any-evm/compute-published-contract-address.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type { FetchDeployMetadataResult } from "./deploy-metadata.js";
1818
* @param args.version - The version of the contract.
1919
* @example
2020
* ```ts
21-
* import { computePublishedContractAddress } from "thirdweb/contract";
21+
* import { computePublishedContractAddress } from "thirdweb/deploys";
2222
*
2323
* const contractMetadata = await fetchPublishedContractMetadata({
2424
* client,
@@ -27,7 +27,7 @@ import type { FetchDeployMetadataResult } from "./deploy-metadata.js";
2727
* const address = await computePublishedContractAddress({
2828
* client,
2929
* chain,
30-
* contractId: "DropERC721",
30+
* contractId: "AccountFactory",
3131
* constructorParams,
3232
* });
3333
* ```
@@ -40,6 +40,7 @@ export async function computePublishedContractAddress(args: {
4040
constructorParams: unknown[];
4141
publisher?: string;
4242
version?: string;
43+
salt?: string;
4344
}): Promise<string> {
4445
const info = await computeDeploymentInfoFromContractId(args);
4546
return computeDeploymentAddress(info);
@@ -53,6 +54,7 @@ export async function computeContractAddress(args: {
5354
chain: Chain;
5455
contractMetadata: FetchDeployMetadataResult;
5556
constructorParams: unknown[];
57+
salt?: string;
5658
}): Promise<string> {
5759
const info = await computeDeploymentInfoFromMetadata(args);
5860
return computeDeploymentAddress(info);

0 commit comments

Comments
 (0)