Skip to content

Commit 88c213b

Browse files
committed
Handle default extensions (#6497)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the deployment and transaction management of dynamic contracts within the `thirdweb` framework, adding support for extensions and improving the handling of contract metadata. ### Detailed summary - Improved return formatting in `bootstrap.ts`. - Updated `platformFeeBps` handling in `custom-contract.tsx`. - Added tests for dynamic contract transactions in `get-required-transactions.test.ts`. - Enhanced logic for deploying extensions in `deploy-published.ts`. - Introduced new functions for managing dynamic contract extensions in `get-required-transactions.ts`. - Updated `deploy-marketplace.ts` to include dynamic extensions in marketplace contracts. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent d9091bd commit 88c213b

File tree

7 files changed

+312
-85
lines changed

7 files changed

+312
-85
lines changed

apps/dashboard/src/components/contract-components/contract-deploy-form/custom-contract.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,9 +473,7 @@ export const CustomContractForm: React.FC<CustomContractFormProps> = ({
473473
name: params.contractMetadata?.name || "",
474474
contractURI: _contractURI,
475475
defaultAdmin: params.deployParams._defaultAdmin as string,
476-
platformFeeBps: hasInbuiltDefaultFeeConfig
477-
? DEFAULT_FEE_BPS_NEW
478-
: DEFAULT_FEE_BPS,
476+
platformFeeBps: DEFAULT_FEE_BPS_NEW,
479477
platformFeeRecipient: DEFAULT_FEE_RECIPIENT,
480478
trustedForwarders: params.deployParams._trustedForwarders
481479
? JSON.parse(params.deployParams._trustedForwarders as string)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { readContract } from "src/transaction/read-contract.js";
2+
import { resolveMethod } from "src/transaction/resolve-method.js";
3+
import { describe, expect, it } from "vitest";
4+
import { ANVIL_CHAIN } from "../../../test/src/chains.js";
5+
import { TEST_CLIENT } from "../../../test/src/test-clients.js";
6+
import { TEST_ACCOUNT_A } from "../../../test/src/test-wallets.js";
7+
import { deployPublishedContract } from "../../extensions/prebuilts/deploy-published.js";
8+
import { getContract } from "../contract.js";
9+
import { deployCloneFactory } from "./utils/bootstrap.js";
10+
11+
describe.runIf(process.env.TW_SECRET_KEY)("deploy dynamic", () => {
12+
it.sequential("should deploy dynamic contract with extensions", async () => {
13+
await deployCloneFactory({
14+
chain: ANVIL_CHAIN,
15+
client: TEST_CLIENT,
16+
account: TEST_ACCOUNT_A,
17+
});
18+
19+
const deployed = await deployPublishedContract({
20+
chain: ANVIL_CHAIN,
21+
client: TEST_CLIENT,
22+
account: TEST_ACCOUNT_A,
23+
contractId: "EvolvingNFT",
24+
contractParams: {
25+
name: "Evolving nft",
26+
symbol: "ENFT",
27+
defaultAdmin: TEST_ACCOUNT_A.address,
28+
royaltyBps: 0n,
29+
royaltyRecipient: TEST_ACCOUNT_A.address,
30+
saleRecipient: TEST_ACCOUNT_A.address,
31+
trustedForwarders: [],
32+
contractURI: "",
33+
},
34+
});
35+
36+
expect(deployed).toBeDefined();
37+
38+
const contract = getContract({
39+
client: TEST_CLIENT,
40+
address: deployed,
41+
chain: ANVIL_CHAIN,
42+
});
43+
44+
const extensions = await readContract({
45+
contract,
46+
method: resolveMethod("getAllExtensions"),
47+
params: [],
48+
});
49+
50+
expect(extensions.length).toEqual(3);
51+
});
52+
});

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,11 @@ export async function getOrDeployInfraForPublishedContract(
142142
version,
143143
});
144144
}
145-
return { cloneFactoryContract, implementationContract };
145+
146+
return {
147+
cloneFactoryContract,
148+
implementationContract,
149+
};
146150
}
147151

148152
/**

packages/thirdweb/src/extensions/prebuilts/deploy-marketplace.ts

Lines changed: 78 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import type {
2-
Abi,
3-
AbiFunction,
4-
AbiParametersToPrimitiveTypes,
5-
Address,
6-
} from "abitype";
7-
import { toFunctionSelector, toFunctionSignature } from "viem";
1+
import type { AbiParametersToPrimitiveTypes, Address } from "abitype";
82
import type { ThirdwebClient } from "../../client/client.js";
93
import { resolveContractAbi } from "../../contract/actions/resolve-abi.js";
104
import type { ThirdwebContract } from "../../contract/contract.js";
@@ -19,6 +13,19 @@ import { getRoyaltyEngineV1ByChainId } from "../../utils/royalty-engine.js";
1913
import type { Prettify } from "../../utils/type-utils.js";
2014
import type { ClientAndChainAndAccount } from "../../utils/types.js";
2115
import { initialize as initMarketplace } from "./__generated__/Marketplace/write/initialize.js";
16+
import { generateExtensionFunctionsFromAbi } from "./get-required-transactions.js";
17+
18+
type Extension = {
19+
metadata: {
20+
name: string;
21+
metadataURI: string;
22+
implementation: `0x${string}`;
23+
};
24+
functions: {
25+
functionSelector: string;
26+
functionSignature: string;
27+
}[];
28+
};
2229

2330
export type MarketplaceContractParams = {
2431
name: string;
@@ -74,41 +81,72 @@ export async function deployMarketplaceContract(
7481
account,
7582
contractId: "WETH9",
7683
});
77-
const direct = await getOrDeployInfraForPublishedContract({
78-
chain,
79-
client,
80-
account,
81-
contractId: "DirectListingsLogic",
82-
constructorParams: { _nativeTokenWrapper: WETH.address },
83-
});
8484

85-
const english = await getOrDeployInfraForPublishedContract({
86-
chain,
87-
client,
88-
account,
89-
contractId: "EnglishAuctionsLogic",
90-
constructorParams: { _nativeTokenWrapper: WETH.address },
91-
});
85+
let extensions: Extension[] = [];
9286

93-
const offers = await getOrDeployInfraForPublishedContract({
94-
chain,
95-
client,
96-
account,
97-
contractId: "OffersLogic",
98-
});
87+
if (options.version !== "6.0.0") {
88+
const direct = await getOrDeployInfraForPublishedContract({
89+
chain,
90+
client,
91+
account,
92+
contractId: "DirectListingsLogic",
93+
constructorParams: { _nativeTokenWrapper: WETH.address },
94+
});
9995

100-
const [directFunctions, englishFunctions, offersFunctions] =
101-
await Promise.all([
102-
resolveContractAbi(direct.implementationContract).then(
103-
generateExtensionFunctionsFromAbi,
104-
),
105-
resolveContractAbi(english.implementationContract).then(
106-
generateExtensionFunctionsFromAbi,
107-
),
108-
resolveContractAbi(offers.implementationContract).then(
109-
generateExtensionFunctionsFromAbi,
110-
),
111-
]);
96+
const english = await getOrDeployInfraForPublishedContract({
97+
chain,
98+
client,
99+
account,
100+
contractId: "EnglishAuctionsLogic",
101+
constructorParams: { _nativeTokenWrapper: WETH.address },
102+
});
103+
104+
const offers = await getOrDeployInfraForPublishedContract({
105+
chain,
106+
client,
107+
account,
108+
contractId: "OffersLogic",
109+
});
110+
111+
const [directFunctions, englishFunctions, offersFunctions] =
112+
await Promise.all([
113+
resolveContractAbi(direct.implementationContract).then(
114+
generateExtensionFunctionsFromAbi,
115+
),
116+
resolveContractAbi(english.implementationContract).then(
117+
generateExtensionFunctionsFromAbi,
118+
),
119+
resolveContractAbi(offers.implementationContract).then(
120+
generateExtensionFunctionsFromAbi,
121+
),
122+
]);
123+
extensions = [
124+
{
125+
metadata: {
126+
name: "Direct Listings",
127+
metadataURI: "",
128+
implementation: direct.implementationContract.address,
129+
},
130+
functions: directFunctions,
131+
},
132+
{
133+
metadata: {
134+
name: "English Auctions",
135+
metadataURI: "",
136+
implementation: english.implementationContract.address,
137+
},
138+
functions: englishFunctions,
139+
},
140+
{
141+
metadata: {
142+
name: "Offers",
143+
metadataURI: "",
144+
implementation: offers.implementationContract.address,
145+
},
146+
functions: offersFunctions,
147+
},
148+
];
149+
}
112150

113151
const { cloneFactoryContract, implementationContract } =
114152
await getOrDeployInfraForPublishedContract({
@@ -118,32 +156,7 @@ export async function deployMarketplaceContract(
118156
contractId: "MarketplaceV3",
119157
constructorParams: {
120158
_marketplaceV3Params: {
121-
extensions: [
122-
{
123-
metadata: {
124-
name: "Direct Listings",
125-
metadataURI: "",
126-
implementation: direct.implementationContract.address,
127-
},
128-
functions: directFunctions,
129-
},
130-
{
131-
metadata: {
132-
name: "English Auctions",
133-
metadataURI: "",
134-
implementation: english.implementationContract.address,
135-
},
136-
functions: englishFunctions,
137-
},
138-
{
139-
metadata: {
140-
name: "Offers",
141-
metadataURI: "",
142-
implementation: offers.implementationContract.address,
143-
},
144-
functions: offersFunctions,
145-
},
146-
],
159+
extensions,
147160
royaltyEngineAddress: getRoyaltyEngineV1ByChainId(chain.id),
148161
nativeTokenWrapper: WETH.address,
149162
} as MarketplaceConstructorParams[number],
@@ -199,22 +212,6 @@ async function getInitializeTransaction(options: {
199212
});
200213
}
201214

202-
// helperFns
203-
204-
function generateExtensionFunctionsFromAbi(abi: Abi): Array<{
205-
functionSelector: string;
206-
functionSignature: string;
207-
}> {
208-
const functions = abi.filter(
209-
(item) => item.type === "function" && !item.name.startsWith("_"),
210-
) as AbiFunction[];
211-
212-
return functions.map((fn) => ({
213-
functionSelector: toFunctionSelector(fn),
214-
functionSignature: toFunctionSignature(fn),
215-
}));
216-
}
217-
218215
// let's just ... put this down here
219216
type MarketplaceConstructorParams = AbiParametersToPrimitiveTypes<
220217
[

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,29 @@ export async function deployContractfromDeployMetadata(
204204
import("../../contract/deployment/deploy-via-autofactory.js"),
205205
import("../../contract/deployment/utils/bootstrap.js"),
206206
]);
207+
208+
if (
209+
deployMetadata.routerType === "dynamic" &&
210+
deployMetadata.defaultExtensions
211+
) {
212+
for (const e of deployMetadata.defaultExtensions) {
213+
await getOrDeployInfraForPublishedContract({
214+
chain,
215+
client,
216+
account,
217+
contractId: e.extensionName,
218+
version: e.extensionVersion || "latest",
219+
publisher: e.publisherAddress,
220+
constructorParams:
221+
await getAllDefaultConstructorParamsForImplementation({
222+
chain,
223+
client,
224+
contractId: e.extensionName,
225+
}),
226+
});
227+
}
228+
}
229+
207230
const { cloneFactoryContract, implementationContract } =
208231
await getOrDeployInfraForPublishedContract({
209232
chain,
@@ -216,6 +239,7 @@ export async function deployContractfromDeployMetadata(
216239
chain,
217240
client,
218241
contractId: deployMetadata.name,
242+
defaultExtensions: deployMetadata.defaultExtensions,
219243
})),
220244
publisher: deployMetadata.publisher,
221245
version: deployMetadata.version,

packages/thirdweb/src/extensions/prebuilts/get-required-transactions.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ describe.runIf(process.env.TW_SECRET_KEY)(
6464
expect(results.length).toBe(7);
6565
});
6666

67+
it("should count transactions for a dynamic contract", async () => {
68+
const deployMetadata = await fetchPublishedContractMetadata({
69+
client: TEST_CLIENT,
70+
contractId: "EvolvingNFT",
71+
});
72+
const results = await getRequiredTransactions({
73+
client: TEST_CLIENT,
74+
chain: CLEAN_ANVIL_CHAIN,
75+
deployMetadata,
76+
});
77+
78+
expect(results.length).toBe(8);
79+
});
80+
6781
it("should return default constructor params for zksync chains", async () => {
6882
const params = await getAllDefaultConstructorParamsForImplementation({
6983
chain: defineChain(300),

0 commit comments

Comments
 (0)