diff --git a/Cargo.lock b/Cargo.lock index 5f7f6e36e..03b4fbf65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2907,6 +2907,7 @@ dependencies = [ "kaspa-rpc-core", "kaspa-txscript", "kaspa-utils", + "kaspa-wallet-macros", "kaspa-wrpc-client", "md-5", "pad", @@ -2937,6 +2938,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "kaspa-wallet-macros" +version = "0.1.6" +dependencies = [ + "convert_case 0.5.0", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", + "xxhash-rust", +] + [[package]] name = "kaspa-wasm" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index df94fdafe..972e79129 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "daemon", "cli", "core", + "wallet/macros", "wallet/core", "wallet/native", "wallet/wasm", @@ -110,6 +111,7 @@ kaspa-wallet = { version = "0.1.6", path = "wallet/native" } kaspa-cli = { version = "0.1.6", path = "cli" } kaspa-wallet-cli-wasm = { version = "0.1.6", path = "wallet/wasm" } kaspa-wallet-core = { version = "0.1.6", path = "wallet/core" } +kaspa-wallet-macros = { version = "0.1.6", path = "wallet/macros" } kaspa-wasm = { version = "0.1.6", path = "wasm" } kaspa-wrpc-core = { version = "0.1.6", path = "rpc/wrpc/core" } kaspa-wrpc-client = { version = "0.1.6", path = "rpc/wrpc/client" } diff --git a/wallet/core/Cargo.toml b/wallet/core/Cargo.toml index 0ef2a723a..04b69c95e 100644 --- a/wallet/core/Cargo.toml +++ b/wallet/core/Cargo.toml @@ -44,6 +44,7 @@ kaspa-consensus-core.workspace = true kaspa-consensus-wasm.workspace = true kaspa-core.workspace = true kaspa-txscript.workspace = true +kaspa-wallet-macros.workspace = true serde.workspace = true serde_json.workspace = true serde-wasm-bindgen.workspace = true diff --git a/wallet/core/src/runtime/account/descriptor.rs b/wallet/core/src/runtime/account/descriptor.rs index 7346ca1ad..07dad3a76 100644 --- a/wallet/core/src/runtime/account/descriptor.rs +++ b/wallet/core/src/runtime/account/descriptor.rs @@ -5,43 +5,89 @@ use kaspa_addresses::Address; use serde::{Deserialize, Serialize}; use std::sync::Arc; +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +pub struct Bip32 { + pub account_id: AccountId, + pub prv_key_data_id: PrvKeyDataId, + pub account_index: u64, + pub xpub_keys: Arc>, + pub ecdsa: bool, + pub receive_address: Address, + pub change_address: Address, + pub meta: AddressDerivationMeta, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +pub struct Keypair { + pub account_id: AccountId, + pub prv_key_data_id: PrvKeyDataId, + pub public_key: String, + pub ecdsa: bool, + pub address: Address, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +pub struct Legacy { + pub account_id: AccountId, + pub prv_key_data_id: PrvKeyDataId, + pub xpub_keys: Arc>, + pub receive_address: Address, + pub change_address: Address, + pub meta: AddressDerivationMeta, +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +pub struct MultiSig { + pub account_id: AccountId, + // TODO add multisig data +} + +#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +pub struct Resident { + pub account_id: AccountId, + pub public_key: String, +} + /// [`Descriptor`] is a type that offers serializable representation of an account. /// It is means for RPC or transports that do not allow for transport /// of the account data. #[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "kebab-case")] +#[serde(tag = "type")] pub enum Descriptor { - Bip32 { - account_id: AccountId, - prv_key_data_id: PrvKeyDataId, - account_index: u64, - xpub_keys: Arc>, - ecdsa: bool, - receive_address: Address, - change_address: Address, - meta: AddressDerivationMeta, - }, - Keypair { - account_id: AccountId, - prv_key_data_id: PrvKeyDataId, - public_key: String, - ecdsa: bool, - address: Address, - }, - Legacy { - account_id: AccountId, - prv_key_data_id: PrvKeyDataId, - xpub_keys: Arc>, - receive_address: Address, - change_address: Address, - meta: AddressDerivationMeta, - }, - MultiSig { - account_id: AccountId, - // TODO add multisig data - }, - Resident { - account_id: AccountId, - public_key: String, - }, + Bip32(Bip32), + Keypair(Keypair), + Legacy(Legacy), + MultiSig(MultiSig), + Resident(Resident), +} + +impl From for Descriptor { + fn from(bip32: Bip32) -> Self { + Self::Bip32(bip32) + } +} + +impl From for Descriptor { + fn from(keypair: Keypair) -> Self { + Self::Keypair(keypair) + } +} + +impl From for Descriptor { + fn from(legacy: Legacy) -> Self { + Self::Legacy(legacy) + } +} + +impl From for Descriptor { + fn from(multisig: MultiSig) -> Self { + Self::MultiSig(multisig) + } +} + +impl From for Descriptor { + fn from(resident: Resident) -> Self { + Self::Resident(resident) + } } diff --git a/wallet/core/src/runtime/account/variants/bip32.rs b/wallet/core/src/runtime/account/variants/bip32.rs index b778f9f7c..027b3eb5b 100644 --- a/wallet/core/src/runtime/account/variants/bip32.rs +++ b/wallet/core/src/runtime/account/variants/bip32.rs @@ -1,7 +1,7 @@ use crate::derivation::AddressDerivationManager; use crate::imports::*; use crate::result::Result; -use crate::runtime::account::descriptor::Descriptor; +use crate::runtime::account::descriptor::{self, Descriptor}; use crate::runtime::account::Inner; use crate::runtime::account::{Account, AccountId, AccountKind, DerivationCapableAccount}; use crate::runtime::Wallet; @@ -85,7 +85,7 @@ impl Account for Bip32 { } fn descriptor(&self) -> Result { - let descriptor = Descriptor::Bip32 { + let descriptor = descriptor::Bip32 { account_id: *self.id(), prv_key_data_id: self.prv_key_data_id, account_index: self.account_index, @@ -96,7 +96,7 @@ impl Account for Bip32 { meta: self.derivation.address_derivation_meta(), }; - Ok(descriptor) + Ok(descriptor.into()) } fn as_derivation_capable(self: Arc) -> Result> { diff --git a/wallet/core/src/runtime/account/variants/keypair.rs b/wallet/core/src/runtime/account/variants/keypair.rs index 82e632e2b..fa62cf2e9 100644 --- a/wallet/core/src/runtime/account/variants/keypair.rs +++ b/wallet/core/src/runtime/account/variants/keypair.rs @@ -1,6 +1,6 @@ use crate::imports::*; use crate::result::Result; -use crate::runtime::account::descriptor::Descriptor; +use crate::runtime::account::descriptor::{self, Descriptor}; use crate::runtime::account::{Account, AccountId, AccountKind, Inner}; use crate::runtime::Wallet; use crate::storage::{self, Metadata, PrvKeyDataId, Settings}; @@ -70,7 +70,7 @@ impl Account for Keypair { } fn descriptor(&self) -> Result { - let descriptor = Descriptor::Keypair { + let descriptor = descriptor::Keypair { account_id: *self.id(), prv_key_data_id: self.prv_key_data_id, public_key: self.public_key.to_string(), @@ -78,6 +78,6 @@ impl Account for Keypair { address: self.receive_address()?, }; - Ok(descriptor) + Ok(descriptor.into()) } } diff --git a/wallet/core/src/runtime/account/variants/legacy.rs b/wallet/core/src/runtime/account/variants/legacy.rs index bfa379aa0..221f11bd5 100644 --- a/wallet/core/src/runtime/account/variants/legacy.rs +++ b/wallet/core/src/runtime/account/variants/legacy.rs @@ -1,6 +1,6 @@ use crate::imports::*; use crate::result::Result; -use crate::runtime::account::descriptor::Descriptor; +use crate::runtime::account::descriptor::{self, Descriptor}; use crate::runtime::account::{Account, AccountId, AccountKind, DerivationCapableAccount, Inner}; use crate::runtime::Wallet; use crate::storage::{self, Metadata, PrvKeyDataId, Settings}; @@ -79,7 +79,7 @@ impl Account for Legacy { } fn descriptor(&self) -> Result { - let descriptor = Descriptor::Legacy { + let descriptor = descriptor::Legacy { account_id: *self.id(), prv_key_data_id: self.prv_key_data_id, xpub_keys: self.xpub_keys.clone(), @@ -88,7 +88,7 @@ impl Account for Legacy { meta: self.derivation.address_derivation_meta(), }; - Ok(descriptor) + Ok(descriptor.into()) } fn as_derivation_capable(self: Arc) -> Result> { diff --git a/wallet/core/src/runtime/account/variants/multisig.rs b/wallet/core/src/runtime/account/variants/multisig.rs index 333dfb479..a8e58953a 100644 --- a/wallet/core/src/runtime/account/variants/multisig.rs +++ b/wallet/core/src/runtime/account/variants/multisig.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use crate::result::Result; -use crate::runtime::account::descriptor::Descriptor; +use crate::runtime::account::descriptor::{self, Descriptor}; use crate::runtime::account::Inner; use crate::runtime::account::{Account, AccountId, AccountKind, DerivationCapableAccount}; use crate::runtime::Wallet; @@ -101,9 +101,8 @@ impl Account for MultiSig { } fn descriptor(&self) -> Result { - let descriptor = Descriptor::MultiSig { account_id: *self.id() }; - - Ok(descriptor) + let descriptor = descriptor::MultiSig { account_id: *self.id() }; + Ok(descriptor.into()) } fn as_derivation_capable(self: Arc) -> Result> { diff --git a/wallet/core/src/runtime/account/variants/resident.rs b/wallet/core/src/runtime/account/variants/resident.rs index 55fdbe08e..a68d5ae6e 100644 --- a/wallet/core/src/runtime/account/variants/resident.rs +++ b/wallet/core/src/runtime/account/variants/resident.rs @@ -1,6 +1,6 @@ use crate::imports::*; use crate::result::Result; -use crate::runtime::account::descriptor::Descriptor; +use crate::runtime::account::descriptor::{self, Descriptor}; use crate::runtime::account::{Account, AccountId, AccountKind, Inner}; use crate::runtime::Wallet; use crate::storage::{self, Metadata, PrvKeyDataId}; @@ -61,8 +61,8 @@ impl Account for Resident { } fn descriptor(&self) -> Result { - let descriptor = Descriptor::Resident { account_id: *self.id(), public_key: self.public_key.to_string() }; + let descriptor = descriptor::Resident { account_id: *self.id(), public_key: self.public_key.to_string() }; - Ok(descriptor) + Ok(descriptor.into()) } } diff --git a/wallet/core/src/runtime/api/mod.rs b/wallet/core/src/runtime/api/mod.rs index 2fbdc91db..5dc4d9e70 100644 --- a/wallet/core/src/runtime/api/mod.rs +++ b/wallet/core/src/runtime/api/mod.rs @@ -3,3 +3,5 @@ pub use message::*; pub mod traits; pub use traits::*; + +pub mod transport; diff --git a/wallet/core/src/runtime/api/transport.rs b/wallet/core/src/runtime/api/transport.rs new file mode 100644 index 000000000..d38c5bd46 --- /dev/null +++ b/wallet/core/src/runtime/api/transport.rs @@ -0,0 +1,108 @@ +use std::sync::Arc; + +use super::message::*; +use super::traits::WalletApi; +use crate::error::Error; +use crate::result::Result; +use async_trait::async_trait; +use borsh::{BorshDeserialize, BorshSerialize}; +use kaspa_wallet_macros::{build_wallet_client_transport_interface, build_wallet_server_transport_interface}; +// use serde::de::DeserializeOwned; +// use serde::{Deserialize, Serialize}; + +#[async_trait] +pub trait Transport { + async fn call(&self, op: u64, request: &[u8]) -> Result>; +} + +// - TODO - WALLET SERVER + +pub struct WalletServer { + pub wallet_api: Arc, +} + +#[async_trait] +impl Transport for WalletServer { + async fn call(&self, op: u64, request: &[u8]) -> Result> { + build_wallet_server_transport_interface! {[ + WalletEnumerate, + WalletCreate, + WalletOpen, + WalletClose, + PrvKeyDataCreate, + PrvKeyDataRemove, + PrvKeyDataGet, + AccountEnumerate, + AccountCreate, + AccountImport, + AccountGet, + AccountCreateNewAddress, + AccountSend, + AccountEstimate, + TransactionDataGet, + AddressBookEnumerate, + ]} + } +} + +pub struct WalletClient { + pub client_sender: Arc, +} + +impl WalletClient { + pub fn new(client_sender: Arc) -> Self { + Self { client_sender } + } +} + +use workflow_core::channel::Receiver; +#[async_trait] +impl WalletApi for WalletClient { + async fn register_notifications(self: Arc, _channel: Receiver) -> Result { + todo!() + } + async fn unregister_notifications(self: Arc, _channel_id: u64) -> Result<()> { + todo!() + } + + async fn connection_status_call(self: Arc, _request: ConnectionStatusRequest) -> Result { + todo!() + } + + // ------------------------------------------------------------------------------------- + + async fn connection_settings_get_call( + self: Arc, + _request: ConnectionSettingsGetRequest, + ) -> Result { + todo!() + } + + async fn connection_settings_set_call( + self: Arc, + _request: ConnectionSettingsSetRequest, + ) -> Result { + todo!() + } + + // ------------------------------------------------------------------------------------- + + build_wallet_client_transport_interface! {[ + WalletEnumerate, + WalletCreate, + WalletOpen, + WalletClose, + PrvKeyDataCreate, + PrvKeyDataRemove, + PrvKeyDataGet, + AccountEnumerate, + AccountCreate, + AccountImport, + AccountGet, + AccountCreateNewAddress, + AccountSend, + AccountEstimate, + TransactionDataGet, + AddressBookEnumerate, + ]} +} diff --git a/wallet/macros/Cargo.toml b/wallet/macros/Cargo.toml new file mode 100644 index 000000000..f35651234 --- /dev/null +++ b/wallet/macros/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "kaspa-wallet-macros" +authors.workspace = true +edition.workspace = true +license.workspace = true +version.workspace = true +keywords = ["rpc"] +categories = [] +exclude = ["/.*", "/test"] +description = """ +Macros for the Kaspa Wallet infrastructure +""" + +[lib] +proc-macro = true + +[dependencies] +proc-macro-error = { version = "1", default-features = false } +proc-macro2 = { version = "1.0.43" } +quote = "1.0.21" +syn = {version="1.0.99",features=["full","fold","extra-traits","parsing","proc-macro"]} +convert_case = "0.5.0" +regex.workspace = true +xxhash-rust.workspace = true diff --git a/wallet/macros/src/handler.rs b/wallet/macros/src/handler.rs new file mode 100644 index 000000000..c057289df --- /dev/null +++ b/wallet/macros/src/handler.rs @@ -0,0 +1,55 @@ +use convert_case::{Case, Casing}; +// use proc_macro::Literal; +use proc_macro2::{Ident, Literal, Span}; +use quote::ToTokens; +use syn::{Error, Expr, ExprArray, Result}; +use xxhash_rust::xxh3::xxh3_64; + +pub struct Handler { + pub name: String, + pub hash: Literal, + pub fn_call: Ident, + pub fn_with_suffix: Option, + pub fn_no_suffix: Ident, + pub fn_camel: Ident, + pub request_type: Ident, + pub response_type: Ident, +} + +impl Handler { + pub fn new(handler: &Expr) -> Handler { + Handler::new_with_args(handler, None) + } + + pub fn new_with_args(handler: &Expr, fn_suffix: Option<&str>) -> Handler { + let name = handler.to_token_stream().to_string(); + let hash = Literal::u64_suffixed(xxh3_64(name.as_bytes())); + let fn_call = Ident::new(&format!("{}_call", name.to_case(Case::Snake)), Span::call_site()); + let fn_with_suffix = fn_suffix.map(|suffix| Ident::new(&format!("{}_{suffix}", name.to_case(Case::Snake)), Span::call_site())); + let fn_no_suffix = Ident::new(&name.to_case(Case::Snake), Span::call_site()); + let fn_camel = Ident::new(&name.to_case(Case::Camel), Span::call_site()); + let request_type = Ident::new(&format!("{name}Request"), Span::call_site()); + let response_type = Ident::new(&format!("{name}Response"), Span::call_site()); + Handler { name, hash, fn_call, fn_with_suffix, fn_no_suffix, fn_camel, request_type, response_type } + } +} + +pub fn get_handlers(handlers: Expr) -> Result { + let handlers = match handlers { + Expr::Array(array) => array, + _ => { + return Err(Error::new_spanned(handlers, "the argument must be an array of enum variants".to_string())); + } + }; + + for ph in handlers.elems.iter() { + match ph { + Expr::Path(_exp_path) => {} + _ => { + return Err(Error::new_spanned(ph, "handlers should contain enum variants".to_string())); + } + } + } + + Ok(handlers) +} diff --git a/wallet/macros/src/lib.rs b/wallet/macros/src/lib.rs new file mode 100644 index 000000000..4434b4cc1 --- /dev/null +++ b/wallet/macros/src/lib.rs @@ -0,0 +1,28 @@ +use proc_macro::TokenStream; +use proc_macro_error::proc_macro_error; +mod handler; +mod wallet; + +#[proc_macro] +#[proc_macro_error] +pub fn build_wallet_client_transport_interface(input: TokenStream) -> TokenStream { + wallet::client::build_transport_interface(input) +} + +#[proc_macro] +#[proc_macro_error] +pub fn build_wallet_server_transport_interface(input: TokenStream) -> TokenStream { + wallet::server::build_transport_interface(input) +} + +// #[proc_macro] +// #[proc_macro_error] +// pub fn build_wrpc_wasm_bindgen_interface(input: TokenStream) -> TokenStream { +// wallet::wasm::build_wrpc_wasm_bindgen_interface(input) +// } + +// #[proc_macro] +// #[proc_macro_error] +// pub fn build_wrpc_wasm_bindgen_subscriptions(input: TokenStream) -> TokenStream { +// wallet::wasm::build_wrpc_wasm_bindgen_subscriptions(input) +// } diff --git a/wallet/macros/src/wallet/client.rs b/wallet/macros/src/wallet/client.rs new file mode 100644 index 000000000..06f72120c --- /dev/null +++ b/wallet/macros/src/wallet/client.rs @@ -0,0 +1,116 @@ +use crate::handler::*; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use std::convert::Into; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + punctuated::Punctuated, + Error, Expr, ExprArray, Result, Token, +}; + +#[derive(Debug)] +struct RpcTable { + // rpc_api_ops: Expr, + handlers: ExprArray, +} + +impl Parse for RpcTable { + fn parse(input: ParseStream) -> Result { + let parsed = Punctuated::::parse_terminated(input).unwrap(); + if parsed.len() != 1 { + return Err(Error::new_spanned(parsed, "usage: build_wrpc_client_interface!([XxxRequest, ..])".to_string())); + } + + let mut iter = parsed.iter(); + // Intake the enum name + // let rpc_api_ops = iter.next().unwrap().clone(); + // Intake enum variants as an array + let handlers = get_handlers(iter.next().unwrap().clone())?; + + Ok(RpcTable { handlers }) + } +} + +impl ToTokens for RpcTable { + fn to_tokens(&self, tokens: &mut TokenStream) { + let mut targets = Vec::new(); + // let rpc_api_ops = &self.rpc_api_ops; + + for handler in self.handlers.elems.iter() { + let Handler { hash, fn_call, request_type, response_type, .. } = Handler::new(handler); + + // async fn #fn_call(&self, request : #request_type) -> RpcResult<#response_type> { + // let response: ClientResult<#response_type> = self.inner.rpc.call(#rpc_api_ops::#handler, request).await; + // Ok(response.map_err(|e| e.to_string())?) + // } + + // Due to conflicts between #[async_trait] macro and other macros, + // the async implementation of the RPC caller is inlined + // targets.push(quote! { + + // fn #fn_call<'life0, 'async_trait>( + // &'life0 self, + // request: #request_type, + // ) -> ::core::pin::Pin> + ::core::marker::Send + 'async_trait>> + // where + // 'life0: 'async_trait, + // Self: 'async_trait, + // { + // Box::pin(async move { + // if let ::core::option::Option::Some(__ret) = ::core::option::Option::None::> { + // return __ret; + // } + // let __self = self; + // //let request = request; + // let __ret: RpcResult<#response_type> = { + // let resp: ClientResult<#response_type> = __self.inner.rpc_client.call(#rpc_api_ops::#handler, request).await; + // Ok(resp.map_err(|e| kaspa_rpc_core::error::RpcError::RpcSubsystem(e.to_string()))?) + // }; + // #[allow(unreachable_code)] + // __ret + // }) + // } + + // }); + + targets.push(quote! { + fn #fn_call<'async_trait>( + self: Arc, + request: #request_type, + ) -> ::core::pin::Pin< + Box> + ::core::marker::Send + 'async_trait>, + > + where + Self: 'async_trait, + { + Box::pin(async move { + if let ::core::option::Option::Some(__ret) = ::core::option::Option::None::> { + return __ret; + } + let op: u64 = #hash; + let __self = self; + let request = request; + let __ret: Result<#response_type> = + { Ok(#response_type::try_from_slice(&__self.client_sender.call(op, &request.try_to_vec()?).await?)?) }; + #[allow(unreachable_code)] + __ret + }) + } + + }); + } + + quote! { + #(#targets)* + } + .to_tokens(tokens); + } +} + +pub fn build_transport_interface(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let rpc_table = parse_macro_input!(input as RpcTable); + let ts = rpc_table.to_token_stream(); + // println!("MACRO: {}", ts.to_string()); + ts.into() +} diff --git a/wallet/macros/src/wallet/mod.rs b/wallet/macros/src/wallet/mod.rs new file mode 100644 index 000000000..23ab8e74b --- /dev/null +++ b/wallet/macros/src/wallet/mod.rs @@ -0,0 +1,3 @@ +pub mod client; +pub mod server; +// pub mod wasm; diff --git a/wallet/macros/src/wallet/server.rs b/wallet/macros/src/wallet/server.rs new file mode 100644 index 000000000..f7faf1326 --- /dev/null +++ b/wallet/macros/src/wallet/server.rs @@ -0,0 +1,100 @@ +use crate::handler::*; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use std::convert::Into; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + punctuated::Punctuated, + Error, Expr, ExprArray, Result, Token, +}; + +#[derive(Debug)] +struct RpcTable { + // server_ctx: Expr, + // server_ctx_type: Expr, + // connection_ctx_type: Expr, + // rpc_api_ops: Expr, + handlers: ExprArray, +} + +impl Parse for RpcTable { + fn parse(input: ParseStream) -> Result { + let parsed = Punctuated::::parse_terminated(input).unwrap(); + if parsed.len() != 1 { + return Err(Error::new_spanned(parsed, "usage: build_wrpc_server_interface!([XxxOp, ..])".to_string())); + } + + let mut iter = parsed.iter(); + // let server_ctx = iter.next().unwrap().clone(); + // let server_ctx_type = iter.next().unwrap().clone(); + // let connection_ctx_type = iter.next().unwrap().clone(); + // let rpc_api_ops = iter.next().unwrap().clone(); + let handlers = get_handlers(iter.next().unwrap().clone())?; + + // Ok(RpcTable { server_ctx, server_ctx_type, connection_ctx_type, rpc_api_ops, handlers }) + Ok(RpcTable { handlers }) + } +} + +impl ToTokens for RpcTable { + fn to_tokens(&self, tokens: &mut TokenStream) { + let mut targets = Vec::new(); + // let server_ctx = &self.server_ctx; + // let server_ctx_type = &self.server_ctx_type; + // let connection_ctx_type = &self.connection_ctx_type; + // let rpc_api_ops = &self.rpc_api_ops; + + for handler in self.handlers.elems.iter() { + // let Handler { hash, fn_call, request_type, response_type, .. } = Handler::new(handler); + let Handler { hash, fn_call, request_type, .. } = Handler::new(handler); + + targets.push(quote! { + #hash => { + + Ok(self.wallet_api.clone().#fn_call(#request_type::try_from_slice(&request)?).await?.try_to_vec()?) + + // Ok(response.try_to_vec()?) + // .map_err(|e|ServerError::Text(e.to_string()))? + + // interface.method(#rpc_api_ops::#handler, method!(|server_ctx: #server_ctx_type, connection_ctx: #connection_ctx_type, request: #request_type| async move { + // let verbose = server_ctx.verbose(); + // if verbose { workflow_log::log_info!("request: {:?}",request); } + // let response: #response_type = server_ctx.rpc_service(&connection_ctx).#fn_call(request).await + // .map_err(|e|ServerError::Text(e.to_string()))?; + // if verbose { workflow_log::log_info!("response: {:?}",response); } + // Ok(response) + // })); + } + }); + } + + quote! { + + // { + // let mut interface = workflow_rpc::server::Interface::< + // #server_ctx_type, + // #connection_ctx_type, + // #rpc_api_ops + // >::new(#server_ctx); + + // for op in #rpc_api_ops::list() { + match op { + #(#targets)* + _ => { Err(Error::NotImplemented) } + } + // } + + // interface + // } + } + .to_tokens(tokens); + } +} + +pub fn build_transport_interface(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let rpc_table = parse_macro_input!(input as RpcTable); + let ts = rpc_table.to_token_stream(); + // println!("MACRO: {}", ts.to_string()); + ts.into() +} diff --git a/wallet/macros/src/wallet/wasm.rs b/wallet/macros/src/wallet/wasm.rs new file mode 100644 index 000000000..89802ad87 --- /dev/null +++ b/wallet/macros/src/wallet/wasm.rs @@ -0,0 +1,172 @@ +use crate::handler::*; +use convert_case::{Case, Casing}; +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{quote, ToTokens}; +use regex::Regex; +use std::convert::Into; +use syn::{ + parse::{Parse, ParseStream}, + parse_macro_input, + punctuated::Punctuated, + Error, Expr, ExprArray, Result, Token, +}; + +#[derive(Debug)] +struct RpcHandlers { + handlers_no_args: ExprArray, + handlers_with_args: ExprArray, +} + +impl Parse for RpcHandlers { + fn parse(input: ParseStream) -> Result { + let parsed = Punctuated::::parse_terminated(input).unwrap(); + if parsed.len() != 2 { + return Err(Error::new_spanned( + parsed, + "usage: build_wrpc_wasm_bindgen_interface!([fn no args, ..],[fn with args, ..])".to_string(), + )); + } + + let mut iter = parsed.iter(); + let handlers_no_args = get_handlers(iter.next().unwrap().clone())?; + let handlers_with_args = get_handlers(iter.next().unwrap().clone())?; + + let handlers = RpcHandlers { handlers_no_args, handlers_with_args }; + Ok(handlers) + } +} + +impl ToTokens for RpcHandlers { + fn to_tokens(&self, tokens: &mut TokenStream) { + let mut targets_no_args = Vec::new(); + let mut targets_with_args = Vec::new(); + + for handler in self.handlers_no_args.elems.iter() { + let Handler { fn_call, fn_camel, fn_no_suffix, request_type, response_type, .. } = Handler::new(handler); + + targets_no_args.push(quote! { + + #[wasm_bindgen(js_name = #fn_camel)] + pub async fn #fn_no_suffix(&self) -> Result { + let value: JsValue = js_sys::Object::new().into(); + let request: #request_type = from_value(value)?; + // log_info!("request: {:#?}",request); + let result: RpcResult<#response_type> = self.client.#fn_call(request).await; + // log_info!("result: {:#?}",result); + + let response: #response_type = result.map_err(|err|wasm_bindgen::JsError::new(&err.to_string()))?; + //log_info!("response: {:#?}",response); + workflow_wasm::serde::to_value(&response).map_err(|err|err.into()) + } + + }); + } + + for handler in self.handlers_with_args.elems.iter() { + let Handler { fn_call, fn_camel, fn_no_suffix, request_type, response_type, .. } = Handler::new(handler); + + targets_with_args.push(quote! { + + #[wasm_bindgen(js_name = #fn_camel)] + pub async fn #fn_no_suffix(&self, request: JsValue) -> Result { + let request: #request_type = from_value(request)?; + let result: RpcResult<#response_type> = self.client.#fn_call(request).await; + let response: #response_type = result.map_err(|err|wasm_bindgen::JsError::new(&err.to_string()))?; + workflow_wasm::serde::to_value(&response).map_err(|err|err.into()) + } + + }); + } + + quote! { + #[wasm_bindgen] + impl RpcClient { + #(#targets_no_args)* + #(#targets_with_args)* + } + } + .to_tokens(tokens); + } +} + +pub fn build_wrpc_wasm_bindgen_interface(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let rpc_table = parse_macro_input!(input as RpcHandlers); + let ts = rpc_table.to_token_stream(); + // println!("MACRO: {}", ts.to_string()); + ts.into() +} + +// ##################################################################### + +#[derive(Debug)] +struct RpcSubscriptions { + handlers: ExprArray, +} + +impl Parse for RpcSubscriptions { + fn parse(input: ParseStream) -> Result { + let parsed = Punctuated::::parse_terminated(input).unwrap(); + if parsed.len() != 1 { + return Err(Error::new_spanned( + parsed, + "usage: build_wrpc_wasm_bindgen_interface!([fn no args, ..],[fn with args, ..])".to_string(), + )); + } + + let mut iter = parsed.iter(); + let handlers = get_handlers(iter.next().unwrap().clone())?; + + Ok(RpcSubscriptions { handlers }) + } +} + +impl ToTokens for RpcSubscriptions { + fn to_tokens(&self, tokens: &mut TokenStream) { + let mut targets = Vec::new(); + + for handler in self.handlers.elems.iter() { + let name = format!("Notify{}", handler.to_token_stream().to_string().as_str()); + let regex = Regex::new(r"^Notify").unwrap(); + let blank = regex.replace(&name, ""); + let subscribe = regex.replace(&name, "Subscribe"); + let unsubscribe = regex.replace(&name, "Unsubscribe"); + let scope = Ident::new(&blank, Span::call_site()); + let sub_scope = Ident::new(format!("{blank}Scope").as_str(), Span::call_site()); + let fn_subscribe_snake = Ident::new(&subscribe.to_case(Case::Snake), Span::call_site()); + let fn_subscribe_camel = Ident::new(&subscribe.to_case(Case::Camel), Span::call_site()); + let fn_unsubscribe_snake = Ident::new(&unsubscribe.to_case(Case::Snake), Span::call_site()); + let fn_unsubscribe_camel = Ident::new(&unsubscribe.to_case(Case::Camel), Span::call_site()); + + targets.push(quote! { + + #[wasm_bindgen(js_name = #fn_subscribe_camel)] + pub async fn #fn_subscribe_snake(&self) -> Result<()> { + self.client.start_notify(ListenerId::default(), Scope::#scope(#sub_scope {})).await?; + Ok(()) + } + + #[wasm_bindgen(js_name = #fn_unsubscribe_camel)] + pub async fn #fn_unsubscribe_snake(&self) -> Result<()> { + self.client.stop_notify(ListenerId::default(), Scope::#scope(#sub_scope {})).await?; + Ok(()) + } + + }); + } + + quote! { + #[wasm_bindgen] + impl RpcClient { + #(#targets)* + } + } + .to_tokens(tokens); + } +} + +pub fn build_wrpc_wasm_bindgen_subscriptions(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let rpc_table = parse_macro_input!(input as RpcSubscriptions); + let ts = rpc_table.to_token_stream(); + // println!("MACRO: {}", ts.to_string()); + ts.into() +}