@zoralabs/protocol-sdk 0.7.3-SPARKS.0 → 0.7.3

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.
@@ -0,0 +1,866 @@
1
+ import {
2
+ Address,
3
+ hashTypedData,
4
+ keccak256,
5
+ stringToBytes,
6
+ zeroAddress,
7
+ } from "viem";
8
+ import { zoraSepolia } from "viem/chains";
9
+ import { describe, expect } from "vitest";
10
+ import { parseEther } from "viem";
11
+ import {
12
+ zoraCreator1155PremintExecutorImplABI as preminterAbi,
13
+ zoraCreator1155ImplABI,
14
+ zoraCreator1155FactoryImplAddress,
15
+ zoraCreator1155FactoryImplConfig,
16
+ PremintConfigV1,
17
+ TokenCreationConfigV1,
18
+ PremintConfigVersion,
19
+ TokenCreationConfigV2,
20
+ PremintConfigV2,
21
+ PremintMintArguments,
22
+ ContractCreationConfig,
23
+ premintTypedDataDefinition,
24
+ encodePremintConfig,
25
+ } from "@zoralabs/protocol-deployments";
26
+
27
+ import {
28
+ isValidSignature,
29
+ getPremintExecutorAddress,
30
+ getPremintMintCosts,
31
+ } from "./preminter";
32
+ import { AnvilViemClientsTest, forkUrls, makeAnvilTest } from "src/anvil";
33
+ import { privateKeyToAccount } from "viem/accounts";
34
+
35
+ const erc1271Abi = [
36
+ {
37
+ type: "function",
38
+ name: "isValidSignature",
39
+ inputs: [
40
+ { name: "_hash", type: "bytes32", internalType: "bytes32" },
41
+ { name: "_signature", type: "bytes", internalType: "bytes" },
42
+ ],
43
+ outputs: [{ name: "", type: "bytes4", internalType: "bytes4" }],
44
+ stateMutability: "view",
45
+ },
46
+ ] as const;
47
+
48
+ // create token and contract creation config:
49
+ export const defaultContractConfig = ({
50
+ contractAdmin,
51
+ }: {
52
+ contractAdmin: Address;
53
+ }): ContractCreationConfig => ({
54
+ contractAdmin,
55
+ contractURI: "ipfs://asdfasdfasdf",
56
+ contractName: "My fun NFT",
57
+ additionalAdmins: [],
58
+ });
59
+
60
+ const defaultTokenConfigV1 = (
61
+ fixedPriceMinterAddress: Address,
62
+ creatorAccount: Address,
63
+ ): TokenCreationConfigV1 => ({
64
+ tokenURI: "ipfs://tokenIpfsId0",
65
+ maxSupply: 100n,
66
+ maxTokensPerAddress: 10n,
67
+ pricePerToken: 0n,
68
+ mintStart: 0n,
69
+ mintDuration: 100n,
70
+ royaltyMintSchedule: 30,
71
+ royaltyBPS: 200,
72
+ royaltyRecipient: creatorAccount,
73
+ fixedPriceMinter: fixedPriceMinterAddress,
74
+ });
75
+
76
+ const defaultTokenConfigV2 = (
77
+ fixedPriceMinterAddress: Address,
78
+ creatorAccount: Address,
79
+ createReferral: Address,
80
+ pricePerToken = 0n,
81
+ ): TokenCreationConfigV2 => ({
82
+ tokenURI: "ipfs://tokenIpfsId0",
83
+ maxSupply: 100n,
84
+ maxTokensPerAddress: 1000n,
85
+ pricePerToken,
86
+ mintStart: 0n,
87
+ mintDuration: 100n,
88
+ royaltyBPS: 200,
89
+ payoutRecipient: creatorAccount,
90
+ fixedPriceMinter: fixedPriceMinterAddress,
91
+ createReferral,
92
+ });
93
+
94
+ const defaultPremintConfigV1 = ({
95
+ fixedPriceMinter,
96
+ creatorAccount,
97
+ }: {
98
+ fixedPriceMinter: Address;
99
+ creatorAccount: Address;
100
+ }): PremintConfigV1 => ({
101
+ tokenConfig: defaultTokenConfigV1(fixedPriceMinter, creatorAccount),
102
+ deleted: false,
103
+ uid: 105,
104
+ version: 0,
105
+ });
106
+
107
+ export const defaultPremintConfigV2 = ({
108
+ fixedPriceMinter,
109
+ creatorAccount,
110
+ createReferral = zeroAddress,
111
+ pricePerToken = 0n,
112
+ }: {
113
+ fixedPriceMinter: Address;
114
+ creatorAccount: Address;
115
+ createReferral?: Address;
116
+ pricePerToken?: bigint;
117
+ }): PremintConfigV2 => ({
118
+ tokenConfig: defaultTokenConfigV2(
119
+ fixedPriceMinter,
120
+ creatorAccount,
121
+ createReferral,
122
+ pricePerToken,
123
+ ),
124
+ deleted: false,
125
+ uid: 106,
126
+ version: 0,
127
+ });
128
+
129
+ const PREMINTER_ADDRESS = getPremintExecutorAddress();
130
+
131
+ async function setupContracts({
132
+ viemClients: { walletClient, testClient, publicClient, chain },
133
+ }: AnvilViemClientsTest) {
134
+ // JSON-RPC Account
135
+ const [
136
+ deployerAccount,
137
+ creatorAccount,
138
+ collectorAccount,
139
+ collaboratorAccount,
140
+ ] = (await walletClient.getAddresses()) as [
141
+ Address,
142
+ Address,
143
+ Address,
144
+ Address,
145
+ ];
146
+
147
+ // deploy signature minter contract
148
+ await testClient.setBalance({
149
+ address: deployerAccount,
150
+ value: parseEther("10"),
151
+ });
152
+
153
+ const fixedPriceMinterAddress = await publicClient.readContract({
154
+ abi: zoraCreator1155FactoryImplConfig.abi,
155
+ address:
156
+ zoraCreator1155FactoryImplAddress[
157
+ chain.id as keyof typeof zoraCreator1155FactoryImplAddress
158
+ ],
159
+ functionName: "fixedPriceMinter",
160
+ });
161
+
162
+ return {
163
+ accounts: {
164
+ deployerAccount,
165
+ creatorAccount,
166
+ collectorAccount,
167
+ collaboratorAccount,
168
+ },
169
+ fixedPriceMinterAddress,
170
+ };
171
+ }
172
+
173
+ const zoraSepoliaAnvilTest = makeAnvilTest({
174
+ forkUrl: forkUrls.zoraSepolia,
175
+ forkBlockNumber: 9467979,
176
+ anvilChainId: zoraSepolia.id,
177
+ });
178
+
179
+ describe("ZoraCreator1155Preminter", () => {
180
+ zoraSepoliaAnvilTest(
181
+ "can sign on the forked premint contract",
182
+ async ({ viemClients }) => {
183
+ const {
184
+ fixedPriceMinterAddress,
185
+ accounts: { creatorAccount },
186
+ } = await setupContracts({ viemClients });
187
+ const premintConfig = defaultPremintConfigV1({
188
+ fixedPriceMinter: fixedPriceMinterAddress,
189
+ creatorAccount,
190
+ });
191
+ const contractConfig = defaultContractConfig({
192
+ contractAdmin: creatorAccount,
193
+ });
194
+
195
+ const preminterAddress = getPremintExecutorAddress();
196
+
197
+ const contractAddress = await viemClients.publicClient.readContract({
198
+ abi: preminterAbi,
199
+ address: preminterAddress,
200
+ functionName: "getContractWithAdditionalAdminsAddress",
201
+ args: [contractConfig],
202
+ });
203
+
204
+ const signedMessage = await viemClients.walletClient.signTypedData({
205
+ ...premintTypedDataDefinition({
206
+ verifyingContract: contractAddress,
207
+ chainId: viemClients.chain.id,
208
+ premintConfig,
209
+ premintConfigVersion: PremintConfigVersion.V1,
210
+ }),
211
+ account: creatorAccount,
212
+ });
213
+
214
+ console.log({
215
+ creatorAccount,
216
+ signedMessage,
217
+ contractConfig,
218
+ premintConfig,
219
+ contractAddress,
220
+ });
221
+ },
222
+ 20 * 1000,
223
+ );
224
+ zoraSepoliaAnvilTest(
225
+ "can sign and recover a v1 premint config signature",
226
+ async ({ viemClients }) => {
227
+ const {
228
+ fixedPriceMinterAddress,
229
+ accounts: { creatorAccount },
230
+ } = await setupContracts({ viemClients });
231
+
232
+ const premintConfig = defaultPremintConfigV1({
233
+ fixedPriceMinter: fixedPriceMinterAddress,
234
+ creatorAccount,
235
+ });
236
+ const contractConfig = defaultContractConfig({
237
+ contractAdmin: creatorAccount,
238
+ });
239
+
240
+ const tokenContract = await viemClients.publicClient.readContract({
241
+ abi: preminterAbi,
242
+ address: PREMINTER_ADDRESS,
243
+ functionName: "getContractWithAdditionalAdminsAddress",
244
+ args: [contractConfig],
245
+ });
246
+
247
+ // sign message containing contract and token creation config and uid
248
+ const signedMessage = await viemClients.walletClient.signTypedData({
249
+ ...premintTypedDataDefinition({
250
+ verifyingContract: tokenContract,
251
+ // we need to sign here for the anvil chain, cause thats where it is run on
252
+ chainId: viemClients.chain.id,
253
+ premintConfig,
254
+ premintConfigVersion: PremintConfigVersion.V1,
255
+ }),
256
+ account: creatorAccount,
257
+ });
258
+
259
+ // recover and verify address is correct
260
+ const { recoveredAddress, isAuthorized } = await isValidSignature({
261
+ collection: contractConfig,
262
+ collectionAddress: tokenContract,
263
+ chainId: viemClients.publicClient.chain!.id,
264
+ premintConfig,
265
+ premintConfigVersion: PremintConfigVersion.V1,
266
+ publicClient: viemClients.publicClient,
267
+ signature: signedMessage,
268
+ });
269
+
270
+ expect(recoveredAddress).to.equal(creatorAccount);
271
+ expect(isAuthorized).toBe(true);
272
+
273
+ expect(recoveredAddress).to.equal(creatorAccount);
274
+ },
275
+
276
+ 20 * 1000,
277
+ );
278
+ zoraSepoliaAnvilTest(
279
+ "can sign and recover a v2 premint config signature",
280
+ async ({ viemClients }) => {
281
+ const {
282
+ fixedPriceMinterAddress,
283
+ accounts: { creatorAccount },
284
+ } = await setupContracts({ viemClients });
285
+
286
+ const premintConfig = defaultPremintConfigV2({
287
+ creatorAccount,
288
+ fixedPriceMinter: fixedPriceMinterAddress,
289
+ createReferral: creatorAccount,
290
+ });
291
+ const contractConfig = defaultContractConfig({
292
+ contractAdmin: creatorAccount,
293
+ });
294
+
295
+ const tokenContract = await viemClients.publicClient.readContract({
296
+ abi: preminterAbi,
297
+ address: PREMINTER_ADDRESS,
298
+ functionName: "getContractWithAdditionalAdminsAddress",
299
+ args: [contractConfig],
300
+ });
301
+
302
+ // sign message containing contract and token creation config and uid
303
+ const signedMessage = await viemClients.walletClient.signTypedData({
304
+ ...premintTypedDataDefinition({
305
+ verifyingContract: tokenContract,
306
+ // we need to sign here for the anvil chain, cause thats where it is run on
307
+ chainId: viemClients.chain.id,
308
+ premintConfig,
309
+ premintConfigVersion: PremintConfigVersion.V2,
310
+ }),
311
+ account: creatorAccount,
312
+ });
313
+
314
+ // recover and verify address is correct
315
+ const { recoveredAddress, isAuthorized } = await isValidSignature({
316
+ collection: contractConfig,
317
+ collectionAddress: tokenContract,
318
+ chainId: viemClients.publicClient.chain!.id,
319
+ premintConfig,
320
+ premintConfigVersion: PremintConfigVersion.V2,
321
+ publicClient: viemClients.publicClient,
322
+ signature: signedMessage,
323
+ });
324
+
325
+ expect(recoveredAddress).to.equal(creatorAccount);
326
+ expect(isAuthorized).toBe(true);
327
+
328
+ expect(recoveredAddress).to.equal(creatorAccount);
329
+ },
330
+
331
+ 20 * 1000,
332
+ );
333
+ zoraSepoliaAnvilTest(
334
+ "can sign and mint multiple tokens",
335
+ async ({ viemClients }) => {
336
+ const {
337
+ fixedPriceMinterAddress,
338
+ accounts: { creatorAccount, collectorAccount },
339
+ } = await setupContracts({ viemClients });
340
+ // setup contract and token creation parameters
341
+ const premintConfig1 = defaultPremintConfigV1({
342
+ fixedPriceMinter: fixedPriceMinterAddress,
343
+ creatorAccount,
344
+ });
345
+ const contractConfig = defaultContractConfig({
346
+ contractAdmin: creatorAccount,
347
+ });
348
+
349
+ // lets make it a random number to not break the existing tests that expect fresh data
350
+ premintConfig1.uid = Math.round(Math.random() * 1000000);
351
+
352
+ let contractAddress = await viemClients.publicClient.readContract({
353
+ abi: preminterAbi,
354
+ address: PREMINTER_ADDRESS,
355
+ functionName: "getContractWithAdditionalAdminsAddress",
356
+ args: [contractConfig],
357
+ });
358
+
359
+ // have creator sign the message to create the contract
360
+ // and the token
361
+ const signedMessage = await viemClients.walletClient.signTypedData({
362
+ ...premintTypedDataDefinition({
363
+ verifyingContract: contractAddress,
364
+ // we need to sign here for the anvil chain, cause thats where it is run on
365
+ chainId: viemClients.chain.id,
366
+ premintConfig: premintConfig1,
367
+ premintConfigVersion: PremintConfigVersion.V1,
368
+ }),
369
+ account: creatorAccount,
370
+ });
371
+
372
+ const quantityToMint = 2n;
373
+
374
+ const valueToSend = (
375
+ await getPremintMintCosts({
376
+ publicClient: viemClients.publicClient,
377
+ quantityToMint,
378
+ tokenContract: contractAddress,
379
+ tokenPrice: premintConfig1.tokenConfig.pricePerToken,
380
+ })
381
+ ).totalCostEth;
382
+
383
+ await viemClients.testClient.setBalance({
384
+ address: collectorAccount,
385
+ value: parseEther("10"),
386
+ });
387
+
388
+ // get the premint status - it should not be minted
389
+ let [contractCreated, tokenId] =
390
+ await viemClients.publicClient.readContract({
391
+ abi: preminterAbi,
392
+ address: PREMINTER_ADDRESS,
393
+ functionName: "premintStatus",
394
+ args: [contractAddress, premintConfig1.uid],
395
+ });
396
+
397
+ expect(contractCreated).toBe(false);
398
+ expect(tokenId).toBe(0n);
399
+
400
+ const mintArguments: PremintMintArguments = {
401
+ mintComment: "",
402
+ mintRecipient: collectorAccount,
403
+ mintRewardsRecipients: [],
404
+ };
405
+
406
+ const firstMinter = collectorAccount;
407
+
408
+ // now have the collector execute the first signed message;
409
+ // it should create the contract, the token,
410
+ // and min the quantity to mint tokens to the collector
411
+ // the signature along with contract + token creation
412
+ // parameters are required to call this function
413
+ const mintHash = await viemClients.walletClient.writeContract({
414
+ abi: preminterAbi,
415
+ functionName: "premint",
416
+ account: collectorAccount,
417
+ chain: viemClients.chain,
418
+ address: PREMINTER_ADDRESS,
419
+ args: [
420
+ contractConfig,
421
+ zeroAddress,
422
+ encodePremintConfig({
423
+ premintConfig: premintConfig1,
424
+ premintConfigVersion: PremintConfigVersion.V1,
425
+ }),
426
+ signedMessage,
427
+ quantityToMint,
428
+ mintArguments,
429
+ firstMinter,
430
+ zeroAddress,
431
+ ],
432
+ value: valueToSend,
433
+ });
434
+
435
+ // ensure it succeeded
436
+ const receipt = await viemClients.publicClient.waitForTransactionReceipt({
437
+ hash: mintHash,
438
+ });
439
+
440
+ expect(receipt.status).toBe("success");
441
+
442
+ // fetch the premint token id
443
+ [contractCreated, tokenId] = await viemClients.publicClient.readContract({
444
+ abi: preminterAbi,
445
+ address: PREMINTER_ADDRESS,
446
+ functionName: "premintStatus",
447
+ args: [contractAddress, premintConfig1.uid],
448
+ });
449
+
450
+ expect(contractCreated).toBe(true);
451
+ expect(tokenId).not.toBe(0n);
452
+
453
+ // now use what was created, to get the balance from the created contract
454
+ const tokenBalance = await viemClients.publicClient.readContract({
455
+ abi: zoraCreator1155ImplABI,
456
+ address: contractAddress,
457
+ functionName: "balanceOf",
458
+ args: [collectorAccount, tokenId],
459
+ });
460
+
461
+ // get token balance - should be amount that was created
462
+ expect(tokenBalance).toBe(quantityToMint);
463
+
464
+ const premintConfig2 = defaultPremintConfigV2({
465
+ creatorAccount,
466
+ fixedPriceMinter: fixedPriceMinterAddress,
467
+ createReferral: creatorAccount,
468
+ });
469
+
470
+ // sign the message to create the second token
471
+ const signedMessage2 = await viemClients.walletClient.signTypedData({
472
+ ...premintTypedDataDefinition({
473
+ verifyingContract: contractAddress,
474
+ chainId: viemClients.chain.id,
475
+ premintConfig: premintConfig2,
476
+ premintConfigVersion: PremintConfigVersion.V2,
477
+ }),
478
+ account: creatorAccount,
479
+ });
480
+
481
+ const quantityToMint2 = 4n;
482
+
483
+ const valueToSend2 = (
484
+ await getPremintMintCosts({
485
+ publicClient: viemClients.publicClient,
486
+ quantityToMint: quantityToMint2,
487
+ tokenContract: contractAddress,
488
+ tokenPrice: premintConfig2.tokenConfig.pricePerToken,
489
+ })
490
+ ).totalCostEth;
491
+
492
+ const simulationResult = await viemClients.publicClient.simulateContract({
493
+ abi: preminterAbi,
494
+ functionName: "premint",
495
+ account: collectorAccount,
496
+ chain: viemClients.chain,
497
+ address: PREMINTER_ADDRESS,
498
+ args: [
499
+ contractConfig,
500
+ zeroAddress,
501
+ encodePremintConfig({
502
+ premintConfig: premintConfig2,
503
+ premintConfigVersion: PremintConfigVersion.V2,
504
+ }),
505
+ signedMessage2,
506
+ quantityToMint2,
507
+ mintArguments,
508
+ firstMinter,
509
+ zeroAddress,
510
+ ],
511
+ value: valueToSend2,
512
+ });
513
+
514
+ // now have the collector execute the second signed message.
515
+ // it should create a new token against the existing contract
516
+ const mintHash2 = await viemClients.walletClient.writeContract(
517
+ simulationResult.request,
518
+ );
519
+
520
+ const premintV2Receipt =
521
+ await viemClients.publicClient.waitForTransactionReceipt({
522
+ hash: mintHash2,
523
+ });
524
+
525
+ expect(premintV2Receipt.status).toBe("success");
526
+
527
+ // now premint status for the second mint, it should be minted
528
+ [, tokenId] = await viemClients.publicClient.readContract({
529
+ abi: preminterAbi,
530
+ address: PREMINTER_ADDRESS,
531
+ functionName: "premintStatus",
532
+ args: [contractAddress, premintConfig2.uid],
533
+ });
534
+
535
+ expect(tokenId).not.toBe(0n);
536
+
537
+ // get balance of second token
538
+ const tokenBalance2 = await viemClients.publicClient.readContract({
539
+ abi: zoraCreator1155ImplABI,
540
+ address: contractAddress,
541
+ functionName: "balanceOf",
542
+ args: [collectorAccount, tokenId],
543
+ });
544
+
545
+ expect(tokenBalance2).toBe(quantityToMint2);
546
+ },
547
+ 40 * 1000,
548
+ ),
549
+ zoraSepoliaAnvilTest(
550
+ "can sign a premint from an smart contract account and mint tokens against that premint",
551
+ async ({ viemClients }) => {
552
+ const {
553
+ fixedPriceMinterAddress,
554
+ accounts: { collectorAccount },
555
+ } = await setupContracts({ viemClients });
556
+
557
+ // this test shows how to create a premint that is signed by an EOA, but the premint
558
+ // contract admin will be a smart wallet owned by the EOA.
559
+ // contract admin is set as the smart wallet. Smart wallet owner is the EOA.
560
+ // EOA signs the premint.
561
+ // When calling `premint` smart wallets address must be passed as an argument
562
+
563
+ // this was an AA contract that was deployed that has has the owner below as the
564
+ // valid signer. See https://sepolia.explorer.zora.energy/address/0x74F5fAf983d54FEd6D937654Aa4FD258534F2d4B?tab=contract
565
+ // it was deployed via the script `packages/1155-deployments/script/DeploySimpleAA.s.sol`
566
+ const smartWalletAddress = "0x74F5fAf983d54FEd6D937654Aa4FD258534F2d4B";
567
+ const ownerAddress = "0x7c8999dC9a822c1f0Df42023113EDB4FDd543266";
568
+ const ownerPrivateKey =
569
+ "0x02016836a56b71f0d02689e69e326f4f4c1b9057164ef592671cf0d37c8040c0";
570
+
571
+ const ownerAccount = privateKeyToAccount(ownerPrivateKey);
572
+
573
+ const premintConfig = defaultPremintConfigV2({
574
+ fixedPriceMinter: fixedPriceMinterAddress,
575
+ // we set the creator to the AA contract
576
+ creatorAccount: smartWalletAddress,
577
+ });
578
+
579
+ expect(ownerAccount.address).toBe(ownerAddress);
580
+
581
+ const contractConfig = defaultContractConfig({
582
+ // for the contract config, we set the smart wallet as the admin
583
+ contractAdmin: smartWalletAddress,
584
+ });
585
+
586
+ let contractAddress = await viemClients.publicClient.readContract({
587
+ abi: preminterAbi,
588
+ address: PREMINTER_ADDRESS,
589
+ functionName: "getContractWithAdditionalAdminsAddress",
590
+ args: [contractConfig],
591
+ });
592
+
593
+ const typedData = premintTypedDataDefinition({
594
+ verifyingContract: contractAddress,
595
+ // we need to sign here for the anvil chain, cause thats where it is run on
596
+ chainId: viemClients.chain.id,
597
+ premintConfig: premintConfig,
598
+ premintConfigVersion: PremintConfigVersion.V2,
599
+ });
600
+
601
+ // have creator sign the message to create the contract
602
+ // and the token
603
+ const signedMessage = await viemClients.walletClient.signTypedData({
604
+ ...typedData,
605
+ account: ownerAccount,
606
+ });
607
+
608
+ // sanity check - validate the signature on the smart wallet contract
609
+ const result = await viemClients.publicClient.readContract({
610
+ abi: erc1271Abi,
611
+ address: smartWalletAddress,
612
+ functionName: "isValidSignature",
613
+ args: [hashTypedData(typedData), signedMessage],
614
+ });
615
+
616
+ // if is a valid signature, signature should return `bytes4(keccak256("isValidSignature(bytes32,bytes)"))`
617
+ const expectedMagicValue = keccak256(
618
+ stringToBytes("isValidSignature(bytes32,bytes)"),
619
+ ).slice(0, 10);
620
+
621
+ expect(result).toBe(expectedMagicValue);
622
+
623
+ const quantityToMint = 2n;
624
+
625
+ const valueToSend = (
626
+ await getPremintMintCosts({
627
+ publicClient: viemClients.publicClient,
628
+ quantityToMint,
629
+ tokenContract: contractAddress,
630
+ tokenPrice: premintConfig.tokenConfig.pricePerToken,
631
+ })
632
+ ).totalCostEth;
633
+
634
+ await viemClients.testClient.setBalance({
635
+ address: collectorAccount,
636
+ value: parseEther("10"),
637
+ });
638
+
639
+ const mintArguments: PremintMintArguments = {
640
+ mintComment: "",
641
+ mintRecipient: collectorAccount,
642
+ mintRewardsRecipients: [],
643
+ };
644
+
645
+ const firstMinter = collectorAccount;
646
+
647
+ // now have the collector execute the first signed message;
648
+ // it should create the contract, the token,
649
+ // and min the quantity to mint tokens to the collector
650
+ // the signature along with contract + token creation
651
+ // parameters are required to call this function
652
+ await viemClients.publicClient.simulateContract({
653
+ abi: preminterAbi,
654
+ functionName: "premint",
655
+ account: collectorAccount,
656
+ chain: viemClients.chain,
657
+ address: PREMINTER_ADDRESS,
658
+ args: [
659
+ contractConfig,
660
+ zeroAddress,
661
+ encodePremintConfig({
662
+ premintConfig: premintConfig,
663
+ premintConfigVersion: PremintConfigVersion.V2,
664
+ }),
665
+ signedMessage,
666
+ quantityToMint,
667
+ mintArguments,
668
+ firstMinter,
669
+ // we must specify the smart wallet address in the call, so that the 1155 contract
670
+ // knows to have the smart wallet validate the signature
671
+ smartWalletAddress,
672
+ ],
673
+ value: valueToSend,
674
+ });
675
+ },
676
+ 40 * 1000,
677
+ ),
678
+ zoraSepoliaAnvilTest(
679
+ "can have collaborators create premints that can be executed on existing contracts",
680
+ async ({ viemClients }) => {
681
+ const {
682
+ fixedPriceMinterAddress,
683
+ accounts: { creatorAccount, collectorAccount, collaboratorAccount },
684
+ } = await setupContracts({ viemClients });
685
+
686
+ await viemClients.testClient.setBalance({
687
+ address: collectorAccount,
688
+ value: parseEther("10"),
689
+ });
690
+
691
+ // setup contract and token creation parameters
692
+ const premintConfig = defaultPremintConfigV2({
693
+ fixedPriceMinter: fixedPriceMinterAddress,
694
+ creatorAccount,
695
+ });
696
+ // lets make it a random number to not break the existing tests that expect fresh data
697
+ premintConfig.uid = Math.round(Math.random() * 1000000);
698
+
699
+ // create a premint config that a collaboratorw ill sign
700
+ const collaboratorPremintConfig = {
701
+ ...premintConfig,
702
+ uid: Math.round(Math.random() * 1000000),
703
+ };
704
+
705
+ const contractConfig = defaultContractConfig({
706
+ contractAdmin: creatorAccount,
707
+ });
708
+
709
+ // modify contract config to have collaborators
710
+ contractConfig.additionalAdmins = [collaboratorAccount];
711
+
712
+ const contractAddress = await viemClients.publicClient.readContract({
713
+ abi: preminterAbi,
714
+ address: PREMINTER_ADDRESS,
715
+ functionName: "getContractWithAdditionalAdminsAddress",
716
+ args: [contractConfig],
717
+ });
718
+
719
+ // have creator sign the message to create the contract
720
+ // and the token
721
+ const creatorSignedMessage =
722
+ await viemClients.walletClient.signTypedData({
723
+ ...premintTypedDataDefinition({
724
+ verifyingContract: contractAddress,
725
+ // we need to sign here for the anvil chain, cause thats where it is run on
726
+ chainId: viemClients.chain.id,
727
+ premintConfig: premintConfig,
728
+ premintConfigVersion: PremintConfigVersion.V2,
729
+ }),
730
+ account: creatorAccount,
731
+ });
732
+
733
+ const collaboratorSignedMessage =
734
+ await viemClients.walletClient.signTypedData({
735
+ ...premintTypedDataDefinition({
736
+ verifyingContract: contractAddress,
737
+ // we need to sign here for the anvil chain, cause thats where it is run on
738
+ chainId: viemClients.chain.id,
739
+ premintConfig: collaboratorPremintConfig,
740
+ premintConfigVersion: PremintConfigVersion.V2,
741
+ }),
742
+ account: collaboratorAccount,
743
+ });
744
+
745
+ const quantityToMint = 2n;
746
+
747
+ const valueToSend = (
748
+ await getPremintMintCosts({
749
+ publicClient: viemClients.publicClient,
750
+ quantityToMint,
751
+ tokenContract: contractAddress,
752
+ tokenPrice: premintConfig.tokenConfig.pricePerToken,
753
+ })
754
+ ).totalCostEth;
755
+
756
+ const mintArguments: PremintMintArguments = {
757
+ mintComment: "",
758
+ mintRecipient: collectorAccount,
759
+ mintRewardsRecipients: [],
760
+ };
761
+
762
+ const firstMinter = collectorAccount;
763
+
764
+ await viemClients.publicClient.simulateContract({
765
+ abi: preminterAbi,
766
+ functionName: "premint",
767
+ account: collectorAccount,
768
+ chain: viemClients.chain,
769
+ address: PREMINTER_ADDRESS,
770
+ args: [
771
+ contractConfig,
772
+ zeroAddress,
773
+ encodePremintConfig({
774
+ premintConfig: collaboratorPremintConfig,
775
+ premintConfigVersion: PremintConfigVersion.V2,
776
+ }),
777
+ collaboratorSignedMessage,
778
+ quantityToMint,
779
+ mintArguments,
780
+ firstMinter,
781
+ zeroAddress,
782
+ ],
783
+ value: valueToSend,
784
+ });
785
+
786
+ // now have the collector execute collaborators signed message;
787
+ // it should create the contract, the token, and add the collaborator
788
+ // as an admin to the contract along with the original creator
789
+ let tx = await viemClients.walletClient.writeContract({
790
+ abi: preminterAbi,
791
+ functionName: "premint",
792
+ account: collectorAccount,
793
+ chain: viemClients.chain,
794
+ address: PREMINTER_ADDRESS,
795
+ args: [
796
+ contractConfig,
797
+ zeroAddress,
798
+ encodePremintConfig({
799
+ premintConfig: collaboratorPremintConfig,
800
+ premintConfigVersion: PremintConfigVersion.V2,
801
+ }),
802
+ collaboratorSignedMessage,
803
+ quantityToMint,
804
+ mintArguments,
805
+ firstMinter,
806
+ zeroAddress,
807
+ ],
808
+ value: valueToSend,
809
+ });
810
+
811
+ // ensure it succeeded
812
+ expect(
813
+ (
814
+ await viemClients.publicClient.waitForTransactionReceipt({
815
+ hash: tx,
816
+ })
817
+ ).status,
818
+ ).toBe("success");
819
+
820
+ tx = await viemClients.walletClient.writeContract({
821
+ abi: preminterAbi,
822
+ functionName: "premint",
823
+ account: collectorAccount,
824
+ chain: viemClients.chain,
825
+ address: PREMINTER_ADDRESS,
826
+ args: [
827
+ contractConfig,
828
+ zeroAddress,
829
+ encodePremintConfig({
830
+ premintConfig: premintConfig,
831
+ premintConfigVersion: PremintConfigVersion.V2,
832
+ }),
833
+ creatorSignedMessage,
834
+ quantityToMint,
835
+ mintArguments,
836
+ firstMinter,
837
+ zeroAddress,
838
+ ],
839
+ value: valueToSend,
840
+ });
841
+
842
+ expect(
843
+ (
844
+ await viemClients.publicClient.waitForTransactionReceipt({
845
+ hash: tx,
846
+ })
847
+ ).status,
848
+ ).toBe("success");
849
+
850
+ // get balance of second token
851
+ const tokenBalances = await viemClients.publicClient.readContract({
852
+ abi: zoraCreator1155ImplABI,
853
+ address: contractAddress,
854
+ functionName: "balanceOfBatch",
855
+ args: [
856
+ [collectorAccount, collectorAccount],
857
+ [1n, 2n],
858
+ ],
859
+ });
860
+
861
+ expect(tokenBalances).toEqual([quantityToMint, quantityToMint]);
862
+ },
863
+ // 10 second timeout
864
+ 40 * 1000,
865
+ );
866
+ });