git.delta.rocks / unique-network / refs/commits / 762c5a79597b

difftreelog

feat(rpc) token children

Fahrrader2022-06-03parent: #fb4beb0.patch.diff
in: master

9 files changed

modifiedclient/rpc/src/lib.rsdiffbeforeafterboth
before · client/rpc/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617use std::sync::Arc;1819use codec::{Decode, Encode};20use jsonrpc_core::{Error as RpcError, ErrorCode, Result};21use jsonrpc_derive::rpc;22use up_data_structs::{23	RpcCollection, CollectionId, CollectionStats, CollectionLimits, TokenId, Property,24	PropertyKeyPermission, TokenData,25};26use sp_api::{BlockId, BlockT, ProvideRuntimeApi, ApiExt};27use sp_blockchain::HeaderBackend;28use up_rpc::UniqueApi as UniqueRuntimeApi;2930// RMRK31use rmrk_rpc::RmrkApi as RmrkRuntimeApi;32use up_data_structs::{33	RmrkCollectionId, RmrkNftId, RmrkBaseId, RmrkNftChild, RmrkThemeName, RmrkResourceId,34};3536pub use rmrk_unique_rpc::RmrkApi;3738#[rpc]39pub trait UniqueApi<BlockHash, CrossAccountId, AccountId> {40	#[rpc(name = "unique_accountTokens")]41	fn account_tokens(42		&self,43		collection: CollectionId,44		account: CrossAccountId,45		at: Option<BlockHash>,46	) -> Result<Vec<TokenId>>;47	#[rpc(name = "unique_collectionTokens")]48	fn collection_tokens(49		&self,50		collection: CollectionId,51		at: Option<BlockHash>,52	) -> Result<Vec<TokenId>>;53	#[rpc(name = "unique_tokenExists")]54	fn token_exists(55		&self,56		collection: CollectionId,57		token: TokenId,58		at: Option<BlockHash>,59	) -> Result<bool>;6061	#[rpc(name = "unique_tokenOwner")]62	fn token_owner(63		&self,64		collection: CollectionId,65		token: TokenId,66		at: Option<BlockHash>,67	) -> Result<Option<CrossAccountId>>;68	#[rpc(name = "unique_topmostTokenOwner")]69	fn topmost_token_owner(70		&self,71		collection: CollectionId,72		token: TokenId,73		at: Option<BlockHash>,74	) -> Result<Option<CrossAccountId>>;7576	#[rpc(name = "unique_collectionProperties")]77	fn collection_properties(78		&self,79		collection: CollectionId,80		keys: Option<Vec<String>>,81		at: Option<BlockHash>,82	) -> Result<Vec<Property>>;8384	#[rpc(name = "unique_tokenProperties")]85	fn token_properties(86		&self,87		collection: CollectionId,88		token_id: TokenId,89		keys: Option<Vec<String>>,90		at: Option<BlockHash>,91	) -> Result<Vec<Property>>;9293	#[rpc(name = "unique_propertyPermissions")]94	fn property_permissions(95		&self,96		collection: CollectionId,97		keys: Option<Vec<String>>,98		at: Option<BlockHash>,99	) -> Result<Vec<PropertyKeyPermission>>;100101	#[rpc(name = "unique_tokenData")]102	fn token_data(103		&self,104		collection: CollectionId,105		token_id: TokenId,106		keys: Option<Vec<String>>,107		at: Option<BlockHash>,108	) -> Result<TokenData<CrossAccountId>>;109110	#[rpc(name = "unique_totalSupply")]111	fn total_supply(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<u32>;112	#[rpc(name = "unique_accountBalance")]113	fn account_balance(114		&self,115		collection: CollectionId,116		account: CrossAccountId,117		at: Option<BlockHash>,118	) -> Result<u32>;119	#[rpc(name = "unique_balance")]120	fn balance(121		&self,122		collection: CollectionId,123		account: CrossAccountId,124		token: TokenId,125		at: Option<BlockHash>,126	) -> Result<String>;127	#[rpc(name = "unique_allowance")]128	fn allowance(129		&self,130		collection: CollectionId,131		sender: CrossAccountId,132		spender: CrossAccountId,133		token: TokenId,134		at: Option<BlockHash>,135	) -> Result<String>;136137	#[rpc(name = "unique_adminlist")]138	fn adminlist(139		&self,140		collection: CollectionId,141		at: Option<BlockHash>,142	) -> Result<Vec<CrossAccountId>>;143	#[rpc(name = "unique_allowlist")]144	fn allowlist(145		&self,146		collection: CollectionId,147		at: Option<BlockHash>,148	) -> Result<Vec<CrossAccountId>>;149	#[rpc(name = "unique_allowed")]150	fn allowed(151		&self,152		collection: CollectionId,153		user: CrossAccountId,154		at: Option<BlockHash>,155	) -> Result<bool>;156	#[rpc(name = "unique_lastTokenId")]157	fn last_token_id(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<TokenId>;158	#[rpc(name = "unique_collectionById")]159	fn collection_by_id(160		&self,161		collection: CollectionId,162		at: Option<BlockHash>,163	) -> Result<Option<RpcCollection<AccountId>>>;164	#[rpc(name = "unique_collectionStats")]165	fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;166167	#[rpc(name = "unique_nextSponsored")]168	fn next_sponsored(169		&self,170		collection: CollectionId,171		account: CrossAccountId,172		token: TokenId,173		at: Option<BlockHash>,174	) -> Result<Option<u64>>;175	#[rpc(name = "unique_effectiveCollectionLimits")]176	fn effective_collection_limits(177		&self,178		collection_id: CollectionId,179		at: Option<BlockHash>,180	) -> Result<Option<CollectionLimits>>;181}182183mod rmrk_unique_rpc {184	use super::*;185186	#[rpc(server)]187	pub trait RmrkApi<188		BlockHash,189		AccountId,190		CollectionInfo,191		NftInfo,192		ResourceInfo,193		PropertyInfo,194		BaseInfo,195		PartType,196		Theme,197	>198	{199		#[rpc(name = "rmrk_lastCollectionIdx")]200		/// Get the latest created collection id201		fn last_collection_idx(&self, at: Option<BlockHash>) -> Result<RmrkCollectionId>;202203		#[rpc(name = "rmrk_collectionById")]204		/// Get collection by id205		fn collection_by_id(206			&self,207			id: RmrkCollectionId,208			at: Option<BlockHash>,209		) -> Result<Option<CollectionInfo>>;210211		#[rpc(name = "rmrk_nftById")]212		/// Get NFT by collection id and NFT id213		fn nft_by_id(214			&self,215			collection_id: RmrkCollectionId,216			nft_id: RmrkNftId,217			at: Option<BlockHash>,218		) -> Result<Option<NftInfo>>;219220		#[rpc(name = "rmrk_accountTokens")]221		/// Get tokens owned by an account in a collection222		fn account_tokens(223			&self,224			account_id: AccountId,225			collection_id: RmrkCollectionId,226			at: Option<BlockHash>,227		) -> Result<Vec<RmrkNftId>>;228229		#[rpc(name = "rmrk_nftChildren")]230		/// Get NFT children231		fn nft_children(232			&self,233			collection_id: RmrkCollectionId,234			nft_id: RmrkNftId,235			at: Option<BlockHash>,236		) -> Result<Vec<RmrkNftChild>>;237238		#[rpc(name = "rmrk_collectionProperties")]239		/// Get collection properties240		fn collection_properties(241			&self,242			collection_id: RmrkCollectionId,243			filter_keys: Option<Vec<String>>,244			at: Option<BlockHash>,245		) -> Result<Vec<PropertyInfo>>;246247		#[rpc(name = "rmrk_nftProperties")]248		/// Get NFT properties249		fn nft_properties(250			&self,251			collection_id: RmrkCollectionId,252			nft_id: RmrkNftId,253			filter_keys: Option<Vec<String>>,254			at: Option<BlockHash>,255		) -> Result<Vec<PropertyInfo>>;256257		#[rpc(name = "rmrk_nftResources")]258		/// Get NFT resources259		fn nft_resources(260			&self,261			collection_id: RmrkCollectionId,262			nft_id: RmrkNftId,263			at: Option<BlockHash>,264		) -> Result<Vec<ResourceInfo>>;265266		#[rpc(name = "rmrk_nftResourcePriorities")]267		/// Get NFT resource priorities268		fn nft_resource_priorities(269			&self,270			collection_id: RmrkCollectionId,271			nft_id: RmrkNftId,272			at: Option<BlockHash>,273		) -> Result<Vec<RmrkResourceId>>;274275		#[rpc(name = "rmrk_base")]276		/// Get base info277		fn base(&self, base_id: RmrkBaseId, at: Option<BlockHash>) -> Result<Option<BaseInfo>>;278279		#[rpc(name = "rmrk_baseParts")]280		/// Get all Base's parts281		fn base_parts(&self, base_id: RmrkBaseId, at: Option<BlockHash>) -> Result<Vec<PartType>>;282283		#[rpc(name = "rmrk_themeNames")]284		fn theme_names(285			&self,286			base_id: RmrkBaseId,287			at: Option<BlockHash>,288		) -> Result<Vec<RmrkThemeName>>;289290		#[rpc(name = "rmrk_themes")]291		fn theme(292			&self,293			base_id: RmrkBaseId,294			theme_name: String,295			filter_keys: Option<Vec<String>>,296			at: Option<BlockHash>,297		) -> Result<Option<Theme>>;298	}299}300301pub struct Unique<C, P> {302	client: Arc<C>,303	_marker: std::marker::PhantomData<P>,304}305306impl<C, P> Unique<C, P> {307	pub fn new(client: Arc<C>) -> Self {308		Self {309			client,310			_marker: Default::default(),311		}312	}313}314315pub enum Error {316	RuntimeError,317}318319impl From<Error> for i64 {320	fn from(e: Error) -> i64 {321		match e {322			Error::RuntimeError => 1,323		}324	}325}326327macro_rules! pass_method {328	(329		$method_name:ident(330			$($(#[map(|$map_arg:ident| $map:expr)])? $name:ident: $ty:ty),* $(,)?331		) -> $result:ty $(=> $mapper:expr)?,332		//$runtime_name:ident $(<$($lt: tt),+>)*333		$runtime_api_macro:ident334		$(; changed_in $ver:expr, $changed_method_name:ident ($($changed_name:expr), * $(,)?) => $fixer:expr)*335	) => {336		fn $method_name(337			&self,338			$(339				$name: $ty,340			)*341			at: Option<<Block as BlockT>::Hash>,342		) -> Result<$result> {343			let api = self.client.runtime_api();344			let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash));345			let _api_version = if let Ok(Some(api_version)) =346				api.api_version::<$runtime_api_macro!()>(&at)347			{348				api_version349			} else {350				// unreachable for our runtime351				return Err(RpcError {352					code: ErrorCode::InvalidParams,353					message: "Api is not available".into(),354					data: None,355				})356			};357358			let result = $(if _api_version < $ver {359				api.$changed_method_name(&at, $($changed_name),*).map(|r| r.map($fixer))360			} else)*361			{ api.$method_name(&at, $($((|$map_arg: $ty| $map))? ($name)),*) };362363			let result = result.map_err(|e| RpcError {364				code: ErrorCode::ServerError(Error::RuntimeError.into()),365				message: "Unable to query".into(),366				data: Some(format!("{:?}", e).into()),367			})?;368			result.map_err(|e| RpcError {369				code: ErrorCode::InvalidParams,370				message: "Runtime returned error".into(),371				data: Some(format!("{:?}", e).into()),372			})$(.map($mapper))?373		}374	};375}376377macro_rules! unique_api {378	() => {379		dyn UniqueRuntimeApi<Block, CrossAccountId, AccountId>380	};381}382383macro_rules! rmrk_api {384	() => {385		dyn RmrkRuntimeApi<Block, AccountId, CollectionInfo, NftInfo, ResourceInfo, PropertyInfo, BaseInfo, PartType, Theme>386	};387}388389#[allow(deprecated)]390impl<C, Block, CrossAccountId, AccountId>391	UniqueApi<<Block as BlockT>::Hash, CrossAccountId, AccountId> for Unique<C, Block>392where393	Block: BlockT,394	AccountId: Decode,395	C: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,396	C::Api: UniqueRuntimeApi<Block, CrossAccountId, AccountId>,397	CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,398{399	pass_method!(400		account_tokens(collection: CollectionId, account: CrossAccountId) -> Vec<TokenId>, unique_api401	);402	pass_method!(403		collection_tokens(collection: CollectionId) -> Vec<TokenId>, unique_api404	);405	pass_method!(406		token_exists(collection: CollectionId, token: TokenId) -> bool, unique_api407	);408	pass_method!(409		token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api410	);411	pass_method!(412		topmost_token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api413	);414	pass_method!(total_supply(collection: CollectionId) -> u32, unique_api);415	pass_method!(account_balance(collection: CollectionId, account: CrossAccountId) -> u32, unique_api);416	pass_method!(balance(collection: CollectionId, account: CrossAccountId, token: TokenId) -> String => |v| v.to_string(), unique_api);417	pass_method!(418		allowance(collection: CollectionId, sender: CrossAccountId, spender: CrossAccountId, token: TokenId) -> String => |v| v.to_string(),419		unique_api420	);421422	pass_method!(collection_properties(423		collection: CollectionId,424425		#[map(|keys| string_keys_to_bytes_keys(keys))]426		keys: Option<Vec<String>>427	) -> Vec<Property>, unique_api);428429	pass_method!(token_properties(430		collection: CollectionId,431		token_id: TokenId,432433		#[map(|keys| string_keys_to_bytes_keys(keys))]434		keys: Option<Vec<String>>435	) -> Vec<Property>, unique_api);436437	pass_method!(property_permissions(438		collection: CollectionId,439440		#[map(|keys| string_keys_to_bytes_keys(keys))]441		keys: Option<Vec<String>>442	) -> Vec<PropertyKeyPermission>, unique_api);443444	pass_method!(token_data(445		collection: CollectionId,446		token_id: TokenId,447448		#[map(|keys| string_keys_to_bytes_keys(keys))]449		keys: Option<Vec<String>>,450	) -> TokenData<CrossAccountId>, unique_api);451452	pass_method!(adminlist(collection: CollectionId) -> Vec<CrossAccountId>, unique_api);453	pass_method!(allowlist(collection: CollectionId) -> Vec<CrossAccountId>, unique_api);454	pass_method!(allowed(collection: CollectionId, user: CrossAccountId) -> bool, unique_api);455	pass_method!(last_token_id(collection: CollectionId) -> TokenId, unique_api);456	pass_method!(collection_by_id(collection: CollectionId) -> Option<RpcCollection<AccountId>>, unique_api);457	pass_method!(collection_stats() -> CollectionStats, unique_api);458	pass_method!(next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Option<u64>, unique_api);459	pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>, unique_api);460}461462#[allow(deprecated)]463impl<464		C,465		Block,466		AccountId,467		CollectionInfo,468		NftInfo,469		ResourceInfo,470		PropertyInfo,471		BaseInfo,472		PartType,473		Theme,474	>475	rmrk_unique_rpc::RmrkApi<476		<Block as BlockT>::Hash,477		AccountId,478		CollectionInfo,479		NftInfo,480		ResourceInfo,481		PropertyInfo,482		BaseInfo,483		PartType,484		Theme,485	> for Unique<C, Block>486where487	C: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,488	C::Api: RmrkRuntimeApi<489		Block,490		AccountId,491		CollectionInfo,492		NftInfo,493		ResourceInfo,494		PropertyInfo,495		BaseInfo,496		PartType,497		Theme,498	>,499	AccountId: Decode + Encode,500	CollectionInfo: Decode,501	NftInfo: Decode,502	ResourceInfo: Decode,503	PropertyInfo: Decode,504	BaseInfo: Decode,505	PartType: Decode,506	Theme: Decode,507	Block: BlockT,508{509	pass_method!(last_collection_idx() -> RmrkCollectionId, rmrk_api);510	pass_method!(collection_by_id(id: RmrkCollectionId) -> Option<CollectionInfo>, rmrk_api);511	pass_method!(nft_by_id(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Option<NftInfo>, rmrk_api);512	pass_method!(account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Vec<RmrkNftId>, rmrk_api);513	pass_method!(nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec<RmrkNftChild>, rmrk_api);514	pass_method!(515		collection_properties(516			collection_id: RmrkCollectionId,517518			#[map(|keys| string_keys_to_bytes_keys(keys))]519			filter_keys: Option<Vec<String>>520		) -> Vec<PropertyInfo>,521		rmrk_api522	);523	pass_method!(524		nft_properties(525			collection_id: RmrkCollectionId,526			nft_id: RmrkNftId,527528			#[map(|keys| string_keys_to_bytes_keys(keys))]529			filter_keys: Option<Vec<String>>530		) -> Vec<PropertyInfo>,531		rmrk_api532	);533	pass_method!(nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec<ResourceInfo>, rmrk_api);534	pass_method!(nft_resource_priorities(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec<RmrkResourceId>, rmrk_api);535	pass_method!(base(base_id: RmrkBaseId) -> Option<BaseInfo>, rmrk_api);536	pass_method!(base_parts(base_id: RmrkBaseId) -> Vec<PartType>, rmrk_api);537	pass_method!(theme_names(base_id: RmrkBaseId) -> Vec<RmrkThemeName>, rmrk_api);538	pass_method!(539		theme(540			base_id: RmrkBaseId,541542			#[map(|n| n.into_bytes())]543			theme_name: String,544545			#[map(|keys| string_keys_to_bytes_keys(keys))]546			filter_keys: Option<Vec<String>>547		) -> Option<Theme>, rmrk_api);548}549550fn string_keys_to_bytes_keys(keys: Option<Vec<String>>) -> Option<Vec<Vec<u8>>> {551	keys.map(|keys| keys.into_iter().map(|key| key.into_bytes()).collect())552}
after · client/rpc/src/lib.rs
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617use std::sync::Arc;1819use codec::{Decode, Encode};20use jsonrpc_core::{Error as RpcError, ErrorCode, Result};21use jsonrpc_derive::rpc;22use up_data_structs::{23	RpcCollection, CollectionId, CollectionStats, CollectionLimits, TokenId, Property,24	PropertyKeyPermission, TokenData, TokenChild,25};26use sp_api::{BlockId, BlockT, ProvideRuntimeApi, ApiExt};27use sp_blockchain::HeaderBackend;28use up_rpc::UniqueApi as UniqueRuntimeApi;2930// RMRK31use rmrk_rpc::RmrkApi as RmrkRuntimeApi;32use up_data_structs::{33	RmrkCollectionId, RmrkNftId, RmrkBaseId, RmrkNftChild, RmrkThemeName, RmrkResourceId,34};3536pub use rmrk_unique_rpc::RmrkApi;3738#[rpc]39pub trait UniqueApi<BlockHash, CrossAccountId, AccountId> {40	#[rpc(name = "unique_accountTokens")]41	fn account_tokens(42		&self,43		collection: CollectionId,44		account: CrossAccountId,45		at: Option<BlockHash>,46	) -> Result<Vec<TokenId>>;47	#[rpc(name = "unique_collectionTokens")]48	fn collection_tokens(49		&self,50		collection: CollectionId,51		at: Option<BlockHash>,52	) -> Result<Vec<TokenId>>;53	#[rpc(name = "unique_tokenExists")]54	fn token_exists(55		&self,56		collection: CollectionId,57		token: TokenId,58		at: Option<BlockHash>,59	) -> Result<bool>;6061	#[rpc(name = "unique_tokenOwner")]62	fn token_owner(63		&self,64		collection: CollectionId,65		token: TokenId,66		at: Option<BlockHash>,67	) -> Result<Option<CrossAccountId>>;68	#[rpc(name = "unique_topmostTokenOwner")]69	fn topmost_token_owner(70		&self,71		collection: CollectionId,72		token: TokenId,73		at: Option<BlockHash>,74	) -> Result<Option<CrossAccountId>>;75	#[rpc(name = "unique_tokenChildren")]76	fn token_children(77		&self,78		collection: CollectionId,79		token: TokenId,80		at: Option<BlockHash>,81	) -> Result<Vec<TokenChild>>;8283	#[rpc(name = "unique_collectionProperties")]84	fn collection_properties(85		&self,86		collection: CollectionId,87		keys: Option<Vec<String>>,88		at: Option<BlockHash>,89	) -> Result<Vec<Property>>;9091	#[rpc(name = "unique_tokenProperties")]92	fn token_properties(93		&self,94		collection: CollectionId,95		token_id: TokenId,96		keys: Option<Vec<String>>,97		at: Option<BlockHash>,98	) -> Result<Vec<Property>>;99100	#[rpc(name = "unique_propertyPermissions")]101	fn property_permissions(102		&self,103		collection: CollectionId,104		keys: Option<Vec<String>>,105		at: Option<BlockHash>,106	) -> Result<Vec<PropertyKeyPermission>>;107108	#[rpc(name = "unique_tokenData")]109	fn token_data(110		&self,111		collection: CollectionId,112		token_id: TokenId,113		keys: Option<Vec<String>>,114		at: Option<BlockHash>,115	) -> Result<TokenData<CrossAccountId>>;116117	#[rpc(name = "unique_totalSupply")]118	fn total_supply(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<u32>;119	#[rpc(name = "unique_accountBalance")]120	fn account_balance(121		&self,122		collection: CollectionId,123		account: CrossAccountId,124		at: Option<BlockHash>,125	) -> Result<u32>;126	#[rpc(name = "unique_balance")]127	fn balance(128		&self,129		collection: CollectionId,130		account: CrossAccountId,131		token: TokenId,132		at: Option<BlockHash>,133	) -> Result<String>;134	#[rpc(name = "unique_allowance")]135	fn allowance(136		&self,137		collection: CollectionId,138		sender: CrossAccountId,139		spender: CrossAccountId,140		token: TokenId,141		at: Option<BlockHash>,142	) -> Result<String>;143144	#[rpc(name = "unique_adminlist")]145	fn adminlist(146		&self,147		collection: CollectionId,148		at: Option<BlockHash>,149	) -> Result<Vec<CrossAccountId>>;150	#[rpc(name = "unique_allowlist")]151	fn allowlist(152		&self,153		collection: CollectionId,154		at: Option<BlockHash>,155	) -> Result<Vec<CrossAccountId>>;156	#[rpc(name = "unique_allowed")]157	fn allowed(158		&self,159		collection: CollectionId,160		user: CrossAccountId,161		at: Option<BlockHash>,162	) -> Result<bool>;163	#[rpc(name = "unique_lastTokenId")]164	fn last_token_id(&self, collection: CollectionId, at: Option<BlockHash>) -> Result<TokenId>;165	#[rpc(name = "unique_collectionById")]166	fn collection_by_id(167		&self,168		collection: CollectionId,169		at: Option<BlockHash>,170	) -> Result<Option<RpcCollection<AccountId>>>;171	#[rpc(name = "unique_collectionStats")]172	fn collection_stats(&self, at: Option<BlockHash>) -> Result<CollectionStats>;173174	#[rpc(name = "unique_nextSponsored")]175	fn next_sponsored(176		&self,177		collection: CollectionId,178		account: CrossAccountId,179		token: TokenId,180		at: Option<BlockHash>,181	) -> Result<Option<u64>>;182	#[rpc(name = "unique_effectiveCollectionLimits")]183	fn effective_collection_limits(184		&self,185		collection_id: CollectionId,186		at: Option<BlockHash>,187	) -> Result<Option<CollectionLimits>>;188}189190mod rmrk_unique_rpc {191	use super::*;192193	#[rpc(server)]194	pub trait RmrkApi<195		BlockHash,196		AccountId,197		CollectionInfo,198		NftInfo,199		ResourceInfo,200		PropertyInfo,201		BaseInfo,202		PartType,203		Theme,204	>205	{206		#[rpc(name = "rmrk_lastCollectionIdx")]207		/// Get the latest created collection id208		fn last_collection_idx(&self, at: Option<BlockHash>) -> Result<RmrkCollectionId>;209210		#[rpc(name = "rmrk_collectionById")]211		/// Get collection by id212		fn collection_by_id(213			&self,214			id: RmrkCollectionId,215			at: Option<BlockHash>,216		) -> Result<Option<CollectionInfo>>;217218		#[rpc(name = "rmrk_nftById")]219		/// Get NFT by collection id and NFT id220		fn nft_by_id(221			&self,222			collection_id: RmrkCollectionId,223			nft_id: RmrkNftId,224			at: Option<BlockHash>,225		) -> Result<Option<NftInfo>>;226227		#[rpc(name = "rmrk_accountTokens")]228		/// Get tokens owned by an account in a collection229		fn account_tokens(230			&self,231			account_id: AccountId,232			collection_id: RmrkCollectionId,233			at: Option<BlockHash>,234		) -> Result<Vec<RmrkNftId>>;235236		#[rpc(name = "rmrk_nftChildren")]237		/// Get NFT children238		fn nft_children(239			&self,240			collection_id: RmrkCollectionId,241			nft_id: RmrkNftId,242			at: Option<BlockHash>,243		) -> Result<Vec<RmrkNftChild>>;244245		#[rpc(name = "rmrk_collectionProperties")]246		/// Get collection properties247		fn collection_properties(248			&self,249			collection_id: RmrkCollectionId,250			filter_keys: Option<Vec<String>>,251			at: Option<BlockHash>,252		) -> Result<Vec<PropertyInfo>>;253254		#[rpc(name = "rmrk_nftProperties")]255		/// Get NFT properties256		fn nft_properties(257			&self,258			collection_id: RmrkCollectionId,259			nft_id: RmrkNftId,260			filter_keys: Option<Vec<String>>,261			at: Option<BlockHash>,262		) -> Result<Vec<PropertyInfo>>;263264		#[rpc(name = "rmrk_nftResources")]265		/// Get NFT resources266		fn nft_resources(267			&self,268			collection_id: RmrkCollectionId,269			nft_id: RmrkNftId,270			at: Option<BlockHash>,271		) -> Result<Vec<ResourceInfo>>;272273		#[rpc(name = "rmrk_nftResourcePriorities")]274		/// Get NFT resource priorities275		fn nft_resource_priorities(276			&self,277			collection_id: RmrkCollectionId,278			nft_id: RmrkNftId,279			at: Option<BlockHash>,280		) -> Result<Vec<RmrkResourceId>>;281282		#[rpc(name = "rmrk_base")]283		/// Get base info284		fn base(&self, base_id: RmrkBaseId, at: Option<BlockHash>) -> Result<Option<BaseInfo>>;285286		#[rpc(name = "rmrk_baseParts")]287		/// Get all Base's parts288		fn base_parts(&self, base_id: RmrkBaseId, at: Option<BlockHash>) -> Result<Vec<PartType>>;289290		#[rpc(name = "rmrk_themeNames")]291		fn theme_names(292			&self,293			base_id: RmrkBaseId,294			at: Option<BlockHash>,295		) -> Result<Vec<RmrkThemeName>>;296297		#[rpc(name = "rmrk_themes")]298		fn theme(299			&self,300			base_id: RmrkBaseId,301			theme_name: String,302			filter_keys: Option<Vec<String>>,303			at: Option<BlockHash>,304		) -> Result<Option<Theme>>;305	}306}307308pub struct Unique<C, P> {309	client: Arc<C>,310	_marker: std::marker::PhantomData<P>,311}312313impl<C, P> Unique<C, P> {314	pub fn new(client: Arc<C>) -> Self {315		Self {316			client,317			_marker: Default::default(),318		}319	}320}321322pub enum Error {323	RuntimeError,324}325326impl From<Error> for i64 {327	fn from(e: Error) -> i64 {328		match e {329			Error::RuntimeError => 1,330		}331	}332}333334macro_rules! pass_method {335	(336		$method_name:ident(337			$($(#[map(|$map_arg:ident| $map:expr)])? $name:ident: $ty:ty),* $(,)?338		) -> $result:ty $(=> $mapper:expr)?,339		//$runtime_name:ident $(<$($lt: tt),+>)*340		$runtime_api_macro:ident341		$(; changed_in $ver:expr, $changed_method_name:ident ($($changed_name:expr), * $(,)?) => $fixer:expr)*342	) => {343		fn $method_name(344			&self,345			$(346				$name: $ty,347			)*348			at: Option<<Block as BlockT>::Hash>,349		) -> Result<$result> {350			let api = self.client.runtime_api();351			let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash));352			let _api_version = if let Ok(Some(api_version)) =353				api.api_version::<$runtime_api_macro!()>(&at)354			{355				api_version356			} else {357				// unreachable for our runtime358				return Err(RpcError {359					code: ErrorCode::InvalidParams,360					message: "Api is not available".into(),361					data: None,362				})363			};364365			let result = $(if _api_version < $ver {366				api.$changed_method_name(&at, $($changed_name),*).map(|r| r.map($fixer))367			} else)*368			{ api.$method_name(&at, $($((|$map_arg: $ty| $map))? ($name)),*) };369370			let result = result.map_err(|e| RpcError {371				code: ErrorCode::ServerError(Error::RuntimeError.into()),372				message: "Unable to query".into(),373				data: Some(format!("{:?}", e).into()),374			})?;375			result.map_err(|e| RpcError {376				code: ErrorCode::InvalidParams,377				message: "Runtime returned error".into(),378				data: Some(format!("{:?}", e).into()),379			})$(.map($mapper))?380		}381	};382}383384macro_rules! unique_api {385	() => {386		dyn UniqueRuntimeApi<Block, CrossAccountId, AccountId>387	};388}389390macro_rules! rmrk_api {391	() => {392		dyn RmrkRuntimeApi<Block, AccountId, CollectionInfo, NftInfo, ResourceInfo, PropertyInfo, BaseInfo, PartType, Theme>393	};394}395396#[allow(deprecated)]397impl<C, Block, CrossAccountId, AccountId>398	UniqueApi<<Block as BlockT>::Hash, CrossAccountId, AccountId> for Unique<C, Block>399where400	Block: BlockT,401	AccountId: Decode,402	C: 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,403	C::Api: UniqueRuntimeApi<Block, CrossAccountId, AccountId>,404	CrossAccountId: pallet_evm::account::CrossAccountId<AccountId>,405{406	pass_method!(407		account_tokens(collection: CollectionId, account: CrossAccountId) -> Vec<TokenId>, unique_api408	);409	pass_method!(410		collection_tokens(collection: CollectionId) -> Vec<TokenId>, unique_api411	);412	pass_method!(413		token_exists(collection: CollectionId, token: TokenId) -> bool, unique_api414	);415	pass_method!(416		token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api417	);418	pass_method!(419		topmost_token_owner(collection: CollectionId, token: TokenId) -> Option<CrossAccountId>, unique_api420	);421	pass_method!(token_children(collection: CollectionId, token: TokenId) -> Vec<TokenChild>, unique_api);422	pass_method!(total_supply(collection: CollectionId) -> u32, unique_api);423	pass_method!(account_balance(collection: CollectionId, account: CrossAccountId) -> u32, unique_api);424	pass_method!(balance(collection: CollectionId, account: CrossAccountId, token: TokenId) -> String => |v| v.to_string(), unique_api);425	pass_method!(426		allowance(collection: CollectionId, sender: CrossAccountId, spender: CrossAccountId, token: TokenId) -> String => |v| v.to_string(),427		unique_api428	);429430	pass_method!(collection_properties(431		collection: CollectionId,432433		#[map(|keys| string_keys_to_bytes_keys(keys))]434		keys: Option<Vec<String>>435	) -> Vec<Property>, unique_api);436437	pass_method!(token_properties(438		collection: CollectionId,439		token_id: TokenId,440441		#[map(|keys| string_keys_to_bytes_keys(keys))]442		keys: Option<Vec<String>>443	) -> Vec<Property>, unique_api);444445	pass_method!(property_permissions(446		collection: CollectionId,447448		#[map(|keys| string_keys_to_bytes_keys(keys))]449		keys: Option<Vec<String>>450	) -> Vec<PropertyKeyPermission>, unique_api);451452	pass_method!(token_data(453		collection: CollectionId,454		token_id: TokenId,455456		#[map(|keys| string_keys_to_bytes_keys(keys))]457		keys: Option<Vec<String>>,458	) -> TokenData<CrossAccountId>, unique_api);459460	pass_method!(adminlist(collection: CollectionId) -> Vec<CrossAccountId>, unique_api);461	pass_method!(allowlist(collection: CollectionId) -> Vec<CrossAccountId>, unique_api);462	pass_method!(allowed(collection: CollectionId, user: CrossAccountId) -> bool, unique_api);463	pass_method!(last_token_id(collection: CollectionId) -> TokenId, unique_api);464	pass_method!(collection_by_id(collection: CollectionId) -> Option<RpcCollection<AccountId>>, unique_api);465	pass_method!(collection_stats() -> CollectionStats, unique_api);466	pass_method!(next_sponsored(collection: CollectionId, account: CrossAccountId, token: TokenId) -> Option<u64>, unique_api);467	pass_method!(effective_collection_limits(collection_id: CollectionId) -> Option<CollectionLimits>, unique_api);468}469470#[allow(deprecated)]471impl<472		C,473		Block,474		AccountId,475		CollectionInfo,476		NftInfo,477		ResourceInfo,478		PropertyInfo,479		BaseInfo,480		PartType,481		Theme,482	>483	rmrk_unique_rpc::RmrkApi<484		<Block as BlockT>::Hash,485		AccountId,486		CollectionInfo,487		NftInfo,488		ResourceInfo,489		PropertyInfo,490		BaseInfo,491		PartType,492		Theme,493	> for Unique<C, Block>494where495	C: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>,496	C::Api: RmrkRuntimeApi<497		Block,498		AccountId,499		CollectionInfo,500		NftInfo,501		ResourceInfo,502		PropertyInfo,503		BaseInfo,504		PartType,505		Theme,506	>,507	AccountId: Decode + Encode,508	CollectionInfo: Decode,509	NftInfo: Decode,510	ResourceInfo: Decode,511	PropertyInfo: Decode,512	BaseInfo: Decode,513	PartType: Decode,514	Theme: Decode,515	Block: BlockT,516{517	pass_method!(last_collection_idx() -> RmrkCollectionId, rmrk_api);518	pass_method!(collection_by_id(id: RmrkCollectionId) -> Option<CollectionInfo>, rmrk_api);519	pass_method!(nft_by_id(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Option<NftInfo>, rmrk_api);520	pass_method!(account_tokens(account_id: AccountId, collection_id: RmrkCollectionId) -> Vec<RmrkNftId>, rmrk_api);521	pass_method!(nft_children(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec<RmrkNftChild>, rmrk_api);522	pass_method!(523		collection_properties(524			collection_id: RmrkCollectionId,525526			#[map(|keys| string_keys_to_bytes_keys(keys))]527			filter_keys: Option<Vec<String>>528		) -> Vec<PropertyInfo>,529		rmrk_api530	);531	pass_method!(532		nft_properties(533			collection_id: RmrkCollectionId,534			nft_id: RmrkNftId,535536			#[map(|keys| string_keys_to_bytes_keys(keys))]537			filter_keys: Option<Vec<String>>538		) -> Vec<PropertyInfo>,539		rmrk_api540	);541	pass_method!(nft_resources(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec<ResourceInfo>, rmrk_api);542	pass_method!(nft_resource_priorities(collection_id: RmrkCollectionId, nft_id: RmrkNftId) -> Vec<RmrkResourceId>, rmrk_api);543	pass_method!(base(base_id: RmrkBaseId) -> Option<BaseInfo>, rmrk_api);544	pass_method!(base_parts(base_id: RmrkBaseId) -> Vec<PartType>, rmrk_api);545	pass_method!(theme_names(base_id: RmrkBaseId) -> Vec<RmrkThemeName>, rmrk_api);546	pass_method!(547		theme(548			base_id: RmrkBaseId,549550			#[map(|n| n.into_bytes())]551			theme_name: String,552553			#[map(|keys| string_keys_to_bytes_keys(keys))]554			filter_keys: Option<Vec<String>>555		) -> Option<Theme>, rmrk_api);556}557558fn string_keys_to_bytes_keys(keys: Option<Vec<String>>) -> Option<Vec<Vec<u8>>> {559	keys.map(|keys| keys.into_iter().map(|key| key.into_bytes()).collect())560}
modifiedpallets/common/src/lib.rsdiffbeforeafterboth
--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -40,6 +40,7 @@
 	MAX_TOKEN_PREFIX_LENGTH,
 	COLLECTION_ADMINS_LIMIT,
 	TokenId,
+	TokenChild,
 	CollectionStats,
 	MAX_TOKEN_OWNERSHIP,
 	CollectionMode,
@@ -502,6 +503,7 @@
 			CollectionStats,
 			CollectionId,
 			TokenId,
+			TokenChild,
 			PhantomType<(
 				TokenData<T::CrossAccountId>,
 				RpcCollection<T::AccountId>,
modifiedpallets/nonfungible/src/lib.rsdiffbeforeafterboth
--- a/pallets/nonfungible/src/lib.rs
+++ b/pallets/nonfungible/src/lib.rs
@@ -22,7 +22,7 @@
 use up_data_structs::{
 	AccessMode, CollectionId, CustomDataLimit, TokenId, CreateCollectionData, CreateNftExData,
 	mapping::TokenAddressMapping, NestingRule, budget::Budget, Property, PropertyPermission,
-	PropertyKey, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty,
+	PropertyKey, PropertyKeyPermission, Properties, PropertyScope, TrySetProperty, TokenChild,
 };
 use pallet_evm::{account::CrossAccountId, Pallet as PalletEvm};
 use pallet_common::{
@@ -988,6 +988,15 @@
 			.is_some()
 	}
 
+	pub fn token_children_ids(collection_id: CollectionId, token_id: TokenId) -> Vec<TokenChild> {
+		<TokenChildren<T>>::iter_prefix((collection_id, token_id))
+			.map(|((child_collection_id, child_id), _)| TokenChild {
+				collection: child_collection_id,
+				token: child_id,
+			})
+			.collect()
+	}
+
 	/// Delegated to `create_multiple_items`
 	pub fn create_item(
 		collection: &NonfungibleHandle<T>,
modifiedprimitives/data-structs/src/lib.rsdiffbeforeafterboth
--- a/primitives/data-structs/src/lib.rs
+++ b/primitives/data-structs/src/lib.rs
@@ -585,6 +585,14 @@
 
 #[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]
 #[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
+// todo possibly rename to be used generally as an address pair
+pub struct TokenChild {
+	pub token: TokenId,
+	pub collection: CollectionId,
+}
+
+#[derive(Encode, Decode, MaxEncodedLen, PartialEq, Clone, Debug, TypeInfo)]
+#[cfg_attr(feature = "serde1", derive(Serialize, Deserialize))]
 pub struct CollectionStats {
 	pub created: u32,
 	pub destroyed: u32,
modifiedprimitives/rpc/src/lib.rsdiffbeforeafterboth
--- a/primitives/rpc/src/lib.rs
+++ b/primitives/rpc/src/lib.rs
@@ -18,7 +18,7 @@
 
 use up_data_structs::{
 	CollectionId, TokenId, RpcCollection, CollectionStats, CollectionLimits, Property,
-	PropertyKeyPermission, TokenData,
+	PropertyKeyPermission, TokenData, TokenChild,
 };
 use sp_std::vec::Vec;
 use codec::Decode;
@@ -41,6 +41,7 @@
 
 		fn token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>>;
 		fn topmost_token_owner(collection: CollectionId, token: TokenId) -> Result<Option<CrossAccountId>>;
+		fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>>;
 
 		fn collection_properties(collection: CollectionId, properties: Option<Vec<Vec<u8>>>) -> Result<Vec<Property>>;
 
modifiedruntime/common/src/runtime_apis.rsdiffbeforeafterboth
--- a/runtime/common/src/runtime_apis.rs
+++ b/runtime/common/src/runtime_apis.rs
@@ -29,7 +29,9 @@
 
                     Ok(Some(<pallet_structure::Pallet<Runtime>>::find_topmost_owner(collection, token, &budget)?))
                 }
-
+                fn token_children(collection: CollectionId, token: TokenId) -> Result<Vec<TokenChild>, DispatchError> {
+                    Ok(<pallet_nonfungible::Pallet<Runtime>>::token_children_ids(collection, token))
+                }
                 fn collection_properties(
                     collection: CollectionId,
                     keys: Option<Vec<Vec<u8>>>
modifiedtests/src/interfaces/unique/definitions.tsdiffbeforeafterboth
--- a/tests/src/interfaces/unique/definitions.ts
+++ b/tests/src/interfaces/unique/definitions.ts
@@ -50,6 +50,7 @@
     allowance: fun('Get allowed amount', [collectionParam, crossAccountParam('sender'), crossAccountParam('spender'), tokenParam], 'u128'),
     tokenOwner: fun('Get token owner', [collectionParam, tokenParam], `Option<${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>'),
     variableMetadata: fun('Get token variable metadata', [collectionParam, tokenParam], 'Vec<u8>'),
     collectionProperties: fun(
modifiedtests/src/nesting/nest.test.tsdiffbeforeafterboth
--- a/tests/src/nesting/nest.test.ts
+++ b/tests/src/nesting/nest.test.ts
@@ -8,6 +8,7 @@
   createItemExpectSuccess,
   enableAllowListExpectSuccess,
   enablePublicMintingExpectSuccess,
+  getTokenChildren,
   getTokenOwner,
   getTopmostTokenOwner,
   normalizeAccountId,
@@ -89,6 +90,63 @@
     });
   });
 
+  it('Checks token children', async () => {
+    await usingApi(async api => {
+      const collectionA = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+      await setCollectionPermissionsExpectSuccess(alice, collectionA, {nesting: 'Owner'});
+      const collectionB = await createCollectionExpectSuccess({mode: {type: 'Fungible', decimalPoints: 0}});
+
+      const targetToken = await createItemExpectSuccess(alice, collectionA, 'NFT');
+      const targetAddress = {Ethereum: tokenIdToAddress(collectionA, targetToken)};
+      let children = await getTokenChildren(api, collectionA, targetToken);
+      expect(children.length).to.be.equal(0, 'Children length check at creation');
+
+      // Create a nested NFT token
+      const tokenA = await createItemExpectSuccess(alice, collectionA, 'NFT', targetAddress);
+      children = await getTokenChildren(api, collectionA, targetToken);
+      expect(children.length).to.be.equal(1, 'Children length check at nesting #1');
+      expect(children).to.have.deep.members([
+        {token: tokenA, collection: collectionA},
+      ], 'Children contents check at nesting #1');
+
+      // Create then nest
+      const tokenB = await createItemExpectSuccess(alice, collectionA, 'NFT');
+      await transferExpectSuccess(collectionA, tokenB, alice, targetAddress);
+      children = await getTokenChildren(api, collectionA, targetToken);
+      expect(children.length).to.be.equal(2, 'Children length check at nesting #2');
+      expect(children).to.have.deep.members([
+        {token: tokenA, collection: collectionA},
+        {token: tokenB, collection: collectionA},
+      ], 'Children contents check at nesting #2');
+
+      // Move token B to a different user outside the nesting tree
+      await transferFromExpectSuccess(collectionA, tokenB, alice, targetAddress, bob);
+      children = await getTokenChildren(api, collectionA, targetToken);
+      expect(children.length).to.be.equal(1, 'Children length check at unnesting');
+      expect(children).to.be.have.deep.members([
+        {token: tokenA, collection: collectionA},
+      ], 'Children contents check at unnesting');
+      
+      // Create a fungible token in another collection and then nest
+      const tokenC = await createItemExpectSuccess(alice, collectionB, 'Fungible');
+      await transferExpectSuccess(collectionB, tokenC, alice, targetAddress, 1, 'Fungible');
+      children = await getTokenChildren(api, collectionA, targetToken);
+      expect(children.length).to.be.equal(2, 'Children length check at nesting #3 (from another collection)');
+      expect(children).to.be.have.deep.members([
+        {token: tokenA, collection: collectionA},
+        {token: tokenC, collection: collectionB},
+      ], 'Children contents check at nesting #3 (from another collection)');
+
+      // Move the fungible token inside token A deeper in the nesting tree
+      await transferFromExpectSuccess(collectionB, tokenC, alice, targetAddress, {Ethereum: tokenIdToAddress(collectionA, tokenA)}, 1, 'Fungible');
+      children = await getTokenChildren(api, collectionA, targetToken);
+      expect(children.length).to.be.equal(1, 'Children length check at deeper nesting');
+      expect(children).to.be.have.deep.members([
+        {token: tokenA, collection: collectionA},
+      ], 'Children contents check at deeper nesting');
+    });
+  });
+
   // ---------- Non-Fungible ----------
 
   it('NFT: allows an Owner to nest/unnest their token', async () => {
@@ -232,6 +290,20 @@
     });
   });
 
+  // TODO delete if this is actually wrong
+  // TODO remake all other nesting tests if this is right
+  it('Affirms that transfer is disallowed to transfer nested tokens', async () => {
+    await usingApi(async () => {
+      const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
+      await setCollectionPermissionsExpectSuccess(alice, collection, {nesting: 'Owner'});
+
+      const tokenA = await createItemExpectSuccess(alice, collection, 'NFT');
+      const tokenB = await createItemExpectSuccess(alice, collection, 'NFT', {Ethereum: tokenIdToAddress(collection, tokenA)});
+      
+      await transferExpectFailure(collection, tokenB, alice, bob);
+    });
+  });
+
   it('Disallows excessive token nesting', async () => {
     await usingApi(async api => {
       const collection = await createCollectionExpectSuccess({mode: {type: 'NFT'}});
modifiedtests/src/util/helpers.tsdiffbeforeafterboth
--- a/tests/src/util/helpers.ts
+++ b/tests/src/util/helpers.ts
@@ -28,6 +28,7 @@
 import {default as usingApi, executeTransaction, submitTransactionAsync, submitTransactionExpectFailAsync} from '../substrate/substrate-api';
 import {hexToStr, strToUTF16, utf16ToStr} from './util';
 import {UpDataStructsRpcCollection, UpDataStructsCreateItemData, UpDataStructsProperty} from '@polkadot/types/lookup';
+import {UpDataStructsTokenChild} from '../interfaces';
 
 chai.use(chaiAsPromised);
 const expect = chai.expect;
@@ -1072,6 +1073,13 @@
   if (owner == null) throw new Error('owner == null');
   return normalizeAccountId(owner);
 }
+export async function getTokenChildren(
+  api: ApiPromise,
+  collectionId: number,
+  tokenId: number,
+): Promise<UpDataStructsTokenChild[]> {
+  return (await api.rpc.unique.tokenChildren(collectionId, tokenId)).toJSON() as any;
+}
 export async function isTokenExists(
   api: ApiPromise,
   collectionId: number,