git.delta.rocks / unique-network / refs/commits / 537f33b30fa3

difftreelog

Merge branch 'develop' into feature/NFTPAR-268_inregration_test_broken

Greg Zaitsev2020-12-30parents: #31c9ea3 #a9dbb97.patch.diff
in: master

24 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
--- a/pallets/nft/src/lib.rs
+++ b/pallets/nft/src/lib.rs
@@ -136,7 +136,7 @@
     pub offchain_schema: Vec<u8>,
     pub schema_version: SchemaVersion,
     pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender
-    pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship
+    pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.
     pub limits: CollectionLimits, // Collection private restrictions 
     pub variable_on_chain_schema: Vec<u8>, //
     pub const_on_chain_schema: Vec<u8>, //
@@ -145,7 +145,6 @@
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 pub struct NftItemType<AccountId> {
-    pub collection: CollectionId,
     pub owner: AccountId,
     pub const_data: Vec<u8>,
     pub variable_data: Vec<u8>,
@@ -153,45 +152,28 @@
 
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct FungibleItemType<AccountId> {
-    pub collection: CollectionId,
-    pub owner: AccountId,
+pub struct FungibleItemType {
     pub value: u128,
 }
 
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 pub struct ReFungibleItemType<AccountId> {
-    pub collection: CollectionId,
     pub owner: Vec<Ownership<AccountId>>,
     pub const_data: Vec<u8>,
     pub variable_data: Vec<u8>,
 }
 
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct ApprovePermissions<AccountId> {
-    pub approved: AccountId,
-    pub amount: u128,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct VestingItem<AccountId, Moment> {
-    pub sender: AccountId,
-    pub recipient: AccountId,
-    pub collection_id: CollectionId,
-    pub item_id: TokenId,
-    pub amount: u64,
-    pub vesting_date: Moment,
-}
-
-#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
-#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
-pub struct BasketItem<AccountId, BlockNumber> {
-    pub address: AccountId,
-    pub start_block: BlockNumber,
-}
+// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
+// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
+// pub struct VestingItem<AccountId, Moment> {
+//     pub sender: AccountId,
+//     pub recipient: AccountId,
+//     pub collection_id: CollectionId,
+//     pub item_id: TokenId,
+//     pub amount: u64,
+//     pub vesting_date: Moment,
+// }
 
 #[derive(Encode, Decode, Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
@@ -270,6 +252,7 @@
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
 #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
 pub struct CreateFungibleData {
+    pub value: u128,
 }
 
 #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
@@ -424,12 +407,12 @@
         /// Balance owner per collection map
         pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;
 
-        /// second parameter: item id + owner account id
-        pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;
+        /// second parameter: item id + owner account id + spender account id
+        pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;
 
         /// Item collections
         pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;
-        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;
+        pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;
         pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;
 
         /// Index list
@@ -437,7 +420,7 @@
 
         /// Tokens transfer baskets
         pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;
-        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;
+        pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;
         pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;
 
         // Contract Sponsorship and Ownership
@@ -455,16 +438,16 @@
                 <Module<T>>::init_collection(_c);
             }
 
-            for (_num, _q, _i) in &config.nft_item_id {
-                <Module<T>>::init_nft_token(_i);
+            for (_num, _c, _i) in &config.nft_item_id {
+                <Module<T>>::init_nft_token(*_c, _i);
             }
 
-            for (_num, _q, _i) in &config.fungible_item_id {
-                <Module<T>>::init_fungible_token(_i);
+            for (collection_id, account_id, fungible_item) in &config.fungible_item_id {
+                <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);
             }
 
-            for (_num, _q, _i) in &config.refungible_item_id {
-                <Module<T>>::init_refungible_token(_i);
+            for (_num, _c, _i) in &config.refungible_item_id {
+                <Module<T>>::init_refungible_token(*_c, _i);
             }
         })
     }
@@ -591,7 +574,7 @@
                 offchain_schema: Vec::new(),
                 schema_version: SchemaVersion::ImageURL,
                 sponsor: T::AccountId::default(),
-                unconfirmed_sponsor: T::AccountId::default(),
+                sponsor_confirmed: false,
                 variable_on_chain_schema: Vec::new(),
                 const_on_chain_schema: Vec::new(),
                 limits: CollectionLimits::default(),
@@ -622,7 +605,7 @@
             Self::check_owner_permissions(collection_id, sender)?;
 
             <AddressTokens<T>>::remove_prefix(collection_id);
-            <ApprovedList<T>>::remove_prefix(collection_id);
+            <Allowances<T>>::remove_prefix(collection_id);
             <Balance<T>>::remove_prefix(collection_id);
             <ItemListIndex>::remove(collection_id);
             <AdminList<T>>::remove(collection_id);
@@ -850,7 +833,8 @@
             let mut target_collection = <Collection<T>>::get(collection_id);
             ensure!(sender == target_collection.owner, Error::<T>::NoPermission);
 
-            target_collection.unconfirmed_sponsor = new_sponsor;
+            target_collection.sponsor = new_sponsor;
+            target_collection.sponsor_confirmed = false;
             <Collection<T>>::insert(collection_id, target_collection);
 
             Ok(())
@@ -870,10 +854,9 @@
             ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);
 
             let mut target_collection = <Collection<T>>::get(collection_id);
-            ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);
+            ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);
 
-            target_collection.sponsor = target_collection.unconfirmed_sponsor;
-            target_collection.unconfirmed_sponsor = T::AccountId::default();
+            target_collection.sponsor_confirmed = true;
             <Collection<T>>::insert(collection_id, target_collection);
 
             Ok(())
@@ -898,6 +881,7 @@
             ensure!(sender == target_collection.owner, Error::<T>::NoPermission);
 
             target_collection.sponsor = T::AccountId::default();
+            target_collection.sponsor_confirmed = false;
             <Collection<T>>::insert(collection_id, target_collection);
 
             Ok(())
@@ -998,7 +982,7 @@
         /// 
         /// * item_id: ID of NFT to burn.
         #[weight = T::WeightInfo::burn_item()]
-        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {
+        pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {
 
             let sender = ensure_signed(origin)?;
             Self::collection_exists(collection_id)?;
@@ -1016,7 +1000,7 @@
             match target_collection.mode
             {
                 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,
-                CollectionMode::Fungible(_)  => Self::burn_fungible_item(collection_id, item_id)?,
+                CollectionMode::Fungible(_)  => Self::burn_fungible_item(&sender, collection_id, value)?,
                 CollectionMode::ReFungible(_)  => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,
                 _ => ()
             };
@@ -1072,7 +1056,7 @@
             match target_collection.mode
             {
                 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,
-                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,
+                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,
                 CollectionMode::ReFungible(_)  => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,
                 _ => ()
             };
@@ -1096,7 +1080,7 @@
         /// 
         /// * item_id: ID of the item.
         #[weight = T::WeightInfo::approve()]
-        pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {
+        pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {
 
             let sender = ensure_signed(origin)?;
 
@@ -1108,28 +1092,15 @@
 
             if target_collection.access == AccessMode::WhiteList {
                 Self::check_white_list(collection_id, &sender)?;
-                Self::check_white_list(collection_id, &approved)?;
+                Self::check_white_list(collection_id, &spender)?;
             }
-
-            // amount param stub
-            let amount = 100000000;
-
-            let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));
-            if list_exists {
-
-                let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));
-                let item_contains = list.iter().any(|i| i.approved == approved);
-
-                if !item_contains {
-                    list.push(ApprovePermissions { approved: approved.clone(), amount: amount });
-                    <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);
-                }
-            } else {
 
-                let mut list = Vec::new();
-                list.push(ApprovePermissions { approved: approved.clone(), amount: amount });
-                <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);
+            let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));
+            let mut allowance: u128 = amount;
+            if allowance_exists {
+                allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));
             }
+            <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);
 
             Ok(())
         }
@@ -1159,15 +1130,12 @@
             let sender = ensure_signed(origin)?;
             let mut appoved_transfer = false;
 
-            // Check approve
-            if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {
-                let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));
-                let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());
-                if opt_item.is_some()
-                {
-                    appoved_transfer = true;
-                    ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);
-                }
+            // Check approval
+            let mut approval: u128 = 0;
+            if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {
+                approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));
+                ensure!(approval >= value, Error::<T>::TokenValueNotEnough);
+                appoved_transfer = true;
             }
 
             let target_collection = <Collection<T>>::get(collection_id);
@@ -1177,23 +1145,25 @@
 
             // Transfer permissions check         
             ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),
-            Error::<T>::NoPermission);
+                Error::<T>::NoPermission);
 
             if target_collection.access == AccessMode::WhiteList {
                 Self::check_white_list(collection_id, &sender)?;
                 Self::check_white_list(collection_id, &recipient)?;
             }
 
-            // remove approve
-            let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))
-                .into_iter().filter(|i| i.approved != sender.clone()).collect();
-            <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);
-
+            // Reduce approval by transferred amount or remove if remaining approval drops to 0
+            if approval - value > 0 {
+                <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);
+            }
+            else {
+                <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));
+            }
 
             match target_collection.mode
             {
                 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,
-                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,
+                CollectionMode::Fungible(_)  => Self::transfer_fungible(collection_id, value, &from, &recipient)?,
                 CollectionMode::ReFungible(_)  => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,
                 _ => ()
             };
@@ -1627,22 +1597,15 @@
         {
             CreateItemData::NFT(data) => {
                 let item = NftItemType {
-                    collection: collection_id,
                     owner,
                     const_data: data.const_data,
                     variable_data: data.variable_data
                 };
 
-                Self::add_nft_item(item)?;
+                Self::add_nft_item(collection_id, item)?;
             },
-            CreateItemData::Fungible(_) => {
-                let item = FungibleItemType {
-                    collection: collection_id,
-                    owner,
-                    value: (10 as u128).pow(collection.decimal_points as u32)
-                };
-
-                Self::add_fungible_item(item)?;
+            CreateItemData::Fungible(data) => {
+                Self::add_fungible_item(collection_id, &owner, data.value)?;
             },
             CreateItemData::ReFungible(data) => {
                 let mut owner_list = Vec::new();
@@ -1650,13 +1613,12 @@
                 owner_list.push(Ownership {owner: owner.clone(), fraction: value});
 
                 let item = ReFungibleItemType {
-                    collection: collection_id,
                     owner: owner_list,
                     const_data: data.const_data,
                     variable_data: data.variable_data
                 };
 
-                Self::add_refungible_item(item)?;
+                Self::add_refungible_item(collection_id, item)?;
             }
         };
 
@@ -1666,33 +1628,31 @@
         Ok(())
     }
 
-    fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {
-        let current_index = <ItemListIndex>::get(item.collection)
-            .checked_add(1)
-            .ok_or(Error::<T>::NumOverflow)?;
-        let itemcopy = item.clone();
-        let owner = item.owner.clone();
+    fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {
 
-        Self::add_token_index(item.collection, current_index, owner.clone())?;
+        // Does new owner already have an account?
+        let mut balance: u128 = 0;
+        if <FungibleItemList<T>>::contains_key(collection_id, owner) {
+            balance = <FungibleItemList<T>>::get(collection_id, owner).value;
+        } 
 
-        <ItemListIndex>::insert(item.collection, current_index);
-        <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);
+        // Mint 
+        let item = FungibleItemType {
+            value: balance + value
+        };
+        <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);
 
-        // Add current block
-        let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();
-        <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);
-        
         // Update balance
-        let new_balance = <Balance<T>>::get(item.collection, owner.clone())
-            .checked_add(item.value)
+        let new_balance = <Balance<T>>::get(collection_id, owner)
+            .checked_add(value)
             .ok_or(Error::<T>::NumOverflow)?;
-        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);
+        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);
 
         Ok(())
     }
 
-    fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {
-        let current_index = <ItemListIndex>::get(item.collection)
+    fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {
+        let current_index = <ItemListIndex>::get(collection_id)
             .checked_add(1)
             .ok_or(Error::<T>::NumOverflow)?;
         let itemcopy = item.clone();
@@ -1700,40 +1660,31 @@
         let value = item.owner.first().unwrap().fraction;
         let owner = item.owner.first().unwrap().owner.clone();
 
-        Self::add_token_index(item.collection, current_index, owner.clone())?;
+        Self::add_token_index(collection_id, current_index, owner.clone())?;
 
-        <ItemListIndex>::insert(item.collection, current_index);
-        <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);
-
-        // Add current block
-        let block_number: T::BlockNumber = 0.into();
-        <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);
+        <ItemListIndex>::insert(collection_id, current_index);
+        <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);
 
         // Update balance
-        let new_balance = <Balance<T>>::get(item.collection, owner.clone())
+        let new_balance = <Balance<T>>::get(collection_id, owner.clone())
             .checked_add(value)
             .ok_or(Error::<T>::NumOverflow)?;
-        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);
+        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);
 
         Ok(())
     }
 
-    fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {
-        let current_index = <ItemListIndex>::get(item.collection)
+    fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {
+        let current_index = <ItemListIndex>::get(collection_id)
             .checked_add(1)
             .ok_or(Error::<T>::NumOverflow)?;
 
         let item_owner = item.owner.clone();
-        let collection_id = item.collection.clone();
         Self::add_token_index(collection_id, current_index, item.owner.clone())?;
 
         <ItemListIndex>::insert(collection_id, current_index);
         <NftItemList<T>>::insert(collection_id, current_index, item);
 
-        // Add current block
-        let block_number: T::BlockNumber = 0.into();
-        <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);
-
         // Update balance
         let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())
             .checked_add(1)
@@ -1760,9 +1711,6 @@
             .next()
             .unwrap();
         Self::remove_token_index(collection_id, item_id, owner.clone())?;
-
-        // remove approve list
-        <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));
 
         // update balance
         let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())
@@ -1783,9 +1731,6 @@
         let item = <NftItemList<T>>::get(collection_id, item_id);
         Self::remove_token_index(collection_id, item_id, item.owner.clone())?;
 
-        // remove approve list
-        <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));
-
         // update balance
         let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())
             .checked_sub(1)
@@ -1796,24 +1741,27 @@
         Ok(())
     }
 
-    fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {
+    fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {
         ensure!(
-            <FungibleItemList<T>>::contains_key(collection_id, item_id),
+            <FungibleItemList<T>>::contains_key(collection_id, owner),
             Error::<T>::TokenNotFound
         );
-        let item = <FungibleItemList<T>>::get(collection_id, item_id);
-        Self::remove_token_index(collection_id, item_id, item.owner.clone())?;
-
-        // remove approve list
-        <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));
+        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);
+        ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);
 
         // update balance
-        let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())
-            .checked_sub(item.value)
+        let new_balance = <Balance<T>>::get(collection_id, owner)
+            .checked_sub(value)
             .ok_or(Error::<T>::NumOverflow)?;
-        <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);
+        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);
 
-        <FungibleItemList<T>>::remove(collection_id, item_id);
+        if balance.value - value > 0 {
+            balance.value -= value;
+            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);
+        }
+        else {
+            <FungibleItemList<T>>::remove(collection_id, owner);
+        }
 
         Ok(())
     }
@@ -1874,7 +1822,7 @@
                 <NftItemList<T>>::get(collection_id, item_id).owner == subject
             }
             CollectionMode::Fungible(_) => {
-                <FungibleItemList<T>>::get(collection_id, item_id).owner == subject
+                <FungibleItemList<T>>::contains_key(collection_id, &subject)
             }
             CollectionMode::ReFungible(_) => {
                 <ReFungibleItemList<T>>::get(collection_id, item_id)
@@ -1895,86 +1843,31 @@
 
     fn transfer_fungible(
         collection_id: CollectionId,
-        item_id: TokenId,
         value: u128,
-        owner: T::AccountId,
-        new_owner: T::AccountId,
+        owner: &T::AccountId,
+        recipient: &T::AccountId,
     ) -> DispatchResult {
         ensure!(
-            <FungibleItemList<T>>::contains_key(collection_id, item_id),
+            <FungibleItemList<T>>::contains_key(collection_id, owner),
             Error::<T>::TokenNotFound
         );
 
-        let full_item = <FungibleItemList<T>>::get(collection_id, item_id);
-        let amount = full_item.value;
+        let mut balance = <FungibleItemList<T>>::get(collection_id, owner);
+        ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);
 
-        ensure!(amount >= value, Error::<T>::TokenValueTooLow);
+        // Send balance to recipient (updates balanceOf of recipient)
+        Self::add_fungible_item(collection_id, recipient, value)?;
 
-        // update balance
-        let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())
-            .checked_sub(value)
-            .ok_or(Error::<T>::NumOverflow)?;
-        <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);
+        // update balanceOf of sender
+        <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);
 
-        let mut new_owner_account_id = 0;
-        let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());
-        if new_owner_items.len() > 0 {
-            new_owner_account_id = new_owner_items[0];
+        // Reduce or remove sender
+        if balance.value == value {
+            <FungibleItemList<T>>::remove(collection_id, owner);
         }
-
-        // transfer
-        if amount == value && new_owner_account_id == 0 {
-            // change owner
-            // new owner do not have account
-            let mut new_full_item = full_item.clone();
-            new_full_item.owner = new_owner.clone();
-            <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);
-
-            // update balance
-            let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())
-                .checked_add(value)
-                .ok_or(Error::<T>::NumOverflow)?;
-            <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);
-
-            // update index collection
-            Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;
-        } else {
-            let mut new_full_item = full_item.clone();
-            new_full_item.value -= value;
-
-            // separate amount
-            if new_owner_account_id > 0 {
-                // new owner has account
-                let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);
-                item.value += value;
-
-                // update balance
-                let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())
-                    .checked_add(value)
-                    .ok_or(Error::<T>::NumOverflow)?;
-                <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);
-
-                <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);
-            } else {
-                // new owner do not have account
-                let item = FungibleItemType {
-                    collection: collection_id,
-                    owner: new_owner.clone(),
-                    value
-                };
-
-                Self::add_fungible_item(item)?;
-            }
-
-            if amount == value {
-                Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;
-
-                // remove approve list
-                <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));
-                <FungibleItemList<T>>::remove(collection_id, item_id);
-            }
-
-            <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);
+        else {
+            balance.value -= value;
+            <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);
         }
 
         Ok(())
@@ -2009,10 +1902,10 @@
             .ok_or(Error::<T>::NumOverflow)?;
         <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);
 
-        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())
+        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())
             .checked_add(value)
             .ok_or(Error::<T>::NumOverflow)?;
-        <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);
+        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);
 
         let old_owner = item.owner.clone();
         let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);
@@ -2089,10 +1982,10 @@
             .ok_or(Error::<T>::NumOverflow)?;
         <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);
 
-        let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())
+        let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())
             .checked_add(1)
             .ok_or(Error::<T>::NumOverflow)?;
-        <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);
+        <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);
 
         // change owner
         let old_owner = item.owner.clone();
@@ -2102,8 +1995,6 @@
         // update index collection
         Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;
 
-        // reset approved list
-        <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));
         Ok(())
     }
     
@@ -2115,7 +2006,6 @@
         match mode {
             CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),
             CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),
-            CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),
             _ => ()
         };
         
@@ -2177,13 +2067,12 @@
         CreatedCollectionCount::put(next_id);
     }
 
-    fn init_nft_token(item: &NftItemType<T::AccountId>) {
-        let current_index = <ItemListIndex>::get(item.collection)
+    fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {
+        let current_index = <ItemListIndex>::get(collection_id)
             .checked_add(1)
             .unwrap();
 
         let item_owner = item.owner.clone();
-        let collection_id = item.collection.clone();
         Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();
 
         <ItemListIndex>::insert(collection_id, current_index);
@@ -2195,40 +2084,39 @@
         <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);
     }
 
-    fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {
-        let current_index = <ItemListIndex>::get(item.collection)
+    fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {
+        let current_index = <ItemListIndex>::get(collection_id)
             .checked_add(1)
             .unwrap();
-        let owner = item.owner.clone();
 
-        Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();
+        Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();
 
-        <ItemListIndex>::insert(item.collection, current_index);
+        <ItemListIndex>::insert(collection_id, current_index);
 
         // Update balance
-        let new_balance = <Balance<T>>::get(item.collection, owner.clone())
+        let new_balance = <Balance<T>>::get(collection_id, owner)
             .checked_add(item.value)
             .unwrap();
-        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);
+        <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);
     }
 
-    fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {
-        let current_index = <ItemListIndex>::get(item.collection)
+    fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {
+        let current_index = <ItemListIndex>::get(collection_id)
             .checked_add(1)
             .unwrap();
 
         let value = item.owner.first().unwrap().fraction;
         let owner = item.owner.first().unwrap().owner.clone();
 
-        Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();
+        Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();
 
-        <ItemListIndex>::insert(item.collection, current_index);
+        <ItemListIndex>::insert(collection_id, current_index);
 
         // Update balance
-        let new_balance = <Balance<T>>::get(item.collection, owner.clone())
+        let new_balance = <Balance<T>>::get(collection_id, owner.clone())
             .checked_add(value)
             .unwrap();
-        <Balance<T>>::insert(item.collection, owner.clone(), new_balance);
+        <Balance<T>>::insert(collection_id, owner.clone(), new_balance);
     }
 
     fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {
@@ -2399,99 +2287,111 @@
         // };
         let fee = Self::traditional_fee(len, info, tip);
 
+        // Only mess with balances if fee is not zero.
+        if fee.is_zero() {
+            return Ok((fee, None));
+        }
+
         // Determine who is paying transaction fee based on ecnomic model
         // Parse call to extract collection ID and access collection sponsor
         let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {
             Some(Call::create_item(collection_id, _owner, _properties)) => {
 
                 // check free create limit
-                if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)
+                if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&
+                   (<Collection<T>>::get(collection_id).sponsor_confirmed)
                 {
                     <Collection<T>>::get(collection_id).sponsor
                 } else {
                     T::AccountId::default()
                 }
             }
-            Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {
+            Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {
                 
-                let _collection_limits = <Collection<T>>::get(collection_id).limits;
-                let _collection_mode = <Collection<T>>::get(collection_id).mode;
+                let mut sponsor_transfer = false;
+                if <Collection<T>>::get(collection_id).sponsor_confirmed {
 
-                // sponsor timeout
-                let sponsor_transfer = match _collection_mode {
-                    CollectionMode::NFT => {
+                    let collection_limits = <Collection<T>>::get(collection_id).limits;
+                    let collection_mode = <Collection<T>>::get(collection_id).mode;
+    
+                    // sponsor timeout
+                    let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
+                    sponsor_transfer = match collection_mode {
+                        CollectionMode::NFT => {
+    
+                            // get correct limit
+                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
+                                collection_limits.sponsor_transfer_timeout
+                            } else {
+                                ChainLimit::get().nft_sponsor_transfer_timeout
+                            };
+    
+                            let mut sponsored = true;
+                            if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {
+                                let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);
+                                let limit_time = last_tx_block + limit.into();
+                                if block_number <= limit_time {
+                                    sponsored = false;
+                                }
+                            }
+                            if sponsored {
+                                <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);
+                            }
 
-                        // get correct limit
-                        let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {
-                            _collection_limits.sponsor_transfer_timeout
-                        } else {
-                            ChainLimit::get().nft_sponsor_transfer_timeout
-                        };
-
-                        let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);
-                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
-                        let limit_time = basket + limit.into();
-                        if block_number >= limit_time {
-                            <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);
-                            true
-                        }
-                        else {
-                            false
+                            sponsored
                         }
-                    }
-                    CollectionMode::Fungible(_) => {
-
-                        // get correct limit
-                        let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {
-                            _collection_limits.sponsor_transfer_timeout
-                        } else {
-                            ChainLimit::get().fungible_sponsor_transfer_timeout
-                        };
-
-                        let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);
-                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
-                        if basket.iter().any(|i| i.address == _new_owner.clone())
-                        {
-                            let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();
-                            let limit_time = item.start_block + limit.into();
-                            if block_number >= limit_time {
-                                basket.retain(|x| x.address == item.address);
-                                basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });
-                                <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);
-                                true
+                        CollectionMode::Fungible(_) => {
+    
+                            // get correct limit
+                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
+                                collection_limits.sponsor_transfer_timeout
+                            } else {
+                                ChainLimit::get().fungible_sponsor_transfer_timeout
+                            };
+    
+                            let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
+                            let mut sponsored = true;
+                            if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {
+                                let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);
+                                let limit_time = last_tx_block + limit.into();
+                                if block_number <= limit_time {
+                                    sponsored = false;
+                                }
                             }
-                            else {
-                                false
+                            if sponsored {
+                                <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);
                             }
+
+                            sponsored
                         }
-                        else {
-                            basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});
-                            true
+                        CollectionMode::ReFungible(_) => {
+    
+                            // get correct limit
+                            let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
+                                collection_limits.sponsor_transfer_timeout
+                            } else {
+                                ChainLimit::get().refungible_sponsor_transfer_timeout
+                            };
+    
+                            let mut sponsored = true;
+                            if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {
+                                let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);
+                                let limit_time = last_tx_block + limit.into();
+                                if block_number <= limit_time {
+                                    sponsored = false;
+                                }
+                            }
+                            if sponsored {
+                                <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);
+                            }
+
+                            sponsored
                         }
-                    }
-                    CollectionMode::ReFungible(_) => {
-
-                        // get correct limit
-                        let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {
-                            _collection_limits.sponsor_transfer_timeout
-                        } else {
-                            ChainLimit::get().refungible_sponsor_transfer_timeout
-                        };
-
-                        let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);
-                        let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
-                        let limit_time = basket + limit.into();
-                        if block_number >= limit_time {
-                            <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);
-                            true
-                        } else {
+                        _ => {
                             false
-                        }
-                    }
-                    _ => {
-                        false
-                    },
-                };
+                        },
+                    };
+                }
 
                 if !sponsor_transfer {
                     T::AccountId::default()
@@ -2568,11 +2468,6 @@
         let mut who_pays_fee: T::AccountId = sponsor.clone();
         if sponsor == T::AccountId::default() {
             who_pays_fee = who.clone();
-        }
-
-        // Only mess with balances if fee is not zero.
-        if fee.is_zero() {
-            return Ok((fee, None));
         }
 
         match <T as transaction_payment::Trait>::Currency::withdraw(
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/loadtester-src/.gitignorediffbeforeafterboth
--- /dev/null
+++ b/tests/loadtester-src/.gitignore
@@ -0,0 +1,9 @@
+# Ignore build artifacts from the local tests sub-crate.
+/target/
+
+# Ignore backup files creates by cargo fmt.
+**/*.rs.bk
+
+# Remove Cargo.lock when creating an executable, leave it for libraries
+# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
+Cargo.lock
\ No newline at end of file
addedtests/loadtester-src/Cargo.tomldiffbeforeafterboth
--- /dev/null
+++ b/tests/loadtester-src/Cargo.toml
@@ -0,0 +1,36 @@
+[package]
+name = "loadtester"
+version = "0.1.0"
+authors = ["[your_name] <[your_email]>"]
+edition = "2018"
+
+[dependencies]
+ink_primitives = { version = "3.0.0-rc2", default-features = false }
+ink_metadata = { version = "3.0.0-rc2", default-features = false, features = ["derive"], optional = true }
+ink_env = { version = "3.0.0-rc2", default-features = false }
+ink_storage = { version = "3.0.0-rc2", default-features = false }
+ink_lang = { version = "3.0.0-rc2", default-features = false }
+ink_prelude = { version = "3.0.0-rc2", default-features = false }
+
+scale = { package = "parity-scale-codec", version = "1.3", default-features = false, features = ["derive"] }
+scale-info = { version = "0.4.1", default-features = false, features = ["derive"], optional = true }
+
+[lib]
+name = "loadtester"
+path = "lib.rs"
+crate-type = [
+	# Used for normal contract Wasm blobs.
+	"cdylib",
+]
+
+[features]
+default = ["std"]
+std = [
+    "ink_metadata/std",
+    "ink_env/std",
+    "ink_storage/std",
+    "ink_primitives/std",
+    "scale/std",
+    "scale-info/std",
+]
+ink-as-dependency = []
addedtests/loadtester-src/lib.rsdiffbeforeafterboth
--- /dev/null
+++ b/tests/loadtester-src/lib.rs
@@ -0,0 +1,53 @@
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use ink_lang as ink;
+
+#[ink::contract]
+mod loadtester {
+    use ink_storage::collections::Vec as InkVec;
+
+    #[ink(storage)]
+    pub struct LoadTester {
+        vector: InkVec<u64>,
+    }
+
+    impl LoadTester {
+        #[ink(constructor)]
+        pub fn new() -> Self {
+            Self {
+                vector: InkVec::new(),
+            }
+        }
+
+        #[ink(message)]
+        pub fn bloat(&mut self, count: u64){
+            for i in 1..count+1 {
+                self.vector.push(i);
+            }
+        }
+
+        #[ink(message)]
+        pub fn get(&self) -> u128 {
+            let mut sum: u128 = 0;
+            for num in self.vector.iter() {
+                sum += *num as u128;
+            }
+            sum
+        }
+    }
+
+    #[cfg(test)]
+    mod tests {
+       
+        use super::*;
+
+        #[test]
+        fn it_works() {
+            let mut lt = LoadTester::new();
+            lt.bloat(4);
+            assert_eq!(lt.get(), [1,2,3,4]);
+            lt.bloat(3);
+            assert_eq!(lt.get(), [1,2,3,4,1,2,3]);
+        }
+    }
+}
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -16,7 +16,8 @@
     "typescript": "^3.9.7"
   },
   "scripts": {
-    "test": "mocha --timeout 9999999 -r ts-node/register ./**/*.test.ts"
+    "test": "mocha --timeout 9999999 -r ts-node/register ./**/*.test.ts",
+    "load": "mocha --timeout 9999999 -r ts-node/register ./**/*.load.ts"
   },
   "author": "",
   "license": "Apache 2.0",
addedtests/src/change-collection-owner.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/change-collection-owner.test.ts
@@ -0,0 +1,59 @@
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import privateKey from './substrate/privateKey';
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
+import { createCollectionExpectSuccess, createCollectionExpectFailure } from "./util/helpers";
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+describe('Integration Test changeCollectionOwner(collection_id, new_owner):', () => {
+  it('Changing owner changes owner.', async () => {
+    await usingApi(async api => {
+      const collectionId = await createCollectionExpectSuccess();
+      const alice = privateKey('//Alice');
+      const bob = privateKey('//Bob');
+
+      const collection: any = (await api.query.nft.collection(collectionId));
+      expect(collection.Owner.toString()).to.be.eq(alice.address);
+
+      const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
+      await submitTransactionAsync(alice, changeOwnerTx);
+
+      const collectionAfterOwnerChange: any = (await api.query.nft.collection(collectionId));
+      expect(collectionAfterOwnerChange.Owner.toString()).to.be.eq(bob.address);
+    });
+  });
+});
+
+describe('Negative Integration Test changeCollectionOwner(collection_id, new_owner):', () => {
+  it(`Not owner can't change owner.`, async () => {
+    await usingApi(async api => {
+      const collectionId = await createCollectionExpectSuccess();
+      const alice = privateKey('//Alice');
+      const bob = privateKey('//Bob');
+
+      const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
+      await expect(submitTransactionExpectFailAsync(bob, changeOwnerTx)).to.be.rejected;
+
+      const collectionAfterOwnerChange: any = (await api.query.nft.collection(collectionId));
+      expect(collectionAfterOwnerChange.Owner.toString()).to.be.eq(alice.address);
+
+      // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
+      await createCollectionExpectSuccess();
+    });
+  });
+  it(`Can't change owner of not existing collection.`, async () => {
+    await usingApi(async api => {
+      const collectionId = (1<<32) - 1;
+      const alice = privateKey('//Alice');
+      const bob = privateKey('//Bob');
+
+      const changeOwnerTx = api.tx.nft.changeCollectionOwner(collectionId, bob.address);
+      await expect(submitTransactionExpectFailAsync(alice, changeOwnerTx)).to.be.rejected;
+
+      // Verifying that nothing bad happened (network is live, new collections can be created, etc.)
+      await createCollectionExpectSuccess();
+    });
+  });
+});
addedtests/src/confirmSponsorship.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/confirmSponsorship.test.ts
@@ -0,0 +1,338 @@
+//
+// 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, submitTransactionExpectFailAsync } 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('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();
+    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();
+    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();
+    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();
+    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({mode: '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({mode: '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();
+    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();
+    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 () { 
+        await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);
+      };
+      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({mode: '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 () { 
+        await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);
+      };
+
+      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({mode: '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 () { 
+        await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);
+      };
+      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('(!negative test!) integration test: ext. removeCollectionSponsor():', () => {
+  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();
+    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();
+    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();
+    await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+  });
+    
+  it('(!negative test!) Confirm sponsorship in a collection that was destroyed', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await destroyCollectionExpectSuccess(collectionId);
+    await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+  });
+});
modifiedtests/src/connection.test.tsdiffbeforeafterboth
--- a/tests/src/connection.test.ts
+++ b/tests/src/connection.test.ts
@@ -21,6 +21,8 @@
   });
 
   it('Cannot connect to 255.255.255.255', async () => {
+    const log = console.log;
+    const error = console.error;
     console.log = function () {};
     console.error = function () {};
 
@@ -31,7 +33,7 @@
       }, { provider: neverConnectProvider });
     })()).to.be.eventually.rejected;
 
-    delete console.log;
-    delete console.error;
+    console.log = log;
+    console.error = error;
   });
 });
\ No newline at end of file
modifiedtests/src/contracts.test.tsdiffbeforeafterboth
--- a/tests/src/contracts.test.ts
+++ b/tests/src/contracts.test.ts
@@ -109,9 +109,6 @@
       const bob = privateKey("//Bob");
       
       const [contract, deployer] = await deployFlipper(api);
-      const consoleError = console.error;
-      console.error = (...data: any[]) => {
-      };
 
       let expectedFlipValue = await getFlipValue(contract, deployer);
 
@@ -166,7 +163,6 @@
       const afterWhiteListDisabled = await getFlipValue(contract,deployer);
       expect(afterWhiteListDisabled).to.be.eq(expectedFlipValue, `Anyone can call contract with disabled whitelist.`);
 
-      console.error = consoleError;
     });
   });
 
modifiedtests/src/createCollection.test.tsdiffbeforeafterboth
--- a/tests/src/createCollection.test.ts
+++ b/tests/src/createCollection.test.ts
@@ -6,37 +6,29 @@
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
 import { default as usingApi } from "./substrate/substrate-api";
-import { createCollectionExpectSuccess, createCollectionExpectFailure } from "./util/helpers";
+import { createCollectionExpectSuccess, createCollectionExpectFailure, CollectionMode } from "./util/helpers";
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
 
 describe('integration test: ext. createCollection():', () => {
   it('Create new NFT collection', async () => {
-    await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await createCollectionExpectSuccess({name: 'A', description: 'B', tokenPrefix: 'C', mode: 'NFT'});
   });
   it('Create new NFT collection whith collection_name of maximum length (64 bytes)', async () => {
-    await createCollectionExpectSuccess(
-      'ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCD',
-      '1', '1', 'NFT');
+    await createCollectionExpectSuccess({name: 'A'.repeat(64)});
   });
   it('Create new NFT collection whith collection_description of maximum length (256 bytes)', async () => {
-    await createCollectionExpectSuccess(
-      'A', 
-      'ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJabcdef',
-      '1', 'NFT');
+    await createCollectionExpectSuccess({description: 'A'.repeat(256)});
   });
   it('Create new NFT collection whith token_prefix of maximum length (16 bytes)', async () => {
-    await createCollectionExpectSuccess(
-      '1', 
-      '1',
-      'ABCDEFGHIJABCDEF', 'NFT');
+    await createCollectionExpectSuccess({tokenPrefix: 'A'.repeat(16)});
   });
   it('Create new Fungible collection', async () => {
-    await createCollectionExpectSuccess('1', '1', '1', 'Fungible');
+    await createCollectionExpectSuccess({mode: 'Fungible'});
   });
   it('Create new ReFungible collection', async () => {
-    await createCollectionExpectSuccess('1', '1', '1', 'ReFungible');
+    await createCollectionExpectSuccess({mode: 'ReFungible'});
   });
 });
 
@@ -46,7 +38,7 @@
       const AcollectionCount = parseInt((await api.query.nft.collectionCount()).toString());
 
       const badTransaction = async function () { 
-        await createCollectionExpectSuccess('1', '1', '1', 'BadMode');
+        await createCollectionExpectSuccess({mode: 'BadMode' as CollectionMode});
       };
       expect(badTransaction()).to.be.rejected;
 
@@ -55,18 +47,12 @@
     });
   });
   it('(!negative test!) create new NFT collection whith incorrect data (collection_name)', async () => {
-    await createCollectionExpectFailure(
-      'ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDE', 
-      '1', '1', 'NFT');
+    await createCollectionExpectFailure({name: 'A'.repeat(65)});
   });
   it('(!negative test!) create new NFT collection whith incorrect data (collection_description)', async () => {
-    await createCollectionExpectFailure('1',
-      'ABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJABCDEFGHIJabcdefg',
-      '1', 'NFT');
+    await createCollectionExpectFailure({description: 'A'.repeat(257)});
   });
   it('(!negative test!) create new NFT collection whith incorrect data (token_prefix)', async () => {
-    await createCollectionExpectFailure('1', '1', 
-    'ABCDEFGHIJABCDEFG',
-    'NFT');
+    await createCollectionExpectFailure({tokenPrefix: 'A'.repeat(17)});
   });
 });
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');
+    const newCollectionID = await createCollectionExpectSuccess({mode: createMode});
+    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');
+    const newCollectionID = await createCollectionExpectSuccess({mode: createMode});
+    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');
+    const newCollectionID = await createCollectionExpectSuccess({mode: createMode});
+    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/creditFeesToTreasury.test.tsdiffbeforeafterboth
--- a/tests/src/creditFeesToTreasury.test.ts
+++ b/tests/src/creditFeesToTreasury.test.ts
@@ -5,7 +5,7 @@
 
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
-import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
 import { alicesPublicKey, bobsPublicKey } from "./accounts";
 import privateKey from "./substrate/privateKey";
 import { BigNumber } from 'bignumber.js';
@@ -63,14 +63,13 @@
       const bobBalanceBefore = new BigNumber((await api.query.system.account(bobsPublicKey)).data.free.toString());
 
       const badTx = api.tx.balances.setBalance(alicesPublicKey, 0, 0);
-      const result = getGenericResult(await submitTransactionAsync(bobPrivateKey, badTx));
+      await expect(submitTransactionExpectFailAsync(bobPrivateKey, badTx)).to.be.rejected;
 
       const treasuryBalanceAfter = new BigNumber((await api.query.system.account(Treasury)).data.free.toString());
       const bobBalanceAfter = new BigNumber((await api.query.system.account(bobsPublicKey)).data.free.toString());
       const fee = bobBalanceBefore.minus(bobBalanceAfter);
       const treasuryIncrease = treasuryBalanceAfter.minus(treasuryBalanceBefore);
 
-      expect(result.success).to.be.false;
       expect(treasuryIncrease.toFixed()).to.be.equal(fee.toFixed());
     });
   });
@@ -80,7 +79,7 @@
       const treasuryBalanceBefore = new BigNumber((await api.query.system.account(Treasury)).data.free.toString());
       const aliceBalanceBefore = new BigNumber((await api.query.system.account(alicesPublicKey)).data.free.toString());
 
-      await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+      await createCollectionExpectSuccess();
 
       const treasuryBalanceAfter = new BigNumber((await api.query.system.account(Treasury)).data.free.toString());
       const aliceBalanceAfter = new BigNumber((await api.query.system.account(alicesPublicKey)).data.free.toString());
@@ -96,7 +95,7 @@
       const treasuryBalanceBefore = new BigNumber((await api.query.system.account(Treasury)).data.free.toString());
       const aliceBalanceBefore = new BigNumber((await api.query.system.account(alicesPublicKey)).data.free.toString());
 
-      await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+      await createCollectionExpectSuccess();
 
       const treasuryBalanceAfter = new BigNumber((await api.query.system.account(Treasury)).data.free.toString());
       const aliceBalanceAfter = new BigNumber((await api.query.system.account(alicesPublicKey)).data.free.toString());
modifiedtests/src/destroyCollection.test.tsdiffbeforeafterboth
--- a/tests/src/destroyCollection.test.ts
+++ b/tests/src/destroyCollection.test.ts
@@ -1,67 +1,24 @@
 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 () => {
-    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    const collectionId = await createCollectionExpectSuccess();
     await destroyCollectionExpectSuccess(collectionId);
   });
   it('Fungible collection can be destroyed', async () => {
-    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+    const collectionId = await createCollectionExpectSuccess({ mode: 'Fungible' });
     await destroyCollectionExpectSuccess(collectionId);
   });
   it('ReFungible collection can be destroyed', async () => {
-    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+    const collectionId = await createCollectionExpectSuccess({ mode: 'ReFungible' });
     await destroyCollectionExpectSuccess(collectionId);
   });
 });
@@ -75,12 +32,12 @@
     });
   });
   it('(!negative test!) Destroy a collection that has already been destroyed', async () => {
-    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    const collectionId = await createCollectionExpectSuccess();
     await destroyCollectionExpectSuccess(collectionId);
     await destroyCollectionExpectFailure(collectionId);
   });
   it('(!negative test!) Destroy a collection using non-owner account', async () => {
-    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    const collectionId = await createCollectionExpectSuccess();
     await destroyCollectionExpectFailure(collectionId, '//Bob');
     await destroyCollectionExpectSuccess(collectionId, '//Alice');
   });
addedtests/src/load_test_sc/loadtester.wasmdiffbeforeafterboth

binary blob — no preview

addedtests/src/load_test_sc/metadata.jsondiffbeforeafterboth

no changes

addedtests/src/removeCollectionSponsor.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/removeCollectionSponsor.test.ts
@@ -0,0 +1,137 @@
+//
+// 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, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
+import { 
+  createCollectionExpectSuccess, 
+  setCollectionSponsorExpectSuccess, 
+  destroyCollectionExpectSuccess, 
+  setCollectionSponsorExpectFailure,
+  confirmSponsorshipExpectSuccess,
+  confirmSponsorshipExpectFailure,
+  createItemExpectSuccess,
+  findUnusedAddress,
+  getGenericResult,
+  enableWhiteListExpectSuccess,
+  enablePublicMintingExpectSuccess,
+  addToWhiteListExpectSuccess,
+  removeCollectionSponsorExpectSuccess,
+  removeCollectionSponsorExpectFailure,
+} 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('integration test: ext. removeCollectionSponsor():', () => {
+
+  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('Remove NFT collection sponsor stops sponsorship', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+    await removeCollectionSponsorExpectSuccess(collectionId);
+
+    await usingApi(async (api) => {
+      // 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 - 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 () { 
+        await submitTransactionExpectFailAsync(zeroBalance, zeroToAlice);
+      };
+      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());
+
+      expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+    });
+  });
+
+  it('Remove a sponsor after it was already removed', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+    await removeCollectionSponsorExpectSuccess(collectionId);
+    await removeCollectionSponsorExpectSuccess(collectionId);
+  });
+
+  it('Remove sponsor in a collection that never had the sponsor set', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await removeCollectionSponsorExpectSuccess(collectionId);
+  });
+
+  it('Remove sponsor for a collection that had the sponsor set, but not confirmed', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await removeCollectionSponsorExpectSuccess(collectionId);
+  });
+
+});
+
+describe('(!negative test!) integration test: ext. removeCollectionSponsor():', () => {
+  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!) Remove sponsor 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 removeCollectionSponsorExpectFailure(collectionId);
+  });
+
+  it('(!negative test!) Remove sponsor in a destroyed collection', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await destroyCollectionExpectSuccess(collectionId);
+    await removeCollectionSponsorExpectFailure(collectionId);
+  });
+
+  it('Set - remove - confirm: fails', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await removeCollectionSponsorExpectSuccess(collectionId);
+    await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+  });
+
+  it('Set - confirm - remove - confirm: Sponsor cannot come back', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+    await removeCollectionSponsorExpectSuccess(collectionId);
+    await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+  });
+
+});
addedtests/src/rpc.load.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/rpc.load.ts
@@ -0,0 +1,145 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import { expect, assert } from "chai";
+import usingApi, { submitTransactionAsync, submitTransactionExpectFailAsync } from "./substrate/substrate-api";
+import { IKeyringPair } from "@polkadot/types/types";
+import { Abi, BlueprintPromise as Blueprint, CodePromise, ContractPromise as Contract } from "@polkadot/api-contract";
+import { ApiPromise, Keyring } from "@polkadot/api";
+import { ApiTypes, SubmittableExtrinsic } from "@polkadot/api/types";
+import { BigNumber } from 'bignumber.js';
+import { findUnusedAddress } from './util/helpers'
+import fs from "fs";
+import privateKey from "./substrate/privateKey";
+
+const value = 0;
+const gasLimit = 500000n * 1000000n;
+const endowment = `1000000000000000`;
+
+
+function deployBlueprint(alice: IKeyringPair, code: CodePromise): Promise<Blueprint> {
+  return new Promise<Blueprint>(async (resolve, reject) => {
+    const unsub = await code
+      .createBlueprint()
+      .signAndSend(alice, (result) => {
+        if (result.status.isInBlock || result.status.isFinalized) {
+          // here we have an additional field in the result, containing the blueprint
+          resolve(result.blueprint);
+          unsub();
+        }
+      })
+  });
+}
+
+function deployContract(alice: IKeyringPair, blueprint: Blueprint) : Promise<any> {
+  return new Promise<any>(async (resolve, reject) => {
+    const unsub = await blueprint.tx
+    .new(endowment, gasLimit)
+    .signAndSend(alice, (result) => {
+      if (result.status.isInBlock || result.status.isFinalized) {
+        unsub();
+        resolve(result);
+      }
+    });    
+  });
+}
+
+async function prepareDeployer(api: ApiPromise) {
+  // Find unused address
+  const deployer = await findUnusedAddress(api);
+
+  // Transfer balance to it
+  const keyring = new Keyring({ type: 'sr25519' });
+  const alice = keyring.addFromUri(`//Alice`);
+  let amount = new BigNumber(endowment);
+  amount = amount.plus(1e15);
+  const tx = api.tx.balances.transfer(deployer.address, amount.toFixed());
+  await submitTransactionAsync(alice, tx);
+
+  return deployer;
+}
+
+async function deployLoadTester(api: ApiPromise): Promise<[Contract, IKeyringPair]> {
+  const metadata = JSON.parse(fs.readFileSync('./src/load_test_sc/metadata.json').toString('utf-8'));
+  const abi = new Abi(metadata);
+
+  const deployer = await prepareDeployer(api);
+
+  const wasm = fs.readFileSync('./src/load_test_sc/loadtester.wasm');
+
+  const code = new CodePromise(api, abi, wasm);
+
+  const blueprint = await deployBlueprint(deployer, code);
+  const contract = (await deployContract(deployer, blueprint))['contract'] as Contract;
+
+  return [contract, deployer];
+}
+
+async function getScData(contract: Contract, deployer: IKeyringPair) {
+  const result = await contract.query.get(deployer.address, value, gasLimit);
+
+  if(!result.result.isSuccess) {
+    throw `Failed to get value`;
+  }
+  return result.result.asSuccess.data;
+}
+
+
+describe('RPC Tests', () => {
+  it('Simple RPC Load Test', async () => {
+    await usingApi(async api => {
+      let count = 0;
+      let hrTime = process.hrtime();
+      let microsec1 = hrTime[0] * 1000000 + hrTime[1] / 1000;
+      let rate = 0;
+      const checkPoint = 1000;
+      while (true) {
+        await api.rpc.system.chain();
+        count++;
+        process.stdout.write(`RPC reads: ${count} times at rate ${rate} r/s            \r`);
+    
+        if (count % checkPoint == 0) {
+          hrTime = process.hrtime();
+          let microsec2 = hrTime[0] * 1000000 + hrTime[1] / 1000;
+          rate = 1000000*checkPoint/(microsec2 - microsec1);
+          microsec1 = microsec2;
+        }
+      }
+    });
+  });
+
+  it.only('Smart Contract RPC Load Test', async () => {
+    await usingApi(async api => {
+
+      // Deploy smart contract
+      const [contract, deployer] = await deployLoadTester(api);
+
+      // Fill smart contract up with data
+      const bob = privateKey("//Bob");
+      const tx = contract.tx.bloat(value, gasLimit, 200);
+      await submitTransactionAsync(bob, tx);
+
+      // Run load test
+      let count = 0;
+      let hrTime = process.hrtime();
+      let microsec1 = hrTime[0] * 1000000 + hrTime[1] / 1000;
+      let rate = 0;
+      const checkPoint = 10;
+      while (true) {
+        await getScData(contract, deployer);
+        count++;
+        process.stdout.write(`SC reads: ${count} times at rate ${rate} r/s            \r`);
+    
+        if (count % checkPoint == 0) {
+          hrTime = process.hrtime();
+          let microsec2 = hrTime[0] * 1000000 + hrTime[1] / 1000;
+          rate = 1000000*checkPoint/(microsec2 - microsec1);
+          microsec1 = microsec2;
+        }
+      }
+    });
+  });
+
+});
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();
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Set Fungible collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode: 'Fungible' });
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Set ReFungible collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess({ mode: 'ReFungible' });
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+
+  it('Set the same sponsor repeatedly', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Replace collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess();
+
+    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();
+    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();
+    await destroyCollectionExpectSuccess(collectionId);
+    await setCollectionSponsorExpectFailure(collectionId, bob.address);
+  });
+});
modifiedtests/src/substrate/substrate-api.tsdiffbeforeafterboth
--- a/tests/src/substrate/substrate-api.ts
+++ b/tests/src/substrate/substrate-api.ts
@@ -33,21 +33,42 @@
   }
 }
 
+enum TransactionStatus {
+  Success,
+  Fail,
+  NotReady
+}
+
+function getTransactionStatus(events: EventRecord[], status: ExtrinsicStatus): TransactionStatus {
+  if (status.isReady) {
+    return TransactionStatus.NotReady;
+  }
+  if (status.isBroadcast) {
+    return TransactionStatus.NotReady;
+  } 
+  if (status.isInBlock || status.isFinalized) {
+    if(events.filter(e => e.event.data.method === 'ExtrinsicFailed').length > 0) {
+      return TransactionStatus.Fail;
+    }
+    if(events.filter(e => e.event.data.method === 'ExtrinsicSuccess').length > 0) {
+      return TransactionStatus.Success;
+    }
+  }
+
+  return TransactionStatus.Fail;
+}
+
 export function submitTransactionAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic<ApiTypes>): Promise<EventRecord[]> {
   return new Promise(async function(resolve, reject) {
     try {
       await transaction.signAndSend(sender, ({ events = [], status }) => {
-        if (status.isReady) {
-          // nothing to do
-          // console.log(`Current tx status is Ready`);
-        } else if (status.isBroadcast) {
-          // nothing to do
-          // console.log(`Current tx status is Broadcast`);
-        } else if (status.isInBlock || status.isFinalized) {
+        const transactionStatus = getTransactionStatus(events, status);
+
+        if (transactionStatus == TransactionStatus.Success) {
           resolve(events);
-        } else {
+        } else if (transactionStatus == TransactionStatus.Fail) {
           console.log(`Something went wrong with transaction. Status: ${status}`);
-          reject("Transaction failed");
+          reject(events);
         }
       });
     } catch (e) {
@@ -58,19 +79,35 @@
 }
 
 export function submitTransactionExpectFailAsync(sender: IKeyringPair, transaction: SubmittableExtrinsic<ApiTypes>): Promise<EventRecord[]> {
-  return new Promise(async function(resolve, reject) {
+  const consoleError = console.error;
+  const consoleLog = console.log;
+  console.error = () => {};
+  console.log = () => {};
+
+  return new Promise<EventRecord[]>(async function(res, rej) {
+    const resolve = (rec: EventRecord[]) => {
+      setTimeout(() => {
+        res(rec);
+        console.error = consoleError;
+        console.log = consoleLog;
+        
+      });
+    };
+    const reject = (errror: any) => {
+      setTimeout(() => {
+        rej(errror);
+        console.error = consoleError;
+        console.log = consoleLog;
+      });
+    };
     try {
       await transaction.signAndSend(sender, ({ events = [], status }) => {
-        if (status.isReady) {
-          // nothing to do
-          // console.log(`Current tx status is Ready`);
-        } else if (status.isBroadcast) {
-          // nothing to do
-          // console.log(`Current tx status is Broadcast`);
-        } else if (status.isInBlock || status.isFinalized) {
+        const transactionStatus = getTransactionStatus(events, status);
+
+        if (transactionStatus == TransactionStatus.Success) {
           resolve(events);
-        } else {
-          reject("Transaction failed");
+        } else if (transactionStatus == TransactionStatus.Fail) {
+          reject(events);
         }
       });
     } catch (e) {
modifiedtests/src/transfer.test.tsdiffbeforeafterboth
--- a/tests/src/transfer.test.ts
+++ b/tests/src/transfer.test.ts
@@ -33,6 +33,8 @@
       // Find unused address
       const pk = await findUnusedAddress(api);
 
+      const error = console.error;
+      const log = console.log;
       console.log = function () {};
       console.error = function () {};
   
@@ -42,8 +44,8 @@
       };
       await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
 
-      delete console.log;
-      delete console.error;
+      console.log = log;
+      console.error = error;
     });
   });
 });
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -7,12 +7,14 @@
 import chaiAsPromised from 'chai-as-promised';
 import type { AccountId, EventRecord } from '@polkadot/types/interfaces';
 import { ApiPromise, Keyring } from "@polkadot/api";
-import { default as usingApi, submitTransactionAsync } from "../substrate/substrate-api";
+import { default as usingApi, submitTransactionAsync, submitTransactionExpectFailAsync } 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,8 +65,46 @@
   return result;
 }
 
-export async function createCollectionExpectSuccess(name: string, description: string, tokenPrefix: string, mode: string): Promise<number> {
+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 type CollectionMode = 'NFT' | 'Fungible' | 'ReFungible';
+export type CreateCollectionParams = {
+  mode: CollectionMode,
+  name: string,
+  description: string,
+  tokenPrefix: string
+};
+
+const defaultCreateCollectionParams: CreateCollectionParams = {
+  name: 'name',
+  description: 'description',
+  mode: 'NFT',
+  tokenPrefix: 'prefix'
+}
+
+export async function createCollectionExpectSuccess(params: Partial<CreateCollectionParams> = {}): Promise<number> {
+  const {name, description, mode, tokenPrefix } = {...defaultCreateCollectionParams, ...params};
+
+  let collectionId: number = 0;
   await usingApi(async (api) => {
     // Get number of collections before the transaction
     const AcollectionCount = parseInt((await api.query.nft.createdCollectionCount()).toString());
@@ -91,15 +137,17 @@
   return collectionId;
 }
   
-export async function createCollectionExpectFailure(name: string, description: string, tokenPrefix: string, mode: string) {
+export async function createCollectionExpectFailure(params: Partial<CreateCollectionParams> = {}) {
+  const {name, description, mode, tokenPrefix } = {...defaultCreateCollectionParams, ...params};
+
   await usingApi(async (api) => {
     // Get number of collections before the transaction
     const AcollectionCount = parseInt((await api.query.nft.createdCollectionCount()).toString());
 
     // Run the CreateCollection transaction
     const alicePrivateKey = privateKey('//Alice');
-    const tx = api.tx.nft.createCollection(name, description, tokenPrefix, mode);
-    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const tx = api.tx.nft.createCollection(strToUTF16(name), strToUTF16(description), strToUTF16(tokenPrefix), mode);
+    const events = await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;
     const result = getCreateCollectionResult(events);
 
     // Get number of collections after the transaction
@@ -123,20 +171,231 @@
   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);
+    await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;
+  });
+}
+
+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) => {
+
+    // 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 removeCollectionSponsorExpectSuccess(collectionId: number) {
   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.removeCollectionSponsor(collectionId);
+    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).to.be.equal(nullPublicKey);
+    expect(collection.SponsorConfirmed).to.be.false;
+  });
+}
+
+export async function removeCollectionSponsorExpectFailure(collectionId: number) {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const alicePrivateKey = privateKey('//Alice');
+    const tx = api.tx.nft.removeCollectionSponsor(collectionId);
+    await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;
+  });
+}
+
+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);
+    await expect(submitTransactionExpectFailAsync(alicePrivateKey, tx)).to.be.rejected;
+  });
+}
+
+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);
+    await expect(submitTransactionExpectFailAsync(sender, tx)).to.be.rejected;
+  });
+}
+
+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;
+    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(BItemCount).to.be.equal(AItemCount+1);
+    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);
+  });
+}
+