git.delta.rocks / unique-network / refs/commits / cd2930267fbc

difftreelog

Merge pull request #50 from usetech-llc/feature/NFTPAR-250

str-mv2020-12-25parents: #d430fcf #b526a8b.patch.diff
in: master
NFTPAR-250 Tests for collection sponsoring

9 files changed

modifiednode/src/chain_spec.rsdiffbeforeafterboth
--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -193,7 +193,7 @@
 					offchain_schema: vec![],
 					schema_version: SchemaVersion::default(),
                     sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
-                    unconfirmed_sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
+                    sponsor_confirmed: true,
                     const_on_chain_schema: vec![],
 					variable_on_chain_schema: vec![],
 					limits: CollectionLimits::default()
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
before · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18    construct_runtime, decl_event, decl_module, decl_storage, decl_error,19    dispatch::DispatchResult,20    ensure, fail, parameter_types,21    traits::{22        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23        Randomness, WithdrawReason,24    },25    weights::{26        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28        WeightToFeePolynomial,29    },30    IsSubType, StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36    traits::{37        DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38    },39    transaction_validity::{40        TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41    },42    FixedPointOperand, FixedU128,43};44use pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69    Invalid,70    NFT,71    // decimal points72    Fungible(DecimalPoints),73    // decimal points74    ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78    fn into(self) -> u8 {79        match self {80            CollectionMode::Invalid => 0,81            CollectionMode::NFT => 1,82            CollectionMode::Fungible(_) => 2,83            CollectionMode::ReFungible(_) => 3,84        }85    }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91    Normal,92    WhiteList,93}94impl Default for AccessMode {95    fn default() -> Self {96        Self::Normal97    }98}99100impl Default for CollectionMode {101    fn default() -> Self {102        Self::Invalid103    }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109    ImageURL,110    Unique,111}112impl Default for SchemaVersion {113    fn default() -> Self {114        Self::ImageURL115    }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121    pub owner: AccountId,122    pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128    pub owner: AccountId,129    pub mode: CollectionMode,130    pub access: AccessMode,131    pub decimal_points: DecimalPoints,132    pub name: Vec<u16>,        // 64 include null escape char133    pub description: Vec<u16>, // 256 include null escape char134    pub token_prefix: Vec<u8>, // 16 include null escape char135    pub mint_mode: bool,136    pub offchain_schema: Vec<u8>,137    pub schema_version: SchemaVersion,138    pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139    pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship140    pub limits: CollectionLimits, // Collection private restrictions 141    pub variable_on_chain_schema: Vec<u8>, //142    pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148    pub collection: CollectionId,149    pub owner: AccountId,150    pub const_data: Vec<u8>,151    pub variable_data: Vec<u8>,152}153154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]156pub struct FungibleItemType<AccountId> {157    pub collection: CollectionId,158    pub owner: AccountId,159    pub value: u128,160}161162#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]163#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]164pub struct ReFungibleItemType<AccountId> {165    pub collection: CollectionId,166    pub owner: Vec<Ownership<AccountId>>,167    pub const_data: Vec<u8>,168    pub variable_data: Vec<u8>,169}170171#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]172#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]173pub struct ApprovePermissions<AccountId> {174    pub approved: AccountId,175    pub amount: u128,176}177178#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct VestingItem<AccountId, Moment> {181    pub sender: AccountId,182    pub recipient: AccountId,183    pub collection_id: CollectionId,184    pub item_id: TokenId,185    pub amount: u64,186    pub vesting_date: Moment,187}188189#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]190#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]191pub struct BasketItem<AccountId, BlockNumber> {192    pub address: AccountId,193    pub start_block: BlockNumber,194}195196#[derive(Encode, Decode, Debug, Clone, PartialEq)]197#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]198pub struct CollectionLimits {199    pub account_token_ownership_limit: u32,200    pub sponsored_data_size: u32,201    pub token_limit: u32,202203    // Timeouts for item types in passed blocks204    pub sponsor_transfer_timeout: u32,205}206207impl Default for CollectionLimits {208    fn default() -> CollectionLimits {209        CollectionLimits { 210            account_token_ownership_limit: 10_000_000, 211            token_limit: u32::max_value(),212            sponsored_data_size: u32::max_value(), 213            sponsor_transfer_timeout: 14400 }214    }215}216217#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]218#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]219pub struct ChainLimits {220    pub collection_numbers_limit: u32,221    pub account_token_ownership_limit: u32,222    pub collections_admins_limit: u64,223    pub custom_data_limit: u32,224225    // Timeouts for item types in passed blocks226    pub nft_sponsor_transfer_timeout: u32,227    pub fungible_sponsor_transfer_timeout: u32,228    pub refungible_sponsor_transfer_timeout: u32,229}230231pub trait WeightInfo {232	fn create_collection() -> Weight;233	fn destroy_collection() -> Weight;234	fn add_to_white_list() -> Weight;235	fn remove_from_white_list() -> Weight;236    fn set_public_access_mode() -> Weight;237    fn set_mint_permission() -> Weight;238    fn change_collection_owner() -> Weight;239    fn add_collection_admin() -> Weight;240    fn remove_collection_admin() -> Weight;241    fn set_collection_sponsor() -> Weight;242    fn confirm_sponsorship() -> Weight;243    fn remove_collection_sponsor() -> Weight;244    fn create_item(s: usize) -> Weight;245    fn burn_item() -> Weight;246    fn transfer() -> Weight;247    fn approve() -> Weight;248    fn transfer_from() -> Weight;249    fn set_offchain_schema() -> Weight;250    fn set_const_on_chain_schema() -> Weight;251    fn set_variable_on_chain_schema() -> Weight;252    fn set_variable_meta_data() -> Weight;253    fn enable_contract_sponsoring() -> Weight;254}255256#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]257#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]258pub struct CreateNftData {259    pub const_data: Vec<u8>,260    pub variable_data: Vec<u8>,261}262263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]265pub struct CreateFungibleData {266}267268#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]269#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]270pub struct CreateReFungibleData {271    pub const_data: Vec<u8>,272    pub variable_data: Vec<u8>,273}274275#[derive(Encode, Decode, Debug, Clone, PartialEq)]276#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]277pub enum CreateItemData {278    NFT(CreateNftData),279    Fungible(CreateFungibleData),280    ReFungible(CreateReFungibleData),281}282283impl CreateItemData {284    pub fn len(&self) -> usize {285        let len = match self {286            CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),287            CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),288            _ => 0289        };290        291        return len;292    }293}294295impl From<CreateNftData> for CreateItemData {296    fn from(item: CreateNftData) -> Self {297        CreateItemData::NFT(item)298    }299}300301impl From<CreateReFungibleData> for CreateItemData {302    fn from(item: CreateReFungibleData) -> Self {303        CreateItemData::ReFungible(item)304    }305}306307impl From<CreateFungibleData> for CreateItemData {308    fn from(item: CreateFungibleData) -> Self {309        CreateItemData::Fungible(item)310    }311}312313314decl_error! {315	/// Error for non-fungible-token module.316	pub enum Error for Module<T: Trait> {317        /// Total collections bound exceeded.318        TotalCollectionsLimitExceeded,319		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.320        CollectionDecimalPointLimitExceeded, 321        /// Collection name can not be longer than 63 char.322        CollectionNameLimitExceeded, 323        /// Collection description can not be longer than 255 char.324        CollectionDescriptionLimitExceeded, 325        /// Token prefix can not be longer than 15 char.326        CollectionTokenPrefixLimitExceeded,327        /// This collection does not exist.328        CollectionNotFound,329        /// Item not exists.330        TokenNotFound,331        /// Arithmetic calculation overflow.332        NumOverflow,       333        /// Account already has admin role.334        AlreadyAdmin,  335        /// You do not own this collection.336        NoPermission,337        /// This address is not set as sponsor, use setCollectionSponsor first.338        ConfirmUnsetSponsorFail,339        /// Collection is not in mint mode.340        PublicMintingNotAllowed,341        /// Sender parameter and item owner must be equal.342        MustBeTokenOwner,343        /// Item balance not enough.344        TokenValueTooLow,345        /// Size of item is too large.346        NftSizeLimitExceeded,347        /// No approve found348        ApproveNotFound,349        /// Requested value more than approved.350        TokenValueNotEnough,351        /// Only approved addresses can call this method.352        ApproveRequired,353        /// Address is not in white list.354        AddresNotInWhiteList,355        /// Number of collection admins bound exceeded.356        CollectionAdminsLimitExceeded,357        /// Owned tokens by a single address bound exceeded.358        AddressOwnershipLimitExceeded,359        /// Length of items properties must be greater than 0.360        EmptyArgument,361        /// const_data exceeded data limit.362        TokenConstDataLimitExceeded,363        /// variable_data exceeded data limit.364        TokenVariableDataLimitExceeded,365        /// Not NFT item data used to mint in NFT collection.366        NotNftDataUsedToMintNftCollectionToken,367        /// Not Fungible item data used to mint in Fungible collection.368        NotFungibleDataUsedToMintFungibleCollectionToken,369        /// Not Re Fungible item data used to mint in Re Fungible collection.370        NotReFungibleDataUsedToMintReFungibleCollectionToken,371        /// Unexpected collection type.372        UnexpectedCollectionType,373        /// Can't store metadata in fungible tokens.374        CantStoreMetadataInFungibleTokens,375        /// Collection token limit exceeded376        CollectionTokenLimitExceeded,377        /// Account token limit exceeded per collection378        AccountTokenLimitExceeded,379        /// Collection limit bounds per collection exceeded380        CollectionLimitBoundsExceeded381	}382}383384pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {385    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;386387    /// Weight information for extrinsics in this pallet.388	type WeightInfo: WeightInfo;389}390391#[cfg(feature = "runtime-benchmarks")]392mod benchmarking;393394// #endregion395396decl_storage! {397    trait Store for Module<T: Trait> as Nft {398399        // Private members400        NextCollectionID: CollectionId;401        CreatedCollectionCount: u32;402        ChainVersion: u64;403        ItemListIndex: map hasher(identity) CollectionId => TokenId;404405        // Chain limits struct406        pub ChainLimit get(fn chain_limit) config(): ChainLimits;407408        // Bound counters409        CollectionCount: u32;410        pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;411412        // Basic collections413        pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;414        pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;415        pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;416417        /// Balance owner per collection map418        pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;419420        /// second parameter: item id + owner account id421        pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;422423        /// Item collections424        pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;425        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;426        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;427428        /// Index list429        pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;430431        /// Tokens transfer baskets432        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;433        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;434        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;435436        // Contract Sponsorship and Ownership437        pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;438        pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;439        pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;440        pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;441        pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 442        pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 443    }444    add_extra_genesis {445        build(|config: &GenesisConfig<T>| {446            // Modification of storage447            for (_num, _c) in &config.collection {448                <Module<T>>::init_collection(_c);449            }450451            for (_num, _q, _i) in &config.nft_item_id {452                <Module<T>>::init_nft_token(_i);453            }454455            for (_num, _q, _i) in &config.fungible_item_id {456                <Module<T>>::init_fungible_token(_i);457            }458459            for (_num, _q, _i) in &config.refungible_item_id {460                <Module<T>>::init_refungible_token(_i);461            }462        })463    }464}465466decl_event!(467    pub enum Event<T>468    where469        AccountId = <T as system::Trait>::AccountId,470    {471        /// New collection was created472        /// 473        /// # Arguments474        /// 475        /// * collection_id: Globally unique identifier of newly created collection.476        /// 477        /// * mode: [CollectionMode] converted into u8.478        /// 479        /// * account_id: Collection owner.480        Created(CollectionId, u8, AccountId),481482        /// New item was created.483        /// 484        /// # Arguments485        /// 486        /// * collection_id: Id of the collection where item was created.487        /// 488        /// * item_id: Id of an item. Unique within the collection.489        ItemCreated(CollectionId, TokenId),490491        /// Collection item was burned.492        /// 493        /// # Arguments494        /// 495        /// collection_id.496        /// 497        /// item_id: Identifier of burned NFT.498        ItemDestroyed(CollectionId, TokenId),499    }500);501502decl_module! {503    pub struct Module<T: Trait> for enum Call where origin: T::Origin {504505        fn deposit_event() = default;506        type Error = Error<T>;507508        fn on_initialize(now: T::BlockNumber) -> Weight {509510            if ChainVersion::get() < 2511            {512                let value = NextCollectionID::get();513                CreatedCollectionCount::put(value);514                ChainVersion::put(2);515            }516517            0518        }519520        /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.521        /// 522        /// # Permissions523        /// 524        /// * Anyone.525        /// 526        /// # Arguments527        /// 528        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.529        /// 530        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.531        /// 532        /// * token_prefix: UTF-8 string with token prefix.533        /// 534        /// * mode: [CollectionMode] collection type and type dependent data.535        // returns collection ID536        #[weight = T::WeightInfo::create_collection()]537        pub fn create_collection(origin,538                                 collection_name: Vec<u16>,539                                 collection_description: Vec<u16>,540                                 token_prefix: Vec<u8>,541                                 mode: CollectionMode) -> DispatchResult {542543            // Anyone can create a collection544            let who = ensure_signed(origin)?;545546            let decimal_points = match mode {547                CollectionMode::Fungible(points) => points,548                CollectionMode::ReFungible(points) => points,549                _ => 0550            };551552            // bound Total number of collections553            ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);554555            // check params556            ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);557558            let mut name = collection_name.to_vec();559            name.push(0);560            ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);561562            let mut description = collection_description.to_vec();563            description.push(0);564            ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);565566            let mut prefix = token_prefix.to_vec();567            prefix.push(0);568            ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);569570            // Generate next collection ID571            let next_id = CreatedCollectionCount::get()572                .checked_add(1)573                .ok_or(Error::<T>::NumOverflow)?;574575            // bound counter576            let total = CollectionCount::get()577                .checked_add(1)578                .ok_or(Error::<T>::NumOverflow)?;579580            CreatedCollectionCount::put(next_id);581            CollectionCount::put(total);582583            // Create new collection584            let new_collection = CollectionType {585                owner: who.clone(),586                name: name,587                mode: mode.clone(),588                mint_mode: false,589                access: AccessMode::Normal,590                description: description,591                decimal_points: decimal_points,592                token_prefix: prefix,593                offchain_schema: Vec::new(),594                schema_version: SchemaVersion::ImageURL,595                sponsor: T::AccountId::default(),596                unconfirmed_sponsor: T::AccountId::default(),597                variable_on_chain_schema: Vec::new(),598                const_on_chain_schema: Vec::new(),599                limits: CollectionLimits::default(),600            };601602            // Add new collection to map603            <Collection<T>>::insert(next_id, new_collection);604605            // call event606            Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));607608            Ok(())609        }610611        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.612        /// 613        /// # Permissions614        /// 615        /// * Collection Owner.616        /// 617        /// # Arguments618        /// 619        /// * collection_id: collection to destroy.620        #[weight = T::WeightInfo::destroy_collection()]621        pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {622623            let sender = ensure_signed(origin)?;624            Self::check_owner_permissions(collection_id, sender)?;625626            <AddressTokens<T>>::remove_prefix(collection_id);627            <ApprovedList<T>>::remove_prefix(collection_id);628            <Balance<T>>::remove_prefix(collection_id);629            <ItemListIndex>::remove(collection_id);630            <AdminList<T>>::remove(collection_id);631            <Collection<T>>::remove(collection_id);632            <WhiteList<T>>::remove_prefix(collection_id);633634            <NftItemList<T>>::remove_prefix(collection_id);635            <FungibleItemList<T>>::remove_prefix(collection_id);636            <ReFungibleItemList<T>>::remove_prefix(collection_id);637638            <NftTransferBasket<T>>::remove_prefix(collection_id);639            <FungibleTransferBasket<T>>::remove_prefix(collection_id);640            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);641642            if CollectionCount::get() > 0643            {644                // bound couter645                let total = CollectionCount::get()646                    .checked_sub(1)647                    .ok_or(Error::<T>::NumOverflow)?;648649                CollectionCount::put(total);650            }651652            Ok(())653        }654655        /// Add an address to white list.656        /// 657        /// # Permissions658        /// 659        /// * Collection Owner660        /// * Collection Admin661        /// 662        /// # Arguments663        /// 664        /// * collection_id.665        /// 666        /// * address.667        #[weight = T::WeightInfo::add_to_white_list()]668        pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{669670            let sender = ensure_signed(origin)?;671            Self::check_owner_or_admin_permissions(collection_id, sender)?;672673            <WhiteList<T>>::insert(collection_id, address, true);674            675            Ok(())676        }677678        /// Remove an address from white list.679        /// 680        /// # Permissions681        /// 682        /// * Collection Owner683        /// * Collection Admin684        /// 685        /// # Arguments686        /// 687        /// * collection_id.688        /// 689        /// * address.690        #[weight = T::WeightInfo::remove_from_white_list()]691        pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{692693            let sender = ensure_signed(origin)?;694            Self::check_owner_or_admin_permissions(collection_id, sender)?;695696            <WhiteList<T>>::remove(collection_id, address);697698            Ok(())699        }700701        /// Toggle between normal and white list access for the methods with access for `Anyone`.702        /// 703        /// # Permissions704        /// 705        /// * Collection Owner.706        /// 707        /// # Arguments708        /// 709        /// * collection_id.710        /// 711        /// * mode: [AccessMode]712        #[weight = T::WeightInfo::set_public_access_mode()]713        pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult714        {715            let sender = ensure_signed(origin)?;716717            Self::check_owner_permissions(collection_id, sender)?;718            let mut target_collection = <Collection<T>>::get(collection_id);719            target_collection.access = mode;720            <Collection<T>>::insert(collection_id, target_collection);721722            Ok(())723        }724725        /// Allows Anyone to create tokens if:726        /// * White List is enabled, and727        /// * Address is added to white list, and728        /// * This method was called with True parameter729        /// 730        /// # Permissions731        /// * Collection Owner732        ///733        /// # Arguments734        /// 735        /// * collection_id.736        /// 737        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.738        #[weight = T::WeightInfo::set_mint_permission()]739        pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult740        {741            let sender = ensure_signed(origin)?;742743            Self::check_owner_permissions(collection_id, sender)?;744            let mut target_collection = <Collection<T>>::get(collection_id);745            target_collection.mint_mode = mint_permission;746            <Collection<T>>::insert(collection_id, target_collection);747748            Ok(())749        }750751        /// Change the owner of the collection.752        /// 753        /// # Permissions754        /// 755        /// * Collection Owner.756        /// 757        /// # Arguments758        /// 759        /// * collection_id.760        /// 761        /// * new_owner.762        #[weight = T::WeightInfo::change_collection_owner()]763        pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {764765            let sender = ensure_signed(origin)?;766            Self::check_owner_permissions(collection_id, sender)?;767            let mut target_collection = <Collection<T>>::get(collection_id);768            target_collection.owner = new_owner;769            <Collection<T>>::insert(collection_id, target_collection);770771            Ok(())772        }773774        /// Adds an admin of the Collection.775        /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 776        /// 777        /// # Permissions778        /// 779        /// * Collection Owner.780        /// * Collection Admin.781        /// 782        /// # Arguments783        /// 784        /// * collection_id: ID of the Collection to add admin for.785        /// 786        /// * new_admin_id: Address of new admin to add.787        #[weight = T::WeightInfo::add_collection_admin()]788        pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {789790            let sender = ensure_signed(origin)?;791            Self::check_owner_or_admin_permissions(collection_id, sender)?;792            let mut admin_arr: Vec<T::AccountId> = Vec::new();793794            if <AdminList<T>>::contains_key(collection_id)795            {796                admin_arr = <AdminList<T>>::get(collection_id);797                ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);798            }799800            // Number of collection admins801            ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);802803            admin_arr.push(new_admin_id);804            <AdminList<T>>::insert(collection_id, admin_arr);805806            Ok(())807        }808809        /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.810        ///811        /// # Permissions812        /// 813        /// * Collection Owner.814        /// * Collection Admin.815        /// 816        /// # Arguments817        /// 818        /// * collection_id: ID of the Collection to remove admin for.819        /// 820        /// * account_id: Address of admin to remove.821        #[weight = T::WeightInfo::remove_collection_admin()]822        pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {823824            let sender = ensure_signed(origin)?;825            Self::check_owner_or_admin_permissions(collection_id, sender)?;826827            if <AdminList<T>>::contains_key(collection_id)828            {829                let mut admin_arr = <AdminList<T>>::get(collection_id);830                admin_arr.retain(|i| *i != account_id);831                <AdminList<T>>::insert(collection_id, admin_arr);832            }833834            Ok(())835        }836837        /// # Permissions838        /// 839        /// * Collection Owner840        /// 841        /// # Arguments842        /// 843        /// * collection_id.844        /// 845        /// * new_sponsor.846        #[weight = T::WeightInfo::set_collection_sponsor()]847        pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {848849            let sender = ensure_signed(origin)?;850            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);851852            let mut target_collection = <Collection<T>>::get(collection_id);853            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);854855            target_collection.unconfirmed_sponsor = new_sponsor;856            <Collection<T>>::insert(collection_id, target_collection);857858            Ok(())859        }860861        /// # Permissions862        /// 863        /// * Sponsor.864        /// 865        /// # Arguments866        /// 867        /// * collection_id.868        #[weight = T::WeightInfo::confirm_sponsorship()]869        pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {870871            let sender = ensure_signed(origin)?;872            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);873874            let mut target_collection = <Collection<T>>::get(collection_id);875            ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);876877            target_collection.sponsor = target_collection.unconfirmed_sponsor;878            target_collection.unconfirmed_sponsor = T::AccountId::default();879            <Collection<T>>::insert(collection_id, target_collection);880881            Ok(())882        }883884        /// Switch back to pay-per-own-transaction model.885        ///886        /// # Permissions887        ///888        /// * Collection owner.889        /// 890        /// # Arguments891        /// 892        /// * collection_id.893        #[weight = T::WeightInfo::remove_collection_sponsor()]894        pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {895896            let sender = ensure_signed(origin)?;897            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);898899            let mut target_collection = <Collection<T>>::get(collection_id);900            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);901902            target_collection.sponsor = T::AccountId::default();903            <Collection<T>>::insert(collection_id, target_collection);904905            Ok(())906        }907908        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.909        /// 910        /// # Permissions911        /// 912        /// * Collection Owner.913        /// * Collection Admin.914        /// * Anyone if915        ///     * White List is enabled, and916        ///     * Address is added to white list, and917        ///     * MintPermission is enabled (see SetMintPermission method)918        /// 919        /// # Arguments920        /// 921        /// * collection_id: ID of the collection.922        /// 923        /// * owner: Address, initial owner of the NFT.924        ///925        /// * data: Token data to store on chain.926        // #[weight =927        // (130_000_000 as Weight)928        // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))929        // .saturating_add(RocksDbWeight::get().reads(10 as Weight))930        // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]931932        #[weight = T::WeightInfo::create_item(data.len())]933        pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {934935            let sender = ensure_signed(origin)?;936937            Self::collection_exists(collection_id)?;938939            let target_collection = <Collection<T>>::get(collection_id);940941            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;942            Self::validate_create_item_args(&target_collection, &data)?;943            Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;944945            Ok(())946        }947948        /// This method creates multiple instances of NFT Collection created with CreateCollection method.949        /// 950        /// # Permissions951        /// 952        /// * Collection Owner.953        /// * Collection Admin.954        /// * Anyone if955        ///     * White List is enabled, and956        ///     * Address is added to white list, and957        ///     * MintPermission is enabled (see SetMintPermission method)958        /// 959        /// # Arguments960        /// 961        /// * collection_id: ID of the collection.962        /// 963        /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].964        /// 965        /// * owner: Address, initial owner of the NFT.966        #[weight = T::WeightInfo::create_item(items_data.into_iter()967                               .map(|data| { data.len() })968                               .sum())]969        pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {970971            ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);972            let sender = ensure_signed(origin)?;973974            Self::collection_exists(collection_id)?;975            let target_collection = <Collection<T>>::get(collection_id);976977            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;978979            for data in &items_data {980                Self::validate_create_item_args(&target_collection, data)?;981            }982            for data in &items_data {983                Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;984            }985986            Ok(())987        }988989        /// Destroys a concrete instance of NFT.990        /// 991        /// # Permissions992        /// 993        /// * Collection Owner.994        /// * Collection Admin.995        /// * Current NFT Owner.996        /// 997        /// # Arguments998        /// 999        /// * collection_id: ID of the collection.1000        /// 1001        /// * item_id: ID of NFT to burn.1002        #[weight = T::WeightInfo::burn_item()]1003        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {10041005            let sender = ensure_signed(origin)?;1006            Self::collection_exists(collection_id)?;10071008            // Transfer permissions check1009            let target_collection = <Collection<T>>::get(collection_id);1010            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1011                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1012                Error::<T>::NoPermission);10131014            if target_collection.access == AccessMode::WhiteList {1015                Self::check_white_list(collection_id, &sender)?;1016            }10171018            match target_collection.mode1019            {1020                CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1021                CollectionMode::Fungible(_)  => Self::burn_fungible_item(collection_id, item_id)?,1022                CollectionMode::ReFungible(_)  => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1023                _ => ()1024            };10251026            // call event1027            Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10281029            Ok(())1030        }10311032        /// Change ownership of the token.1033        /// 1034        /// # Permissions1035        /// 1036        /// * Collection Owner1037        /// * Collection Admin1038        /// * Current NFT owner1039        ///1040        /// # Arguments1041        /// 1042        /// * recipient: Address of token recipient.1043        /// 1044        /// * collection_id.1045        /// 1046        /// * item_id: ID of the item1047        ///     * Non-Fungible Mode: Required.1048        ///     * Fungible Mode: Ignored.1049        ///     * Re-Fungible Mode: Required.1050        /// 1051        /// * value: Amount to transfer.1052        ///     * Non-Fungible Mode: Ignored1053        ///     * Fungible Mode: Must specify transferred amount1054        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1055        #[weight = T::WeightInfo::transfer()]1056        pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10571058            let sender = ensure_signed(origin)?;1059            let target_collection = <Collection<T>>::get(collection_id);10601061            // Limits check1062            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10631064            // Transfer permissions check1065            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1066                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1067                Error::<T>::NoPermission);10681069            if target_collection.access == AccessMode::WhiteList {1070                Self::check_white_list(collection_id, &sender)?;1071                Self::check_white_list(collection_id, &recipient)?;1072            }10731074            match target_collection.mode1075            {1076                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1077                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1078                CollectionMode::ReFungible(_)  => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1079                _ => ()1080            };10811082            Ok(())1083        }10841085        /// Set, change, or remove approved address to transfer the ownership of the NFT.1086        /// 1087        /// # Permissions1088        /// 1089        /// * Collection Owner1090        /// * Collection Admin1091        /// * Current NFT owner1092        /// 1093        /// # Arguments1094        /// 1095        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1096        /// 1097        /// * collection_id.1098        /// 1099        /// * item_id: ID of the item.1100        #[weight = T::WeightInfo::approve()]1101        pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {11021103            let sender = ensure_signed(origin)?;11041105            // Transfer permissions check1106            let target_collection = <Collection<T>>::get(collection_id);1107            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1108                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1109                Error::<T>::NoPermission);11101111            if target_collection.access == AccessMode::WhiteList {1112                Self::check_white_list(collection_id, &sender)?;1113                Self::check_white_list(collection_id, &approved)?;1114            }11151116            // amount param stub1117            let amount = 100000000;11181119            let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));1120            if list_exists {11211122                let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));1123                let item_contains = list.iter().any(|i| i.approved == approved);11241125                if !item_contains {1126                    list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1127                    <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1128                }1129            } else {11301131                let mut list = Vec::new();1132                list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1133                <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);1134            }11351136            Ok(())1137        }1138        1139        /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1140        /// 1141        /// # Permissions1142        /// * Collection Owner1143        /// * Collection Admin1144        /// * Current NFT owner1145        /// * Address approved by current NFT owner1146        /// 1147        /// # Arguments1148        /// 1149        /// * from: Address that owns token.1150        /// 1151        /// * recipient: Address of token recipient.1152        /// 1153        /// * collection_id.1154        /// 1155        /// * item_id: ID of the item.1156        /// 1157        /// * value: Amount to transfer.1158        #[weight = T::WeightInfo::transfer_from()]1159        pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11601161            let sender = ensure_signed(origin)?;1162            let mut appoved_transfer = false;11631164            // Check approve1165            if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1166                let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1167                let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());1168                if opt_item.is_some()1169                {1170                    appoved_transfer = true;1171                    ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);1172                }1173            }11741175            let target_collection = <Collection<T>>::get(collection_id);11761177            // Limits check1178            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11791180            // Transfer permissions check         1181            ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1182            Error::<T>::NoPermission);11831184            if target_collection.access == AccessMode::WhiteList {1185                Self::check_white_list(collection_id, &sender)?;1186                Self::check_white_list(collection_id, &recipient)?;1187            }11881189            // remove approve1190            let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1191                .into_iter().filter(|i| i.approved != sender.clone()).collect();1192            <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);119311941195            match target_collection.mode1196            {1197                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1198                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1199                CollectionMode::ReFungible(_)  => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1200                _ => ()1201            };12021203            Ok(())1204        }12051206        #[weight = 0]1207        pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {12081209            // let no_perm_mes = "You do not have permissions to modify this collection";1210            // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1211            // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1212            // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);12131214            // // on_nft_received  call12151216            // Self::transfer(origin, collection_id, item_id, new_owner)?;12171218            Ok(())1219        }12201221        /// Set off-chain data schema.1222        /// 1223        /// # Permissions1224        /// 1225        /// * Collection Owner1226        /// * Collection Admin1227        /// 1228        /// # Arguments1229        /// 1230        /// * collection_id.1231        /// 1232        /// * schema: String representing the offchain data schema.1233        #[weight = T::WeightInfo::set_variable_meta_data()]1234        pub fn set_variable_meta_data (1235            origin,1236            collection_id: CollectionId,1237            item_id: TokenId,1238            data: Vec<u8>1239        ) -> DispatchResult {1240            let sender = ensure_signed(origin)?;1241            1242            Self::collection_exists(collection_id)?;1243            1244            ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12451246            // Modify permissions check1247            let target_collection = <Collection<T>>::get(collection_id);1248            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1249                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1250                Error::<T>::NoPermission);12511252            Self::item_exists(collection_id, item_id, &target_collection.mode)?;12531254            match target_collection.mode1255            {1256                CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1257                CollectionMode::ReFungible(_)  => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1258                CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1259                _ => fail!(Error::<T>::UnexpectedCollectionType)1260            };12611262            Ok(())1263        }1264 1265        /// Set schema standard1266        /// ImageURL1267        /// Unique1268        /// 1269        /// # Permissions1270        /// 1271        /// * Collection Owner1272        /// * Collection Admin1273        /// 1274        /// # Arguments1275        /// 1276        /// * collection_id.1277        /// 1278        /// * schema: SchemaVersion: enum1279        #[weight = 0]1280        pub fn set_schema_version(1281            origin,1282            collection_id: CollectionId,1283            version: SchemaVersion1284        ) -> DispatchResult {1285            let sender = ensure_signed(origin)?;1286            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1287            let mut target_collection = <Collection<T>>::get(collection_id);1288            target_collection.schema_version = version;1289            <Collection<T>>::insert(collection_id, target_collection);12901291            Ok(())1292        }12931294        /// Set off-chain data schema.1295        /// 1296        /// # Permissions1297        /// 1298        /// * Collection Owner1299        /// * Collection Admin1300        /// 1301        /// # Arguments1302        /// 1303        /// * collection_id.1304        /// 1305        /// * schema: String representing the offchain data schema.1306        #[weight = T::WeightInfo::set_offchain_schema()]1307        pub fn set_offchain_schema(1308            origin,1309            collection_id: CollectionId,1310            schema: Vec<u8>1311        ) -> DispatchResult {1312            let sender = ensure_signed(origin)?;1313            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13141315            let mut target_collection = <Collection<T>>::get(collection_id);1316            target_collection.offchain_schema = schema;1317            <Collection<T>>::insert(collection_id, target_collection);13181319            Ok(())1320        }13211322        /// Set const on-chain data schema.1323        /// 1324        /// # Permissions1325        /// 1326        /// * Collection Owner1327        /// * Collection Admin1328        /// 1329        /// # Arguments1330        /// 1331        /// * collection_id.1332        /// 1333        /// * schema: String representing the const on-chain data schema.1334        #[weight = T::WeightInfo::set_const_on_chain_schema()]1335        pub fn set_const_on_chain_schema (1336            origin,1337            collection_id: CollectionId,1338            schema: Vec<u8>1339        ) -> DispatchResult {1340            let sender = ensure_signed(origin)?;1341            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13421343            let mut target_collection = <Collection<T>>::get(collection_id);1344            target_collection.const_on_chain_schema = schema;1345            <Collection<T>>::insert(collection_id, target_collection);13461347            Ok(())1348        }13491350        /// Set variable on-chain data schema.1351        /// 1352        /// # Permissions1353        /// 1354        /// * Collection Owner1355        /// * Collection Admin1356        /// 1357        /// # Arguments1358        /// 1359        /// * collection_id.1360        /// 1361        /// * schema: String representing the variable on-chain data schema.1362        #[weight = T::WeightInfo::set_const_on_chain_schema()]1363        pub fn set_variable_on_chain_schema (1364            origin,1365            collection_id: CollectionId,1366            schema: Vec<u8>1367        ) -> DispatchResult {1368            let sender = ensure_signed(origin)?;1369            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13701371            let mut target_collection = <Collection<T>>::get(collection_id);1372            target_collection.variable_on_chain_schema = schema;1373            <Collection<T>>::insert(collection_id, target_collection);13741375            Ok(())1376        }13771378        // Sudo permissions function1379        #[weight = 0]1380        pub fn set_chain_limits(1381            origin,1382            limits: ChainLimits1383        ) -> DispatchResult {1384            ensure_root(origin)?;1385            <ChainLimit>::put(limits);1386            Ok(())1387        }13881389        /// Enable smart contract self-sponsoring.1390        /// 1391        /// # Permissions1392        /// 1393        /// * Contract Owner1394        /// 1395        /// # Arguments1396        /// 1397        /// * contract address1398        /// * enable flag1399        /// 1400        #[weight = T::WeightInfo::enable_contract_sponsoring()]1401        pub fn enable_contract_sponsoring(1402            origin,1403            contract_address: T::AccountId,1404            enable: bool1405        ) -> DispatchResult {14061407            let sender = ensure_signed(origin)?;14081409            #[cfg(feature = "runtime-benchmarks")]1410            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());14111412            Self::ensure_contract_owned(sender, &contract_address)?;14131414            <ContractSelfSponsoring<T>>::insert(contract_address, enable);1415            Ok(())1416        }14171418        /// Set the rate limit for contract sponsoring to specified number of blocks.1419        /// 1420        /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1421        /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1422        /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1423        /// from contract endowment if there are at least B blocks between such transactions. 1424        /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1425        /// 1426        /// # Permissions1427        /// 1428        /// * Contract Owner1429        /// 1430        /// # Arguments1431        /// 1432        /// -`contract_address`: Address of the contract to sponsor1433        /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1434        /// 1435        #[weight = 0]1436        pub fn set_contract_sponsoring_rate_limit(1437            origin,1438            contract_address: T::AccountId,1439            rate_limit: T::BlockNumber1440        ) -> DispatchResult {1441            let sender = ensure_signed(origin)?;1442            Self::ensure_contract_owned(sender, &contract_address)?;14431444            <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1445            Ok(())1446        }14471448        /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1449        /// 1450        /// # Permissions1451        /// 1452        /// * Address that deployed smart contract.1453        /// 1454        /// # Arguments1455        /// 1456        /// -`contract_address`: Address of the contract.1457        /// 1458        /// - `enable`: .  1459        #[weight = 0]1460        pub fn toggle_contract_white_list(1461            origin,1462            contract_address: T::AccountId,1463            enable: bool1464        ) -> DispatchResult {1465            let sender = ensure_signed(origin)?;1466            Self::ensure_contract_owned(sender, &contract_address)?;14671468            <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1469            Ok(())1470        }1471        1472        /// Add an address to smart contract white list.1473        /// 1474        /// # Permissions1475        /// 1476        /// * Address that deployed smart contract.1477        /// 1478        /// # Arguments1479        /// 1480        /// -`contract_address`: Address of the contract.1481        ///1482        /// -`account_address`: Address to add.1483        #[weight = 0]1484        pub fn add_to_contract_white_list(1485            origin,1486            contract_address: T::AccountId,1487            account_address: T::AccountId1488        ) -> DispatchResult {1489            let sender = ensure_signed(origin)?;1490            Self::ensure_contract_owned(sender, &contract_address)?;1491            1492            <ContractWhiteList<T>>::insert(contract_address, account_address, true);1493            Ok(())1494        }14951496        /// Remove an address from smart contract white list.1497        /// 1498        /// # Permissions1499        /// 1500        /// * Address that deployed smart contract.1501        /// 1502        /// # Arguments1503        /// 1504        /// -`contract_address`: Address of the contract.1505        ///1506        /// -`account_address`: Address to remove.1507        #[weight = 0]1508        pub fn remove_from_contract_white_list(1509            origin,1510            contract_address: T::AccountId,1511            account_address: T::AccountId1512        ) -> DispatchResult {1513            let sender = ensure_signed(origin)?;1514            Self::ensure_contract_owned(sender, &contract_address)?;1515            1516            <ContractWhiteList<T>>::remove(contract_address, account_address);1517            Ok(())1518        }15191520        #[weight = 0]1521        pub fn set_collection_limits(1522            origin,1523            collection_id: u32,1524            limits: CollectionLimits,1525        ) -> DispatchResult {1526            let sender = ensure_signed(origin)?;1527            Self::check_owner_permissions(collection_id, sender.clone())?;1528            let mut target_collection = <Collection<T>>::get(collection_id);1529            let chain_limits = ChainLimit::get();1530            let climits = target_collection.limits;15311532            // collection bounds1533            ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1534                limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP,  1535                Error::<T>::CollectionLimitBoundsExceeded);15361537            // token_limit   check  prev1538            ensure!(climits.token_limit > limits.token_limit && 1539                limits.token_limit <= chain_limits.account_token_ownership_limit, 1540                Error::<T>::AccountTokenLimitExceeded);15411542            target_collection.limits = limits;1543            <Collection<T>>::insert(collection_id, target_collection);15441545            Ok(())1546        } 1547    }1548}15491550impl<T: Trait> Module<T> {15511552    fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15531554        // check token limit and account token limit1555        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1556        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);1557        1558        Ok(())1559    }15601561    fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15621563        // check token limit and account token limit1564        let total_items: u32 = ItemListIndex::get(collection_id);1565        let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1566        ensure!(collection.limits.token_limit > total_items,  Error::<T>::CollectionTokenLimitExceeded);1567        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);15681569        if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1570            ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1571            Self::check_white_list(collection_id, owner)?;1572            Self::check_white_list(collection_id, sender)?;1573        }15741575        Ok(())1576    }15771578    fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1579        match target_collection.mode1580        {1581            CollectionMode::NFT => {1582                if let CreateItemData::NFT(data) = data {1583                    // check sizes1584                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1585                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1586                } else {1587                    fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1588                }1589            },1590            CollectionMode::Fungible(_) => {1591                if let CreateItemData::Fungible(_) = data {1592                } else {1593                    fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1594                }1595            },1596            CollectionMode::ReFungible(_) => {1597                if let CreateItemData::ReFungible(data) = data {15981599                    // check sizes1600                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1601                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1602                } else {1603                    fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1604                }1605            },1606            _ => { fail!(Error::<T>::UnexpectedCollectionType); }1607        };16081609        Ok(())1610    }16111612    fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1613        match data1614        {1615            CreateItemData::NFT(data) => {1616                let item = NftItemType {1617                    collection: collection_id,1618                    owner,1619                    const_data: data.const_data,1620                    variable_data: data.variable_data1621                };16221623                Self::add_nft_item(item)?;1624            },1625            CreateItemData::Fungible(_) => {1626                let item = FungibleItemType {1627                    collection: collection_id,1628                    owner,1629                    value: (10 as u128).pow(collection.decimal_points as u32)1630                };16311632                Self::add_fungible_item(item)?;1633            },1634            CreateItemData::ReFungible(data) => {1635                let mut owner_list = Vec::new();1636                let value = (10 as u128).pow(collection.decimal_points as u32);1637                owner_list.push(Ownership {owner: owner.clone(), fraction: value});16381639                let item = ReFungibleItemType {1640                    collection: collection_id,1641                    owner: owner_list,1642                    const_data: data.const_data,1643                    variable_data: data.variable_data1644                };16451646                Self::add_refungible_item(item)?;1647            }1648        };16491650        // call event1651        Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16521653        Ok(())1654    }16551656    fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1657        let current_index = <ItemListIndex>::get(item.collection)1658            .checked_add(1)1659            .ok_or(Error::<T>::NumOverflow)?;1660        let itemcopy = item.clone();1661        let owner = item.owner.clone();16621663        Self::add_token_index(item.collection, current_index, owner.clone())?;16641665        <ItemListIndex>::insert(item.collection, current_index);1666        <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16671668        // Add current block1669        let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();1670        <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);1671        1672        // Update balance1673        let new_balance = <Balance<T>>::get(item.collection, owner.clone())1674            .checked_add(item.value)1675            .ok_or(Error::<T>::NumOverflow)?;1676        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);16771678        Ok(())1679    }16801681    fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1682        let current_index = <ItemListIndex>::get(item.collection)1683            .checked_add(1)1684            .ok_or(Error::<T>::NumOverflow)?;1685        let itemcopy = item.clone();16861687        let value = item.owner.first().unwrap().fraction;1688        let owner = item.owner.first().unwrap().owner.clone();16891690        Self::add_token_index(item.collection, current_index, owner.clone())?;16911692        <ItemListIndex>::insert(item.collection, current_index);1693        <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);16941695        // Add current block1696        let block_number: T::BlockNumber = 0.into();1697        <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);16981699        // Update balance1700        let new_balance = <Balance<T>>::get(item.collection, owner.clone())1701            .checked_add(value)1702            .ok_or(Error::<T>::NumOverflow)?;1703        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);17041705        Ok(())1706    }17071708    fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1709        let current_index = <ItemListIndex>::get(item.collection)1710            .checked_add(1)1711            .ok_or(Error::<T>::NumOverflow)?;17121713        let item_owner = item.owner.clone();1714        let collection_id = item.collection.clone();1715        Self::add_token_index(collection_id, current_index, item.owner.clone())?;17161717        <ItemListIndex>::insert(collection_id, current_index);1718        <NftItemList<T>>::insert(collection_id, current_index, item);17191720        // Add current block1721        let block_number: T::BlockNumber = 0.into();1722        <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);17231724        // Update balance1725        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1726            .checked_add(1)1727            .ok_or(Error::<T>::NumOverflow)?;1728        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);17291730        Ok(())1731    }17321733    fn burn_refungible_item(1734        collection_id: CollectionId,1735        item_id: TokenId,1736        owner: T::AccountId,1737    ) -> DispatchResult {1738        ensure!(1739            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1740            Error::<T>::TokenNotFound1741        );1742        let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1743        let item = collection1744            .owner1745            .iter()1746            .filter(|&i| i.owner == owner)1747            .next()1748            .unwrap();1749        Self::remove_token_index(collection_id, item_id, owner.clone())?;17501751        // remove approve list1752        <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));17531754        // update balance1755        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1756            .checked_sub(item.fraction)1757            .ok_or(Error::<T>::NumOverflow)?;1758        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17591760        <ReFungibleItemList<T>>::remove(collection_id, item_id);17611762        Ok(())1763    }17641765    fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1766        ensure!(1767            <NftItemList<T>>::contains_key(collection_id, item_id),1768            Error::<T>::TokenNotFound1769        );1770        let item = <NftItemList<T>>::get(collection_id, item_id);1771        Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17721773        // remove approve list1774        <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17751776        // update balance1777        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1778            .checked_sub(1)1779            .ok_or(Error::<T>::NumOverflow)?;1780        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1781        <NftItemList<T>>::remove(collection_id, item_id);17821783        Ok(())1784    }17851786    fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1787        ensure!(1788            <FungibleItemList<T>>::contains_key(collection_id, item_id),1789            Error::<T>::TokenNotFound1790        );1791        let item = <FungibleItemList<T>>::get(collection_id, item_id);1792        Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17931794        // remove approve list1795        <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));17961797        // update balance1798        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1799            .checked_sub(item.value)1800            .ok_or(Error::<T>::NumOverflow)?;1801        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);18021803        <FungibleItemList<T>>::remove(collection_id, item_id);18041805        Ok(())1806    }18071808    fn collection_exists(collection_id: CollectionId) -> DispatchResult {1809        ensure!(1810            <Collection<T>>::contains_key(collection_id),1811            Error::<T>::CollectionNotFound1812        );1813        Ok(())1814    }18151816    fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1817        Self::collection_exists(collection_id)?;18181819        let target_collection = <Collection<T>>::get(collection_id);1820        ensure!(1821            subject == target_collection.owner,1822            Error::<T>::NoPermission1823        );18241825        Ok(())1826    }18271828    fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1829        let target_collection = <Collection<T>>::get(collection_id);1830        let mut result: bool = subject == target_collection.owner;1831        let exists = <AdminList<T>>::contains_key(collection_id);18321833        if !result & exists {1834            if <AdminList<T>>::get(collection_id).contains(&subject) {1835                result = true1836            }1837        }18381839        result1840    }18411842    fn check_owner_or_admin_permissions(1843        collection_id: CollectionId,1844        subject: T::AccountId,1845    ) -> DispatchResult {1846        Self::collection_exists(collection_id)?;1847        let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());18481849        ensure!(1850            result,1851            Error::<T>::NoPermission1852        );1853        Ok(())1854    }18551856    fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1857        let target_collection = <Collection<T>>::get(collection_id);18581859        match target_collection.mode {1860            CollectionMode::NFT => {1861                <NftItemList<T>>::get(collection_id, item_id).owner == subject1862            }1863            CollectionMode::Fungible(_) => {1864                <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1865            }1866            CollectionMode::ReFungible(_) => {1867                <ReFungibleItemList<T>>::get(collection_id, item_id)1868                    .owner1869                    .iter()1870                    .any(|i| i.owner == subject)1871            }1872            CollectionMode::Invalid => false,1873        }1874    }18751876    fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1877        let mes = Error::<T>::AddresNotInWhiteList;1878        ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18791880        Ok(())1881    }18821883    fn transfer_fungible(1884        collection_id: CollectionId,1885        item_id: TokenId,1886        value: u128,1887        owner: T::AccountId,1888        new_owner: T::AccountId,1889    ) -> DispatchResult {1890        ensure!(1891            <FungibleItemList<T>>::contains_key(collection_id, item_id),1892            Error::<T>::TokenNotFound1893        );18941895        let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1896        let amount = full_item.value;18971898        ensure!(amount >= value, Error::<T>::TokenValueTooLow);18991900        // update balance1901        let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1902            .checked_sub(value)1903            .ok_or(Error::<T>::NumOverflow)?;1904        <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);19051906        let mut new_owner_account_id = 0;1907        let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1908        if new_owner_items.len() > 0 {1909            new_owner_account_id = new_owner_items[0];1910        }19111912        // transfer1913        if amount == value && new_owner_account_id == 0 {1914            // change owner1915            // new owner do not have account1916            let mut new_full_item = full_item.clone();1917            new_full_item.owner = new_owner.clone();1918            <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19191920            // update balance1921            let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1922                .checked_add(value)1923                .ok_or(Error::<T>::NumOverflow)?;1924            <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19251926            // update index collection1927            Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;1928        } else {1929            let mut new_full_item = full_item.clone();1930            new_full_item.value -= value;19311932            // separate amount1933            if new_owner_account_id > 0 {1934                // new owner has account1935                let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);1936                item.value += value;19371938                // update balance1939                let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1940                    .checked_add(value)1941                    .ok_or(Error::<T>::NumOverflow)?;1942                <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);19431944                <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);1945            } else {1946                // new owner do not have account1947                let item = FungibleItemType {1948                    collection: collection_id,1949                    owner: new_owner.clone(),1950                    value1951                };19521953                Self::add_fungible_item(item)?;1954            }19551956            if amount == value {1957                Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;19581959                // remove approve list1960                <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));1961                <FungibleItemList<T>>::remove(collection_id, item_id);1962            }19631964            <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1965        }19661967        Ok(())1968    }19691970    fn transfer_refungible(1971        collection_id: CollectionId,1972        item_id: TokenId,1973        value: u128,1974        owner: T::AccountId,1975        new_owner: T::AccountId,1976    ) -> DispatchResult {1977        ensure!(1978            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1979            Error::<T>::TokenNotFound1980        );19811982        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1983        let item = full_item1984            .owner1985            .iter()1986            .filter(|i| i.owner == owner)1987            .next()1988            .ok_or(Error::<T>::NumOverflow)?;1989        let amount = item.fraction;19901991        ensure!(amount >= value, Error::<T>::TokenValueTooLow);19921993        // update balance1994        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1995            .checked_sub(value)1996            .ok_or(Error::<T>::NumOverflow)?;1997        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19981999        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2000            .checked_add(value)2001            .ok_or(Error::<T>::NumOverflow)?;2002        <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);20032004        let old_owner = item.owner.clone();2005        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);20062007        // transfer2008        if amount == value && !new_owner_has_account {2009            // change owner2010            // new owner do not have account2011            let mut new_full_item = full_item.clone();2012            new_full_item2013                .owner2014                .iter_mut()2015                .find(|i| i.owner == owner)2016                .unwrap()2017                .owner = new_owner.clone();2018            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);20192020            // update index collection2021            Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;2022        } else {2023            let mut new_full_item = full_item.clone();2024            new_full_item2025                .owner2026                .iter_mut()2027                .find(|i| i.owner == owner)2028                .unwrap()2029                .fraction -= value;20302031            // separate amount2032            if new_owner_has_account {2033                // new owner has account2034                new_full_item2035                    .owner2036                    .iter_mut()2037                    .find(|i| i.owner == new_owner)2038                    .unwrap()2039                    .fraction += value;2040            } else {2041                // new owner do not have account2042                new_full_item.owner.push(Ownership {2043                    owner: new_owner.clone(),2044                    fraction: value,2045                });2046                Self::add_token_index(collection_id, item_id, new_owner.clone())?;2047            }20482049            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);2050        }20512052        Ok(())2053    }20542055    fn transfer_nft(2056        collection_id: CollectionId,2057        item_id: TokenId,2058        sender: T::AccountId,2059        new_owner: T::AccountId,2060    ) -> DispatchResult {2061        ensure!(2062            <NftItemList<T>>::contains_key(collection_id, item_id),2063            Error::<T>::TokenNotFound2064        );20652066        let mut item = <NftItemList<T>>::get(collection_id, item_id);20672068        ensure!(2069            sender == item.owner,2070            Error::<T>::MustBeTokenOwner2071        );20722073        // update balance2074        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())2075            .checked_sub(1)2076            .ok_or(Error::<T>::NumOverflow)?;2077        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);20782079        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())2080            .checked_add(1)2081            .ok_or(Error::<T>::NumOverflow)?;2082        <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);20832084        // change owner2085        let old_owner = item.owner.clone();2086        item.owner = new_owner.clone();2087        <NftItemList<T>>::insert(collection_id, item_id, item);20882089        // update index collection2090        Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;20912092        // reset approved list2093        <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));2094        Ok(())2095    }2096    2097    fn item_exists(2098        collection_id: CollectionId,2099        item_id: TokenId,2100        mode: &CollectionMode2101    ) -> DispatchResult {2102        match mode {2103            CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2104            CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2105            CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),2106            _ => ()2107        };2108        2109        Ok(())2110    }21112112    fn set_re_fungible_variable_data(2113        collection_id: CollectionId,2114        item_id: TokenId,2115        data: Vec<u8>2116    ) -> DispatchResult {2117        let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);21182119        item.variable_data = data;21202121        <ReFungibleItemList<T>>::insert(collection_id, item_id, item);21222123        Ok(())2124    }21252126    fn set_nft_variable_data(2127        collection_id: CollectionId,2128        item_id: TokenId,2129        data: Vec<u8>2130    ) -> DispatchResult {2131        let mut item = <NftItemList<T>>::get(collection_id, item_id);2132        2133        item.variable_data = data;21342135        <NftItemList<T>>::insert(collection_id, item_id, item);2136        2137        Ok(())2138    }21392140    fn init_collection(item: &CollectionType<T::AccountId>) {2141        // check params2142        assert!(2143            item.decimal_points <= MAX_DECIMAL_POINTS,2144            "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2145        );2146        assert!(2147            item.name.len() <= 64,2148            "Collection name can not be longer than 63 char"2149        );2150        assert!(2151            item.name.len() <= 256,2152            "Collection description can not be longer than 255 char"2153        );2154        assert!(2155            item.token_prefix.len() <= 16,2156            "Token prefix can not be longer than 15 char"2157        );21582159        // Generate next collection ID2160        let next_id = CreatedCollectionCount::get()2161            .checked_add(1)2162            .unwrap();21632164        CreatedCollectionCount::put(next_id);2165    }21662167    fn init_nft_token(item: &NftItemType<T::AccountId>) {2168        let current_index = <ItemListIndex>::get(item.collection)2169            .checked_add(1)2170            .unwrap();21712172        let item_owner = item.owner.clone();2173        let collection_id = item.collection.clone();2174        Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();21752176        <ItemListIndex>::insert(collection_id, current_index);21772178        // Update balance2179        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2180            .checked_add(1)2181            .unwrap();2182        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2183    }21842185    fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2186        let current_index = <ItemListIndex>::get(item.collection)2187            .checked_add(1)2188            .unwrap();2189        let owner = item.owner.clone();21902191        Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();21922193        <ItemListIndex>::insert(item.collection, current_index);21942195        // Update balance2196        let new_balance = <Balance<T>>::get(item.collection, owner.clone())2197            .checked_add(item.value)2198            .unwrap();2199        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2200    }22012202    fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2203        let current_index = <ItemListIndex>::get(item.collection)2204            .checked_add(1)2205            .unwrap();22062207        let value = item.owner.first().unwrap().fraction;2208        let owner = item.owner.first().unwrap().owner.clone();22092210        Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();22112212        <ItemListIndex>::insert(item.collection, current_index);22132214        // Update balance2215        let new_balance = <Balance<T>>::get(item.collection, owner.clone())2216            .checked_add(value)2217            .unwrap();2218        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2219    }22202221    fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {22222223        // add to account limit2224        if <AccountItemCount<T>>::contains_key(owner.clone()) {22252226            // bound Owned tokens by a single address2227            let count = <AccountItemCount<T>>::get(owner.clone());2228            ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);22292230            <AccountItemCount<T>>::insert(owner.clone(), count2231                .checked_add(1)2232                .ok_or(Error::<T>::NumOverflow)?);2233        }2234        else {2235            <AccountItemCount<T>>::insert(owner.clone(), 1);2236        }22372238        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2239        if list_exists {2240            let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2241            let item_contains = list.contains(&item_index.clone());22422243            if !item_contains {2244                list.push(item_index.clone());2245            }22462247            <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2248        } else {2249            let mut itm = Vec::new();2250            itm.push(item_index.clone());2251            <AddressTokens<T>>::insert(collection_id, owner, itm);2252            2253        }22542255        Ok(())2256    }22572258    fn remove_token_index(2259        collection_id: CollectionId,2260        item_index: TokenId,2261        owner: T::AccountId,2262    ) -> DispatchResult {22632264        // update counter2265        <AccountItemCount<T>>::insert(owner.clone(), 2266            <AccountItemCount<T>>::get(owner.clone())2267            .checked_sub(1)2268            .ok_or(Error::<T>::NumOverflow)?);226922702271        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2272        if list_exists {2273            let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2274            let item_contains = list.contains(&item_index.clone());22752276            if item_contains {2277                list.retain(|&item| item != item_index);2278                <AddressTokens<T>>::insert(collection_id, owner, list);2279            }2280        }22812282        Ok(())2283    }22842285    fn move_token_index(2286        collection_id: CollectionId,2287        item_index: TokenId,2288        old_owner: T::AccountId,2289        new_owner: T::AccountId,2290    ) -> DispatchResult {2291        Self::remove_token_index(collection_id, item_index, old_owner)?;2292        Self::add_token_index(collection_id, item_index, new_owner)?;22932294        Ok(())2295    }2296    2297    fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2298        if <ContractOwner<T>>::contains_key(contract.clone()) {2299            let owner = <ContractOwner<T>>::get(contract);2300            ensure!(account == owner, Error::<T>::NoPermission);2301        } else {2302            fail!(Error::<T>::NoPermission);2303        }23042305        Ok(())2306    }2307}23082309////////////////////////////////////////////////////////////////////////////////////////////////////2310// Economic models2311// #region23122313/// Fee multiplier.2314pub type Multiplier = FixedU128;23152316type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2317    <T as system::Trait>::AccountId,2318>>::Balance;2319type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2320    <T as system::Trait>::AccountId,2321>>::NegativeImbalance;23222323/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2324/// in the queue.2325#[derive(Encode, Decode, Clone, Eq, PartialEq)]2326pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2327    #[codec(compact)] BalanceOf<T>2328);23292330impl<T: Trait + Send + Sync> sp_std::fmt::Debug2331    for ChargeTransactionPayment<T>2332{2333    #[cfg(feature = "std")]2334    fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2335        write!(f, "ChargeTransactionPayment<{:?}>", self.0)2336    }2337    #[cfg(not(feature = "std"))]2338    fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2339        Ok(())2340    }2341}23422343impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2344where2345    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2346    BalanceOf<T>: Send + Sync + FixedPointOperand,2347{2348    /// utility constructor. Used only in client/factory code.2349    pub fn from(fee: BalanceOf<T>) -> Self {2350        Self(fee)2351    }23522353    pub fn traditional_fee(2354        len: usize,2355        info: &DispatchInfoOf<T::Call>,2356        tip: BalanceOf<T>,2357    ) -> BalanceOf<T>2358    where2359        T::Call: Dispatchable<Info = DispatchInfo>,2360    {2361        <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2362    }23632364	fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2365		let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2366		let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2367		let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2368		final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2369	}23702371    fn withdraw_fee(2372        &self,2373        who: &T::AccountId,2374        call: &T::Call,2375        info: &DispatchInfoOf<T::Call>,2376        len: usize,2377    ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2378        let tip = self.0;23792380        // Set fee based on call type. Creating collection costs 1 Unique.2381        // All other transactions have traditional fees so far2382        // let fee = match call.is_sub_type() {2383        //     Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2384        //     _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2385        //                                                 // _ => <BalanceOf<T>>::from(100)2386        // };2387        let fee = Self::traditional_fee(len, info, tip);23882389        // Determine who is paying transaction fee based on ecnomic model2390        // Parse call to extract collection ID and access collection sponsor2391        let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2392            Some(Call::create_item(collection_id, _owner, _properties)) => {23932394                // check free create limit2395                if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2396                {2397                    <Collection<T>>::get(collection_id).sponsor2398                } else {2399                    T::AccountId::default()2400                }2401            }2402            Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2403                2404                let _collection_limits = <Collection<T>>::get(collection_id).limits;2405                let _collection_mode = <Collection<T>>::get(collection_id).mode;24062407                // sponsor timeout2408                let sponsor_transfer = match _collection_mode {2409                    CollectionMode::NFT => {24102411                        // get correct limit2412                        let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2413                            _collection_limits.sponsor_transfer_timeout2414                        } else {2415                            ChainLimit::get().nft_sponsor_transfer_timeout2416                        };24172418                        let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);2419                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2420                        let limit_time = basket + limit.into();2421                        if block_number >= limit_time {2422                            <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);2423                            true2424                        }2425                        else {2426                            false2427                        }2428                    }2429                    CollectionMode::Fungible(_) => {24302431                        // get correct limit2432                        let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2433                            _collection_limits.sponsor_transfer_timeout2434                        } else {2435                            ChainLimit::get().fungible_sponsor_transfer_timeout2436                        };24372438                        let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2439                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2440                        if basket.iter().any(|i| i.address == _new_owner.clone())2441                        {2442                            let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2443                            let limit_time = item.start_block + limit.into();2444                            if block_number >= limit_time {2445                                basket.retain(|x| x.address == item.address);2446                                basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2447                                <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2448                                true2449                            }2450                            else {2451                                false2452                            }2453                        }2454                        else {2455                            basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2456                            true2457                        }2458                    }2459                    CollectionMode::ReFungible(_) => {24602461                        // get correct limit2462                        let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2463                            _collection_limits.sponsor_transfer_timeout2464                        } else {2465                            ChainLimit::get().refungible_sponsor_transfer_timeout2466                        };24672468                        let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);2469                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2470                        let limit_time = basket + limit.into();2471                        if block_number >= limit_time {2472                            <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);2473                            true2474                        } else {2475                            false2476                        }2477                    }2478                    _ => {2479                        false2480                    },2481                };24822483                if !sponsor_transfer {2484                    T::AccountId::default()2485                } else {2486                    <Collection<T>>::get(collection_id).sponsor2487                }2488            }24892490            _ => T::AccountId::default(),2491        };24922493        // Sponsor smart contracts2494        sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {24952496            // On instantiation: set the contract owner2497            Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {24982499                let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2500                    code_hash,2501                    &data,2502                    &who,2503                );2504                <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());25052506                T::AccountId::default()2507            },25082509            // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2510            Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {25112512                let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());25132514                let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2515                  && <ContractOwner<T>>::get(called_contract.clone()) == *who;2516                let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2517                  2518                if !owned_contract && white_list_enabled {2519                    if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2520                        return Err(InvalidTransaction::Call.into());2521                    }2522                }25232524                let mut sponsor_transfer = false;2525                if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2526                    let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2527                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2528                    let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2529                    let limit_time = last_tx_block + rate_limit;25302531                    if block_number >= limit_time {2532                        <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2533                        sponsor_transfer = true;2534                    }2535                } else {2536                    sponsor_transfer = false;2537                }2538               2539                2540                let mut sp = T::AccountId::default();2541                if sponsor_transfer {2542                    if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2543                        if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2544                            sp = called_contract;2545                        }2546                    }2547                }25482549                sp2550            },25512552            _ => sponsor,2553        };25542555        let mut who_pays_fee: T::AccountId = sponsor.clone();2556        if sponsor == T::AccountId::default() {2557            who_pays_fee = who.clone();2558        }25592560        // Only mess with balances if fee is not zero.2561        if fee.is_zero() {2562            return Ok((fee, None));2563        }25642565        match <T as transaction_payment::Trait>::Currency::withdraw(2566            &who_pays_fee,2567            fee,2568            if tip.is_zero() {2569                WithdrawReason::TransactionPayment.into()2570            } else {2571                WithdrawReason::TransactionPayment | WithdrawReason::Tip2572            },2573            ExistenceRequirement::KeepAlive,2574        ) {2575            Ok(imbalance) => Ok((fee, Some(imbalance))),2576            Err(_) => Err(InvalidTransaction::Payment.into()),2577        }2578    }2579}258025812582impl<T: Trait + Send + Sync> SignedExtension2583    for ChargeTransactionPayment<T>2584where2585    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2586    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2587{2588    const IDENTIFIER: &'static str = "ChargeTransactionPayment";2589    type AccountId = T::AccountId;2590    type Call = T::Call;2591    type AdditionalSigned = ();2592    type Pre = (2593        BalanceOf<T>,2594        Self::AccountId,2595        Option<NegativeImbalanceOf<T>>,2596        BalanceOf<T>,2597    );2598    fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2599        Ok(())2600    }26012602    fn validate(2603        &self,2604        who: &Self::AccountId,2605        call: &Self::Call,2606        info: &DispatchInfoOf<Self::Call>,2607        len: usize,2608    ) -> TransactionValidity {2609		let (fee, _) = self.withdraw_fee(who, call, info, len)?;2610		Ok(ValidTransaction {2611			priority: Self::get_priority(len, info, fee),2612			..Default::default()2613		})2614    }26152616    fn pre_dispatch(2617        self,2618        who: &Self::AccountId,2619        call: &Self::Call,2620        info: &DispatchInfoOf<Self::Call>,2621        len: usize,2622    ) -> Result<Self::Pre, TransactionValidityError> {2623        let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2624        Ok((self.0, who.clone(), imbalance, fee))2625    }26262627    fn post_dispatch(2628        pre: Self::Pre,2629        info: &DispatchInfoOf<Self::Call>,2630        post_info: &PostDispatchInfoOf<Self::Call>,2631        len: usize,2632        _result: &DispatchResult,2633    ) -> Result<(), TransactionValidityError> {2634        let (tip, who, imbalance, fee) = pre;2635        if let Some(payed) = imbalance {2636            let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2637                len as u32, info, post_info, tip,2638            );2639            let refund = fee.saturating_sub(actual_fee);2640            let actual_payment =2641                match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2642                    &who, refund,2643                ) {2644                    Ok(refund_imbalance) => {2645                        // The refund cannot be larger than the up front payed max weight.2646                        // `PostDispatchInfo::calc_unspent` guards against such a case.2647                        match payed.offset(refund_imbalance) {2648                            Ok(actual_payment) => actual_payment,2649                            Err(_) => return Err(InvalidTransaction::Payment.into()),2650                        }2651                    }2652                    // We do not recreate the account using the refund. The up front payment2653                    // is gone in that case.2654                    Err(_) => payed,2655                };2656            let imbalances = actual_payment.split(tip);2657            <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2658                Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2659            );2660        }2661        Ok(())2662    }2663}26642665// #endregion
after · pallets/nft/src/lib.rs
1//2// This file is subject to the terms and conditions defined in3// file 'LICENSE', which is part of this source code package.4//56#![recursion_limit = "1024"]78#![cfg_attr(not(feature = "std"), no_std)]910#[cfg(feature = "std")]11pub use std::*;1213#[cfg(feature = "std")]14pub use serde::*;1516use codec::{Decode, Encode};17pub use frame_support::{18    construct_runtime, decl_event, decl_module, decl_storage, decl_error,19    dispatch::DispatchResult,20    ensure, fail, parameter_types,21    traits::{22        Currency, ExistenceRequirement, Get, Imbalance, KeyOwnerProofSystem, OnUnbalanced,23        Randomness, WithdrawReason,24    },25    weights::{26        constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND},27        DispatchInfo, GetDispatchInfo, IdentityFee, Pays, PostDispatchInfo, Weight,28        WeightToFeePolynomial,29    },30    IsSubType, StorageValue,31};3233use frame_system::{self as system, ensure_signed, ensure_root};34use sp_runtime::sp_std::prelude::Vec;35use sp_runtime::{36    traits::{37        DispatchInfoOf, Dispatchable, PostDispatchInfoOf, Saturating, SaturatedConversion, SignedExtension, Zero,38    },39    transaction_validity::{40        TransactionPriority, InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,41    },42    FixedPointOperand, FixedU128,43};44use pallet_contracts::ContractAddressFor;45use sp_runtime::traits::StaticLookup;4647#[cfg(test)]48mod mock;4950#[cfg(test)]51mod tests;5253mod default_weights;5455pub const MAX_DECIMAL_POINTS: DecimalPoints = 30;56pub const MAX_SPONSOR_TIMEOUT: u32 = 10_368_000;57pub const MAX_TOKEN_OWNERSHIP: u32 = 10_000_000;5859// Structs60// #region6162pub type CollectionId = u32;63pub type TokenId = u32;64pub type DecimalPoints = u8;6566#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]67#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]68pub enum CollectionMode {69    Invalid,70    NFT,71    // decimal points72    Fungible(DecimalPoints),73    // decimal points74    ReFungible(DecimalPoints),75}7677impl Into<u8> for CollectionMode {78    fn into(self) -> u8 {79        match self {80            CollectionMode::Invalid => 0,81            CollectionMode::NFT => 1,82            CollectionMode::Fungible(_) => 2,83            CollectionMode::ReFungible(_) => 3,84        }85    }86}8788#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]89#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]90pub enum AccessMode {91    Normal,92    WhiteList,93}94impl Default for AccessMode {95    fn default() -> Self {96        Self::Normal97    }98}99100impl Default for CollectionMode {101    fn default() -> Self {102        Self::Invalid103    }104}105106#[derive(Encode, Decode, Eq, Debug, Clone, PartialEq)]107#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]108pub enum SchemaVersion {109    ImageURL,110    Unique,111}112impl Default for SchemaVersion {113    fn default() -> Self {114        Self::ImageURL115    }116}117118#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]119#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]120pub struct Ownership<AccountId> {121    pub owner: AccountId,122    pub fraction: u128,123}124125#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]126#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]127pub struct CollectionType<AccountId> {128    pub owner: AccountId,129    pub mode: CollectionMode,130    pub access: AccessMode,131    pub decimal_points: DecimalPoints,132    pub name: Vec<u16>,        // 64 include null escape char133    pub description: Vec<u16>, // 256 include null escape char134    pub token_prefix: Vec<u8>, // 16 include null escape char135    pub mint_mode: bool,136    pub offchain_schema: Vec<u8>,137    pub schema_version: SchemaVersion,138    pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender139    pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.140    pub limits: CollectionLimits, // Collection private restrictions 141    pub variable_on_chain_schema: Vec<u8>, //142    pub const_on_chain_schema: Vec<u8>, //143}144145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]147pub struct NftItemType<AccountId> {148    pub owner: AccountId,149    pub const_data: Vec<u8>,150    pub variable_data: Vec<u8>,151}152153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]155pub struct FungibleItemType {156    pub value: u128,157}158159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]161pub struct ReFungibleItemType<AccountId> {162    pub owner: Vec<Ownership<AccountId>>,163    pub const_data: Vec<u8>,164    pub variable_data: Vec<u8>,165}166167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]169// pub struct VestingItem<AccountId, Moment> {170//     pub sender: AccountId,171//     pub recipient: AccountId,172//     pub collection_id: CollectionId,173//     pub item_id: TokenId,174//     pub amount: u64,175//     pub vesting_date: Moment,176// }177178#[derive(Encode, Decode, Debug, Clone, PartialEq)]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]180pub struct CollectionLimits {181    pub account_token_ownership_limit: u32,182    pub sponsored_data_size: u32,183    pub token_limit: u32,184185    // Timeouts for item types in passed blocks186    pub sponsor_transfer_timeout: u32,187}188189impl Default for CollectionLimits {190    fn default() -> CollectionLimits {191        CollectionLimits { 192            account_token_ownership_limit: 10_000_000, 193            token_limit: u32::max_value(),194            sponsored_data_size: u32::max_value(), 195            sponsor_transfer_timeout: 14400 }196    }197}198199#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]200#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]201pub struct ChainLimits {202    pub collection_numbers_limit: u32,203    pub account_token_ownership_limit: u32,204    pub collections_admins_limit: u64,205    pub custom_data_limit: u32,206207    // Timeouts for item types in passed blocks208    pub nft_sponsor_transfer_timeout: u32,209    pub fungible_sponsor_transfer_timeout: u32,210    pub refungible_sponsor_transfer_timeout: u32,211}212213pub trait WeightInfo {214	fn create_collection() -> Weight;215	fn destroy_collection() -> Weight;216	fn add_to_white_list() -> Weight;217	fn remove_from_white_list() -> Weight;218    fn set_public_access_mode() -> Weight;219    fn set_mint_permission() -> Weight;220    fn change_collection_owner() -> Weight;221    fn add_collection_admin() -> Weight;222    fn remove_collection_admin() -> Weight;223    fn set_collection_sponsor() -> Weight;224    fn confirm_sponsorship() -> Weight;225    fn remove_collection_sponsor() -> Weight;226    fn create_item(s: usize) -> Weight;227    fn burn_item() -> Weight;228    fn transfer() -> Weight;229    fn approve() -> Weight;230    fn transfer_from() -> Weight;231    fn set_offchain_schema() -> Weight;232    fn set_const_on_chain_schema() -> Weight;233    fn set_variable_on_chain_schema() -> Weight;234    fn set_variable_meta_data() -> Weight;235    fn enable_contract_sponsoring() -> Weight;236}237238#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]239#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]240pub struct CreateNftData {241    pub const_data: Vec<u8>,242    pub variable_data: Vec<u8>,243}244245#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]246#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]247pub struct CreateFungibleData {248    pub value: u128,249}250251#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]252#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]253pub struct CreateReFungibleData {254    pub const_data: Vec<u8>,255    pub variable_data: Vec<u8>,256}257258#[derive(Encode, Decode, Debug, Clone, PartialEq)]259#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]260pub enum CreateItemData {261    NFT(CreateNftData),262    Fungible(CreateFungibleData),263    ReFungible(CreateReFungibleData),264}265266impl CreateItemData {267    pub fn len(&self) -> usize {268        let len = match self {269            CreateItemData::NFT(data) => data.variable_data.len() + data.const_data.len(),270            CreateItemData::ReFungible(data) => data.variable_data.len() + data.const_data.len(),271            _ => 0272        };273        274        return len;275    }276}277278impl From<CreateNftData> for CreateItemData {279    fn from(item: CreateNftData) -> Self {280        CreateItemData::NFT(item)281    }282}283284impl From<CreateReFungibleData> for CreateItemData {285    fn from(item: CreateReFungibleData) -> Self {286        CreateItemData::ReFungible(item)287    }288}289290impl From<CreateFungibleData> for CreateItemData {291    fn from(item: CreateFungibleData) -> Self {292        CreateItemData::Fungible(item)293    }294}295296297decl_error! {298	/// Error for non-fungible-token module.299	pub enum Error for Module<T: Trait> {300        /// Total collections bound exceeded.301        TotalCollectionsLimitExceeded,302		/// Decimal_points parameter must be lower than MAX_DECIMAL_POINTS constant, currently it is 30.303        CollectionDecimalPointLimitExceeded, 304        /// Collection name can not be longer than 63 char.305        CollectionNameLimitExceeded, 306        /// Collection description can not be longer than 255 char.307        CollectionDescriptionLimitExceeded, 308        /// Token prefix can not be longer than 15 char.309        CollectionTokenPrefixLimitExceeded,310        /// This collection does not exist.311        CollectionNotFound,312        /// Item not exists.313        TokenNotFound,314        /// Arithmetic calculation overflow.315        NumOverflow,       316        /// Account already has admin role.317        AlreadyAdmin,  318        /// You do not own this collection.319        NoPermission,320        /// This address is not set as sponsor, use setCollectionSponsor first.321        ConfirmUnsetSponsorFail,322        /// Collection is not in mint mode.323        PublicMintingNotAllowed,324        /// Sender parameter and item owner must be equal.325        MustBeTokenOwner,326        /// Item balance not enough.327        TokenValueTooLow,328        /// Size of item is too large.329        NftSizeLimitExceeded,330        /// No approve found331        ApproveNotFound,332        /// Requested value more than approved.333        TokenValueNotEnough,334        /// Only approved addresses can call this method.335        ApproveRequired,336        /// Address is not in white list.337        AddresNotInWhiteList,338        /// Number of collection admins bound exceeded.339        CollectionAdminsLimitExceeded,340        /// Owned tokens by a single address bound exceeded.341        AddressOwnershipLimitExceeded,342        /// Length of items properties must be greater than 0.343        EmptyArgument,344        /// const_data exceeded data limit.345        TokenConstDataLimitExceeded,346        /// variable_data exceeded data limit.347        TokenVariableDataLimitExceeded,348        /// Not NFT item data used to mint in NFT collection.349        NotNftDataUsedToMintNftCollectionToken,350        /// Not Fungible item data used to mint in Fungible collection.351        NotFungibleDataUsedToMintFungibleCollectionToken,352        /// Not Re Fungible item data used to mint in Re Fungible collection.353        NotReFungibleDataUsedToMintReFungibleCollectionToken,354        /// Unexpected collection type.355        UnexpectedCollectionType,356        /// Can't store metadata in fungible tokens.357        CantStoreMetadataInFungibleTokens,358        /// Collection token limit exceeded359        CollectionTokenLimitExceeded,360        /// Account token limit exceeded per collection361        AccountTokenLimitExceeded,362        /// Collection limit bounds per collection exceeded363        CollectionLimitBoundsExceeded364	}365}366367pub trait Trait: system::Trait + Sized + transaction_payment::Trait + pallet_contracts::Trait {368    type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;369370    /// Weight information for extrinsics in this pallet.371	type WeightInfo: WeightInfo;372}373374#[cfg(feature = "runtime-benchmarks")]375mod benchmarking;376377// #endregion378379decl_storage! {380    trait Store for Module<T: Trait> as Nft {381382        // Private members383        NextCollectionID: CollectionId;384        CreatedCollectionCount: u32;385        ChainVersion: u64;386        ItemListIndex: map hasher(identity) CollectionId => TokenId;387388        // Chain limits struct389        pub ChainLimit get(fn chain_limit) config(): ChainLimits;390391        // Bound counters392        CollectionCount: u32;393        pub AccountItemCount get(fn account_item_count): map hasher(twox_64_concat) T::AccountId => u32;394395        // Basic collections396        pub Collection get(fn collection) config(): map hasher(identity) CollectionId => CollectionType<T::AccountId>;397        pub AdminList get(fn admin_list_collection): map hasher(identity) CollectionId => Vec<T::AccountId>;398        pub WhiteList get(fn white_list): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => bool;399400        /// Balance owner per collection map401        pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;402403        /// second parameter: item id + owner account id + spender account id404        pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;405406        /// Item collections407        pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;408        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;409        pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;410411        /// Index list412        pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;413414        /// Tokens transfer baskets415        pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;416        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;417        pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;418419        // Contract Sponsorship and Ownership420        pub ContractOwner get(fn contract_owner): map hasher(twox_64_concat) T::AccountId => T::AccountId;421        pub ContractSelfSponsoring get(fn contract_self_sponsoring): map hasher(twox_64_concat) T::AccountId => bool;422        pub ContractSponsorBasket get(fn contract_sponsor_basket): map hasher(twox_64_concat) (T::AccountId, T::AccountId) => T::BlockNumber;423        pub ContractSponsoringRateLimit get(fn contract_sponsoring_rate_limit): map hasher(twox_64_concat) T::AccountId => T::BlockNumber;424        pub ContractWhiteListEnabled get(fn contract_white_list_enabled): map hasher(twox_64_concat) T::AccountId => bool; 425        pub ContractWhiteList get(fn contract_white_list): double_map hasher(twox_64_concat) T::AccountId, hasher(twox_64_concat) T::AccountId => bool; 426    }427    add_extra_genesis {428        build(|config: &GenesisConfig<T>| {429            // Modification of storage430            for (_num, _c) in &config.collection {431                <Module<T>>::init_collection(_c);432            }433434            for (_num, _c, _i) in &config.nft_item_id {435                <Module<T>>::init_nft_token(*_c, _i);436            }437438            for (collection_id, account_id, fungible_item) in &config.fungible_item_id {439                <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);440            }441442            for (_num, _c, _i) in &config.refungible_item_id {443                <Module<T>>::init_refungible_token(*_c, _i);444            }445        })446    }447}448449decl_event!(450    pub enum Event<T>451    where452        AccountId = <T as system::Trait>::AccountId,453    {454        /// New collection was created455        /// 456        /// # Arguments457        /// 458        /// * collection_id: Globally unique identifier of newly created collection.459        /// 460        /// * mode: [CollectionMode] converted into u8.461        /// 462        /// * account_id: Collection owner.463        Created(CollectionId, u8, AccountId),464465        /// New item was created.466        /// 467        /// # Arguments468        /// 469        /// * collection_id: Id of the collection where item was created.470        /// 471        /// * item_id: Id of an item. Unique within the collection.472        ItemCreated(CollectionId, TokenId),473474        /// Collection item was burned.475        /// 476        /// # Arguments477        /// 478        /// collection_id.479        /// 480        /// item_id: Identifier of burned NFT.481        ItemDestroyed(CollectionId, TokenId),482    }483);484485decl_module! {486    pub struct Module<T: Trait> for enum Call where origin: T::Origin {487488        fn deposit_event() = default;489        type Error = Error<T>;490491        fn on_initialize(now: T::BlockNumber) -> Weight {492493            if ChainVersion::get() < 2494            {495                let value = NextCollectionID::get();496                CreatedCollectionCount::put(value);497                ChainVersion::put(2);498            }499500            0501        }502503        /// This method creates a Collection of NFTs. Each Token may have multiple properties encoded as an array of bytes of certain length. The initial owner and admin of the collection are set to the address that signed the transaction. Both addresses can be changed later.504        /// 505        /// # Permissions506        /// 507        /// * Anyone.508        /// 509        /// # Arguments510        /// 511        /// * collection_name: UTF-16 string with collection name (limit 64 characters), will be stored as zero-terminated.512        /// 513        /// * collection_description: UTF-16 string with collection description (limit 256 characters), will be stored as zero-terminated.514        /// 515        /// * token_prefix: UTF-8 string with token prefix.516        /// 517        /// * mode: [CollectionMode] collection type and type dependent data.518        // returns collection ID519        #[weight = T::WeightInfo::create_collection()]520        pub fn create_collection(origin,521                                 collection_name: Vec<u16>,522                                 collection_description: Vec<u16>,523                                 token_prefix: Vec<u8>,524                                 mode: CollectionMode) -> DispatchResult {525526            // Anyone can create a collection527            let who = ensure_signed(origin)?;528529            let decimal_points = match mode {530                CollectionMode::Fungible(points) => points,531                CollectionMode::ReFungible(points) => points,532                _ => 0533            };534535            // bound Total number of collections536            ensure!(CollectionCount::get() < ChainLimit::get().collection_numbers_limit, Error::<T>::TotalCollectionsLimitExceeded);537538            // check params539            ensure!(decimal_points <= MAX_DECIMAL_POINTS, Error::<T>::CollectionDecimalPointLimitExceeded);540541            let mut name = collection_name.to_vec();542            name.push(0);543            ensure!(name.len() <= 64, Error::<T>::CollectionNameLimitExceeded);544545            let mut description = collection_description.to_vec();546            description.push(0);547            ensure!(name.len() <= 256, Error::<T>::CollectionDescriptionLimitExceeded);548549            let mut prefix = token_prefix.to_vec();550            prefix.push(0);551            ensure!(prefix.len() <= 16, Error::<T>::CollectionTokenPrefixLimitExceeded);552553            // Generate next collection ID554            let next_id = CreatedCollectionCount::get()555                .checked_add(1)556                .ok_or(Error::<T>::NumOverflow)?;557558            // bound counter559            let total = CollectionCount::get()560                .checked_add(1)561                .ok_or(Error::<T>::NumOverflow)?;562563            CreatedCollectionCount::put(next_id);564            CollectionCount::put(total);565566            // Create new collection567            let new_collection = CollectionType {568                owner: who.clone(),569                name: name,570                mode: mode.clone(),571                mint_mode: false,572                access: AccessMode::Normal,573                description: description,574                decimal_points: decimal_points,575                token_prefix: prefix,576                offchain_schema: Vec::new(),577                schema_version: SchemaVersion::ImageURL,578                sponsor: T::AccountId::default(),579                sponsor_confirmed: false,580                variable_on_chain_schema: Vec::new(),581                const_on_chain_schema: Vec::new(),582                limits: CollectionLimits::default(),583            };584585            // Add new collection to map586            <Collection<T>>::insert(next_id, new_collection);587588            // call event589            Self::deposit_event(RawEvent::Created(next_id, mode.into(), who.clone()));590591            Ok(())592        }593594        /// **DANGEROUS**: Destroys collection and all NFTs within this collection. Users irrecoverably lose their assets and may lose real money.595        /// 596        /// # Permissions597        /// 598        /// * Collection Owner.599        /// 600        /// # Arguments601        /// 602        /// * collection_id: collection to destroy.603        #[weight = T::WeightInfo::destroy_collection()]604        pub fn destroy_collection(origin, collection_id: CollectionId) -> DispatchResult {605606            let sender = ensure_signed(origin)?;607            Self::check_owner_permissions(collection_id, sender)?;608609            <AddressTokens<T>>::remove_prefix(collection_id);610            <Allowances<T>>::remove_prefix(collection_id);611            <Balance<T>>::remove_prefix(collection_id);612            <ItemListIndex>::remove(collection_id);613            <AdminList<T>>::remove(collection_id);614            <Collection<T>>::remove(collection_id);615            <WhiteList<T>>::remove_prefix(collection_id);616617            <NftItemList<T>>::remove_prefix(collection_id);618            <FungibleItemList<T>>::remove_prefix(collection_id);619            <ReFungibleItemList<T>>::remove_prefix(collection_id);620621            <NftTransferBasket<T>>::remove_prefix(collection_id);622            <FungibleTransferBasket<T>>::remove_prefix(collection_id);623            <ReFungibleTransferBasket<T>>::remove_prefix(collection_id);624625            if CollectionCount::get() > 0626            {627                // bound couter628                let total = CollectionCount::get()629                    .checked_sub(1)630                    .ok_or(Error::<T>::NumOverflow)?;631632                CollectionCount::put(total);633            }634635            Ok(())636        }637638        /// Add an address to white list.639        /// 640        /// # Permissions641        /// 642        /// * Collection Owner643        /// * Collection Admin644        /// 645        /// # Arguments646        /// 647        /// * collection_id.648        /// 649        /// * address.650        #[weight = T::WeightInfo::add_to_white_list()]651        pub fn add_to_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{652653            let sender = ensure_signed(origin)?;654            Self::check_owner_or_admin_permissions(collection_id, sender)?;655656            <WhiteList<T>>::insert(collection_id, address, true);657            658            Ok(())659        }660661        /// Remove an address from white list.662        /// 663        /// # Permissions664        /// 665        /// * Collection Owner666        /// * Collection Admin667        /// 668        /// # Arguments669        /// 670        /// * collection_id.671        /// 672        /// * address.673        #[weight = T::WeightInfo::remove_from_white_list()]674        pub fn remove_from_white_list(origin, collection_id: CollectionId, address: T::AccountId) -> DispatchResult{675676            let sender = ensure_signed(origin)?;677            Self::check_owner_or_admin_permissions(collection_id, sender)?;678679            <WhiteList<T>>::remove(collection_id, address);680681            Ok(())682        }683684        /// Toggle between normal and white list access for the methods with access for `Anyone`.685        /// 686        /// # Permissions687        /// 688        /// * Collection Owner.689        /// 690        /// # Arguments691        /// 692        /// * collection_id.693        /// 694        /// * mode: [AccessMode]695        #[weight = T::WeightInfo::set_public_access_mode()]696        pub fn set_public_access_mode(origin, collection_id: CollectionId, mode: AccessMode) -> DispatchResult697        {698            let sender = ensure_signed(origin)?;699700            Self::check_owner_permissions(collection_id, sender)?;701            let mut target_collection = <Collection<T>>::get(collection_id);702            target_collection.access = mode;703            <Collection<T>>::insert(collection_id, target_collection);704705            Ok(())706        }707708        /// Allows Anyone to create tokens if:709        /// * White List is enabled, and710        /// * Address is added to white list, and711        /// * This method was called with True parameter712        /// 713        /// # Permissions714        /// * Collection Owner715        ///716        /// # Arguments717        /// 718        /// * collection_id.719        /// 720        /// * mint_permission: Boolean parameter. If True, allows minting to Anyone with conditions above.721        #[weight = T::WeightInfo::set_mint_permission()]722        pub fn set_mint_permission(origin, collection_id: CollectionId, mint_permission: bool) -> DispatchResult723        {724            let sender = ensure_signed(origin)?;725726            Self::check_owner_permissions(collection_id, sender)?;727            let mut target_collection = <Collection<T>>::get(collection_id);728            target_collection.mint_mode = mint_permission;729            <Collection<T>>::insert(collection_id, target_collection);730731            Ok(())732        }733734        /// Change the owner of the collection.735        /// 736        /// # Permissions737        /// 738        /// * Collection Owner.739        /// 740        /// # Arguments741        /// 742        /// * collection_id.743        /// 744        /// * new_owner.745        #[weight = T::WeightInfo::change_collection_owner()]746        pub fn change_collection_owner(origin, collection_id: CollectionId, new_owner: T::AccountId) -> DispatchResult {747748            let sender = ensure_signed(origin)?;749            Self::check_owner_permissions(collection_id, sender)?;750            let mut target_collection = <Collection<T>>::get(collection_id);751            target_collection.owner = new_owner;752            <Collection<T>>::insert(collection_id, target_collection);753754            Ok(())755        }756757        /// Adds an admin of the Collection.758        /// NFT Collection can be controlled by multiple admin addresses (some which can also be servers, for example). Admins can issue and burn NFTs, as well as add and remove other admins, but cannot change NFT or Collection ownership. 759        /// 760        /// # Permissions761        /// 762        /// * Collection Owner.763        /// * Collection Admin.764        /// 765        /// # Arguments766        /// 767        /// * collection_id: ID of the Collection to add admin for.768        /// 769        /// * new_admin_id: Address of new admin to add.770        #[weight = T::WeightInfo::add_collection_admin()]771        pub fn add_collection_admin(origin, collection_id: CollectionId, new_admin_id: T::AccountId) -> DispatchResult {772773            let sender = ensure_signed(origin)?;774            Self::check_owner_or_admin_permissions(collection_id, sender)?;775            let mut admin_arr: Vec<T::AccountId> = Vec::new();776777            if <AdminList<T>>::contains_key(collection_id)778            {779                admin_arr = <AdminList<T>>::get(collection_id);780                ensure!(!admin_arr.contains(&new_admin_id), Error::<T>::AlreadyAdmin);781            }782783            // Number of collection admins784            ensure!((admin_arr.len() as u64) < ChainLimit::get().collections_admins_limit, Error::<T>::CollectionAdminsLimitExceeded);785786            admin_arr.push(new_admin_id);787            <AdminList<T>>::insert(collection_id, admin_arr);788789            Ok(())790        }791792        /// Remove admin address of the Collection. An admin address can remove itself. List of admins may become empty, in which case only Collection Owner will be able to add an Admin.793        ///794        /// # Permissions795        /// 796        /// * Collection Owner.797        /// * Collection Admin.798        /// 799        /// # Arguments800        /// 801        /// * collection_id: ID of the Collection to remove admin for.802        /// 803        /// * account_id: Address of admin to remove.804        #[weight = T::WeightInfo::remove_collection_admin()]805        pub fn remove_collection_admin(origin, collection_id: CollectionId, account_id: T::AccountId) -> DispatchResult {806807            let sender = ensure_signed(origin)?;808            Self::check_owner_or_admin_permissions(collection_id, sender)?;809810            if <AdminList<T>>::contains_key(collection_id)811            {812                let mut admin_arr = <AdminList<T>>::get(collection_id);813                admin_arr.retain(|i| *i != account_id);814                <AdminList<T>>::insert(collection_id, admin_arr);815            }816817            Ok(())818        }819820        /// # Permissions821        /// 822        /// * Collection Owner823        /// 824        /// # Arguments825        /// 826        /// * collection_id.827        /// 828        /// * new_sponsor.829        #[weight = T::WeightInfo::set_collection_sponsor()]830        pub fn set_collection_sponsor(origin, collection_id: CollectionId, new_sponsor: T::AccountId) -> DispatchResult {831832            let sender = ensure_signed(origin)?;833            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);834835            let mut target_collection = <Collection<T>>::get(collection_id);836            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);837838            target_collection.sponsor = new_sponsor;839            target_collection.sponsor_confirmed = false;840            <Collection<T>>::insert(collection_id, target_collection);841842            Ok(())843        }844845        /// # Permissions846        /// 847        /// * Sponsor.848        /// 849        /// # Arguments850        /// 851        /// * collection_id.852        #[weight = T::WeightInfo::confirm_sponsorship()]853        pub fn confirm_sponsorship(origin, collection_id: CollectionId) -> DispatchResult {854855            let sender = ensure_signed(origin)?;856            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);857858            let mut target_collection = <Collection<T>>::get(collection_id);859            ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);860861            target_collection.sponsor_confirmed = true;862            <Collection<T>>::insert(collection_id, target_collection);863864            Ok(())865        }866867        /// Switch back to pay-per-own-transaction model.868        ///869        /// # Permissions870        ///871        /// * Collection owner.872        /// 873        /// # Arguments874        /// 875        /// * collection_id.876        #[weight = T::WeightInfo::remove_collection_sponsor()]877        pub fn remove_collection_sponsor(origin, collection_id: CollectionId) -> DispatchResult {878879            let sender = ensure_signed(origin)?;880            ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);881882            let mut target_collection = <Collection<T>>::get(collection_id);883            ensure!(sender == target_collection.owner, Error::<T>::NoPermission);884885            target_collection.sponsor = T::AccountId::default();886            target_collection.sponsor_confirmed = false;887            <Collection<T>>::insert(collection_id, target_collection);888889            Ok(())890        }891892        /// This method creates a concrete instance of NFT Collection created with CreateCollection method.893        /// 894        /// # Permissions895        /// 896        /// * Collection Owner.897        /// * Collection Admin.898        /// * Anyone if899        ///     * White List is enabled, and900        ///     * Address is added to white list, and901        ///     * MintPermission is enabled (see SetMintPermission method)902        /// 903        /// # Arguments904        /// 905        /// * collection_id: ID of the collection.906        /// 907        /// * owner: Address, initial owner of the NFT.908        ///909        /// * data: Token data to store on chain.910        // #[weight =911        // (130_000_000 as Weight)912        // .saturating_add((2135 as Weight).saturating_mul((properties.len() as u64) as Weight))913        // .saturating_add(RocksDbWeight::get().reads(10 as Weight))914        // .saturating_add(RocksDbWeight::get().writes(8 as Weight))]915916        #[weight = T::WeightInfo::create_item(data.len())]917        pub fn create_item(origin, collection_id: CollectionId, owner: T::AccountId, data: CreateItemData) -> DispatchResult {918919            let sender = ensure_signed(origin)?;920921            Self::collection_exists(collection_id)?;922923            let target_collection = <Collection<T>>::get(collection_id);924925            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;926            Self::validate_create_item_args(&target_collection, &data)?;927            Self::create_item_no_validation(collection_id, &target_collection, owner, data)?;928929            Ok(())930        }931932        /// This method creates multiple instances of NFT Collection created with CreateCollection method.933        /// 934        /// # Permissions935        /// 936        /// * Collection Owner.937        /// * Collection Admin.938        /// * Anyone if939        ///     * White List is enabled, and940        ///     * Address is added to white list, and941        ///     * MintPermission is enabled (see SetMintPermission method)942        /// 943        /// # Arguments944        /// 945        /// * collection_id: ID of the collection.946        /// 947        /// * itemsData: Array items properties. Each property is an array of bytes itself, see [create_item].948        /// 949        /// * owner: Address, initial owner of the NFT.950        #[weight = T::WeightInfo::create_item(items_data.into_iter()951                               .map(|data| { data.len() })952                               .sum())]953        pub fn create_multiple_items(origin, collection_id: CollectionId, owner: T::AccountId, items_data: Vec<CreateItemData>) -> DispatchResult {954955            ensure!(items_data.len() > 0, Error::<T>::EmptyArgument);956            let sender = ensure_signed(origin)?;957958            Self::collection_exists(collection_id)?;959            let target_collection = <Collection<T>>::get(collection_id);960961            Self::can_create_items_in_collection(collection_id, &target_collection, &sender, &owner)?;962963            for data in &items_data {964                Self::validate_create_item_args(&target_collection, data)?;965            }966            for data in &items_data {967                Self::create_item_no_validation(collection_id, &target_collection, owner.clone(), data.clone())?;968            }969970            Ok(())971        }972973        /// Destroys a concrete instance of NFT.974        /// 975        /// # Permissions976        /// 977        /// * Collection Owner.978        /// * Collection Admin.979        /// * Current NFT Owner.980        /// 981        /// # Arguments982        /// 983        /// * collection_id: ID of the collection.984        /// 985        /// * item_id: ID of NFT to burn.986        #[weight = T::WeightInfo::burn_item()]987        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {988989            let sender = ensure_signed(origin)?;990            Self::collection_exists(collection_id)?;991992            // Transfer permissions check993            let target_collection = <Collection<T>>::get(collection_id);994            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||995                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),996                Error::<T>::NoPermission);997998            if target_collection.access == AccessMode::WhiteList {999                Self::check_white_list(collection_id, &sender)?;1000            }10011002            match target_collection.mode1003            {1004                CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1005                CollectionMode::Fungible(_)  => Self::burn_fungible_item(&sender, collection_id, value)?,1006                CollectionMode::ReFungible(_)  => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1007                _ => ()1008            };10091010            // call event1011            Self::deposit_event(RawEvent::ItemDestroyed(collection_id, item_id));10121013            Ok(())1014        }10151016        /// Change ownership of the token.1017        /// 1018        /// # Permissions1019        /// 1020        /// * Collection Owner1021        /// * Collection Admin1022        /// * Current NFT owner1023        ///1024        /// # Arguments1025        /// 1026        /// * recipient: Address of token recipient.1027        /// 1028        /// * collection_id.1029        /// 1030        /// * item_id: ID of the item1031        ///     * Non-Fungible Mode: Required.1032        ///     * Fungible Mode: Ignored.1033        ///     * Re-Fungible Mode: Required.1034        /// 1035        /// * value: Amount to transfer.1036        ///     * Non-Fungible Mode: Ignored1037        ///     * Fungible Mode: Must specify transferred amount1038        ///     * Re-Fungible Mode: Must specify transferred portion (between 0 and 1)1039        #[weight = T::WeightInfo::transfer()]1040        pub fn transfer(origin, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {10411042            let sender = ensure_signed(origin)?;1043            let target_collection = <Collection<T>>::get(collection_id);10441045            // Limits check1046            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;10471048            // Transfer permissions check1049            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1050                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1051                Error::<T>::NoPermission);10521053            if target_collection.access == AccessMode::WhiteList {1054                Self::check_white_list(collection_id, &sender)?;1055                Self::check_white_list(collection_id, &recipient)?;1056            }10571058            match target_collection.mode1059            {1060                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1061                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,1062                CollectionMode::ReFungible(_)  => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1063                _ => ()1064            };10651066            Ok(())1067        }10681069        /// Set, change, or remove approved address to transfer the ownership of the NFT.1070        /// 1071        /// # Permissions1072        /// 1073        /// * Collection Owner1074        /// * Collection Admin1075        /// * Current NFT owner1076        /// 1077        /// # Arguments1078        /// 1079        /// * approved: Address that is approved to transfer this NFT or zero (if needed to remove approval).1080        /// 1081        /// * collection_id.1082        /// 1083        /// * item_id: ID of the item.1084        #[weight = T::WeightInfo::approve()]1085        pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {10861087            let sender = ensure_signed(origin)?;10881089            // Transfer permissions check1090            let target_collection = <Collection<T>>::get(collection_id);1091            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1092                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1093                Error::<T>::NoPermission);10941095            if target_collection.access == AccessMode::WhiteList {1096                Self::check_white_list(collection_id, &sender)?;1097                Self::check_white_list(collection_id, &spender)?;1098            }10991100            let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));1101            let mut allowance: u128 = amount;1102            if allowance_exists {1103                allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));1104            }1105            <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);11061107            Ok(())1108        }1109        1110        /// Change ownership of a NFT on behalf of the owner. See Approve method for additional information. After this method executes, the approval is removed so that the approved address will not be able to transfer this NFT again from this owner.1111        /// 1112        /// # Permissions1113        /// * Collection Owner1114        /// * Collection Admin1115        /// * Current NFT owner1116        /// * Address approved by current NFT owner1117        /// 1118        /// # Arguments1119        /// 1120        /// * from: Address that owns token.1121        /// 1122        /// * recipient: Address of token recipient.1123        /// 1124        /// * collection_id.1125        /// 1126        /// * item_id: ID of the item.1127        /// 1128        /// * value: Amount to transfer.1129        #[weight = T::WeightInfo::transfer_from()]1130        pub fn transfer_from(origin, from: T::AccountId, recipient: T::AccountId, collection_id: CollectionId, item_id: TokenId, value: u128 ) -> DispatchResult {11311132            let sender = ensure_signed(origin)?;1133            let mut appoved_transfer = false;11341135            // Check approval1136            let mut approval: u128 = 0;1137            if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {1138                approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));1139                ensure!(approval >= value, Error::<T>::TokenValueNotEnough);1140                appoved_transfer = true;1141            }11421143            let target_collection = <Collection<T>>::get(collection_id);11441145            // Limits check1146            Self::is_correct_transfer(collection_id, &target_collection, &recipient)?;11471148            // Transfer permissions check         1149            ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1150                Error::<T>::NoPermission);11511152            if target_collection.access == AccessMode::WhiteList {1153                Self::check_white_list(collection_id, &sender)?;1154                Self::check_white_list(collection_id, &recipient)?;1155            }11561157            // Reduce approval by transferred amount or remove if remaining approval drops to 01158            if approval - value > 0 {1159                <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);1160            }1161            else {1162                <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));1163            }11641165            match target_collection.mode1166            {1167                CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1168                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &from, &recipient)?,1169                CollectionMode::ReFungible(_)  => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1170                _ => ()1171            };11721173            Ok(())1174        }11751176        #[weight = 0]1177        pub fn safe_transfer_from(origin, collection_id: CollectionId, item_id: TokenId, new_owner: T::AccountId) -> DispatchResult {11781179            // let no_perm_mes = "You do not have permissions to modify this collection";1180            // ensure!(<ApprovedList<T>>::contains_key((collection_id, item_id)), no_perm_mes);1181            // let list_itm = <ApprovedList<T>>::get((collection_id, item_id));1182            // ensure!(list_itm.contains(&new_owner.clone()), no_perm_mes);11831184            // // on_nft_received  call11851186            // Self::transfer(origin, collection_id, item_id, new_owner)?;11871188            Ok(())1189        }11901191        /// Set off-chain data schema.1192        /// 1193        /// # Permissions1194        /// 1195        /// * Collection Owner1196        /// * Collection Admin1197        /// 1198        /// # Arguments1199        /// 1200        /// * collection_id.1201        /// 1202        /// * schema: String representing the offchain data schema.1203        #[weight = T::WeightInfo::set_variable_meta_data()]1204        pub fn set_variable_meta_data (1205            origin,1206            collection_id: CollectionId,1207            item_id: TokenId,1208            data: Vec<u8>1209        ) -> DispatchResult {1210            let sender = ensure_signed(origin)?;1211            1212            Self::collection_exists(collection_id)?;1213            1214            ensure!(ChainLimit::get().custom_data_limit >= data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);12151216            // Modify permissions check1217            let target_collection = <Collection<T>>::get(collection_id);1218            ensure!(Self::is_item_owner(sender.clone(), collection_id, item_id) ||1219                Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1220                Error::<T>::NoPermission);12211222            Self::item_exists(collection_id, item_id, &target_collection.mode)?;12231224            match target_collection.mode1225            {1226                CollectionMode::NFT => Self::set_nft_variable_data(collection_id, item_id, data)?,1227                CollectionMode::ReFungible(_)  => Self::set_re_fungible_variable_data(collection_id, item_id, data)?,1228                CollectionMode::Fungible(_) => fail!(Error::<T>::CantStoreMetadataInFungibleTokens),1229                _ => fail!(Error::<T>::UnexpectedCollectionType)1230            };12311232            Ok(())1233        }1234 1235        /// Set schema standard1236        /// ImageURL1237        /// Unique1238        /// 1239        /// # Permissions1240        /// 1241        /// * Collection Owner1242        /// * Collection Admin1243        /// 1244        /// # Arguments1245        /// 1246        /// * collection_id.1247        /// 1248        /// * schema: SchemaVersion: enum1249        #[weight = 0]1250        pub fn set_schema_version(1251            origin,1252            collection_id: CollectionId,1253            version: SchemaVersion1254        ) -> DispatchResult {1255            let sender = ensure_signed(origin)?;1256            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;1257            let mut target_collection = <Collection<T>>::get(collection_id);1258            target_collection.schema_version = version;1259            <Collection<T>>::insert(collection_id, target_collection);12601261            Ok(())1262        }12631264        /// Set off-chain data schema.1265        /// 1266        /// # Permissions1267        /// 1268        /// * Collection Owner1269        /// * Collection Admin1270        /// 1271        /// # Arguments1272        /// 1273        /// * collection_id.1274        /// 1275        /// * schema: String representing the offchain data schema.1276        #[weight = T::WeightInfo::set_offchain_schema()]1277        pub fn set_offchain_schema(1278            origin,1279            collection_id: CollectionId,1280            schema: Vec<u8>1281        ) -> DispatchResult {1282            let sender = ensure_signed(origin)?;1283            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;12841285            let mut target_collection = <Collection<T>>::get(collection_id);1286            target_collection.offchain_schema = schema;1287            <Collection<T>>::insert(collection_id, target_collection);12881289            Ok(())1290        }12911292        /// Set const on-chain data schema.1293        /// 1294        /// # Permissions1295        /// 1296        /// * Collection Owner1297        /// * Collection Admin1298        /// 1299        /// # Arguments1300        /// 1301        /// * collection_id.1302        /// 1303        /// * schema: String representing the const on-chain data schema.1304        #[weight = T::WeightInfo::set_const_on_chain_schema()]1305        pub fn set_const_on_chain_schema (1306            origin,1307            collection_id: CollectionId,1308            schema: Vec<u8>1309        ) -> DispatchResult {1310            let sender = ensure_signed(origin)?;1311            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13121313            let mut target_collection = <Collection<T>>::get(collection_id);1314            target_collection.const_on_chain_schema = schema;1315            <Collection<T>>::insert(collection_id, target_collection);13161317            Ok(())1318        }13191320        /// Set variable on-chain data schema.1321        /// 1322        /// # Permissions1323        /// 1324        /// * Collection Owner1325        /// * Collection Admin1326        /// 1327        /// # Arguments1328        /// 1329        /// * collection_id.1330        /// 1331        /// * schema: String representing the variable on-chain data schema.1332        #[weight = T::WeightInfo::set_const_on_chain_schema()]1333        pub fn set_variable_on_chain_schema (1334            origin,1335            collection_id: CollectionId,1336            schema: Vec<u8>1337        ) -> DispatchResult {1338            let sender = ensure_signed(origin)?;1339            Self::check_owner_or_admin_permissions(collection_id, sender.clone())?;13401341            let mut target_collection = <Collection<T>>::get(collection_id);1342            target_collection.variable_on_chain_schema = schema;1343            <Collection<T>>::insert(collection_id, target_collection);13441345            Ok(())1346        }13471348        // Sudo permissions function1349        #[weight = 0]1350        pub fn set_chain_limits(1351            origin,1352            limits: ChainLimits1353        ) -> DispatchResult {1354            ensure_root(origin)?;1355            <ChainLimit>::put(limits);1356            Ok(())1357        }13581359        /// Enable smart contract self-sponsoring.1360        /// 1361        /// # Permissions1362        /// 1363        /// * Contract Owner1364        /// 1365        /// # Arguments1366        /// 1367        /// * contract address1368        /// * enable flag1369        /// 1370        #[weight = T::WeightInfo::enable_contract_sponsoring()]1371        pub fn enable_contract_sponsoring(1372            origin,1373            contract_address: T::AccountId,1374            enable: bool1375        ) -> DispatchResult {13761377            let sender = ensure_signed(origin)?;13781379            #[cfg(feature = "runtime-benchmarks")]1380            <ContractOwner<T>>::insert(contract_address.clone(), sender.clone());13811382            Self::ensure_contract_owned(sender, &contract_address)?;13831384            <ContractSelfSponsoring<T>>::insert(contract_address, enable);1385            Ok(())1386        }13871388        /// Set the rate limit for contract sponsoring to specified number of blocks.1389        /// 1390        /// If not set (has the default value of 0 blocks), the sponsoring will be disabled. 1391        /// If set to the number B (for blocks), the transactions will be sponsored with a rate 1392        /// limit of B, i.e. fees for every transaction sent to this smart contract will be paid 1393        /// from contract endowment if there are at least B blocks between such transactions. 1394        /// Nonetheless, if transactions are sent more frequently, the fees are paid by the sender.1395        /// 1396        /// # Permissions1397        /// 1398        /// * Contract Owner1399        /// 1400        /// # Arguments1401        /// 1402        /// -`contract_address`: Address of the contract to sponsor1403        /// -`rate_limit`: Number of blocks to wait until the next sponsored transaction is allowed1404        /// 1405        #[weight = 0]1406        pub fn set_contract_sponsoring_rate_limit(1407            origin,1408            contract_address: T::AccountId,1409            rate_limit: T::BlockNumber1410        ) -> DispatchResult {1411            let sender = ensure_signed(origin)?;1412            Self::ensure_contract_owned(sender, &contract_address)?;14131414            <ContractSponsoringRateLimit<T>>::insert(contract_address, rate_limit);1415            Ok(())1416        }14171418        /// Enable the white list for a contract. Only addresses added to the white list with addToContractWhiteList will be able to call this smart contract.1419        /// 1420        /// # Permissions1421        /// 1422        /// * Address that deployed smart contract.1423        /// 1424        /// # Arguments1425        /// 1426        /// -`contract_address`: Address of the contract.1427        /// 1428        /// - `enable`: .  1429        #[weight = 0]1430        pub fn toggle_contract_white_list(1431            origin,1432            contract_address: T::AccountId,1433            enable: bool1434        ) -> DispatchResult {1435            let sender = ensure_signed(origin)?;1436            Self::ensure_contract_owned(sender, &contract_address)?;14371438            <ContractWhiteListEnabled<T>>::insert(contract_address, enable);1439            Ok(())1440        }1441        1442        /// Add an address to smart contract white list.1443        /// 1444        /// # Permissions1445        /// 1446        /// * Address that deployed smart contract.1447        /// 1448        /// # Arguments1449        /// 1450        /// -`contract_address`: Address of the contract.1451        ///1452        /// -`account_address`: Address to add.1453        #[weight = 0]1454        pub fn add_to_contract_white_list(1455            origin,1456            contract_address: T::AccountId,1457            account_address: T::AccountId1458        ) -> DispatchResult {1459            let sender = ensure_signed(origin)?;1460            Self::ensure_contract_owned(sender, &contract_address)?;1461            1462            <ContractWhiteList<T>>::insert(contract_address, account_address, true);1463            Ok(())1464        }14651466        /// Remove an address from smart contract white list.1467        /// 1468        /// # Permissions1469        /// 1470        /// * Address that deployed smart contract.1471        /// 1472        /// # Arguments1473        /// 1474        /// -`contract_address`: Address of the contract.1475        ///1476        /// -`account_address`: Address to remove.1477        #[weight = 0]1478        pub fn remove_from_contract_white_list(1479            origin,1480            contract_address: T::AccountId,1481            account_address: T::AccountId1482        ) -> DispatchResult {1483            let sender = ensure_signed(origin)?;1484            Self::ensure_contract_owned(sender, &contract_address)?;1485            1486            <ContractWhiteList<T>>::remove(contract_address, account_address);1487            Ok(())1488        }14891490        #[weight = 0]1491        pub fn set_collection_limits(1492            origin,1493            collection_id: u32,1494            limits: CollectionLimits,1495        ) -> DispatchResult {1496            let sender = ensure_signed(origin)?;1497            Self::check_owner_permissions(collection_id, sender.clone())?;1498            let mut target_collection = <Collection<T>>::get(collection_id);1499            let chain_limits = ChainLimit::get();1500            let climits = target_collection.limits;15011502            // collection bounds1503            ensure!(limits.sponsor_transfer_timeout <= MAX_SPONSOR_TIMEOUT &&1504                limits.account_token_ownership_limit <= MAX_TOKEN_OWNERSHIP,  1505                Error::<T>::CollectionLimitBoundsExceeded);15061507            // token_limit   check  prev1508            ensure!(climits.token_limit > limits.token_limit && 1509                limits.token_limit <= chain_limits.account_token_ownership_limit, 1510                Error::<T>::AccountTokenLimitExceeded);15111512            target_collection.limits = limits;1513            <Collection<T>>::insert(collection_id, target_collection);15141515            Ok(())1516        } 1517    }1518}15191520impl<T: Trait> Module<T> {15211522    fn is_correct_transfer(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, recipient: &T::AccountId) -> DispatchResult {15231524        // check token limit and account token limit1525        let account_items: u32 = <AddressTokens<T>>::get(collection_id, recipient).len() as u32;1526        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);1527        1528        Ok(())1529    }15301531    fn can_create_items_in_collection(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, sender: &T::AccountId, owner: &T::AccountId) -> DispatchResult {15321533        // check token limit and account token limit1534        let total_items: u32 = ItemListIndex::get(collection_id);1535        let account_items: u32 = <AddressTokens<T>>::get(collection_id, owner).len() as u32;1536        ensure!(collection.limits.token_limit > total_items,  Error::<T>::CollectionTokenLimitExceeded);1537        ensure!(collection.limits.account_token_ownership_limit > account_items,  Error::<T>::AccountTokenLimitExceeded);15381539        if !Self::is_owner_or_admin_permissions(collection_id, sender.clone()) {1540            ensure!(collection.mint_mode == true, Error::<T>::PublicMintingNotAllowed);1541            Self::check_white_list(collection_id, owner)?;1542            Self::check_white_list(collection_id, sender)?;1543        }15441545        Ok(())1546    }15471548    fn validate_create_item_args(target_collection: &CollectionType<T::AccountId>, data: &CreateItemData) -> DispatchResult {1549        match target_collection.mode1550        {1551            CollectionMode::NFT => {1552                if let CreateItemData::NFT(data) = data {1553                    // check sizes1554                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1555                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1556                } else {1557                    fail!(Error::<T>::NotNftDataUsedToMintNftCollectionToken);1558                }1559            },1560            CollectionMode::Fungible(_) => {1561                if let CreateItemData::Fungible(_) = data {1562                } else {1563                    fail!(Error::<T>::NotFungibleDataUsedToMintFungibleCollectionToken);1564                }1565            },1566            CollectionMode::ReFungible(_) => {1567                if let CreateItemData::ReFungible(data) = data {15681569                    // check sizes1570                    ensure!(ChainLimit::get().custom_data_limit >= data.const_data.len() as u32, Error::<T>::TokenConstDataLimitExceeded);1571                    ensure!(ChainLimit::get().custom_data_limit >= data.variable_data.len() as u32, Error::<T>::TokenVariableDataLimitExceeded);1572                } else {1573                    fail!(Error::<T>::NotReFungibleDataUsedToMintReFungibleCollectionToken);1574                }1575            },1576            _ => { fail!(Error::<T>::UnexpectedCollectionType); }1577        };15781579        Ok(())1580    }15811582    fn create_item_no_validation(collection_id: CollectionId, collection: &CollectionType<T::AccountId>, owner: T::AccountId, data: CreateItemData) -> DispatchResult {1583        match data1584        {1585            CreateItemData::NFT(data) => {1586                let item = NftItemType {1587                    owner,1588                    const_data: data.const_data,1589                    variable_data: data.variable_data1590                };15911592                Self::add_nft_item(collection_id, item)?;1593            },1594            CreateItemData::Fungible(data) => {1595                Self::add_fungible_item(collection_id, &owner, data.value)?;1596            },1597            CreateItemData::ReFungible(data) => {1598                let mut owner_list = Vec::new();1599                let value = (10 as u128).pow(collection.decimal_points as u32);1600                owner_list.push(Ownership {owner: owner.clone(), fraction: value});16011602                let item = ReFungibleItemType {1603                    owner: owner_list,1604                    const_data: data.const_data,1605                    variable_data: data.variable_data1606                };16071608                Self::add_refungible_item(collection_id, item)?;1609            }1610        };16111612        // call event1613        Self::deposit_event(RawEvent::ItemCreated(collection_id, <ItemListIndex>::get(collection_id)));16141615        Ok(())1616    }16171618    fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {16191620        // Does new owner already have an account?1621        let mut balance: u128 = 0;1622        if <FungibleItemList<T>>::contains_key(collection_id, owner) {1623            balance = <FungibleItemList<T>>::get(collection_id, owner).value;1624        } 16251626        // Mint 1627        let item = FungibleItemType {1628            value: balance + value1629        };1630        <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);16311632        // Update balance1633        let new_balance = <Balance<T>>::get(collection_id, owner)1634            .checked_add(value)1635            .ok_or(Error::<T>::NumOverflow)?;1636        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);16371638        Ok(())1639    }16401641    fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1642        let current_index = <ItemListIndex>::get(collection_id)1643            .checked_add(1)1644            .ok_or(Error::<T>::NumOverflow)?;1645        let itemcopy = item.clone();16461647        let value = item.owner.first().unwrap().fraction;1648        let owner = item.owner.first().unwrap().owner.clone();16491650        Self::add_token_index(collection_id, current_index, owner.clone())?;16511652        <ItemListIndex>::insert(collection_id, current_index);1653        <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);16541655        // Update balance1656        let new_balance = <Balance<T>>::get(collection_id, owner.clone())1657            .checked_add(value)1658            .ok_or(Error::<T>::NumOverflow)?;1659        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);16601661        Ok(())1662    }16631664    fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {1665        let current_index = <ItemListIndex>::get(collection_id)1666            .checked_add(1)1667            .ok_or(Error::<T>::NumOverflow)?;16681669        let item_owner = item.owner.clone();1670        Self::add_token_index(collection_id, current_index, item.owner.clone())?;16711672        <ItemListIndex>::insert(collection_id, current_index);1673        <NftItemList<T>>::insert(collection_id, current_index, item);16741675        // Update balance1676        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1677            .checked_add(1)1678            .ok_or(Error::<T>::NumOverflow)?;1679        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);16801681        Ok(())1682    }16831684    fn burn_refungible_item(1685        collection_id: CollectionId,1686        item_id: TokenId,1687        owner: T::AccountId,1688    ) -> DispatchResult {1689        ensure!(1690            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1691            Error::<T>::TokenNotFound1692        );1693        let collection = <ReFungibleItemList<T>>::get(collection_id, item_id);1694        let item = collection1695            .owner1696            .iter()1697            .filter(|&i| i.owner == owner)1698            .next()1699            .unwrap();1700        Self::remove_token_index(collection_id, item_id, owner.clone())?;17011702        // update balance1703        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1704            .checked_sub(item.fraction)1705            .ok_or(Error::<T>::NumOverflow)?;1706        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);17071708        <ReFungibleItemList<T>>::remove(collection_id, item_id);17091710        Ok(())1711    }17121713    fn burn_nft_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1714        ensure!(1715            <NftItemList<T>>::contains_key(collection_id, item_id),1716            Error::<T>::TokenNotFound1717        );1718        let item = <NftItemList<T>>::get(collection_id, item_id);1719        Self::remove_token_index(collection_id, item_id, item.owner.clone())?;17201721        // update balance1722        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1723            .checked_sub(1)1724            .ok_or(Error::<T>::NumOverflow)?;1725        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1726        <NftItemList<T>>::remove(collection_id, item_id);17271728        Ok(())1729    }17301731    fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {1732        ensure!(1733            <FungibleItemList<T>>::contains_key(collection_id, owner),1734            Error::<T>::TokenNotFound1735        );1736        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1737        ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);17381739        // update balance1740        let new_balance = <Balance<T>>::get(collection_id, owner)1741            .checked_sub(value)1742            .ok_or(Error::<T>::NumOverflow)?;1743        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);17441745        if balance.value - value > 0 {1746            balance.value -= value;1747            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1748        }1749        else {1750            <FungibleItemList<T>>::remove(collection_id, owner);1751        }17521753        Ok(())1754    }17551756    fn collection_exists(collection_id: CollectionId) -> DispatchResult {1757        ensure!(1758            <Collection<T>>::contains_key(collection_id),1759            Error::<T>::CollectionNotFound1760        );1761        Ok(())1762    }17631764    fn check_owner_permissions(collection_id: CollectionId, subject: T::AccountId) -> DispatchResult {1765        Self::collection_exists(collection_id)?;17661767        let target_collection = <Collection<T>>::get(collection_id);1768        ensure!(1769            subject == target_collection.owner,1770            Error::<T>::NoPermission1771        );17721773        Ok(())1774    }17751776    fn is_owner_or_admin_permissions(collection_id: CollectionId, subject: T::AccountId) -> bool {1777        let target_collection = <Collection<T>>::get(collection_id);1778        let mut result: bool = subject == target_collection.owner;1779        let exists = <AdminList<T>>::contains_key(collection_id);17801781        if !result & exists {1782            if <AdminList<T>>::get(collection_id).contains(&subject) {1783                result = true1784            }1785        }17861787        result1788    }17891790    fn check_owner_or_admin_permissions(1791        collection_id: CollectionId,1792        subject: T::AccountId,1793    ) -> DispatchResult {1794        Self::collection_exists(collection_id)?;1795        let result = Self::is_owner_or_admin_permissions(collection_id, subject.clone());17961797        ensure!(1798            result,1799            Error::<T>::NoPermission1800        );1801        Ok(())1802    }18031804    fn is_item_owner(subject: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> bool {1805        let target_collection = <Collection<T>>::get(collection_id);18061807        match target_collection.mode {1808            CollectionMode::NFT => {1809                <NftItemList<T>>::get(collection_id, item_id).owner == subject1810            }1811            CollectionMode::Fungible(_) => {1812                <FungibleItemList<T>>::contains_key(collection_id, &subject)1813            }1814            CollectionMode::ReFungible(_) => {1815                <ReFungibleItemList<T>>::get(collection_id, item_id)1816                    .owner1817                    .iter()1818                    .any(|i| i.owner == subject)1819            }1820            CollectionMode::Invalid => false,1821        }1822    }18231824    fn check_white_list(collection_id: CollectionId, address: &T::AccountId) -> DispatchResult {1825        let mes = Error::<T>::AddresNotInWhiteList;1826        ensure!(<WhiteList<T>>::contains_key(collection_id, address), mes);18271828        Ok(())1829    }18301831    fn transfer_fungible(1832        collection_id: CollectionId,1833        value: u128,1834        owner: &T::AccountId,1835        recipient: &T::AccountId,1836    ) -> DispatchResult {1837        ensure!(1838            <FungibleItemList<T>>::contains_key(collection_id, owner),1839            Error::<T>::TokenNotFound1840        );18411842        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);1843        ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);18441845        // Send balance to recipient (updates balanceOf of recipient)1846        Self::add_fungible_item(collection_id, recipient, value)?;18471848        // update balanceOf of sender1849        <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);18501851        // Reduce or remove sender1852        if balance.value == value {1853            <FungibleItemList<T>>::remove(collection_id, owner);1854        }1855        else {1856            balance.value -= value;1857            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);1858        }18591860        Ok(())1861    }18621863    fn transfer_refungible(1864        collection_id: CollectionId,1865        item_id: TokenId,1866        value: u128,1867        owner: T::AccountId,1868        new_owner: T::AccountId,1869    ) -> DispatchResult {1870        ensure!(1871            <ReFungibleItemList<T>>::contains_key(collection_id, item_id),1872            Error::<T>::TokenNotFound1873        );18741875        let full_item = <ReFungibleItemList<T>>::get(collection_id, item_id);1876        let item = full_item1877            .owner1878            .iter()1879            .filter(|i| i.owner == owner)1880            .next()1881            .ok_or(Error::<T>::NumOverflow)?;1882        let amount = item.fraction;18831884        ensure!(amount >= value, Error::<T>::TokenValueTooLow);18851886        // update balance1887        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1888            .checked_sub(value)1889            .ok_or(Error::<T>::NumOverflow)?;1890        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);18911892        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1893            .checked_add(value)1894            .ok_or(Error::<T>::NumOverflow)?;1895        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);18961897        let old_owner = item.owner.clone();1898        let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);18991900        // transfer1901        if amount == value && !new_owner_has_account {1902            // change owner1903            // new owner do not have account1904            let mut new_full_item = full_item.clone();1905            new_full_item1906                .owner1907                .iter_mut()1908                .find(|i| i.owner == owner)1909                .unwrap()1910                .owner = new_owner.clone();1911            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);19121913            // update index collection1914            Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1915        } else {1916            let mut new_full_item = full_item.clone();1917            new_full_item1918                .owner1919                .iter_mut()1920                .find(|i| i.owner == owner)1921                .unwrap()1922                .fraction -= value;19231924            // separate amount1925            if new_owner_has_account {1926                // new owner has account1927                new_full_item1928                    .owner1929                    .iter_mut()1930                    .find(|i| i.owner == new_owner)1931                    .unwrap()1932                    .fraction += value;1933            } else {1934                // new owner do not have account1935                new_full_item.owner.push(Ownership {1936                    owner: new_owner.clone(),1937                    fraction: value,1938                });1939                Self::add_token_index(collection_id, item_id, new_owner.clone())?;1940            }19411942            <ReFungibleItemList<T>>::insert(collection_id, item_id, new_full_item);1943        }19441945        Ok(())1946    }19471948    fn transfer_nft(1949        collection_id: CollectionId,1950        item_id: TokenId,1951        sender: T::AccountId,1952        new_owner: T::AccountId,1953    ) -> DispatchResult {1954        ensure!(1955            <NftItemList<T>>::contains_key(collection_id, item_id),1956            Error::<T>::TokenNotFound1957        );19581959        let mut item = <NftItemList<T>>::get(collection_id, item_id);19601961        ensure!(1962            sender == item.owner,1963            Error::<T>::MustBeTokenOwner1964        );19651966        // update balance1967        let balance_old_owner = <Balance<T>>::get(collection_id, item.owner.clone())1968            .checked_sub(1)1969            .ok_or(Error::<T>::NumOverflow)?;1970        <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);19711972        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())1973            .checked_add(1)1974            .ok_or(Error::<T>::NumOverflow)?;1975        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);19761977        // change owner1978        let old_owner = item.owner.clone();1979        item.owner = new_owner.clone();1980        <NftItemList<T>>::insert(collection_id, item_id, item);19811982        // update index collection1983        Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;19841985        Ok(())1986    }1987    1988    fn item_exists(1989        collection_id: CollectionId,1990        item_id: TokenId,1991        mode: &CollectionMode1992    ) -> DispatchResult {1993        match mode {1994            CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1995            CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1996            _ => ()1997        };1998        1999        Ok(())2000    }20012002    fn set_re_fungible_variable_data(2003        collection_id: CollectionId,2004        item_id: TokenId,2005        data: Vec<u8>2006    ) -> DispatchResult {2007        let mut item = <ReFungibleItemList<T>>::get(collection_id, item_id);20082009        item.variable_data = data;20102011        <ReFungibleItemList<T>>::insert(collection_id, item_id, item);20122013        Ok(())2014    }20152016    fn set_nft_variable_data(2017        collection_id: CollectionId,2018        item_id: TokenId,2019        data: Vec<u8>2020    ) -> DispatchResult {2021        let mut item = <NftItemList<T>>::get(collection_id, item_id);2022        2023        item.variable_data = data;20242025        <NftItemList<T>>::insert(collection_id, item_id, item);2026        2027        Ok(())2028    }20292030    fn init_collection(item: &CollectionType<T::AccountId>) {2031        // check params2032        assert!(2033            item.decimal_points <= MAX_DECIMAL_POINTS,2034            "decimal_points parameter must be lower than MAX_DECIMAL_POINTS"2035        );2036        assert!(2037            item.name.len() <= 64,2038            "Collection name can not be longer than 63 char"2039        );2040        assert!(2041            item.name.len() <= 256,2042            "Collection description can not be longer than 255 char"2043        );2044        assert!(2045            item.token_prefix.len() <= 16,2046            "Token prefix can not be longer than 15 char"2047        );20482049        // Generate next collection ID2050        let next_id = CreatedCollectionCount::get()2051            .checked_add(1)2052            .unwrap();20532054        CreatedCollectionCount::put(next_id);2055    }20562057    fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {2058        let current_index = <ItemListIndex>::get(collection_id)2059            .checked_add(1)2060            .unwrap();20612062        let item_owner = item.owner.clone();2063        Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();20642065        <ItemListIndex>::insert(collection_id, current_index);20662067        // Update balance2068        let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())2069            .checked_add(1)2070            .unwrap();2071        <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2072    }20732074    fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {2075        let current_index = <ItemListIndex>::get(collection_id)2076            .checked_add(1)2077            .unwrap();20782079        Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();20802081        <ItemListIndex>::insert(collection_id, current_index);20822083        // Update balance2084        let new_balance = <Balance<T>>::get(collection_id, owner)2085            .checked_add(item.value)2086            .unwrap();2087        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);2088    }20892090    fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {2091        let current_index = <ItemListIndex>::get(collection_id)2092            .checked_add(1)2093            .unwrap();20942095        let value = item.owner.first().unwrap().fraction;2096        let owner = item.owner.first().unwrap().owner.clone();20972098        Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();20992100        <ItemListIndex>::insert(collection_id, current_index);21012102        // Update balance2103        let new_balance = <Balance<T>>::get(collection_id, owner.clone())2104            .checked_add(value)2105            .unwrap();2106        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);2107    }21082109    fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {21102111        // add to account limit2112        if <AccountItemCount<T>>::contains_key(owner.clone()) {21132114            // bound Owned tokens by a single address2115            let count = <AccountItemCount<T>>::get(owner.clone());2116            ensure!(count < ChainLimit::get().account_token_ownership_limit, Error::<T>::AddressOwnershipLimitExceeded);21172118            <AccountItemCount<T>>::insert(owner.clone(), count2119                .checked_add(1)2120                .ok_or(Error::<T>::NumOverflow)?);2121        }2122        else {2123            <AccountItemCount<T>>::insert(owner.clone(), 1);2124        }21252126        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2127        if list_exists {2128            let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2129            let item_contains = list.contains(&item_index.clone());21302131            if !item_contains {2132                list.push(item_index.clone());2133            }21342135            <AddressTokens<T>>::insert(collection_id, owner.clone(), list);2136        } else {2137            let mut itm = Vec::new();2138            itm.push(item_index.clone());2139            <AddressTokens<T>>::insert(collection_id, owner, itm);2140            2141        }21422143        Ok(())2144    }21452146    fn remove_token_index(2147        collection_id: CollectionId,2148        item_index: TokenId,2149        owner: T::AccountId,2150    ) -> DispatchResult {21512152        // update counter2153        <AccountItemCount<T>>::insert(owner.clone(), 2154            <AccountItemCount<T>>::get(owner.clone())2155            .checked_sub(1)2156            .ok_or(Error::<T>::NumOverflow)?);215721582159        let list_exists = <AddressTokens<T>>::contains_key(collection_id, owner.clone());2160        if list_exists {2161            let mut list = <AddressTokens<T>>::get(collection_id, owner.clone());2162            let item_contains = list.contains(&item_index.clone());21632164            if item_contains {2165                list.retain(|&item| item != item_index);2166                <AddressTokens<T>>::insert(collection_id, owner, list);2167            }2168        }21692170        Ok(())2171    }21722173    fn move_token_index(2174        collection_id: CollectionId,2175        item_index: TokenId,2176        old_owner: T::AccountId,2177        new_owner: T::AccountId,2178    ) -> DispatchResult {2179        Self::remove_token_index(collection_id, item_index, old_owner)?;2180        Self::add_token_index(collection_id, item_index, new_owner)?;21812182        Ok(())2183    }2184    2185    fn ensure_contract_owned(account: T::AccountId, contract: &T::AccountId) -> DispatchResult {2186        if <ContractOwner<T>>::contains_key(contract.clone()) {2187            let owner = <ContractOwner<T>>::get(contract);2188            ensure!(account == owner, Error::<T>::NoPermission);2189        } else {2190            fail!(Error::<T>::NoPermission);2191        }21922193        Ok(())2194    }2195}21962197////////////////////////////////////////////////////////////////////////////////////////////////////2198// Economic models2199// #region22002201/// Fee multiplier.2202pub type Multiplier = FixedU128;22032204type BalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2205    <T as system::Trait>::AccountId,2206>>::Balance;2207type NegativeImbalanceOf<T> = <<T as transaction_payment::Trait>::Currency as Currency<2208    <T as system::Trait>::AccountId,2209>>::NegativeImbalance;22102211/// Require the transactor pay for themselves and maybe include a tip to gain additional priority2212/// in the queue.2213#[derive(Encode, Decode, Clone, Eq, PartialEq)]2214pub struct ChargeTransactionPayment<T: Trait + Send + Sync>(2215    #[codec(compact)] BalanceOf<T>2216);22172218impl<T: Trait + Send + Sync> sp_std::fmt::Debug2219    for ChargeTransactionPayment<T>2220{2221    #[cfg(feature = "std")]2222    fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2223        write!(f, "ChargeTransactionPayment<{:?}>", self.0)2224    }2225    #[cfg(not(feature = "std"))]2226    fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {2227        Ok(())2228    }2229}22302231impl<T: Trait + Send + Sync> ChargeTransactionPayment<T>2232where2233    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2234    BalanceOf<T>: Send + Sync + FixedPointOperand,2235{2236    /// utility constructor. Used only in client/factory code.2237    pub fn from(fee: BalanceOf<T>) -> Self {2238        Self(fee)2239    }22402241    pub fn traditional_fee(2242        len: usize,2243        info: &DispatchInfoOf<T::Call>,2244        tip: BalanceOf<T>,2245    ) -> BalanceOf<T>2246    where2247        T::Call: Dispatchable<Info = DispatchInfo>,2248    {2249        <transaction_payment::Module<T>>::compute_fee(len as u32, info, tip)2250    }22512252	fn get_priority(len: usize, info: &DispatchInfoOf<T::Call>, final_fee: BalanceOf<T>) -> TransactionPriority {2253		let weight_saturation = T::MaximumBlockWeight::get() / info.weight.max(1);2254		let len_saturation = T::MaximumBlockLength::get() as u64 / (len as u64).max(1);2255		let coefficient: BalanceOf<T> = weight_saturation.min(len_saturation).saturated_into::<BalanceOf<T>>();2256		final_fee.saturating_mul(coefficient).saturated_into::<TransactionPriority>()2257	}22582259    fn withdraw_fee(2260        &self,2261        who: &T::AccountId,2262        call: &T::Call,2263        info: &DispatchInfoOf<T::Call>,2264        len: usize,2265    ) -> Result<(BalanceOf<T>, Option<NegativeImbalanceOf<T>>), TransactionValidityError> {2266        let tip = self.0;22672268        // Set fee based on call type. Creating collection costs 1 Unique.2269        // All other transactions have traditional fees so far2270        // let fee = match call.is_sub_type() {2271        //     Some(Call::create_collection(..)) => <BalanceOf<T>>::from(1_000_000_000),2272        //     _ => Self::traditional_fee(len, info, tip), // Flat fee model, use only for testing purposes2273        //                                                 // _ => <BalanceOf<T>>::from(100)2274        // };2275        let fee = Self::traditional_fee(len, info, tip);22762277        // Only mess with balances if fee is not zero.2278        if fee.is_zero() {2279            return Ok((fee, None));2280        }22812282        // Determine who is paying transaction fee based on ecnomic model2283        // Parse call to extract collection ID and access collection sponsor2284        let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2285            Some(Call::create_item(collection_id, _owner, _properties)) => {22862287                // check free create limit2288                if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&2289                   (<Collection<T>>::get(collection_id).sponsor_confirmed)2290                {2291                    <Collection<T>>::get(collection_id).sponsor2292                } else {2293                    T::AccountId::default()2294                }2295            }2296            Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {2297                2298                let mut sponsor_transfer = false;2299                if <Collection<T>>::get(collection_id).sponsor_confirmed {23002301                    let collection_limits = <Collection<T>>::get(collection_id).limits;2302                    let collection_mode = <Collection<T>>::get(collection_id).mode;2303    2304                    // sponsor timeout2305                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2306                    sponsor_transfer = match collection_mode {2307                        CollectionMode::NFT => {2308    2309                            // get correct limit2310                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2311                                collection_limits.sponsor_transfer_timeout2312                            } else {2313                                ChainLimit::get().nft_sponsor_transfer_timeout2314                            };2315    2316                            let mut sponsored = true;2317                            if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {2318                                let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);2319                                let limit_time = last_tx_block + limit.into();2320                                if block_number <= limit_time {2321                                    sponsored = false;2322                                }2323                            }2324                            if sponsored {2325                                <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);2326                            }23272328                            sponsored2329                        }2330                        CollectionMode::Fungible(_) => {2331    2332                            // get correct limit2333                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2334                                collection_limits.sponsor_transfer_timeout2335                            } else {2336                                ChainLimit::get().fungible_sponsor_transfer_timeout2337                            };2338    2339                            let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2340                            let mut sponsored = true;2341                            if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {2342                                let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);2343                                let limit_time = last_tx_block + limit.into();2344                                if block_number <= limit_time {2345                                    sponsored = false;2346                                }2347                            }2348                            if sponsored {2349                                <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);2350                            }23512352                            sponsored2353                        }2354                        CollectionMode::ReFungible(_) => {2355    2356                            // get correct limit2357                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {2358                                collection_limits.sponsor_transfer_timeout2359                            } else {2360                                ChainLimit::get().refungible_sponsor_transfer_timeout2361                            };2362    2363                            let mut sponsored = true;2364                            if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {2365                                let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);2366                                let limit_time = last_tx_block + limit.into();2367                                if block_number <= limit_time {2368                                    sponsored = false;2369                                }2370                            }2371                            if sponsored {2372                                <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);2373                            }23742375                            sponsored2376                        }2377                        _ => {2378                            false2379                        },2380                    };2381                }23822383                if !sponsor_transfer {2384                    T::AccountId::default()2385                } else {2386                    <Collection<T>>::get(collection_id).sponsor2387                }2388            }23892390            _ => T::AccountId::default(),2391        };23922393        // Sponsor smart contracts2394        sponsor = match IsSubType::<pallet_contracts::Call<T>>::is_sub_type(call) {23952396            // On instantiation: set the contract owner2397            Some(pallet_contracts::Call::instantiate(_endowment, _gas_limit, code_hash, data)) => {23982399                let new_contract_address = <T as pallet_contracts::Trait>::DetermineContractAddress::contract_address_for(2400                    code_hash,2401                    &data,2402                    &who,2403                );2404                <ContractOwner<T>>::insert(new_contract_address.clone(), who.clone());24052406                T::AccountId::default()2407            },24082409            // When the contract is called, check if the sponsoring is enabled and pay fees from contract endowment if it is2410            Some(pallet_contracts::Call::call(dest, _value, _gas_limit, _data)) => {24112412                let called_contract: T::AccountId = T::Lookup::lookup((*dest).clone()).unwrap_or(T::AccountId::default());24132414                let owned_contract = <ContractOwner<T>>::contains_key(called_contract.clone())2415                  && <ContractOwner<T>>::get(called_contract.clone()) == *who;2416                let white_list_enabled = <ContractWhiteListEnabled<T>>::contains_key(called_contract.clone()) && <ContractWhiteListEnabled<T>>::get(called_contract.clone());2417                  2418                if !owned_contract && white_list_enabled {2419                    if !<ContractWhiteList<T>>::contains_key(called_contract.clone(), who) {2420                        return Err(InvalidTransaction::Call.into());2421                    }2422                }24232424                let mut sponsor_transfer = false;2425                if <ContractSponsoringRateLimit<T>>::contains_key(called_contract.clone()) {2426                    let last_tx_block = <ContractSponsorBasket<T>>::get((&called_contract, &who));2427                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2428                    let rate_limit = <ContractSponsoringRateLimit<T>>::get(&called_contract);2429                    let limit_time = last_tx_block + rate_limit;24302431                    if block_number >= limit_time {2432                        <ContractSponsorBasket<T>>::insert((called_contract.clone(), who.clone()), block_number);2433                        sponsor_transfer = true;2434                    }2435                } else {2436                    sponsor_transfer = false;2437                }2438               2439                2440                let mut sp = T::AccountId::default();2441                if sponsor_transfer {2442                    if <ContractSelfSponsoring<T>>::contains_key(called_contract.clone()) {2443                        if <ContractSelfSponsoring<T>>::get(called_contract.clone()) {2444                            sp = called_contract;2445                        }2446                    }2447                }24482449                sp2450            },24512452            _ => sponsor,2453        };24542455        let mut who_pays_fee: T::AccountId = sponsor.clone();2456        if sponsor == T::AccountId::default() {2457            who_pays_fee = who.clone();2458        }24592460        match <T as transaction_payment::Trait>::Currency::withdraw(2461            &who_pays_fee,2462            fee,2463            if tip.is_zero() {2464                WithdrawReason::TransactionPayment.into()2465            } else {2466                WithdrawReason::TransactionPayment | WithdrawReason::Tip2467            },2468            ExistenceRequirement::KeepAlive,2469        ) {2470            Ok(imbalance) => Ok((fee, Some(imbalance))),2471            Err(_) => Err(InvalidTransaction::Payment.into()),2472        }2473    }2474}247524762477impl<T: Trait + Send + Sync> SignedExtension2478    for ChargeTransactionPayment<T>2479where2480    BalanceOf<T>: Send + Sync + From<u64> + FixedPointOperand,2481    T::Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo> + IsSubType<Call<T>> + IsSubType<pallet_contracts::Call<T>>,2482{2483    const IDENTIFIER: &'static str = "ChargeTransactionPayment";2484    type AccountId = T::AccountId;2485    type Call = T::Call;2486    type AdditionalSigned = ();2487    type Pre = (2488        BalanceOf<T>,2489        Self::AccountId,2490        Option<NegativeImbalanceOf<T>>,2491        BalanceOf<T>,2492    );2493    fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {2494        Ok(())2495    }24962497    fn validate(2498        &self,2499        who: &Self::AccountId,2500        call: &Self::Call,2501        info: &DispatchInfoOf<Self::Call>,2502        len: usize,2503    ) -> TransactionValidity {2504		let (fee, _) = self.withdraw_fee(who, call, info, len)?;2505		Ok(ValidTransaction {2506			priority: Self::get_priority(len, info, fee),2507			..Default::default()2508		})2509    }25102511    fn pre_dispatch(2512        self,2513        who: &Self::AccountId,2514        call: &Self::Call,2515        info: &DispatchInfoOf<Self::Call>,2516        len: usize,2517    ) -> Result<Self::Pre, TransactionValidityError> {2518        let (fee, imbalance) = self.withdraw_fee(who, call, info, len)?;2519        Ok((self.0, who.clone(), imbalance, fee))2520    }25212522    fn post_dispatch(2523        pre: Self::Pre,2524        info: &DispatchInfoOf<Self::Call>,2525        post_info: &PostDispatchInfoOf<Self::Call>,2526        len: usize,2527        _result: &DispatchResult,2528    ) -> Result<(), TransactionValidityError> {2529        let (tip, who, imbalance, fee) = pre;2530        if let Some(payed) = imbalance {2531            let actual_fee = <transaction_payment::Module<T>>::compute_actual_fee(2532                len as u32, info, post_info, tip,2533            );2534            let refund = fee.saturating_sub(actual_fee);2535            let actual_payment =2536                match <T as transaction_payment::Trait>::Currency::deposit_into_existing(2537                    &who, refund,2538                ) {2539                    Ok(refund_imbalance) => {2540                        // The refund cannot be larger than the up front payed max weight.2541                        // `PostDispatchInfo::calc_unspent` guards against such a case.2542                        match payed.offset(refund_imbalance) {2543                            Ok(actual_payment) => actual_payment,2544                            Err(_) => return Err(InvalidTransaction::Payment.into()),2545                        }2546                    }2547                    // We do not recreate the account using the refund. The up front payment2548                    // is gone in that case.2549                    Err(_) => payed,2550                };2551            let imbalances = actual_payment.split(tip);2552            <T as transaction_payment::Trait>::OnTransactionPayment::on_unbalanceds(2553                Some(imbalances.0).into_iter().chain(Some(imbalances.1)),2554            );2555        }2556        Ok(())2557    }2558}25592560// #endregion
modifiedruntime_types.jsondiffbeforeafterboth
--- a/runtime_types.json
+++ b/runtime_types.json
@@ -42,18 +42,14 @@
       "Fraction": "u128"
     },
     "FungibleItemType": {
-      "Collection": "CollectionId",
-      "Owner": "AccountId",
       "Value": "u128"
     },
     "NftItemType": {
-      "Collection": "CollectionId",
       "Owner": "AccountId",
       "ConstData": "Vec<u8>",
       "VariableData": "Vec<u8>"
     },
     "ReFungibleItemType": {
-      "Collection": "CollectionId",
       "Owner": "Vec<Ownership<AccountId>>",
       "ConstData": "Vec<u8>",
       "VariableData": "Vec<u8>"
@@ -70,14 +66,10 @@
       "OffchainSchema": "Vec<u8>",
       "SchemaVersion": "SchemaVersion",
       "Sponsor": "AccountId",
-      "UnconfirmedSponsor": "AccountId",
+      "SponsorConfirmed": "bool",
       "Limits": "CollectionLimits",
       "VariableOnChainSchema": "Vec<u8>",
       "ConstOnChainSchema": "Vec<u8>"
-    },
-    "ApprovePermissions": {
-      "Approved": "AccountId",
-      "Amount": "u128"
     },
     "RawData": "Vec<u8>",
     "Address": "AccountId",
@@ -87,7 +79,9 @@
       "const_data": "Vec<u8>",
       "variable_data": "Vec<u8>" 
     },
-    "CreateFungibleData": {},
+    "CreateFungibleData": {
+      "value": "u128"
+    },
     "CreateReFungibleData": {
       "const_data": "Vec<u8>",
       "variable_data": "Vec<u8>" 
@@ -107,10 +101,6 @@
     },
     "CollectionId": "u32",
     "TokenId": "u32",
-    "BasketItem": {
-      "Address": "AccountId",
-      "start_block": "BlockNumber"
-    },
     "ChainLimits": {
       "collection_numbers_limit": "u32",
       "account_token_ownership_limit": "u32",
addedtests/src/confirmSponsorship.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/confirmSponsorship.test.ts
@@ -0,0 +1,350 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
+import { 
+  createCollectionExpectSuccess, 
+  setCollectionSponsorExpectSuccess, 
+  destroyCollectionExpectSuccess, 
+  setCollectionSponsorExpectFailure,
+  confirmSponsorshipExpectSuccess,
+  confirmSponsorshipExpectFailure,
+  createItemExpectSuccess,
+  findUnusedAddress,
+  getGenericResult,
+  enableWhiteListExpectSuccess,
+  enablePublicMintingExpectSuccess,
+  addToWhiteListExpectSuccess,
+} from "./util/helpers";
+import { Keyring } from "@polkadot/api";
+import { IKeyringPair } from "@polkadot/types/types";
+import type { AccountId } from '@polkadot/types/interfaces';
+import { BigNumber } from 'bignumber.js';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let alice: IKeyringPair;
+let bob: IKeyringPair;
+let charlie: IKeyringPair;
+
+describe.only('integration test: ext. confirmSponsorship():', () => {
+
+  before(async () => {
+    await usingApi(async (api) => {
+      const keyring = new Keyring({ type: 'sr25519' });
+      alice = keyring.addFromUri(`//Alice`);
+      bob = keyring.addFromUri(`//Bob`);
+      charlie = keyring.addFromUri(`//Charlie`);
+    });
+  });
+
+  it('Confirm collection sponsorship', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+  });
+  it('Add sponsor to a collection after the same sponsor was already added and confirmed', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Add new sponsor to a collection after another sponsor was already added and confirmed', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+    await setCollectionSponsorExpectSuccess(collectionId, charlie.address);
+  });
+
+  it('NFT: Transfer fees are paid by the sponsor after confirmation', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Mint token for unused address
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', zeroBalance.address);
+
+      // Transfer this tokens from unused address to Alice
+      const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 0);
+      const events = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result = getGenericResult(events);
+
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      expect(result.success).to.be.true;
+      expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+    });
+
+  });
+
+  it('Fungible: Transfer fees are paid by the sponsor after confirmation', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Mint token for unused address
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', zeroBalance.address);
+
+      // Transfer this tokens from unused address to Alice
+      const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+      const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result1 = getGenericResult(events1);
+
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      expect(result1.success).to.be.true;
+      expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+    });
+  });
+
+  it('ReFungible: Transfer fees are paid by the sponsor after confirmation', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Mint token for unused address
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', zeroBalance.address);
+
+      // Transfer this tokens from unused address to Alice
+      const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+      const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result1 = getGenericResult(events1);
+
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      expect(result1.success).to.be.true;
+      expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+    });
+  });
+
+  it('CreateItem fees are paid by the sponsor after confirmation', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    // Enable collection white list 
+    await enableWhiteListExpectSuccess(alice, collectionId);
+
+    // Enable public minting
+    await enablePublicMintingExpectSuccess(alice, collectionId);
+
+    // Create Item 
+    await usingApi(async (api) => {
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Add zeroBalance address to white list
+      await addToWhiteListExpectSuccess(alice, collectionId, zeroBalance.address);
+
+      // Mint token using unused address as signer
+      const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT', zeroBalance.address);
+
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+    });
+  });
+
+  it('NFT: Sponsoring is rate limited', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Mint token for alice
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
+
+      // Transfer this token from Alice to unused address and back
+      // Alice to Zero gets sponsored
+      const aliceToZero = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 0);
+      const events1 = await submitTransactionAsync(alice, aliceToZero);
+      const result1 = getGenericResult(events1);
+
+      // Second transfer should fail
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+      const zeroToAlice = api.tx.nft.transfer(alice.address, collectionId, itemId, 0);
+      const badTransaction = async function () { 
+        console.log = function () {};
+        console.error = function () {};
+        await submitTransactionAsync(zeroBalance, zeroToAlice);
+        delete console.log;
+        delete console.error;
+      };
+      await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Try again after Zero gets some balance - now it should succeed
+      const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
+      await submitTransactionAsync(alice, balancetx);
+      const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result2 = getGenericResult(events2);
+
+      expect(result1.success).to.be.true;
+      expect(result2.success).to.be.true;
+      expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+    });
+  });
+
+  it('Fungible: Sponsoring is rate limited', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Mint token for unused address
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', zeroBalance.address);
+
+      // Transfer this tokens in parts from unused address to Alice
+      const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+      const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result1 = getGenericResult(events1);
+
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      const badTransaction = async function () { 
+        console.log = function () {};
+        console.error = function () {};
+        await submitTransactionAsync(zeroBalance, zeroToAlice);
+        delete console.log;
+        delete console.error;
+      };
+
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Try again after Zero gets some balance - now it should succeed
+      const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
+      await submitTransactionAsync(alice, balancetx);
+      const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result2 = getGenericResult(events2);
+
+      expect(result1.success).to.be.true;
+      expect(result2.success).to.be.true;
+      expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+    });
+  });
+
+  it('ReFungible: Sponsoring is rate limited', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Mint token for alice
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', alice.address);
+
+      // Transfer this token from Alice to unused address and back
+      // Alice to Zero gets sponsored
+      const aliceToZero = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+      const events1 = await submitTransactionAsync(alice, aliceToZero);
+      const result1 = getGenericResult(events1);
+
+      // Second transfer should fail
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+      const zeroToAlice = api.tx.nft.transfer(alice.address, collectionId, itemId, 1);
+      const badTransaction = async function () { 
+        console.log = function () {};
+        console.error = function () {};
+        await submitTransactionAsync(zeroBalance, zeroToAlice);
+        delete console.log;
+        delete console.error;
+      };
+      await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Try again after Zero gets some balance - now it should succeed
+      const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
+      await submitTransactionAsync(alice, balancetx);
+      const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result2 = getGenericResult(events2);
+
+      expect(result1.success).to.be.true;
+      expect(result2.success).to.be.true;
+      expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+    });
+  });
+
+});
+
+describe.only('(!negative test!) integration test: ext. setCollectionSponsor():', () => {
+  before(async () => {
+    await usingApi(async (api) => {
+      const keyring = new Keyring({ type: 'sr25519' });
+      alice = keyring.addFromUri(`//Alice`);
+      bob = keyring.addFromUri(`//Bob`);
+      charlie = keyring.addFromUri(`//Charlie`);
+    });
+  });
+
+  it('(!negative test!) Confirm sponsorship for a collection that never existed', async () => {
+    // Find the collection that never existed
+    const collectionId = 0;
+    await usingApi(async (api) => {
+      const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+    });
+
+    await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+  });
+
+  it('(!negative test!) Confirm sponsorship using a non-sponsor address', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+
+    await usingApi(async (api) => {
+      const transfer = api.tx.balances.transfer(charlie.address, 1e15);
+      await submitTransactionAsync(alice, transfer);
+    });
+
+    await confirmSponsorshipExpectFailure(collectionId, '//Charlie');
+  });
+
+  it('(!negative test!) Confirm sponsorship using owner address', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectFailure(collectionId, '//Alice');
+  });
+
+  it('(!negative test!) Confirm sponsorship without sponsor being set with setCollectionSponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+  });
+    
+  it('(!negative test!) Confirm sponsorship in a collection that was destroyed', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await destroyCollectionExpectSuccess(collectionId);
+    await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+  });
+});
modifiedtests/src/createItem.test.tsdiffbeforeafterboth
--- a/tests/src/createItem.test.ts
+++ b/tests/src/createItem.test.ts
@@ -1,27 +1,34 @@
-import { assert } from 'chai';
-import { alicesPublicKey } from './accounts';
-import privateKey from './substrate/privateKey';
 import { default as usingApi } from './substrate/substrate-api';
-import waitNewBlocks from './substrate/wait-new-blocks';
+import { Keyring } from "@polkadot/api";
+import { IKeyringPair } from "@polkadot/types/types";
 import { 
   createCollectionExpectSuccess, 
   createItemExpectSuccess
 } from './util/helpers';
 
+let alice: IKeyringPair;
+
 describe('integration test: ext. createItem():', () => {
+  before(async () => {
+    await usingApi(async (api) => {
+      const keyring = new Keyring({ type: 'sr25519' });
+      alice = keyring.addFromUri(`//Alice`);
+    });
+  });
+
   it('Create new item in NFT collection', async () => {
     const createMode = 'NFT';
     const newCollectionID = await createCollectionExpectSuccess('0', '0', '0', createMode);
-    await createItemExpectSuccess(newCollectionID, createMode, '//Alice');
+    await createItemExpectSuccess(alice, newCollectionID, createMode);
   });
   it('Create new item in Fungible collection', async () => {
     const createMode = 'Fungible';
     const newCollectionID = await createCollectionExpectSuccess('0', '0', '0', createMode);
-    await createItemExpectSuccess(newCollectionID, createMode, '//Alice');
+    await createItemExpectSuccess(alice, newCollectionID, createMode);
   });
   it('Create new item in ReFungible collection', async () => {
     const createMode = 'ReFungible';
     const newCollectionID = await createCollectionExpectSuccess('0', '0', '0', createMode);
-    await createItemExpectSuccess(newCollectionID, createMode, '//Alice');
+    await createItemExpectSuccess(alice, newCollectionID, createMode);
   });
 });
modifiedtests/src/createMultipleItems.test.tsdiffbeforeafterboth
--- a/tests/src/createMultipleItems.test.ts
+++ b/tests/src/createMultipleItems.test.ts
@@ -11,7 +11,7 @@
 
 const idCollection = 12;
 
-describe('integration test: ext. createMultipleItems():', () => {
+describe.skip('integration test: ext. createMultipleItems():', () => {
   it('Create two NFT tokens in active NFT collection', async () => {
     await usingApi(async (api) => {
       const AitemListIndex = await api.query.nft.itemListIndex(idCollection);
modifiedtests/src/destroyCollection.test.tsdiffbeforeafterboth
--- a/tests/src/destroyCollection.test.ts
+++ b/tests/src/destroyCollection.test.ts
@@ -1,55 +1,12 @@
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
 import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
-import { createCollectionExpectSuccess, createCollectionExpectFailure } from "./util/helpers";
+import { createCollectionExpectSuccess, createCollectionExpectFailure, destroyCollectionExpectSuccess, destroyCollectionExpectFailure } from "./util/helpers";
 import type { AccountId, EventRecord } from '@polkadot/types/interfaces';
 import privateKey from './substrate/privateKey';
-import { nullPublicKey } from './accounts'; 
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
-
-function getDestroyResult(events: EventRecord[]): boolean {
-  let success: boolean = false;
-  events.forEach(({ phase, event: { data, method, section } }) => {
-    // console.log(`    ${phase}: ${section}.${method}:: ${data}`);
-    if (method == 'ExtrinsicSuccess') {
-      success = true;
-    }
-  });
-  return success;
-}
-
-async function destroyCollectionExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
-  await usingApi(async (api) => {
-    // Run the DestroyCollection transaction
-    const alicePrivateKey = privateKey(senderSeed);
-    const tx = api.tx.nft.destroyCollection(collectionId);
-    const events = await submitTransactionAsync(alicePrivateKey, tx);
-    const result = getDestroyResult(events);
-
-    // Get the collection 
-    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
-
-    // What to expect
-    expect(result).to.be.true;
-    expect(collection).to.be.not.null;
-    expect(collection.Owner).to.be.equal(nullPublicKey);
-  });
-}
-
-async function destroyCollectionExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
-  await usingApi(async (api) => {
-    // Run the DestroyCollection transaction
-    const alicePrivateKey = privateKey(senderSeed);
-    const tx = api.tx.nft.destroyCollection(collectionId);
-    const events = await submitTransactionAsync(alicePrivateKey, tx);
-    const result = getDestroyResult(events);
-
-    // What to expect
-    expect(result).to.be.false;
-  });
-}
 
 describe('integration test: ext. destroyCollection():', () => {
   it('NFT collection can be destroyed', async () => {
addedtests/src/setCollectionSponsor.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -0,0 +1,82 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi } from "./substrate/substrate-api";
+import { createCollectionExpectSuccess, setCollectionSponsorExpectSuccess, destroyCollectionExpectSuccess, setCollectionSponsorExpectFailure } from "./util/helpers";
+import { Keyring } from "@polkadot/api";
+import { IKeyringPair } from "@polkadot/types/types";
+import type { AccountId } from '@polkadot/types/interfaces';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let bob: IKeyringPair;
+
+describe('integration test: ext. setCollectionSponsor():', () => {
+
+  before(async () => {
+    await usingApi(async (api) => {
+      const keyring = new Keyring({ type: 'sr25519' });
+      bob = keyring.addFromUri(`//Bob`);
+    });
+  });
+
+  it('Set NFT collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Set Fungible collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Set ReFungible collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+
+  it('Set the same sponsor repeatedly', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Replace collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+
+    const keyring = new Keyring({ type: 'sr25519' });
+    const charlie = keyring.addFromUri(`//Charlie`);
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await setCollectionSponsorExpectSuccess(collectionId, charlie.address);
+  });
+});
+
+describe('(!negative test!) integration test: ext. setCollectionSponsor():', () => {
+  before(async () => {
+    await usingApi(async (api) => {
+      const keyring = new Keyring({ type: 'sr25519' });
+      bob = keyring.addFromUri(`//Bob`);
+    });
+  });
+
+  it('(!negative test!) Add sponsor with a non-owner', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectFailure(collectionId, bob.address, '//Bob');
+  });
+  it('(!negative test!) Add sponsor to a collection that never existed', async () => {
+    // Find the collection that never existed
+    const collectionId = 0;
+    await usingApi(async (api) => {
+      const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+    });
+
+    await setCollectionSponsorExpectFailure(collectionId, bob.address);
+  });
+  it('(!negative test!) Add sponsor to a collection that was destroyed', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await destroyCollectionExpectSuccess(collectionId);
+    await setCollectionSponsorExpectFailure(collectionId, bob.address);
+  });
+});
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -9,10 +9,12 @@
 import { ApiPromise, Keyring } from "@polkadot/api";
 import { default as usingApi, submitTransactionAsync } from "../substrate/substrate-api";
 import privateKey from '../substrate/privateKey';
-import { alicesPublicKey } from "../accounts";
+import { alicesPublicKey, nullPublicKey } from "../accounts";
 import { strToUTF16, utf16ToStr, hexToStr } from '../util/util';
 import { IKeyringPair } from "@polkadot/types/types";
 import { BigNumber } from 'bignumber.js';
+import { Struct, Enum } from '@polkadot/types/codec';
+import { u128 } from '@polkadot/types/primitive';
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
@@ -26,6 +28,12 @@
   collectionId: number
 };
 
+type CreateItemResult = {
+  success: boolean,
+  collectionId: number,
+  itemId: number
+};
+
 export function getGenericResult(events: EventRecord[]): GenericResult {
   let result: GenericResult = {
     success: false
@@ -57,6 +65,27 @@
   return result;
 }
 
+function getCreateItemResult(events: EventRecord[]): CreateItemResult {
+  let success = false;
+  let collectionId: number = 0;
+  let itemId: number = 0;
+  events.forEach(({ phase, event: { data, method, section } }) => {
+    // console.log(`    ${phase}: ${section}.${method}:: ${data}`);
+    if (method == 'ExtrinsicSuccess') {
+      success = true;
+    } else if ((section == 'nft') && (method == 'ItemCreated')) {
+      collectionId = parseInt(data[0].toString());
+      itemId = parseInt(data[1].toString());
+    }
+  });
+  let result: CreateItemResult = {
+    success,
+    collectionId,
+    itemId
+  }
+  return result;
+}
+
 export async function createCollectionExpectSuccess(name: string, description: string, tokenPrefix: string, mode: string): Promise<number> {
   let collectionId: number = 0;
   await usingApi(async (api) => {
@@ -123,20 +152,214 @@
   return unused; 
 }
 
-export async function createItemExpectSuccess(collectionId: number, createMode: string, senderSeed: string = '//Alice') {
+function getDestroyResult(events: EventRecord[]): boolean {
+  let success: boolean = false;
+  events.forEach(({ phase, event: { data, method, section } }) => {
+    // console.log(`    ${phase}: ${section}.${method}:: ${data}`);
+    if (method == 'ExtrinsicSuccess') {
+      success = true;
+    }
+  });
+  return success;
+}
+
+export async function destroyCollectionExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+    // Run the DestroyCollection transaction
+    const alicePrivateKey = privateKey(senderSeed);
+    const tx = api.tx.nft.destroyCollection(collectionId);
+    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const result = getDestroyResult(events);
+
+    // What to expect
+    expect(result).to.be.false;
+  });
+}
+
+export async function destroyCollectionExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+    // Run the DestroyCollection transaction
+    const alicePrivateKey = privateKey(senderSeed);
+    const tx = api.tx.nft.destroyCollection(collectionId);
+    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const result = getDestroyResult(events);
+
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result).to.be.true;
+    expect(collection).to.be.not.null;
+    expect(collection.Owner).to.be.equal(nullPublicKey);
+  });
+}
+
+export async function setCollectionSponsorExpectSuccess(collectionId: number, sponsor: string) {
   await usingApi(async (api) => {
 
-    const AItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
+    // Run the transaction
+    const alicePrivateKey = privateKey('//Alice');
+    const tx = api.tx.nft.setCollectionSponsor(collectionId, sponsor);
+    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const result = getGenericResult(events);
 
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result.success).to.be.true;
+    expect(collection.Sponsor.toString()).to.be.equal(sponsor.toString());
+    expect(collection.SponsorConfirmed).to.be.false;
+  });
+}
+
+export async function setCollectionSponsorExpectFailure(collectionId: number, sponsor: string, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const alicePrivateKey = privateKey(senderSeed);
+    const tx = api.tx.nft.setCollectionSponsor(collectionId, sponsor);
+    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const result = getGenericResult(events);
+
+    // What to expect
+    expect(result.success).to.be.false;
+  });
+}
+
+export async function confirmSponsorshipExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+
+    // Run the transaction
     const sender = privateKey(senderSeed);
-    const tx = api.tx.nft.createItem(collectionId, sender.address, createMode);
+    const tx = api.tx.nft.confirmSponsorship(collectionId);
     const events = await submitTransactionAsync(sender, tx);
     const result = getGenericResult(events);
-  
+
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result.success).to.be.true;
+    expect(collection.Sponsor).to.be.equal(sender.address);
+    expect(collection.SponsorConfirmed).to.be.true;
+  });
+}
+
+export async function confirmSponsorshipExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const sender = privateKey(senderSeed);
+    const tx = api.tx.nft.confirmSponsorship(collectionId);
+    const events = await submitTransactionAsync(sender, tx);
+    const result = getGenericResult(events);
+
+    // What to expect
+    expect(result.success).to.be.false;
+  });
+}
+
+export interface CreateFungibleData extends Struct {
+  readonly value: u128;
+};
+
+export interface CreateReFungibleData extends Struct {};
+export interface CreateNftData extends Struct {};
+
+export interface CreateItemData extends Enum {
+  NFT: CreateNftData,
+  Fungible: CreateFungibleData,
+  ReFungible: CreateReFungibleData
+};
+
+export async function createItemExpectSuccess(sender: IKeyringPair, collectionId: number, createMode: string, owner: string = '') {
+  let newItemId: number = 0;
+  await usingApi(async (api) => {
+    const AItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
+    const Aitem: any = (await api.query.nft.fungibleItemList(collectionId, owner)).toJSON();    
+    const AItemBalance = new BigNumber(Aitem.Value);
+
+    if (owner === '') owner = sender.address;
+
+    let tx;
+    if (createMode == 'Fungible') {
+      let createData = {fungible: {value: 10}};
+      tx = api.tx.nft.createItem(collectionId, owner, createData);
+    }
+    else {
+      tx = api.tx.nft.createItem(collectionId, owner, createMode);
+    }
+    const events = await submitTransactionAsync(sender, tx);
+    const result = getCreateItemResult(events);
+
     const BItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
+    const Bitem: any = (await api.query.nft.fungibleItemList(collectionId, owner)).toJSON();    
+    const BItemBalance = new BigNumber(Bitem.Value);
 
     // What to expect
     expect(result.success).to.be.true;
-    expect(BItemCount).to.be.equal(AItemCount+1);
+    if (createMode == 'Fungible') {
+      expect(BItemBalance.minus(AItemBalance).toNumber()).to.be.equal(10);
+    }
+    else {
+      expect(BItemCount).to.be.equal(AItemCount+1);
+    }
+    expect(collectionId).to.be.equal(result.collectionId);
+    expect(BItemCount).to.be.equal(result.itemId);
+    newItemId = result.itemId;
+  });
+  return newItemId;
+}
+
+export async function enableWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number) {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const tx = api.tx.nft.setPublicAccessMode(collectionId, 'WhiteList');
+    const events = await submitTransactionAsync(sender, tx);
+    const result = getGenericResult(events);
+
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result.success).to.be.true;
+    expect(collection.Access).to.be.equal('WhiteList');
+  });
+}
+
+export async function enablePublicMintingExpectSuccess(sender: IKeyringPair, collectionId: number) {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const tx = api.tx.nft.setMintPermission(collectionId, true);
+    const events = await submitTransactionAsync(sender, tx);
+    const result = getGenericResult(events);
+
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result.success).to.be.true;
+    expect(collection.MintMode).to.be.equal(true);
   });
 }
+
+export async function addToWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string) {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const tx = api.tx.nft.addToWhiteList(collectionId, address);
+    const events = await submitTransactionAsync(sender, tx);
+    const result = getGenericResult(events);
+
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result.success).to.be.true;
+    expect(collection.MintMode).to.be.equal(true);
+  });
+}
+