git.delta.rocks / unique-network / refs/commits / 509c8280fa0c

difftreelog

Merge pull request #426 from UniqueNetwork/feature/token_owners

Yaroslav Bolyukin2022-07-22parents: #c2a34ed #10fad85.patch.diff
in: master

31 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5953,7 +5953,7 @@
 
 [[package]]
 name = "pallet-fungible"
-version = "0.1.0"
+version = "0.1.1"
 dependencies = [
  "ethereum",
  "evm-coder",
@@ -6198,7 +6198,7 @@
 
 [[package]]
 name = "pallet-nonfungible"
-version = "0.1.0"
+version = "0.1.1"
 dependencies = [
  "ethereum",
  "evm-coder",
@@ -12377,7 +12377,7 @@
 
 [[package]]
 name = "uc-rpc"
-version = "0.1.0"
+version = "0.1.1"
 dependencies = [
  "anyhow",
  "jsonrpsee",
@@ -12682,7 +12682,7 @@
 
 [[package]]
 name = "unique-runtime-common"
-version = "0.9.24"
+version = "0.9.25"
 dependencies = [
  "evm-coder",
  "fp-rpc",
@@ -12754,7 +12754,7 @@
 
 [[package]]
 name = "up-rpc"
-version = "0.1.0"
+version = "0.1.1"
 dependencies = [
  "pallet-common",
  "pallet-evm",
addedclient/rpc/CHANGELOG.mddiffbeforeafterboth
--- /dev/null
+++ b/client/rpc/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+    This was an internal request to improve the web interface and support fractionalization event. 
+ 
\ No newline at end of file
modifiedclient/rpc/Cargo.tomldiffbeforeafterboth
--- a/client/rpc/Cargo.toml
+++ b/client/rpc/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "uc-rpc"
-version = "0.1.0"
+version = "0.1.1"
 license = "GPLv3"
 edition = "2021"
 
modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
--- a/client/rpc/src/lib.rs
+++ b/client/rpc/src/lib.rs
@@ -70,6 +70,16 @@
 		token: TokenId,
 		at: Option<BlockHash>,
 	) -> Result<Option<CrossAccountId>>;
+
+	/// Returns 10 tokens owners in no particular order.
+	#[method(name = "unique_tokenOwners")]
+	fn token_owners(
+		&self,
+		collection: CollectionId,
+		token: TokenId,
+		at: Option<BlockHash>,
+	) -> Result<Vec<CrossAccountId>>;
+
 	#[method(name = "unique_topmostTokenOwner")]
 	fn topmost_token_owner(
 		&self,
@@ -473,6 +483,7 @@
 	pass_method!(next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Option<u64>, unique_api);
 	pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>, unique_api);
 	pass_method!(total_pieces(collection_id: CollectionId, token_id: TokenId) -> Option<u128>, unique_api);
+	pass_method!(token_owners(collection: CollectionId, token: TokenId) -> Vec<CrossAccountId>, unique_api);
 }
 
 #[allow(deprecated)]
addedpallets/common/CHANGELOG.mddiffbeforeafterboth
--- /dev/null
+++ b/pallets/common/CHANGELOG.md
@@ -0,0 +1,12 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+    This was an internal request to improve the web interface and support fractionalization event. 
+
+ 
\ No newline at end of file
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -1748,6 +1748,11 @@
 	/// * `token` - The token for which you need to find out the owner.
 	fn token_owner(&self, token: TokenId) -> Option<T::CrossAccountId>;
 
+	/// Returns 10 tokens owners in no particular order.
+	///
+	/// * `token` - The token for which you need to find out the owners.
+	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId>;
+
 	/// Get the value of the token property by key.
 	///
 	/// * `token` - Token with the property to get.
addedpallets/fungible/CHANGELOG.mddiffbeforeafterboth
--- /dev/null
+++ b/pallets/fungible/CHANGELOG.md
@@ -0,0 +1,13 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+    This was an internal request to improve the web interface and support fractionalization event. 
+    
+ 
+ 
\ No newline at end of file
modifiedpallets/fungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/fungible/Cargo.toml
+++ b/pallets/fungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-fungible"
-version = "0.1.0"
+version = "0.1.1"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/fungible/src/common.rsdiffbeforeafterboth
--- a/pallets/fungible/src/common.rs
+++ b/pallets/fungible/src/common.rs
@@ -362,6 +362,11 @@
 		None
 	}
 
+	/// Returns 10 tokens owners in no particular order.
+	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
+		<Pallet<T>>::token_owners(self.id, token).unwrap_or_default()
+	}
+
 	fn token_property(&self, _token_id: TokenId, _key: &PropertyKey) -> Option<PropertyValue> {
 		None
 	}
modifiedpallets/fungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/fungible/src/lib.rs
+++ b/pallets/fungible/src/lib.rs
@@ -95,7 +95,7 @@
 use pallet_evm_coder_substrate::WithRecorder;
 use sp_core::H160;
 use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
-use sp_std::{collections::btree_map::BTreeMap};
+use sp_std::{collections::btree_map::BTreeMap, vec::Vec};
 
 pub use pallet::*;
 
@@ -613,4 +613,26 @@
 			nesting_budget,
 		)
 	}
+
+	/// Returns 10 tokens owners in no particular order
+	///
+	/// There is no direct way to get token holders in ascending order,
+	/// since `iter_prefix` returns values in no particular order.
+	/// Therefore, getting the 10 largest holders with a large value of holders
+	/// can lead to impact memory allocation + sorting with  `n * log (n)`.
+	pub fn token_owners(
+		collection: CollectionId,
+		_token: TokenId,
+	) -> Option<Vec<T::CrossAccountId>> {
+		let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection,))
+			.map(|(owner, _amount)| owner)
+			.take(10)
+			.collect();
+
+		if res.is_empty() {
+			None
+		} else {
+			Some(res)
+		}
+	}
 }
addedpallets/nonfungible/CHANGELOG.mddiffbeforeafterboth
--- /dev/null
+++ b/pallets/nonfungible/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+- Implementation of RPC method `token_owners`.
+   For reasons of compatibility with this pallet, returns only one owner if token exists.
+   This was an internal request to improve the web interface and support fractionalization event. 
\ No newline at end of file
modifiedpallets/nonfungible/Cargo.tomldiffbeforeafterboth
--- a/pallets/nonfungible/Cargo.toml
+++ b/pallets/nonfungible/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pallet-nonfungible"
-version = "0.1.0"
+version = "0.1.1"
 license = "GPLv3"
 edition = "2021"
 
modifiedpallets/nonfungible/src/common.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/common.rs
+++ b/pallets/nonfungible/src/common.rs
@@ -26,7 +26,7 @@
 	weights::WeightInfo as _,
 };
 use sp_runtime::DispatchError;
-use sp_std::vec::Vec;
+use sp_std::{vec::Vec, vec};
 
 use crate::{
 	AccountBalance, Allowance, Config, CreateItemData, Error, NonfungibleHandle, Owned, Pallet,
@@ -422,6 +422,11 @@
 		<TokenData<T>>::get((self.id, token)).map(|t| t.owner)
 	}
 
+	/// Returns token owners.
+	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
+		self.token_owner(token).map_or_else(|| vec![], |t| vec![t])
+	}
+
 	fn token_property(&self, token_id: TokenId, key: &PropertyKey) -> Option<PropertyValue> {
 		<Pallet<T>>::token_properties((self.id, token_id))
 			.get(key)
modifiedpallets/refungible/CHANGELOG.mddiffbeforeafterboth
--- a/pallets/refungible/CHANGELOG.md
+++ b/pallets/refungible/CHANGELOG.md
@@ -1,6 +1,18 @@
-## v0.1.2 - 2022-07
+# Change Log
 
-### Refungible Pallet
+All notable changes to this project will be documented in this file.
 
+## [v0.1.2] - 2022-07-14
+
+### Other changes
+
 feat(refungible-pallet): add ERC-20 EVM API for RFT token pieces ([#413](https://github.com/UniqueNetwork/unique-chain/pull/413))
-test(refungible-pallet): add tests for ERC-20 EVM API for RFT token pieces ([#413](https://github.com/UniqueNetwork/unique-chain/pull/413))
\ No newline at end of file
+test(refungible-pallet): add tests for ERC-20 EVM API for RFT token pieces ([#413](https://github.com/UniqueNetwork/unique-chain/pull/413))
+
+## [v0.1.1] - 2022-07-14
+
+### Other changes
+
+- feat: RPC method `token_owners` returning 10 owners in no particular order.
+
+This was an internal request to improve the web interface and support fractionalization event. 
modifiedpallets/refungible/src/common.rsdiffbeforeafterboth
--- a/pallets/refungible/src/common.rs
+++ b/pallets/refungible/src/common.rs
@@ -438,6 +438,11 @@
 		<Pallet<T>>::token_owner(self.id, token)
 	}
 
+	/// Returns 10 token in no particular order.
+	fn token_owners(&self, token: TokenId) -> Vec<T::CrossAccountId> {
+		<Pallet<T>>::token_owners(self.id, token).unwrap_or_default()
+	}
+
 	fn token_property(&self, _token_id: TokenId, _key: &PropertyKey) -> Option<PropertyValue> {
 		None
 	}
modifiedpallets/refungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/refungible/src/lib.rs
+++ b/pallets/refungible/src/lib.rs
@@ -1235,4 +1235,26 @@
 	) -> DispatchResult {
 		<PalletCommon<T>>::set_token_property_permissions(collection, sender, property_permissions)
 	}
+
+	/// Returns 10 token in no particular order.
+	///
+	/// There is no direct way to get token holders in ascending order,
+	/// since `iter_prefix` returns values in no particular order.
+	/// Therefore, getting the 10 largest holders with a large value of holders
+	/// can lead to impact memory allocation + sorting with  `n * log (n)`.
+	pub fn token_owners(
+		collection_id: CollectionId,
+		token: TokenId,
+	) -> Option<Vec<T::CrossAccountId>> {
+		let res: Vec<T::CrossAccountId> = <Balance<T>>::iter_prefix((collection_id, token))
+			.map(|(owner, _amount)| owner)
+			.take(10)
+			.collect();
+
+		if res.is_empty() {
+			None
+		} else {
+			Some(res)
+		}
+	}
 }
addedprimitives/rpc/CHANGELOG.mddiffbeforeafterboth
--- /dev/null
+++ b/primitives/rpc/CHANGELOG.md
@@ -0,0 +1,10 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.1.1] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+   This was an internal request to improve the web interface and support fractionalization event. 
\ No newline at end of file
modifiedprimitives/rpc/Cargo.tomldiffbeforeafterboth
--- a/primitives/rpc/Cargo.toml
+++ b/primitives/rpc/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "up-rpc"
-version = "0.1.0"
+version = "0.1.1"
 license = "GPLv3"
 edition = "2021"
 
modifiedprimitives/rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -81,5 +81,6 @@
 		fn next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Result<Option<u64>>;
 		fn effective_collection_limits(collection_id: CollectionId) -> Result<Option<CollectionLimits>>;
 		fn total_pieces(collection_id: CollectionId, token_id: TokenId) -> Result<Option<u128>>;
+		fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec<CrossAccountId>>;
 	}
 }
addedruntime/common/CHANGELOG.mddiffbeforeafterboth
--- /dev/null
+++ b/runtime/common/CHANGELOG.md
@@ -0,0 +1,13 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## [0.9.25] - 2022-07-14
+
+### Added
+
+ - Implementation of RPC method `token_owners` returning 10 owners in no particular order.
+    This was an internal request to improve the web interface and support fractionalization event. 
+
+
+ 
\ No newline at end of file
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -40,6 +40,11 @@
                 fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
                     dispatch_unique_runtime!(collection.token_owner(token))
                 }
+
+                fn token_owners(collection: CollectionId, token: TokenId) -> Result<Vec::<CrossAccountId>, DispatchError>  {
+                   dispatch_unique_runtime!(collection.token_owners(token))
+                }
+
                 fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>, DispatchError> {
                     let budget = up_data_structs::budget::Value::new(10);
 
addedtests/CHANGELOG.mddiffbeforeafterboth
--- /dev/null
+++ b/tests/CHANGELOG.md
@@ -0,0 +1,11 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+
+## 2022-07-14
+
+### Added
+ - Integrintegration tests of RPC method `token_owners`.
+ - Integrintegration tests of Fungible Pallet.
+  
+ 
\ No newline at end of file
modifiedtests/package.jsondiffbeforeafterboth
--- a/tests/package.json
+++ b/tests/package.json
@@ -80,6 +80,8 @@
     "testLimits": "mocha --timeout 9999999 -r ts-node/register ./**/limits.test.ts",
     "testEthCreateCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createCollection.test.ts",
     "testRFT": "mocha --timeout 9999999 -r ts-node/register ./**/refungible.test.ts",
+    "testFT": "mocha --timeout 9999999 -r ts-node/register ./**/fungible.test.ts",
+    "testRPC": "mocha --timeout 9999999 -r ts-node/register ./**/rpc.test.ts",
     "polkadot-types-fetch-metadata": "curl -H 'Content-Type: application/json' -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > src/interfaces/metadata.json",
     "polkadot-types-from-defs": "ts-node ./node_modules/.bin/polkadot-types-from-defs --endpoint src/interfaces/metadata.json --input src/interfaces/ --package .",
     "polkadot-types-from-chain": "ts-node ./node_modules/.bin/polkadot-types-from-chain --endpoint src/interfaces/metadata.json --output src/interfaces/ --package .",
addedtests/src/fungible.test.tsdiffbeforeafterboth
--- /dev/null
+++ b/tests/src/fungible.test.ts
@@ -0,0 +1,184 @@
+// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.
+// This file is part of Unique Network.
+
+// Unique Network is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Unique Network is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.
+
+import {default as usingApi} from './substrate/substrate-api';
+import {IKeyringPair} from '@polkadot/types/types';
+import {
+  getBalance,
+  createMultipleItemsExpectSuccess,
+  isTokenExists,
+  getLastTokenId,
+  getAllowance,
+  approve,
+  transferFrom,
+  createCollection,
+  transfer,
+  burnItem,
+  normalizeAccountId,
+  CrossAccountId,
+  createFungibleItemExpectSuccess,
+  U128_MAX,
+  burnFromExpectSuccess,
+} from './util/helpers';
+
+import chai from 'chai';
+import chaiAsPromised from 'chai-as-promised';
+chai.use(chaiAsPromised);
+const expect = chai.expect;
+
+let alice: IKeyringPair;
+let bob: IKeyringPair;
+
+describe('integration test: Fungible functionality:', () => {
+  before(async () => {
+    await usingApi(async (api, privateKeyWrapper) => {
+      alice = privateKeyWrapper('//Alice');
+      bob = privateKeyWrapper('//Bob');
+    });
+  });
+
+  it('Create fungible collection and token', async () => {
+    await usingApi(async api => {
+      const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}});
+      expect(createCollectionResult.success).to.be.true;
+      const collectionId  = createCollectionResult.collectionId;
+      const defaultTokenId = await getLastTokenId(api, collectionId);
+      const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address);
+      const aliceBalance = await getBalance(api, collectionId, alice, aliceTokenId); 
+      const itemCountAfter = await getLastTokenId(api, collectionId);
+      
+      // What to expect
+      // tslint:disable-next-line:no-unused-expression
+      expect(itemCountAfter).to.be.equal(defaultTokenId);
+      expect(aliceBalance).to.be.equal(U128_MAX);
+    });
+  });
+  
+  it('RPC method tokenOnewrs for fungible collection and token', async () => {
+    await usingApi(async (api, privateKeyWrapper) => {
+      const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+      const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString())));
+      
+      const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}});
+      const collectionId = createCollectionResult.collectionId;
+      const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address);
+     
+      await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n);
+      await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n);
+            
+      for (let i = 0; i < 7; i++) {
+        await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 1);
+      } 
+      
+      const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId);
+      const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s));
+      const aliceID = normalizeAccountId(alice);
+      const bobId = normalizeAccountId(bob);
+
+      // What to expect
+      // tslint:disable-next-line:no-unused-expression
+      expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]);
+      expect(owners.length == 10).to.be.true;
+      
+      const eleven = privateKeyWrapper('11');
+      expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
+      expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+    });
+  });
+  
+  it('Transfer token', async () => {
+    await usingApi(async api => {
+      const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+      const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+      const tokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address);
+
+      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(500n);
+      expect(await transfer(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
+      expect(await transfer(api, collectionId, tokenId, alice, ethAcc, 140n)).to.be.true;
+
+      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(300n);
+      expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(60n);
+      expect(await getBalance(api, collectionId, ethAcc, tokenId)).to.be.equal(140n);
+      await expect(transfer(api, collectionId, tokenId, alice, bob, 350n)).to.eventually.be.rejected;
+    });
+  });
+
+  it('Tokens multiple creation', async () => {
+    await usingApi(async api => {
+      const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+      
+      const args = [
+        {Fungible: {Value: 500n}},
+        {Fungible: {Value: 400n}},
+        {Fungible: {Value: 300n}},
+      ];
+      
+      await createMultipleItemsExpectSuccess(alice, collectionId, args);
+      expect(await getBalance(api, collectionId, alice, 0)).to.be.equal(1200n);
+    });   
+  });
+
+  it('Burn some tokens ', async () => {
+    await usingApi(async api => {   
+      const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+      const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address));
+      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(500n);
+      expect(await burnItem(api, alice, collectionId, tokenId, 499n)).to.be.true;
+      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(1n);
+    });
+  });
+  
+  it('Burn all tokens ', async () => {
+    await usingApi(async api => {   
+      const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+      const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 500n}, alice.address));
+      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+      expect(await burnItem(api, alice, collectionId, tokenId, 500n)).to.be.true;
+      expect(await isTokenExists(api, collectionId, tokenId)).to.be.true;
+      
+      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(0n);
+      expect((await api.rpc.unique.totalPieces(collectionId, tokenId)).value.toBigInt()).to.be.equal(0n);
+    });
+  });
+
+  it('Set allowance for token', async () => {
+    await usingApi(async api => {
+      const collectionId = (await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}})).collectionId;
+      const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+      
+      const tokenId = (await createFungibleItemExpectSuccess(alice, collectionId, {Value: 100n}, alice.address));
+      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(100n);
+
+      expect(await approve(api, collectionId, tokenId, alice, bob, 60n)).to.be.true;
+      expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(60n);
+      expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(0n);
+      
+      expect(await transferFrom(api, collectionId, tokenId, bob, alice, bob,  20n)).to.be.true;
+      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(80n);
+      expect(await getBalance(api, collectionId, bob, tokenId)).to.be.equal(20n);
+      expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(40n);
+      
+      await burnFromExpectSuccess(bob, alice, collectionId, tokenId, 10n);
+     
+      expect(await getBalance(api, collectionId, alice, tokenId)).to.be.equal(70n);
+      expect(await getAllowance(api, collectionId, alice, bob, tokenId)).to.be.equal(30n);
+      expect(await transferFrom(api, collectionId, tokenId, bob, alice, ethAcc,  10n)).to.be.true;
+      expect(await getBalance(api, collectionId, ethAcc, tokenId)).to.be.equal(10n);
+    });
+  });
+});
modifiedtests/src/interfaces/augment-api-consts.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-consts.ts
+++ b/tests/src/interfaces/augment-api-consts.ts
@@ -29,7 +29,13 @@
       [key: string]: Codec;
     };
     common: {
+      /**
+       * Maximum admins per collection.
+       **/
       collectionAdminsLimit: u32 & AugmentedConst<ApiType>;
+      /**
+       * Set price to create a collection.
+       **/
       collectionCreationPrice: u128 & AugmentedConst<ApiType>;
       /**
        * Generic const
modifiedtests/src/interfaces/augment-api-events.tsdiffbeforeafterboth
before · tests/src/interfaces/augment-api-events.ts
1// Auto-generated via `yarn polkadot-types-from-chain`, do not edit2/* eslint-disable */34import type { ApiTypes } from '@polkadot/api-base/types';5import type { Bytes, Null, Option, Result, U256, U8aFixed, bool, u128, u32, u64, u8 } from '@polkadot/types-codec';6import type { ITuple } from '@polkadot/types-codec/types';7import type { AccountId32, H160, H256 } from '@polkadot/types/interfaces/runtime';8import type { EthereumLog, EvmCoreErrorExitReason, FrameSupportScheduleLookupError, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchInfo, OrmlVestingVestingSchedule, PalletEvmAccountBasicCrossAccountIdRepr, RmrkTraitsNftAccountIdOrCollectionNftTuple, SpRuntimeDispatchError, XcmV1MultiLocation, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation } from '@polkadot/types/lookup';910declare module '@polkadot/api-base/types/events' {11  export interface AugmentedEvents<ApiType extends ApiTypes> {12    balances: {13      /**14       * A balance was set by root.15       **/16      BalanceSet: AugmentedEvent<ApiType, [who: AccountId32, free: u128, reserved: u128], { who: AccountId32, free: u128, reserved: u128 }>;17      /**18       * Some amount was deposited (e.g. for transaction fees).19       **/20      Deposit: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;21      /**22       * An account was removed whose balance was non-zero but below ExistentialDeposit,23       * resulting in an outright loss.24       **/25      DustLost: AugmentedEvent<ApiType, [account: AccountId32, amount: u128], { account: AccountId32, amount: u128 }>;26      /**27       * An account was created with some free balance.28       **/29      Endowed: AugmentedEvent<ApiType, [account: AccountId32, freeBalance: u128], { account: AccountId32, freeBalance: u128 }>;30      /**31       * Some balance was reserved (moved from free to reserved).32       **/33      Reserved: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;34      /**35       * Some balance was moved from the reserve of the first account to the second account.36       * Final argument indicates the destination balance type.37       **/38      ReserveRepatriated: AugmentedEvent<ApiType, [from: AccountId32, to: AccountId32, amount: u128, destinationStatus: FrameSupportTokensMiscBalanceStatus], { from: AccountId32, to: AccountId32, amount: u128, destinationStatus: FrameSupportTokensMiscBalanceStatus }>;39      /**40       * Some amount was removed from the account (e.g. for misbehavior).41       **/42      Slashed: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;43      /**44       * Transfer succeeded.45       **/46      Transfer: AugmentedEvent<ApiType, [from: AccountId32, to: AccountId32, amount: u128], { from: AccountId32, to: AccountId32, amount: u128 }>;47      /**48       * Some balance was unreserved (moved from reserved to free).49       **/50      Unreserved: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;51      /**52       * Some amount was withdrawn from the account (e.g. for transaction fees).53       **/54      Withdraw: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;55      /**56       * Generic event57       **/58      [key: string]: AugmentedEvent<ApiType>;59    };60    common: {61      /**62       * * collection_id63       * 64       * * item_id65       * 66       * * sender67       * 68       * * spender69       * 70       * * amount71       **/72      Approved: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;73      /**74       * New collection was created75       * 76       * # Arguments77       * 78       * * collection_id: Globally unique identifier of newly created collection.79       * 80       * * mode: [CollectionMode] converted into u8.81       * 82       * * account_id: Collection owner.83       **/84      CollectionCreated: AugmentedEvent<ApiType, [u32, u8, AccountId32]>;85      /**86       * New collection was destroyed87       * 88       * # Arguments89       * 90       * * collection_id: Globally unique identifier of collection.91       **/92      CollectionDestroyed: AugmentedEvent<ApiType, [u32]>;93      CollectionPropertyDeleted: AugmentedEvent<ApiType, [u32, Bytes]>;94      CollectionPropertySet: AugmentedEvent<ApiType, [u32, Bytes]>;95      /**96       * New item was created.97       * 98       * # Arguments99       * 100       * * collection_id: Id of the collection where item was created.101       * 102       * * item_id: Id of an item. Unique within the collection.103       * 104       * * recipient: Owner of newly created item105       * 106       * * amount: Always 1 for NFT107       **/108      ItemCreated: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;109      /**110       * Collection item was burned.111       * 112       * # Arguments113       * 114       * * collection_id.115       * 116       * * item_id: Identifier of burned NFT.117       * 118       * * owner: which user has destroyed its tokens119       * 120       * * amount: Always 1 for NFT121       **/122      ItemDestroyed: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;123      PropertyPermissionSet: AugmentedEvent<ApiType, [u32, Bytes]>;124      TokenPropertyDeleted: AugmentedEvent<ApiType, [u32, u32, Bytes]>;125      TokenPropertySet: AugmentedEvent<ApiType, [u32, u32, Bytes]>;126      /**127       * Item was transferred128       * 129       * * collection_id: Id of collection to which item is belong130       * 131       * * item_id: Id of an item132       * 133       * * sender: Original owner of item134       * 135       * * recipient: New owner of item136       * 137       * * amount: Always 1 for NFT138       **/139      Transfer: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;140      /**141       * Generic event142       **/143      [key: string]: AugmentedEvent<ApiType>;144    };145    cumulusXcm: {146      /**147       * Downward message executed with the given outcome.148       * \[ id, outcome \]149       **/150      ExecutedDownward: AugmentedEvent<ApiType, [U8aFixed, XcmV2TraitsOutcome]>;151      /**152       * Downward message is invalid XCM.153       * \[ id \]154       **/155      InvalidFormat: AugmentedEvent<ApiType, [U8aFixed]>;156      /**157       * Downward message is unsupported version of XCM.158       * \[ id \]159       **/160      UnsupportedVersion: AugmentedEvent<ApiType, [U8aFixed]>;161      /**162       * Generic event163       **/164      [key: string]: AugmentedEvent<ApiType>;165    };166    dmpQueue: {167      /**168       * Downward message executed with the given outcome.169       **/170      ExecutedDownward: AugmentedEvent<ApiType, [messageId: U8aFixed, outcome: XcmV2TraitsOutcome], { messageId: U8aFixed, outcome: XcmV2TraitsOutcome }>;171      /**172       * Downward message is invalid XCM.173       **/174      InvalidFormat: AugmentedEvent<ApiType, [messageId: U8aFixed], { messageId: U8aFixed }>;175      /**176       * Downward message is overweight and was placed in the overweight queue.177       **/178      OverweightEnqueued: AugmentedEvent<ApiType, [messageId: U8aFixed, overweightIndex: u64, requiredWeight: u64], { messageId: U8aFixed, overweightIndex: u64, requiredWeight: u64 }>;179      /**180       * Downward message from the overweight queue was executed.181       **/182      OverweightServiced: AugmentedEvent<ApiType, [overweightIndex: u64, weightUsed: u64], { overweightIndex: u64, weightUsed: u64 }>;183      /**184       * Downward message is unsupported version of XCM.185       **/186      UnsupportedVersion: AugmentedEvent<ApiType, [messageId: U8aFixed], { messageId: U8aFixed }>;187      /**188       * The weight limit for handling downward messages was reached.189       **/190      WeightExhausted: AugmentedEvent<ApiType, [messageId: U8aFixed, remainingWeight: u64, requiredWeight: u64], { messageId: U8aFixed, remainingWeight: u64, requiredWeight: u64 }>;191      /**192       * Generic event193       **/194      [key: string]: AugmentedEvent<ApiType>;195    };196    ethereum: {197      /**198       * An ethereum transaction was successfully executed. [from, to/contract_address, transaction_hash, exit_reason]199       **/200      Executed: AugmentedEvent<ApiType, [H160, H160, H256, EvmCoreErrorExitReason]>;201      /**202       * Generic event203       **/204      [key: string]: AugmentedEvent<ApiType>;205    };206    evm: {207      /**208       * A deposit has been made at a given address. \[sender, address, value\]209       **/210      BalanceDeposit: AugmentedEvent<ApiType, [AccountId32, H160, U256]>;211      /**212       * A withdrawal has been made from a given address. \[sender, address, value\]213       **/214      BalanceWithdraw: AugmentedEvent<ApiType, [AccountId32, H160, U256]>;215      /**216       * A contract has been created at given \[address\].217       **/218      Created: AugmentedEvent<ApiType, [H160]>;219      /**220       * A \[contract\] was attempted to be created, but the execution failed.221       **/222      CreatedFailed: AugmentedEvent<ApiType, [H160]>;223      /**224       * A \[contract\] has been executed successfully with states applied.225       **/226      Executed: AugmentedEvent<ApiType, [H160]>;227      /**228       * A \[contract\] has been executed with errors. States are reverted with only gas fees applied.229       **/230      ExecutedFailed: AugmentedEvent<ApiType, [H160]>;231      /**232       * Ethereum events from contracts.233       **/234      Log: AugmentedEvent<ApiType, [EthereumLog]>;235      /**236       * Generic event237       **/238      [key: string]: AugmentedEvent<ApiType>;239    };240    parachainSystem: {241      /**242       * Downward messages were processed using the given weight.243       **/244      DownwardMessagesProcessed: AugmentedEvent<ApiType, [weightUsed: u64, dmqHead: H256], { weightUsed: u64, dmqHead: H256 }>;245      /**246       * Some downward messages have been received and will be processed.247       **/248      DownwardMessagesReceived: AugmentedEvent<ApiType, [count: u32], { count: u32 }>;249      /**250       * An upgrade has been authorized.251       **/252      UpgradeAuthorized: AugmentedEvent<ApiType, [codeHash: H256], { codeHash: H256 }>;253      /**254       * The validation function was applied as of the contained relay chain block number.255       **/256      ValidationFunctionApplied: AugmentedEvent<ApiType, [relayChainBlockNum: u32], { relayChainBlockNum: u32 }>;257      /**258       * The relay-chain aborted the upgrade process.259       **/260      ValidationFunctionDiscarded: AugmentedEvent<ApiType, []>;261      /**262       * The validation function has been scheduled to apply.263       **/264      ValidationFunctionStored: AugmentedEvent<ApiType, []>;265      /**266       * Generic event267       **/268      [key: string]: AugmentedEvent<ApiType>;269    };270    polkadotXcm: {271      /**272       * Some assets have been placed in an asset trap.273       * 274       * \[ hash, origin, assets \]275       **/276      AssetsTrapped: AugmentedEvent<ApiType, [H256, XcmV1MultiLocation, XcmVersionedMultiAssets]>;277      /**278       * Execution of an XCM message was attempted.279       * 280       * \[ outcome \]281       **/282      Attempted: AugmentedEvent<ApiType, [XcmV2TraitsOutcome]>;283      /**284       * Expected query response has been received but the origin location of the response does285       * not match that expected. The query remains registered for a later, valid, response to286       * be received and acted upon.287       * 288       * \[ origin location, id, expected location \]289       **/290      InvalidResponder: AugmentedEvent<ApiType, [XcmV1MultiLocation, u64, Option<XcmV1MultiLocation>]>;291      /**292       * Expected query response has been received but the expected origin location placed in293       * storage by this runtime previously cannot be decoded. The query remains registered.294       * 295       * This is unexpected (since a location placed in storage in a previously executing296       * runtime should be readable prior to query timeout) and dangerous since the possibly297       * valid response will be dropped. Manual governance intervention is probably going to be298       * needed.299       * 300       * \[ origin location, id \]301       **/302      InvalidResponderVersion: AugmentedEvent<ApiType, [XcmV1MultiLocation, u64]>;303      /**304       * Query response has been received and query is removed. The registered notification has305       * been dispatched and executed successfully.306       * 307       * \[ id, pallet index, call index \]308       **/309      Notified: AugmentedEvent<ApiType, [u64, u8, u8]>;310      /**311       * Query response has been received and query is removed. The dispatch was unable to be312       * decoded into a `Call`; this might be due to dispatch function having a signature which313       * is not `(origin, QueryId, Response)`.314       * 315       * \[ id, pallet index, call index \]316       **/317      NotifyDecodeFailed: AugmentedEvent<ApiType, [u64, u8, u8]>;318      /**319       * Query response has been received and query is removed. There was a general error with320       * dispatching the notification call.321       * 322       * \[ id, pallet index, call index \]323       **/324      NotifyDispatchError: AugmentedEvent<ApiType, [u64, u8, u8]>;325      /**326       * Query response has been received and query is removed. The registered notification could327       * not be dispatched because the dispatch weight is greater than the maximum weight328       * originally budgeted by this runtime for the query result.329       * 330       * \[ id, pallet index, call index, actual weight, max budgeted weight \]331       **/332      NotifyOverweight: AugmentedEvent<ApiType, [u64, u8, u8, u64, u64]>;333      /**334       * A given location which had a version change subscription was dropped owing to an error335       * migrating the location to our new XCM format.336       * 337       * \[ location, query ID \]338       **/339      NotifyTargetMigrationFail: AugmentedEvent<ApiType, [XcmVersionedMultiLocation, u64]>;340      /**341       * A given location which had a version change subscription was dropped owing to an error342       * sending the notification to it.343       * 344       * \[ location, query ID, error \]345       **/346      NotifyTargetSendFail: AugmentedEvent<ApiType, [XcmV1MultiLocation, u64, XcmV2TraitsError]>;347      /**348       * Query response has been received and is ready for taking with `take_response`. There is349       * no registered notification call.350       * 351       * \[ id, response \]352       **/353      ResponseReady: AugmentedEvent<ApiType, [u64, XcmV2Response]>;354      /**355       * Received query response has been read and removed.356       * 357       * \[ id \]358       **/359      ResponseTaken: AugmentedEvent<ApiType, [u64]>;360      /**361       * A XCM message was sent.362       * 363       * \[ origin, destination, message \]364       **/365      Sent: AugmentedEvent<ApiType, [XcmV1MultiLocation, XcmV1MultiLocation, XcmV2Xcm]>;366      /**367       * The supported version of a location has been changed. This might be through an368       * automatic notification or a manual intervention.369       * 370       * \[ location, XCM version \]371       **/372      SupportedVersionChanged: AugmentedEvent<ApiType, [XcmV1MultiLocation, u32]>;373      /**374       * Query response received which does not match a registered query. This may be because a375       * matching query was never registered, it may be because it is a duplicate response, or376       * because the query timed out.377       * 378       * \[ origin location, id \]379       **/380      UnexpectedResponse: AugmentedEvent<ApiType, [XcmV1MultiLocation, u64]>;381      /**382       * An XCM version change notification message has been attempted to be sent.383       * 384       * \[ destination, result \]385       **/386      VersionChangeNotified: AugmentedEvent<ApiType, [XcmV1MultiLocation, u32]>;387      /**388       * Generic event389       **/390      [key: string]: AugmentedEvent<ApiType>;391    };392    rmrkCore: {393      CollectionCreated: AugmentedEvent<ApiType, [issuer: AccountId32, collectionId: u32], { issuer: AccountId32, collectionId: u32 }>;394      CollectionDestroyed: AugmentedEvent<ApiType, [issuer: AccountId32, collectionId: u32], { issuer: AccountId32, collectionId: u32 }>;395      CollectionLocked: AugmentedEvent<ApiType, [issuer: AccountId32, collectionId: u32], { issuer: AccountId32, collectionId: u32 }>;396      IssuerChanged: AugmentedEvent<ApiType, [oldIssuer: AccountId32, newIssuer: AccountId32, collectionId: u32], { oldIssuer: AccountId32, newIssuer: AccountId32, collectionId: u32 }>;397      NFTAccepted: AugmentedEvent<ApiType, [sender: AccountId32, recipient: RmrkTraitsNftAccountIdOrCollectionNftTuple, collectionId: u32, nftId: u32], { sender: AccountId32, recipient: RmrkTraitsNftAccountIdOrCollectionNftTuple, collectionId: u32, nftId: u32 }>;398      NFTBurned: AugmentedEvent<ApiType, [owner: AccountId32, nftId: u32], { owner: AccountId32, nftId: u32 }>;399      NftMinted: AugmentedEvent<ApiType, [owner: AccountId32, collectionId: u32, nftId: u32], { owner: AccountId32, collectionId: u32, nftId: u32 }>;400      NFTRejected: AugmentedEvent<ApiType, [sender: AccountId32, collectionId: u32, nftId: u32], { sender: AccountId32, collectionId: u32, nftId: u32 }>;401      NFTSent: AugmentedEvent<ApiType, [sender: AccountId32, recipient: RmrkTraitsNftAccountIdOrCollectionNftTuple, collectionId: u32, nftId: u32, approvalRequired: bool], { sender: AccountId32, recipient: RmrkTraitsNftAccountIdOrCollectionNftTuple, collectionId: u32, nftId: u32, approvalRequired: bool }>;402      PrioritySet: AugmentedEvent<ApiType, [collectionId: u32, nftId: u32], { collectionId: u32, nftId: u32 }>;403      PropertySet: AugmentedEvent<ApiType, [collectionId: u32, maybeNftId: Option<u32>, key: Bytes, value: Bytes], { collectionId: u32, maybeNftId: Option<u32>, key: Bytes, value: Bytes }>;404      ResourceAccepted: AugmentedEvent<ApiType, [nftId: u32, resourceId: u32], { nftId: u32, resourceId: u32 }>;405      ResourceAdded: AugmentedEvent<ApiType, [nftId: u32, resourceId: u32], { nftId: u32, resourceId: u32 }>;406      ResourceRemoval: AugmentedEvent<ApiType, [nftId: u32, resourceId: u32], { nftId: u32, resourceId: u32 }>;407      ResourceRemovalAccepted: AugmentedEvent<ApiType, [nftId: u32, resourceId: u32], { nftId: u32, resourceId: u32 }>;408      /**409       * Generic event410       **/411      [key: string]: AugmentedEvent<ApiType>;412    };413    rmrkEquip: {414      BaseCreated: AugmentedEvent<ApiType, [issuer: AccountId32, baseId: u32], { issuer: AccountId32, baseId: u32 }>;415      EquippablesUpdated: AugmentedEvent<ApiType, [baseId: u32, slotId: u32], { baseId: u32, slotId: u32 }>;416      /**417       * Generic event418       **/419      [key: string]: AugmentedEvent<ApiType>;420    };421    scheduler: {422      /**423       * The call for the provided hash was not found so the task has been aborted.424       **/425      CallLookupFailed: AugmentedEvent<ApiType, [task: ITuple<[u32, u32]>, id: Option<U8aFixed>, error: FrameSupportScheduleLookupError], { task: ITuple<[u32, u32]>, id: Option<U8aFixed>, error: FrameSupportScheduleLookupError }>;426      /**427       * Canceled some task.428       **/429      Canceled: AugmentedEvent<ApiType, [when: u32, index: u32], { when: u32, index: u32 }>;430      /**431       * Dispatched some task.432       **/433      Dispatched: AugmentedEvent<ApiType, [task: ITuple<[u32, u32]>, id: Option<U8aFixed>, result: Result<Null, SpRuntimeDispatchError>], { task: ITuple<[u32, u32]>, id: Option<U8aFixed>, result: Result<Null, SpRuntimeDispatchError> }>;434      /**435       * Scheduled some task.436       **/437      Scheduled: AugmentedEvent<ApiType, [when: u32, index: u32], { when: u32, index: u32 }>;438      /**439       * Generic event440       **/441      [key: string]: AugmentedEvent<ApiType>;442    };443    structure: {444      /**445       * Executed call on behalf of token446       **/447      Executed: AugmentedEvent<ApiType, [Result<Null, SpRuntimeDispatchError>]>;448      /**449       * Generic event450       **/451      [key: string]: AugmentedEvent<ApiType>;452    };453    sudo: {454      /**455       * The \[sudoer\] just switched identity; the old key is supplied if one existed.456       **/457      KeyChanged: AugmentedEvent<ApiType, [oldSudoer: Option<AccountId32>], { oldSudoer: Option<AccountId32> }>;458      /**459       * A sudo just took place. \[result\]460       **/461      Sudid: AugmentedEvent<ApiType, [sudoResult: Result<Null, SpRuntimeDispatchError>], { sudoResult: Result<Null, SpRuntimeDispatchError> }>;462      /**463       * A sudo just took place. \[result\]464       **/465      SudoAsDone: AugmentedEvent<ApiType, [sudoResult: Result<Null, SpRuntimeDispatchError>], { sudoResult: Result<Null, SpRuntimeDispatchError> }>;466      /**467       * Generic event468       **/469      [key: string]: AugmentedEvent<ApiType>;470    };471    system: {472      /**473       * `:code` was updated.474       **/475      CodeUpdated: AugmentedEvent<ApiType, []>;476      /**477       * An extrinsic failed.478       **/479      ExtrinsicFailed: AugmentedEvent<ApiType, [dispatchError: SpRuntimeDispatchError, dispatchInfo: FrameSupportWeightsDispatchInfo], { dispatchError: SpRuntimeDispatchError, dispatchInfo: FrameSupportWeightsDispatchInfo }>;480      /**481       * An extrinsic completed successfully.482       **/483      ExtrinsicSuccess: AugmentedEvent<ApiType, [dispatchInfo: FrameSupportWeightsDispatchInfo], { dispatchInfo: FrameSupportWeightsDispatchInfo }>;484      /**485       * An account was reaped.486       **/487      KilledAccount: AugmentedEvent<ApiType, [account: AccountId32], { account: AccountId32 }>;488      /**489       * A new account was created.490       **/491      NewAccount: AugmentedEvent<ApiType, [account: AccountId32], { account: AccountId32 }>;492      /**493       * On on-chain remark happened.494       **/495      Remarked: AugmentedEvent<ApiType, [sender: AccountId32, hash_: H256], { sender: AccountId32, hash_: H256 }>;496      /**497       * Generic event498       **/499      [key: string]: AugmentedEvent<ApiType>;500    };501    treasury: {502      /**503       * Some funds have been allocated.504       **/505      Awarded: AugmentedEvent<ApiType, [proposalIndex: u32, award: u128, account: AccountId32], { proposalIndex: u32, award: u128, account: AccountId32 }>;506      /**507       * Some of our funds have been burnt.508       **/509      Burnt: AugmentedEvent<ApiType, [burntFunds: u128], { burntFunds: u128 }>;510      /**511       * Some funds have been deposited.512       **/513      Deposit: AugmentedEvent<ApiType, [value: u128], { value: u128 }>;514      /**515       * New proposal.516       **/517      Proposed: AugmentedEvent<ApiType, [proposalIndex: u32], { proposalIndex: u32 }>;518      /**519       * A proposal was rejected; funds were slashed.520       **/521      Rejected: AugmentedEvent<ApiType, [proposalIndex: u32, slashed: u128], { proposalIndex: u32, slashed: u128 }>;522      /**523       * Spending has finished; this is the amount that rolls over until next spend.524       **/525      Rollover: AugmentedEvent<ApiType, [rolloverBalance: u128], { rolloverBalance: u128 }>;526      /**527       * We have ended a spend period and will now allocate funds.528       **/529      Spending: AugmentedEvent<ApiType, [budgetRemaining: u128], { budgetRemaining: u128 }>;530      /**531       * Generic event532       **/533      [key: string]: AugmentedEvent<ApiType>;534    };535    unique: {536      /**537       * Address was add to allow list538       * 539       * # Arguments540       * 541       * * collection_id: Globally unique collection identifier.542       * 543       * * user:  Address.544       **/545      AllowListAddressAdded: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;546      /**547       * Address was remove from allow list548       * 549       * # Arguments550       * 551       * * collection_id: Globally unique collection identifier.552       * 553       * * user:  Address.554       **/555      AllowListAddressRemoved: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;556      /**557       * Collection admin was added558       * 559       * # Arguments560       * 561       * * collection_id: Globally unique collection identifier.562       * 563       * * admin:  Admin address.564       **/565      CollectionAdminAdded: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;566      /**567       * Collection admin was removed568       * 569       * # Arguments570       * 571       * * collection_id: Globally unique collection identifier.572       * 573       * * admin:  Admin address.574       **/575      CollectionAdminRemoved: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;576      /**577       * Collection limits was set578       * 579       * # Arguments580       * 581       * * collection_id: Globally unique collection identifier.582       **/583      CollectionLimitSet: AugmentedEvent<ApiType, [u32]>;584      /**585       * Collection owned was change586       * 587       * # Arguments588       * 589       * * collection_id: Globally unique collection identifier.590       * 591       * * owner:  New owner address.592       **/593      CollectionOwnedChanged: AugmentedEvent<ApiType, [u32, AccountId32]>;594      CollectionPermissionSet: AugmentedEvent<ApiType, [u32]>;595      /**596       * Collection sponsor was removed597       * 598       * # Arguments599       * 600       * * collection_id: Globally unique collection identifier.601       **/602      CollectionSponsorRemoved: AugmentedEvent<ApiType, [u32]>;603      /**604       * Collection sponsor was set605       * 606       * # Arguments607       * 608       * * collection_id: Globally unique collection identifier.609       * 610       * * owner:  New sponsor address.611       **/612      CollectionSponsorSet: AugmentedEvent<ApiType, [u32, AccountId32]>;613      /**614       * New sponsor was confirm615       * 616       * # Arguments617       * 618       * * collection_id: Globally unique collection identifier.619       * 620       * * sponsor:  New sponsor address.621       **/622      SponsorshipConfirmed: AugmentedEvent<ApiType, [u32, AccountId32]>;623      /**624       * Generic event625       **/626      [key: string]: AugmentedEvent<ApiType>;627    };628    vesting: {629      /**630       * Claimed vesting.631       **/632      Claimed: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;633      /**634       * Added new vesting schedule.635       **/636      VestingScheduleAdded: AugmentedEvent<ApiType, [from: AccountId32, to: AccountId32, vestingSchedule: OrmlVestingVestingSchedule], { from: AccountId32, to: AccountId32, vestingSchedule: OrmlVestingVestingSchedule }>;637      /**638       * Updated vesting schedules.639       **/640      VestingSchedulesUpdated: AugmentedEvent<ApiType, [who: AccountId32], { who: AccountId32 }>;641      /**642       * Generic event643       **/644      [key: string]: AugmentedEvent<ApiType>;645    };646    xcmpQueue: {647      /**648       * Bad XCM format used.649       **/650      BadFormat: AugmentedEvent<ApiType, [Option<H256>]>;651      /**652       * Bad XCM version used.653       **/654      BadVersion: AugmentedEvent<ApiType, [Option<H256>]>;655      /**656       * Some XCM failed.657       **/658      Fail: AugmentedEvent<ApiType, [Option<H256>, XcmV2TraitsError]>;659      /**660       * An XCM exceeded the individual message weight budget.661       **/662      OverweightEnqueued: AugmentedEvent<ApiType, [u32, u32, u64, u64]>;663      /**664       * An XCM from the overweight queue was executed with the given actual weight used.665       **/666      OverweightServiced: AugmentedEvent<ApiType, [u64, u64]>;667      /**668       * Some XCM was executed ok.669       **/670      Success: AugmentedEvent<ApiType, [Option<H256>]>;671      /**672       * An upward message was sent to the relay chain.673       **/674      UpwardMessageSent: AugmentedEvent<ApiType, [Option<H256>]>;675      /**676       * An HRMP message was sent to a sibling parachain.677       **/678      XcmpMessageSent: AugmentedEvent<ApiType, [Option<H256>]>;679      /**680       * Generic event681       **/682      [key: string]: AugmentedEvent<ApiType>;683    };684  } // AugmentedEvents685} // declare module
after · tests/src/interfaces/augment-api-events.ts
1// Auto-generated via `yarn polkadot-types-from-chain`, do not edit2/* eslint-disable */34import type { ApiTypes } from '@polkadot/api-base/types';5import type { Bytes, Null, Option, Result, U256, U8aFixed, bool, u128, u32, u64, u8 } from '@polkadot/types-codec';6import type { ITuple } from '@polkadot/types-codec/types';7import type { AccountId32, H160, H256 } from '@polkadot/types/interfaces/runtime';8import type { EthereumLog, EvmCoreErrorExitReason, FrameSupportScheduleLookupError, FrameSupportTokensMiscBalanceStatus, FrameSupportWeightsDispatchInfo, OrmlVestingVestingSchedule, PalletEvmAccountBasicCrossAccountIdRepr, RmrkTraitsNftAccountIdOrCollectionNftTuple, SpRuntimeDispatchError, XcmV1MultiLocation, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2Xcm, XcmVersionedMultiAssets, XcmVersionedMultiLocation } from '@polkadot/types/lookup';910declare module '@polkadot/api-base/types/events' {11  export interface AugmentedEvents<ApiType extends ApiTypes> {12    balances: {13      /**14       * A balance was set by root.15       **/16      BalanceSet: AugmentedEvent<ApiType, [who: AccountId32, free: u128, reserved: u128], { who: AccountId32, free: u128, reserved: u128 }>;17      /**18       * Some amount was deposited (e.g. for transaction fees).19       **/20      Deposit: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;21      /**22       * An account was removed whose balance was non-zero but below ExistentialDeposit,23       * resulting in an outright loss.24       **/25      DustLost: AugmentedEvent<ApiType, [account: AccountId32, amount: u128], { account: AccountId32, amount: u128 }>;26      /**27       * An account was created with some free balance.28       **/29      Endowed: AugmentedEvent<ApiType, [account: AccountId32, freeBalance: u128], { account: AccountId32, freeBalance: u128 }>;30      /**31       * Some balance was reserved (moved from free to reserved).32       **/33      Reserved: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;34      /**35       * Some balance was moved from the reserve of the first account to the second account.36       * Final argument indicates the destination balance type.37       **/38      ReserveRepatriated: AugmentedEvent<ApiType, [from: AccountId32, to: AccountId32, amount: u128, destinationStatus: FrameSupportTokensMiscBalanceStatus], { from: AccountId32, to: AccountId32, amount: u128, destinationStatus: FrameSupportTokensMiscBalanceStatus }>;39      /**40       * Some amount was removed from the account (e.g. for misbehavior).41       **/42      Slashed: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;43      /**44       * Transfer succeeded.45       **/46      Transfer: AugmentedEvent<ApiType, [from: AccountId32, to: AccountId32, amount: u128], { from: AccountId32, to: AccountId32, amount: u128 }>;47      /**48       * Some balance was unreserved (moved from reserved to free).49       **/50      Unreserved: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;51      /**52       * Some amount was withdrawn from the account (e.g. for transaction fees).53       **/54      Withdraw: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;55      /**56       * Generic event57       **/58      [key: string]: AugmentedEvent<ApiType>;59    };60    common: {61      /**62       * Amount pieces of token owned by `sender` was approved for `spender`.63       **/64      Approved: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;65      /**66       * New collection was created67       **/68      CollectionCreated: AugmentedEvent<ApiType, [u32, u8, AccountId32]>;69      /**70       * New collection was destroyed71       **/72      CollectionDestroyed: AugmentedEvent<ApiType, [u32]>;73      /**74       * The property has been deleted.75       **/76      CollectionPropertyDeleted: AugmentedEvent<ApiType, [u32, Bytes]>;77      /**78       * The colletion property has been set.79       **/80      CollectionPropertySet: AugmentedEvent<ApiType, [u32, Bytes]>;81      /**82       * New item was created.83       **/84      ItemCreated: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;85      /**86       * Collection item was burned.87       **/88      ItemDestroyed: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;89      /**90       * The colletion property permission has been set.91       **/92      PropertyPermissionSet: AugmentedEvent<ApiType, [u32, Bytes]>;93      /**94       * The token property has been deleted.95       **/96      TokenPropertyDeleted: AugmentedEvent<ApiType, [u32, u32, Bytes]>;97      /**98       * The token property has been set.99       **/100      TokenPropertySet: AugmentedEvent<ApiType, [u32, u32, Bytes]>;101      /**102       * Item was transferred103       **/104      Transfer: AugmentedEvent<ApiType, [u32, u32, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmAccountBasicCrossAccountIdRepr, u128]>;105      /**106       * Generic event107       **/108      [key: string]: AugmentedEvent<ApiType>;109    };110    cumulusXcm: {111      /**112       * Downward message executed with the given outcome.113       * \[ id, outcome \]114       **/115      ExecutedDownward: AugmentedEvent<ApiType, [U8aFixed, XcmV2TraitsOutcome]>;116      /**117       * Downward message is invalid XCM.118       * \[ id \]119       **/120      InvalidFormat: AugmentedEvent<ApiType, [U8aFixed]>;121      /**122       * Downward message is unsupported version of XCM.123       * \[ id \]124       **/125      UnsupportedVersion: AugmentedEvent<ApiType, [U8aFixed]>;126      /**127       * Generic event128       **/129      [key: string]: AugmentedEvent<ApiType>;130    };131    dmpQueue: {132      /**133       * Downward message executed with the given outcome.134       **/135      ExecutedDownward: AugmentedEvent<ApiType, [messageId: U8aFixed, outcome: XcmV2TraitsOutcome], { messageId: U8aFixed, outcome: XcmV2TraitsOutcome }>;136      /**137       * Downward message is invalid XCM.138       **/139      InvalidFormat: AugmentedEvent<ApiType, [messageId: U8aFixed], { messageId: U8aFixed }>;140      /**141       * Downward message is overweight and was placed in the overweight queue.142       **/143      OverweightEnqueued: AugmentedEvent<ApiType, [messageId: U8aFixed, overweightIndex: u64, requiredWeight: u64], { messageId: U8aFixed, overweightIndex: u64, requiredWeight: u64 }>;144      /**145       * Downward message from the overweight queue was executed.146       **/147      OverweightServiced: AugmentedEvent<ApiType, [overweightIndex: u64, weightUsed: u64], { overweightIndex: u64, weightUsed: u64 }>;148      /**149       * Downward message is unsupported version of XCM.150       **/151      UnsupportedVersion: AugmentedEvent<ApiType, [messageId: U8aFixed], { messageId: U8aFixed }>;152      /**153       * The weight limit for handling downward messages was reached.154       **/155      WeightExhausted: AugmentedEvent<ApiType, [messageId: U8aFixed, remainingWeight: u64, requiredWeight: u64], { messageId: U8aFixed, remainingWeight: u64, requiredWeight: u64 }>;156      /**157       * Generic event158       **/159      [key: string]: AugmentedEvent<ApiType>;160    };161    ethereum: {162      /**163       * An ethereum transaction was successfully executed. [from, to/contract_address, transaction_hash, exit_reason]164       **/165      Executed: AugmentedEvent<ApiType, [H160, H160, H256, EvmCoreErrorExitReason]>;166      /**167       * Generic event168       **/169      [key: string]: AugmentedEvent<ApiType>;170    };171    evm: {172      /**173       * A deposit has been made at a given address. \[sender, address, value\]174       **/175      BalanceDeposit: AugmentedEvent<ApiType, [AccountId32, H160, U256]>;176      /**177       * A withdrawal has been made from a given address. \[sender, address, value\]178       **/179      BalanceWithdraw: AugmentedEvent<ApiType, [AccountId32, H160, U256]>;180      /**181       * A contract has been created at given \[address\].182       **/183      Created: AugmentedEvent<ApiType, [H160]>;184      /**185       * A \[contract\] was attempted to be created, but the execution failed.186       **/187      CreatedFailed: AugmentedEvent<ApiType, [H160]>;188      /**189       * A \[contract\] has been executed successfully with states applied.190       **/191      Executed: AugmentedEvent<ApiType, [H160]>;192      /**193       * A \[contract\] has been executed with errors. States are reverted with only gas fees applied.194       **/195      ExecutedFailed: AugmentedEvent<ApiType, [H160]>;196      /**197       * Ethereum events from contracts.198       **/199      Log: AugmentedEvent<ApiType, [EthereumLog]>;200      /**201       * Generic event202       **/203      [key: string]: AugmentedEvent<ApiType>;204    };205    parachainSystem: {206      /**207       * Downward messages were processed using the given weight.208       **/209      DownwardMessagesProcessed: AugmentedEvent<ApiType, [weightUsed: u64, dmqHead: H256], { weightUsed: u64, dmqHead: H256 }>;210      /**211       * Some downward messages have been received and will be processed.212       **/213      DownwardMessagesReceived: AugmentedEvent<ApiType, [count: u32], { count: u32 }>;214      /**215       * An upgrade has been authorized.216       **/217      UpgradeAuthorized: AugmentedEvent<ApiType, [codeHash: H256], { codeHash: H256 }>;218      /**219       * The validation function was applied as of the contained relay chain block number.220       **/221      ValidationFunctionApplied: AugmentedEvent<ApiType, [relayChainBlockNum: u32], { relayChainBlockNum: u32 }>;222      /**223       * The relay-chain aborted the upgrade process.224       **/225      ValidationFunctionDiscarded: AugmentedEvent<ApiType, []>;226      /**227       * The validation function has been scheduled to apply.228       **/229      ValidationFunctionStored: AugmentedEvent<ApiType, []>;230      /**231       * Generic event232       **/233      [key: string]: AugmentedEvent<ApiType>;234    };235    polkadotXcm: {236      /**237       * Some assets have been placed in an asset trap.238       * 239       * \[ hash, origin, assets \]240       **/241      AssetsTrapped: AugmentedEvent<ApiType, [H256, XcmV1MultiLocation, XcmVersionedMultiAssets]>;242      /**243       * Execution of an XCM message was attempted.244       * 245       * \[ outcome \]246       **/247      Attempted: AugmentedEvent<ApiType, [XcmV2TraitsOutcome]>;248      /**249       * Expected query response has been received but the origin location of the response does250       * not match that expected. The query remains registered for a later, valid, response to251       * be received and acted upon.252       * 253       * \[ origin location, id, expected location \]254       **/255      InvalidResponder: AugmentedEvent<ApiType, [XcmV1MultiLocation, u64, Option<XcmV1MultiLocation>]>;256      /**257       * Expected query response has been received but the expected origin location placed in258       * storage by this runtime previously cannot be decoded. The query remains registered.259       * 260       * This is unexpected (since a location placed in storage in a previously executing261       * runtime should be readable prior to query timeout) and dangerous since the possibly262       * valid response will be dropped. Manual governance intervention is probably going to be263       * needed.264       * 265       * \[ origin location, id \]266       **/267      InvalidResponderVersion: AugmentedEvent<ApiType, [XcmV1MultiLocation, u64]>;268      /**269       * Query response has been received and query is removed. The registered notification has270       * been dispatched and executed successfully.271       * 272       * \[ id, pallet index, call index \]273       **/274      Notified: AugmentedEvent<ApiType, [u64, u8, u8]>;275      /**276       * Query response has been received and query is removed. The dispatch was unable to be277       * decoded into a `Call`; this might be due to dispatch function having a signature which278       * is not `(origin, QueryId, Response)`.279       * 280       * \[ id, pallet index, call index \]281       **/282      NotifyDecodeFailed: AugmentedEvent<ApiType, [u64, u8, u8]>;283      /**284       * Query response has been received and query is removed. There was a general error with285       * dispatching the notification call.286       * 287       * \[ id, pallet index, call index \]288       **/289      NotifyDispatchError: AugmentedEvent<ApiType, [u64, u8, u8]>;290      /**291       * Query response has been received and query is removed. The registered notification could292       * not be dispatched because the dispatch weight is greater than the maximum weight293       * originally budgeted by this runtime for the query result.294       * 295       * \[ id, pallet index, call index, actual weight, max budgeted weight \]296       **/297      NotifyOverweight: AugmentedEvent<ApiType, [u64, u8, u8, u64, u64]>;298      /**299       * A given location which had a version change subscription was dropped owing to an error300       * migrating the location to our new XCM format.301       * 302       * \[ location, query ID \]303       **/304      NotifyTargetMigrationFail: AugmentedEvent<ApiType, [XcmVersionedMultiLocation, u64]>;305      /**306       * A given location which had a version change subscription was dropped owing to an error307       * sending the notification to it.308       * 309       * \[ location, query ID, error \]310       **/311      NotifyTargetSendFail: AugmentedEvent<ApiType, [XcmV1MultiLocation, u64, XcmV2TraitsError]>;312      /**313       * Query response has been received and is ready for taking with `take_response`. There is314       * no registered notification call.315       * 316       * \[ id, response \]317       **/318      ResponseReady: AugmentedEvent<ApiType, [u64, XcmV2Response]>;319      /**320       * Received query response has been read and removed.321       * 322       * \[ id \]323       **/324      ResponseTaken: AugmentedEvent<ApiType, [u64]>;325      /**326       * A XCM message was sent.327       * 328       * \[ origin, destination, message \]329       **/330      Sent: AugmentedEvent<ApiType, [XcmV1MultiLocation, XcmV1MultiLocation, XcmV2Xcm]>;331      /**332       * The supported version of a location has been changed. This might be through an333       * automatic notification or a manual intervention.334       * 335       * \[ location, XCM version \]336       **/337      SupportedVersionChanged: AugmentedEvent<ApiType, [XcmV1MultiLocation, u32]>;338      /**339       * Query response received which does not match a registered query. This may be because a340       * matching query was never registered, it may be because it is a duplicate response, or341       * because the query timed out.342       * 343       * \[ origin location, id \]344       **/345      UnexpectedResponse: AugmentedEvent<ApiType, [XcmV1MultiLocation, u64]>;346      /**347       * An XCM version change notification message has been attempted to be sent.348       * 349       * \[ destination, result \]350       **/351      VersionChangeNotified: AugmentedEvent<ApiType, [XcmV1MultiLocation, u32]>;352      /**353       * Generic event354       **/355      [key: string]: AugmentedEvent<ApiType>;356    };357    rmrkCore: {358      CollectionCreated: AugmentedEvent<ApiType, [issuer: AccountId32, collectionId: u32], { issuer: AccountId32, collectionId: u32 }>;359      CollectionDestroyed: AugmentedEvent<ApiType, [issuer: AccountId32, collectionId: u32], { issuer: AccountId32, collectionId: u32 }>;360      CollectionLocked: AugmentedEvent<ApiType, [issuer: AccountId32, collectionId: u32], { issuer: AccountId32, collectionId: u32 }>;361      IssuerChanged: AugmentedEvent<ApiType, [oldIssuer: AccountId32, newIssuer: AccountId32, collectionId: u32], { oldIssuer: AccountId32, newIssuer: AccountId32, collectionId: u32 }>;362      NFTAccepted: AugmentedEvent<ApiType, [sender: AccountId32, recipient: RmrkTraitsNftAccountIdOrCollectionNftTuple, collectionId: u32, nftId: u32], { sender: AccountId32, recipient: RmrkTraitsNftAccountIdOrCollectionNftTuple, collectionId: u32, nftId: u32 }>;363      NFTBurned: AugmentedEvent<ApiType, [owner: AccountId32, nftId: u32], { owner: AccountId32, nftId: u32 }>;364      NftMinted: AugmentedEvent<ApiType, [owner: AccountId32, collectionId: u32, nftId: u32], { owner: AccountId32, collectionId: u32, nftId: u32 }>;365      NFTRejected: AugmentedEvent<ApiType, [sender: AccountId32, collectionId: u32, nftId: u32], { sender: AccountId32, collectionId: u32, nftId: u32 }>;366      NFTSent: AugmentedEvent<ApiType, [sender: AccountId32, recipient: RmrkTraitsNftAccountIdOrCollectionNftTuple, collectionId: u32, nftId: u32, approvalRequired: bool], { sender: AccountId32, recipient: RmrkTraitsNftAccountIdOrCollectionNftTuple, collectionId: u32, nftId: u32, approvalRequired: bool }>;367      PrioritySet: AugmentedEvent<ApiType, [collectionId: u32, nftId: u32], { collectionId: u32, nftId: u32 }>;368      PropertySet: AugmentedEvent<ApiType, [collectionId: u32, maybeNftId: Option<u32>, key: Bytes, value: Bytes], { collectionId: u32, maybeNftId: Option<u32>, key: Bytes, value: Bytes }>;369      ResourceAccepted: AugmentedEvent<ApiType, [nftId: u32, resourceId: u32], { nftId: u32, resourceId: u32 }>;370      ResourceAdded: AugmentedEvent<ApiType, [nftId: u32, resourceId: u32], { nftId: u32, resourceId: u32 }>;371      ResourceRemoval: AugmentedEvent<ApiType, [nftId: u32, resourceId: u32], { nftId: u32, resourceId: u32 }>;372      ResourceRemovalAccepted: AugmentedEvent<ApiType, [nftId: u32, resourceId: u32], { nftId: u32, resourceId: u32 }>;373      /**374       * Generic event375       **/376      [key: string]: AugmentedEvent<ApiType>;377    };378    rmrkEquip: {379      BaseCreated: AugmentedEvent<ApiType, [issuer: AccountId32, baseId: u32], { issuer: AccountId32, baseId: u32 }>;380      EquippablesUpdated: AugmentedEvent<ApiType, [baseId: u32, slotId: u32], { baseId: u32, slotId: u32 }>;381      /**382       * Generic event383       **/384      [key: string]: AugmentedEvent<ApiType>;385    };386    scheduler: {387      /**388       * The call for the provided hash was not found so the task has been aborted.389       **/390      CallLookupFailed: AugmentedEvent<ApiType, [task: ITuple<[u32, u32]>, id: Option<U8aFixed>, error: FrameSupportScheduleLookupError], { task: ITuple<[u32, u32]>, id: Option<U8aFixed>, error: FrameSupportScheduleLookupError }>;391      /**392       * Canceled some task.393       **/394      Canceled: AugmentedEvent<ApiType, [when: u32, index: u32], { when: u32, index: u32 }>;395      /**396       * Dispatched some task.397       **/398      Dispatched: AugmentedEvent<ApiType, [task: ITuple<[u32, u32]>, id: Option<U8aFixed>, result: Result<Null, SpRuntimeDispatchError>], { task: ITuple<[u32, u32]>, id: Option<U8aFixed>, result: Result<Null, SpRuntimeDispatchError> }>;399      /**400       * Scheduled some task.401       **/402      Scheduled: AugmentedEvent<ApiType, [when: u32, index: u32], { when: u32, index: u32 }>;403      /**404       * Generic event405       **/406      [key: string]: AugmentedEvent<ApiType>;407    };408    structure: {409      /**410       * Executed call on behalf of token411       **/412      Executed: AugmentedEvent<ApiType, [Result<Null, SpRuntimeDispatchError>]>;413      /**414       * Generic event415       **/416      [key: string]: AugmentedEvent<ApiType>;417    };418    sudo: {419      /**420       * The \[sudoer\] just switched identity; the old key is supplied if one existed.421       **/422      KeyChanged: AugmentedEvent<ApiType, [oldSudoer: Option<AccountId32>], { oldSudoer: Option<AccountId32> }>;423      /**424       * A sudo just took place. \[result\]425       **/426      Sudid: AugmentedEvent<ApiType, [sudoResult: Result<Null, SpRuntimeDispatchError>], { sudoResult: Result<Null, SpRuntimeDispatchError> }>;427      /**428       * A sudo just took place. \[result\]429       **/430      SudoAsDone: AugmentedEvent<ApiType, [sudoResult: Result<Null, SpRuntimeDispatchError>], { sudoResult: Result<Null, SpRuntimeDispatchError> }>;431      /**432       * Generic event433       **/434      [key: string]: AugmentedEvent<ApiType>;435    };436    system: {437      /**438       * `:code` was updated.439       **/440      CodeUpdated: AugmentedEvent<ApiType, []>;441      /**442       * An extrinsic failed.443       **/444      ExtrinsicFailed: AugmentedEvent<ApiType, [dispatchError: SpRuntimeDispatchError, dispatchInfo: FrameSupportWeightsDispatchInfo], { dispatchError: SpRuntimeDispatchError, dispatchInfo: FrameSupportWeightsDispatchInfo }>;445      /**446       * An extrinsic completed successfully.447       **/448      ExtrinsicSuccess: AugmentedEvent<ApiType, [dispatchInfo: FrameSupportWeightsDispatchInfo], { dispatchInfo: FrameSupportWeightsDispatchInfo }>;449      /**450       * An account was reaped.451       **/452      KilledAccount: AugmentedEvent<ApiType, [account: AccountId32], { account: AccountId32 }>;453      /**454       * A new account was created.455       **/456      NewAccount: AugmentedEvent<ApiType, [account: AccountId32], { account: AccountId32 }>;457      /**458       * On on-chain remark happened.459       **/460      Remarked: AugmentedEvent<ApiType, [sender: AccountId32, hash_: H256], { sender: AccountId32, hash_: H256 }>;461      /**462       * Generic event463       **/464      [key: string]: AugmentedEvent<ApiType>;465    };466    treasury: {467      /**468       * Some funds have been allocated.469       **/470      Awarded: AugmentedEvent<ApiType, [proposalIndex: u32, award: u128, account: AccountId32], { proposalIndex: u32, award: u128, account: AccountId32 }>;471      /**472       * Some of our funds have been burnt.473       **/474      Burnt: AugmentedEvent<ApiType, [burntFunds: u128], { burntFunds: u128 }>;475      /**476       * Some funds have been deposited.477       **/478      Deposit: AugmentedEvent<ApiType, [value: u128], { value: u128 }>;479      /**480       * New proposal.481       **/482      Proposed: AugmentedEvent<ApiType, [proposalIndex: u32], { proposalIndex: u32 }>;483      /**484       * A proposal was rejected; funds were slashed.485       **/486      Rejected: AugmentedEvent<ApiType, [proposalIndex: u32, slashed: u128], { proposalIndex: u32, slashed: u128 }>;487      /**488       * Spending has finished; this is the amount that rolls over until next spend.489       **/490      Rollover: AugmentedEvent<ApiType, [rolloverBalance: u128], { rolloverBalance: u128 }>;491      /**492       * We have ended a spend period and will now allocate funds.493       **/494      Spending: AugmentedEvent<ApiType, [budgetRemaining: u128], { budgetRemaining: u128 }>;495      /**496       * Generic event497       **/498      [key: string]: AugmentedEvent<ApiType>;499    };500    unique: {501      /**502       * Address was add to allow list503       * 504       * # Arguments505       * 506       * * collection_id: Globally unique collection identifier.507       * 508       * * user:  Address.509       **/510      AllowListAddressAdded: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;511      /**512       * Address was remove from allow list513       * 514       * # Arguments515       * 516       * * collection_id: Globally unique collection identifier.517       * 518       * * user:  Address.519       **/520      AllowListAddressRemoved: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;521      /**522       * Collection admin was added523       * 524       * # Arguments525       * 526       * * collection_id: Globally unique collection identifier.527       * 528       * * admin:  Admin address.529       **/530      CollectionAdminAdded: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;531      /**532       * Collection admin was removed533       * 534       * # Arguments535       * 536       * * collection_id: Globally unique collection identifier.537       * 538       * * admin:  Admin address.539       **/540      CollectionAdminRemoved: AugmentedEvent<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;541      /**542       * Collection limits was set543       * 544       * # Arguments545       * 546       * * collection_id: Globally unique collection identifier.547       **/548      CollectionLimitSet: AugmentedEvent<ApiType, [u32]>;549      /**550       * Collection owned was change551       * 552       * # Arguments553       * 554       * * collection_id: Globally unique collection identifier.555       * 556       * * owner:  New owner address.557       **/558      CollectionOwnedChanged: AugmentedEvent<ApiType, [u32, AccountId32]>;559      CollectionPermissionSet: AugmentedEvent<ApiType, [u32]>;560      /**561       * Collection sponsor was removed562       * 563       * # Arguments564       * 565       * * collection_id: Globally unique collection identifier.566       **/567      CollectionSponsorRemoved: AugmentedEvent<ApiType, [u32]>;568      /**569       * Collection sponsor was set570       * 571       * # Arguments572       * 573       * * collection_id: Globally unique collection identifier.574       * 575       * * owner:  New sponsor address.576       **/577      CollectionSponsorSet: AugmentedEvent<ApiType, [u32, AccountId32]>;578      /**579       * New sponsor was confirm580       * 581       * # Arguments582       * 583       * * collection_id: Globally unique collection identifier.584       * 585       * * sponsor:  New sponsor address.586       **/587      SponsorshipConfirmed: AugmentedEvent<ApiType, [u32, AccountId32]>;588      /**589       * Generic event590       **/591      [key: string]: AugmentedEvent<ApiType>;592    };593    vesting: {594      /**595       * Claimed vesting.596       **/597      Claimed: AugmentedEvent<ApiType, [who: AccountId32, amount: u128], { who: AccountId32, amount: u128 }>;598      /**599       * Added new vesting schedule.600       **/601      VestingScheduleAdded: AugmentedEvent<ApiType, [from: AccountId32, to: AccountId32, vestingSchedule: OrmlVestingVestingSchedule], { from: AccountId32, to: AccountId32, vestingSchedule: OrmlVestingVestingSchedule }>;602      /**603       * Updated vesting schedules.604       **/605      VestingSchedulesUpdated: AugmentedEvent<ApiType, [who: AccountId32], { who: AccountId32 }>;606      /**607       * Generic event608       **/609      [key: string]: AugmentedEvent<ApiType>;610    };611    xcmpQueue: {612      /**613       * Bad XCM format used.614       **/615      BadFormat: AugmentedEvent<ApiType, [Option<H256>]>;616      /**617       * Bad XCM version used.618       **/619      BadVersion: AugmentedEvent<ApiType, [Option<H256>]>;620      /**621       * Some XCM failed.622       **/623      Fail: AugmentedEvent<ApiType, [Option<H256>, XcmV2TraitsError]>;624      /**625       * An XCM exceeded the individual message weight budget.626       **/627      OverweightEnqueued: AugmentedEvent<ApiType, [u32, u32, u64, u64]>;628      /**629       * An XCM from the overweight queue was executed with the given actual weight used.630       **/631      OverweightServiced: AugmentedEvent<ApiType, [u64, u64]>;632      /**633       * Some XCM was executed ok.634       **/635      Success: AugmentedEvent<ApiType, [Option<H256>]>;636      /**637       * An upward message was sent to the relay chain.638       **/639      UpwardMessageSent: AugmentedEvent<ApiType, [Option<H256>]>;640      /**641       * An HRMP message was sent to a sibling parachain.642       **/643      XcmpMessageSent: AugmentedEvent<ApiType, [Option<H256>]>;644      /**645       * Generic event646       **/647      [key: string]: AugmentedEvent<ApiType>;648    };649  } // AugmentedEvents650} // declare module
modifiedtests/src/interfaces/augment-api-query.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-query.ts
+++ b/tests/src/interfaces/augment-api-query.ts
@@ -69,24 +69,36 @@
       [key: string]: QueryableStorageEntry<ApiType>;
     };
     common: {
+      /**
+       * Storage of collection admins count.
+       **/
       adminAmount: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
       /**
        * Allowlisted collection users
        **/
       allowlist: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<bool>, [u32, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
       /**
-       * Collection info
+       * Storage of collection info.
        **/
       collectionById: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<Option<UpDataStructsCollection>>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
       /**
-       * Collection properties
+       * Storage of collection properties.
        **/
       collectionProperties: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<UpDataStructsProperties>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
+      /**
+       * Storage of collection properties permissions.
+       **/
       collectionPropertyPermissions: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<BTreeMap<Bytes, UpDataStructsPropertyPermission>>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
+      /**
+       * Storage of the count of created collections.
+       **/
       createdCollectionCount: AugmentedQuery<ApiType, () => Observable<u32>, []> & QueryableStorageEntry<ApiType, []>;
+      /**
+       * Storage of the count of deleted collections.
+       **/
       destroyedCollectionCount: AugmentedQuery<ApiType, () => Observable<u32>, []> & QueryableStorageEntry<ApiType, []>;
       /**
-       * Not used by code, exists only to provide some types to metadata
+       * Not used by code, exists only to provide some types to metadata.
        **/
       dummyStorageValue: AugmentedQuery<ApiType, () => Observable<Option<ITuple<[UpDataStructsCollectionStats, u32, u32, UpDataStructsTokenChild, PhantomTypeUpDataStructs]>>>, []> & QueryableStorageEntry<ApiType, []>;
       /**
@@ -231,20 +243,42 @@
       [key: string]: QueryableStorageEntry<ApiType>;
     };
     nonfungible: {
+      /**
+       * Amount of tokens owned by account.
+       **/
       accountBalance: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array) => Observable<u32>, [u32, PalletEvmAccountBasicCrossAccountIdRepr]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr]>;
+      /**
+       * Allowance set by an owner for a spender for a token.
+       **/
       allowance: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<Option<PalletEvmAccountBasicCrossAccountIdRepr>>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
       /**
-       * Used to enumerate tokens owned by account
+       * Used to enumerate tokens owned by account.
        **/
       owned: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: PalletEvmAccountBasicCrossAccountIdRepr | { Substrate: any } | { Ethereum: any } | string | Uint8Array, arg3: u32 | AnyNumber | Uint8Array) => Observable<bool>, [u32, PalletEvmAccountBasicCrossAccountIdRepr, u32]> & QueryableStorageEntry<ApiType, [u32, PalletEvmAccountBasicCrossAccountIdRepr, u32]>;
+      /**
+       * Custom data that is serialized to bytes and attached to a token property.
+       * Currently used to store RMRK data.
+       **/
       tokenAuxProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: UpDataStructsPropertyScope | 'None' | 'Rmrk' | number | Uint8Array, arg4: Bytes | string | Uint8Array) => Observable<Option<Bytes>>, [u32, u32, UpDataStructsPropertyScope, Bytes]> & QueryableStorageEntry<ApiType, [u32, u32, UpDataStructsPropertyScope, Bytes]>;
       /**
-       * Used to enumerate token's children
+       * Used to enumerate token's children.
        **/
       tokenChildren: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array, arg3: ITuple<[u32, u32]> | [u32 | AnyNumber | Uint8Array, u32 | AnyNumber | Uint8Array]) => Observable<bool>, [u32, u32, ITuple<[u32, u32]>]> & QueryableStorageEntry<ApiType, [u32, u32, ITuple<[u32, u32]>]>;
+      /**
+       * Custom data serialized to bytes for token.
+       **/
       tokenData: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<Option<PalletNonfungibleItemData>>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
+      /**
+       * Key-Value map stored for token.
+       **/
       tokenProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<UpDataStructsProperties>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
+      /**
+       * Amount of burnt tokens for collection.
+       **/
       tokensBurnt: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
+      /**
+       * Amount of tokens minted for collection.
+       **/
       tokensMinted: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
       /**
        * Generic query
@@ -430,6 +464,9 @@
        **/
       tokenData: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<PalletRefungibleItemData>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
       tokenProperties: AugmentedQuery<ApiType, (arg1: u32 | AnyNumber | Uint8Array, arg2: u32 | AnyNumber | Uint8Array) => Observable<UpDataStructsProperties>, [u32, u32]> & QueryableStorageEntry<ApiType, [u32, u32]>;
+      /**
+       * Amount of burnt tokens for collection
+       **/
       tokensBurnt: AugmentedQuery<ApiType, (arg: u32 | AnyNumber | Uint8Array) => Observable<u32>, [u32]> & QueryableStorageEntry<ApiType, [u32]>;
       /**
        * Amount of tokens minted for collection
modifiedtests/src/interfaces/augment-api-rpc.tsdiffbeforeafterboth
--- a/tests/src/interfaces/augment-api-rpc.ts
+++ b/tests/src/interfaces/augment-api-rpc.ts
@@ -709,6 +709,10 @@
        **/
       tokenOwner: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Option<PalletEvmAccountBasicCrossAccountIdRepr>>>;
       /**
+       * Returns 10 tokens owners in no particular order
+       **/
+      tokenOwners: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, at?: Hash | string | Uint8Array) => Observable<Vec<PalletEvmAccountBasicCrossAccountIdRepr>>>;
+      /**
        * Get token properties
        **/
       tokenProperties: AugmentedRpc<(collection: u32 | AnyNumber | Uint8Array, tokenId: u32 | AnyNumber | Uint8Array, propertyKeys?: Vec<Text> | (Text | string)[], at?: Hash | string | Uint8Array) => Observable<Vec<UpDataStructsProperty>>>;
modifiedtests/src/interfaces/unique/definitions.tsdiffbeforeafterboth
--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -49,6 +49,7 @@
     balance: fun('Get amount of specific account token', [collectionParam, crossAccountParam(), tokenParam], 'u128'),
     allowance: fun('Get allowed amount', [collectionParam, crossAccountParam('sender'), crossAccountParam('spender'), tokenParam], 'u128'),
     tokenOwner: fun('Get token owner', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
+    tokenOwners: fun('Returns 10 tokens owners in no particular order', [collectionParam, tokenParam], `Vec<${CROSS_ACCOUNT_ID_TYPE}>`),
     topmostTokenOwner: fun('Get token owner, in case of nested token - find parent recursive', [collectionParam, tokenParam], `Option<${CROSS_ACCOUNT_ID_TYPE}>`),
     tokenChildren: fun('Get tokens nested directly into the token', [collectionParam, tokenParam], 'Vec<UpDataStructsTokenChild>'),
     constMetadata: fun('Get token constant metadata', [collectionParam, tokenParam], 'Vec<u8>'),
modifiedtests/src/refungible.test.tsdiffbeforeafterboth
--- a/tests/src/refungible.test.ts
+++ b/tests/src/refungible.test.ts
@@ -32,6 +32,8 @@
   repartitionRFT,
   createCollectionWithPropsExpectSuccess,
   getDetailedCollectionInfo,
+  normalizeAccountId,
+  CrossAccountId,
   getCreateItemsResult,
   getDestroyItemsResult,
 } from './util/helpers';
@@ -55,14 +57,14 @@
   it('Create refungible collection and token', async () => {
     await usingApi(async api => {
       const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}});
-      expect(createCollectionResult.success).to.be.true;    
+      expect(createCollectionResult.success).to.be.true;
       const collectionId  = createCollectionResult.collectionId;
-
+      
       const itemCountBefore = await getLastTokenId(api, collectionId);
       const result = await createRefungibleToken(api, alice, collectionId, 100n);
-
+      
       const itemCountAfter = await getLastTokenId(api, collectionId);
-
+      
       // What to expect
       // tslint:disable-next-line:no-unused-expression
       expect(result.success).to.be.true;
@@ -71,7 +73,43 @@
       expect(itemCountAfter.toString()).to.be.equal(result.itemId.toString());
     });
   });
-
+  
+  it('RPC method tokenOnewrs for refungible collection and token', async () => {
+    await usingApi(async (api, privateKeyWrapper) => {
+      const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+      const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString())));
+      
+      const createCollectionResult = await createCollection(api, alice, {mode: {type: 'ReFungible'}});
+      const collectionId = createCollectionResult.collectionId;
+      
+      const result = await createRefungibleToken(api, alice, collectionId, 10_000n);
+      const aliceTokenId = result.itemId;
+      
+      
+      await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n);
+      await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n);
+      
+      for (let i = 0; i < 7; i++) {
+        await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 50*(i+1));
+      } 
+      
+      const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId);
+      const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s));
+      
+      const aliceID = normalizeAccountId(alice);
+      const bobId = normalizeAccountId(bob);
+      
+      // What to expect
+      // tslint:disable-next-line:no-unused-expression
+      expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]);
+      expect(owners.length).to.be.equal(10);
+      
+      const eleven = privateKeyWrapper('11');
+      expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
+      expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+    });
+  });
+  
   it('Transfer token pieces', async () => {
     await usingApi(async api => {
       const collectionId = (await createCollection(api, alice, {mode: {type: 'ReFungible'}})).collectionId;
modifiedtests/src/rpc.test.tsdiffbeforeafterboth
--- a/tests/src/rpc.test.ts
+++ b/tests/src/rpc.test.ts
@@ -1,12 +1,57 @@
+import {IKeyringPair} from '@polkadot/types/types';
 import {expect} from 'chai';
 import usingApi from './substrate/substrate-api';
-import {createCollectionExpectSuccess, getTokenOwner} from './util/helpers';
+import {createCollection, createCollectionExpectSuccess, createFungibleItemExpectSuccess, CrossAccountId, getTokenOwner, normalizeAccountId, transfer, U128_MAX} from './util/helpers';
+
+let alice: IKeyringPair;
+let bob: IKeyringPair;
 
-describe('getTokenOwner', () => {
+
+describe('integration test: RPC methods', () => {
+  before(async () => {
+    await usingApi(async (api, privateKeyWrapper) => {
+      alice = privateKeyWrapper('//Alice');
+      bob = privateKeyWrapper('//Bob');
+    });
+  });
+
+  
   it('returns None for fungible collection', async () => {
     await usingApi(async api => {
       const collection = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
       await expect(getTokenOwner(api, collection, 0)).to.be.rejectedWith(/^owner == null$/);
     });
   });
+  
+  it('RPC method tokenOwners for fungible collection and token', async () => {
+    await usingApi(async (api, privateKeyWrapper) => {
+      const ethAcc = {Ethereum: '0x67fb3503a61b284dc83fa96dceec4192db47dc7c'};
+      const facelessCrowd = Array.from(Array(7).keys()).map(i => normalizeAccountId(privateKeyWrapper(i.toString())));
+      
+      const createCollectionResult = await createCollection(api, alice, {mode: {type: 'Fungible', decimalPoints: 0}});
+      const collectionId = createCollectionResult.collectionId;
+      const aliceTokenId = await createFungibleItemExpectSuccess(alice, collectionId, {Value: U128_MAX}, alice.address);
+     
+      await transfer(api, collectionId, aliceTokenId, alice, bob, 1000n);
+      await transfer(api, collectionId, aliceTokenId, alice, ethAcc, 900n);
+            
+      for (let i = 0; i < 7; i++) {
+        await transfer(api, collectionId, aliceTokenId, alice, facelessCrowd[i], 1);
+      } 
+      
+      const owners = await api.rpc.unique.tokenOwners(collectionId, aliceTokenId);
+      const ids = (owners.toJSON() as CrossAccountId[]).map(s => normalizeAccountId(s));
+      const aliceID = normalizeAccountId(alice);
+      const bobId = normalizeAccountId(bob);
+
+      // What to expect
+      // tslint:disable-next-line:no-unused-expression
+      expect(ids).to.deep.include.members([aliceID, ethAcc, bobId, ...facelessCrowd]);
+      expect(owners.length == 10).to.be.true;
+      
+      const eleven = privateKeyWrapper('11');
+      expect(await transfer(api, collectionId, aliceTokenId, alice, eleven, 10n)).to.be.true;
+      expect((await api.rpc.unique.tokenOwners(collectionId, aliceTokenId)).length).to.be.equal(10);
+    });
+  });
 });
\ No newline at end of file