@silvana-one/nft 0.2.11 → 1.0.0
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.
- package/README.md +12 -0
- package/dist/node/contracts/admin.d.ts +100 -10
- package/dist/node/contracts/admin.js +130 -29
- package/dist/node/contracts/admin.js.map +1 -1
- package/dist/node/contracts/collection.d.ts +51 -34
- package/dist/node/contracts/collection.js +162 -71
- package/dist/node/contracts/collection.js.map +1 -1
- package/dist/node/contracts/nft.js +3 -0
- package/dist/node/contracts/nft.js.map +1 -1
- package/dist/node/contracts.d.ts +36 -30
- package/dist/node/index.cjs +447 -182
- package/dist/node/interfaces/collection.d.ts +5 -5
- package/dist/node/interfaces/events.d.ts +306 -16
- package/dist/node/interfaces/events.js +42 -4
- package/dist/node/interfaces/events.js.map +1 -1
- package/dist/node/interfaces/ownable.d.ts +7 -7
- package/dist/node/interfaces/ownable.js.map +1 -1
- package/dist/node/interfaces/pausable.d.ts +3 -3
- package/dist/node/interfaces/pausable.js.map +1 -1
- package/dist/node/interfaces/types.d.ts +182 -27
- package/dist/node/interfaces/types.js +120 -71
- package/dist/node/interfaces/types.js.map +1 -1
- package/dist/node/marketplace/auction.d.ts +2 -2
- package/dist/node/marketplace/auction.js +4 -4
- package/dist/node/marketplace/auction.js.map +1 -1
- package/dist/node/marketplace/bid.js +3 -5
- package/dist/node/marketplace/bid.js.map +1 -1
- package/dist/node/marketplace/nft-shares.d.ts +2 -2
- package/dist/node/util/div.js +10 -5
- package/dist/node/util/div.js.map +1 -1
- package/dist/node/vk.js +6 -6
- package/dist/node/vk.js.map +1 -1
- package/dist/node/zkprogram-example/game.d.ts +2 -2
- package/dist/node/zkprogram-example/update.d.ts +2 -2
- package/dist/tsconfig.node.tsbuildinfo +1 -1
- package/dist/tsconfig.web.tsbuildinfo +1 -1
- package/dist/web/contracts/admin.d.ts +100 -10
- package/dist/web/contracts/admin.js +130 -29
- package/dist/web/contracts/admin.js.map +1 -1
- package/dist/web/contracts/collection.d.ts +51 -34
- package/dist/web/contracts/collection.js +162 -71
- package/dist/web/contracts/collection.js.map +1 -1
- package/dist/web/contracts/nft.js +3 -0
- package/dist/web/contracts/nft.js.map +1 -1
- package/dist/web/contracts.d.ts +36 -30
- package/dist/web/interfaces/collection.d.ts +5 -5
- package/dist/web/interfaces/events.d.ts +306 -16
- package/dist/web/interfaces/events.js +42 -4
- package/dist/web/interfaces/events.js.map +1 -1
- package/dist/web/interfaces/ownable.d.ts +7 -7
- package/dist/web/interfaces/ownable.js.map +1 -1
- package/dist/web/interfaces/pausable.d.ts +3 -3
- package/dist/web/interfaces/pausable.js.map +1 -1
- package/dist/web/interfaces/types.d.ts +182 -27
- package/dist/web/interfaces/types.js +120 -71
- package/dist/web/interfaces/types.js.map +1 -1
- package/dist/web/marketplace/auction.d.ts +2 -2
- package/dist/web/marketplace/auction.js +4 -4
- package/dist/web/marketplace/auction.js.map +1 -1
- package/dist/web/marketplace/bid.js +3 -5
- package/dist/web/marketplace/bid.js.map +1 -1
- package/dist/web/marketplace/nft-shares.d.ts +2 -2
- package/dist/web/util/div.js +10 -5
- package/dist/web/util/div.js.map +1 -1
- package/dist/web/vk.js +6 -6
- package/dist/web/vk.js.map +1 -1
- package/dist/web/zkprogram-example/game.d.ts +2 -2
- package/dist/web/zkprogram-example/update.d.ts +2 -2
- package/package.json +8 -8
- package/src/contracts/admin.ts +137 -24
- package/src/contracts/collection.ts +188 -96
- package/src/contracts/nft.ts +3 -0
- package/src/interfaces/collection.ts +11 -5
- package/src/interfaces/events.ts +47 -3
- package/src/interfaces/ownable.ts +1 -1
- package/src/interfaces/pausable.ts +1 -1
- package/src/interfaces/types.ts +152 -78
- package/src/marketplace/auction.ts +5 -4
- package/src/marketplace/bid.ts +4 -6
- package/src/util/div.ts +12 -5
- package/src/vk.ts +6 -6
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
VerificationKey,
|
|
22
22
|
UInt32,
|
|
23
23
|
UInt64,
|
|
24
|
-
SmartContract,
|
|
25
24
|
Mina,
|
|
26
25
|
Provable,
|
|
27
26
|
} from "o1js";
|
|
@@ -29,11 +28,13 @@ import { NFT } from "./nft.js";
|
|
|
29
28
|
import {
|
|
30
29
|
MintParams,
|
|
31
30
|
MintRequest,
|
|
32
|
-
|
|
31
|
+
TransferBySignatureParams,
|
|
32
|
+
TransferByProofParams,
|
|
33
33
|
CollectionData,
|
|
34
34
|
NFTUpdateProof,
|
|
35
35
|
NFTStateStruct,
|
|
36
36
|
MintEvent,
|
|
37
|
+
NFTUpdateEvent,
|
|
37
38
|
TransferEvent,
|
|
38
39
|
ApproveEvent,
|
|
39
40
|
UpgradeVerificationKeyEvent,
|
|
@@ -43,6 +44,11 @@ import {
|
|
|
43
44
|
NFTAdminContractConstructor,
|
|
44
45
|
PausableContract,
|
|
45
46
|
PauseEvent,
|
|
47
|
+
SetNameEvent,
|
|
48
|
+
SetBaseURLEvent,
|
|
49
|
+
SetRoyaltyFeeEvent,
|
|
50
|
+
SetTransferFeeEvent,
|
|
51
|
+
SetAdminEvent,
|
|
46
52
|
OwnableContract,
|
|
47
53
|
OwnershipChangeEvent,
|
|
48
54
|
NFTOwnerBase,
|
|
@@ -85,6 +91,7 @@ const CollectionErrors = {
|
|
|
85
91
|
onlyOwnerCanUpgradeVerificationKey: "Only owner can upgrade verification key",
|
|
86
92
|
invalidRoyaltyFee: "Royalty fee is too high, cannot be more than 100%",
|
|
87
93
|
invalidOracleAddress: "Oracle address is invalid",
|
|
94
|
+
pendingCreatorIsEmpty: "Pending creator address is empty",
|
|
88
95
|
};
|
|
89
96
|
|
|
90
97
|
interface CollectionDeployProps extends Exclude<DeployArgs, undefined> {
|
|
@@ -132,6 +139,8 @@ function CollectionFactory(params: {
|
|
|
132
139
|
* such as flags and fee configurations.
|
|
133
140
|
*/
|
|
134
141
|
@state(Field) packedData = State<Field>();
|
|
142
|
+
/** The public key part (x) of the pending creator. The isOdd field is written to the packedData */
|
|
143
|
+
@state(Field) pendingCreatorX = State<Field>();
|
|
135
144
|
|
|
136
145
|
/**
|
|
137
146
|
* Deploys the NFT Collection Contract with the initial settings.
|
|
@@ -150,6 +159,9 @@ function CollectionFactory(params: {
|
|
|
150
159
|
isPaused: true,
|
|
151
160
|
}).pack()
|
|
152
161
|
);
|
|
162
|
+
this.pendingCreatorX.set(PublicKey.empty().x);
|
|
163
|
+
// Changes must be made if the number of state fields available on the Mina blockchain changes
|
|
164
|
+
// This function should initialize ALL state fields due to the logic in the initialize() method
|
|
153
165
|
this.account.zkappUri.set(props.url);
|
|
154
166
|
this.account.tokenSymbol.set(props.symbol);
|
|
155
167
|
this.account.permissions.set({
|
|
@@ -159,8 +171,8 @@ function CollectionFactory(params: {
|
|
|
159
171
|
setPermissions: Permissions.impossible(),
|
|
160
172
|
access: Permissions.proof(),
|
|
161
173
|
send: Permissions.proof(),
|
|
162
|
-
setZkappUri: Permissions.
|
|
163
|
-
setTokenSymbol: Permissions.
|
|
174
|
+
setZkappUri: Permissions.proof(),
|
|
175
|
+
setTokenSymbol: Permissions.proof(),
|
|
164
176
|
});
|
|
165
177
|
}
|
|
166
178
|
|
|
@@ -172,6 +184,9 @@ function CollectionFactory(params: {
|
|
|
172
184
|
*/
|
|
173
185
|
@method
|
|
174
186
|
async initialize(masterNFT: MintParams, collectionData: CollectionData) {
|
|
187
|
+
// Changes must be made if the number of state fields available on the Mina blockchain changes
|
|
188
|
+
// as the next line relies on the fact that the state size is 8 Fields
|
|
189
|
+
// and all 8 Field are initialized in deploy()
|
|
175
190
|
this.account.provedState.requireEquals(Bool(false));
|
|
176
191
|
collectionData.royaltyFee.assertLessThanOrEqual(
|
|
177
192
|
UInt32.from(MAX_ROYALTY_FEE),
|
|
@@ -189,22 +204,23 @@ function CollectionFactory(params: {
|
|
|
189
204
|
*/
|
|
190
205
|
events = {
|
|
191
206
|
mint: MintEvent,
|
|
192
|
-
update:
|
|
207
|
+
update: NFTUpdateEvent,
|
|
193
208
|
transfer: TransferEvent,
|
|
194
209
|
approve: ApproveEvent,
|
|
195
210
|
upgradeNFTVerificationKey: UpgradeVerificationKeyEvent,
|
|
196
|
-
upgradeVerificationKey:
|
|
211
|
+
upgradeVerificationKey: UpgradeVerificationKeyEvent,
|
|
197
212
|
limitMinting: LimitMintingEvent,
|
|
198
213
|
pause: PauseEvent,
|
|
199
214
|
resume: PauseEvent,
|
|
200
215
|
pauseNFT: PauseNFTEvent,
|
|
201
216
|
resumeNFT: PauseNFTEvent,
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
217
|
+
ownershipTransfer: OwnershipChangeEvent,
|
|
218
|
+
ownershipAccepted: OwnershipChangeEvent,
|
|
219
|
+
setName: SetNameEvent,
|
|
220
|
+
setBaseURL: SetBaseURLEvent,
|
|
221
|
+
setRoyaltyFee: SetRoyaltyFeeEvent,
|
|
222
|
+
setTransferFee: SetTransferFeeEvent,
|
|
223
|
+
setAdmin: SetAdminEvent,
|
|
208
224
|
};
|
|
209
225
|
|
|
210
226
|
/**
|
|
@@ -285,10 +301,12 @@ function CollectionFactory(params: {
|
|
|
285
301
|
*
|
|
286
302
|
* @returns The packed data of the collection.
|
|
287
303
|
*/
|
|
288
|
-
async ensureNotPaused(): Promise<
|
|
289
|
-
CollectionData.
|
|
304
|
+
async ensureNotPaused(): Promise<CollectionData> {
|
|
305
|
+
const collectionData = CollectionData.unpack(
|
|
290
306
|
this.packedData.getAndRequireEquals()
|
|
291
|
-
)
|
|
307
|
+
);
|
|
308
|
+
collectionData.isPaused.assertFalse(CollectionErrors.collectionPaused);
|
|
309
|
+
return collectionData;
|
|
292
310
|
}
|
|
293
311
|
|
|
294
312
|
/**
|
|
@@ -307,9 +325,8 @@ function CollectionFactory(params: {
|
|
|
307
325
|
*/
|
|
308
326
|
|
|
309
327
|
@method async mintByCreator(params: MintParams): Promise<void> {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
).assertFalse(CollectionErrors.cannotMint);
|
|
328
|
+
const collectionData = await this.ensureNotPaused();
|
|
329
|
+
collectionData.mintingIsLimited.assertFalse(CollectionErrors.cannotMint);
|
|
313
330
|
const creatorUpdate = await this.ensureCreatorSignature();
|
|
314
331
|
// Pay 1 MINA fee for a new account
|
|
315
332
|
creatorUpdate.balance.subInPlace(1_000_000_000);
|
|
@@ -322,14 +339,15 @@ function CollectionFactory(params: {
|
|
|
322
339
|
* @param mintRequest - The minting request containing parameters and proofs.
|
|
323
340
|
*/
|
|
324
341
|
@method async mint(mintRequest: MintRequest): Promise<void> {
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
).assertFalse(CollectionErrors.cannotMint);
|
|
342
|
+
const collectionData = await this.ensureNotPaused();
|
|
343
|
+
collectionData.mintingIsLimited.assertFalse(CollectionErrors.cannotMint);
|
|
328
344
|
const adminContract = this.getAdminContract();
|
|
329
345
|
// The admin contract checks that the sender is allowed to mint
|
|
330
346
|
const mintParams = (await adminContract.canMint(mintRequest)).assertSome(
|
|
331
347
|
CollectionErrors.cannotMint
|
|
332
348
|
);
|
|
349
|
+
mintParams.address.assertEquals(mintRequest.address);
|
|
350
|
+
mintParams.data.owner.assertEquals(mintRequest.owner);
|
|
333
351
|
|
|
334
352
|
// Prevent minting the Master NFT using this method
|
|
335
353
|
mintParams.address
|
|
@@ -354,12 +372,19 @@ function CollectionFactory(params: {
|
|
|
354
372
|
storage,
|
|
355
373
|
metadataVerificationKeyHash,
|
|
356
374
|
expiry,
|
|
375
|
+
fee,
|
|
376
|
+
tokenId,
|
|
357
377
|
} = params;
|
|
358
378
|
|
|
359
379
|
this.network.globalSlotSinceGenesis.requireBetween(UInt32.zero, expiry);
|
|
360
|
-
data.version.assertEquals(
|
|
380
|
+
data.version.assertEquals(UInt64.zero);
|
|
381
|
+
data.isPaused
|
|
382
|
+
.equals(Bool(false))
|
|
383
|
+
.or(data.canPause.equals(Bool(true)))
|
|
384
|
+
.assertTrue(CollectionErrors.cannotMint);
|
|
361
385
|
const packedData = data.pack();
|
|
362
|
-
const
|
|
386
|
+
const collectionTokenId = this.deriveTokenId();
|
|
387
|
+
collectionTokenId.assertEquals(tokenId);
|
|
363
388
|
|
|
364
389
|
const update = AccountUpdate.createSigned(address, tokenId);
|
|
365
390
|
update.body.useFullCommitment = Bool(true); // Prevent memo and fee change
|
|
@@ -405,23 +430,13 @@ function CollectionFactory(params: {
|
|
|
405
430
|
const devnetVerificationKeyHash = Field(
|
|
406
431
|
nftVerificationKeys.devnet.vk.NFT.hash
|
|
407
432
|
);
|
|
408
|
-
const isMainnet = Provable.witness(Bool, () => {
|
|
409
|
-
// This check does NOT create a constraint on the verification key
|
|
410
|
-
// as this witness can be replaced during runtime
|
|
411
|
-
// and is useful only for making sure that the verification key
|
|
412
|
-
// of the NFT will match the compiled verification key of the NFT
|
|
413
|
-
// at the time of the deployment of the Collection Contract
|
|
414
|
-
return Bool(Mina.getNetworkId() === "mainnet");
|
|
415
|
-
});
|
|
416
433
|
// We check that the verification key hash is the same as the one
|
|
417
|
-
// that was compiled at the time of the deployment
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
)
|
|
424
|
-
);
|
|
434
|
+
// that was compiled at the time of the collection deployment
|
|
435
|
+
if (Mina.getNetworkId() === "mainnet") {
|
|
436
|
+
verificationKey.hash.assertEquals(mainnetVerificationKeyHash);
|
|
437
|
+
} else {
|
|
438
|
+
verificationKey.hash.assertEquals(devnetVerificationKeyHash);
|
|
439
|
+
}
|
|
425
440
|
update.body.update.verificationKey = {
|
|
426
441
|
isSome: Bool(true),
|
|
427
442
|
value: verificationKey,
|
|
@@ -459,6 +474,8 @@ function CollectionFactory(params: {
|
|
|
459
474
|
const event = new MintEvent({
|
|
460
475
|
initialState,
|
|
461
476
|
address,
|
|
477
|
+
tokenId,
|
|
478
|
+
fee,
|
|
462
479
|
});
|
|
463
480
|
this.emitEvent("mint", event);
|
|
464
481
|
return event;
|
|
@@ -474,6 +491,12 @@ function CollectionFactory(params: {
|
|
|
474
491
|
proof: NFTUpdateProof,
|
|
475
492
|
vk: VerificationKey
|
|
476
493
|
): Promise<void> {
|
|
494
|
+
// The oracle address is optional and can be empty, NFT ZkProgram can verify the address
|
|
495
|
+
// as it can be different for different NFTs. It should be empty for the update() call
|
|
496
|
+
const oracleAddress = proof.publicInput.oracleAddress;
|
|
497
|
+
oracleAddress
|
|
498
|
+
.equals(PublicKey.empty())
|
|
499
|
+
.assertTrue(CollectionErrors.invalidOracleAddress);
|
|
477
500
|
await this._update(proof, vk);
|
|
478
501
|
}
|
|
479
502
|
|
|
@@ -488,7 +511,7 @@ function CollectionFactory(params: {
|
|
|
488
511
|
vk: VerificationKey
|
|
489
512
|
): Promise<void> {
|
|
490
513
|
// The oracle address is optional and can be empty, NFT ZkProgram can verify the address
|
|
491
|
-
// as it can be different for different NFTs
|
|
514
|
+
// as it can be different for different NFTs. It should be non-empty for the updateWithOracle() call
|
|
492
515
|
const oracleAddress = proof.publicInput.oracleAddress;
|
|
493
516
|
oracleAddress
|
|
494
517
|
.equals(PublicKey.empty())
|
|
@@ -535,7 +558,12 @@ function CollectionFactory(params: {
|
|
|
535
558
|
// Verify the metadata update proof
|
|
536
559
|
metadataVerificationKeyHash.assertEquals(vk.hash);
|
|
537
560
|
proof.verify(vk);
|
|
538
|
-
this.emitEvent(
|
|
561
|
+
this.emitEvent(
|
|
562
|
+
"update",
|
|
563
|
+
new NFTUpdateEvent({
|
|
564
|
+
address: proof.publicInput.immutableState.address,
|
|
565
|
+
})
|
|
566
|
+
);
|
|
539
567
|
}
|
|
540
568
|
|
|
541
569
|
/**
|
|
@@ -559,13 +587,14 @@ function CollectionFactory(params: {
|
|
|
559
587
|
/**
|
|
560
588
|
* Transfers ownership of an NFT without admin approval.
|
|
561
589
|
*
|
|
562
|
-
* @param
|
|
563
|
-
* @param
|
|
590
|
+
* @param nftAddress - The address of the NFT.
|
|
591
|
+
* @param approved - The approved public key.
|
|
564
592
|
*/
|
|
565
593
|
@method async approveAddressByProof(
|
|
566
594
|
nftAddress: PublicKey,
|
|
567
595
|
approved: PublicKey
|
|
568
596
|
): Promise<void> {
|
|
597
|
+
await this.ensureNotPaused();
|
|
569
598
|
const tokenId = this.deriveTokenId();
|
|
570
599
|
const nft = new NFT(nftAddress, tokenId);
|
|
571
600
|
const owner = await nft.approveAddress(approved);
|
|
@@ -588,12 +617,11 @@ function CollectionFactory(params: {
|
|
|
588
617
|
* @param to - The recipient's public key.
|
|
589
618
|
* @param price - The price of the NFT (optional).
|
|
590
619
|
*/
|
|
591
|
-
@method async transferBySignature(
|
|
620
|
+
@method async transferBySignature(
|
|
621
|
+
params: TransferBySignatureParams
|
|
622
|
+
): Promise<void> {
|
|
592
623
|
const { address, to, price, context } = params;
|
|
593
|
-
const collectionData =
|
|
594
|
-
this.packedData.getAndRequireEquals()
|
|
595
|
-
);
|
|
596
|
-
collectionData.isPaused.assertFalse(CollectionErrors.collectionPaused);
|
|
624
|
+
const collectionData = await this.ensureNotPaused();
|
|
597
625
|
collectionData.requireTransferApproval.assertFalse(
|
|
598
626
|
CollectionErrors.transferApprovalRequired
|
|
599
627
|
);
|
|
@@ -623,12 +651,14 @@ function CollectionFactory(params: {
|
|
|
623
651
|
*
|
|
624
652
|
* @param params - The transfer parameters.
|
|
625
653
|
*/
|
|
626
|
-
@method async transferByProof(
|
|
654
|
+
@method async transferByProof(
|
|
655
|
+
params: TransferByProofParams
|
|
656
|
+
): Promise<void> {
|
|
627
657
|
const { address, from, to, price, context } = params;
|
|
628
|
-
const collectionData =
|
|
629
|
-
|
|
658
|
+
const collectionData = await this.ensureNotPaused();
|
|
659
|
+
collectionData.requireTransferApproval.assertFalse(
|
|
660
|
+
CollectionErrors.transferApprovalRequired
|
|
630
661
|
);
|
|
631
|
-
collectionData.isPaused.assertFalse(CollectionErrors.collectionPaused);
|
|
632
662
|
|
|
633
663
|
const transferEventDraft = new TransferExtendedParams({
|
|
634
664
|
from,
|
|
@@ -666,14 +696,11 @@ function CollectionFactory(params: {
|
|
|
666
696
|
*
|
|
667
697
|
* @param params - The transfer parameters.
|
|
668
698
|
*/
|
|
669
|
-
@method async
|
|
670
|
-
params:
|
|
699
|
+
@method async adminApprovedTransferByProof(
|
|
700
|
+
params: TransferByProofParams
|
|
671
701
|
): Promise<void> {
|
|
672
702
|
const { address, from, to, price, context } = params;
|
|
673
|
-
const collectionData =
|
|
674
|
-
this.packedData.getAndRequireEquals()
|
|
675
|
-
);
|
|
676
|
-
collectionData.isPaused.assertFalse(CollectionErrors.collectionPaused);
|
|
703
|
+
const collectionData = await this.ensureNotPaused();
|
|
677
704
|
|
|
678
705
|
const transferEventDraft = new TransferExtendedParams({
|
|
679
706
|
from,
|
|
@@ -718,14 +745,11 @@ function CollectionFactory(params: {
|
|
|
718
745
|
* @param to - The recipient's public key.
|
|
719
746
|
* @param price - The price of the NFT (optional).
|
|
720
747
|
*/
|
|
721
|
-
@method async
|
|
722
|
-
params:
|
|
748
|
+
@method async adminApprovedTransferBySignature(
|
|
749
|
+
params: TransferBySignatureParams
|
|
723
750
|
): Promise<void> {
|
|
724
751
|
const { address, to, price, context } = params;
|
|
725
|
-
const collectionData =
|
|
726
|
-
this.packedData.getAndRequireEquals()
|
|
727
|
-
);
|
|
728
|
-
collectionData.isPaused.assertFalse(CollectionErrors.collectionPaused);
|
|
752
|
+
const collectionData = await this.ensureNotPaused();
|
|
729
753
|
|
|
730
754
|
const transferEventDraft = new TransferExtendedParams({
|
|
731
755
|
from: PublicKey.empty(), // will be added later
|
|
@@ -746,15 +770,22 @@ function CollectionFactory(params: {
|
|
|
746
770
|
const adminContract = this.getAdminContract();
|
|
747
771
|
const canTransfer = await adminContract.canTransfer(transferEvent);
|
|
748
772
|
canTransfer.assertTrue();
|
|
749
|
-
this.emitEvent("transfer", transferEvent);
|
|
750
773
|
}
|
|
751
774
|
|
|
752
775
|
/**
|
|
753
776
|
* Internal method to transfer an NFT.
|
|
754
777
|
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
778
|
+
* This method handles the transfer logic and fee calculation. The fee is determined as follows:
|
|
779
|
+
* - If a price is provided, the fee is calculated as (price * royaltyFee / MAX_ROYALTY_FEE)
|
|
780
|
+
* - If no price is provided, the fixed transferFee is used to handle two cases:
|
|
781
|
+
* when NFT is being sold and the price is not provided to the contract
|
|
782
|
+
* when NFT is being transferred by the owner (without price)
|
|
783
|
+
* - If the sender is the creator, no fee is charged
|
|
784
|
+
* - The minimum fee is always the transferFee (unless sender is creator)
|
|
785
|
+
*
|
|
786
|
+
* @param transferEventDraft - The transfer event draft, containing the information about the transfer
|
|
757
787
|
* @param transferFee - The transfer fee amount.
|
|
788
|
+
* @param royaltyFee - The royalty fee amount.
|
|
758
789
|
* @returns The TransferEvent emitted.
|
|
759
790
|
*/
|
|
760
791
|
async _transfer(params: {
|
|
@@ -776,7 +807,6 @@ function CollectionFactory(params: {
|
|
|
776
807
|
const nft = new NFT(transferEventDraft.nft, tokenId);
|
|
777
808
|
const transferEvent = await nft.transfer(transferEventDraft);
|
|
778
809
|
const creator = this.creator.getAndRequireEquals();
|
|
779
|
-
// TODO: check is the owner is the creator
|
|
780
810
|
let fee = Provable.if(
|
|
781
811
|
transferEventDraft.price.isSome,
|
|
782
812
|
// We cannot check the price here, so we just rely on owner contract
|
|
@@ -834,6 +864,7 @@ function CollectionFactory(params: {
|
|
|
834
864
|
address: PublicKey,
|
|
835
865
|
vk: VerificationKey
|
|
836
866
|
): Promise<void> {
|
|
867
|
+
await this.ensureNotPaused();
|
|
837
868
|
const sender = this.sender.getAndRequireSignature();
|
|
838
869
|
const data = await this._upgrade(address, vk);
|
|
839
870
|
data.owner
|
|
@@ -853,6 +884,7 @@ function CollectionFactory(params: {
|
|
|
853
884
|
address: PublicKey,
|
|
854
885
|
vk: VerificationKey
|
|
855
886
|
): Promise<void> {
|
|
887
|
+
await this.ensureNotPaused();
|
|
856
888
|
const data = await this._upgrade(address, vk);
|
|
857
889
|
const ownerContract = this.getOwnerContract(data.owner);
|
|
858
890
|
const canUpgrade = await ownerContract.canChangeVerificationKey(
|
|
@@ -895,6 +927,7 @@ function CollectionFactory(params: {
|
|
|
895
927
|
*/
|
|
896
928
|
@method
|
|
897
929
|
async upgradeVerificationKey(vk: VerificationKey): Promise<void> {
|
|
930
|
+
await this.ensureNotPaused();
|
|
898
931
|
const adminContract = this.getAdminContract();
|
|
899
932
|
const canUpgrade = await adminContract.canChangeVerificationKey(
|
|
900
933
|
vk,
|
|
@@ -904,7 +937,14 @@ function CollectionFactory(params: {
|
|
|
904
937
|
canUpgrade.assertTrue(CollectionErrors.cannotUpgradeVerificationKey);
|
|
905
938
|
this.account.verificationKey.set(vk);
|
|
906
939
|
|
|
907
|
-
this.emitEvent(
|
|
940
|
+
this.emitEvent(
|
|
941
|
+
"upgradeVerificationKey",
|
|
942
|
+
new UpgradeVerificationKeyEvent({
|
|
943
|
+
address: this.address,
|
|
944
|
+
tokenId: this.tokenId,
|
|
945
|
+
verificationKeyHash: vk.hash,
|
|
946
|
+
})
|
|
947
|
+
);
|
|
908
948
|
}
|
|
909
949
|
|
|
910
950
|
/**
|
|
@@ -913,10 +953,7 @@ function CollectionFactory(params: {
|
|
|
913
953
|
@method
|
|
914
954
|
async limitMinting(): Promise<void> {
|
|
915
955
|
await this.ensureCreatorSignature();
|
|
916
|
-
const collectionData =
|
|
917
|
-
this.packedData.getAndRequireEquals()
|
|
918
|
-
);
|
|
919
|
-
collectionData.isPaused.assertFalse(CollectionErrors.collectionPaused);
|
|
956
|
+
const collectionData = await this.ensureNotPaused();
|
|
920
957
|
collectionData.mintingIsLimited = Bool(true);
|
|
921
958
|
this.packedData.set(collectionData.pack());
|
|
922
959
|
this.emitEvent(
|
|
@@ -930,10 +967,7 @@ function CollectionFactory(params: {
|
|
|
930
967
|
*/
|
|
931
968
|
@method
|
|
932
969
|
async pause(): Promise<void> {
|
|
933
|
-
const collectionData =
|
|
934
|
-
this.packedData.getAndRequireEquals()
|
|
935
|
-
);
|
|
936
|
-
collectionData.isPaused.assertFalse(CollectionErrors.collectionPaused);
|
|
970
|
+
const collectionData = await this.ensureNotPaused();
|
|
937
971
|
const adminContract = this.getAdminContract();
|
|
938
972
|
const canPause = await adminContract.canPause();
|
|
939
973
|
canPause.assertTrue(CollectionErrors.noPermissionToPause);
|
|
@@ -966,6 +1000,7 @@ function CollectionFactory(params: {
|
|
|
966
1000
|
*/
|
|
967
1001
|
@method
|
|
968
1002
|
async pauseNFTBySignature(address: PublicKey): Promise<void> {
|
|
1003
|
+
await this.ensureNotPaused();
|
|
969
1004
|
const tokenId = this.deriveTokenId();
|
|
970
1005
|
const nft = new NFT(address, tokenId);
|
|
971
1006
|
const owner = await nft.pause();
|
|
@@ -983,6 +1018,7 @@ function CollectionFactory(params: {
|
|
|
983
1018
|
*/
|
|
984
1019
|
@method
|
|
985
1020
|
async pauseNFTByProof(address: PublicKey): Promise<void> {
|
|
1021
|
+
await this.ensureNotPaused();
|
|
986
1022
|
const tokenId = this.deriveTokenId();
|
|
987
1023
|
const nft = new NFT(address, tokenId);
|
|
988
1024
|
const owner = await nft.pause();
|
|
@@ -1002,6 +1038,7 @@ function CollectionFactory(params: {
|
|
|
1002
1038
|
*/
|
|
1003
1039
|
@method
|
|
1004
1040
|
async resumeNFT(address: PublicKey): Promise<void> {
|
|
1041
|
+
await this.ensureNotPaused();
|
|
1005
1042
|
const tokenId = this.deriveTokenId();
|
|
1006
1043
|
const nft = new NFT(address, tokenId);
|
|
1007
1044
|
const owner = await nft.resume();
|
|
@@ -1019,6 +1056,7 @@ function CollectionFactory(params: {
|
|
|
1019
1056
|
*/
|
|
1020
1057
|
@method
|
|
1021
1058
|
async resumeNFTByProof(address: PublicKey): Promise<void> {
|
|
1059
|
+
await this.ensureNotPaused();
|
|
1022
1060
|
const tokenId = this.deriveTokenId();
|
|
1023
1061
|
const nft = new NFT(address, tokenId);
|
|
1024
1062
|
const owner = await nft.resume();
|
|
@@ -1046,7 +1084,7 @@ function CollectionFactory(params: {
|
|
|
1046
1084
|
const canChangeName = await adminContract.canChangeName(name);
|
|
1047
1085
|
canChangeName.assertTrue(CollectionErrors.noPermissionToChangeName);
|
|
1048
1086
|
this.collectionName.set(name);
|
|
1049
|
-
this.emitEvent("setName", name);
|
|
1087
|
+
this.emitEvent("setName", new SetNameEvent({ name }));
|
|
1050
1088
|
}
|
|
1051
1089
|
|
|
1052
1090
|
/**
|
|
@@ -1064,7 +1102,7 @@ function CollectionFactory(params: {
|
|
|
1064
1102
|
const canChangeBaseUri = await adminContract.canChangeBaseUri(baseURL);
|
|
1065
1103
|
canChangeBaseUri.assertTrue(CollectionErrors.noPermissionToChangeBaseUri);
|
|
1066
1104
|
this.baseURL.set(baseURL);
|
|
1067
|
-
this.emitEvent("setBaseURL", baseURL);
|
|
1105
|
+
this.emitEvent("setBaseURL", new SetBaseURLEvent({ baseURL }));
|
|
1068
1106
|
}
|
|
1069
1107
|
|
|
1070
1108
|
/**
|
|
@@ -1082,7 +1120,7 @@ function CollectionFactory(params: {
|
|
|
1082
1120
|
const canSetAdmin = await adminContract.canSetAdmin(admin);
|
|
1083
1121
|
canSetAdmin.assertTrue(CollectionErrors.noPermissionToSetAdmin);
|
|
1084
1122
|
this.admin.set(admin);
|
|
1085
|
-
this.emitEvent("setAdmin", admin);
|
|
1123
|
+
this.emitEvent("setAdmin", new SetAdminEvent({ admin }));
|
|
1086
1124
|
}
|
|
1087
1125
|
|
|
1088
1126
|
/**
|
|
@@ -1095,10 +1133,7 @@ function CollectionFactory(params: {
|
|
|
1095
1133
|
*/
|
|
1096
1134
|
@method
|
|
1097
1135
|
async setRoyaltyFee(royaltyFee: UInt32): Promise<void> {
|
|
1098
|
-
const collectionData =
|
|
1099
|
-
this.packedData.getAndRequireEquals()
|
|
1100
|
-
);
|
|
1101
|
-
collectionData.isPaused.assertFalse(CollectionErrors.collectionPaused);
|
|
1136
|
+
const collectionData = await this.ensureNotPaused();
|
|
1102
1137
|
royaltyFee.assertLessThanOrEqual(
|
|
1103
1138
|
UInt32.from(MAX_ROYALTY_FEE),
|
|
1104
1139
|
CollectionErrors.invalidRoyaltyFee
|
|
@@ -1108,7 +1143,7 @@ function CollectionFactory(params: {
|
|
|
1108
1143
|
canChangeRoyalty.assertTrue(CollectionErrors.noPermissionToChangeRoyalty);
|
|
1109
1144
|
collectionData.royaltyFee = royaltyFee;
|
|
1110
1145
|
this.packedData.set(collectionData.pack());
|
|
1111
|
-
this.emitEvent("setRoyaltyFee", royaltyFee);
|
|
1146
|
+
this.emitEvent("setRoyaltyFee", new SetRoyaltyFeeEvent({ royaltyFee }));
|
|
1112
1147
|
}
|
|
1113
1148
|
|
|
1114
1149
|
/**
|
|
@@ -1121,10 +1156,7 @@ function CollectionFactory(params: {
|
|
|
1121
1156
|
*/
|
|
1122
1157
|
@method
|
|
1123
1158
|
async setTransferFee(transferFee: UInt64): Promise<void> {
|
|
1124
|
-
const collectionData =
|
|
1125
|
-
this.packedData.getAndRequireEquals()
|
|
1126
|
-
);
|
|
1127
|
-
collectionData.isPaused.assertFalse(CollectionErrors.collectionPaused);
|
|
1159
|
+
const collectionData = await this.ensureNotPaused();
|
|
1128
1160
|
const adminContract = this.getAdminContract();
|
|
1129
1161
|
const canChangeTransferFee = await adminContract.canChangeTransferFee(
|
|
1130
1162
|
transferFee
|
|
@@ -1134,26 +1166,38 @@ function CollectionFactory(params: {
|
|
|
1134
1166
|
);
|
|
1135
1167
|
collectionData.transferFee = transferFee;
|
|
1136
1168
|
this.packedData.set(collectionData.pack());
|
|
1137
|
-
this.emitEvent(
|
|
1169
|
+
this.emitEvent(
|
|
1170
|
+
"setTransferFee",
|
|
1171
|
+
new SetTransferFeeEvent({ transferFee })
|
|
1172
|
+
);
|
|
1138
1173
|
}
|
|
1139
1174
|
|
|
1140
1175
|
/**
|
|
1141
|
-
* Transfers ownership of the collection to a new
|
|
1176
|
+
* Transfers ownership of the collection to a new creator.
|
|
1177
|
+
* This method is called transferOwnership as the Collection is implementing OwnableContract interface
|
|
1178
|
+
* For the Collection, the creator is the owner of the collection
|
|
1142
1179
|
*
|
|
1143
|
-
* @param to - The public key of the new
|
|
1144
|
-
* @returns The public key of the old
|
|
1180
|
+
* @param to - The public key of the new creator.
|
|
1181
|
+
* @returns The public key of the old creator.
|
|
1145
1182
|
*/
|
|
1146
1183
|
@method.returns(PublicKey)
|
|
1147
1184
|
async transferOwnership(to: PublicKey): Promise<PublicKey> {
|
|
1148
1185
|
await this.ensureCreatorSignature();
|
|
1149
|
-
|
|
1186
|
+
const collectionData = CollectionData.unpack(
|
|
1187
|
+
this.packedData.getAndRequireEquals()
|
|
1188
|
+
);
|
|
1189
|
+
collectionData.isPaused.assertFalse(CollectionErrors.collectionNotPaused);
|
|
1190
|
+
|
|
1150
1191
|
const adminContract = this.getAdminContract();
|
|
1151
1192
|
const canChangeCreator = await adminContract.canChangeCreator(to);
|
|
1152
1193
|
canChangeCreator.assertTrue(CollectionErrors.noPermissionToChangeCreator);
|
|
1153
1194
|
const from = this.creator.getAndRequireEquals();
|
|
1154
|
-
|
|
1195
|
+
// Pending creator public key can be empty, it cancels the transfer
|
|
1196
|
+
this.pendingCreatorX.set(to.x);
|
|
1197
|
+
collectionData.pendingCreatorIsOdd = to.isOdd;
|
|
1198
|
+
this.packedData.set(collectionData.pack());
|
|
1155
1199
|
this.emitEvent(
|
|
1156
|
-
"
|
|
1200
|
+
"ownershipTransfer",
|
|
1157
1201
|
new OwnershipChangeEvent({
|
|
1158
1202
|
from,
|
|
1159
1203
|
to,
|
|
@@ -1162,6 +1206,54 @@ function CollectionFactory(params: {
|
|
|
1162
1206
|
return from;
|
|
1163
1207
|
}
|
|
1164
1208
|
|
|
1209
|
+
/**
|
|
1210
|
+
* Transfers ownership of the collection to a new owner.
|
|
1211
|
+
*
|
|
1212
|
+
* @param to - The public key of the new owner.
|
|
1213
|
+
* @returns The public key of the old owner.
|
|
1214
|
+
*/
|
|
1215
|
+
@method.returns(PublicKey)
|
|
1216
|
+
async acceptOwnership(): Promise<PublicKey> {
|
|
1217
|
+
const collectionData = CollectionData.unpack(
|
|
1218
|
+
this.packedData.getAndRequireEquals()
|
|
1219
|
+
);
|
|
1220
|
+
collectionData.isPaused.assertFalse(CollectionErrors.collectionNotPaused);
|
|
1221
|
+
|
|
1222
|
+
const pendingCreatorX = this.pendingCreatorX.getAndRequireEquals();
|
|
1223
|
+
const pendingCreator = PublicKey.from({
|
|
1224
|
+
x: pendingCreatorX,
|
|
1225
|
+
isOdd: collectionData.pendingCreatorIsOdd,
|
|
1226
|
+
});
|
|
1227
|
+
const emptyPublicKey = PublicKey.empty();
|
|
1228
|
+
pendingCreator
|
|
1229
|
+
.equals(emptyPublicKey)
|
|
1230
|
+
.assertFalse(CollectionErrors.pendingCreatorIsEmpty);
|
|
1231
|
+
// pendingCreator can be different from the sender, but it should sign the tx
|
|
1232
|
+
const pendingCreatorUpdate = AccountUpdate.createSigned(pendingCreator);
|
|
1233
|
+
pendingCreatorUpdate.body.useFullCommitment = Bool(true); // Prevent memo and fee change
|
|
1234
|
+
|
|
1235
|
+
// Check second time that the transfer is allowed
|
|
1236
|
+
const adminContract = this.getAdminContract();
|
|
1237
|
+
const canChangeCreator = await adminContract.canChangeCreator(
|
|
1238
|
+
pendingCreator
|
|
1239
|
+
);
|
|
1240
|
+
canChangeCreator.assertTrue(CollectionErrors.noPermissionToChangeCreator);
|
|
1241
|
+
const from = this.creator.getAndRequireEquals();
|
|
1242
|
+
|
|
1243
|
+
this.pendingCreatorX.set(emptyPublicKey.x);
|
|
1244
|
+
collectionData.pendingCreatorIsOdd = Bool(emptyPublicKey.isOdd);
|
|
1245
|
+
this.creator.set(pendingCreator);
|
|
1246
|
+
this.packedData.set(collectionData.pack());
|
|
1247
|
+
this.emitEvent(
|
|
1248
|
+
"ownershipAccepted",
|
|
1249
|
+
new OwnershipChangeEvent({
|
|
1250
|
+
from,
|
|
1251
|
+
to: pendingCreator,
|
|
1252
|
+
})
|
|
1253
|
+
);
|
|
1254
|
+
return from;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1165
1257
|
@method.returns(NFTStateStruct)
|
|
1166
1258
|
async getNFTState(address: PublicKey): Promise<NFTStateStruct> {
|
|
1167
1259
|
const tokenId = this.deriveTokenId();
|
package/src/contracts/nft.ts
CHANGED
|
@@ -30,6 +30,7 @@ const NftErrors = {
|
|
|
30
30
|
cannotChangeMetadataVerificationKeyHash:
|
|
31
31
|
"Cannot change metadata verification key hash",
|
|
32
32
|
cannotChangeOwner: "Cannot change owner",
|
|
33
|
+
cannotChangeApproval: "Cannot change approval",
|
|
33
34
|
cannotChangeStorage: "Cannot change storage",
|
|
34
35
|
cannotChangePauseState: "Cannot change pause state",
|
|
35
36
|
noPermissionToPause: "No permission to pause",
|
|
@@ -212,6 +213,7 @@ class NFT extends SmartContract {
|
|
|
212
213
|
data.owner = output.owner;
|
|
213
214
|
data.approved = output.approved;
|
|
214
215
|
data.version = output.version;
|
|
216
|
+
data.isPaused = output.isPaused;
|
|
215
217
|
|
|
216
218
|
this.packedData.set(data.pack());
|
|
217
219
|
|
|
@@ -282,6 +284,7 @@ class NFT extends SmartContract {
|
|
|
282
284
|
async approveAddress(approved: PublicKey): Promise<PublicKey> {
|
|
283
285
|
const data = NFTData.unpack(this.packedData.getAndRequireEquals());
|
|
284
286
|
data.isPaused.assertFalse(NftErrors.nftIsPaused);
|
|
287
|
+
data.canApprove.assertTrue(NftErrors.cannotChangeApproval);
|
|
285
288
|
data.approved = approved;
|
|
286
289
|
this.packedData.set(data.pack());
|
|
287
290
|
this.emitEvent("approve", approved);
|