From 3242af07832ab7108d5341e8cc901207c618859c Mon Sep 17 00:00:00 2001 From: david barinas Date: Wed, 17 Apr 2024 11:53:36 -0500 Subject: [PATCH 01/11] refactor: Improve code structure and organization in builder.rs and lib.rs. Fix function signatures, add missing type definitions, and implement default methods for QueryBuilder and TxBuilder. Add error handling and logging for better clarity and debugging --- sube/src/builder.rs | 218 ++++++++++++++++++++++++++++++++++++------- sube/src/lib.rs | 75 ++++++++------- sube/src/meta_ext.rs | 7 +- 3 files changed, 232 insertions(+), 68 deletions(-) diff --git a/sube/src/builder.rs b/sube/src/builder.rs index 992708c..c5765b6 100644 --- a/sube/src/builder.rs +++ b/sube/src/builder.rs @@ -1,51 +1,100 @@ use crate::http::Backend as HttpBackend; -use crate::prelude::*; -use crate::ws::{Backend as WSbackend, WS2}; + +#[cfg(feature = "ws")] +use crate::ws::{Backend as WSBackend, WS2}; + +use crate::meta::Meta; +use crate::{ prelude::* }; use crate::{ - meta::BlockInfo, Backend, Error, ExtrinicBody, Metadata, Response, Result as SubeResult, + meta::BlockInfo, Backend, Error, ExtrinsicBody, Metadata, Response, Result as SubeResult, SignerFn, StorageKey, }; use async_trait::async_trait; use core::future::{Future, IntoFuture}; +use core::pin::Pin; +use heapless::Vec as HVec; +use once_cell::sync::OnceCell; +use scale_info::build; +use serde::Serializer; use url::Url; -pub trait SignerFn: Fn(&[u8], &mut [u8; 64]) -> SubeResult<()> {} -impl SignerFn for T where T: Fn(&[u8], &mut [u8; 64]) -> SubeResult<()> {} -pub struct SubeBuilder<'a, Body, F> +type PairHostBackend<'a> = (&'a str, AnyBackend, Metadata); +static INSTANCE: OnceCell> = OnceCell::new(); + + +pub struct QueryBuilder<'a> +{ + url: Option<&'a str>, + metadata: Option, +} + +// default for non body queries +impl<'a> Default for QueryBuilder<'a> +{ + fn default() -> Self { + QueryBuilder { + url: Option::None, + metadata: Option::::None, + } + } +} + +pub struct TxBuilder<'a, Signer, Body> where Body: serde::Serialize, { url: Option<&'a str>, nonce: Option, - body: Option>, + body: Option, sender: Option<&'a [u8]>, - signer: Option, + signer: Option, metadata: Option, } -impl<'a, Body, F> Default for SubeBuilder<'a, Body, F> +// default for non body queries +impl<'a, Body, Signer> Default for TxBuilder<'a, Signer, Body> where Body: serde::Serialize, { fn default() -> Self { - SubeBuilder { + TxBuilder { url: None, nonce: None, body: None, sender: None, - signer: None, + signer: Option::::None, metadata: None, } } } -impl<'a, Body, F> SubeBuilder<'a, Body, F> -where - Body: serde::Serialize, - F: SignerFn, + +impl<'a> QueryBuilder<'a> +{ + + pub fn with_url(self, url: &'a str) -> Self { + Self { + url: Some(url), + ..self + } + } + + pub fn with_meta(self, metadata: Metadata) -> Self { + Self { + metadata: Some(metadata), + ..self + } + } +} + +impl<'a, Signer, Body> TxBuilder<'a, Signer, Body> where + Body: serde::Serialize, + Signer: SignerFn { + + pub fn with_url(self, url: &'a str) -> Self { Self { url: Some(url), @@ -53,14 +102,15 @@ where } } - pub fn with_body(self, body: ExtrinicBody) -> Self { + pub fn with_body(self, body: Body) -> Self { Self { body: Some(body), ..self } } - pub fn with_signer(self, signer: F) -> Self { + pub fn with_signer(self, signer: Signer) -> Self { + Self { signer: Some(signer), ..self @@ -89,13 +139,53 @@ where } } + static BACKEND: async_once_cell::OnceCell = async_once_cell::OnceCell::new(); static META: async_once_cell::OnceCell = async_once_cell::OnceCell::new(); -impl<'a, Body, F> IntoFuture for SubeBuilder<'a, Body, F> +impl<'a> IntoFuture for QueryBuilder<'a> { + type Output = SubeResult>; + type IntoFuture = impl Future; + + fn into_future(self) -> Self::IntoFuture { + let Self { + url, + metadata, + } = self; + + async move { + let url = chain_string_to_url(&url.ok_or(Error::BadInput)?)?; + let path = url.path(); + + let backend = BACKEND + .get_or_try_init(get_backend_by_url(url.clone())) + .await?; + + let meta = META + .get_or_try_init(async { + match metadata { + Some(m) => Ok(m), + None => backend.metadata().await.map_err(|_| Error::BadMetadata), + } + }) + .await?; + + Ok(match path { + "_meta" => Response::Meta(meta), + "_meta/registry" => Response::Registry(&meta.types), + _ => { + crate::query(&backend, meta, path).await? + } + }) + } + } +} + + +impl<'a, Signer, Body> IntoFuture for TxBuilder<'a, Signer, Body> where - Body: serde::Serialize, - F: SignerFn, + Body: serde::Serialize + core::fmt::Debug, + Signer: SignerFn { type Output = SubeResult>; type IntoFuture = impl Future; @@ -103,14 +193,18 @@ where fn into_future(self) -> Self::IntoFuture { let Self { url, - nonce: _, + nonce, body, - sender: _, + sender, signer, metadata, } = self; + async move { let url = chain_string_to_url(&url.ok_or(Error::BadInput)?)?; + let path = url.path(); + let body = body.ok_or(Error::BadInput)?; + let backend = BACKEND .get_or_try_init(get_backend_by_url(url.clone())) .await?; @@ -119,23 +213,23 @@ where .get_or_try_init(async { match metadata { Some(m) => Ok(m), - None => backend.metadata().await.map_err(|_| Error::BadMetadata), + None => backend.metadata().await.map_err(|err| Error::BadMetadata ), } }) .await?; - let signer = signer.ok_or(Error::BadInput)?; - let path = url.path(); Ok(match path { "_meta" => Response::Meta(meta), "_meta/registry" => Response::Registry(&meta.types), _ => { - if let Some(tx_data) = body { - crate::submit(backend, meta, path, tx_data, signer).await? - } else { - crate::query(&backend, meta, path).await? - } + let signer = signer.ok_or(Error::BadInput)?; + let from = sender.ok_or(Error::BadInput)?; + + crate::submit(backend, meta, path, from, ExtrinsicBody { + nonce, + body, + }, signer).await? } }) } @@ -171,8 +265,10 @@ fn chain_string_to_url(chain: &str) -> SubeResult { async fn get_backend_by_url(url: Url) -> SubeResult { match url.scheme() { + #[cfg(feature = "ws")] "ws" | "wss" => Ok(AnyBackend::Ws( - WSbackend::new_ws2(url.to_string().as_str()).await?, + #[cfg(feature = "ws")] + WSBackend::new_ws2(url.to_string().as_str()).await?, )), "http" | "https" => Ok(AnyBackend::Http(HttpBackend::new(url))), _ => Err(Error::BadInput), @@ -181,7 +277,8 @@ async fn get_backend_by_url(url: Url) -> SubeResult { enum AnyBackend { Http(HttpBackend), - Ws(WSbackend), + #[cfg(feature = "ws")] + Ws(WSBackend), } #[async_trait] @@ -189,6 +286,7 @@ impl Backend for &AnyBackend { async fn metadata(&self) -> SubeResult { match self { AnyBackend::Http(b) => b.metadata().await, + #[cfg(feature = "ws")] AnyBackend::Ws(b) => b.metadata().await, } } @@ -196,6 +294,7 @@ impl Backend for &AnyBackend { async fn submit + Send>(&self, ext: U) -> SubeResult<()> { match self { AnyBackend::Http(b) => b.submit(ext).await, + #[cfg(feature = "ws")] AnyBackend::Ws(b) => b.submit(ext).await, } } @@ -203,13 +302,68 @@ impl Backend for &AnyBackend { async fn block_info(&self, at: Option) -> SubeResult { match self { AnyBackend::Http(b) => b.block_info(at).await, + #[cfg(feature = "ws")] AnyBackend::Ws(b) => b.block_info(at).await, } } async fn query_storage(&self, key: &StorageKey) -> SubeResult> { match self { AnyBackend::Http(b) => b.query_storage(key).await, + #[cfg(feature = "ws")] AnyBackend::Ws(b) => b.query_storage(&key).await, } } } + + +#[inline] +async fn get_metadata(b: &AnyBackend, metadata: Option) -> SubeResult { + match metadata { + Some(m) => Ok(m), + None => Ok(b.metadata().await?) + } +} + +#[macro_export] +macro_rules! sube { + + ($url:expr) => { + async { + $crate::builder::QueryBuilder::default().with_url($url).await + } + }; + + // Two parameters + // Match when the macro is called with an expression (url) followed by a block of key-value pairs + ( $url:expr => { $($key:ident: $value:expr),+ $(,)? }) => { + + async { + use $crate::paste; + + let mut builder = $crate::builder::TxBuilder::default(); + + paste!($( + builder = builder.[]($value); + )*); + + builder.await + } + }; + + ($url:expr => ($wallet:expr, $body:expr)) => { + async { + let mut builder = $crate::builder::TxBuilder::default(); + + let public = $wallet.default_account().public(); + + builder + .with_url($url) + .with_signer(|message: &[u8]| Ok($wallet.sign(message).as_bytes())) + .with_sender(&public.as_ref()) + .with_body($body) + .await?; + + $crate::Result::Ok($crate::Response::Void) + } + }; +} diff --git a/sube/src/lib.rs b/sube/src/lib.rs index 5dcd9aa..36f6e22 100644 --- a/sube/src/lib.rs +++ b/sube/src/lib.rs @@ -1,6 +1,7 @@ #![feature(trait_alias)] -#![feature(type_alias_impl_trait)] -#![cfg_attr(not(feature = "std"), no_std)] +// #![feature(type_alias_impl_trait)] +#![feature(impl_trait_in_assoc_type)] +// #![cfg_attr(not(feature = "std"), no_std)] /*! Sube is a lightweight Substrate client with multi-backend support that can use a chain's type information to auto encode/decode data @@ -69,7 +70,7 @@ extern crate alloc; pub mod util; -use builder::SignerFn; + pub use codec; use codec::{Decode, Encode}; pub use frame_metadata::RuntimeMetadataPrefixed; @@ -83,6 +84,7 @@ pub use scales::{Serializer, Value}; use async_trait::async_trait; use codec::Compact; +use serde_json::json; use core::fmt; use hasher::hash; use meta::{Entry, Meta, Pallet, PalletMeta, Storage}; @@ -91,6 +93,9 @@ use prelude::*; use scale_info::PortableRegistry; use serde::{Deserialize, Serialize}; +#[cfg(feature = "builder")] +pub use paste::paste; + use crate::util::to_camel; mod prelude { @@ -99,15 +104,12 @@ mod prelude { pub use alloc::vec::Vec; } -#[derive(Serialize, Deserialize)] -pub struct ExtrinicBody { +#[derive(Serialize, Deserialize, Debug)] +pub struct ExtrinsicBody { pub nonce: Option, - pub body: Body, - pub from: Vec, + pub body: Body } -pub type Result = core::result::Result; - /// Surf based backend #[cfg(any(feature = "http", feature = "http-web", feature = "js"))] pub mod http; @@ -117,32 +119,26 @@ pub mod ws; #[cfg(any(feature = "builder"))] pub mod builder; + + pub mod hasher; pub mod meta_ext; #[cfg(any(feature = "http", feature = "http-web", feature = "ws", feature = "js"))] pub mod rpc; -#[derive(Deserialize, Decode)] -struct ChainVersion { - spec_version: u64, - transaction_version: u64, -} +pub type Result = core::result::Result; +// type Bytes = [u8; N]; +pub trait SignerFn: Fn(&[u8]) -> Result<[u8; 64]> {} +impl SignerFn for T where T: Fn(&[u8]) -> Result<[u8; 64]> {} -#[derive(Deserialize, Serialize, Decode)] -struct AccountInfo { - nonce: u64, -} -use wasm_bindgen::prelude::*; -#[wasm_bindgen] -extern "C" { - // Use `js_namespace` here to bind `console.log(..)` instead of just - // `log(..)` - #[wasm_bindgen(js_namespace = console)] - fn log(s: &str); -} +// impl From> for [u8; 64] { +// fn from(value: AsRef) -> Self { +// let a: [u8; 64] = value.as_ref()[..].try_into().expect("error, value is not 64 bytes") +// } +// } async fn query<'m>(chain: &impl Backend, meta: &'m Metadata, path: &str) -> Result> { let (pallet, item_or_call, mut keys) = parse_uri(path).ok_or(Error::BadInput)?; @@ -174,18 +170,19 @@ async fn query<'m>(chain: &impl Backend, meta: &'m Metadata, path: &str) -> Resu } } -async fn submit<'m, V>( +async fn submit<'m, V>( chain: impl Backend, meta: &'m Metadata, path: &str, - tx_data: ExtrinicBody, + from: &'m [u8], + tx_data: ExtrinsicBody, signer: impl SignerFn, ) -> Result> where - V: serde::Serialize, + V: serde::Serialize + std::fmt::Debug, { let (pallet, item_or_call, keys) = parse_uri(path).ok_or(Error::BadInput)?; - + println!("{:?}", item_or_call); let pallet = meta .pallet_by_name(&pallet) .ok_or_else(|| Error::PalletNotFound(pallet))?; @@ -196,10 +193,19 @@ where let mut encoded_call = vec![pallet.index]; - let call_data = scales::to_vec_with_info(&tx_data.body, (reg, ty).into()) + println!("before buiding the call"); + println!("Body={:?}", tx_data.body); + println!("Payload={:?}", json!({ + &item_or_call.to_lowercase(): &tx_data.body + })); + + let call_data = scales::to_vec_with_info(&json!( { + &item_or_call.to_lowercase(): &tx_data.body + }), (reg, ty).into()) .map_err(|e| Error::Encode(e.to_string()))?; encoded_call.extend(&call_data); + println!("done build the call"); let extra_params = { // ImmortalEra @@ -213,7 +219,7 @@ where let response = query( &chain, meta, - &format!("system/account/0x{}", hex::encode(&tx_data.from)), + &format!("system/account/0x{}", hex::encode(from)), ) .await?; @@ -296,9 +302,8 @@ where }; let raw = payload.as_slice(); - let mut signature: [u8; 64] = [0u8; 64]; - signer(raw, &mut signature)?; + let signature = signer(raw)?; let extrinsic_call = { let encoded_inner = [ @@ -306,7 +311,7 @@ where vec![0b10000000 + 4u8], // signer vec![0x00], - tx_data.from.to_vec(), + from.to_vec(), // signature [vec![0x01], signature.to_vec()].concat(), // extra diff --git a/sube/src/meta_ext.rs b/sube/src/meta_ext.rs index f114561..0ed7b81 100644 --- a/sube/src/meta_ext.rs +++ b/sube/src/meta_ext.rs @@ -4,7 +4,12 @@ use core::{borrow::Borrow, slice}; use codec::Decode; #[cfg(any(feature = "v13"))] use frame_metadata::decode_different::DecodeDifferent; -use frame_metadata::{PalletCallMetadata, RuntimeMetadata, RuntimeMetadataPrefixed}; + +use frame_metadata::{ RuntimeMetadata, RuntimeMetadataPrefixed}; +#[cfg(any(feature = "v14"))] +use frame_metadata::v14::PalletCallMetadata; + + #[cfg(feature = "v13")] pub use v13::*; From c41d2bc496caf4d52cd0a5231f1c6c41f62c50d2 Mon Sep 17 00:00:00 2001 From: david barinas Date: Wed, 17 Apr 2024 11:54:41 -0500 Subject: [PATCH 02/11] feat: Update dependencies in sube/Cargo.toml and add new features for improved functionality --- sube/Cargo.toml | 28 +++++++++++++++++----------- sube/cli/Cargo.toml | 1 - 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/sube/Cargo.toml b/sube/Cargo.toml index 9e2a57b..0e79da6 100644 --- a/sube/Cargo.toml +++ b/sube/Cargo.toml @@ -12,7 +12,7 @@ log = "0.4.17" async-trait = "0.1.53" blake2 = { version = "0.10.5", default-features = false } codec = { version = "3.1.2", package = "parity-scale-codec", default-features = false } -frame-metadata = { version = "15.0.0", git = "https://github.com/paritytech/frame-metadata.git", default-features = false, features = ["serde_full", "decode"] } +frame-metadata = { version = "16.0.0", default-features = false, features = ["serde_full", "decode"] } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } twox-hash = { version = "1.6.2", default-features = false } serde = { version = "1.0.137", default-features = false } @@ -36,29 +36,35 @@ async-tls = { version = "0.11.0", default-features = false, optional = true } async-std = { version = "1.11.0", default_features = false, optional = true } serde_json = { version = "1.0.80", default-features = false, optional = true } url = { version = "2.2.2", optional = true } - -wasm-bindgen = "0.2.63" - +paste = { version = "1.0", optional = true } +wasm-bindgen = { version = "=0.2.84", optional = true } +once_cell = { version = "1.17.1", optional = true } +heapless = { version = "0.7.16", optional = true } +anyhow = { version = "1.0.40", optional = true } +rand_core = {version = "0.6.3", optional = true } [dev-dependencies] async-std = { version = "1.11.0", features = ["attributes"] } hex-literal = "0.3.4" +libwallet = { path = "../libwallet" } [features] # default = ["json", "std"] -default = ["async-std", "builder", "v14", "url", "wss", "ws", "http", "json"] +default = ["async-std", "builder", "url", "wss", "ws", "http", "json", "v14", "serde_json"] #default = ["v14", "ws", "decode", "json", "std"] # decode = ["v14", "serde", "scales/codec"] # encode = ["v14", ] http = ["jsonrpc", "surf/h1-client-rustls"] -http-web = ["jsonrpc", "surf/wasm-client"] -json = ["v14", "scales/json"] +http-web = ["jsonrpc", "surf/wasm-client", "wasm-bindgen", "url", "serde_json"] +json = [ "scales/json"] std = [] -builder = [] -v12 = ["frame-metadata/v12"] -v13 = ["frame-metadata/v13"] -v14 = ["frame-metadata/v14", "scale-info"] + +builder = ["paste", "once_cell", "heapless"] +# v12 = ["frame-metadata/v12"] +# v13 = ["frame-metadata/v13"] +v14 = ["scale-info", "frame-metadata/current"] ws = ["async-std", "async-tungstenite", "async-mutex", "futures-util", "futures-channel", "jsonrpc"] wss = ["ws", "async-tls", "async-tungstenite/async-tls"] +examples = ["anyhow", "rand_core"] [package.metadata.docs.rs] diff --git a/sube/cli/Cargo.toml b/sube/cli/Cargo.toml index d805136..4ba396a 100644 --- a/sube/cli/Cargo.toml +++ b/sube/cli/Cargo.toml @@ -19,7 +19,6 @@ hex = { version = "0.4.3", default-features = false, features = ["alloc"] } path = ".." features = [ "std", - "v14", "http", # "wss", ] From 317a5e36638fe6bf5ab2f0b5dafda888c75f4dce Mon Sep 17 00:00:00 2001 From: david barinas Date: Wed, 17 Apr 2024 11:55:24 -0500 Subject: [PATCH 03/11] refactor: Update Cargo.toml dependencies and code in lib.rs for better functionality and clarity. Fix typos and organize code for improved readability --- sube/sube-js/Cargo.toml | 15 +++++++-------- sube/sube-js/src/lib.rs | 34 +++++++++++++--------------------- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/sube/sube-js/Cargo.toml b/sube/sube-js/Cargo.toml index 9f46322..3659356 100644 --- a/sube/sube-js/Cargo.toml +++ b/sube/sube-js/Cargo.toml @@ -7,12 +7,8 @@ edition = "2018" [lib] crate-type = ["cdylib", "rlib"] -[features] -default = ["alloc"] -alloc = ["wee_alloc"] - [dependencies] -wasm-bindgen = "0.2.63" +wasm-bindgen = "=0.2.84" wee_alloc = { version = "0.4.5", optional = true } wasm-bindgen-futures = "0.4.33" serde = "1.0.152" @@ -31,11 +27,14 @@ wasm-bindgen-test = "0.3.13" [dependencies.sube] path = ".." +default-features=false features = [ "http-web", + "json", "v14", - "json" + "builder" ] -# [profile.release] -# opt-level = "s" +[features] +default = ["alloc"] +alloc = ["wee_alloc"] diff --git a/sube/sube-js/src/lib.rs b/sube/sube-js/src/lib.rs index b6169ea..223f9cf 100644 --- a/sube/sube-js/src/lib.rs +++ b/sube/sube-js/src/lib.rs @@ -11,9 +11,10 @@ use sube::{ meta_ext::Pallet, rpc, sube, util::to_camel, - Backend, Error as SubeError, ExtrinicBody, JsonValue, Response, + Backend, Error as SubeError, ExtrinsicBody, JsonValue, Response, }; +use core::convert::TryInto; // use sp_core::{crypto::Ss58Codec, hexdisplay::AsBytesRef}; use util::*; use wasm_bindgen::prelude::*; @@ -63,18 +64,10 @@ pub async fn sube_js( params: JsValue, signer: Option, ) -> Result { - let url = chain_string_to_url(url)?; - - let backend = sube::http::Backend::new(url.clone()); console_error_panic_hook::set_once(); - let meta = backend - .metadata() - .await - .map_err(|e| JsError::new("Error fetching metadata"))?; - if params.is_undefined() { - let response = sube::<()>(backend, &meta, url.path(), None, move |_, _| Ok(())) + let response = sube!(url) .await .map_err(|e| JsError::new(&format!("Error querying: {:?}", &e.to_string())))?; @@ -90,17 +83,14 @@ pub async fn sube_js( return Ok(value); } - let mut extrinsic_value: ExtrinicBody = serde_wasm_bindgen::from_value(params)?; + let mut extrinsic_value: ExtrinsicBody = serde_wasm_bindgen::from_value(params)?; extrinsic_value.body = decode_addresses(&extrinsic_value.body); - let value = sube::( - backend, - &meta, - url.path(), - Some(extrinsic_value), - |message, out: &mut [u8; 64]| unsafe { + let value = sube!(url => { + signer: move |message: &[u8]| unsafe { let response: JsValue = signer + .clone() .ok_or(SubeError::BadInput)? .call1( &JsValue::null(), @@ -108,14 +98,16 @@ pub async fn sube_js( ) .map_err(|_| SubeError::Signing)?; - let mut vec: Vec = serde_wasm_bindgen::from_value(response) + let vec: Vec = serde_wasm_bindgen::from_value(response) .map_err(|_| SubeError::Encode("Unknown value to decode".into()))?; - out.copy_from_slice(&vec); + let buffer: [u8; 64] = vec.try_into().expect("slice with incorrect length"); - Ok(()) + Ok(buffer) }, - ) + sender: extrinsic_value.from, + body: extrinsic_value.body, + }) .await .map_err(|e| JsError::new(&format!("Error trying: {:?}", e.to_string())))?; From f1cc057261878458c7dc1195ff7ce6abb094f509 Mon Sep 17 00:00:00 2001 From: david barinas Date: Wed, 17 Apr 2024 11:55:57 -0500 Subject: [PATCH 04/11] feat: Add new examples for querying balance, sending transactions, and building transactions using Sube API --- sube/examples/query_balance.rs | 14 ++--- sube/examples/query_balance_builder.rs | 13 +++++ sube/examples/send_tx.rs | 47 +++++++++++++++++ sube/examples/send_tx_builder.rs | 44 ++++++++++++++++ sube/examples/send_tx_signer.rs | 47 +++++++++++++++++ sube/examples/test.rs | 72 ++++++++++++++++++++++++++ 6 files changed, 231 insertions(+), 6 deletions(-) create mode 100644 sube/examples/query_balance_builder.rs create mode 100644 sube/examples/send_tx.rs create mode 100644 sube/examples/send_tx_builder.rs create mode 100644 sube/examples/send_tx_signer.rs create mode 100644 sube/examples/test.rs diff --git a/sube/examples/query_balance.rs b/sube/examples/query_balance.rs index ee72917..2dcb332 100644 --- a/sube/examples/query_balance.rs +++ b/sube/examples/query_balance.rs @@ -1,10 +1,12 @@ - - - -use sube::{ builder::SubeBuilder as Sube }; use async_trait::async_trait; +use core::future::{Future, IntoFuture}; +use sube::{ Response, sube, Result }; #[async_std::main] -async fn main () { - let a = Sube("wss://rpc.polkadot.io").await?; +async fn main() -> Result<()> { + + let result = sube!("https://kusama.olanod.com/system/_constants/version").await?; + + println!("{:?}", result); + Ok(()) } diff --git a/sube/examples/query_balance_builder.rs b/sube/examples/query_balance_builder.rs new file mode 100644 index 0000000..a0a2cb3 --- /dev/null +++ b/sube/examples/query_balance_builder.rs @@ -0,0 +1,13 @@ +use async_trait::async_trait; + +use sube::{ Response, sube, Result, builder::QueryBuilder, SignerFn, ExtrinsicBody }; + +#[async_std::main] +async fn main() -> Result<()> { + let builder = QueryBuilder::default() + .with_url("https://kusama.olanod.com/system/_constants/version") + .await?; + + println!("{:?}", builder); + Ok(()) +} diff --git a/sube/examples/send_tx.rs b/sube/examples/send_tx.rs new file mode 100644 index 0000000..81ee329 --- /dev/null +++ b/sube/examples/send_tx.rs @@ -0,0 +1,47 @@ +use futures_util::TryFutureExt; +use serde_json::json; +use libwallet::{self, vault}; +use sube::sube; +use std::env; +use rand_core::OsRng; + +type Wallet = libwallet::Wallet; + +use anyhow::{ Result, anyhow }; + +#[async_std::main] +async fn main() -> Result<(), Box> { + let phrase = env::args().skip(1).collect::>().join(" "); + + let (vault, phrase) = if phrase.is_empty() { + vault::Simple::generate_with_phrase(&mut rand_core::OsRng) + } else { + let phrase: libwallet::Mnemonic = phrase.parse().expect("Invalid phrase"); + (vault::Simple::from_phrase(&phrase), phrase) + }; + + let mut wallet = Wallet::new(vault); + wallet.unlock(None).await?; + + let account = wallet.default_account(); + let public = account.public(); + + let response = sube!("https://kusama.olanod.com/balances/transfer" => { + signer: |message: &[u8]| Ok(wallet.sign(message).into()), + sender: public.as_ref(), + body: json!({ + "dest": { + "Id": public.as_ref() + }, + "value": 100000 + }), + }) + .map_err(|err| anyhow!(format!("SubeError {:?}", err))) + .await?; + + + println!("Secret phrase: \"{phrase}\""); + println!("Default Account: 0x{account}"); + + Ok(()) +} \ No newline at end of file diff --git a/sube/examples/send_tx_builder.rs b/sube/examples/send_tx_builder.rs new file mode 100644 index 0000000..75fd5cd --- /dev/null +++ b/sube/examples/send_tx_builder.rs @@ -0,0 +1,44 @@ +use jsonrpc::error; +use serde_json::json; +use libwallet::{self, vault, Signature}; +use sube::builder::TxBuilder; +use std::env; +use rand_core::OsRng; + +type Wallet = libwallet::Wallet; +use anyhow::{ Result, anyhow }; + +#[async_std::main] +async fn main() -> Result<()> { + let phrase = env::args().skip(1).collect::>().join(" "); + + let (vault, phrase) = if phrase.is_empty() { + vault::Simple::generate_with_phrase(&mut rand_core::OsRng) + } else { + let phrase: libwallet::Mnemonic = phrase.parse().expect("Invalid phrase"); + (vault::Simple::from_phrase(&phrase), phrase) + }; + + let mut wallet = Wallet::new(vault); + + wallet.unlock(None).await?; + + let account = wallet.default_account(); + + let response = TxBuilder::default() + .with_url("https://kusama.olanod.com/balances/transfer") + .with_signer(|message: &[u8]| Ok(wallet.sign(message).as_bytes()) ) + .with_sender(wallet.default_account().public().as_ref()) + .with_body(json!({ + "dest": { + "Id": wallet.default_account().public().as_ref() + }, + "value": 100000 + })) + .await + .map_err(|err| { + anyhow!(format!("Error {:?}", err)) + })?; + + Ok(()) +} \ No newline at end of file diff --git a/sube/examples/send_tx_signer.rs b/sube/examples/send_tx_signer.rs new file mode 100644 index 0000000..24069cf --- /dev/null +++ b/sube/examples/send_tx_signer.rs @@ -0,0 +1,47 @@ +use futures_util::TryFutureExt; +use serde_json::json; +use libwallet::{self, vault, Signature}; +use sube::{ sube }; +use std::env; +use rand_core::OsRng; + +type Wallet = libwallet::Wallet; + +use anyhow::{ Result, anyhow }; + +#[async_std::main] +async fn main() -> Result<(), Box> { + let phrase = env::args().skip(1).collect::>().join(" "); + + let (vault, phrase) = if phrase.is_empty() { + vault::Simple::generate_with_phrase(&mut rand_core::OsRng) + } else { + let phrase: libwallet::Mnemonic = phrase.parse().expect("Invalid phrase"); + (vault::Simple::from_phrase(&phrase), phrase) + }; + + let mut wallet = Wallet::new(vault); + wallet.unlock(None).await?; + + let account = wallet.default_account(); + let public = account.public(); + + + let response = sube!( + "https://kusama.olanod.com/balances/transfer" => + (wallet, json!({ + "dest": { + "Id": public.as_ref(), + }, + "value": 100000 + })) + ) + .await + .map_err(|err| anyhow!(format!("SubeError {:?}", err)))?; + + + println!("Secret phrase: \"{phrase}\""); + // println!("Default Account: 0x{account}"); + + Ok(()) +} \ No newline at end of file diff --git a/sube/examples/test.rs b/sube/examples/test.rs new file mode 100644 index 0000000..7449c48 --- /dev/null +++ b/sube/examples/test.rs @@ -0,0 +1,72 @@ +#![feature(prelude_import)] +#[prelude_import] +use std::prelude::rust_2021::*; +#[macro_use] +extern crate std; +use futures_util::TryFutureExt; +use serde_json::json; +use libwallet::{self, vault}; +use sube::sube; +use std::env; +use rand_core::OsRng; +type Wallet = libwallet::Wallet; +use anyhow::{Result, anyhow}; +fn main() -> Result<(), Box> { + async fn main() -> Result<(), Box> { + { + let phrase = env::args().skip(1).collect::>().join(" "); + let (vault, phrase) = if phrase.is_empty() { + vault::Simple::generate_with_phrase(&mut rand_core::OsRng) + } else { + let phrase: libwallet::Mnemonic = phrase + .parse() + .expect("Invalid phrase"); + (vault::Simple::from_phrase(&phrase), phrase) + }; + let mut wallet = Wallet::new(vault); + wallet.unlock(None).await?; + let account = wallet.default_account().public(); + let public = account.as_ref(); + + let response = async { + let mut builder = ::sube::builder::TxBuilder::default(); + // let account = &wallet.default_account(); + // let public = account.public(); + + builder + .with_signer(|message: &[u8]| Ok(wallet.sign(message).into())) + .with_sender(public.into()) + .with_body( + ::serde_json::Value::Object({ + let mut object = ::serde_json::Map::new(); + let _ = object + .insert( + ("dest").into(), + ::serde_json::Value::Object({ + let mut object = ::serde_json::Map::new(); + let _ = object + .insert( + ("Id").into(), + ::serde_json::to_value(&public.as_ref()).unwrap(), + ); + object + }), + ); + let _ = object + .insert( + ("value").into(), + ::serde_json::to_value(&100000).unwrap(), + ); + object + }), + ) + .await + } + .map_err(|err| anyhow!(format!("SubeError {:?}", err))) + .await?; + + Ok(()) + } + } + async_std::task::block_on(async { main().await }) +} \ No newline at end of file From 08bf1c3a751371a182b206dde36ba606caf4bffd Mon Sep 17 00:00:00 2001 From: david barinas Date: Wed, 17 Apr 2024 17:39:32 -0500 Subject: [PATCH 05/11] refactor: Update dependencies in Cargo.toml and package.json, refactor code for better performance, and add logging in sube-js/src/lib.rs --- sube/sube-js/Cargo.toml | 16 ++++++------- sube/sube-js/demo/package-lock.json | 25 ++++++++++++++++---- sube/sube-js/demo/package.json | 2 +- sube/sube-js/demo/src/sube.ts | 1 + sube/sube-js/package-lock.json | 36 +++++++++++++++++++++++++++++ sube/sube-js/src/lib.rs | 20 +++++++++++----- 6 files changed, 81 insertions(+), 19 deletions(-) create mode 100644 sube/sube-js/package-lock.json diff --git a/sube/sube-js/Cargo.toml b/sube/sube-js/Cargo.toml index 3659356..a5b24fe 100644 --- a/sube/sube-js/Cargo.toml +++ b/sube/sube-js/Cargo.toml @@ -8,31 +8,31 @@ edition = "2018" crate-type = ["cdylib", "rlib"] [dependencies] -wasm-bindgen = "=0.2.84" +wasm-bindgen = "0.2.91" wee_alloc = { version = "0.4.5", optional = true } -wasm-bindgen-futures = "0.4.33" +wasm-bindgen-futures = "0.4.41" serde = "1.0.152" serde_json = "1.0.91" -serde-wasm-bindgen = "0.4.5" -js-sys = "0.3.60" +serde-wasm-bindgen = "0.6.3" +js-sys = "0.3.68" hex = { version = "0.4.3", default-features = false, features = ["alloc"] } parity-scale-codec = "3.2.1" console_error_panic_hook = "0.1.7" # sp-core = "10.0.0" +console_log = "1.0.0" +log = "0.4.21" [dev-dependencies] console_error_panic_hook = { version = "0.1.6" } wasm-bindgen-test = "0.3.13" + [dependencies.sube] path = ".." default-features=false features = [ - "http-web", - "json", - "v14", - "builder" + "js" ] [features] diff --git a/sube/sube-js/demo/package-lock.json b/sube/sube-js/demo/package-lock.json index eb69e1e..9111aaf 100644 --- a/sube/sube-js/demo/package-lock.json +++ b/sube/sube-js/demo/package-lock.json @@ -8,7 +8,7 @@ "name": "demo", "version": "0.0.0", "dependencies": { - "@virtonetwork/libwallet": "file:/Users/davidbarinas/db/virto/libwallet/libwallet-js/pkg", + "@virtonetwork/libwallet": "file:../../../libwallet/js", "sube-js": "file:../", "ts-node": "^10.9.1", "vite-plugin-top-level-await": "^1.2.2", @@ -26,9 +26,23 @@ "typescript": "^4.9.5" } }, + "../..": {}, + "../../../../libwallet/js": { + "extraneous": true + }, + "../../../../libwallet/libwallet-js/pkg": { + "extraneous": true + }, + "../../../libwallet/js": { + "version": "1.0.0-alpha.1", + "devDependencies": { + "zx": "^7.2.1" + } + }, "../../../libwallet/libwallet-js/pkg": { "name": "libwallet-js", - "version": "0.1.0" + "version": "0.1.0", + "extraneous": true }, "../pkg-web": { "name": "sube-js", @@ -616,7 +630,7 @@ "peer": true }, "node_modules/@virtonetwork/libwallet": { - "resolved": "../../../libwallet/libwallet-js/pkg", + "resolved": "../../../libwallet/js", "link": true }, "node_modules/acorn": { @@ -1250,7 +1264,10 @@ "peer": true }, "@virtonetwork/libwallet": { - "version": "file:../../../libwallet/libwallet-js/pkg" + "version": "file:../../../libwallet/js", + "requires": { + "zx": "^7.2.1" + } }, "acorn": { "version": "8.8.2", diff --git a/sube/sube-js/demo/package.json b/sube/sube-js/demo/package.json index c4306d9..7682574 100644 --- a/sube/sube-js/demo/package.json +++ b/sube/sube-js/demo/package.json @@ -13,7 +13,7 @@ "vite": "^4.0.0" }, "dependencies": { - "@virtonetwork/libwallet": "file:/Users/davidbarinas/db/virto/libwallet/libwallet-js/pkg", + "@virtonetwork/libwallet": "file:../../../libwallet/js", "sube-js": "file:../", "ts-node": "^10.9.1", "vite-plugin-top-level-await": "^1.2.2", diff --git a/sube/sube-js/demo/src/sube.ts b/sube/sube-js/demo/src/sube.ts index 3679a8e..33160a2 100644 --- a/sube/sube-js/demo/src/sube.ts +++ b/sube/sube-js/demo/src/sube.ts @@ -1,6 +1,7 @@ import { sube } from 'sube-js'; import { default as Initwallet, JsWallet } from '@virtonetwork/libwallet'; +window.sube = sube; export function setupSign(element: HTMLButtonElement) { let counter = 0 const setCounter = async (count: number) => { diff --git a/sube/sube-js/package-lock.json b/sube/sube-js/package-lock.json new file mode 100644 index 0000000..2eaf19e --- /dev/null +++ b/sube/sube-js/package-lock.json @@ -0,0 +1,36 @@ +{ + "name": "@virtonetwork/sube", + "version": "1.0.0-alpha.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@virtonetwork/sube", + "version": "1.0.0-alpha.1", + "devDependencies": { + "typescript": "^4.9.5" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + } + } +} diff --git a/sube/sube-js/src/lib.rs b/sube/sube-js/src/lib.rs index 223f9cf..36ad969 100644 --- a/sube/sube-js/src/lib.rs +++ b/sube/sube-js/src/lib.rs @@ -1,5 +1,8 @@ mod util; +use core::convert::TryInto; +use js_sys::Uint8Array; +use log::Level; use parity_scale_codec::Encode; use serde::{Deserialize, Serialize}; use serde_json::{self, Error}; @@ -13,8 +16,6 @@ use sube::{ util::to_camel, Backend, Error as SubeError, ExtrinsicBody, JsonValue, Response, }; - -use core::convert::TryInto; // use sp_core::{crypto::Ss58Codec, hexdisplay::AsBytesRef}; use util::*; use wasm_bindgen::prelude::*; @@ -58,12 +59,19 @@ fn chain_string_to_url(chain: &str) -> Result { Ok(url) } +#[derive(Serialize, Deserialize)] +struct ExtrinsicBodyWithFrom { + from: Vec, + call: ExtrinsicBody, +} + #[wasm_bindgen] pub async fn sube_js( url: &str, params: JsValue, signer: Option, ) -> Result { + console_log::init_with_level(Level::max()); console_error_panic_hook::set_once(); if params.is_undefined() { @@ -83,9 +91,9 @@ pub async fn sube_js( return Ok(value); } - let mut extrinsic_value: ExtrinsicBody = serde_wasm_bindgen::from_value(params)?; + let mut extrinsic_value: ExtrinsicBodyWithFrom = serde_wasm_bindgen::from_value(params)?; - extrinsic_value.body = decode_addresses(&extrinsic_value.body); + extrinsic_value.call.body = decode_addresses(&extrinsic_value.call.body); let value = sube!(url => { signer: move |message: &[u8]| unsafe { @@ -105,8 +113,8 @@ pub async fn sube_js( Ok(buffer) }, - sender: extrinsic_value.from, - body: extrinsic_value.body, + sender: extrinsic_value.from.as_slice(), + body: extrinsic_value.call, }) .await .map_err(|e| JsError::new(&format!("Error trying: {:?}", e.to_string())))?; From 14b2173ecd381eabfd4752a963ff05afe646d8a2 Mon Sep 17 00:00:00 2001 From: david barinas Date: Wed, 17 Apr 2024 17:39:48 -0500 Subject: [PATCH 06/11] refactor: Refactor SubeBuilder struct and related methods for better structuring and readability --- sube/src/builder.rs | 137 +++++++++++++++-------------------------- sube/src/lib.rs | 38 ++++-------- sube/src/meta_ext.rs | 4 +- sube/src/ws.rs | 142 +++++++++++++++++++++++++++---------------- 4 files changed, 150 insertions(+), 171 deletions(-) diff --git a/sube/src/builder.rs b/sube/src/builder.rs index c5765b6..02891a5 100644 --- a/sube/src/builder.rs +++ b/sube/src/builder.rs @@ -1,13 +1,13 @@ use crate::http::Backend as HttpBackend; #[cfg(feature = "ws")] -use crate::ws::{Backend as WSBackend, WS2}; +use crate::ws::Backend as WSBackend; use crate::meta::Meta; -use crate::{ prelude::* }; +use crate::prelude::*; use crate::{ - meta::BlockInfo, Backend, Error, ExtrinsicBody, Metadata, Response, Result as SubeResult, SignerFn, - StorageKey, + meta::BlockInfo, Backend, Error, ExtrinsicBody, Metadata, Response, Result as SubeResult, + SignerFn, StorageKey, }; use async_trait::async_trait; @@ -19,32 +19,10 @@ use scale_info::build; use serde::Serializer; use url::Url; - type PairHostBackend<'a> = (&'a str, AnyBackend, Metadata); static INSTANCE: OnceCell> = OnceCell::new(); - -pub struct QueryBuilder<'a> -{ - url: Option<&'a str>, - metadata: Option, -} - -// default for non body queries -impl<'a> Default for QueryBuilder<'a> -{ - fn default() -> Self { - QueryBuilder { - url: Option::None, - metadata: Option::::None, - } - } -} - -pub struct TxBuilder<'a, Signer, Body> -where - Body: serde::Serialize, -{ +pub struct SubeBuilder<'a, Body, Signer> { url: Option<&'a str>, nonce: Option, body: Option, @@ -53,27 +31,20 @@ where metadata: Option, } -// default for non body queries -impl<'a, Body, Signer> Default for TxBuilder<'a, Signer, Body> -where - Body: serde::Serialize, -{ +impl<'a, B> Default for SubeBuilder<'a, B, ()> { fn default() -> Self { - TxBuilder { + SubeBuilder { url: None, nonce: None, body: None, sender: None, - signer: Option::::None, + signer: None, metadata: None, } } } - -impl<'a> QueryBuilder<'a> -{ - +impl<'a, B> SubeBuilder<'a, B, ()> { pub fn with_url(self, url: &'a str) -> Self { Self { url: Some(url), @@ -89,34 +60,31 @@ impl<'a> QueryBuilder<'a> } } -impl<'a, Signer, Body> TxBuilder<'a, Signer, Body> where - Body: serde::Serialize, - Signer: SignerFn -{ - - - pub fn with_url(self, url: &'a str) -> Self { - Self { - url: Some(url), - ..self +impl<'a, B> SubeBuilder<'a, B, ()> { + pub fn with_signer(self, signer: Signer) -> SubeBuilder<'a, B, Signer> { + SubeBuilder { + signer: Some(signer), + body: self.body, + metadata: self.metadata, + nonce: self.nonce, + sender: self.sender, + url: self.url, } } +} - pub fn with_body(self, body: Body) -> Self { +impl<'a, B, S> SubeBuilder<'a, B, S> +where + B: serde::Serialize, + S: SignerFn, +{ + pub fn with_body(self, body: B) -> Self { Self { body: Some(body), ..self } } - pub fn with_signer(self, signer: Signer) -> Self { - - Self { - signer: Some(signer), - ..self - } - } - pub fn with_nonce(self, nonce: u64) -> Self { Self { nonce: Some(nonce), @@ -124,13 +92,6 @@ impl<'a, Signer, Body> TxBuilder<'a, Signer, Body> where } } - pub fn with_meta(self, metadata: Metadata) -> Self { - Self { - metadata: Some(metadata), - ..self - } - } - pub fn with_sender(self, sender: &'a [u8]) -> Self { Self { sender: Some(sender), @@ -139,24 +100,21 @@ impl<'a, Signer, Body> TxBuilder<'a, Signer, Body> where } } - static BACKEND: async_once_cell::OnceCell = async_once_cell::OnceCell::new(); static META: async_once_cell::OnceCell = async_once_cell::OnceCell::new(); -impl<'a> IntoFuture for QueryBuilder<'a> { +impl<'a> IntoFuture for SubeBuilder<'a, (), ()> { type Output = SubeResult>; type IntoFuture = impl Future; fn into_future(self) -> Self::IntoFuture { - let Self { - url, - metadata, - } = self; + let Self { url, metadata, .. } = self; async move { let url = chain_string_to_url(&url.ok_or(Error::BadInput)?)?; let path = url.path(); + log::info!("building the backend for {}", url); let backend = BACKEND .get_or_try_init(get_backend_by_url(url.clone())) .await?; @@ -173,19 +131,16 @@ impl<'a> IntoFuture for QueryBuilder<'a> { Ok(match path { "_meta" => Response::Meta(meta), "_meta/registry" => Response::Registry(&meta.types), - _ => { - crate::query(&backend, meta, path).await? - } + _ => crate::query(&backend, meta, path).await?, }) } } } - -impl<'a, Signer, Body> IntoFuture for TxBuilder<'a, Signer, Body> +impl<'a, Body, Signer> IntoFuture for SubeBuilder<'a, Body, Signer> where Body: serde::Serialize + core::fmt::Debug, - Signer: SignerFn + Signer: SignerFn, { type Output = SubeResult>; type IntoFuture = impl Future; @@ -213,12 +168,11 @@ where .get_or_try_init(async { match metadata { Some(m) => Ok(m), - None => backend.metadata().await.map_err(|err| Error::BadMetadata ), + None => backend.metadata().await.map_err(|err| Error::BadMetadata), } }) .await?; - Ok(match path { "_meta" => Response::Meta(meta), "_meta/registry" => Response::Registry(&meta.types), @@ -226,10 +180,15 @@ where let signer = signer.ok_or(Error::BadInput)?; let from = sender.ok_or(Error::BadInput)?; - crate::submit(backend, meta, path, from, ExtrinsicBody { - nonce, - body, - }, signer).await? + crate::submit( + backend, + meta, + path, + from, + ExtrinsicBody { nonce, body }, + signer, + ) + .await? } }) } @@ -278,7 +237,7 @@ async fn get_backend_by_url(url: Url) -> SubeResult { enum AnyBackend { Http(HttpBackend), #[cfg(feature = "ws")] - Ws(WSBackend), + Ws(WSBackend), } #[async_trait] @@ -315,12 +274,11 @@ impl Backend for &AnyBackend { } } - #[inline] async fn get_metadata(b: &AnyBackend, metadata: Option) -> SubeResult { match metadata { Some(m) => Ok(m), - None => Ok(b.metadata().await?) + None => Ok(b.metadata().await?), } } @@ -329,7 +287,7 @@ macro_rules! sube { ($url:expr) => { async { - $crate::builder::QueryBuilder::default().with_url($url).await + $crate::builder::SubeBuilder::default().with_url($url).await } }; @@ -340,10 +298,11 @@ macro_rules! sube { async { use $crate::paste; - let mut builder = $crate::builder::TxBuilder::default(); + let mut builder = $crate::builder::SubeBuilder::default() + .with_url($url); paste!($( - builder = builder.[]($value); + let mut builder = builder.[]($value); )*); builder.await @@ -352,7 +311,7 @@ macro_rules! sube { ($url:expr => ($wallet:expr, $body:expr)) => { async { - let mut builder = $crate::builder::TxBuilder::default(); + let mut builder = $crate::builder::SubeBuilder::default(); let public = $wallet.default_account().public(); diff --git a/sube/src/lib.rs b/sube/src/lib.rs index 36f6e22..9b1482f 100644 --- a/sube/src/lib.rs +++ b/sube/src/lib.rs @@ -70,9 +70,8 @@ extern crate alloc; pub mod util; - pub use codec; -use codec::{Decode, Encode}; +use codec::Encode; pub use frame_metadata::RuntimeMetadataPrefixed; pub use meta::Metadata; @@ -84,7 +83,6 @@ pub use scales::{Serializer, Value}; use async_trait::async_trait; use codec::Compact; -use serde_json::json; use core::fmt; use hasher::hash; use meta::{Entry, Meta, Pallet, PalletMeta, Storage}; @@ -92,6 +90,7 @@ use prelude::*; #[cfg(feature = "v14")] use scale_info::PortableRegistry; use serde::{Deserialize, Serialize}; +use serde_json::json; #[cfg(feature = "builder")] pub use paste::paste; @@ -107,7 +106,7 @@ mod prelude { #[derive(Serialize, Deserialize, Debug)] pub struct ExtrinsicBody { pub nonce: Option, - pub body: Body + pub body: Body, } /// Surf based backend @@ -120,7 +119,6 @@ pub mod ws; #[cfg(any(feature = "builder"))] pub mod builder; - pub mod hasher; pub mod meta_ext; @@ -132,8 +130,6 @@ pub type Result = core::result::Result; pub trait SignerFn: Fn(&[u8]) -> Result<[u8; 64]> {} impl SignerFn for T where T: Fn(&[u8]) -> Result<[u8; 64]> {} - - // impl From> for [u8; 64] { // fn from(value: AsRef) -> Self { // let a: [u8; 64] = value.as_ref()[..].try_into().expect("error, value is not 64 bytes") @@ -170,7 +166,7 @@ async fn query<'m>(chain: &impl Backend, meta: &'m Metadata, path: &str) -> Resu } } -async fn submit<'m, V>( +async fn submit<'m, V>( chain: impl Backend, meta: &'m Metadata, path: &str, @@ -193,19 +189,15 @@ where let mut encoded_call = vec![pallet.index]; - println!("before buiding the call"); - println!("Body={:?}", tx_data.body); - println!("Payload={:?}", json!({ - &item_or_call.to_lowercase(): &tx_data.body - })); - - let call_data = scales::to_vec_with_info(&json!( { - &item_or_call.to_lowercase(): &tx_data.body - }), (reg, ty).into()) - .map_err(|e| Error::Encode(e.to_string()))?; + let call_data = scales::to_vec_with_info( + &json!( { + &item_or_call.to_lowercase(): &tx_data.body + }), + (reg, ty).into(), + ) + .map_err(|e| Error::Encode(e.to_string()))?; encoded_call.extend(&call_data); - println!("done build the call"); let extra_params = { // ImmortalEra @@ -456,6 +448,7 @@ pub enum Error { Mapping(String), AccountNotFound, ConstantNotFound(String), + Platform(String), } impl fmt::Display for Error { @@ -467,13 +460,6 @@ impl fmt::Display for Error { } } -#[cfg(feature = "ws")] -impl From for Error { - fn from(_err: async_tungstenite::tungstenite::Error) -> Self { - Error::ChainUnavailable - } -} - #[cfg(feature = "std")] impl std::error::Error for Error {} diff --git a/sube/src/meta_ext.rs b/sube/src/meta_ext.rs index 0ed7b81..d6cb98f 100644 --- a/sube/src/meta_ext.rs +++ b/sube/src/meta_ext.rs @@ -5,11 +5,9 @@ use codec::Decode; #[cfg(any(feature = "v13"))] use frame_metadata::decode_different::DecodeDifferent; -use frame_metadata::{ RuntimeMetadata, RuntimeMetadataPrefixed}; #[cfg(any(feature = "v14"))] use frame_metadata::v14::PalletCallMetadata; - - +use frame_metadata::{RuntimeMetadata, RuntimeMetadataPrefixed}; #[cfg(feature = "v13")] pub use v13::*; diff --git a/sube/src/ws.rs b/sube/src/ws.rs index f087413..29f342d 100644 --- a/sube/src/ws.rs +++ b/sube/src/ws.rs @@ -2,40 +2,57 @@ use crate::prelude::*; use alloc::{collections::BTreeMap, sync::Arc}; use async_mutex::Mutex; -use async_std::task; +use async_std::future::IntoFuture; +use async_std::stream::IntoStream; +use async_std::{channel::Receiver, task}; use async_trait::async_trait; -use async_tungstenite::tungstenite::{Error as WsError, Message}; + +use futures_channel::mpsc; +use futures_channel::mpsc::TryRecvError; use futures_channel::oneshot; +use futures_util::stream::Next; + +use ewebsock::{Error as WsError, WsMessage as Message, WsReceiver as Rx, WsSender as Tx}; + +#[cfg(not(feature = "js"))] +use async_std::task::spawn; + +#[cfg(feature = "js")] +use async_std::task::spawn_local as spawn; + +use futures_util::stream; use futures_util::{ sink::{Sink, SinkExt}, stream::SplitSink, Stream, StreamExt, }; + use jsonrpc::{ error::{result_to_response, standard_error, StandardError}, serde_json, }; +use log::info; use crate::{ rpc::{self, Rpc, RpcResult}, Error, }; -type Id = u8; +type Id = u32; -pub struct Backend { - tx: Mutex, +pub struct Backend { + tx: Mutex>, + ws_sender: Arc>, messages: Arc>>>, } +unsafe impl Send for Backend {} +unsafe impl Sync for Backend {} #[async_trait] -impl Rpc for Backend -where - Tx: Sink + Unpin + Send, -{ +impl Rpc for Backend { async fn rpc(&self, method: &str, params: &[&str]) -> RpcResult { let id = self.next_id().await; - log::info!("RPC `{}` (ID={})", method, id); + info!("RPC `{}` (ID={})", method, id); // Store a sender that will notify our receiver when a matching message arrives let (sender, recv) = oneshot::channel::(); @@ -50,84 +67,93 @@ where params: &Self::convert_params(params), }) .expect("Request is serializable"); + log::debug!("RPC Request {} ...", &msg[..50]); - let _ = self.tx.lock().await.send(Message::Text(msg)).await; + self.tx + .lock() + .await + .try_send(Message::Text(msg)) + .map_err(|x| standard_error(StandardError::InternalError, None))?; + + log::info!("sent CMD"); // wait for the matching response to arrive let res = recv .await .map_err(|_| standard_error(StandardError::InternalError, None))? .result::()?; + log::debug!("RPC Response: {}...", &res[..res.len().min(20)]); + let res = hex::decode(&res[2..]) .map_err(|_| standard_error(StandardError::InternalError, None))?; + Ok(res) } } -impl Backend { +impl Backend { async fn next_id(&self) -> Id { self.messages.lock().await.keys().last().unwrap_or(&0) + 1 } -} - -#[cfg(not(feature = "wss"))] -pub type WS2 = futures_util::sink::SinkErrInto< - SplitSink, Message>, - Message, - Error, ->; -#[cfg(feature = "wss")] -pub type WS2 = futures_util::sink::SinkErrInto< - SplitSink< - async_tungstenite::WebSocketStream< - async_tungstenite::stream::Stream< - async_std::net::TcpStream, - async_tls::client::TlsStream, - >, - >, - Message, - >, - Message, - Error, ->; -impl Backend { - pub async fn new_ws2<'a, U: Into<&'a str>>(url: U) -> core::result::Result { + pub async fn new_ws2<'a, U: Into<&'a str>>(url: U) -> core::result::Result { let url = url.into(); log::trace!("WS connecting to {}", url); - let (stream, _) = async_tungstenite::async_std::connect_async(url).await?; - let (tx, rx) = stream.split(); + + let (tx, rx) = + ewebsock::connect(url, ewebsock::Options::default()).map_err(|e| Error::Platform(e))?; + + let (mut sender, recv) = mpsc::channel::(0); let backend = Backend { - tx: Mutex::new(tx.sink_err_into()), + tx: Mutex::new(sender), + ws_sender: Arc::new(Mutex::new(tx)), messages: Arc::new(Mutex::new(BTreeMap::new())), }; - backend.process_incoming_messages(rx); + let recv = Arc::new(Mutex::new(recv)); + backend.process_incoming_messages(rx, backend.ws_sender.clone(), recv.clone()); Ok(backend) } - fn process_incoming_messages(&self, mut rx: Rx) - where - Rx: Stream> + Unpin + Send + 'static, - { + fn process_tx_send_messages( + mut tx: Arc>, + mut recv: Arc>>, + ) { + spawn(async move { + info!("waiting for coommands..."); + + while let Some(m) = recv.lock().await.next().await { + info!("got for coommands...?"); + tx.lock().await.send(m); + } + }); + } + + fn process_incoming_messages( + &self, + mut rx: Rx, + mut tx: Arc>, + mut recv: Arc>>, + ) { let messages = self.messages.clone(); + spawn(async move { + while let Some(event) = rx.next().await { + match event { + ewebsock::WsEvent::Message(msg) => { + log::trace!("Got WS message {:?}", msg); - task::spawn(async move { - while let Some(msg) = rx.next().await { - match msg { - Ok(msg) => { - log::trace!("Got WS message {}", msg); - if let Ok(msg) = msg.to_text() { + if let Message::Text(msg) = msg { let res: rpc::Response = - serde_json::from_str(msg).unwrap_or_else(|_| { + serde_json::from_str(&msg).unwrap_or_else(|_| { result_to_response( Err(standard_error(StandardError::ParseError, None)), ().into(), ) }); + if res.id.is_u64() { let id = res.id.as_u64().unwrap() as Id; log::trace!("Answering request {}", id); @@ -139,12 +165,22 @@ impl Backend { } } } - Err(err) => { - log::warn!("WS Error: {}", err); + ewebsock::WsEvent::Error(e) => { + log::warn!("WS error {}", &e); + } + ewebsock::WsEvent::Closed => { + log::info!("WS connection closed"); + } + ewebsock::WsEvent::Opened => { + log::info!("Processing tx msg"); + Backend::process_tx_send_messages(tx.clone(), recv.clone()); + log::trace!("Ws connection opened"); } } } + log::warn!("WS connection closed"); + }); } } From 34f149fb24fcf410a8b820ca968c9d3998864833 Mon Sep 17 00:00:00 2001 From: david barinas Date: Wed, 17 Apr 2024 17:41:31 -0500 Subject: [PATCH 07/11] feat(key_pair): Add method to convert signature to bytes --- libwallet/src/key_pair.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/libwallet/src/key_pair.rs b/libwallet/src/key_pair.rs index aeaf0c2..9e67cce 100644 --- a/libwallet/src/key_pair.rs +++ b/libwallet/src/key_pair.rs @@ -1,6 +1,6 @@ -use core::fmt::Debug; - pub use derive::Derive; +use core::{convert::TryInto, fmt::Debug}; + type Bytes = [u8; N]; @@ -18,7 +18,11 @@ pub trait Pair: Signer + Derive { pub trait Public: AsRef<[u8]> + Debug {} impl Public for Bytes {} -pub trait Signature: AsRef<[u8]> + Debug + PartialEq {} +pub trait Signature: AsRef<[u8]> + Debug + PartialEq { + fn as_bytes(&self) -> Bytes { + self.as_ref().try_into().expect("error") + } +} impl Signature for Bytes {} /// Something that can sign messages From bc30892458b77011681387443fab8dc94757ea55 Mon Sep 17 00:00:00 2001 From: david barinas Date: Wed, 17 Apr 2024 17:41:41 -0500 Subject: [PATCH 08/11] chore: Update repository URL and add new dependencies for enhanced functionality --- sube/Cargo.toml | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/sube/Cargo.toml b/sube/Cargo.toml index 0e79da6..9847164 100644 --- a/sube/Cargo.toml +++ b/sube/Cargo.toml @@ -5,7 +5,7 @@ license = "Apache-2.0" version = "1.0.0" authors = ["Daniel Olano "] edition = "2021" -repository = "https://github.com/valibre-org/sube" +repository = "https://github.com/valibre-org/virto-dk/sube" [dependencies] log = "0.4.17" @@ -20,7 +20,7 @@ async-once-cell = "0.4.4" scale-info = { version = "2.1.1", default-features = false, optional = true } jsonrpc = { version = "0.12.1", default-features = false, optional = true } -scales = { path = "../scales", package = "scale-serialization", default-features = false, features = ["codec", "experimental-serializer"] } +scales = { path="../scales", package = "scale-serialization", default-features = false, features = ["codec", "experimental-serializer"] } # http backend surf = { version = "2.3.2", default_features = false, optional = true } @@ -37,15 +37,18 @@ async-std = { version = "1.11.0", default_features = false, optional = true } serde_json = { version = "1.0.80", default-features = false, optional = true } url = { version = "2.2.2", optional = true } paste = { version = "1.0", optional = true } -wasm-bindgen = { version = "=0.2.84", optional = true } +wasm-bindgen = { version = "0.2.91", optional = true } once_cell = { version = "1.17.1", optional = true } heapless = { version = "0.7.16", optional = true } anyhow = { version = "1.0.40", optional = true } rand_core = {version = "0.6.3", optional = true } +ewebsock = { git = "https://github.com/S0c5/ewebsock.git", optional = true, branch = "enhacement/aviod-blocking-operations-with-mpsc-futures" } +env_logger = "0.11.3" + [dev-dependencies] async-std = { version = "1.11.0", features = ["attributes"] } hex-literal = "0.3.4" -libwallet = { path = "../libwallet" } +libwallet = { path = "../libwallet", default_features=false, features=["substrate", "mnemonic", "sr25519", "util_pin", "rand", ] } [features] # default = ["json", "std"] @@ -62,10 +65,10 @@ builder = ["paste", "once_cell", "heapless"] # v12 = ["frame-metadata/v12"] # v13 = ["frame-metadata/v13"] v14 = ["scale-info", "frame-metadata/current"] -ws = ["async-std", "async-tungstenite", "async-mutex", "futures-util", "futures-channel", "jsonrpc"] -wss = ["ws", "async-tls", "async-tungstenite/async-tls"] +ws = ["async-std", "async-mutex", "futures-channel", "jsonrpc", "async-std/unstable", "ewebsock", 'futures-util'] +wss = ["ws", "async-tls", "ewebsock/tls", "async-std/unstable"] examples = ["anyhow", "rand_core"] - +js = ["http-web", "json", "v14", "builder", 'async-std/unstable', "ws"] [package.metadata.docs.rs] features = ["http"] @@ -75,3 +78,6 @@ members = [ "sube-js", "cli" ] + +[patch.crates-io] +cookie = { git = "https://github.com/S0c5/cookie-rs.git" } From fe435faadae676633d8ce652bad86493a01fd2cf Mon Sep 17 00:00:00 2001 From: david barinas Date: Wed, 17 Apr 2024 17:41:57 -0500 Subject: [PATCH 09/11] feat: Add new functionality for querying balance over WebSocket and sending transactions with updated URL and builder implementation --- sube/examples/query_balance.rs | 5 +- sube/examples/query_balance_builder.rs | 9 ++- sube/examples/query_balance_ws.rs | 33 +++++++++ sube/examples/send_tx.rs | 21 +++--- sube/examples/send_tx_builder.rs | 22 +++--- ...send_tx_signer.rs => send_tx_libwallet.rs} | 18 +++-- sube/examples/test.rs | 72 ------------------- 7 files changed, 69 insertions(+), 111 deletions(-) create mode 100644 sube/examples/query_balance_ws.rs rename sube/examples/{send_tx_signer.rs => send_tx_libwallet.rs} (82%) delete mode 100644 sube/examples/test.rs diff --git a/sube/examples/query_balance.rs b/sube/examples/query_balance.rs index 2dcb332..fb9965e 100644 --- a/sube/examples/query_balance.rs +++ b/sube/examples/query_balance.rs @@ -1,11 +1,10 @@ use async_trait::async_trait; use core::future::{Future, IntoFuture}; -use sube::{ Response, sube, Result }; +use sube::{sube, Response, Result}; #[async_std::main] async fn main() -> Result<()> { - - let result = sube!("https://kusama.olanod.com/system/_constants/version").await?; + let result = sube!("wss://rococo-rpc.polkadot.io/system/account/0x3c85f79f28628bee75cdb9eddfeae249f813fad95f84120d068fbc990c4b717d").await?; println!("{:?}", result); Ok(()) diff --git a/sube/examples/query_balance_builder.rs b/sube/examples/query_balance_builder.rs index a0a2cb3..f08fb49 100644 --- a/sube/examples/query_balance_builder.rs +++ b/sube/examples/query_balance_builder.rs @@ -1,11 +1,14 @@ use async_trait::async_trait; -use sube::{ Response, sube, Result, builder::QueryBuilder, SignerFn, ExtrinsicBody }; +use env_logger; +use sube::{builder::SubeBuilder, sube, ExtrinsicBody, Response, Result, SignerFn}; #[async_std::main] async fn main() -> Result<()> { - let builder = QueryBuilder::default() - .with_url("https://kusama.olanod.com/system/_constants/version") + env_logger::init(); + + let builder = SubeBuilder::default() + .with_url("wss://rococo-rpc.polkadot.io/system/account/0x3c85f79f28628bee75cdb9eddfeae249f813fad95f84120d068fbc990c4b717d") .await?; println!("{:?}", builder); diff --git a/sube/examples/query_balance_ws.rs b/sube/examples/query_balance_ws.rs new file mode 100644 index 0000000..9063921 --- /dev/null +++ b/sube/examples/query_balance_ws.rs @@ -0,0 +1,33 @@ +use env_logger; +use serde::Deserialize; +use sube::{builder::SubeBuilder, sube, ExtrinsicBody, Response, Result, SignerFn, Error}; + +#[derive(Debug, Deserialize)] +pub struct AccountInfo { + pub nonce: u64, + pub consumers: u64, + pub providers: u64, + pub sufficients: u64, + pub data: AccountData, +} + +#[derive(Debug, Deserialize)] +pub struct AccountData { + pub free: u128, + pub reserved: u128, + pub frozen: u128, + pub flags: u128, +} + +#[async_std::main] +async fn main() -> Result<()> { + let response = SubeBuilder::default() + .with_url("wss://rococo-rpc.polkadot.io/system/account/0x3c85f79f28628bee75cdb9eddfeae249f813fad95f84120d068fbc990c4b717d") + .await?; + + if let Response::Value(v) = response { + println!("{}", v); + } + + Ok(()) +} diff --git a/sube/examples/send_tx.rs b/sube/examples/send_tx.rs index 81ee329..0aa60fc 100644 --- a/sube/examples/send_tx.rs +++ b/sube/examples/send_tx.rs @@ -1,13 +1,13 @@ use futures_util::TryFutureExt; +use libwallet::{self, vault, Signature}; +use rand_core::OsRng; use serde_json::json; -use libwallet::{self, vault}; -use sube::sube; use std::env; -use rand_core::OsRng; +use sube::sube; type Wallet = libwallet::Wallet; -use anyhow::{ Result, anyhow }; +use anyhow::{anyhow, Result}; #[async_std::main] async fn main() -> Result<(), Box> { @@ -21,13 +21,13 @@ async fn main() -> Result<(), Box> { }; let mut wallet = Wallet::new(vault); - wallet.unlock(None).await?; + wallet.unlock(None).await.map_err(|_| anyhow!("error wallet"))?; let account = wallet.default_account(); let public = account.public(); - let response = sube!("https://kusama.olanod.com/balances/transfer" => { - signer: |message: &[u8]| Ok(wallet.sign(message).into()), + let response = sube!("wss://rococo-rpc.polkadot.io/balances/transfer" => { + signer: |message: &[u8]| Ok(wallet.sign(message).as_bytes()), sender: public.as_ref(), body: json!({ "dest": { @@ -36,12 +36,11 @@ async fn main() -> Result<(), Box> { "value": 100000 }), }) - .map_err(|err| anyhow!(format!("SubeError {:?}", err))) - .await?; - + .await + .map_err(|err| anyhow!(format!("SubeError {:?}", err)))?; println!("Secret phrase: \"{phrase}\""); println!("Default Account: 0x{account}"); Ok(()) -} \ No newline at end of file +} diff --git a/sube/examples/send_tx_builder.rs b/sube/examples/send_tx_builder.rs index 75fd5cd..599ceff 100644 --- a/sube/examples/send_tx_builder.rs +++ b/sube/examples/send_tx_builder.rs @@ -1,12 +1,12 @@ use jsonrpc::error; -use serde_json::json; use libwallet::{self, vault, Signature}; -use sube::builder::TxBuilder; -use std::env; use rand_core::OsRng; +use serde_json::json; +use std::env; +use sube::builder::SubeBuilder; type Wallet = libwallet::Wallet; -use anyhow::{ Result, anyhow }; +use anyhow::{anyhow, Result}; #[async_std::main] async fn main() -> Result<()> { @@ -21,13 +21,13 @@ async fn main() -> Result<()> { let mut wallet = Wallet::new(vault); - wallet.unlock(None).await?; + wallet.unlock(None).await.map_err(|e| anyhow!("error"))?; let account = wallet.default_account(); - let response = TxBuilder::default() - .with_url("https://kusama.olanod.com/balances/transfer") - .with_signer(|message: &[u8]| Ok(wallet.sign(message).as_bytes()) ) + let response = SubeBuilder::default() + .with_url("wss://rococo-rpc.polkadot.io/balances/transfer") + .with_signer(|message: &[u8]| Ok(wallet.sign(message).as_bytes())) .with_sender(wallet.default_account().public().as_ref()) .with_body(json!({ "dest": { @@ -36,9 +36,7 @@ async fn main() -> Result<()> { "value": 100000 })) .await - .map_err(|err| { - anyhow!(format!("Error {:?}", err)) - })?; + .map_err(|err| anyhow!(format!("Error {:?}", err)))?; Ok(()) -} \ No newline at end of file +} diff --git a/sube/examples/send_tx_signer.rs b/sube/examples/send_tx_libwallet.rs similarity index 82% rename from sube/examples/send_tx_signer.rs rename to sube/examples/send_tx_libwallet.rs index 24069cf..4b5908d 100644 --- a/sube/examples/send_tx_signer.rs +++ b/sube/examples/send_tx_libwallet.rs @@ -1,16 +1,16 @@ use futures_util::TryFutureExt; -use serde_json::json; use libwallet::{self, vault, Signature}; -use sube::{ sube }; -use std::env; use rand_core::OsRng; +use serde_json::json; +use std::env; +use sube::sube; type Wallet = libwallet::Wallet; -use anyhow::{ Result, anyhow }; +use anyhow::{anyhow, Result}; #[async_std::main] -async fn main() -> Result<(), Box> { +async fn main() -> Result<()> { let phrase = env::args().skip(1).collect::>().join(" "); let (vault, phrase) = if phrase.is_empty() { @@ -21,14 +21,13 @@ async fn main() -> Result<(), Box> { }; let mut wallet = Wallet::new(vault); - wallet.unlock(None).await?; + wallet.unlock(None).await.map_err(|e| anyhow!("Error unlocking the wallet"))?; let account = wallet.default_account(); let public = account.public(); - let response = sube!( - "https://kusama.olanod.com/balances/transfer" => + "wss://rococo-rpc.polkadot.io/balances/transfer" => (wallet, json!({ "dest": { "Id": public.as_ref(), @@ -39,9 +38,8 @@ async fn main() -> Result<(), Box> { .await .map_err(|err| anyhow!(format!("SubeError {:?}", err)))?; - println!("Secret phrase: \"{phrase}\""); // println!("Default Account: 0x{account}"); Ok(()) -} \ No newline at end of file +} diff --git a/sube/examples/test.rs b/sube/examples/test.rs deleted file mode 100644 index 7449c48..0000000 --- a/sube/examples/test.rs +++ /dev/null @@ -1,72 +0,0 @@ -#![feature(prelude_import)] -#[prelude_import] -use std::prelude::rust_2021::*; -#[macro_use] -extern crate std; -use futures_util::TryFutureExt; -use serde_json::json; -use libwallet::{self, vault}; -use sube::sube; -use std::env; -use rand_core::OsRng; -type Wallet = libwallet::Wallet; -use anyhow::{Result, anyhow}; -fn main() -> Result<(), Box> { - async fn main() -> Result<(), Box> { - { - let phrase = env::args().skip(1).collect::>().join(" "); - let (vault, phrase) = if phrase.is_empty() { - vault::Simple::generate_with_phrase(&mut rand_core::OsRng) - } else { - let phrase: libwallet::Mnemonic = phrase - .parse() - .expect("Invalid phrase"); - (vault::Simple::from_phrase(&phrase), phrase) - }; - let mut wallet = Wallet::new(vault); - wallet.unlock(None).await?; - let account = wallet.default_account().public(); - let public = account.as_ref(); - - let response = async { - let mut builder = ::sube::builder::TxBuilder::default(); - // let account = &wallet.default_account(); - // let public = account.public(); - - builder - .with_signer(|message: &[u8]| Ok(wallet.sign(message).into())) - .with_sender(public.into()) - .with_body( - ::serde_json::Value::Object({ - let mut object = ::serde_json::Map::new(); - let _ = object - .insert( - ("dest").into(), - ::serde_json::Value::Object({ - let mut object = ::serde_json::Map::new(); - let _ = object - .insert( - ("Id").into(), - ::serde_json::to_value(&public.as_ref()).unwrap(), - ); - object - }), - ); - let _ = object - .insert( - ("value").into(), - ::serde_json::to_value(&100000).unwrap(), - ); - object - }), - ) - .await - } - .map_err(|err| anyhow!(format!("SubeError {:?}", err))) - .await?; - - Ok(()) - } - } - async_std::task::block_on(async { main().await }) -} \ No newline at end of file From 3e117c012a570711d01b2393e36ff75ee410f389 Mon Sep 17 00:00:00 2001 From: david barinas Date: Thu, 18 Apr 2024 09:31:24 -0500 Subject: [PATCH 10/11] docs: Update README.md with improved formatting and additional usage examples --- sube/README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/sube/README.md b/sube/README.md index e4f9883..ce27fba 100644 --- a/sube/README.md +++ b/sube/README.md @@ -2,19 +2,25 @@ A client library for Substrate chains, doing less by design than [subxt](https://github.com/paritytech/substrate-subxt) with a big focus on size and portability so it can run in constrainted environments like the browser. -Making use of the type information in a chain's metadata(`>= v14`) and powered by our [Scales](https://github.com/virto-network/scales) library, Sube allows automatic conversion between the [SCALE](https://github.com/paritytech/parity-scale-codec) binary format used by the blockchain with a human-readable representation like JSON without having to hardcode type information for each network. -When submitting extrinsics Sube only does that, it's your responsability to sign the payload with a different tool first(e.g. [libwallet](https://github.com/valibre-org/libwallet)) before you feed the extrinsic data to the library. +Making use of the type information in a chain's metadata(`>= v15`) and powered by our [Scales](../scales/) library, Sube allows automatic conversion between the [SCALE](https://github.com/paritytech/parity-scale-codec) binary format used by the blockchain with a human-readable representation like JSON without having to hardcode type information for each network. +When submitting extrinsics Sube only does that, it's your responsability to sign the payload with a different tool first(e.g. [libwallet](../libwallet)) before you feed the extrinsic data to the library. -Sube supports multiple backends under different feature flags like `http`, `http-web` or `ws`/`wss`, with [future support](https://github.com/virto-network/sube/milestone/3) for a [`smoldot`](https://github.com/paritytech/smoldot) based light-node. +Sube supports multiple backends under different feature flags like `http`, `http-web` or `ws`/`wss`. + + +## Example Usage + +To make Queries/Extrinsics using Sube, you can use the `SubeBuilder` or the convenient `sube!` macro. [here are the examples](./examples/) ## Progressive decentralization > 🛠️ ⚠️ [Upcoming feature](https://github.com/virto-network/sube/milestone/2) -The true _raison d'etre_ of Sube is not to create yet another Substrate client but to enable the Virto.Network and any project in the ecosystem to reach a broader audience of end-users and developers by lowering the technical entry barrier and drasticaly improving the overal user experience of interacting with blockchains. We call it **progressive decentralization**. +The true _raison d'etre_ of Sube is not to create yet another Substrate client but to enable the Virto.Network and any project in the ecosystem to reach a broader audience of end-users and developers by lowering the technical entry barrier and drastically improving the overall user experience of interacting with blockchains. We call it **progressive decentralization**. When paired with our plugin runtime [Valor](https://github.com/virto-network/valor), Sube can be exposed as an HTTP API that runs both in the server and the browser and be composed with other plugins to create higher level APIs that a client aplication can use from any plattform thanks to the ubiquitousness of HTTP. We imagine existing centralized projects easily integrating with Substrate blockchains in the server with the option to progressively migrate to a decentralized set-up with whole backends later running in the user device(web browser included). + But progressive decentralization goes beyond the migration of a centralized project, it's rather about giving users the the best experience by possibly combining the best of both worlds. A Sube powered application can start being served from a server to have an immediate response and 0 start-up time and since plugins can be hot-swapped, the blockchain backend can be switched from HTTP to lightnode transparently without the application code ever realizing, giving our users with bad connectivity and slower devices the opportunity to enjoy the best possible user experience without compromizing decentralization. From d9660a38cd8d802d26aef774b94f5fd4333c3ac7 Mon Sep 17 00:00:00 2001 From: david barinas Date: Thu, 18 Apr 2024 10:51:35 -0500 Subject: [PATCH 11/11] chore: fmt --- libwallet/src/key_pair.rs | 3 +-- sube/examples/query_balance_ws.rs | 2 +- sube/examples/send_tx.rs | 5 ++++- sube/examples/send_tx_libwallet.rs | 5 ++++- sube/src/ws.rs | 1 - 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/libwallet/src/key_pair.rs b/libwallet/src/key_pair.rs index 9e67cce..84dd9ae 100644 --- a/libwallet/src/key_pair.rs +++ b/libwallet/src/key_pair.rs @@ -1,6 +1,5 @@ -pub use derive::Derive; use core::{convert::TryInto, fmt::Debug}; - +pub use derive::Derive; type Bytes = [u8; N]; diff --git a/sube/examples/query_balance_ws.rs b/sube/examples/query_balance_ws.rs index 9063921..0d9e660 100644 --- a/sube/examples/query_balance_ws.rs +++ b/sube/examples/query_balance_ws.rs @@ -1,6 +1,6 @@ use env_logger; use serde::Deserialize; -use sube::{builder::SubeBuilder, sube, ExtrinsicBody, Response, Result, SignerFn, Error}; +use sube::{builder::SubeBuilder, sube, Error, ExtrinsicBody, Response, Result, SignerFn}; #[derive(Debug, Deserialize)] pub struct AccountInfo { diff --git a/sube/examples/send_tx.rs b/sube/examples/send_tx.rs index 0aa60fc..195d35f 100644 --- a/sube/examples/send_tx.rs +++ b/sube/examples/send_tx.rs @@ -21,7 +21,10 @@ async fn main() -> Result<(), Box> { }; let mut wallet = Wallet::new(vault); - wallet.unlock(None).await.map_err(|_| anyhow!("error wallet"))?; + wallet + .unlock(None) + .await + .map_err(|_| anyhow!("error wallet"))?; let account = wallet.default_account(); let public = account.public(); diff --git a/sube/examples/send_tx_libwallet.rs b/sube/examples/send_tx_libwallet.rs index 4b5908d..3237ae4 100644 --- a/sube/examples/send_tx_libwallet.rs +++ b/sube/examples/send_tx_libwallet.rs @@ -21,7 +21,10 @@ async fn main() -> Result<()> { }; let mut wallet = Wallet::new(vault); - wallet.unlock(None).await.map_err(|e| anyhow!("Error unlocking the wallet"))?; + wallet + .unlock(None) + .await + .map_err(|e| anyhow!("Error unlocking the wallet"))?; let account = wallet.default_account(); let public = account.public(); diff --git a/sube/src/ws.rs b/sube/src/ws.rs index 29f342d..1483456 100644 --- a/sube/src/ws.rs +++ b/sube/src/ws.rs @@ -180,7 +180,6 @@ impl Backend { } log::warn!("WS connection closed"); - }); } }