difftreelog
feat(rpc) token children
in: master
9 files changed
client/rpc/src/lib.rsdiffbeforeafterboth1// 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}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}pallets/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>,
pallets/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>,
primitives/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,
primitives/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>>;
runtime/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>>>
tests/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(
tests/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'}});
tests/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,