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

difftreelog

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

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

9 files changed

modifiednode/src/chain_spec.rsdiffbeforeafterboth
--- a/node/src/chain_spec.rs
+++ b/node/src/chain_spec.rs
@@ -193,7 +193,7 @@
 					offchain_schema: vec![],
 					schema_version: SchemaVersion::default(),
                     sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
-                    unconfirmed_sponsor: get_account_id_from_seed::<sr25519::Public>("Alice"),
+                    sponsor_confirmed: true,
                     const_on_chain_schema: vec![],
 					variable_on_chain_schema: vec![],
 					limits: CollectionLimits::default()
modifiedpallets/nft/src/lib.rsdiffbeforeafterboth
136 pub offchain_schema: Vec<u8>,136 pub offchain_schema: Vec<u8>,
137 pub schema_version: SchemaVersion,137 pub schema_version: SchemaVersion,
138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender138 pub sponsor: AccountId, // Who pays fees. If set to default address, the fees are applied to the transaction sender
139 pub unconfirmed_sponsor: AccountId, // Sponsor address that has not yet confirmed sponsorship139 pub sponsor_confirmed: bool, // False if sponsor address has not yet confirmed sponsorship. True otherwise.
140 pub limits: CollectionLimits, // Collection private restrictions 140 pub limits: CollectionLimits, // Collection private restrictions
141 pub variable_on_chain_schema: Vec<u8>, //141 pub variable_on_chain_schema: Vec<u8>, //
142 pub const_on_chain_schema: Vec<u8>, //142 pub const_on_chain_schema: Vec<u8>, //
145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]145#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]146#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
147pub struct NftItemType<AccountId> {147pub struct NftItemType<AccountId> {
148 pub collection: CollectionId,
149 pub owner: AccountId,148 pub owner: AccountId,
150 pub const_data: Vec<u8>,149 pub const_data: Vec<u8>,
151 pub variable_data: Vec<u8>,150 pub variable_data: Vec<u8>,
152}151}
153152
154#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]153#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
155#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]154#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
156pub struct FungibleItemType<AccountId> {155pub struct FungibleItemType {
157 pub collection: CollectionId,
158 pub owner: AccountId,
159 pub value: u128,156 pub value: u128,
160}157}
161158
162#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]159#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
163#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]160#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
164pub struct ReFungibleItemType<AccountId> {161pub struct ReFungibleItemType<AccountId> {
165 pub collection: CollectionId,
166 pub owner: Vec<Ownership<AccountId>>,162 pub owner: Vec<Ownership<AccountId>>,
167 pub const_data: Vec<u8>,163 pub const_data: Vec<u8>,
168 pub variable_data: Vec<u8>,164 pub variable_data: Vec<u8>,
169}165}
170166
171#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]167// #[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
172#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]168// #[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
173pub struct ApprovePermissions<AccountId> {169// pub struct VestingItem<AccountId, Moment> {
174 pub approved: AccountId,170// pub sender: AccountId,
175 pub amount: u128,171// pub recipient: AccountId,
172// pub collection_id: CollectionId,
173// pub item_id: TokenId,
174// pub amount: u64,
175// pub vesting_date: Moment,
176}176// }
177177
178#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
180pub struct VestingItem<AccountId, Moment> {
181 pub sender: AccountId,
182 pub recipient: AccountId,
183 pub collection_id: CollectionId,
184 pub item_id: TokenId,
185 pub amount: u64,
186 pub vesting_date: Moment,
187}
188
189#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
190#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
191pub struct BasketItem<AccountId, BlockNumber> {
192 pub address: AccountId,
193 pub start_block: BlockNumber,
194}
195
196#[derive(Encode, Decode, Debug, Clone, PartialEq)]178#[derive(Encode, Decode, Debug, Clone, PartialEq)]
197#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]179#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
198pub struct CollectionLimits {180pub struct CollectionLimits {
263#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]245#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
264#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]246#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
265pub struct CreateFungibleData {247pub struct CreateFungibleData {
248 pub value: u128,
266}249}
267250
268#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]251#[derive(Encode, Decode, Default, Debug, Clone, PartialEq)]
417 /// Balance owner per collection map400 /// Balance owner per collection map
418 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;401 pub Balance get(fn balance_count): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => u128;
419402
420 /// second parameter: item id + owner account id403 /// second parameter: item id + owner account id + spender account id
421 pub ApprovedList get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId) => Vec<ApprovePermissions<T::AccountId>>;404 pub Allowances get(fn approved): double_map hasher(identity) CollectionId, hasher(twox_64_concat) (TokenId, T::AccountId, T::AccountId) => u128;
422405
423 /// Item collections406 /// Item collections
424 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;407 pub NftItemList get(fn nft_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => NftItemType<T::AccountId>;
425 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => FungibleItemType<T::AccountId>;408 pub FungibleItemList get(fn fungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => FungibleItemType;
426 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;409 pub ReFungibleItemList get(fn refungible_item_id) config(): double_map hasher(identity) CollectionId, hasher(identity) TokenId => ReFungibleItemType<T::AccountId>;
427410
428 /// Index list411 /// Index list
429 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;412 pub AddressTokens get(fn address_tokens): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => Vec<TokenId>;
430413
431 /// Tokens transfer baskets414 /// Tokens transfer baskets
432 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;415 pub NftTransferBasket get(fn nft_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;
433 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => Vec<BasketItem<T::AccountId, T::BlockNumber>>;416 pub FungibleTransferBasket get(fn fungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(twox_64_concat) T::AccountId => T::BlockNumber;
434 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;417 pub ReFungibleTransferBasket get(fn refungible_transfer_basket): double_map hasher(identity) CollectionId, hasher(identity) TokenId => T::BlockNumber;
435418
436 // Contract Sponsorship and Ownership419 // Contract Sponsorship and Ownership
448 <Module<T>>::init_collection(_c);431 <Module<T>>::init_collection(_c);
449 }432 }
450433
451 for (_num, _q, _i) in &config.nft_item_id {434 for (_num, _c, _i) in &config.nft_item_id {
452 <Module<T>>::init_nft_token(_i);435 <Module<T>>::init_nft_token(*_c, _i);
453 }436 }
454437
455 for (_num, _q, _i) in &config.fungible_item_id {438 for (collection_id, account_id, fungible_item) in &config.fungible_item_id {
456 <Module<T>>::init_fungible_token(_i);439 <Module<T>>::init_fungible_token(*collection_id, account_id, fungible_item);
457 }440 }
458441
459 for (_num, _q, _i) in &config.refungible_item_id {442 for (_num, _c, _i) in &config.refungible_item_id {
460 <Module<T>>::init_refungible_token(_i);443 <Module<T>>::init_refungible_token(*_c, _i);
461 }444 }
462 })445 })
463 }446 }
593 offchain_schema: Vec::new(),576 offchain_schema: Vec::new(),
594 schema_version: SchemaVersion::ImageURL,577 schema_version: SchemaVersion::ImageURL,
595 sponsor: T::AccountId::default(),578 sponsor: T::AccountId::default(),
596 unconfirmed_sponsor: T::AccountId::default(),579 sponsor_confirmed: false,
597 variable_on_chain_schema: Vec::new(),580 variable_on_chain_schema: Vec::new(),
598 const_on_chain_schema: Vec::new(),581 const_on_chain_schema: Vec::new(),
599 limits: CollectionLimits::default(),582 limits: CollectionLimits::default(),
624 Self::check_owner_permissions(collection_id, sender)?;607 Self::check_owner_permissions(collection_id, sender)?;
625608
626 <AddressTokens<T>>::remove_prefix(collection_id);609 <AddressTokens<T>>::remove_prefix(collection_id);
627 <ApprovedList<T>>::remove_prefix(collection_id);610 <Allowances<T>>::remove_prefix(collection_id);
628 <Balance<T>>::remove_prefix(collection_id);611 <Balance<T>>::remove_prefix(collection_id);
629 <ItemListIndex>::remove(collection_id);612 <ItemListIndex>::remove(collection_id);
630 <AdminList<T>>::remove(collection_id);613 <AdminList<T>>::remove(collection_id);
852 let mut target_collection = <Collection<T>>::get(collection_id);835 let mut target_collection = <Collection<T>>::get(collection_id);
853 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);836 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);
854837
855 target_collection.unconfirmed_sponsor = new_sponsor;838 target_collection.sponsor = new_sponsor;
839 target_collection.sponsor_confirmed = false;
856 <Collection<T>>::insert(collection_id, target_collection);840 <Collection<T>>::insert(collection_id, target_collection);
857841
858 Ok(())842 Ok(())
872 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);856 ensure!(<Collection<T>>::contains_key(collection_id), Error::<T>::CollectionNotFound);
873857
874 let mut target_collection = <Collection<T>>::get(collection_id);858 let mut target_collection = <Collection<T>>::get(collection_id);
875 ensure!(sender == target_collection.unconfirmed_sponsor, Error::<T>::ConfirmUnsetSponsorFail);859 ensure!(sender == target_collection.sponsor, Error::<T>::ConfirmUnsetSponsorFail);
876860
877 target_collection.sponsor = target_collection.unconfirmed_sponsor;861 target_collection.sponsor_confirmed = true;
878 target_collection.unconfirmed_sponsor = T::AccountId::default();
879 <Collection<T>>::insert(collection_id, target_collection);862 <Collection<T>>::insert(collection_id, target_collection);
880863
881 Ok(())864 Ok(())
900 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);883 ensure!(sender == target_collection.owner, Error::<T>::NoPermission);
901884
902 target_collection.sponsor = T::AccountId::default();885 target_collection.sponsor = T::AccountId::default();
886 target_collection.sponsor_confirmed = false;
903 <Collection<T>>::insert(collection_id, target_collection);887 <Collection<T>>::insert(collection_id, target_collection);
904888
905 Ok(())889 Ok(())
1000 /// 984 ///
1001 /// * item_id: ID of NFT to burn.985 /// * item_id: ID of NFT to burn.
1002 #[weight = T::WeightInfo::burn_item()]986 #[weight = T::WeightInfo::burn_item()]
1003 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {987 pub fn burn_item(origin, collection_id: CollectionId, item_id: TokenId, value: u128) -> DispatchResult {
1004988
1005 let sender = ensure_signed(origin)?;989 let sender = ensure_signed(origin)?;
1006 Self::collection_exists(collection_id)?;990 Self::collection_exists(collection_id)?;
1018 match target_collection.mode1002 match target_collection.mode
1019 {1003 {
1020 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,1004 CollectionMode::NFT => Self::burn_nft_item(collection_id, item_id)?,
1021 CollectionMode::Fungible(_) => Self::burn_fungible_item(collection_id, item_id)?,1005 CollectionMode::Fungible(_) => Self::burn_fungible_item(&sender, collection_id, value)?,
1022 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,1006 CollectionMode::ReFungible(_) => Self::burn_refungible_item(collection_id, item_id, sender.clone())?,
1023 _ => ()1007 _ => ()
1024 };1008 };
1074 match target_collection.mode1058 match target_collection.mode
1075 {1059 {
1076 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,1060 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, sender.clone(), recipient)?,
1077 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, sender.clone(), recipient)?,1061 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &sender, &recipient)?,
1078 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,1062 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, sender.clone(), recipient)?,
1079 _ => ()1063 _ => ()
1080 };1064 };
1098 /// 1082 ///
1099 /// * item_id: ID of the item.1083 /// * item_id: ID of the item.
1100 #[weight = T::WeightInfo::approve()]1084 #[weight = T::WeightInfo::approve()]
1101 pub fn approve(origin, approved: T::AccountId, collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1085 pub fn approve(origin, spender: T::AccountId, collection_id: CollectionId, item_id: TokenId, amount: u128) -> DispatchResult {
11021086
1103 let sender = ensure_signed(origin)?;1087 let sender = ensure_signed(origin)?;
11041088
11101094
1111 if target_collection.access == AccessMode::WhiteList {1095 if target_collection.access == AccessMode::WhiteList {
1112 Self::check_white_list(collection_id, &sender)?;1096 Self::check_white_list(collection_id, &sender)?;
1113 Self::check_white_list(collection_id, &approved)?;1097 Self::check_white_list(collection_id, &spender)?;
1114 }1098 }
11151099
1116 // amount param stub1100 let allowance_exists = <Allowances<T>>::contains_key(collection_id, (item_id, &sender, &spender));
1117 let amount = 100000000;
1118
1119 let list_exists = <ApprovedList<T>>::contains_key(collection_id, (item_id, sender.clone()));
1120 if list_exists {1101 let mut allowance: u128 = amount;
1121
1122 let mut list = <ApprovedList<T>>::get(collection_id, (item_id, sender.clone()));
1123 let item_contains = list.iter().any(|i| i.approved == approved);
1124
1125 if !item_contains {1102 if allowance_exists {
1126 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });1103 allowance += <Allowances<T>>::get(collection_id, (item_id, &sender, &spender));
1127 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);
1128 }
1129 } else {
1130
1131 let mut list = Vec::new();
1132 list.push(ApprovePermissions { approved: approved.clone(), amount: amount });
1133 <ApprovedList<T>>::insert(collection_id, (item_id, sender.clone()), list);
1134 }1104 }
1105 <Allowances<T>>::insert(collection_id, (item_id, sender.clone(), spender.clone()), allowance);
11351106
1136 Ok(())1107 Ok(())
1137 }1108 }
1161 let sender = ensure_signed(origin)?;1132 let sender = ensure_signed(origin)?;
1162 let mut appoved_transfer = false;1133 let mut appoved_transfer = false;
11631134
1164 // Check approve1135 // Check approval
1165 if <ApprovedList<T>>::contains_key(collection_id, (item_id, from.clone())) {1136 let mut approval: u128 = 0;
1137 if <Allowances<T>>::contains_key(collection_id, (item_id, &from, &recipient)) {
1166 let list_itm = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()));1138 approval = <Allowances<T>>::get(collection_id, (item_id, &from, &recipient));
1167 let opt_item = list_itm.iter().find(|i| i.approved == sender.clone());
1168 if opt_item.is_some()1139 ensure!(approval >= value, Error::<T>::TokenValueNotEnough);
1169 {
1170 appoved_transfer = true;
1171 ensure!(opt_item.unwrap().amount >= value, Error::<T>::TokenValueNotEnough);
1172 }1140 appoved_transfer = true;
1173 }1141 }
11741142
1175 let target_collection = <Collection<T>>::get(collection_id);1143 let target_collection = <Collection<T>>::get(collection_id);
11791147
1180 // Transfer permissions check 1148 // Transfer permissions check
1181 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),1149 ensure!(appoved_transfer || Self::is_owner_or_admin_permissions(collection_id, sender.clone()),
1182 Error::<T>::NoPermission);1150 Error::<T>::NoPermission);
11831151
1184 if target_collection.access == AccessMode::WhiteList {1152 if target_collection.access == AccessMode::WhiteList {
1185 Self::check_white_list(collection_id, &sender)?;1153 Self::check_white_list(collection_id, &sender)?;
1186 Self::check_white_list(collection_id, &recipient)?;1154 Self::check_white_list(collection_id, &recipient)?;
1187 }1155 }
11881156
1189 // remove approve1157 // Reduce approval by transferred amount or remove if remaining approval drops to 0
1190 let approve_list: Vec<ApprovePermissions<T::AccountId>> = <ApprovedList<T>>::get(collection_id, (item_id, from.clone()))1158 if approval - value > 0 {
1159 <Allowances<T>>::insert(collection_id, (item_id, &from, &recipient), approval - value);
1191 .into_iter().filter(|i| i.approved != sender.clone()).collect();1160 }
1161 else {
1192 <ApprovedList<T>>::insert(collection_id, (item_id, from.clone()), approve_list);1162 <Allowances<T>>::remove(collection_id, (item_id, &from, &recipient));
1163 }
11931164
1194
1195 match target_collection.mode1165 match target_collection.mode
1196 {1166 {
1197 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,1167 CollectionMode::NFT => Self::transfer_nft(collection_id, item_id, from, recipient)?,
1198 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, item_id, value, from.clone(), recipient)?,1168 CollectionMode::Fungible(_) => Self::transfer_fungible(collection_id, value, &from, &recipient)?,
1199 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,1169 CollectionMode::ReFungible(_) => Self::transfer_refungible(collection_id, item_id, value, from.clone(), recipient)?,
1200 _ => ()1170 _ => ()
1201 };1171 };
1614 {1584 {
1615 CreateItemData::NFT(data) => {1585 CreateItemData::NFT(data) => {
1616 let item = NftItemType {1586 let item = NftItemType {
1617 collection: collection_id,
1618 owner,1587 owner,
1619 const_data: data.const_data,1588 const_data: data.const_data,
1620 variable_data: data.variable_data1589 variable_data: data.variable_data
1621 };1590 };
16221591
1623 Self::add_nft_item(item)?;1592 Self::add_nft_item(collection_id, item)?;
1624 },1593 },
1625 CreateItemData::Fungible(_) => {1594 CreateItemData::Fungible(data) => {
1626 let item = FungibleItemType {1595 Self::add_fungible_item(collection_id, &owner, data.value)?;
1627 collection: collection_id,
1628 owner,
1629 value: (10 as u128).pow(collection.decimal_points as u32)
1630 };
1631
1632 Self::add_fungible_item(item)?;
1633 },1596 },
1634 CreateItemData::ReFungible(data) => {1597 CreateItemData::ReFungible(data) => {
1635 let mut owner_list = Vec::new();1598 let mut owner_list = Vec::new();
1636 let value = (10 as u128).pow(collection.decimal_points as u32);1599 let value = (10 as u128).pow(collection.decimal_points as u32);
1637 owner_list.push(Ownership {owner: owner.clone(), fraction: value});1600 owner_list.push(Ownership {owner: owner.clone(), fraction: value});
16381601
1639 let item = ReFungibleItemType {1602 let item = ReFungibleItemType {
1640 collection: collection_id,
1641 owner: owner_list,1603 owner: owner_list,
1642 const_data: data.const_data,1604 const_data: data.const_data,
1643 variable_data: data.variable_data1605 variable_data: data.variable_data
1644 };1606 };
16451607
1646 Self::add_refungible_item(item)?;1608 Self::add_refungible_item(collection_id, item)?;
1647 }1609 }
1648 };1610 };
16491611
1653 Ok(())1615 Ok(())
1654 }1616 }
16551617
1656 fn add_fungible_item(item: FungibleItemType<T::AccountId>) -> DispatchResult {1618 fn add_fungible_item(collection_id: CollectionId, owner: &T::AccountId, value: u128) -> DispatchResult {
1657 let current_index = <ItemListIndex>::get(item.collection)
1658 .checked_add(1)
1659 .ok_or(Error::<T>::NumOverflow)?;
1660 let itemcopy = item.clone();
1661 let owner = item.owner.clone();
16621619
1663 Self::add_token_index(item.collection, current_index, owner.clone())?;1620 // Does new owner already have an account?
1621 let mut balance: u128 = 0;
1622 if <FungibleItemList<T>>::contains_key(collection_id, owner) {
1623 balance = <FungibleItemList<T>>::get(collection_id, owner).value;
1624 }
16641625
1665 <ItemListIndex>::insert(item.collection, current_index);1626 // Mint
1627 let item = FungibleItemType {
1628 value: balance + value
1629 };
1666 <FungibleItemList<T>>::insert(item.collection, current_index, itemcopy);1630 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), item);
16671631
1668 // Add current block
1669 let v: Vec<BasketItem<T::AccountId, T::BlockNumber>> = Vec::new();
1670 <FungibleTransferBasket<T>>::insert(item.collection, current_index, v);
1671
1672 // Update balance1632 // Update balance
1673 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1633 let new_balance = <Balance<T>>::get(collection_id, owner)
1674 .checked_add(item.value)1634 .checked_add(value)
1675 .ok_or(Error::<T>::NumOverflow)?;1635 .ok_or(Error::<T>::NumOverflow)?;
1676 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1636 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);
16771637
1678 Ok(())1638 Ok(())
1679 }1639 }
16801640
1681 fn add_refungible_item(item: ReFungibleItemType<T::AccountId>) -> DispatchResult {1641 fn add_refungible_item(collection_id: CollectionId, item: ReFungibleItemType<T::AccountId>) -> DispatchResult {
1682 let current_index = <ItemListIndex>::get(item.collection)1642 let current_index = <ItemListIndex>::get(collection_id)
1683 .checked_add(1)1643 .checked_add(1)
1684 .ok_or(Error::<T>::NumOverflow)?;1644 .ok_or(Error::<T>::NumOverflow)?;
1685 let itemcopy = item.clone();1645 let itemcopy = item.clone();
16861646
1687 let value = item.owner.first().unwrap().fraction;1647 let value = item.owner.first().unwrap().fraction;
1688 let owner = item.owner.first().unwrap().owner.clone();1648 let owner = item.owner.first().unwrap().owner.clone();
16891649
1690 Self::add_token_index(item.collection, current_index, owner.clone())?;1650 Self::add_token_index(collection_id, current_index, owner.clone())?;
16911651
1692 <ItemListIndex>::insert(item.collection, current_index);1652 <ItemListIndex>::insert(collection_id, current_index);
1693 <ReFungibleItemList<T>>::insert(item.collection, current_index, itemcopy);1653 <ReFungibleItemList<T>>::insert(collection_id, current_index, itemcopy);
16941654
1695 // Add current block
1696 let block_number: T::BlockNumber = 0.into();
1697 <ReFungibleTransferBasket<T>>::insert(item.collection, current_index, block_number);
1698
1699 // Update balance1655 // Update balance
1700 let new_balance = <Balance<T>>::get(item.collection, owner.clone())1656 let new_balance = <Balance<T>>::get(collection_id, owner.clone())
1701 .checked_add(value)1657 .checked_add(value)
1702 .ok_or(Error::<T>::NumOverflow)?;1658 .ok_or(Error::<T>::NumOverflow)?;
1703 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);1659 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);
17041660
1705 Ok(())1661 Ok(())
1706 }1662 }
17071663
1708 fn add_nft_item(item: NftItemType<T::AccountId>) -> DispatchResult {1664 fn add_nft_item(collection_id: CollectionId, item: NftItemType<T::AccountId>) -> DispatchResult {
1709 let current_index = <ItemListIndex>::get(item.collection)1665 let current_index = <ItemListIndex>::get(collection_id)
1710 .checked_add(1)1666 .checked_add(1)
1711 .ok_or(Error::<T>::NumOverflow)?;1667 .ok_or(Error::<T>::NumOverflow)?;
17121668
1713 let item_owner = item.owner.clone();1669 let item_owner = item.owner.clone();
1714 let collection_id = item.collection.clone();
1715 Self::add_token_index(collection_id, current_index, item.owner.clone())?;1670 Self::add_token_index(collection_id, current_index, item.owner.clone())?;
17161671
1717 <ItemListIndex>::insert(collection_id, current_index);1672 <ItemListIndex>::insert(collection_id, current_index);
1718 <NftItemList<T>>::insert(collection_id, current_index, item);1673 <NftItemList<T>>::insert(collection_id, current_index, item);
17191674
1720 // Add current block
1721 let block_number: T::BlockNumber = 0.into();
1722 <NftTransferBasket<T>>::insert(collection_id, current_index, block_number);
1723
1724 // Update balance1675 // Update balance
1725 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())1676 let new_balance = <Balance<T>>::get(collection_id, item_owner.clone())
1726 .checked_add(1)1677 .checked_add(1)
1748 .unwrap();1699 .unwrap();
1749 Self::remove_token_index(collection_id, item_id, owner.clone())?;1700 Self::remove_token_index(collection_id, item_id, owner.clone())?;
17501701
1751 // remove approve list
1752 <ApprovedList<T>>::remove(collection_id, (item_id, owner.clone()));
1753
1754 // update balance1702 // update balance
1755 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1703 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())
1756 .checked_sub(item.fraction)1704 .checked_sub(item.fraction)
1770 let item = <NftItemList<T>>::get(collection_id, item_id);1718 let item = <NftItemList<T>>::get(collection_id, item_id);
1771 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;1719 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;
17721720
1773 // remove approve list
1774 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));
1775
1776 // update balance1721 // update balance
1777 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1722 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())
1778 .checked_sub(1)1723 .checked_sub(1)
1783 Ok(())1728 Ok(())
1784 }1729 }
17851730
1786 fn burn_fungible_item(collection_id: CollectionId, item_id: TokenId) -> DispatchResult {1731 fn burn_fungible_item(owner: &T::AccountId, collection_id: CollectionId, value: u128) -> DispatchResult {
1787 ensure!(1732 ensure!(
1788 <FungibleItemList<T>>::contains_key(collection_id, item_id),1733 <FungibleItemList<T>>::contains_key(collection_id, owner),
1789 Error::<T>::TokenNotFound1734 Error::<T>::TokenNotFound
1790 );1735 );
1791 let item = <FungibleItemList<T>>::get(collection_id, item_id);1736 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);
1792 Self::remove_token_index(collection_id, item_id, item.owner.clone())?;1737 ensure!(balance.value >= value, Error::<T>::TokenValueNotEnough);
17931738
1794 // remove approve list
1795 <ApprovedList<T>>::remove(collection_id, (item_id, item.owner.clone()));
1796
1797 // update balance1739 // update balance
1798 let new_balance = <Balance<T>>::get(collection_id, item.owner.clone())1740 let new_balance = <Balance<T>>::get(collection_id, owner)
1799 .checked_sub(item.value)1741 .checked_sub(value)
1800 .ok_or(Error::<T>::NumOverflow)?;1742 .ok_or(Error::<T>::NumOverflow)?;
1801 <Balance<T>>::insert(collection_id, item.owner.clone(), new_balance);1743 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);
18021744
1803 <FungibleItemList<T>>::remove(collection_id, item_id);1745 if balance.value - value > 0 {
1746 balance.value -= value;
1747 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);
1748 }
1749 else {
1750 <FungibleItemList<T>>::remove(collection_id, owner);
1751 }
18041752
1805 Ok(())1753 Ok(())
1806 }1754 }
1861 <NftItemList<T>>::get(collection_id, item_id).owner == subject1809 <NftItemList<T>>::get(collection_id, item_id).owner == subject
1862 }1810 }
1863 CollectionMode::Fungible(_) => {1811 CollectionMode::Fungible(_) => {
1864 <FungibleItemList<T>>::get(collection_id, item_id).owner == subject1812 <FungibleItemList<T>>::contains_key(collection_id, &subject)
1865 }1813 }
1866 CollectionMode::ReFungible(_) => {1814 CollectionMode::ReFungible(_) => {
1867 <ReFungibleItemList<T>>::get(collection_id, item_id)1815 <ReFungibleItemList<T>>::get(collection_id, item_id)
18821830
1883 fn transfer_fungible(1831 fn transfer_fungible(
1884 collection_id: CollectionId,1832 collection_id: CollectionId,
1885 item_id: TokenId,
1886 value: u128,1833 value: u128,
1887 owner: T::AccountId,1834 owner: &T::AccountId,
1888 new_owner: T::AccountId,1835 recipient: &T::AccountId,
1889 ) -> DispatchResult {1836 ) -> DispatchResult {
1890 ensure!(1837 ensure!(
1891 <FungibleItemList<T>>::contains_key(collection_id, item_id),1838 <FungibleItemList<T>>::contains_key(collection_id, owner),
1892 Error::<T>::TokenNotFound1839 Error::<T>::TokenNotFound
1893 );1840 );
18941841
1895 let full_item = <FungibleItemList<T>>::get(collection_id, item_id);1842 let mut balance = <FungibleItemList<T>>::get(collection_id, owner);
1896 let amount = full_item.value;1843 ensure!(balance.value >= value, Error::<T>::TokenValueTooLow);
18971844
1898 ensure!(amount >= value, Error::<T>::TokenValueTooLow);1845 // Send balance to recipient (updates balanceOf of recipient)
1846 Self::add_fungible_item(collection_id, recipient, value)?;
18991847
1900 // update balance1848 // update balanceOf of sender
1901 let balance_old_owner = <Balance<T>>::get(collection_id, owner.clone())1849 <Balance<T>>::insert(collection_id, (*owner).clone(), balance.value - value);
1902 .checked_sub(value)
1903 .ok_or(Error::<T>::NumOverflow)?;
1904 <Balance<T>>::insert(collection_id, owner.clone(), balance_old_owner);
19051850
1906 let mut new_owner_account_id = 0;1851 // Reduce or remove sender
1907 let new_owner_items = <AddressTokens<T>>::get(collection_id, new_owner.clone());1852 if balance.value == value {
1908 if new_owner_items.len() > 0 {
1909 new_owner_account_id = new_owner_items[0];1853 <FungibleItemList<T>>::remove(collection_id, owner);
1910 }1854 }
19111855 else {
1912 // transfer1856 balance.value -= value;
1913 if amount == value && new_owner_account_id == 0 {1857 <FungibleItemList<T>>::insert(collection_id, (*owner).clone(), balance);
1914 // change owner
1915 // new owner do not have account
1916 let mut new_full_item = full_item.clone();
1917 new_full_item.owner = new_owner.clone();
1918 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);
1919
1920 // update balance
1921 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())
1922 .checked_add(value)
1923 .ok_or(Error::<T>::NumOverflow)?;
1924 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);
1925
1926 // update index collection
1927 Self::move_token_index(collection_id, item_id, owner.clone(), new_owner.clone())?;
1928 } else {
1929 let mut new_full_item = full_item.clone();
1930 new_full_item.value -= value;
1931
1932 // separate amount
1933 if new_owner_account_id > 0 {
1934 // new owner has account
1935 let mut item = <FungibleItemList<T>>::get(collection_id, new_owner_account_id);
1936 item.value += value;
1937
1938 // update balance
1939 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())
1940 .checked_add(value)
1941 .ok_or(Error::<T>::NumOverflow)?;
1942 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);
1943
1944 <FungibleItemList<T>>::insert(collection_id, new_owner_account_id, item);
1945 } else {
1946 // new owner do not have account
1947 let item = FungibleItemType {
1948 collection: collection_id,
1949 owner: new_owner.clone(),
1950 value
1951 };
1952
1953 Self::add_fungible_item(item)?;
1954 }
1955
1956 if amount == value {
1957 Self::remove_token_index(collection_id, item_id, full_item.owner.clone())?;
1958
1959 // remove approve list
1960 <ApprovedList<T>>::remove(collection_id, (item_id, full_item.owner.clone()));
1961 <FungibleItemList<T>>::remove(collection_id, item_id);
1962 }
1963
1964 <FungibleItemList<T>>::insert(collection_id, item_id, new_full_item);
1965 }1858 }
19661859
1967 Ok(())1860 Ok(())
1996 .ok_or(Error::<T>::NumOverflow)?;1889 .ok_or(Error::<T>::NumOverflow)?;
1997 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);1890 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);
19981891
1999 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1892 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())
2000 .checked_add(value)1893 .checked_add(value)
2001 .ok_or(Error::<T>::NumOverflow)?;1894 .ok_or(Error::<T>::NumOverflow)?;
2002 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);1895 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);
20031896
2004 let old_owner = item.owner.clone();1897 let old_owner = item.owner.clone();
2005 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);1898 let new_owner_has_account = full_item.owner.iter().any(|i| i.owner == new_owner);
2076 .ok_or(Error::<T>::NumOverflow)?;1969 .ok_or(Error::<T>::NumOverflow)?;
2077 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);1970 <Balance<T>>::insert(collection_id, item.owner.clone(), balance_old_owner);
20781971
2079 let balance_new_owner = <Balance<T>>::get(collection_id, new_owner.clone())1972 let balancenew_owner = <Balance<T>>::get(collection_id, new_owner.clone())
2080 .checked_add(1)1973 .checked_add(1)
2081 .ok_or(Error::<T>::NumOverflow)?;1974 .ok_or(Error::<T>::NumOverflow)?;
2082 <Balance<T>>::insert(collection_id, new_owner.clone(), balance_new_owner);1975 <Balance<T>>::insert(collection_id, new_owner.clone(), balancenew_owner);
20831976
2084 // change owner1977 // change owner
2085 let old_owner = item.owner.clone();1978 let old_owner = item.owner.clone();
2089 // update index collection1982 // update index collection
2090 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;1983 Self::move_token_index(collection_id, item_id, old_owner.clone(), new_owner.clone())?;
20911984
2092 // reset approved list
2093 <ApprovedList<T>>::remove(collection_id, (item_id, old_owner));
2094 Ok(())1985 Ok(())
2095 }1986 }
2096 1987
2102 match mode {1993 match mode {
2103 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1994 CollectionMode::NFT => ensure!(<NftItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),
2104 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),1995 CollectionMode::ReFungible(_) => ensure!(<ReFungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),
2105 CollectionMode::Fungible(_) => ensure!(<FungibleItemList<T>>::contains_key(collection_id, item_id), Error::<T>::TokenNotFound),
2106 _ => ()1996 _ => ()
2107 };1997 };
2108 1998
2164 CreatedCollectionCount::put(next_id);2054 CreatedCollectionCount::put(next_id);
2165 }2055 }
21662056
2167 fn init_nft_token(item: &NftItemType<T::AccountId>) {2057 fn init_nft_token(collection_id: CollectionId, item: &NftItemType<T::AccountId>) {
2168 let current_index = <ItemListIndex>::get(item.collection)2058 let current_index = <ItemListIndex>::get(collection_id)
2169 .checked_add(1)2059 .checked_add(1)
2170 .unwrap();2060 .unwrap();
21712061
2172 let item_owner = item.owner.clone();2062 let item_owner = item.owner.clone();
2173 let collection_id = item.collection.clone();
2174 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();2063 Self::add_token_index(collection_id, current_index, item.owner.clone()).unwrap();
21752064
2176 <ItemListIndex>::insert(collection_id, current_index);2065 <ItemListIndex>::insert(collection_id, current_index);
2182 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);2071 <Balance<T>>::insert(collection_id, item_owner.clone(), new_balance);
2183 }2072 }
21842073
2185 fn init_fungible_token(item: &FungibleItemType<T::AccountId>) {2074 fn init_fungible_token(collection_id: CollectionId, owner: &T::AccountId, item: &FungibleItemType) {
2186 let current_index = <ItemListIndex>::get(item.collection)2075 let current_index = <ItemListIndex>::get(collection_id)
2187 .checked_add(1)2076 .checked_add(1)
2188 .unwrap();2077 .unwrap();
2189 let owner = item.owner.clone();
21902078
2191 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();2079 Self::add_token_index(collection_id, current_index, (*owner).clone()).unwrap();
21922080
2193 <ItemListIndex>::insert(item.collection, current_index);2081 <ItemListIndex>::insert(collection_id, current_index);
21942082
2195 // Update balance2083 // Update balance
2196 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2084 let new_balance = <Balance<T>>::get(collection_id, owner)
2197 .checked_add(item.value)2085 .checked_add(item.value)
2198 .unwrap();2086 .unwrap();
2199 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2087 <Balance<T>>::insert(collection_id, (*owner).clone(), new_balance);
2200 }2088 }
22012089
2202 fn init_refungible_token(item: &ReFungibleItemType<T::AccountId>) {2090 fn init_refungible_token(collection_id: CollectionId, item: &ReFungibleItemType<T::AccountId>) {
2203 let current_index = <ItemListIndex>::get(item.collection)2091 let current_index = <ItemListIndex>::get(collection_id)
2204 .checked_add(1)2092 .checked_add(1)
2205 .unwrap();2093 .unwrap();
22062094
2207 let value = item.owner.first().unwrap().fraction;2095 let value = item.owner.first().unwrap().fraction;
2208 let owner = item.owner.first().unwrap().owner.clone();2096 let owner = item.owner.first().unwrap().owner.clone();
22092097
2210 Self::add_token_index(item.collection, current_index, owner.clone()).unwrap();2098 Self::add_token_index(collection_id, current_index, owner.clone()).unwrap();
22112099
2212 <ItemListIndex>::insert(item.collection, current_index);2100 <ItemListIndex>::insert(collection_id, current_index);
22132101
2214 // Update balance2102 // Update balance
2215 let new_balance = <Balance<T>>::get(item.collection, owner.clone())2103 let new_balance = <Balance<T>>::get(collection_id, owner.clone())
2216 .checked_add(value)2104 .checked_add(value)
2217 .unwrap();2105 .unwrap();
2218 <Balance<T>>::insert(item.collection, owner.clone(), new_balance);2106 <Balance<T>>::insert(collection_id, owner.clone(), new_balance);
2219 }2107 }
22202108
2221 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {2109 fn add_token_index(collection_id: CollectionId, item_index: TokenId, owner: T::AccountId) -> DispatchResult {
2386 // };2274 // };
2387 let fee = Self::traditional_fee(len, info, tip);2275 let fee = Self::traditional_fee(len, info, tip);
23882276
2277 // Only mess with balances if fee is not zero.
2278 if fee.is_zero() {
2279 return Ok((fee, None));
2280 }
2281
2389 // Determine who is paying transaction fee based on ecnomic model2282 // Determine who is paying transaction fee based on ecnomic model
2390 // Parse call to extract collection ID and access collection sponsor2283 // Parse call to extract collection ID and access collection sponsor
2391 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {2284 let mut sponsor: T::AccountId = match IsSubType::<Call<T>>::is_sub_type(call) {
2392 Some(Call::create_item(collection_id, _owner, _properties)) => {2285 Some(Call::create_item(collection_id, _owner, _properties)) => {
23932286
2394 // check free create limit2287 // check free create limit
2395 if <Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)2288 if (<Collection<T>>::get(collection_id).limits.sponsored_data_size >= (_properties.len() as u32)) &&
2289 (<Collection<T>>::get(collection_id).sponsor_confirmed)
2396 {2290 {
2397 <Collection<T>>::get(collection_id).sponsor2291 <Collection<T>>::get(collection_id).sponsor
2398 } else {2292 } else {
2399 T::AccountId::default()2293 T::AccountId::default()
2400 }2294 }
2401 }2295 }
2402 Some(Call::transfer(_new_owner, collection_id, _item_id, _value)) => {2296 Some(Call::transfer(_new_owner, collection_id, item_id, _value)) => {
2403 2297
2404 let _collection_limits = <Collection<T>>::get(collection_id).limits;2298 let mut sponsor_transfer = false;
2405 let _collection_mode = <Collection<T>>::get(collection_id).mode;2299 if <Collection<T>>::get(collection_id).sponsor_confirmed {
24062300
2407 // sponsor timeout2301 let collection_limits = <Collection<T>>::get(collection_id).limits;
2302 let collection_mode = <Collection<T>>::get(collection_id).mode;
2303
2304 // sponsor timeout
2408 let sponsor_transfer = match _collection_mode {2305 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
2306 sponsor_transfer = match collection_mode {
2409 CollectionMode::NFT => {2307 CollectionMode::NFT => {
2308
2309 // get correct limit
2310 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
2311 collection_limits.sponsor_transfer_timeout
2312 } else {
2313 ChainLimit::get().nft_sponsor_transfer_timeout
2314 };
2315
2316 let mut sponsored = true;
2317 if <NftTransferBasket<T>>::contains_key(collection_id, item_id) {
2318 let last_tx_block = <NftTransferBasket<T>>::get(collection_id, item_id);
2319 let limit_time = last_tx_block + limit.into();
2320 if block_number <= limit_time {
2321 sponsored = false;
2322 }
2323 }
2324 if sponsored {
2325 <NftTransferBasket<T>>::insert(collection_id, item_id, block_number);
2326 }
24102327
2411 // get correct limit2328 sponsored
2412 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {
2413 _collection_limits.sponsor_transfer_timeout
2414 } else {
2415 ChainLimit::get().nft_sponsor_transfer_timeout
2416 };
2417
2418 let basket = <NftTransferBasket<T>>::get(collection_id, _item_id);
2419 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
2420 let limit_time = basket + limit.into();
2421 if block_number >= limit_time {
2422 <NftTransferBasket<T>>::insert(collection_id, _item_id, block_number);
2423 true
2424 }2329 }
2425 else {2330 CollectionMode::Fungible(_) => {
2426 false2331
2427 }
2428 }
2429 CollectionMode::Fungible(_) => {
2430
2431 // get correct limit2332 // get correct limit
2432 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {2333 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
2433 _collection_limits.sponsor_transfer_timeout2334 collection_limits.sponsor_transfer_timeout
2434 } else {2335 } else {
2435 ChainLimit::get().fungible_sponsor_transfer_timeout2336 ChainLimit::get().fungible_sponsor_transfer_timeout
2436 };2337 };
2437
2438 let mut basket = <FungibleTransferBasket<T>>::get(collection_id, _item_id);2338
2439 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;2339 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
2440 if basket.iter().any(|i| i.address == _new_owner.clone())2340 let mut sponsored = true;
2441 {
2442 let item = basket.iter_mut().find(|i| i.address == _new_owner.clone()).unwrap().clone();2341 if <FungibleTransferBasket<T>>::contains_key(collection_id, who) {
2443 let limit_time = item.start_block + limit.into();2342 let last_tx_block = <FungibleTransferBasket<T>>::get(collection_id, who);
2444 if block_number >= limit_time {2343 let limit_time = last_tx_block + limit.into();
2445 basket.retain(|x| x.address == item.address);
2446 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone() });2344 if block_number <= limit_time {
2447 <FungibleTransferBasket<T>>::insert(collection_id, _item_id, basket);2345 sponsored = false;
2448 true2346 }
2449 }2347 }
2450 else {2348 if sponsored {
2451 false2349 <FungibleTransferBasket<T>>::insert(collection_id, who, block_number);
2452 }2350 }
2351
2352 sponsored
2453 }2353 }
2454 else {2354 CollectionMode::ReFungible(_) => {
2455 basket.push(BasketItem { start_block: block_number, address: _new_owner.clone()});2355
2356 // get correct limit
2357 let limit: u32 = if collection_limits.sponsor_transfer_timeout > 0 {
2358 collection_limits.sponsor_transfer_timeout
2359 } else {
2360 ChainLimit::get().refungible_sponsor_transfer_timeout
2361 };
2362
2363 let mut sponsored = true;
2364 if <ReFungibleTransferBasket<T>>::contains_key(collection_id, item_id) {
2365 let last_tx_block = <ReFungibleTransferBasket<T>>::get(collection_id, item_id);
2366 let limit_time = last_tx_block + limit.into();
2456 true2367 if block_number <= limit_time {
2457 }2368 sponsored = false;
2369 }
2458 }2370 }
2459 CollectionMode::ReFungible(_) => {2371 if sponsored {
2372 <ReFungibleTransferBasket<T>>::insert(collection_id, item_id, block_number);
2373 }
24602374
2461 // get correct limit2375 sponsored
2462 let limit: u32 = if _collection_limits.sponsor_transfer_timeout > 0 {
2463 _collection_limits.sponsor_transfer_timeout
2464 } else {
2465 ChainLimit::get().refungible_sponsor_transfer_timeout
2466 };
2467
2468 let basket = <ReFungibleTransferBasket<T>>::get(collection_id, _item_id);
2469 let block_number = <system::Module<T>>::block_number() as T::BlockNumber;
2470 let limit_time = basket + limit.into();
2471 if block_number >= limit_time {
2472 <ReFungibleTransferBasket<T>>::insert(collection_id, _item_id, block_number);
2473 true
2474 } else {
2475 false
2476 }2376 }
2477 }2377 _ => {
2478 _ => {2378 false
2479 false2379 },
2480 },2380 };
2481 };2381 }
24822382
2483 if !sponsor_transfer {2383 if !sponsor_transfer {
2484 T::AccountId::default()2384 T::AccountId::default()
2555 let mut who_pays_fee: T::AccountId = sponsor.clone();2455 let mut who_pays_fee: T::AccountId = sponsor.clone();
2556 if sponsor == T::AccountId::default() {2456 if sponsor == T::AccountId::default() {
2557 who_pays_fee = who.clone();2457 who_pays_fee = who.clone();
2558 }
2559
2560 // Only mess with balances if fee is not zero.
2561 if fee.is_zero() {
2562 return Ok((fee, None));
2563 }2458 }
25642459
2565 match <T as transaction_payment::Trait>::Currency::withdraw(2460 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/src/confirmSponsorship.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/confirmSponsorship.test.ts
@@ -0,0 +1,350 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
+import { 
+  createCollectionExpectSuccess, 
+  setCollectionSponsorExpectSuccess, 
+  destroyCollectionExpectSuccess, 
+  setCollectionSponsorExpectFailure,
+  confirmSponsorshipExpectSuccess,
+  confirmSponsorshipExpectFailure,
+  createItemExpectSuccess,
+  findUnusedAddress,
+  getGenericResult,
+  enableWhiteListExpectSuccess,
+  enablePublicMintingExpectSuccess,
+  addToWhiteListExpectSuccess,
+} from "./util/helpers";
+import { Keyring } from "@polkadot/api";
+import { IKeyringPair } from "@polkadot/types/types";
+import type { AccountId } from '@polkadot/types/interfaces';
+import { BigNumber } from 'bignumber.js';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let alice: IKeyringPair;
+let bob: IKeyringPair;
+let charlie: IKeyringPair;
+
+describe.only('integration test: ext. confirmSponsorship():', () => {
+
+  before(async () => {
+    await usingApi(async (api) => {
+      const keyring = new Keyring({ type: 'sr25519' });
+      alice = keyring.addFromUri(`//Alice`);
+      bob = keyring.addFromUri(`//Bob`);
+      charlie = keyring.addFromUri(`//Charlie`);
+    });
+  });
+
+  it('Confirm collection sponsorship', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+  });
+  it('Add sponsor to a collection after the same sponsor was already added and confirmed', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Add new sponsor to a collection after another sponsor was already added and confirmed', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+    await setCollectionSponsorExpectSuccess(collectionId, charlie.address);
+  });
+
+  it('NFT: Transfer fees are paid by the sponsor after confirmation', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Mint token for unused address
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', zeroBalance.address);
+
+      // Transfer this tokens from unused address to Alice
+      const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 0);
+      const events = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result = getGenericResult(events);
+
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      expect(result.success).to.be.true;
+      expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+    });
+
+  });
+
+  it('Fungible: Transfer fees are paid by the sponsor after confirmation', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Mint token for unused address
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', zeroBalance.address);
+
+      // Transfer this tokens from unused address to Alice
+      const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+      const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result1 = getGenericResult(events1);
+
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      expect(result1.success).to.be.true;
+      expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+    });
+  });
+
+  it('ReFungible: Transfer fees are paid by the sponsor after confirmation', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Mint token for unused address
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', zeroBalance.address);
+
+      // Transfer this tokens from unused address to Alice
+      const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+      const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result1 = getGenericResult(events1);
+
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      expect(result1.success).to.be.true;
+      expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+    });
+  });
+
+  it('CreateItem fees are paid by the sponsor after confirmation', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    // Enable collection white list 
+    await enableWhiteListExpectSuccess(alice, collectionId);
+
+    // Enable public minting
+    await enablePublicMintingExpectSuccess(alice, collectionId);
+
+    // Create Item 
+    await usingApi(async (api) => {
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Add zeroBalance address to white list
+      await addToWhiteListExpectSuccess(alice, collectionId, zeroBalance.address);
+
+      // Mint token using unused address as signer
+      const tokenId = await createItemExpectSuccess(zeroBalance, collectionId, 'NFT', zeroBalance.address);
+
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      expect(BsponsorBalance.lt(AsponsorBalance)).to.be.true;
+    });
+  });
+
+  it('NFT: Sponsoring is rate limited', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Mint token for alice
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'NFT', alice.address);
+
+      // Transfer this token from Alice to unused address and back
+      // Alice to Zero gets sponsored
+      const aliceToZero = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 0);
+      const events1 = await submitTransactionAsync(alice, aliceToZero);
+      const result1 = getGenericResult(events1);
+
+      // Second transfer should fail
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+      const zeroToAlice = api.tx.nft.transfer(alice.address, collectionId, itemId, 0);
+      const badTransaction = async function () { 
+        console.log = function () {};
+        console.error = function () {};
+        await submitTransactionAsync(zeroBalance, zeroToAlice);
+        delete console.log;
+        delete console.error;
+      };
+      await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Try again after Zero gets some balance - now it should succeed
+      const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
+      await submitTransactionAsync(alice, balancetx);
+      const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result2 = getGenericResult(events2);
+
+      expect(result1.success).to.be.true;
+      expect(result2.success).to.be.true;
+      expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+    });
+  });
+
+  it('Fungible: Sponsoring is rate limited', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Mint token for unused address
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'Fungible', zeroBalance.address);
+
+      // Transfer this tokens in parts from unused address to Alice
+      const zeroToAlice = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+      const events1 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result1 = getGenericResult(events1);
+
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      const badTransaction = async function () { 
+        console.log = function () {};
+        console.error = function () {};
+        await submitTransactionAsync(zeroBalance, zeroToAlice);
+        delete console.log;
+        delete console.error;
+      };
+
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Try again after Zero gets some balance - now it should succeed
+      const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
+      await submitTransactionAsync(alice, balancetx);
+      const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result2 = getGenericResult(events2);
+
+      expect(result1.success).to.be.true;
+      expect(result2.success).to.be.true;
+      expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+    });
+  });
+
+  it('ReFungible: Sponsoring is rate limited', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectSuccess(collectionId, '//Bob');
+
+    await usingApi(async (api) => {
+      // Find unused address
+      const zeroBalance = await findUnusedAddress(api);
+
+      // Mint token for alice
+      const itemId = await createItemExpectSuccess(alice, collectionId, 'ReFungible', alice.address);
+
+      // Transfer this token from Alice to unused address and back
+      // Alice to Zero gets sponsored
+      const aliceToZero = api.tx.nft.transfer(zeroBalance.address, collectionId, itemId, 1);
+      const events1 = await submitTransactionAsync(alice, aliceToZero);
+      const result1 = getGenericResult(events1);
+
+      // Second transfer should fail
+      const AsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+      const zeroToAlice = api.tx.nft.transfer(alice.address, collectionId, itemId, 1);
+      const badTransaction = async function () { 
+        console.log = function () {};
+        console.error = function () {};
+        await submitTransactionAsync(zeroBalance, zeroToAlice);
+        delete console.log;
+        delete console.error;
+      };
+      await expect(badTransaction()).to.be.rejectedWith("Inability to pay some fees");
+      const BsponsorBalance = new BigNumber((await api.query.system.account(bob.address)).data.free.toString());
+
+      // Try again after Zero gets some balance - now it should succeed
+      const balancetx = api.tx.balances.transfer(zeroBalance.address, 1e15);
+      await submitTransactionAsync(alice, balancetx);
+      const events2 = await submitTransactionAsync(zeroBalance, zeroToAlice);
+      const result2 = getGenericResult(events2);
+
+      expect(result1.success).to.be.true;
+      expect(result2.success).to.be.true;
+      expect(BsponsorBalance.isEqualTo(AsponsorBalance)).to.be.true;
+    });
+  });
+
+});
+
+describe.only('(!negative test!) integration test: ext. setCollectionSponsor():', () => {
+  before(async () => {
+    await usingApi(async (api) => {
+      const keyring = new Keyring({ type: 'sr25519' });
+      alice = keyring.addFromUri(`//Alice`);
+      bob = keyring.addFromUri(`//Bob`);
+      charlie = keyring.addFromUri(`//Charlie`);
+    });
+  });
+
+  it('(!negative test!) Confirm sponsorship for a collection that never existed', async () => {
+    // Find the collection that never existed
+    const collectionId = 0;
+    await usingApi(async (api) => {
+      const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+    });
+
+    await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+  });
+
+  it('(!negative test!) Confirm sponsorship using a non-sponsor address', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+
+    await usingApi(async (api) => {
+      const transfer = api.tx.balances.transfer(charlie.address, 1e15);
+      await submitTransactionAsync(alice, transfer);
+    });
+
+    await confirmSponsorshipExpectFailure(collectionId, '//Charlie');
+  });
+
+  it('(!negative test!) Confirm sponsorship using owner address', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await confirmSponsorshipExpectFailure(collectionId, '//Alice');
+  });
+
+  it('(!negative test!) Confirm sponsorship without sponsor being set with setCollectionSponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+  });
+    
+  it('(!negative test!) Confirm sponsorship in a collection that was destroyed', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await destroyCollectionExpectSuccess(collectionId);
+    await confirmSponsorshipExpectFailure(collectionId, '//Bob');
+  });
+});
modifiedtests/src/createItem.test.tsdiffbeforeafterboth
--- a/tests/src/createItem.test.ts
+++ b/tests/src/createItem.test.ts
@@ -1,27 +1,34 @@
-import { assert } from 'chai';
-import { alicesPublicKey } from './accounts';
-import privateKey from './substrate/privateKey';
 import { default as usingApi } from './substrate/substrate-api';
-import waitNewBlocks from './substrate/wait-new-blocks';
+import { Keyring } from "@polkadot/api";
+import { IKeyringPair } from "@polkadot/types/types";
 import { 
   createCollectionExpectSuccess, 
   createItemExpectSuccess
 } from './util/helpers';
 
+let alice: IKeyringPair;
+
 describe('integration test: ext. createItem():', () => {
+  before(async () => {
+    await usingApi(async (api) => {
+      const keyring = new Keyring({ type: 'sr25519' });
+      alice = keyring.addFromUri(`//Alice`);
+    });
+  });
+
   it('Create new item in NFT collection', async () => {
     const createMode = 'NFT';
     const newCollectionID = await createCollectionExpectSuccess('0', '0', '0', createMode);
-    await createItemExpectSuccess(newCollectionID, createMode, '//Alice');
+    await createItemExpectSuccess(alice, newCollectionID, createMode);
   });
   it('Create new item in Fungible collection', async () => {
     const createMode = 'Fungible';
     const newCollectionID = await createCollectionExpectSuccess('0', '0', '0', createMode);
-    await createItemExpectSuccess(newCollectionID, createMode, '//Alice');
+    await createItemExpectSuccess(alice, newCollectionID, createMode);
   });
   it('Create new item in ReFungible collection', async () => {
     const createMode = 'ReFungible';
     const newCollectionID = await createCollectionExpectSuccess('0', '0', '0', createMode);
-    await createItemExpectSuccess(newCollectionID, createMode, '//Alice');
+    await createItemExpectSuccess(alice, newCollectionID, createMode);
   });
 });
modifiedtests/src/createMultipleItems.test.tsdiffbeforeafterboth
--- a/tests/src/createMultipleItems.test.ts
+++ b/tests/src/createMultipleItems.test.ts
@@ -11,7 +11,7 @@
 
 const idCollection = 12;
 
-describe('integration test: ext. createMultipleItems():', () => {
+describe.skip('integration test: ext. createMultipleItems():', () => {
   it('Create two NFT tokens in active NFT collection', async () => {
     await usingApi(async (api) => {
       const AitemListIndex = await api.query.nft.itemListIndex(idCollection);
modifiedtests/src/destroyCollection.test.tsdiffbeforeafterboth
--- a/tests/src/destroyCollection.test.ts
+++ b/tests/src/destroyCollection.test.ts
@@ -1,55 +1,12 @@
 import chai from 'chai';
 import chaiAsPromised from 'chai-as-promised';
 import { default as usingApi, submitTransactionAsync } from "./substrate/substrate-api";
-import { createCollectionExpectSuccess, createCollectionExpectFailure } from "./util/helpers";
+import { createCollectionExpectSuccess, createCollectionExpectFailure, destroyCollectionExpectSuccess, destroyCollectionExpectFailure } from "./util/helpers";
 import type { AccountId, EventRecord } from '@polkadot/types/interfaces';
 import privateKey from './substrate/privateKey';
-import { nullPublicKey } from './accounts'; 
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
-
-function getDestroyResult(events: EventRecord[]): boolean {
-  let success: boolean = false;
-  events.forEach(({ phase, event: { data, method, section } }) => {
-    // console.log(`    ${phase}: ${section}.${method}:: ${data}`);
-    if (method == 'ExtrinsicSuccess') {
-      success = true;
-    }
-  });
-  return success;
-}
-
-async function destroyCollectionExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
-  await usingApi(async (api) => {
-    // Run the DestroyCollection transaction
-    const alicePrivateKey = privateKey(senderSeed);
-    const tx = api.tx.nft.destroyCollection(collectionId);
-    const events = await submitTransactionAsync(alicePrivateKey, tx);
-    const result = getDestroyResult(events);
-
-    // Get the collection 
-    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
-
-    // What to expect
-    expect(result).to.be.true;
-    expect(collection).to.be.not.null;
-    expect(collection.Owner).to.be.equal(nullPublicKey);
-  });
-}
-
-async function destroyCollectionExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
-  await usingApi(async (api) => {
-    // Run the DestroyCollection transaction
-    const alicePrivateKey = privateKey(senderSeed);
-    const tx = api.tx.nft.destroyCollection(collectionId);
-    const events = await submitTransactionAsync(alicePrivateKey, tx);
-    const result = getDestroyResult(events);
-
-    // What to expect
-    expect(result).to.be.false;
-  });
-}
 
 describe('integration test: ext. destroyCollection():', () => {
   it('NFT collection can be destroyed', async () => {
addedtests/src/setCollectionSponsor.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/setCollectionSponsor.test.ts
@@ -0,0 +1,82 @@
+//
+// This file is subject to the terms and conditions defined in
+// file 'LICENSE', which is part of this source code package.
+//
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+import { default as usingApi } from "./substrate/substrate-api";
+import { createCollectionExpectSuccess, setCollectionSponsorExpectSuccess, destroyCollectionExpectSuccess, setCollectionSponsorExpectFailure } from "./util/helpers";
+import { Keyring } from "@polkadot/api";
+import { IKeyringPair } from "@polkadot/types/types";
+import type { AccountId } from '@polkadot/types/interfaces';
+
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let bob: IKeyringPair;
+
+describe('integration test: ext. setCollectionSponsor():', () => {
+
+  before(async () => {
+    await usingApi(async (api) => {
+      const keyring = new Keyring({ type: 'sr25519' });
+      bob = keyring.addFromUri(`//Bob`);
+    });
+  });
+
+  it('Set NFT collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Set Fungible collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'Fungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Set ReFungible collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'ReFungible');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+
+  it('Set the same sponsor repeatedly', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+  });
+  it('Replace collection sponsor', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+
+    const keyring = new Keyring({ type: 'sr25519' });
+    const charlie = keyring.addFromUri(`//Charlie`);
+    await setCollectionSponsorExpectSuccess(collectionId, bob.address);
+    await setCollectionSponsorExpectSuccess(collectionId, charlie.address);
+  });
+});
+
+describe('(!negative test!) integration test: ext. setCollectionSponsor():', () => {
+  before(async () => {
+    await usingApi(async (api) => {
+      const keyring = new Keyring({ type: 'sr25519' });
+      bob = keyring.addFromUri(`//Bob`);
+    });
+  });
+
+  it('(!negative test!) Add sponsor with a non-owner', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await setCollectionSponsorExpectFailure(collectionId, bob.address, '//Bob');
+  });
+  it('(!negative test!) Add sponsor to a collection that never existed', async () => {
+    // Find the collection that never existed
+    const collectionId = 0;
+    await usingApi(async (api) => {
+      const collectionId = parseInt((await api.query.nft.createdCollectionCount()).toString()) + 1;
+    });
+
+    await setCollectionSponsorExpectFailure(collectionId, bob.address);
+  });
+  it('(!negative test!) Add sponsor to a collection that was destroyed', async () => {
+    const collectionId = await createCollectionExpectSuccess('A', 'B', 'C', 'NFT');
+    await destroyCollectionExpectSuccess(collectionId);
+    await setCollectionSponsorExpectFailure(collectionId, bob.address);
+  });
+});
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -9,10 +9,12 @@
 import { ApiPromise, Keyring } from "@polkadot/api";
 import { default as usingApi, submitTransactionAsync } from "../substrate/substrate-api";
 import privateKey from '../substrate/privateKey';
-import { alicesPublicKey } from "../accounts";
+import { alicesPublicKey, nullPublicKey } from "../accounts";
 import { strToUTF16, utf16ToStr, hexToStr } from '../util/util';
 import { IKeyringPair } from "@polkadot/types/types";
 import { BigNumber } from 'bignumber.js';
+import { Struct, Enum } from '@polkadot/types/codec';
+import { u128 } from '@polkadot/types/primitive';
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
@@ -26,6 +28,12 @@
   collectionId: number
 };
 
+type CreateItemResult = {
+  success: boolean,
+  collectionId: number,
+  itemId: number
+};
+
 export function getGenericResult(events: EventRecord[]): GenericResult {
   let result: GenericResult = {
     success: false
@@ -57,6 +65,27 @@
   return result;
 }
 
+function getCreateItemResult(events: EventRecord[]): CreateItemResult {
+  let success = false;
+  let collectionId: number = 0;
+  let itemId: number = 0;
+  events.forEach(({ phase, event: { data, method, section } }) => {
+    // console.log(`    ${phase}: ${section}.${method}:: ${data}`);
+    if (method == 'ExtrinsicSuccess') {
+      success = true;
+    } else if ((section == 'nft') && (method == 'ItemCreated')) {
+      collectionId = parseInt(data[0].toString());
+      itemId = parseInt(data[1].toString());
+    }
+  });
+  let result: CreateItemResult = {
+    success,
+    collectionId,
+    itemId
+  }
+  return result;
+}
+
 export async function createCollectionExpectSuccess(name: string, description: string, tokenPrefix: string, mode: string): Promise<number> {
   let collectionId: number = 0;
   await usingApi(async (api) => {
@@ -123,20 +152,214 @@
   return unused; 
 }
 
-export async function createItemExpectSuccess(collectionId: number, createMode: string, senderSeed: string = '//Alice') {
+function getDestroyResult(events: EventRecord[]): boolean {
+  let success: boolean = false;
+  events.forEach(({ phase, event: { data, method, section } }) => {
+    // console.log(`    ${phase}: ${section}.${method}:: ${data}`);
+    if (method == 'ExtrinsicSuccess') {
+      success = true;
+    }
+  });
+  return success;
+}
+
+export async function destroyCollectionExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+    // Run the DestroyCollection transaction
+    const alicePrivateKey = privateKey(senderSeed);
+    const tx = api.tx.nft.destroyCollection(collectionId);
+    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const result = getDestroyResult(events);
+
+    // What to expect
+    expect(result).to.be.false;
+  });
+}
+
+export async function destroyCollectionExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+    // Run the DestroyCollection transaction
+    const alicePrivateKey = privateKey(senderSeed);
+    const tx = api.tx.nft.destroyCollection(collectionId);
+    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const result = getDestroyResult(events);
+
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result).to.be.true;
+    expect(collection).to.be.not.null;
+    expect(collection.Owner).to.be.equal(nullPublicKey);
+  });
+}
+
+export async function setCollectionSponsorExpectSuccess(collectionId: number, sponsor: string) {
   await usingApi(async (api) => {
 
-    const AItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
+    // Run the transaction
+    const alicePrivateKey = privateKey('//Alice');
+    const tx = api.tx.nft.setCollectionSponsor(collectionId, sponsor);
+    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const result = getGenericResult(events);
 
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result.success).to.be.true;
+    expect(collection.Sponsor.toString()).to.be.equal(sponsor.toString());
+    expect(collection.SponsorConfirmed).to.be.false;
+  });
+}
+
+export async function setCollectionSponsorExpectFailure(collectionId: number, sponsor: string, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const alicePrivateKey = privateKey(senderSeed);
+    const tx = api.tx.nft.setCollectionSponsor(collectionId, sponsor);
+    const events = await submitTransactionAsync(alicePrivateKey, tx);
+    const result = getGenericResult(events);
+
+    // What to expect
+    expect(result.success).to.be.false;
+  });
+}
+
+export async function confirmSponsorshipExpectSuccess(collectionId: number, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+
+    // Run the transaction
     const sender = privateKey(senderSeed);
-    const tx = api.tx.nft.createItem(collectionId, sender.address, createMode);
+    const tx = api.tx.nft.confirmSponsorship(collectionId);
     const events = await submitTransactionAsync(sender, tx);
     const result = getGenericResult(events);
-  
+
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result.success).to.be.true;
+    expect(collection.Sponsor).to.be.equal(sender.address);
+    expect(collection.SponsorConfirmed).to.be.true;
+  });
+}
+
+export async function confirmSponsorshipExpectFailure(collectionId: number, senderSeed: string = '//Alice') {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const sender = privateKey(senderSeed);
+    const tx = api.tx.nft.confirmSponsorship(collectionId);
+    const events = await submitTransactionAsync(sender, tx);
+    const result = getGenericResult(events);
+
+    // What to expect
+    expect(result.success).to.be.false;
+  });
+}
+
+export interface CreateFungibleData extends Struct {
+  readonly value: u128;
+};
+
+export interface CreateReFungibleData extends Struct {};
+export interface CreateNftData extends Struct {};
+
+export interface CreateItemData extends Enum {
+  NFT: CreateNftData,
+  Fungible: CreateFungibleData,
+  ReFungible: CreateReFungibleData
+};
+
+export async function createItemExpectSuccess(sender: IKeyringPair, collectionId: number, createMode: string, owner: string = '') {
+  let newItemId: number = 0;
+  await usingApi(async (api) => {
+    const AItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
+    const Aitem: any = (await api.query.nft.fungibleItemList(collectionId, owner)).toJSON();    
+    const AItemBalance = new BigNumber(Aitem.Value);
+
+    if (owner === '') owner = sender.address;
+
+    let tx;
+    if (createMode == 'Fungible') {
+      let createData = {fungible: {value: 10}};
+      tx = api.tx.nft.createItem(collectionId, owner, createData);
+    }
+    else {
+      tx = api.tx.nft.createItem(collectionId, owner, createMode);
+    }
+    const events = await submitTransactionAsync(sender, tx);
+    const result = getCreateItemResult(events);
+
     const BItemCount = parseInt((await api.query.nft.itemListIndex(collectionId)).toString());
+    const Bitem: any = (await api.query.nft.fungibleItemList(collectionId, owner)).toJSON();    
+    const BItemBalance = new BigNumber(Bitem.Value);
 
     // What to expect
     expect(result.success).to.be.true;
-    expect(BItemCount).to.be.equal(AItemCount+1);
+    if (createMode == 'Fungible') {
+      expect(BItemBalance.minus(AItemBalance).toNumber()).to.be.equal(10);
+    }
+    else {
+      expect(BItemCount).to.be.equal(AItemCount+1);
+    }
+    expect(collectionId).to.be.equal(result.collectionId);
+    expect(BItemCount).to.be.equal(result.itemId);
+    newItemId = result.itemId;
+  });
+  return newItemId;
+}
+
+export async function enableWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number) {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const tx = api.tx.nft.setPublicAccessMode(collectionId, 'WhiteList');
+    const events = await submitTransactionAsync(sender, tx);
+    const result = getGenericResult(events);
+
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result.success).to.be.true;
+    expect(collection.Access).to.be.equal('WhiteList');
+  });
+}
+
+export async function enablePublicMintingExpectSuccess(sender: IKeyringPair, collectionId: number) {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const tx = api.tx.nft.setMintPermission(collectionId, true);
+    const events = await submitTransactionAsync(sender, tx);
+    const result = getGenericResult(events);
+
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result.success).to.be.true;
+    expect(collection.MintMode).to.be.equal(true);
   });
 }
+
+export async function addToWhiteListExpectSuccess(sender: IKeyringPair, collectionId: number, address: string) {
+  await usingApi(async (api) => {
+
+    // Run the transaction
+    const tx = api.tx.nft.addToWhiteList(collectionId, address);
+    const events = await submitTransactionAsync(sender, tx);
+    const result = getGenericResult(events);
+
+    // Get the collection 
+    const collection: any = (await api.query.nft.collection(collectionId)).toJSON();
+
+    // What to expect
+    expect(result.success).to.be.true;
+    expect(collection.MintMode).to.be.equal(true);
+  });
+}
+