1#![recursion_limit = "256"]
28#![cfg_attr(not(feature = "std"), no_std)]
30
31#[cfg(feature = "runtime-benchmarks")]
32mod benchmarking;
33pub mod migration;
34#[cfg(test)]
35pub mod mock;
36#[cfg(test)]
37mod tests;
38
39mod common_functions;
40mod features;
45mod impl_nonfungibles;
46mod types;
47
48pub mod macros;
49pub mod weights;
50
51extern crate alloc;
52
53use alloc::{boxed::Box, vec, vec::Vec};
54use codec::{Decode, Encode};
55use frame_support::traits::{
56 tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, Incrementable,
57 ReservableCurrency,
58};
59use frame_system::Config as SystemConfig;
60use sp_runtime::{
61 traits::{BlockNumberProvider, IdentifyAccount, Saturating, StaticLookup, Verify, Zero},
62 RuntimeDebug,
63};
64
65pub use pallet::*;
66pub use types::*;
67pub use weights::WeightInfo;
68
69pub const LOG_TARGET: &'static str = "runtime::nfts";
71
72type AccountIdLookupOf<T> = <<T as SystemConfig>::Lookup as StaticLookup>::Source;
74
75#[frame_support::pallet]
76pub mod pallet {
77 use super::*;
78 use frame_support::{pallet_prelude::*, traits::ExistenceRequirement};
79 use frame_system::{ensure_signed, pallet_prelude::OriginFor};
80
81 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
83
84 #[pallet::pallet]
85 #[pallet::storage_version(STORAGE_VERSION)]
86 pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
87
88 #[cfg(feature = "runtime-benchmarks")]
89 pub trait BenchmarkHelper<CollectionId, ItemId, Public, AccountId, Signature> {
90 fn collection(i: u16) -> CollectionId;
91 fn item(i: u16) -> ItemId;
92 fn signer() -> (Public, AccountId);
93 fn sign(signer: &Public, message: &[u8]) -> Signature;
94 }
95 #[cfg(feature = "runtime-benchmarks")]
96 impl<CollectionId, ItemId>
97 BenchmarkHelper<
98 CollectionId,
99 ItemId,
100 sp_runtime::MultiSigner,
101 sp_runtime::AccountId32,
102 sp_runtime::MultiSignature,
103 > for ()
104 where
105 CollectionId: From<u16>,
106 ItemId: From<u16>,
107 {
108 fn collection(i: u16) -> CollectionId {
109 i.into()
110 }
111 fn item(i: u16) -> ItemId {
112 i.into()
113 }
114 fn signer() -> (sp_runtime::MultiSigner, sp_runtime::AccountId32) {
115 let public = sp_io::crypto::sr25519_generate(0.into(), None);
116 let account = sp_runtime::MultiSigner::Sr25519(public).into_account();
117 (public.into(), account)
118 }
119 fn sign(signer: &sp_runtime::MultiSigner, message: &[u8]) -> sp_runtime::MultiSignature {
120 sp_runtime::MultiSignature::Sr25519(
121 sp_io::crypto::sr25519_sign(0.into(), &signer.clone().try_into().unwrap(), message)
122 .unwrap(),
123 )
124 }
125 }
126
127 #[pallet::config]
128 pub trait Config<I: 'static = ()>: frame_system::Config {
130 #[allow(deprecated)]
132 type RuntimeEvent: From<Event<Self, I>>
133 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
134
135 type CollectionId: Member + Parameter + MaxEncodedLen + Copy + Incrementable;
145
146 type ItemId: Member + Parameter + MaxEncodedLen + Copy;
148
149 type Currency: ReservableCurrency<Self::AccountId>;
151
152 type ForceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
155
156 type CreateOrigin: EnsureOriginWithArg<
159 Self::RuntimeOrigin,
160 Self::CollectionId,
161 Success = Self::AccountId,
162 >;
163
164 type Locker: Locker<Self::CollectionId, Self::ItemId>;
166
167 #[pallet::constant]
169 type CollectionDeposit: Get<DepositBalanceOf<Self, I>>;
170
171 #[pallet::constant]
173 type ItemDeposit: Get<DepositBalanceOf<Self, I>>;
174
175 #[pallet::constant]
177 type MetadataDepositBase: Get<DepositBalanceOf<Self, I>>;
178
179 #[pallet::constant]
181 type AttributeDepositBase: Get<DepositBalanceOf<Self, I>>;
182
183 #[pallet::constant]
186 type DepositPerByte: Get<DepositBalanceOf<Self, I>>;
187
188 #[pallet::constant]
190 type StringLimit: Get<u32>;
191
192 #[pallet::constant]
194 type KeyLimit: Get<u32>;
195
196 #[pallet::constant]
198 type ValueLimit: Get<u32>;
199
200 #[pallet::constant]
202 type ApprovalsLimit: Get<u32>;
203
204 #[pallet::constant]
206 type ItemAttributesApprovalsLimit: Get<u32>;
207
208 #[pallet::constant]
210 type MaxTips: Get<u32>;
211
212 #[pallet::constant]
214 type MaxDeadlineDuration: Get<BlockNumberFor<Self, I>>;
215
216 #[pallet::constant]
218 type MaxAttributesPerCall: Get<u32>;
219
220 #[pallet::constant]
222 type Features: Get<PalletFeatures>;
223
224 type OffchainSignature: Verify<Signer = Self::OffchainPublic> + Parameter;
228
229 type OffchainPublic: IdentifyAccount<AccountId = Self::AccountId>;
233
234 #[cfg(feature = "runtime-benchmarks")]
235 type Helper: BenchmarkHelper<
237 Self::CollectionId,
238 Self::ItemId,
239 Self::OffchainPublic,
240 Self::AccountId,
241 Self::OffchainSignature,
242 >;
243
244 type WeightInfo: WeightInfo;
246
247 type BlockNumberProvider: BlockNumberProvider;
249 }
250
251 #[pallet::storage]
253 pub type Collection<T: Config<I>, I: 'static = ()> = StorageMap<
254 _,
255 Blake2_128Concat,
256 T::CollectionId,
257 CollectionDetails<T::AccountId, DepositBalanceOf<T, I>>,
258 >;
259
260 #[pallet::storage]
262 pub type OwnershipAcceptance<T: Config<I>, I: 'static = ()> =
263 StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>;
264
265 #[pallet::storage]
268 pub type Account<T: Config<I>, I: 'static = ()> = StorageNMap<
269 _,
270 (
271 NMapKey<Blake2_128Concat, T::AccountId>, NMapKey<Blake2_128Concat, T::CollectionId>,
273 NMapKey<Blake2_128Concat, T::ItemId>,
274 ),
275 (),
276 OptionQuery,
277 >;
278
279 #[pallet::storage]
282 pub type CollectionAccount<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
283 _,
284 Blake2_128Concat,
285 T::AccountId,
286 Blake2_128Concat,
287 T::CollectionId,
288 (),
289 OptionQuery,
290 >;
291
292 #[pallet::storage]
294 pub type CollectionRoleOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
296 _,
297 Blake2_128Concat,
298 T::CollectionId,
299 Blake2_128Concat,
300 T::AccountId,
301 CollectionRoles,
302 OptionQuery,
303 >;
304
305 #[pallet::storage]
307 pub type Item<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
308 _,
309 Blake2_128Concat,
310 T::CollectionId,
311 Blake2_128Concat,
312 T::ItemId,
313 ItemDetails<T::AccountId, ItemDepositOf<T, I>, ApprovalsOf<T, I>>,
314 OptionQuery,
315 >;
316
317 #[pallet::storage]
319 pub type CollectionMetadataOf<T: Config<I>, I: 'static = ()> = StorageMap<
320 _,
321 Blake2_128Concat,
322 T::CollectionId,
323 CollectionMetadata<DepositBalanceOf<T, I>, T::StringLimit>,
324 OptionQuery,
325 >;
326
327 #[pallet::storage]
329 pub type ItemMetadataOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
330 _,
331 Blake2_128Concat,
332 T::CollectionId,
333 Blake2_128Concat,
334 T::ItemId,
335 ItemMetadata<ItemMetadataDepositOf<T, I>, T::StringLimit>,
336 OptionQuery,
337 >;
338
339 #[pallet::storage]
341 pub type Attribute<T: Config<I>, I: 'static = ()> = StorageNMap<
342 _,
343 (
344 NMapKey<Blake2_128Concat, T::CollectionId>,
345 NMapKey<Blake2_128Concat, Option<T::ItemId>>,
346 NMapKey<Blake2_128Concat, AttributeNamespace<T::AccountId>>,
347 NMapKey<Blake2_128Concat, BoundedVec<u8, T::KeyLimit>>,
348 ),
349 (BoundedVec<u8, T::ValueLimit>, AttributeDepositOf<T, I>),
350 OptionQuery,
351 >;
352
353 #[pallet::storage]
355 pub type ItemPriceOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
356 _,
357 Blake2_128Concat,
358 T::CollectionId,
359 Blake2_128Concat,
360 T::ItemId,
361 (ItemPrice<T, I>, Option<T::AccountId>),
362 OptionQuery,
363 >;
364
365 #[pallet::storage]
367 pub type ItemAttributesApprovalsOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
368 _,
369 Blake2_128Concat,
370 T::CollectionId,
371 Blake2_128Concat,
372 T::ItemId,
373 ItemAttributesApprovals<T, I>,
374 ValueQuery,
375 >;
376
377 #[pallet::storage]
380 pub type NextCollectionId<T: Config<I>, I: 'static = ()> =
381 StorageValue<_, T::CollectionId, OptionQuery>;
382
383 #[pallet::storage]
385 pub type PendingSwapOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
386 _,
387 Blake2_128Concat,
388 T::CollectionId,
389 Blake2_128Concat,
390 T::ItemId,
391 PendingSwap<
392 T::CollectionId,
393 T::ItemId,
394 PriceWithDirection<ItemPrice<T, I>>,
395 BlockNumberFor<T, I>,
396 >,
397 OptionQuery,
398 >;
399
400 #[pallet::storage]
402 pub type CollectionConfigOf<T: Config<I>, I: 'static = ()> =
403 StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfigFor<T, I>, OptionQuery>;
404
405 #[pallet::storage]
407 pub type ItemConfigOf<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
408 _,
409 Blake2_128Concat,
410 T::CollectionId,
411 Blake2_128Concat,
412 T::ItemId,
413 ItemConfig,
414 OptionQuery,
415 >;
416
417 #[pallet::event]
418 #[pallet::generate_deposit(pub(super) fn deposit_event)]
419 pub enum Event<T: Config<I>, I: 'static = ()> {
420 Created { collection: T::CollectionId, creator: T::AccountId, owner: T::AccountId },
422 ForceCreated { collection: T::CollectionId, owner: T::AccountId },
424 Destroyed { collection: T::CollectionId },
426 Issued { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId },
428 Transferred {
430 collection: T::CollectionId,
431 item: T::ItemId,
432 from: T::AccountId,
433 to: T::AccountId,
434 },
435 Burned { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId },
437 ItemTransferLocked { collection: T::CollectionId, item: T::ItemId },
439 ItemTransferUnlocked { collection: T::CollectionId, item: T::ItemId },
441 ItemPropertiesLocked {
443 collection: T::CollectionId,
444 item: T::ItemId,
445 lock_metadata: bool,
446 lock_attributes: bool,
447 },
448 CollectionLocked { collection: T::CollectionId },
450 OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId },
452 TeamChanged {
454 collection: T::CollectionId,
455 issuer: Option<T::AccountId>,
456 admin: Option<T::AccountId>,
457 freezer: Option<T::AccountId>,
458 },
459 TransferApproved {
462 collection: T::CollectionId,
463 item: T::ItemId,
464 owner: T::AccountId,
465 delegate: T::AccountId,
466 deadline: Option<BlockNumberFor<T, I>>,
467 },
468 ApprovalCancelled {
471 collection: T::CollectionId,
472 item: T::ItemId,
473 owner: T::AccountId,
474 delegate: T::AccountId,
475 },
476 AllApprovalsCancelled { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId },
478 CollectionConfigChanged { collection: T::CollectionId },
480 CollectionMetadataSet { collection: T::CollectionId, data: BoundedVec<u8, T::StringLimit> },
482 CollectionMetadataCleared { collection: T::CollectionId },
484 ItemMetadataSet {
486 collection: T::CollectionId,
487 item: T::ItemId,
488 data: BoundedVec<u8, T::StringLimit>,
489 },
490 ItemMetadataCleared { collection: T::CollectionId, item: T::ItemId },
492 Redeposited { collection: T::CollectionId, successful_items: Vec<T::ItemId> },
494 AttributeSet {
496 collection: T::CollectionId,
497 maybe_item: Option<T::ItemId>,
498 key: BoundedVec<u8, T::KeyLimit>,
499 value: BoundedVec<u8, T::ValueLimit>,
500 namespace: AttributeNamespace<T::AccountId>,
501 },
502 AttributeCleared {
504 collection: T::CollectionId,
505 maybe_item: Option<T::ItemId>,
506 key: BoundedVec<u8, T::KeyLimit>,
507 namespace: AttributeNamespace<T::AccountId>,
508 },
509 ItemAttributesApprovalAdded {
511 collection: T::CollectionId,
512 item: T::ItemId,
513 delegate: T::AccountId,
514 },
515 ItemAttributesApprovalRemoved {
517 collection: T::CollectionId,
518 item: T::ItemId,
519 delegate: T::AccountId,
520 },
521 OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option<T::CollectionId> },
523 CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 },
525 CollectionMintSettingsUpdated { collection: T::CollectionId },
527 NextCollectionIdIncremented { next_id: Option<T::CollectionId> },
529 ItemPriceSet {
531 collection: T::CollectionId,
532 item: T::ItemId,
533 price: ItemPrice<T, I>,
534 whitelisted_buyer: Option<T::AccountId>,
535 },
536 ItemPriceRemoved { collection: T::CollectionId, item: T::ItemId },
538 ItemBought {
540 collection: T::CollectionId,
541 item: T::ItemId,
542 price: ItemPrice<T, I>,
543 seller: T::AccountId,
544 buyer: T::AccountId,
545 },
546 TipSent {
548 collection: T::CollectionId,
549 item: T::ItemId,
550 sender: T::AccountId,
551 receiver: T::AccountId,
552 amount: DepositBalanceOf<T, I>,
553 },
554 SwapCreated {
556 offered_collection: T::CollectionId,
557 offered_item: T::ItemId,
558 desired_collection: T::CollectionId,
559 desired_item: Option<T::ItemId>,
560 price: Option<PriceWithDirection<ItemPrice<T, I>>>,
561 deadline: BlockNumberFor<T, I>,
562 },
563 SwapCancelled {
565 offered_collection: T::CollectionId,
566 offered_item: T::ItemId,
567 desired_collection: T::CollectionId,
568 desired_item: Option<T::ItemId>,
569 price: Option<PriceWithDirection<ItemPrice<T, I>>>,
570 deadline: BlockNumberFor<T, I>,
571 },
572 SwapClaimed {
574 sent_collection: T::CollectionId,
575 sent_item: T::ItemId,
576 sent_item_owner: T::AccountId,
577 received_collection: T::CollectionId,
578 received_item: T::ItemId,
579 received_item_owner: T::AccountId,
580 price: Option<PriceWithDirection<ItemPrice<T, I>>>,
581 deadline: BlockNumberFor<T, I>,
582 },
583 PreSignedAttributesSet {
585 collection: T::CollectionId,
586 item: T::ItemId,
587 namespace: AttributeNamespace<T::AccountId>,
588 },
589 PalletAttributeSet {
592 collection: T::CollectionId,
593 item: Option<T::ItemId>,
594 attribute: PalletAttributes<T::CollectionId>,
595 value: BoundedVec<u8, T::ValueLimit>,
596 },
597 }
598
599 #[pallet::error]
600 pub enum Error<T, I = ()> {
601 NoPermission,
603 UnknownCollection,
605 AlreadyExists,
607 ApprovalExpired,
609 WrongOwner,
611 BadWitness,
613 CollectionIdInUse,
615 ItemsNonTransferable,
617 NotDelegate,
619 WrongDelegate,
621 Unapproved,
623 Unaccepted,
625 ItemLocked,
627 LockedItemAttributes,
629 LockedCollectionAttributes,
631 LockedItemMetadata,
633 LockedCollectionMetadata,
635 MaxSupplyReached,
637 MaxSupplyLocked,
639 MaxSupplyTooSmall,
641 UnknownItem,
643 UnknownSwap,
645 MetadataNotFound,
647 AttributeNotFound,
649 NotForSale,
651 BidTooLow,
653 ReachedApprovalLimit,
655 DeadlineExpired,
657 WrongDuration,
659 MethodDisabled,
661 WrongSetting,
663 InconsistentItemConfig,
665 NoConfig,
667 RolesNotCleared,
669 MintNotStarted,
671 MintEnded,
673 AlreadyClaimed,
675 IncorrectData,
677 WrongOrigin,
679 WrongSignature,
681 IncorrectMetadata,
683 MaxAttributesLimitReached,
685 WrongNamespace,
687 CollectionNotEmpty,
689 WitnessRequired,
691 }
692
693 #[pallet::call]
694 impl<T: Config<I>, I: 'static> Pallet<T, I> {
695 #[pallet::call_index(0)]
711 #[pallet::weight(T::WeightInfo::create())]
712 pub fn create(
713 origin: OriginFor<T>,
714 admin: AccountIdLookupOf<T>,
715 config: CollectionConfigFor<T, I>,
716 ) -> DispatchResult {
717 let collection = NextCollectionId::<T, I>::get()
718 .or(T::CollectionId::initial_value())
719 .ok_or(Error::<T, I>::UnknownCollection)?;
720
721 let owner = T::CreateOrigin::ensure_origin(origin, &collection)?;
722 let admin = T::Lookup::lookup(admin)?;
723
724 ensure!(
726 !config.has_disabled_setting(CollectionSetting::DepositRequired),
727 Error::<T, I>::WrongSetting
728 );
729
730 Self::do_create_collection(
731 collection,
732 owner.clone(),
733 admin.clone(),
734 config,
735 T::CollectionDeposit::get(),
736 Event::Created { collection, creator: owner, owner: admin },
737 )?;
738
739 Self::set_next_collection_id(collection);
740 Ok(())
741 }
742
743 #[pallet::call_index(1)]
759 #[pallet::weight(T::WeightInfo::force_create())]
760 pub fn force_create(
761 origin: OriginFor<T>,
762 owner: AccountIdLookupOf<T>,
763 config: CollectionConfigFor<T, I>,
764 ) -> DispatchResult {
765 T::ForceOrigin::ensure_origin(origin)?;
766 let owner = T::Lookup::lookup(owner)?;
767
768 let collection = NextCollectionId::<T, I>::get()
769 .or(T::CollectionId::initial_value())
770 .ok_or(Error::<T, I>::UnknownCollection)?;
771
772 Self::do_create_collection(
773 collection,
774 owner.clone(),
775 owner.clone(),
776 config,
777 Zero::zero(),
778 Event::ForceCreated { collection, owner },
779 )?;
780
781 Self::set_next_collection_id(collection);
782 Ok(())
783 }
784
785 #[pallet::call_index(2)]
803 #[pallet::weight(T::WeightInfo::destroy(
804 witness.item_metadatas,
805 witness.item_configs,
806 witness.attributes,
807 ))]
808 pub fn destroy(
809 origin: OriginFor<T>,
810 collection: T::CollectionId,
811 witness: DestroyWitness,
812 ) -> DispatchResultWithPostInfo {
813 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
814 .map(|_| None)
815 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
816 let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?;
817
818 Ok(Some(T::WeightInfo::destroy(
819 details.item_metadatas,
820 details.item_configs,
821 details.attributes,
822 ))
823 .into())
824 }
825
826 #[pallet::call_index(3)]
843 #[pallet::weight(T::WeightInfo::mint())]
844 pub fn mint(
845 origin: OriginFor<T>,
846 collection: T::CollectionId,
847 item: T::ItemId,
848 mint_to: AccountIdLookupOf<T>,
849 witness_data: Option<MintWitness<T::ItemId, DepositBalanceOf<T, I>>>,
850 ) -> DispatchResult {
851 let caller = ensure_signed(origin)?;
852 let mint_to = T::Lookup::lookup(mint_to)?;
853 let item_config =
854 ItemConfig { settings: Self::get_default_item_settings(&collection)? };
855
856 Self::do_mint(
857 collection,
858 item,
859 Some(caller.clone()),
860 mint_to.clone(),
861 item_config,
862 |collection_details, collection_config| {
863 let mint_settings = collection_config.mint_settings;
864 let now = T::BlockNumberProvider::current_block_number();
865
866 if let Some(start_block) = mint_settings.start_block {
867 ensure!(start_block <= now, Error::<T, I>::MintNotStarted);
868 }
869 if let Some(end_block) = mint_settings.end_block {
870 ensure!(end_block >= now, Error::<T, I>::MintEnded);
871 }
872
873 match mint_settings.mint_type {
874 MintType::Issuer => {
875 ensure!(
876 Self::has_role(&collection, &caller, CollectionRole::Issuer),
877 Error::<T, I>::NoPermission
878 );
879 },
880 MintType::HolderOf(collection_id) => {
881 let MintWitness { owned_item, .. } =
882 witness_data.clone().ok_or(Error::<T, I>::WitnessRequired)?;
883 let owned_item = owned_item.ok_or(Error::<T, I>::BadWitness)?;
884
885 let owns_item = Account::<T, I>::contains_key((
886 &caller,
887 &collection_id,
888 &owned_item,
889 ));
890 ensure!(owns_item, Error::<T, I>::BadWitness);
891
892 let pallet_attribute =
893 PalletAttributes::<T::CollectionId>::UsedToClaim(collection);
894
895 let key = (
896 &collection_id,
897 Some(owned_item),
898 AttributeNamespace::Pallet,
899 &Self::construct_attribute_key(pallet_attribute.encode())?,
900 );
901 let already_claimed = Attribute::<T, I>::contains_key(key.clone());
902 ensure!(!already_claimed, Error::<T, I>::AlreadyClaimed);
903
904 let attribute_value = Self::construct_attribute_value(vec![])?;
905 Attribute::<T, I>::insert(
906 key,
907 (
908 attribute_value.clone(),
909 AttributeDeposit { account: None, amount: Zero::zero() },
910 ),
911 );
912 Self::deposit_event(Event::PalletAttributeSet {
913 collection: collection_id,
914 item: Some(owned_item),
915 attribute: pallet_attribute,
916 value: attribute_value,
917 });
918 },
919 _ => {},
920 }
921
922 if let Some(price) = mint_settings.price {
923 let MintWitness { mint_price, .. } =
924 witness_data.clone().ok_or(Error::<T, I>::WitnessRequired)?;
925 let mint_price = mint_price.ok_or(Error::<T, I>::BadWitness)?;
926 ensure!(mint_price >= price, Error::<T, I>::BadWitness);
927 T::Currency::transfer(
928 &caller,
929 &collection_details.owner,
930 price,
931 ExistenceRequirement::KeepAlive,
932 )?;
933 }
934
935 Ok(())
936 },
937 )
938 }
939
940 #[pallet::call_index(4)]
954 #[pallet::weight(T::WeightInfo::force_mint())]
955 pub fn force_mint(
956 origin: OriginFor<T>,
957 collection: T::CollectionId,
958 item: T::ItemId,
959 mint_to: AccountIdLookupOf<T>,
960 item_config: ItemConfig,
961 ) -> DispatchResult {
962 let maybe_check_origin = T::ForceOrigin::try_origin(origin)
963 .map(|_| None)
964 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
965 let mint_to = T::Lookup::lookup(mint_to)?;
966
967 if let Some(check_origin) = maybe_check_origin {
968 ensure!(
969 Self::has_role(&collection, &check_origin, CollectionRole::Issuer),
970 Error::<T, I>::NoPermission
971 );
972 }
973 Self::do_mint(collection, item, None, mint_to, item_config, |_, _| Ok(()))
974 }
975
976 #[pallet::call_index(5)]
988 #[pallet::weight(T::WeightInfo::burn())]
989 pub fn burn(
990 origin: OriginFor<T>,
991 collection: T::CollectionId,
992 item: T::ItemId,
993 ) -> DispatchResult {
994 let maybe_check_origin = T::ForceOrigin::try_origin(origin)
995 .map(|_| None)
996 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
997
998 Self::do_burn(collection, item, |details| {
999 if let Some(check_origin) = maybe_check_origin {
1000 ensure!(details.owner == check_origin, Error::<T, I>::NoPermission);
1001 }
1002 Ok(())
1003 })
1004 }
1005
1006 #[pallet::call_index(6)]
1021 #[pallet::weight(T::WeightInfo::transfer())]
1022 pub fn transfer(
1023 origin: OriginFor<T>,
1024 collection: T::CollectionId,
1025 item: T::ItemId,
1026 dest: AccountIdLookupOf<T>,
1027 ) -> DispatchResult {
1028 let origin = ensure_signed(origin)?;
1029 let dest = T::Lookup::lookup(dest)?;
1030
1031 Self::do_transfer(collection, item, dest, |_, details| {
1032 if details.owner != origin {
1033 let deadline =
1034 details.approvals.get(&origin).ok_or(Error::<T, I>::NoPermission)?;
1035 if let Some(d) = deadline {
1036 let block_number = T::BlockNumberProvider::current_block_number();
1037 ensure!(block_number <= *d, Error::<T, I>::ApprovalExpired);
1038 }
1039 }
1040 Ok(())
1041 })
1042 }
1043
1044 #[pallet::call_index(7)]
1062 #[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))]
1063 pub fn redeposit(
1064 origin: OriginFor<T>,
1065 collection: T::CollectionId,
1066 items: Vec<T::ItemId>,
1067 ) -> DispatchResult {
1068 let origin = ensure_signed(origin)?;
1069
1070 let collection_details =
1071 Collection::<T, I>::get(&collection).ok_or(Error::<T, I>::UnknownCollection)?;
1072 ensure!(collection_details.owner == origin, Error::<T, I>::NoPermission);
1073
1074 let config = Self::get_collection_config(&collection)?;
1075 let deposit = match config.is_setting_enabled(CollectionSetting::DepositRequired) {
1076 true => T::ItemDeposit::get(),
1077 false => Zero::zero(),
1078 };
1079
1080 let mut successful = Vec::with_capacity(items.len());
1081 for item in items.into_iter() {
1082 let mut details = match Item::<T, I>::get(&collection, &item) {
1083 Some(x) => x,
1084 None => continue,
1085 };
1086 let old = details.deposit.amount;
1087 if old > deposit {
1088 T::Currency::unreserve(&details.deposit.account, old - deposit);
1089 } else if deposit > old {
1090 if T::Currency::reserve(&details.deposit.account, deposit - old).is_err() {
1091 continue
1094 }
1095 } else {
1096 continue
1097 }
1098 details.deposit.amount = deposit;
1099 Item::<T, I>::insert(&collection, &item, &details);
1100 successful.push(item);
1101 }
1102
1103 Self::deposit_event(Event::<T, I>::Redeposited {
1104 collection,
1105 successful_items: successful,
1106 });
1107
1108 Ok(())
1109 }
1110
1111 #[pallet::call_index(8)]
1122 #[pallet::weight(T::WeightInfo::lock_item_transfer())]
1123 pub fn lock_item_transfer(
1124 origin: OriginFor<T>,
1125 collection: T::CollectionId,
1126 item: T::ItemId,
1127 ) -> DispatchResult {
1128 let origin = ensure_signed(origin)?;
1129 Self::do_lock_item_transfer(origin, collection, item)
1130 }
1131
1132 #[pallet::call_index(9)]
1143 #[pallet::weight(T::WeightInfo::unlock_item_transfer())]
1144 pub fn unlock_item_transfer(
1145 origin: OriginFor<T>,
1146 collection: T::CollectionId,
1147 item: T::ItemId,
1148 ) -> DispatchResult {
1149 let origin = ensure_signed(origin)?;
1150 Self::do_unlock_item_transfer(origin, collection, item)
1151 }
1152
1153 #[pallet::call_index(10)]
1166 #[pallet::weight(T::WeightInfo::lock_collection())]
1167 pub fn lock_collection(
1168 origin: OriginFor<T>,
1169 collection: T::CollectionId,
1170 lock_settings: CollectionSettings,
1171 ) -> DispatchResult {
1172 let origin = ensure_signed(origin)?;
1173 Self::do_lock_collection(origin, collection, lock_settings)
1174 }
1175
1176 #[pallet::call_index(11)]
1188 #[pallet::weight(T::WeightInfo::transfer_ownership())]
1189 pub fn transfer_ownership(
1190 origin: OriginFor<T>,
1191 collection: T::CollectionId,
1192 new_owner: AccountIdLookupOf<T>,
1193 ) -> DispatchResult {
1194 let origin = ensure_signed(origin)?;
1195 let new_owner = T::Lookup::lookup(new_owner)?;
1196 Self::do_transfer_ownership(origin, collection, new_owner)
1197 }
1198
1199 #[pallet::call_index(12)]
1216 #[pallet::weight(T::WeightInfo::set_team())]
1217 pub fn set_team(
1218 origin: OriginFor<T>,
1219 collection: T::CollectionId,
1220 issuer: Option<AccountIdLookupOf<T>>,
1221 admin: Option<AccountIdLookupOf<T>>,
1222 freezer: Option<AccountIdLookupOf<T>>,
1223 ) -> DispatchResult {
1224 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1225 .map(|_| None)
1226 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1227 let issuer = issuer.map(T::Lookup::lookup).transpose()?;
1228 let admin = admin.map(T::Lookup::lookup).transpose()?;
1229 let freezer = freezer.map(T::Lookup::lookup).transpose()?;
1230 Self::do_set_team(maybe_check_owner, collection, issuer, admin, freezer)
1231 }
1232
1233 #[pallet::call_index(13)]
1244 #[pallet::weight(T::WeightInfo::force_collection_owner())]
1245 pub fn force_collection_owner(
1246 origin: OriginFor<T>,
1247 collection: T::CollectionId,
1248 owner: AccountIdLookupOf<T>,
1249 ) -> DispatchResult {
1250 T::ForceOrigin::ensure_origin(origin)?;
1251 let new_owner = T::Lookup::lookup(owner)?;
1252 Self::do_force_collection_owner(collection, new_owner)
1253 }
1254
1255 #[pallet::call_index(14)]
1266 #[pallet::weight(T::WeightInfo::force_collection_config())]
1267 pub fn force_collection_config(
1268 origin: OriginFor<T>,
1269 collection: T::CollectionId,
1270 config: CollectionConfigFor<T, I>,
1271 ) -> DispatchResult {
1272 T::ForceOrigin::ensure_origin(origin)?;
1273 Self::do_force_collection_config(collection, config)
1274 }
1275
1276 #[pallet::call_index(15)]
1291 #[pallet::weight(T::WeightInfo::approve_transfer())]
1292 pub fn approve_transfer(
1293 origin: OriginFor<T>,
1294 collection: T::CollectionId,
1295 item: T::ItemId,
1296 delegate: AccountIdLookupOf<T>,
1297 maybe_deadline: Option<BlockNumberFor<T, I>>,
1298 ) -> DispatchResult {
1299 let maybe_check_origin = T::ForceOrigin::try_origin(origin)
1300 .map(|_| None)
1301 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1302 let delegate = T::Lookup::lookup(delegate)?;
1303 Self::do_approve_transfer(
1304 maybe_check_origin,
1305 collection,
1306 item,
1307 delegate,
1308 maybe_deadline,
1309 )
1310 }
1311
1312 #[pallet::call_index(16)]
1327 #[pallet::weight(T::WeightInfo::cancel_approval())]
1328 pub fn cancel_approval(
1329 origin: OriginFor<T>,
1330 collection: T::CollectionId,
1331 item: T::ItemId,
1332 delegate: AccountIdLookupOf<T>,
1333 ) -> DispatchResult {
1334 let maybe_check_origin = T::ForceOrigin::try_origin(origin)
1335 .map(|_| None)
1336 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1337 let delegate = T::Lookup::lookup(delegate)?;
1338 Self::do_cancel_approval(maybe_check_origin, collection, item, delegate)
1339 }
1340
1341 #[pallet::call_index(17)]
1355 #[pallet::weight(T::WeightInfo::clear_all_transfer_approvals())]
1356 pub fn clear_all_transfer_approvals(
1357 origin: OriginFor<T>,
1358 collection: T::CollectionId,
1359 item: T::ItemId,
1360 ) -> DispatchResult {
1361 let maybe_check_origin = T::ForceOrigin::try_origin(origin)
1362 .map(|_| None)
1363 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1364 Self::do_clear_all_transfer_approvals(maybe_check_origin, collection, item)
1365 }
1366
1367 #[pallet::call_index(18)]
1385 #[pallet::weight(T::WeightInfo::lock_item_properties())]
1386 pub fn lock_item_properties(
1387 origin: OriginFor<T>,
1388 collection: T::CollectionId,
1389 item: T::ItemId,
1390 lock_metadata: bool,
1391 lock_attributes: bool,
1392 ) -> DispatchResult {
1393 let maybe_check_origin = T::ForceOrigin::try_origin(origin)
1394 .map(|_| None)
1395 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1396 Self::do_lock_item_properties(
1397 maybe_check_origin,
1398 collection,
1399 item,
1400 lock_metadata,
1401 lock_attributes,
1402 )
1403 }
1404
1405 #[pallet::call_index(19)]
1428 #[pallet::weight(T::WeightInfo::set_attribute())]
1429 pub fn set_attribute(
1430 origin: OriginFor<T>,
1431 collection: T::CollectionId,
1432 maybe_item: Option<T::ItemId>,
1433 namespace: AttributeNamespace<T::AccountId>,
1434 key: BoundedVec<u8, T::KeyLimit>,
1435 value: BoundedVec<u8, T::ValueLimit>,
1436 ) -> DispatchResult {
1437 let origin = ensure_signed(origin)?;
1438 let depositor = match namespace {
1439 AttributeNamespace::CollectionOwner =>
1440 Self::collection_owner(collection).ok_or(Error::<T, I>::UnknownCollection)?,
1441 _ => origin.clone(),
1442 };
1443 Self::do_set_attribute(origin, collection, maybe_item, namespace, key, value, depositor)
1444 }
1445
1446 #[pallet::call_index(20)]
1464 #[pallet::weight(T::WeightInfo::force_set_attribute())]
1465 pub fn force_set_attribute(
1466 origin: OriginFor<T>,
1467 set_as: Option<T::AccountId>,
1468 collection: T::CollectionId,
1469 maybe_item: Option<T::ItemId>,
1470 namespace: AttributeNamespace<T::AccountId>,
1471 key: BoundedVec<u8, T::KeyLimit>,
1472 value: BoundedVec<u8, T::ValueLimit>,
1473 ) -> DispatchResult {
1474 T::ForceOrigin::ensure_origin(origin)?;
1475 Self::do_force_set_attribute(set_as, collection, maybe_item, namespace, key, value)
1476 }
1477
1478 #[pallet::call_index(21)]
1494 #[pallet::weight(T::WeightInfo::clear_attribute())]
1495 pub fn clear_attribute(
1496 origin: OriginFor<T>,
1497 collection: T::CollectionId,
1498 maybe_item: Option<T::ItemId>,
1499 namespace: AttributeNamespace<T::AccountId>,
1500 key: BoundedVec<u8, T::KeyLimit>,
1501 ) -> DispatchResult {
1502 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1503 .map(|_| None)
1504 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1505 Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, namespace, key)
1506 }
1507
1508 #[pallet::call_index(22)]
1518 #[pallet::weight(T::WeightInfo::approve_item_attributes())]
1519 pub fn approve_item_attributes(
1520 origin: OriginFor<T>,
1521 collection: T::CollectionId,
1522 item: T::ItemId,
1523 delegate: AccountIdLookupOf<T>,
1524 ) -> DispatchResult {
1525 let origin = ensure_signed(origin)?;
1526 let delegate = T::Lookup::lookup(delegate)?;
1527 Self::do_approve_item_attributes(origin, collection, item, delegate)
1528 }
1529
1530 #[pallet::call_index(23)]
1541 #[pallet::weight(T::WeightInfo::cancel_item_attributes_approval(
1542 witness.account_attributes
1543 ))]
1544 pub fn cancel_item_attributes_approval(
1545 origin: OriginFor<T>,
1546 collection: T::CollectionId,
1547 item: T::ItemId,
1548 delegate: AccountIdLookupOf<T>,
1549 witness: CancelAttributesApprovalWitness,
1550 ) -> DispatchResult {
1551 let origin = ensure_signed(origin)?;
1552 let delegate = T::Lookup::lookup(delegate)?;
1553 Self::do_cancel_item_attributes_approval(origin, collection, item, delegate, witness)
1554 }
1555
1556 #[pallet::call_index(24)]
1573 #[pallet::weight(T::WeightInfo::set_metadata())]
1574 pub fn set_metadata(
1575 origin: OriginFor<T>,
1576 collection: T::CollectionId,
1577 item: T::ItemId,
1578 data: BoundedVec<u8, T::StringLimit>,
1579 ) -> DispatchResult {
1580 let maybe_check_origin = T::ForceOrigin::try_origin(origin)
1581 .map(|_| None)
1582 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1583 Self::do_set_item_metadata(maybe_check_origin, collection, item, data, None)
1584 }
1585
1586 #[pallet::call_index(25)]
1600 #[pallet::weight(T::WeightInfo::clear_metadata())]
1601 pub fn clear_metadata(
1602 origin: OriginFor<T>,
1603 collection: T::CollectionId,
1604 item: T::ItemId,
1605 ) -> DispatchResult {
1606 let maybe_check_origin = T::ForceOrigin::try_origin(origin)
1607 .map(|_| None)
1608 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1609 Self::do_clear_item_metadata(maybe_check_origin, collection, item)
1610 }
1611
1612 #[pallet::call_index(26)]
1628 #[pallet::weight(T::WeightInfo::set_collection_metadata())]
1629 pub fn set_collection_metadata(
1630 origin: OriginFor<T>,
1631 collection: T::CollectionId,
1632 data: BoundedVec<u8, T::StringLimit>,
1633 ) -> DispatchResult {
1634 let maybe_check_origin = T::ForceOrigin::try_origin(origin)
1635 .map(|_| None)
1636 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1637 Self::do_set_collection_metadata(maybe_check_origin, collection, data)
1638 }
1639
1640 #[pallet::call_index(27)]
1653 #[pallet::weight(T::WeightInfo::clear_collection_metadata())]
1654 pub fn clear_collection_metadata(
1655 origin: OriginFor<T>,
1656 collection: T::CollectionId,
1657 ) -> DispatchResult {
1658 let maybe_check_origin = T::ForceOrigin::try_origin(origin)
1659 .map(|_| None)
1660 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1661 Self::do_clear_collection_metadata(maybe_check_origin, collection)
1662 }
1663
1664 #[pallet::call_index(28)]
1675 #[pallet::weight(T::WeightInfo::set_accept_ownership())]
1676 pub fn set_accept_ownership(
1677 origin: OriginFor<T>,
1678 maybe_collection: Option<T::CollectionId>,
1679 ) -> DispatchResult {
1680 let who = ensure_signed(origin)?;
1681 Self::do_set_accept_ownership(who, maybe_collection)
1682 }
1683
1684 #[pallet::call_index(29)]
1694 #[pallet::weight(T::WeightInfo::set_collection_max_supply())]
1695 pub fn set_collection_max_supply(
1696 origin: OriginFor<T>,
1697 collection: T::CollectionId,
1698 max_supply: u32,
1699 ) -> DispatchResult {
1700 let maybe_check_owner = T::ForceOrigin::try_origin(origin)
1701 .map(|_| None)
1702 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1703 Self::do_set_collection_max_supply(maybe_check_owner, collection, max_supply)
1704 }
1705
1706 #[pallet::call_index(30)]
1716 #[pallet::weight(T::WeightInfo::update_mint_settings())]
1717 pub fn update_mint_settings(
1718 origin: OriginFor<T>,
1719 collection: T::CollectionId,
1720 mint_settings: MintSettings<BalanceOf<T, I>, BlockNumberFor<T, I>, T::CollectionId>,
1721 ) -> DispatchResult {
1722 let maybe_check_origin = T::ForceOrigin::try_origin(origin)
1723 .map(|_| None)
1724 .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?;
1725 Self::do_update_mint_settings(maybe_check_origin, collection, mint_settings)
1726 }
1727
1728 #[pallet::call_index(31)]
1740 #[pallet::weight(T::WeightInfo::set_price())]
1741 pub fn set_price(
1742 origin: OriginFor<T>,
1743 collection: T::CollectionId,
1744 item: T::ItemId,
1745 price: Option<ItemPrice<T, I>>,
1746 whitelisted_buyer: Option<AccountIdLookupOf<T>>,
1747 ) -> DispatchResult {
1748 let origin = ensure_signed(origin)?;
1749 let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?;
1750 Self::do_set_price(collection, item, origin, price, whitelisted_buyer)
1751 }
1752
1753 #[pallet::call_index(32)]
1763 #[pallet::weight(T::WeightInfo::buy_item())]
1764 pub fn buy_item(
1765 origin: OriginFor<T>,
1766 collection: T::CollectionId,
1767 item: T::ItemId,
1768 bid_price: ItemPrice<T, I>,
1769 ) -> DispatchResult {
1770 let origin = ensure_signed(origin)?;
1771 Self::do_buy_item(collection, item, origin, bid_price)
1772 }
1773
1774 #[pallet::call_index(33)]
1782 #[pallet::weight(T::WeightInfo::pay_tips(tips.len() as u32))]
1783 pub fn pay_tips(
1784 origin: OriginFor<T>,
1785 tips: BoundedVec<ItemTipOf<T, I>, T::MaxTips>,
1786 ) -> DispatchResult {
1787 let origin = ensure_signed(origin)?;
1788 Self::do_pay_tips(origin, tips)
1789 }
1790
1791 #[pallet::call_index(34)]
1808 #[pallet::weight(T::WeightInfo::create_swap())]
1809 pub fn create_swap(
1810 origin: OriginFor<T>,
1811 offered_collection: T::CollectionId,
1812 offered_item: T::ItemId,
1813 desired_collection: T::CollectionId,
1814 maybe_desired_item: Option<T::ItemId>,
1815 maybe_price: Option<PriceWithDirection<ItemPrice<T, I>>>,
1816 duration: BlockNumberFor<T, I>,
1817 ) -> DispatchResult {
1818 let origin = ensure_signed(origin)?;
1819 Self::do_create_swap(
1820 origin,
1821 offered_collection,
1822 offered_item,
1823 desired_collection,
1824 maybe_desired_item,
1825 maybe_price,
1826 duration,
1827 )
1828 }
1829
1830 #[pallet::call_index(35)]
1840 #[pallet::weight(T::WeightInfo::cancel_swap())]
1841 pub fn cancel_swap(
1842 origin: OriginFor<T>,
1843 offered_collection: T::CollectionId,
1844 offered_item: T::ItemId,
1845 ) -> DispatchResult {
1846 let origin = ensure_signed(origin)?;
1847 Self::do_cancel_swap(origin, offered_collection, offered_item)
1848 }
1849
1850 #[pallet::call_index(36)]
1863 #[pallet::weight(T::WeightInfo::claim_swap())]
1864 pub fn claim_swap(
1865 origin: OriginFor<T>,
1866 send_collection: T::CollectionId,
1867 send_item: T::ItemId,
1868 receive_collection: T::CollectionId,
1869 receive_item: T::ItemId,
1870 witness_price: Option<PriceWithDirection<ItemPrice<T, I>>>,
1871 ) -> DispatchResult {
1872 let origin = ensure_signed(origin)?;
1873 Self::do_claim_swap(
1874 origin,
1875 send_collection,
1876 send_item,
1877 receive_collection,
1878 receive_item,
1879 witness_price,
1880 )
1881 }
1882
1883 #[pallet::call_index(37)]
1897 #[pallet::weight(T::WeightInfo::mint_pre_signed(mint_data.attributes.len() as u32))]
1898 pub fn mint_pre_signed(
1899 origin: OriginFor<T>,
1900 mint_data: Box<PreSignedMintOf<T, I>>,
1901 signature: T::OffchainSignature,
1902 signer: T::AccountId,
1903 ) -> DispatchResult {
1904 let origin = ensure_signed(origin)?;
1905 Self::validate_signature(&Encode::encode(&mint_data), &signature, &signer)?;
1906 Self::do_mint_pre_signed(origin, *mint_data, signer)
1907 }
1908
1909 #[pallet::call_index(38)]
1923 #[pallet::weight(T::WeightInfo::set_attributes_pre_signed(data.attributes.len() as u32))]
1924 pub fn set_attributes_pre_signed(
1925 origin: OriginFor<T>,
1926 data: PreSignedAttributesOf<T, I>,
1927 signature: T::OffchainSignature,
1928 signer: T::AccountId,
1929 ) -> DispatchResult {
1930 let origin = ensure_signed(origin)?;
1931 Self::validate_signature(&Encode::encode(&data), &signature, &signer)?;
1932 Self::do_set_attributes_pre_signed(origin, data, signer)
1933 }
1934 }
1935}
1936
1937sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);