diff --git a/libwallet/js/Cargo.toml b/libwallet/js/Cargo.toml index bd65dd3..2bdd9ff 100644 --- a/libwallet/js/Cargo.toml +++ b/libwallet/js/Cargo.toml @@ -15,7 +15,7 @@ js-sys = "0.3.61" rand_core = { version = "0.6.3", features = ["getrandom"] } serde = { version = "1.0.152", features = ["derive"] } serde-wasm-bindgen = "0.4.5" -wasm-bindgen = "0.2.84" +wasm-bindgen = "=0.2.84" wasm-bindgen-futures = "0.4.34" [dev-dependencies] diff --git a/libwallet/src/key_pair.rs b/libwallet/src/key_pair.rs index aeaf0c2..daac3ed 100644 --- a/libwallet/src/key_pair.rs +++ b/libwallet/src/key_pair.rs @@ -1,8 +1,11 @@ -use core::fmt::Debug; + + +use core::{fmt::Debug, convert::TryInto}; pub use derive::Derive; type Bytes = [u8; N]; +// struct Bytes([u8; N]); /// A key pair with a public key pub trait Pair: Signer + Derive { @@ -18,10 +21,13 @@ 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 pub trait Signer { type Signature: Signature; fn sign_msg>(&self, msg: M) -> Self::Signature; @@ -135,6 +141,7 @@ pub mod any { Ok(()) } } + impl Public for AnyPublic {} #[derive(Debug, PartialEq)] diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index 2f89e4c..4b3efef 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -1,3 +1,6 @@ +#![feature(async_fn_in_trait, impl_trait_projections)] +#![feature(trait_alias)] +// #![feature(result_option_inspect)] #![cfg_attr(not(any(test, feature = "std")), no_std)] //! `libwallet` is the one-stop tool to build easy, slightly opinionated crypto wallets //! that run in all kinds of environments and plattforms including embedded hardware, diff --git a/sube/Cargo.toml b/sube/Cargo.toml index 9e2a57b..2b6f792 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 } @@ -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 = { git = "https://github.com/virto-network/scales.git", package = "scale-serialization", default-features = false, features = ["codec", "experimental-serializer"] } # http backend surf = { version = "2.3.2", default_features = false, optional = true } @@ -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", ] diff --git a/sube/examples/query_balance.rs b/sube/examples/query_balance.rs index ee72917..8fe25e0 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..e3fdda1 --- /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, ExtrinicBody }; + +#[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..fd43739 --- /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..336d153 --- /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..436a5e7 --- /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 diff --git a/sube/src/builder.rs b/sube/src/builder.rs index 992708c..f60aea1 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, ExtrinicBody, 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> -where +// 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, ExtrinicBody { + 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,69 @@ 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..c028fb3 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)] +#[derive(Serialize, Deserialize, Debug)] pub struct ExtrinicBody { 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, + from: &'m [u8], tx_data: ExtrinicBody, 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::*; 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..c88b867 100644 --- a/sube/sube-js/src/lib.rs +++ b/sube/sube-js/src/lib.rs @@ -14,6 +14,7 @@ use sube::{ Backend, Error as SubeError, ExtrinicBody, 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())))?; @@ -94,28 +87,27 @@ pub async fn sube_js( 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(), &JsValue::from(js_sys::Uint8Array::from(message)), ) .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()))?; + + let buffer: [u8; 64] = vec.try_into().expect("slice with incorrect length"); - out.copy_from_slice(&vec); - - Ok(()) + Ok(buffer) }, - ) + sender: extrinsic_value.from, + body: extrinsic_value.body, + }) .await .map_err(|e| JsError::new(&format!("Error trying: {:?}", e.to_string())))?;