@zoralabs/protocol-sdk 0.3.4 → 0.3.5

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.
@@ -1,72 +1,409 @@
1
1
  import { Address } from "abitype";
2
2
  import { ExtractAbiFunction, AbiParametersToPrimitiveTypes } from "abitype";
3
- import { zoraCreator1155PremintExecutorImplABI as preminterAbi } from "@zoralabs/protocol-deployments";
4
- import { TypedDataDefinition } from "viem";
3
+ import {
4
+ zoraCreator1155PremintExecutorImplABI as preminterAbi,
5
+ zoraCreator1155ImplABI,
6
+ zoraCreator1155PremintExecutorImplABI,
7
+ zoraCreator1155PremintExecutorImplAddress,
8
+ } from "@zoralabs/protocol-deployments";
9
+ import {
10
+ TypedDataDefinition,
11
+ recoverTypedDataAddress,
12
+ Hex,
13
+ PublicClient,
14
+ zeroAddress,
15
+ hashDomain,
16
+ keccak256,
17
+ concat,
18
+ recoverAddress,
19
+ GetEventArgs,
20
+ } from "viem";
5
21
 
6
- type PremintInputs = ExtractAbiFunction<
22
+ type PremintV1Inputs = ExtractAbiFunction<
7
23
  typeof preminterAbi,
8
- "premint"
24
+ "premintV1"
9
25
  >["inputs"];
10
26
 
11
- type PreminterHashDataTypes = AbiParametersToPrimitiveTypes<PremintInputs>;
27
+ type PremintV1HashDataTypes = AbiParametersToPrimitiveTypes<PremintV1Inputs>;
12
28
 
13
- export type ContractCreationConfig = PreminterHashDataTypes[0];
14
- export type PremintConfig = PreminterHashDataTypes[1];
15
- export type TokenCreationConfig = PremintConfig["tokenConfig"];
29
+ export type ContractCreationConfig = PremintV1HashDataTypes[0];
16
30
 
17
- // Convenience method to create the structured typed data
18
- // needed to sign for a premint contract and token
19
- export const preminterTypedDataDefinition = ({
31
+ export type PremintConfigV1 = PremintV1HashDataTypes[1];
32
+ export type TokenCreationConfigV1 = PremintConfigV1["tokenConfig"];
33
+
34
+ export type MintArguments = PremintV1HashDataTypes[4];
35
+
36
+ type PremintV2Inputs = ExtractAbiFunction<
37
+ typeof preminterAbi,
38
+ "premintV2"
39
+ >["inputs"];
40
+
41
+ type PremintV2HashDataTypes = AbiParametersToPrimitiveTypes<PremintV2Inputs>;
42
+
43
+ export type PremintConfigV2 = PremintV2HashDataTypes[1];
44
+ export type TokenCreationConfigV2 = PremintConfigV2["tokenConfig"];
45
+
46
+ const v1Types = {
47
+ CreatorAttribution: [
48
+ { name: "tokenConfig", type: "TokenCreationConfig" },
49
+ // unique id scoped to the contract and token to create.
50
+ // ensure that a signature can be replaced, as long as the replacement
51
+ // has the same uid, and a newer version.
52
+ { name: "uid", type: "uint32" },
53
+ { name: "version", type: "uint32" },
54
+ // if this update should result in the signature being deleted.
55
+ { name: "deleted", type: "bool" },
56
+ ],
57
+ TokenCreationConfig: [
58
+ { name: "tokenURI", type: "string" },
59
+ { name: "maxSupply", type: "uint256" },
60
+ { name: "maxTokensPerAddress", type: "uint64" },
61
+ { name: "pricePerToken", type: "uint96" },
62
+ { name: "mintStart", type: "uint64" },
63
+ { name: "mintDuration", type: "uint64" },
64
+ { name: "royaltyMintSchedule", type: "uint32" },
65
+ { name: "royaltyBPS", type: "uint32" },
66
+ { name: "royaltyRecipient", type: "address" },
67
+ { name: "fixedPriceMinter", type: "address" },
68
+ ],
69
+ } as const;
70
+
71
+ const v2Types = {
72
+ CreatorAttribution: [
73
+ { name: "tokenConfig", type: "TokenCreationConfig" },
74
+ // unique id scoped to the contract and token to create.
75
+ // ensure that a signature can be replaced, as long as the replacement
76
+ // has the same uid, and a newer version.
77
+ { name: "uid", type: "uint32" },
78
+ { name: "version", type: "uint32" },
79
+ // if this update should result in the signature being deleted.
80
+ { name: "deleted", type: "bool" },
81
+ ],
82
+ TokenCreationConfig: [
83
+ { name: "tokenURI", type: "string" },
84
+ { name: "maxSupply", type: "uint256" },
85
+ { name: "maxTokensPerAddress", type: "uint64" },
86
+ { name: "pricePerToken", type: "uint96" },
87
+ { name: "mintStart", type: "uint64" },
88
+ { name: "mintDuration", type: "uint64" },
89
+ { name: "royaltyBPS", type: "uint32" },
90
+ { name: "payoutRecipient", type: "address" },
91
+ { name: "fixedPriceMinter", type: "address" },
92
+ { name: "createReferral", type: "address" },
93
+ ],
94
+ } as const;
95
+
96
+ export const PreminterDomain = "Preminter";
97
+
98
+ type PremintConfigVersion = "1" | "2";
99
+
100
+ export const PremintConfigVersion = {
101
+ V1: "1",
102
+ V2: "2",
103
+ } as const;
104
+
105
+ type PremintConfigForVersion<T extends PremintConfigVersion> = T extends "1"
106
+ ? PremintConfigV1
107
+ : PremintConfigV2;
108
+
109
+ type PremintConfigWithVersion<T extends PremintConfigVersion> = {
110
+ premintConfig: PremintConfigForVersion<T>;
111
+ premintConfigVersion: T;
112
+ };
113
+
114
+ export type PremintConfigAndVersion =
115
+ | PremintConfigWithVersion<"1">
116
+ | PremintConfigWithVersion<"2">;
117
+
118
+ export const getPremintExecutorAddress = () =>
119
+ zoraCreator1155PremintExecutorImplAddress[999];
120
+
121
+ /**
122
+ * Creates a typed data definition for a premint config. Works for all versions of the premint config by specifying the premintConfigVersion.
123
+ *
124
+ * @param params.verifyingContract the address of the 1155 contract
125
+ * @param params.chainId the chain id the premint is signed for
126
+ * @param params.premintConfigVersion the version of the premint config
127
+ * @param params.premintConfig the premint config
128
+ * @returns
129
+ */
130
+ export const premintTypedDataDefinition = ({
20
131
  verifyingContract,
21
- premintConfig,
22
132
  chainId,
133
+ premintConfigVersion: version,
134
+ premintConfig,
23
135
  }: {
24
136
  verifyingContract: Address;
25
- premintConfig: PremintConfig;
26
137
  chainId: number;
27
- }) => {
28
- const { tokenConfig, uid, version, deleted } = premintConfig;
29
- const types = {
30
- CreatorAttribution: [
31
- { name: "tokenConfig", type: "TokenCreationConfig" },
32
- // unique id scoped to the contract and token to create.
33
- // ensure that a signature can be replaced, as long as the replacement
34
- // has the same uid, and a newer version.
35
- { name: "uid", type: "uint32" },
36
- { name: "version", type: "uint32" },
37
- // if this update should result in the signature being deleted.
38
- { name: "deleted", type: "bool" },
39
- ],
40
- TokenCreationConfig: [
41
- { name: "tokenURI", type: "string" },
42
- { name: "maxSupply", type: "uint256" },
43
- { name: "maxTokensPerAddress", type: "uint64" },
44
- { name: "pricePerToken", type: "uint96" },
45
- { name: "mintStart", type: "uint64" },
46
- { name: "mintDuration", type: "uint64" },
47
- { name: "royaltyMintSchedule", type: "uint32" },
48
- { name: "royaltyBPS", type: "uint32" },
49
- { name: "royaltyRecipient", type: "address" },
50
- { name: "fixedPriceMinter", type: "address" },
51
- ],
138
+ } & PremintConfigAndVersion): TypedDataDefinition => {
139
+ const domain = {
140
+ chainId,
141
+ name: PreminterDomain,
142
+ version,
143
+ verifyingContract: verifyingContract,
144
+ };
145
+
146
+ if (version === PremintConfigVersion.V1)
147
+ return {
148
+ domain,
149
+ types: v1Types,
150
+ message: premintConfig,
151
+ primaryType: "CreatorAttribution",
152
+ } satisfies TypedDataDefinition<typeof v1Types, "CreatorAttribution">;
153
+ if (version === PremintConfigVersion.V2) {
154
+ return {
155
+ domain,
156
+ types: v2Types,
157
+ message: premintConfig,
158
+ primaryType: "CreatorAttribution",
159
+ } satisfies TypedDataDefinition<typeof v2Types, "CreatorAttribution">;
160
+ }
161
+
162
+ throw new Error(`Invalid version ${version}`);
163
+ };
164
+
165
+ export type IsValidSignatureReturn = {
166
+ isAuthorized: boolean;
167
+ recoveredAddress?: Address;
168
+ };
169
+
170
+ export async function isAuthorizedToCreatePremint({
171
+ collection,
172
+ collectionAddress,
173
+ publicClient,
174
+ premintConfig,
175
+ premintConfigVersion,
176
+ signature,
177
+ signer,
178
+ }: {
179
+ collection: ContractCreationConfig;
180
+ collectionAddress: Address;
181
+ publicClient: PublicClient;
182
+ signature: Hex;
183
+ signer: Address;
184
+ } & PremintConfigAndVersion) {
185
+ // if we are using legacy version of premint config, we can use the function
186
+ // "isValidSignature" which we know exists on the premint executor contract
187
+ if (premintConfigVersion === PremintConfigVersion.V1) {
188
+ const [isValidSignature] = await publicClient.readContract({
189
+ abi: zoraCreator1155PremintExecutorImplABI,
190
+ address: getPremintExecutorAddress(),
191
+ functionName: "isValidSignature",
192
+ args: [collection, premintConfig, signature],
193
+ });
194
+
195
+ return isValidSignature;
196
+ }
197
+
198
+ // otherwize, we must assume the newer version of premint executor is deployed, so we call that.
199
+ return await publicClient.readContract({
200
+ abi: preminterAbi,
201
+ address: getPremintExecutorAddress(),
202
+ functionName: "isAuthorizedToCreatePremint",
203
+ args: [signer, collection.contractAdmin, collectionAddress],
204
+ });
205
+ }
206
+
207
+ export async function recoverPremintSigner({
208
+ signature,
209
+ ...rest
210
+ }: {
211
+ signature: Hex;
212
+ chainId: number;
213
+ verifyingContract: Address;
214
+ } & PremintConfigAndVersion): Promise<Address> {
215
+ const typedData = premintTypedDataDefinition(rest);
216
+ return await recoverTypedDataAddress({
217
+ ...typedData,
218
+ signature,
219
+ });
220
+ }
221
+
222
+ export async function tryRecoverPremintSigner(
223
+ params: Parameters<typeof recoverPremintSigner>[0],
224
+ ) {
225
+ try {
226
+ return await recoverPremintSigner(params);
227
+ } catch (error) {
228
+ console.error(error);
229
+ return undefined;
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Recovers the address from a typed data signature and then checks if the recovered address is authorized to create a premint
235
+ *
236
+ * @param params validationProperties
237
+ * @param params.typedData typed data definition for premint config
238
+ * @param params.signature signature to validate
239
+ * @param params.publicClient public rpc read-only client
240
+ * @param params.premintConfigContractAdmin the original contractAdmin on the ContractCreationConfig for the premint; this is usually the original creator of the premint
241
+ * @param params.tokenContract the address of the 1155 contract
242
+ * @returns
243
+ */
244
+ export async function isValidSignature({
245
+ signature,
246
+ publicClient,
247
+ collection,
248
+ chainId,
249
+ ...premintConfigAndVersion
250
+ }: {
251
+ collection: ContractCreationConfig;
252
+ signature: Hex;
253
+ chainId: number;
254
+ publicClient: PublicClient;
255
+ } & PremintConfigAndVersion): Promise<IsValidSignatureReturn> {
256
+ const tokenContract = await getPremintCollectionAddress({
257
+ collection,
258
+ publicClient,
259
+ });
260
+ const recoveredAddress = await tryRecoverPremintSigner({
261
+ ...premintConfigAndVersion,
262
+ signature,
263
+ verifyingContract: tokenContract,
264
+ chainId,
265
+ });
266
+
267
+ if (!recoverAddress) {
268
+ return {
269
+ isAuthorized: false,
270
+ };
271
+ }
272
+
273
+ const isAuthorized = await isAuthorizedToCreatePremint({
274
+ signer: recoveredAddress!,
275
+ collection,
276
+ collectionAddress: tokenContract,
277
+ publicClient,
278
+ signature,
279
+ ...premintConfigAndVersion,
280
+ });
281
+
282
+ return {
283
+ isAuthorized,
284
+ recoveredAddress,
285
+ };
286
+ }
287
+
288
+ /**
289
+ * Converts a premint config from v1 to v2
290
+ *
291
+ * @param premintConfig premint config to convert
292
+ * @param createReferral address that referred the creator, that will receive create referral rewards for the created token
293
+ */
294
+ export function migratePremintConfigToV2({
295
+ premintConfig,
296
+ createReferral = zeroAddress,
297
+ }: {
298
+ premintConfig: PremintConfigV1;
299
+ createReferral: Address;
300
+ }): PremintConfigV2 {
301
+ return {
302
+ ...premintConfig,
303
+ tokenConfig: {
304
+ tokenURI: premintConfig.tokenConfig.tokenURI,
305
+ maxSupply: premintConfig.tokenConfig.maxSupply,
306
+ maxTokensPerAddress: premintConfig.tokenConfig.maxTokensPerAddress,
307
+ pricePerToken: premintConfig.tokenConfig.pricePerToken,
308
+ mintStart: premintConfig.tokenConfig.mintStart,
309
+ mintDuration: premintConfig.tokenConfig.mintDuration,
310
+ payoutRecipient: premintConfig.tokenConfig.royaltyRecipient,
311
+ royaltyBPS: premintConfig.tokenConfig.royaltyBPS,
312
+ fixedPriceMinter: premintConfig.tokenConfig.fixedPriceMinter,
313
+ createReferral,
314
+ },
52
315
  };
316
+ }
53
317
 
54
- const result: TypedDataDefinition<typeof types, "CreatorAttribution"> = {
318
+ export type CreatorAttributionEventParams = GetEventArgs<
319
+ typeof zoraCreator1155ImplABI,
320
+ "CreatorAttribution",
321
+ { EnableUnion: false }
322
+ >;
323
+
324
+ /**
325
+ * Recovers the address from a CreatorAttribution event emitted from a ZoraCreator1155 contract
326
+ * Useful for verifying that the creator of a token is the one who signed a premint for its creation.
327
+ *
328
+
329
+ * @param creatorAttribution parameters from the CreatorAttribution event
330
+ * @param chainId the chain id of the current chain
331
+ * @param tokenContract the address of the 1155 contract
332
+ * @returns the address of the signer
333
+ */
334
+ export const recoverCreatorFromCreatorAttribution = async ({
335
+ creatorAttribution: { version, domainName, structHash, signature },
336
+ chainId,
337
+ tokenContract,
338
+ }: {
339
+ creatorAttribution: CreatorAttributionEventParams;
340
+ tokenContract: Address;
341
+ chainId: number;
342
+ }) => {
343
+ // hash the eip712 domain based on the parameters emitted from the event:
344
+ const hashedDomain = hashDomain({
55
345
  domain: {
56
346
  chainId,
57
- name: "Preminter",
58
- version: "1",
59
- verifyingContract: verifyingContract,
60
- },
61
- types,
62
- message: {
63
- tokenConfig,
64
- uid,
347
+ name: domainName,
348
+ verifyingContract: tokenContract,
65
349
  version,
66
- deleted,
67
350
  },
68
- primaryType: "CreatorAttribution",
69
- };
351
+ types: {
352
+ EIP712Domain: [
353
+ { name: "name", type: "string" },
354
+ { name: "version", type: "string" },
355
+ {
356
+ name: "chainId",
357
+ type: "uint256",
358
+ },
359
+ {
360
+ name: "verifyingContract",
361
+ type: "address",
362
+ },
363
+ ],
364
+ },
365
+ });
366
+
367
+ // re-build the eip-712 typed data hash, consisting of the hashed domain and the structHash emitted from the event:
368
+ const parts: Hex[] = ["0x1901", hashedDomain, structHash!];
369
+
370
+ const hashedTypedData = keccak256(concat(parts));
371
+
372
+ return await recoverAddress({
373
+ hash: hashedTypedData,
374
+ signature: signature!,
375
+ });
376
+ };
377
+
378
+ /**
379
+ * Checks if the 1155 contract at that address supports the given version of the premint config.
380
+ */
381
+ export const supportsPremintVersion = async (
382
+ version: PremintConfigVersion,
383
+ tokenContract: Address,
384
+ publicClient: PublicClient,
385
+ ): Promise<boolean> => {
386
+ const supportedPremintSignatureVersions = await publicClient.readContract({
387
+ abi: preminterAbi,
388
+ address: getPremintExecutorAddress(),
389
+ functionName: "supportedPremintSignatureVersions",
390
+ args: [tokenContract],
391
+ });
70
392
 
71
- return result;
393
+ return supportedPremintSignatureVersions.includes(version);
72
394
  };
395
+
396
+ export async function getPremintCollectionAddress({
397
+ collection,
398
+ publicClient,
399
+ }: {
400
+ collection: ContractCreationConfig;
401
+ publicClient: PublicClient;
402
+ }): Promise<Address> {
403
+ return publicClient.readContract({
404
+ address: getPremintExecutorAddress(),
405
+ abi: zoraCreator1155PremintExecutorImplABI,
406
+ functionName: "getContractAddress",
407
+ args: [collection],
408
+ });
409
+ }
package/src/types.ts ADDED
@@ -0,0 +1 @@
1
+ export type GenericTokenIdTypes = number | bigint | string;