use std::any::TypeId; use std::convert::TryFrom; use std::sync::{Arc, RwLock}; use hex::FromHex; use frame_metadata::RuntimeMetadataPrefixed; use parity_scale_codec::{Compact, Decode, Encode}; use sp_core::{ crypto::{set_default_ss58_version, Ss58AddressFormat}, hashing::blake2_256, storage::{StorageData, StorageKey}, Pair, H256, }; use sp_runtime::{ generic::{self, Era}, traits, MultiSignature, }; use sp_version::RuntimeVersion; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use dashmap::DashMap; use rust_decimal::{prelude::ToPrimitive, Decimal}; use rhai::serde::from_dynamic; use rhai::{Dynamic, Engine, EvalAltResult, Map as RMap, INT}; use crate::metadata::{EncodedCall, Metadata}; use crate::rpc::*; use crate::types::{TypeLookup, TypeRef}; use crate::users::{AccountId, User}; pub type TxHash = H256; pub type BlockHash = H256; pub type GenericAddress = sp_runtime::MultiAddress; pub type AdditionalSigned = (u32, u32, BlockHash, BlockHash, (), (), ()); #[derive(Clone, Debug, Encode, Decode)] pub struct Extra(Era, Compact, Compact); impl Extra { pub fn new(era: Era, nonce: u32) -> Self { Self(era, nonce.into(), 0u128.into()) } } pub struct SignedPayload<'a>((&'a EncodedCall, &'a Extra, AdditionalSigned)); impl<'a> SignedPayload<'a> { pub fn new(call: &'a EncodedCall, extra: &'a Extra, additional: AdditionalSigned) -> Self { Self((call, extra, additional)) } } impl<'a> Encode for SignedPayload<'a> { fn using_encoded R>(&self, f: F) -> R { self.0.using_encoded(|payload| { if payload.len() > 256 { f(&blake2_256(payload)[..]) } else { f(payload) } }) } } /// Current version of the `UncheckedExtrinsic` format. pub const EXTRINSIC_VERSION: u8 = 4; #[derive(Clone)] pub struct ExtrinsicV4 { pub signature: Option<(GenericAddress, MultiSignature, Extra)>, pub call: EncodedCall, } impl ExtrinsicV4 { pub fn signed(account: AccountId, sig: MultiSignature, extra: Extra, call: EncodedCall) -> Self { Self { signature: Some((GenericAddress::from(account), sig, extra)), call, } } pub fn unsigned(call: EncodedCall) -> Self { Self { signature: None, call, } } pub fn to_hex(&self) -> String { let mut hex = hex::encode(self.encode()); hex.insert_str(0, "0x"); hex } pub fn decode_call(call_ty: &TypeRef, xt: &mut &[u8]) -> Result> { // Decode Vec length. let _len: Compact = Decode::decode(xt).map_err(|e| e.to_string())?; // Version and signed flag. let version: u8 = Decode::decode(xt).map_err(|e| e.to_string())?; let is_signed = version & 0b1000_0000 != 0; if (version & 0b0111_1111) != EXTRINSIC_VERSION { Err("Invalid EXTRINSIC_VERSION")?; } if is_signed { let _sig: (GenericAddress, MultiSignature, Extra) = Decode::decode(xt).map_err(|e| e.to_string())?; } call_ty.decode(xt.to_vec()) } } impl Encode for ExtrinsicV4 { fn encode(&self) -> Vec { let mut buf = Vec::with_capacity(512); // 1 byte version id and signature if signed. match &self.signature { Some(sig) => { buf.push(EXTRINSIC_VERSION | 0b1000_0000); sig.encode_to(&mut buf); } None => { buf.push(EXTRINSIC_VERSION & 0b0111_1111); } } self.call.encode_to(&mut buf); buf.encode() } } #[derive(Clone, Debug, Deserialize)] pub struct AccountInfo { pub nonce: u32, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum TransactionStatus { Future, Ready, Broadcast(Vec), InBlock(BlockHash), Retracted(BlockHash), FinalityTimeout(BlockHash), Finalized(BlockHash), Usurped(TxHash), Dropped, Invalid, } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct SignedBlock { block: Block, // Ignore justifications field. } #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Block { extrinsics: Vec, header: generic::Header, #[serde(skip)] call_ty: Option, } impl Block { pub fn find_extrinsic(&self, xthex: &str) -> Option { self.extrinsics.iter().position(|xt| xt == xthex) } pub fn extrinsics_filtered(&mut self, xthex_partial: &str) -> Dynamic { Dynamic::from(self.extrinsics.iter().filter_map(|xthex| { if xthex.contains(xthex_partial) { self.call_ty .as_ref() .map_or_else( || Some(Dynamic::from(xthex.clone())), |call_ty| { if xthex.starts_with("0x") { hex::decode(&xthex[2..]).ok() .map(|xt| { ExtrinsicV4::decode_call(call_ty, &mut &xt[..]) .map_err(|e| eprintln!("Call decode failed: {:?}", e)) .ok() }) .flatten() } else { None } } ) } else { None } }).collect::>()) } pub fn parent(&mut self) -> BlockHash { self.header.parent_hash } pub fn block_number(&mut self) -> i64 { self.header.number as i64 } pub fn to_string(&mut self) -> String { format!("{:?}", self) } } #[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] pub enum Phase { ApplyExtrinsic(u32), Finalization, Initialization, } #[derive(Clone, Debug)] pub struct EventRecord { pub phase: Phase, pub name: String, pub args: Dynamic, pub topics: Vec, } impl EventRecord { pub fn name(&mut self) -> String { self.name.clone() } pub fn args(&mut self) -> Dynamic { self.args.clone() } pub fn to_string(&mut self) -> String { format!("{:#?}", self) } pub fn from_dynamic(val: Dynamic) -> Result> { let mut map = val.try_cast::().ok_or("Expected Map")?; // Decod event name and args from two nested maps, // should only have one item in each map. let event = map .remove("event") .ok_or("Missing field 'event'")? .try_cast::() .ok_or("Expected Map")?; let (name, args) = match event.into_iter().next() { Some((mod_name, map2)) => { let map2 = map2.try_cast::().ok_or("Expected Map")?; match map2.into_iter().next() { Some((name, args)) => (format!("{}.{}", mod_name, name), args), None => (format!("{}", mod_name), Dynamic::UNIT), } } None => ("()".into(), Dynamic::UNIT), }; Ok(Self { phase: from_dynamic(map.get("phase").ok_or("Missing field 'phase'")?)?, name, args, topics: from_dynamic(map.get("topics").ok_or("Missing field 'topics'")?)?, }) } } #[derive(Clone, Debug, Default)] pub struct EventRecords(Vec); impl EventRecords { pub fn filter(&mut self, phase: Phase) { self.0.retain(|ev| ev.phase == phase); } pub fn to_string(&mut self) -> String { format!("{:#?}", self.0) } pub fn from_dynamic(val: Dynamic) -> Result> { let arr = val.try_cast::>().ok_or("Expected Array")?; Ok(Self( arr .into_iter() .map(EventRecord::from_dynamic) .collect::, _>>()?, )) } } #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChainProperties { pub ss58_format: u16, pub token_decimals: u32, pub token_symbol: String, } pub struct InnerClient { rpc: RpcHandler, runtime_version: RuntimeVersion, genesis_hash: BlockHash, metadata: Metadata, event_records: TypeRef, account_info: TypeRef, call_ty: TypeRef, cached_blocks: DashMap, cached_events: DashMap, } impl InnerClient { pub fn new( rpc: RpcHandler, lookup: &TypeLookup, ) -> Result, Box> { let runtime_version = Self::rpc_get_runtime_version(&rpc)?; let genesis_hash = Self::rpc_get_genesis_hash(&rpc)?; let runtime_metadata = Self::rpc_get_runtime_metadata(&rpc)?; let metadata = Metadata::from_runtime_metadata(runtime_metadata, lookup)?; let event_records = lookup.resolve("EventRecords"); let account_info = lookup.resolve("AccountInfo"); let call_ty = lookup.resolve("Call"); Ok(Arc::new(Self { rpc, runtime_version, genesis_hash, metadata, event_records, account_info, call_ty, cached_blocks: DashMap::new(), cached_events: DashMap::new(), })) } /// Get runtime version from rpc node. fn rpc_get_runtime_version(rpc: &RpcHandler) -> Result> { Ok( rpc .call_method("state_getRuntimeVersion", Value::Null)? .ok_or_else(|| format!("Failed to get RuntimeVersion from node."))?, ) } /// Get block hash from rpc node. fn rpc_get_block_hash(rpc: &RpcHandler, block_number: u64) -> Result, Box> { Ok( rpc .call_method("chain_getBlockHash", json!([block_number]))? ) } /// Get genesis hash from rpc node. fn rpc_get_genesis_hash(rpc: &RpcHandler) -> Result> { Ok(Self::rpc_get_block_hash(rpc, 0)?.ok_or_else(|| format!("Failed to get genesis hash from node."))?) } /// Get metadata from rpc node. fn rpc_get_runtime_metadata( rpc: &RpcHandler, ) -> Result> { let hex: String = rpc .call_method("state_getMetadata", json!([]))? .ok_or_else(|| format!("Failed to get Metadata from node."))?; let bytes = Vec::from_hex(&hex[2..]).map_err(|e| e.to_string())?; Ok(RuntimeMetadataPrefixed::decode(&mut bytes.as_slice()).map_err(|e| e.to_string())?) } pub fn get_transaction_version(&self) -> i64 { self.runtime_version.transaction_version as i64 } pub fn get_metadata(&self) -> Metadata { self.metadata.clone() } pub fn get_signed_extra(&self) -> AdditionalSigned { ( self.runtime_version.spec_version, self.runtime_version.transaction_version, self.genesis_hash, self.genesis_hash, (), (), (), ) } /// Get block hash. pub fn get_block_hash(&self, block_number: u64) -> Result, Box> { Self::rpc_get_block_hash(&self.rpc, block_number) } pub fn get_block_by_number(&self, block_number: u64) -> Result, Box> { let hash = self.get_block_hash(block_number)?; self.get_block(hash) } pub fn get_block(&self, hash: Option) -> Result, Box> { // Only check for cached blocks when the hash is provided. Ok(if let Some(hash) = hash { let block = self.cached_blocks.get(&hash); if block.is_some() { block.as_deref().cloned() } else { let block = self .get_signed_block(Some(hash))? .map(|mut signed| { signed.block.call_ty = Some(self.call_ty.clone()); signed.block }); if let Some(block) = &block { // Cache new block. self.cached_blocks.insert(hash, block.clone()); } block } } else { self.get_signed_block(hash)?.map(|signed| signed.block) }) } pub fn get_chain_properties(&self) -> Result, Box> { self.rpc.call_method("system_properties", json!([])) } pub fn get_signed_block( &self, hash: Option, ) -> Result, Box> { self.rpc.call_method("chain_getBlock", json!([hash])) } pub fn get_storage_keys_paged( &self, prefix: &StorageKey, count: u32, start_key: Option<&StorageKey>, ) -> Result, Box> { self .rpc .call_method( "state_getKeysPaged", json!([prefix, count, start_key.unwrap_or(prefix)]), ) .map(|res| res.unwrap_or_default()) } pub fn get_storage_by_key( &self, key: StorageKey, at_block: Option, ) -> Result, Box> { self .rpc .call_method("state_getStorage", json!([key, at_block])) } pub fn get_storage_by_keys( &self, keys: &[StorageKey], at_block: Option, ) -> Result>, Box> { let tokens: Vec = keys .into_iter() .map(|k| { self .rpc .async_call_method("state_getStorage", json!([k, at_block])) }) .collect::, Box>>()?; self.rpc.get_responses(tokens.as_slice()) } pub fn get_storage_value( &self, module: &str, storage: &str, at_block: Option, ) -> Result, Box> { let md = self.metadata.get_storage(module, storage)?; let key = md.get_value_key()?; self.get_storage_by_key(key, at_block) } pub fn get_storage_map( &self, module: &str, storage: &str, key: Vec, at_block: Option, ) -> Result, Box> { let md = self.metadata.get_storage(module, storage)?; let key = md.raw_map_key(key)?; self.get_storage_by_key(key, at_block) } pub fn get_storage_double_map( &self, module: &str, storage: &str, key1: Vec, key2: Vec, at_block: Option, ) -> Result, Box> { let md = self.metadata.get_storage(module, storage)?; let key = md.raw_double_map_key(key1, key2)?; self.get_storage_by_key(key, at_block) } fn get_block_events(&self, hash: Option) -> Result> { match self.get_storage_value("System", "Events", hash)? { Some(value) => Ok(self.event_records.decode(value.0)?), None => Ok(Dynamic::UNIT), } } pub fn get_events(&self, hash: Option) -> Result> { if let Some(hash) = hash { let events = self.cached_events.get(&hash); if let Some(events) = events { Ok(events.clone()) } else { let events = self.get_block_events(Some(hash))?; // Cache new events. self.cached_events.insert(hash, events.clone()); Ok(events) } } else { self.get_block_events(hash) } } pub fn get_account_info( &self, account: AccountId, ) -> Result, Box> { match self.get_storage_map("System", "Account", account.encode(), None)? { Some(value) => { // Decode chain's 'AccountInfo' value. Ok(Some(self.account_info.decode(value.0)?)) } None => Ok(None), } } pub fn get_nonce(&self, account: AccountId) -> Result, Box> { match self.get_account_info(account)? { Some(value) => { // Get nonce. let account_info: AccountInfo = from_dynamic(&value)?; Ok(Some(account_info.nonce)) } None => Ok(None), } } pub fn get_request_block_hash( &self, token: RequestToken, ) -> Result, Box> { let hash = loop { let status = self.rpc.get_update(token)?; match status { Some(TransactionStatus::InBlock(hash)) | Some(TransactionStatus::Finalized(hash)) | Some(TransactionStatus::FinalityTimeout(hash)) => { break Some(hash); } Some(TransactionStatus::Future) => { log::warn!("Transaction in future (maybe nonce issue)"); } Some(TransactionStatus::Ready) => { log::debug!("Transaction ready."); } Some(TransactionStatus::Broadcast(nodes)) => { log::debug!("Transaction broadcast: {:?}", nodes); } Some(TransactionStatus::Retracted(hash)) => { log::error!("Transaction retracted: {:?}", hash); } Some(TransactionStatus::Usurped(tx_hash)) => { log::error!( "Transaction was replaced by another in the pool: {:?}", tx_hash ); break None; } Some(TransactionStatus::Dropped) => { log::error!("Transaction dropped."); break None; } Some(TransactionStatus::Invalid) => { log::error!("Transaction invalid."); break None; } None => { break None; } } }; self.rpc.close_request(token)?; Ok(hash) } pub fn submit(&self, xthex: String) -> Result<(RequestToken, String), Box> { let token = self.rpc.subscribe( "author_submitAndWatchExtrinsic", json!([xthex]), "author_unwatchExtrinsic", )?; Ok((token, xthex)) } pub fn submit_call( &self, user: &User, call: EncodedCall, ) -> Result<(RequestToken, String), Box> { let extra = Extra::new(Era::Immortal, user.nonce); let payload = SignedPayload::new(&call, &extra, self.get_signed_extra()); let sig = payload.using_encoded(|p| user.pair.sign(p)); let xt = ExtrinsicV4::signed(user.acc(), sig.into(), extra, call); let xthex = xt.to_hex(); self.submit(xthex) } pub fn submit_unsigned( &self, call: EncodedCall, ) -> Result<(RequestToken, String), Box> { let xthex = ExtrinsicV4::unsigned(call).to_hex(); self.submit(xthex) } } #[derive(Clone)] pub struct Client { inner: Arc, } impl Client { pub fn connect(rpc: RpcHandler, lookup: &TypeLookup) -> Result> { Ok(Self { inner: InnerClient::new(rpc, lookup)?, }) } pub fn get_transaction_version(&self) -> i64 { self.inner.get_transaction_version() } pub fn get_metadata(&self) -> Metadata { self.inner.get_metadata() } pub fn get_signed_extra(&self) -> AdditionalSigned { self.inner.get_signed_extra() } pub fn get_chain_properties(&self) -> Result, Box> { self.inner.get_chain_properties() } pub fn get_block_hash(&self, block_number: u64) -> Result, Box> { self.inner.get_block_hash(block_number) } pub fn get_block(&self, hash: Option) -> Result, Box> { self.inner.get_block(hash) } pub fn get_block_by_number(&self, block_number: u64) -> Result, Box> { self.inner.get_block_by_number(block_number) } pub fn get_storage_keys_paged( &self, prefix: &StorageKey, count: u32, start_key: Option<&StorageKey>, ) -> Result, Box> { self .inner .get_storage_keys_paged(prefix, count, start_key) } pub fn get_storage_by_key( &self, key: StorageKey, at_block: Option, ) -> Result, Box> { self.inner.get_storage_by_key(key, at_block) } pub fn get_storage_by_keys( &self, keys: &[StorageKey], at_block: Option, ) -> Result>, Box> { self .inner .get_storage_by_keys(keys, at_block) } pub fn get_storage_value( &self, prefix: &str, key_name: &str, at_block: Option, ) -> Result, Box> { self .inner .get_storage_value(prefix, key_name, at_block) } pub fn get_storage_map( &self, prefix: &str, key_name: &str, map_key: Vec, at_block: Option, ) -> Result, Box> { self .inner .get_storage_map(prefix, key_name, map_key, at_block) } pub fn get_storage_double_map( &self, prefix: &str, storage_name: &str, key1: Vec, key2: Vec, at_block: Option, ) -> Result, Box> { self .inner .get_storage_double_map(prefix, storage_name, key1, key2, at_block) } pub fn get_events(&self, block: Option) -> Result> { self.inner.get_events(block) } pub fn get_nonce(&self, account: AccountId) -> Result, Box> { self.inner.get_nonce(account) } pub fn get_request_block_hash( &self, token: RequestToken, ) -> Result, Box> { self.inner.get_request_block_hash(token) } fn call_results(&self, res: Result<(RequestToken, String), Box>) -> Result> { let (token, xthex) = res?; Ok(ExtrinsicCallResult::new(self, token, xthex)) } pub fn submit(&self, xthex: String) -> Result> { self.call_results(self.inner.submit(xthex)) } pub fn submit_call( &self, user: &User, call: EncodedCall, ) -> Result> { self.call_results(self.inner.submit_call(user, call)) } pub fn submit_unsigned( &self, call: EncodedCall, ) -> Result> { self.call_results(self.inner.submit_unsigned(call)) } pub fn inner(&self) -> Arc { self.inner.clone() } } pub struct InnerCallResult { client: Client, token: RequestToken, hash: Option, xthex: String, idx: Option, events: Option, } impl InnerCallResult { pub fn new(client: &Client, token: RequestToken, xthex: String) -> Self { Self { client: client.clone(), token, hash: None, xthex, idx: None, events: None, } } fn get_block_hash(&mut self) -> Result<(), Box> { if self.hash.is_some() { return Ok(()); } self.hash = self.client.get_request_block_hash(self.token)?; Ok(()) } pub fn is_in_block(&mut self) -> Result> { self.get_block_hash()?; Ok(self.hash.is_some()) } pub fn block_hash(&mut self) -> Result> { self.get_block_hash()?; Ok(self.hash.unwrap_or_default().to_string()) } fn load_events(&mut self) -> Result<(), Box> { if self.events.is_some() { return Ok(()); } self.get_block_hash()?; let events = match self.hash { Some(hash) => { // Load block and find the index of our extrinsic. let xt_idx = match self.client.get_block(Some(hash))? { Some(block) => block.find_extrinsic(&self.xthex), None => None, }; self.idx = xt_idx.map(|idx| idx as u32); let mut events = EventRecords::from_dynamic(self.client.get_events(Some(hash))?)?; if let Some(idx) = self.idx { events.filter(Phase::ApplyExtrinsic(idx)); } events } None => EventRecords::default(), }; self.events = Some(events); Ok(()) } pub fn events_filtered(&mut self, prefix: &str) -> Result, Box> { self.load_events()?; match &self.events { Some(events) => { let filtered = events .0 .iter() .filter(|ev| ev.name.starts_with(prefix)) .cloned() .map(|ev| Dynamic::from(ev)) .collect::>(); Ok(filtered) } None => Ok(vec![]), } } pub fn events(&mut self) -> Result, Box> { self.events_filtered("") } pub fn result(&mut self) -> Result> { // Look for event `System.ExtrinsicSuccess` or `System.ExtrinsicFailed` // to get the Extrinsic result. let mut events = self.events_filtered("System.Extrinsic")?; // Just return the last found event. Should only be one. match events.pop() { Some(result) => Ok(result), None => Ok(Dynamic::UNIT), } } pub fn is_success(&mut self) -> Result> { // Look for event `System.ExtrinsicSuccess`. let events = self.events_filtered("System.ExtrinsicSuccess")?; Ok(events.len() > 0) } pub fn block(&mut self) -> Result> { self.get_block_hash()?; match self.hash { Some(hash) => match self.client.get_block(Some(hash))? { Some(block) => Ok(Dynamic::from(block)), None => Ok(Dynamic::UNIT), }, None => Ok(Dynamic::UNIT), } } pub fn xthex(&self) -> String { self.xthex.clone() } pub fn to_string(&mut self) -> String { let _ = self.get_block_hash(); match &self.hash { Some(hash) => { format!("InBlock: {:?}", hash) } None => { format!("NoBlock") } } } } #[derive(Clone)] pub struct ExtrinsicCallResult(Arc>); impl ExtrinsicCallResult { pub fn new(client: &Client, token: RequestToken, xthex: String) -> Self { Self(Arc::new(RwLock::new(InnerCallResult::new(client, token, xthex)))) } pub fn is_in_block(&mut self) -> Result> { self.0.write().unwrap().is_in_block() } pub fn block_hash(&mut self) -> Result> { self.0.write().unwrap().block_hash() } pub fn events_filtered(&mut self, prefix: &str) -> Result, Box> { self.0.write().unwrap().events_filtered(prefix) } pub fn events(&mut self) -> Result, Box> { self.0.write().unwrap().events() } pub fn result(&mut self) -> Result> { self.0.write().unwrap().result() } pub fn is_success(&mut self) -> Result> { self.0.write().unwrap().is_success() } pub fn block(&mut self) -> Result> { self.0.write().unwrap().block() } pub fn xthex(&mut self) -> String { self.0.read().unwrap().xthex() } pub fn to_string(&mut self) -> String { self.0.write().unwrap().to_string() } } pub fn init_engine( rpc: &RpcHandler, engine: &mut Engine, lookup: &TypeLookup, ) -> Result> { engine .register_type_with_name::("Client") .register_result_fn("get_block_hash", |client: &mut Client, num: i64| { match client.get_block_hash(num as u64)? { Some(hash) => Ok(Dynamic::from(hash)), None => Ok(Dynamic::UNIT), } }) .register_result_fn("get_block", |client: &mut Client, hash: Dynamic| { match client.get_block(hash.try_cast::())? { Some(block) => Ok(Dynamic::from(block)), None => Ok(Dynamic::UNIT), } }) .register_result_fn("get_block_by_number", |client: &mut Client, num: i64| { match client.get_block_by_number(num as u64)? { Some(block) => Ok(Dynamic::from(block)), None => Ok(Dynamic::UNIT), } }) .register_fn("get_transaction_version", |client: &mut Client| client.get_transaction_version()) .register_result_fn("submit_unsigned", Client::submit_unsigned) .register_type_with_name::("BlockHash") .register_fn("to_string", |hash: &mut BlockHash| hash.to_string()) .register_type_with_name::("Block") .register_fn("extrinsics_filtered", Block::extrinsics_filtered) .register_get("parent", Block::parent) .register_get("block_number", Block::block_number) .register_fn("to_string", Block::to_string) .register_type_with_name::("EventRecords") .register_fn("to_string", EventRecords::to_string) .register_type_with_name::("EventRecord") .register_get("name", EventRecord::name) .register_get("args", EventRecord::args) .register_fn("to_string", EventRecord::to_string) .register_type_with_name::("ExtrinsicCallResult") .register_result_fn("events", ExtrinsicCallResult::events_filtered) .register_get_result("events", ExtrinsicCallResult::events) .register_get_result("block", ExtrinsicCallResult::block) .register_get_result("block_hash", ExtrinsicCallResult::block_hash) .register_get_result("result", ExtrinsicCallResult::result) .register_get_result("is_success", ExtrinsicCallResult::is_success) .register_get_result("is_in_block", ExtrinsicCallResult::is_in_block) .register_get("xthex", ExtrinsicCallResult::xthex) .register_fn("to_string", ExtrinsicCallResult::to_string); let client = Client::connect(rpc.clone(), lookup)?; // Get Chain properties. let chain_props = client.get_chain_properties()?; // Set default ss58 format. let ss58_format = chain_props .as_ref() .and_then(|p| Ss58AddressFormat::try_from(p.ss58_format).ok()); if let Some(ss58_format) = ss58_format { set_default_ss58_version(ss58_format); } // Get the `tokenDecimals` value from the chain properties. let token_decimals = chain_props.as_ref().map(|p| p.token_decimals).unwrap_or(0); let balance_scale = 10u128.pow(token_decimals); log::info!( "token_deciamls: {:?}, balance_scale={:?}", token_decimals, balance_scale ); lookup.custom_encode("Balance", TypeId::of::(), move |value, data| { let mut val = value.cast::() as u128; val *= balance_scale; if data.is_compact() { data.encode(Compact::(val)); } else { data.encode(val); } Ok(()) })?; lookup.custom_encode("Balance", TypeId::of::(), move |value, data| { let mut dec = value.cast::(); dec *= Decimal::from(balance_scale); let val = dec .to_u128() .ok_or_else(|| format!("Expected unsigned integer"))?; if data.is_compact() { data.encode(Compact::(val)); } else { data.encode(val); } Ok(()) })?; lookup.custom_decode("Balance", move |mut input| { let mut val = Decimal::from(u128::decode(&mut input)?); val /= Decimal::from(balance_scale); Ok(Dynamic::from_decimal(val)) })?; lookup.custom_decode("Compact", move |mut input| { let num = Compact::::decode(&mut input)?; let mut val = Decimal::from(num.0); val /= Decimal::from(balance_scale); Ok(Dynamic::from_decimal(val)) })?; Ok(client) }