// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! # Nfts Module //! //! A simple, secure module for dealing with non-fungible items. //! //! ## Related Modules //! //! * [`System`](../frame_system/index.html) //! * [`Support`](../frame_support/index.html) #![recursion_limit = "256"] // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] #[cfg(feature = "runtime-benchmarks")] mod benchmarking; pub mod migration; #[cfg(test)] pub mod mock; #[cfg(test)] mod tests; mod common_functions; /// A library providing the feature set of this pallet. It contains modules with helper methods that /// perform storage updates and checks required by this pallet's dispatchables. To use pallet level /// features, make sure to set appropriate bitflags for [`Config::Features`] in your runtime /// configuration trait. mod features; mod impl_nonfungibles; mod types; pub mod macros; pub mod weights; use codec::{Decode, Encode}; use frame_support::traits::{ tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, Incrementable, ReservableCurrency, }; use frame_system::Config as SystemConfig; use sp_runtime::{ traits::{IdentifyAccount, Saturating, StaticLookup, Verify, Zero}, RuntimeDebug, }; use sp_std::prelude::*; pub use pallet::*; pub use types::*; pub use weights::WeightInfo; /// The log target of this pallet. pub const LOG_TARGET: &'static str = "runtime::nfts"; /// A type alias for the account ID type used in the dispatchable functions of this pallet. type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; use frame_system::pallet_prelude::*; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData<(T, I)>); #[cfg(feature = "runtime-benchmarks")] pub trait BenchmarkHelper { fn collection(i: u16) -> CollectionId; fn item(i: u16) -> ItemId; } #[cfg(feature = "runtime-benchmarks")] impl, ItemId: From> BenchmarkHelper for () { fn collection(i: u16) -> CollectionId { i.into() } fn item(i: u16) -> ItemId { i.into() } } #[pallet::config] /// The module configuration trait. pub trait Config: frame_system::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// Identifier for the collection of item. /// /// SAFETY: The functions in the `Incrementable` trait are fallible. If the functions /// of the implementation both return `None`, the automatic CollectionId generation /// should not be used. So the `create` and `force_create` extrinsics and the /// `create_collection` function will return an `UnknownCollection` Error. Instead use /// the `create_collection_with_id` function. However, if the `Incrementable` trait /// implementation has an incremental order, the `create_collection_with_id` function /// should not be used as it can claim a value in the ID sequence. type CollectionId: Member + Parameter + MaxEncodedLen + Copy + Incrementable; /// The type used to identify a unique item within a collection. type ItemId: Member + Parameter + MaxEncodedLen + Copy; /// The currency mechanism, used for paying for reserves. type Currency: ReservableCurrency; /// The origin which may forcibly create or destroy an item or otherwise alter privileged /// attributes. type ForceOrigin: EnsureOrigin; /// Standard collection creation is only allowed if the origin attempting it and the /// collection are in this set. type CreateOrigin: EnsureOriginWithArg< Self::RuntimeOrigin, Self::CollectionId, Success = Self::AccountId, >; /// Locker trait to enable Locking mechanism downstream. type Locker: Locker; /// The basic amount of funds that must be reserved for collection. #[pallet::constant] type CollectionDeposit: Get>; /// The basic amount of funds that must be reserved for an item. #[pallet::constant] type ItemDeposit: Get>; /// The basic amount of funds that must be reserved when adding metadata to your item. #[pallet::constant] type MetadataDepositBase: Get>; /// The basic amount of funds that must be reserved when adding an attribute to an item. #[pallet::constant] type AttributeDepositBase: Get>; /// The additional funds that must be reserved for the number of bytes store in metadata, /// either "normal" metadata or attribute metadata. #[pallet::constant] type DepositPerByte: Get>; /// The maximum length of data stored on-chain. #[pallet::constant] type StringLimit: Get; /// The maximum length of an attribute key. #[pallet::constant] type KeyLimit: Get; /// The maximum length of an attribute value. #[pallet::constant] type ValueLimit: Get; /// The maximum approvals an item could have. #[pallet::constant] type ApprovalsLimit: Get; /// The maximum attributes approvals an item could have. #[pallet::constant] type ItemAttributesApprovalsLimit: Get; /// The max number of tips a user could send. #[pallet::constant] type MaxTips: Get; /// The max duration in blocks for deadlines. #[pallet::constant] type MaxDeadlineDuration: Get>; /// The max number of attributes a user could set per call. #[pallet::constant] type MaxAttributesPerCall: Get; /// Disables some of pallet's features. #[pallet::constant] type Features: Get; /// Off-Chain signature type. /// /// Can verify whether an `Self::OffchainPublic` created a signature. type OffchainSignature: Verify + Parameter; /// Off-Chain public key. /// /// Must identify as an on-chain `Self::AccountId`. type OffchainPublic: IdentifyAccount; #[cfg(feature = "runtime-benchmarks")] /// A set of helper functions for benchmarking. type Helper: BenchmarkHelper; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } /// Details of a collection. #[pallet::storage] pub type Collection, I: 'static = ()> = StorageMap< _, Blake2_128Concat, T::CollectionId, CollectionDetails>, >; /// The collection, if any, of which an account is willing to take ownership. #[pallet::storage] pub type OwnershipAcceptance, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>; /// The items held by any given account; set out this way so that items owned by a single /// account can be enumerated. #[pallet::storage] pub type Account, I: 'static = ()> = StorageNMap< _, ( NMapKey, // owner NMapKey, NMapKey, ), (), OptionQuery, >; /// The collections owned by any given account; set out this way so that collections owned by /// a single account can be enumerated. #[pallet::storage] pub type CollectionAccount, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::AccountId, Blake2_128Concat, T::CollectionId, (), OptionQuery, >; /// The items in existence and their ownership details. #[pallet::storage] /// Stores collection roles as per account. pub type CollectionRoleOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, Blake2_128Concat, T::AccountId, CollectionRoles, OptionQuery, >; /// The items in existence and their ownership details. #[pallet::storage] pub type Item, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, Blake2_128Concat, T::ItemId, ItemDetails, ApprovalsOf>, OptionQuery, >; /// Metadata of a collection. #[pallet::storage] pub type CollectionMetadataOf, I: 'static = ()> = StorageMap< _, Blake2_128Concat, T::CollectionId, CollectionMetadata, T::StringLimit>, OptionQuery, >; /// Metadata of an item. #[pallet::storage] pub type ItemMetadataOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, Blake2_128Concat, T::ItemId, ItemMetadata, T::StringLimit>, OptionQuery, >; /// Attributes of a collection. #[pallet::storage] pub type Attribute, I: 'static = ()> = StorageNMap< _, ( NMapKey, NMapKey>, NMapKey>, NMapKey>, ), (BoundedVec, AttributeDepositOf), OptionQuery, >; /// A price of an item. #[pallet::storage] pub type ItemPriceOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, Blake2_128Concat, T::ItemId, (ItemPrice, Option), OptionQuery, >; /// Item attribute approvals. #[pallet::storage] pub type ItemAttributesApprovalsOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, Blake2_128Concat, T::ItemId, ItemAttributesApprovals, ValueQuery, >; /// Stores the `CollectionId` that is going to be used for the next collection. /// This gets incremented whenever a new collection is created. #[pallet::storage] pub type NextCollectionId, I: 'static = ()> = StorageValue<_, T::CollectionId, OptionQuery>; /// Handles all the pending swaps. #[pallet::storage] pub type PendingSwapOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, Blake2_128Concat, T::ItemId, PendingSwap< T::CollectionId, T::ItemId, PriceWithDirection>, BlockNumberFor, >, OptionQuery, >; /// Config of a collection. #[pallet::storage] pub type CollectionConfigOf, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfigFor, OptionQuery>; /// Config of an item. #[pallet::storage] pub type ItemConfigOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, Blake2_128Concat, T::ItemId, ItemConfig, OptionQuery, >; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { /// A `collection` was created. Created { collection: T::CollectionId, creator: T::AccountId, owner: T::AccountId }, /// A `collection` was force-created. ForceCreated { collection: T::CollectionId, owner: T::AccountId }, /// A `collection` was destroyed. Destroyed { collection: T::CollectionId }, /// An `item` was issued. Issued { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, /// An `item` was transferred. Transferred { collection: T::CollectionId, item: T::ItemId, from: T::AccountId, to: T::AccountId, }, /// An `item` was destroyed. Burned { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, /// An `item` became non-transferable. ItemTransferLocked { collection: T::CollectionId, item: T::ItemId }, /// An `item` became transferable. ItemTransferUnlocked { collection: T::CollectionId, item: T::ItemId }, /// `item` metadata or attributes were locked. ItemPropertiesLocked { collection: T::CollectionId, item: T::ItemId, lock_metadata: bool, lock_attributes: bool, }, /// Some `collection` was locked. CollectionLocked { collection: T::CollectionId }, /// The owner changed. OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId }, /// The management team changed. TeamChanged { collection: T::CollectionId, issuer: Option, admin: Option, freezer: Option, }, /// An `item` of a `collection` has been approved by the `owner` for transfer by /// a `delegate`. TransferApproved { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId, delegate: T::AccountId, deadline: Option>, }, /// An approval for a `delegate` account to transfer the `item` of an item /// `collection` was cancelled by its `owner`. ApprovalCancelled { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId, delegate: T::AccountId, }, /// All approvals of an item got cancelled. AllApprovalsCancelled { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, /// A `collection` has had its config changed by the `Force` origin. CollectionConfigChanged { collection: T::CollectionId }, /// New metadata has been set for a `collection`. CollectionMetadataSet { collection: T::CollectionId, data: BoundedVec }, /// Metadata has been cleared for a `collection`. CollectionMetadataCleared { collection: T::CollectionId }, /// New metadata has been set for an item. ItemMetadataSet { collection: T::CollectionId, item: T::ItemId, data: BoundedVec, }, /// Metadata has been cleared for an item. ItemMetadataCleared { collection: T::CollectionId, item: T::ItemId }, /// The deposit for a set of `item`s within a `collection` has been updated. Redeposited { collection: T::CollectionId, successful_items: Vec }, /// New attribute metadata has been set for a `collection` or `item`. AttributeSet { collection: T::CollectionId, maybe_item: Option, key: BoundedVec, value: BoundedVec, namespace: AttributeNamespace, }, /// Attribute metadata has been cleared for a `collection` or `item`. AttributeCleared { collection: T::CollectionId, maybe_item: Option, key: BoundedVec, namespace: AttributeNamespace, }, /// A new approval to modify item attributes was added. ItemAttributesApprovalAdded { collection: T::CollectionId, item: T::ItemId, delegate: T::AccountId, }, /// A new approval to modify item attributes was removed. ItemAttributesApprovalRemoved { collection: T::CollectionId, item: T::ItemId, delegate: T::AccountId, }, /// Ownership acceptance has changed for an account. OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, /// Max supply has been set for a collection. CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, /// Mint settings for a collection had changed. CollectionMintSettingsUpdated { collection: T::CollectionId }, /// Event gets emitted when the `NextCollectionId` gets incremented. NextCollectionIdIncremented { next_id: Option }, /// The price was set for the item. ItemPriceSet { collection: T::CollectionId, item: T::ItemId, price: ItemPrice, whitelisted_buyer: Option, }, /// The price for the item was removed. ItemPriceRemoved { collection: T::CollectionId, item: T::ItemId }, /// An item was bought. ItemBought { collection: T::CollectionId, item: T::ItemId, price: ItemPrice, seller: T::AccountId, buyer: T::AccountId, }, /// A tip was sent. TipSent { collection: T::CollectionId, item: T::ItemId, sender: T::AccountId, receiver: T::AccountId, amount: DepositBalanceOf, }, /// An `item` swap intent was created. SwapCreated { offered_collection: T::CollectionId, offered_item: T::ItemId, desired_collection: T::CollectionId, desired_item: Option, price: Option>>, deadline: BlockNumberFor, }, /// The swap was cancelled. SwapCancelled { offered_collection: T::CollectionId, offered_item: T::ItemId, desired_collection: T::CollectionId, desired_item: Option, price: Option>>, deadline: BlockNumberFor, }, /// The swap has been claimed. SwapClaimed { sent_collection: T::CollectionId, sent_item: T::ItemId, sent_item_owner: T::AccountId, received_collection: T::CollectionId, received_item: T::ItemId, received_item_owner: T::AccountId, price: Option>>, deadline: BlockNumberFor, }, /// New attributes have been set for an `item` of the `collection`. PreSignedAttributesSet { collection: T::CollectionId, item: T::ItemId, namespace: AttributeNamespace, }, /// A new attribute in the `Pallet` namespace was set for the `collection` or an `item` /// within that `collection`. PalletAttributeSet { collection: T::CollectionId, item: Option, attribute: PalletAttributes, value: BoundedVec, }, } #[pallet::error] pub enum Error { /// The signing account has no permission to do the operation. NoPermission, /// The given item ID is unknown. UnknownCollection, /// The item ID has already been used for an item. AlreadyExists, /// The approval had a deadline that expired, so the approval isn't valid anymore. ApprovalExpired, /// The owner turned out to be different to what was expected. WrongOwner, /// The witness data given does not match the current state of the chain. BadWitness, /// Collection ID is already taken. CollectionIdInUse, /// Items within that collection are non-transferable. ItemsNonTransferable, /// The provided account is not a delegate. NotDelegate, /// The delegate turned out to be different to what was expected. WrongDelegate, /// No approval exists that would allow the transfer. Unapproved, /// The named owner has not signed ownership acceptance of the collection. Unaccepted, /// The item is locked (non-transferable). ItemLocked, /// Item's attributes are locked. LockedItemAttributes, /// Collection's attributes are locked. LockedCollectionAttributes, /// Item's metadata is locked. LockedItemMetadata, /// Collection's metadata is locked. LockedCollectionMetadata, /// All items have been minted. MaxSupplyReached, /// The max supply is locked and can't be changed. MaxSupplyLocked, /// The provided max supply is less than the number of items a collection already has. MaxSupplyTooSmall, /// The given item ID is unknown. UnknownItem, /// Swap doesn't exist. UnknownSwap, /// The given item has no metadata set. MetadataNotFound, /// The provided attribute can't be found. AttributeNotFound, /// Item is not for sale. NotForSale, /// The provided bid is too low. BidTooLow, /// The item has reached its approval limit. ReachedApprovalLimit, /// The deadline has already expired. DeadlineExpired, /// The duration provided should be less than or equal to `MaxDeadlineDuration`. WrongDuration, /// The method is disabled by system settings. MethodDisabled, /// The provided setting can't be set. WrongSetting, /// Item's config already exists and should be equal to the provided one. InconsistentItemConfig, /// Config for a collection or an item can't be found. NoConfig, /// Some roles were not cleared. RolesNotCleared, /// Mint has not started yet. MintNotStarted, /// Mint has already ended. MintEnded, /// The provided Item was already used for claiming. AlreadyClaimed, /// The provided data is incorrect. IncorrectData, /// The extrinsic was sent by the wrong origin. WrongOrigin, /// The provided signature is incorrect. WrongSignature, /// The provided metadata might be too long. IncorrectMetadata, /// Can't set more attributes per one call. MaxAttributesLimitReached, /// The provided namespace isn't supported in this call. WrongNamespace, /// Can't delete non-empty collections. CollectionNotEmpty, /// The witness data should be provided. WitnessRequired, } #[pallet::call] impl, I: 'static> Pallet { /// Issue a new collection of non-fungible items from a public origin. /// /// This new collection has no items initially and its owner is the origin. /// /// The origin must be Signed and the sender must have sufficient funds free. /// /// `CollectionDeposit` funds of sender are reserved. /// /// Parameters: /// - `admin`: The admin of this collection. The admin is the initial address of each /// member of the collection's admin team. /// /// Emits `Created` event when successful. /// /// Weight: `O(1)` #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, admin: AccountIdLookupOf, config: CollectionConfigFor, ) -> DispatchResult { let collection = NextCollectionId::::get() .or(T::CollectionId::initial_value()) .ok_or(Error::::UnknownCollection)?; let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; let admin = T::Lookup::lookup(admin)?; // DepositRequired can be disabled by calling the force_create() only ensure!( !config.has_disabled_setting(CollectionSetting::DepositRequired), Error::::WrongSetting ); Self::do_create_collection( collection, owner.clone(), admin.clone(), config, T::CollectionDeposit::get(), Event::Created { collection, creator: owner, owner: admin }, )?; Self::set_next_collection_id(collection); Ok(()) } /// Issue a new collection of non-fungible items from a privileged origin. /// /// This new collection has no items initially. /// /// The origin must conform to `ForceOrigin`. /// /// Unlike `create`, no funds are reserved. /// /// - `owner`: The owner of this collection of items. The owner has full superuser /// permissions over this item, but may later change and configure the permissions using /// `transfer_ownership` and `set_team`. /// /// Emits `ForceCreated` event when successful. /// /// Weight: `O(1)` #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, owner: AccountIdLookupOf, config: CollectionConfigFor, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; let collection = NextCollectionId::::get() .or(T::CollectionId::initial_value()) .ok_or(Error::::UnknownCollection)?; Self::do_create_collection( collection, owner.clone(), owner.clone(), config, Zero::zero(), Event::ForceCreated { collection, owner }, )?; Self::set_next_collection_id(collection); Ok(()) } /// Destroy a collection of fungible items. /// /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the /// owner of the `collection`. /// /// NOTE: The collection must have 0 items to be destroyed. /// /// - `collection`: The identifier of the collection to be destroyed. /// - `witness`: Information on the items minted in the collection. This must be /// correct. /// /// Emits `Destroyed` event when successful. /// /// Weight: `O(m + c + a)` where: /// - `m = witness.item_metadatas` /// - `c = witness.item_configs` /// - `a = witness.attributes` #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::destroy( witness.item_metadatas, witness.item_configs, witness.attributes, ))] pub fn destroy( origin: OriginFor, collection: T::CollectionId, witness: DestroyWitness, ) -> DispatchResultWithPostInfo { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?; Ok(Some(T::WeightInfo::destroy( details.item_metadatas, details.item_configs, details.attributes, )) .into()) } /// Mint an item of a particular collection. /// /// The origin must be Signed and the sender must comply with the `mint_settings` rules. /// /// - `collection`: The collection of the item to be minted. /// - `item`: An identifier of the new item. /// - `mint_to`: Account into which the item will be minted. /// - `witness_data`: When the mint type is `HolderOf(collection_id)`, then the owned /// item_id from that collection needs to be provided within the witness data object. If /// the mint price is set, then it should be additionally confirmed in the `witness_data`. /// /// Note: the deposit will be taken from the `origin` and not the `owner` of the `item`. /// /// Emits `Issued` event when successful. /// /// Weight: `O(1)` #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::mint())] pub fn mint( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, mint_to: AccountIdLookupOf, witness_data: Option>>, ) -> DispatchResult { let caller = ensure_signed(origin)?; let mint_to = T::Lookup::lookup(mint_to)?; let item_config = ItemConfig { settings: Self::get_default_item_settings(&collection)? }; Self::do_mint( collection, item, Some(caller.clone()), mint_to.clone(), item_config, |collection_details, collection_config| { let mint_settings = collection_config.mint_settings; let now = frame_system::Pallet::::block_number(); if let Some(start_block) = mint_settings.start_block { ensure!(start_block <= now, Error::::MintNotStarted); } if let Some(end_block) = mint_settings.end_block { ensure!(end_block >= now, Error::::MintEnded); } match mint_settings.mint_type { MintType::Issuer => { ensure!( Self::has_role(&collection, &caller, CollectionRole::Issuer), Error::::NoPermission ); }, MintType::HolderOf(collection_id) => { let MintWitness { owned_item, .. } = witness_data.clone().ok_or(Error::::WitnessRequired)?; let owned_item = owned_item.ok_or(Error::::BadWitness)?; let owns_item = Account::::contains_key(( &caller, &collection_id, &owned_item, )); ensure!(owns_item, Error::::BadWitness); let pallet_attribute = PalletAttributes::::UsedToClaim(collection); let key = ( &collection_id, Some(owned_item), AttributeNamespace::Pallet, &Self::construct_attribute_key(pallet_attribute.encode())?, ); let already_claimed = Attribute::::contains_key(key.clone()); ensure!(!already_claimed, Error::::AlreadyClaimed); let attribute_value = Self::construct_attribute_value(vec![])?; Attribute::::insert( key, ( attribute_value.clone(), AttributeDeposit { account: None, amount: Zero::zero() }, ), ); Self::deposit_event(Event::PalletAttributeSet { collection, item: Some(item), attribute: pallet_attribute, value: attribute_value, }); }, _ => {}, } if let Some(price) = mint_settings.price { let MintWitness { mint_price, .. } = witness_data.clone().ok_or(Error::::WitnessRequired)?; let mint_price = mint_price.ok_or(Error::::BadWitness)?; ensure!(mint_price >= price, Error::::BadWitness); T::Currency::transfer( &caller, &collection_details.owner, price, ExistenceRequirement::KeepAlive, )?; } Ok(()) }, ) } /// Mint an item of a particular collection from a privileged origin. /// /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the /// Issuer of the `collection`. /// /// - `collection`: The collection of the item to be minted. /// - `item`: An identifier of the new item. /// - `mint_to`: Account into which the item will be minted. /// - `item_config`: A config of the new item. /// /// Emits `Issued` event when successful. /// /// Weight: `O(1)` #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::force_mint())] pub fn force_mint( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, mint_to: AccountIdLookupOf, item_config: ItemConfig, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let mint_to = T::Lookup::lookup(mint_to)?; if let Some(check_origin) = maybe_check_origin { ensure!( Self::has_role(&collection, &check_origin, CollectionRole::Issuer), Error::::NoPermission ); } Self::do_mint(collection, item, None, mint_to, item_config, |_, _| Ok(())) } /// Destroy a single item. /// /// The origin must conform to `ForceOrigin` or must be Signed and the signing account must /// be the owner of the `item`. /// /// - `collection`: The collection of the item to be burned. /// - `item`: The item to be burned. /// /// Emits `Burned`. /// /// Weight: `O(1)` #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::burn())] pub fn burn( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_burn(collection, item, |details| { if let Some(check_origin) = maybe_check_origin { ensure!(details.owner == check_origin, Error::::NoPermission); } Ok(()) }) } /// Move an item from the sender account to another. /// /// Origin must be Signed and the signing account must be either: /// - the Owner of the `item`; /// - the approved delegate for the `item` (in this case, the approval is reset). /// /// Arguments: /// - `collection`: The collection of the item to be transferred. /// - `item`: The item to be transferred. /// - `dest`: The account to receive ownership of the item. /// /// Emits `Transferred`. /// /// Weight: `O(1)` #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, dest: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; let dest = T::Lookup::lookup(dest)?; Self::do_transfer(collection, item, dest, |_, details| { if details.owner != origin { let deadline = details.approvals.get(&origin).ok_or(Error::::NoPermission)?; if let Some(d) = deadline { let block_number = frame_system::Pallet::::block_number(); ensure!(block_number <= *d, Error::::ApprovalExpired); } } Ok(()) }) } /// Re-evaluate the deposits on some items. /// /// Origin must be Signed and the sender should be the Owner of the `collection`. /// /// - `collection`: The collection of the items to be reevaluated. /// - `items`: The items of the collection whose deposits will be reevaluated. /// /// NOTE: This exists as a best-effort function. Any items which are unknown or /// in the case that the owner account does not have reservable funds to pay for a /// deposit increase are ignored. Generally the owner isn't going to call this on items /// whose existing deposit is less than the refreshed deposit as it would only cost them, /// so it's of little consequence. /// /// It will still return an error in the case that the collection is unknown or the signer /// is not permitted to call it. /// /// Weight: `O(items.len())` #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))] pub fn redeposit( origin: OriginFor, collection: T::CollectionId, items: Vec, ) -> DispatchResult { let origin = ensure_signed(origin)?; let collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(collection_details.owner == origin, Error::::NoPermission); let config = Self::get_collection_config(&collection)?; let deposit = match config.is_setting_enabled(CollectionSetting::DepositRequired) { true => T::ItemDeposit::get(), false => Zero::zero(), }; let mut successful = Vec::with_capacity(items.len()); for item in items.into_iter() { let mut details = match Item::::get(&collection, &item) { Some(x) => x, None => continue, }; let old = details.deposit.amount; if old > deposit { T::Currency::unreserve(&details.deposit.account, old - deposit); } else if deposit > old { if T::Currency::reserve(&details.deposit.account, deposit - old).is_err() { // NOTE: No alterations made to collection_details in this iteration so far, // so this is OK to do. continue } } else { continue } details.deposit.amount = deposit; Item::::insert(&collection, &item, &details); successful.push(item); } Self::deposit_event(Event::::Redeposited { collection, successful_items: successful, }); Ok(()) } /// Disallow further unprivileged transfer of an item. /// /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// /// - `collection`: The collection of the item to be changed. /// - `item`: The item to become non-transferable. /// /// Emits `ItemTransferLocked`. /// /// Weight: `O(1)` #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::lock_item_transfer())] pub fn lock_item_transfer( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { let origin = ensure_signed(origin)?; Self::do_lock_item_transfer(origin, collection, item) } /// Re-allow unprivileged transfer of an item. /// /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// /// - `collection`: The collection of the item to be changed. /// - `item`: The item to become transferable. /// /// Emits `ItemTransferUnlocked`. /// /// Weight: `O(1)` #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::unlock_item_transfer())] pub fn unlock_item_transfer( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { let origin = ensure_signed(origin)?; Self::do_unlock_item_transfer(origin, collection, item) } /// Disallows specified settings for the whole collection. /// /// Origin must be Signed and the sender should be the Owner of the `collection`. /// /// - `collection`: The collection to be locked. /// - `lock_settings`: The settings to be locked. /// /// Note: it's possible to only lock(set) the setting, but not to unset it. /// /// Emits `CollectionLocked`. /// /// Weight: `O(1)` #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::lock_collection())] pub fn lock_collection( origin: OriginFor, collection: T::CollectionId, lock_settings: CollectionSettings, ) -> DispatchResult { let origin = ensure_signed(origin)?; Self::do_lock_collection(origin, collection, lock_settings) } /// Change the Owner of a collection. /// /// Origin must be Signed and the sender should be the Owner of the `collection`. /// /// - `collection`: The collection whose owner should be changed. /// - `owner`: The new Owner of this collection. They must have called /// `set_accept_ownership` with `collection` in order for this operation to succeed. /// /// Emits `OwnerChanged`. /// /// Weight: `O(1)` #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::transfer_ownership())] pub fn transfer_ownership( origin: OriginFor, collection: T::CollectionId, owner: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; Self::do_transfer_ownership(origin, collection, owner) } /// Change the Issuer, Admin and Freezer of a collection. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the /// `collection`. /// /// Note: by setting the role to `None` only the `ForceOrigin` will be able to change it /// after to `Some(account)`. /// /// - `collection`: The collection whose team should be changed. /// - `issuer`: The new Issuer of this collection. /// - `admin`: The new Admin of this collection. /// - `freezer`: The new Freezer of this collection. /// /// Emits `TeamChanged`. /// /// Weight: `O(1)` #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::set_team())] pub fn set_team( origin: OriginFor, collection: T::CollectionId, issuer: Option>, admin: Option>, freezer: Option>, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let issuer = issuer.map(T::Lookup::lookup).transpose()?; let admin = admin.map(T::Lookup::lookup).transpose()?; let freezer = freezer.map(T::Lookup::lookup).transpose()?; Self::do_set_team(maybe_check_owner, collection, issuer, admin, freezer) } /// Change the Owner of a collection. /// /// Origin must be `ForceOrigin`. /// /// - `collection`: The identifier of the collection. /// - `owner`: The new Owner of this collection. /// /// Emits `OwnerChanged`. /// /// Weight: `O(1)` #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::force_collection_owner())] pub fn force_collection_owner( origin: OriginFor, collection: T::CollectionId, owner: AccountIdLookupOf, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let new_owner = T::Lookup::lookup(owner)?; Self::do_force_collection_owner(collection, new_owner) } /// Change the config of a collection. /// /// Origin must be `ForceOrigin`. /// /// - `collection`: The identifier of the collection. /// - `config`: The new config of this collection. /// /// Emits `CollectionConfigChanged`. /// /// Weight: `O(1)` #[pallet::call_index(14)] #[pallet::weight(T::WeightInfo::force_collection_config())] pub fn force_collection_config( origin: OriginFor, collection: T::CollectionId, config: CollectionConfigFor, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; Self::do_force_collection_config(collection, config) } /// Approve an item to be transferred by a delegated third-party account. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the /// `item`. /// /// - `collection`: The collection of the item to be approved for delegated transfer. /// - `item`: The item to be approved for delegated transfer. /// - `delegate`: The account to delegate permission to transfer the item. /// - `maybe_deadline`: Optional deadline for the approval. Specified by providing the /// number of blocks after which the approval will expire /// /// Emits `TransferApproved` on success. /// /// Weight: `O(1)` #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::approve_transfer())] pub fn approve_transfer( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, delegate: AccountIdLookupOf, maybe_deadline: Option>, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let delegate = T::Lookup::lookup(delegate)?; Self::do_approve_transfer( maybe_check_origin, collection, item, delegate, maybe_deadline, ) } /// Cancel one of the transfer approvals for a specific item. /// /// Origin must be either: /// - the `Force` origin; /// - `Signed` with the signer being the Owner of the `item`; /// /// Arguments: /// - `collection`: The collection of the item of whose approval will be cancelled. /// - `item`: The item of the collection of whose approval will be cancelled. /// - `delegate`: The account that is going to loose their approval. /// /// Emits `ApprovalCancelled` on success. /// /// Weight: `O(1)` #[pallet::call_index(16)] #[pallet::weight(T::WeightInfo::cancel_approval())] pub fn cancel_approval( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, delegate: AccountIdLookupOf, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; let delegate = T::Lookup::lookup(delegate)?; Self::do_cancel_approval(maybe_check_origin, collection, item, delegate) } /// Cancel all the approvals of a specific item. /// /// Origin must be either: /// - the `Force` origin; /// - `Signed` with the signer being the Owner of the `item`; /// /// Arguments: /// - `collection`: The collection of the item of whose approvals will be cleared. /// - `item`: The item of the collection of whose approvals will be cleared. /// /// Emits `AllApprovalsCancelled` on success. /// /// Weight: `O(1)` #[pallet::call_index(17)] #[pallet::weight(T::WeightInfo::clear_all_transfer_approvals())] pub fn clear_all_transfer_approvals( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_clear_all_transfer_approvals(maybe_check_origin, collection, item) } /// Disallows changing the metadata or attributes of the item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin /// of the `collection`. /// /// - `collection`: The collection if the `item`. /// - `item`: An item to be locked. /// - `lock_metadata`: Specifies whether the metadata should be locked. /// - `lock_attributes`: Specifies whether the attributes in the `CollectionOwner` namespace /// should be locked. /// /// Note: `lock_attributes` affects the attributes in the `CollectionOwner` namespace only. /// When the metadata or attributes are locked, it won't be possible the unlock them. /// /// Emits `ItemPropertiesLocked`. /// /// Weight: `O(1)` #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::lock_item_properties())] pub fn lock_item_properties( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, lock_metadata: bool, lock_attributes: bool, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_lock_item_properties( maybe_check_origin, collection, item, lock_metadata, lock_attributes, ) } /// Set an attribute for a collection or item. /// /// Origin must be Signed and must conform to the namespace ruleset: /// - `CollectionOwner` namespace could be modified by the `collection` Admin only; /// - `ItemOwner` namespace could be modified by the `maybe_item` owner only. `maybe_item` /// should be set in that case; /// - `Account(AccountId)` namespace could be modified only when the `origin` was given a /// permission to do so; /// /// The funds of `origin` are reserved according to the formula: /// `AttributeDepositBase + DepositPerByte * (key.len + value.len)` taking into /// account any already reserved funds. /// /// - `collection`: The identifier of the collection whose item's metadata to set. /// - `maybe_item`: The identifier of the item whose metadata to set. /// - `namespace`: Attribute's namespace. /// - `key`: The key of the attribute. /// - `value`: The value to which to set the attribute. /// /// Emits `AttributeSet`. /// /// Weight: `O(1)` #[pallet::call_index(19)] #[pallet::weight(T::WeightInfo::set_attribute())] pub fn set_attribute( origin: OriginFor, collection: T::CollectionId, maybe_item: Option, namespace: AttributeNamespace, key: BoundedVec, value: BoundedVec, ) -> DispatchResult { let origin = ensure_signed(origin)?; let depositor = match namespace { AttributeNamespace::CollectionOwner => Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?, _ => origin.clone(), }; Self::do_set_attribute(origin, collection, maybe_item, namespace, key, value, depositor) } /// Force-set an attribute for a collection or item. /// /// Origin must be `ForceOrigin`. /// /// If the attribute already exists and it was set by another account, the deposit /// will be returned to the previous owner. /// /// - `set_as`: An optional owner of the attribute. /// - `collection`: The identifier of the collection whose item's metadata to set. /// - `maybe_item`: The identifier of the item whose metadata to set. /// - `namespace`: Attribute's namespace. /// - `key`: The key of the attribute. /// - `value`: The value to which to set the attribute. /// /// Emits `AttributeSet`. /// /// Weight: `O(1)` #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::force_set_attribute())] pub fn force_set_attribute( origin: OriginFor, set_as: Option, collection: T::CollectionId, maybe_item: Option, namespace: AttributeNamespace, key: BoundedVec, value: BoundedVec, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; Self::do_force_set_attribute(set_as, collection, maybe_item, namespace, key, value) } /// Clear an attribute for a collection or item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the /// attribute. /// /// Any deposit is freed for the collection's owner. /// /// - `collection`: The identifier of the collection whose item's metadata to clear. /// - `maybe_item`: The identifier of the item whose metadata to clear. /// - `namespace`: Attribute's namespace. /// - `key`: The key of the attribute. /// /// Emits `AttributeCleared`. /// /// Weight: `O(1)` #[pallet::call_index(21)] #[pallet::weight(T::WeightInfo::clear_attribute())] pub fn clear_attribute( origin: OriginFor, collection: T::CollectionId, maybe_item: Option, namespace: AttributeNamespace, key: BoundedVec, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_clear_attribute(maybe_check_owner, collection, maybe_item, namespace, key) } /// Approve item's attributes to be changed by a delegated third-party account. /// /// Origin must be Signed and must be an owner of the `item`. /// /// - `collection`: A collection of the item. /// - `item`: The item that holds attributes. /// - `delegate`: The account to delegate permission to change attributes of the item. /// /// Emits `ItemAttributesApprovalAdded` on success. #[pallet::call_index(22)] #[pallet::weight(T::WeightInfo::approve_item_attributes())] pub fn approve_item_attributes( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, delegate: AccountIdLookupOf, ) -> DispatchResult { let origin = ensure_signed(origin)?; let delegate = T::Lookup::lookup(delegate)?; Self::do_approve_item_attributes(origin, collection, item, delegate) } /// Cancel the previously provided approval to change item's attributes. /// All the previously set attributes by the `delegate` will be removed. /// /// Origin must be Signed and must be an owner of the `item`. /// /// - `collection`: Collection that the item is contained within. /// - `item`: The item that holds attributes. /// - `delegate`: The previously approved account to remove. /// /// Emits `ItemAttributesApprovalRemoved` on success. #[pallet::call_index(23)] #[pallet::weight(T::WeightInfo::cancel_item_attributes_approval( witness.account_attributes ))] pub fn cancel_item_attributes_approval( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, delegate: AccountIdLookupOf, witness: CancelAttributesApprovalWitness, ) -> DispatchResult { let origin = ensure_signed(origin)?; let delegate = T::Lookup::lookup(delegate)?; Self::do_cancel_item_attributes_approval(origin, collection, item, delegate, witness) } /// Set the metadata for an item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin of the /// `collection`. /// /// If the origin is Signed, then funds of signer are reserved according to the formula: /// `MetadataDepositBase + DepositPerByte * data.len` taking into /// account any already reserved funds. /// /// - `collection`: The identifier of the collection whose item's metadata to set. /// - `item`: The identifier of the item whose metadata to set. /// - `data`: The general information of this item. Limited in length by `StringLimit`. /// /// Emits `ItemMetadataSet`. /// /// Weight: `O(1)` #[pallet::call_index(24)] #[pallet::weight(T::WeightInfo::set_metadata())] pub fn set_metadata( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, data: BoundedVec, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_set_item_metadata(maybe_check_origin, collection, item, data, None) } /// Clear the metadata for an item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin of the /// `collection`. /// /// Any deposit is freed for the collection's owner. /// /// - `collection`: The identifier of the collection whose item's metadata to clear. /// - `item`: The identifier of the item whose metadata to clear. /// /// Emits `ItemMetadataCleared`. /// /// Weight: `O(1)` #[pallet::call_index(25)] #[pallet::weight(T::WeightInfo::clear_metadata())] pub fn clear_metadata( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_clear_item_metadata(maybe_check_origin, collection, item) } /// Set the metadata for a collection. /// /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Admin of /// the `collection`. /// /// If the origin is `Signed`, then funds of signer are reserved according to the formula: /// `MetadataDepositBase + DepositPerByte * data.len` taking into /// account any already reserved funds. /// /// - `collection`: The identifier of the item whose metadata to update. /// - `data`: The general information of this item. Limited in length by `StringLimit`. /// /// Emits `CollectionMetadataSet`. /// /// Weight: `O(1)` #[pallet::call_index(26)] #[pallet::weight(T::WeightInfo::set_collection_metadata())] pub fn set_collection_metadata( origin: OriginFor, collection: T::CollectionId, data: BoundedVec, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_set_collection_metadata(maybe_check_origin, collection, data) } /// Clear the metadata for a collection. /// /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Admin of /// the `collection`. /// /// Any deposit is freed for the collection's owner. /// /// - `collection`: The identifier of the collection whose metadata to clear. /// /// Emits `CollectionMetadataCleared`. /// /// Weight: `O(1)` #[pallet::call_index(27)] #[pallet::weight(T::WeightInfo::clear_collection_metadata())] pub fn clear_collection_metadata( origin: OriginFor, collection: T::CollectionId, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_clear_collection_metadata(maybe_check_origin, collection) } /// Set (or reset) the acceptance of ownership for a particular account. /// /// Origin must be `Signed` and if `maybe_collection` is `Some`, then the signer must have a /// provider reference. /// /// - `maybe_collection`: The identifier of the collection whose ownership the signer is /// willing to accept, or if `None`, an indication that the signer is willing to accept no /// ownership transferal. /// /// Emits `OwnershipAcceptanceChanged`. #[pallet::call_index(28)] #[pallet::weight(T::WeightInfo::set_accept_ownership())] pub fn set_accept_ownership( origin: OriginFor, maybe_collection: Option, ) -> DispatchResult { let who = ensure_signed(origin)?; Self::do_set_accept_ownership(who, maybe_collection) } /// Set the maximum number of items a collection could have. /// /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of /// the `collection`. /// /// - `collection`: The identifier of the collection to change. /// - `max_supply`: The maximum number of items a collection could have. /// /// Emits `CollectionMaxSupplySet` event when successful. #[pallet::call_index(29)] #[pallet::weight(T::WeightInfo::set_collection_max_supply())] pub fn set_collection_max_supply( origin: OriginFor, collection: T::CollectionId, max_supply: u32, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_set_collection_max_supply(maybe_check_owner, collection, max_supply) } /// Update mint settings. /// /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Issuer /// of the `collection`. /// /// - `collection`: The identifier of the collection to change. /// - `mint_settings`: The new mint settings. /// /// Emits `CollectionMintSettingsUpdated` event when successful. #[pallet::call_index(30)] #[pallet::weight(T::WeightInfo::update_mint_settings())] pub fn update_mint_settings( origin: OriginFor, collection: T::CollectionId, mint_settings: MintSettings, BlockNumberFor, T::CollectionId>, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_update_mint_settings(maybe_check_origin, collection, mint_settings) } /// Set (or reset) the price for an item. /// /// Origin must be Signed and must be the owner of the `item`. /// /// - `collection`: The collection of the item. /// - `item`: The item to set the price for. /// - `price`: The price for the item. Pass `None`, to reset the price. /// - `buyer`: Restricts the buy operation to a specific account. /// /// Emits `ItemPriceSet` on success if the price is not `None`. /// Emits `ItemPriceRemoved` on success if the price is `None`. #[pallet::call_index(31)] #[pallet::weight(T::WeightInfo::set_price())] pub fn set_price( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, price: Option>, whitelisted_buyer: Option>, ) -> DispatchResult { let origin = ensure_signed(origin)?; let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?; Self::do_set_price(collection, item, origin, price, whitelisted_buyer) } /// Allows to buy an item if it's up for sale. /// /// Origin must be Signed and must not be the owner of the `item`. /// /// - `collection`: The collection of the item. /// - `item`: The item the sender wants to buy. /// - `bid_price`: The price the sender is willing to pay. /// /// Emits `ItemBought` on success. #[pallet::call_index(32)] #[pallet::weight(T::WeightInfo::buy_item())] pub fn buy_item( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, bid_price: ItemPrice, ) -> DispatchResult { let origin = ensure_signed(origin)?; Self::do_buy_item(collection, item, origin, bid_price) } /// Allows to pay the tips. /// /// Origin must be Signed. /// /// - `tips`: Tips array. /// /// Emits `TipSent` on every tip transfer. #[pallet::call_index(33)] #[pallet::weight(T::WeightInfo::pay_tips(tips.len() as u32))] pub fn pay_tips( origin: OriginFor, tips: BoundedVec, T::MaxTips>, ) -> DispatchResult { let origin = ensure_signed(origin)?; Self::do_pay_tips(origin, tips) } /// Register a new atomic swap, declaring an intention to send an `item` in exchange for /// `desired_item` from origin to target on the current blockchain. /// The target can execute the swap during the specified `duration` of blocks (if set). /// Additionally, the price could be set for the desired `item`. /// /// Origin must be Signed and must be an owner of the `item`. /// /// - `collection`: The collection of the item. /// - `item`: The item an owner wants to give. /// - `desired_collection`: The collection of the desired item. /// - `desired_item`: The desired item an owner wants to receive. /// - `maybe_price`: The price an owner is willing to pay or receive for the desired `item`. /// - `duration`: A deadline for the swap. Specified by providing the number of blocks /// after which the swap will expire. /// /// Emits `SwapCreated` on success. #[pallet::call_index(34)] #[pallet::weight(T::WeightInfo::create_swap())] pub fn create_swap( origin: OriginFor, offered_collection: T::CollectionId, offered_item: T::ItemId, desired_collection: T::CollectionId, maybe_desired_item: Option, maybe_price: Option>>, duration: BlockNumberFor, ) -> DispatchResult { let origin = ensure_signed(origin)?; Self::do_create_swap( origin, offered_collection, offered_item, desired_collection, maybe_desired_item, maybe_price, duration, ) } /// Cancel an atomic swap. /// /// Origin must be Signed. /// Origin must be an owner of the `item` if the deadline hasn't expired. /// /// - `collection`: The collection of the item. /// - `item`: The item an owner wants to give. /// /// Emits `SwapCancelled` on success. #[pallet::call_index(35)] #[pallet::weight(T::WeightInfo::cancel_swap())] pub fn cancel_swap( origin: OriginFor, offered_collection: T::CollectionId, offered_item: T::ItemId, ) -> DispatchResult { let origin = ensure_signed(origin)?; Self::do_cancel_swap(origin, offered_collection, offered_item) } /// Claim an atomic swap. /// This method executes a pending swap, that was created by a counterpart before. /// /// Origin must be Signed and must be an owner of the `item`. /// /// - `send_collection`: The collection of the item to be sent. /// - `send_item`: The item to be sent. /// - `receive_collection`: The collection of the item to be received. /// - `receive_item`: The item to be received. /// - `witness_price`: A price that was previously agreed on. /// /// Emits `SwapClaimed` on success. #[pallet::call_index(36)] #[pallet::weight(T::WeightInfo::claim_swap())] pub fn claim_swap( origin: OriginFor, send_collection: T::CollectionId, send_item: T::ItemId, receive_collection: T::CollectionId, receive_item: T::ItemId, witness_price: Option>>, ) -> DispatchResult { let origin = ensure_signed(origin)?; Self::do_claim_swap( origin, send_collection, send_item, receive_collection, receive_item, witness_price, ) } /// Mint an item by providing the pre-signed approval. /// /// Origin must be Signed. /// /// - `mint_data`: The pre-signed approval that consists of the information about the item, /// its metadata, attributes, who can mint it (`None` for anyone) and until what block /// number. /// - `signature`: The signature of the `data` object. /// - `signer`: The `data` object's signer. Should be an Issuer of the collection. /// /// Emits `Issued` on success. /// Emits `AttributeSet` if the attributes were provided. /// Emits `ItemMetadataSet` if the metadata was not empty. #[pallet::call_index(37)] #[pallet::weight(T::WeightInfo::mint_pre_signed(mint_data.attributes.len() as u32))] pub fn mint_pre_signed( origin: OriginFor, mint_data: Box>, signature: T::OffchainSignature, signer: T::AccountId, ) -> DispatchResult { let origin = ensure_signed(origin)?; Self::validate_signature(&Encode::encode(&mint_data), &signature, &signer)?; Self::do_mint_pre_signed(origin, *mint_data, signer) } /// Set attributes for an item by providing the pre-signed approval. /// /// Origin must be Signed and must be an owner of the `data.item`. /// /// - `data`: The pre-signed approval that consists of the information about the item, /// attributes to update and until what block number. /// - `signature`: The signature of the `data` object. /// - `signer`: The `data` object's signer. Should be an Admin of the collection for the /// `CollectionOwner` namespace. /// /// Emits `AttributeSet` for each provided attribute. /// Emits `ItemAttributesApprovalAdded` if the approval wasn't set before. /// Emits `PreSignedAttributesSet` on success. #[pallet::call_index(38)] #[pallet::weight(T::WeightInfo::set_attributes_pre_signed(data.attributes.len() as u32))] pub fn set_attributes_pre_signed( origin: OriginFor, data: PreSignedAttributesOf, signature: T::OffchainSignature, signer: T::AccountId, ) -> DispatchResult { let origin = ensure_signed(origin)?; Self::validate_signature(&Encode::encode(&data), &signature, &signer)?; Self::do_set_attributes_pre_signed(origin, data, signer) } } } sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);