diff --git a/Makefile b/Makefile index 6335974a..73eeb893 100644 --- a/Makefile +++ b/Makefile @@ -78,6 +78,9 @@ wasm: wasm-pack build --release --target=web --out-name=zklink-sdk-web --out-dir=web-dist && \ wasm-pack build --release --target=nodejs --out-name=zklink-sdk-node --out-dir=node-dist #wasm-pack build --release --target=bundler --out-name=zklink-bundler-node --out-dir=dist +test_wasm: + cd ${ROOT_DIR}/bindings/wasm && \ + wasm-pack test --firefox --headless -- --test test_rpc run_example_go_%: ${ROOT_DIR}/examples/Golang/%.go diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index ee938e76..86277e24 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -5,22 +5,33 @@ edition = "2021" publish = false [lib] -crate-type = ["cdylib"] +crate-type = ["cdylib","rlib"] [dependencies] zklink_sdk_signers = { path = "../../signers" } zklink_sdk_types = { path = "../../types" } +zklink_sdk_provider = { path = "../../provider" } +zklink_sdk_interface = { path = "../../interface" } +serde_json = "1.0" wasm-bindgen = { version = "0.2.87",features = ["serde-serialize"] } +serde-wasm-bindgen = "0.5" getrandom = { version = "0.2.10", features = ["js"] } +web-sys = "0.3" +hex = "0.4.3" +serde = "1.0.137" +reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen-futures = "0.4" +jsonrpsee = { version = "0.20.1", features = ["macros","jsonrpsee-types","client-core"] } +uuid = "0.8" + [features] default = [] ffi = [] [dev-dependencies] -actix-rt = "2.7" -actix-web = "4.2" -futures = "0.3" -tokio = { version = "1", features = ["full"] } +wasm-bindgen-test = "0.3" [package.metadata.wasm-pack.profile.release] wasm-opt = false diff --git a/bindings/wasm/src/crypto.rs b/bindings/wasm/src/crypto.rs deleted file mode 100644 index 335443f9..00000000 --- a/bindings/wasm/src/crypto.rs +++ /dev/null @@ -1,80 +0,0 @@ -use wasm_bindgen::prelude::*; -use zklink_sdk_signers::zklink_signer::pk_signer::ZkLinkSigner as Signer; -// use zklink_sdk_signers::zklink_signer::error::ZkSignerError; -use zklink_sdk_signers::zklink_signer::signature::ZkLinkSignature as Signature; -// use zklink_sdk_signers::eth_signer::eth_signature::TxEthSignature; -// use wasm_bindgen_futures::{JsFuture, future_to_promise}; -use zklink_sdk_signers::eth_signer::pk_signer::EthSigner; - -#[wasm_bindgen] -pub struct EthPrivateKeySigner { - inner: EthSigner, -} - -#[wasm_bindgen] -impl EthPrivateKeySigner { - #[wasm_bindgen] - pub fn new_from_hex_pk(private_key: &str) -> Result { - let signer = EthSigner::try_from(private_key)?; - Ok(Self { inner: signer }) - } - - #[wasm_bindgen] - pub fn get_address(&self) -> Result { - let address = self.inner.get_address()?; - Ok(format!("{:?}", address)) - } - #[wasm_bindgen] - pub fn sign_message(&self, msg: &[u8]) -> Result { - let signature = self.inner.sign_message(msg)?; - Ok(signature.as_hex()) - } -} - -#[wasm_bindgen] -pub struct ZklinkSigner { - inner: Signer, -} - -#[wasm_bindgen] -impl ZklinkSigner { - #[wasm_bindgen(js_name=NewRand)] - pub fn new_rand() -> Result { - let zklink_signer = Signer::new()?; - Ok(ZklinkSigner { - inner: zklink_signer, - }) - } - - #[wasm_bindgen(js_name=NewFromEthSigner)] - pub fn new_from_hex_eth_signer(eth_hex_private_key: &str) -> Result { - let zklink_signer = Signer::new_from_hex_eth_signer(eth_hex_private_key)?; - Ok(ZklinkSigner { - inner: zklink_signer, - }) - } - - #[wasm_bindgen] - pub fn sign(&self, msg: &[u8]) -> Result { - let signature = self.inner.sign_musig(msg)?; - Ok(signature.as_hex()) - } -} - -#[wasm_bindgen] -pub struct ZklinkSignature { - inner: Signature, -} - -#[wasm_bindgen] -impl ZklinkSignature { - #[wasm_bindgen(js_name=NewFromHexStr)] - pub fn new_from_hex_str(signature_str: &str) -> Result { - let signature = Signature::from_hex(signature_str)?; - Ok(ZklinkSignature { inner: signature }) - } - #[wasm_bindgen] - pub fn verify(&self, msg: &[u8]) -> Result { - Ok(self.inner.verify_musig(msg)?) - } -} diff --git a/bindings/wasm/src/error.rs b/bindings/wasm/src/error.rs deleted file mode 100644 index 310fed7c..00000000 --- a/bindings/wasm/src/error.rs +++ /dev/null @@ -1,55 +0,0 @@ -use thiserror::Error; -use zklink_sdk_provider::RpcError; -use zklink_sdk_signers::eth_signer::error::EthSignerError; -use zklink_sdk_signers::zklink_signer::error::ZkSignerError; -use zklink_sdk_types::basic_types::ChainId; -use wasm_bindgen::JsValue; - -#[derive(Debug, Error)] -pub enum ClientError { - #[error("Network '{0}' is not supported")] - NetworkNotSupported(ChainId), - #[error("Unable to decode server response: {0}")] - MalformedResponse(String), - #[error("RPC error: {0:?}")] - RpcError(#[from] RpcError), - #[error("Network error: {0}")] - NetworkError(String), - - #[error("Provided account credentials are incorrect")] - IncorrectCredentials, - #[error("Seed too short, must be at least 32 bytes long")] - SeedTooShort, - #[error("Token is not supported by zkLink")] - UnknownToken, - #[error("Incorrect address")] - IncorrectAddress, - - #[error("Operation timeout")] - OperationTimeout, - #[error("Polling interval is too small")] - PollingIntervalIsTooSmall, - - #[error("EthSigning error: {0}")] - EthSigningError(#[from] EthSignerError), - #[error("ZkSigning error: {0}")] - ZkSigningError(#[from] ZkSignerError), - #[error("Missing required field for a transaction: {0}")] - MissingRequiredField(String), - - #[error("Ethereum private key was not provided for this wallet")] - NoEthereumPrivateKey, - - #[error("Provided value is not packable")] - NotPackableValue, - #[error("Non-zero subAccountId required submitter signer")] - MissSubmitterSigner, - #[error("Incorrect tx format")] - IncorrectTx, -} - -impl From for JsValue { - fn from(error: ClientError) -> Self { - JsValue::from_str(&format!("error: {error}")) - } -} diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index c93b8562..5894b515 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -1,8 +1,7 @@ -#[cfg(target_arch = "wasm32")] -pub mod crypto; -// #[cfg(not(target_arch = "wasm32"))] -// pub mod wallet; -// #[cfg(not(target_arch = "wasm32"))] -// pub mod error; +#![cfg(target_arch = "wasm32")] +pub mod rpc_client; +pub mod rpc_type_converter; +pub mod signer; +pub mod tx_types; extern crate getrandom; diff --git a/bindings/wasm/src/rpc_client.rs b/bindings/wasm/src/rpc_client.rs new file mode 100644 index 00000000..83304a1a --- /dev/null +++ b/bindings/wasm/src/rpc_client.rs @@ -0,0 +1,336 @@ +use crate::rpc_type_converter::{AccountQuery, TxLayer1Signature}; +use getrandom::getrandom; +use jsonrpsee::core::params::ArrayParams; +use jsonrpsee::core::traits::ToRpcParams; +use jsonrpsee::types::request::Request; +use jsonrpsee::types::Id; +use serde::Deserialize; +use std::collections::HashMap; +use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use zklink_sdk_provider::error::RpcError; +use zklink_sdk_provider::network::Network; +use zklink_sdk_provider::response::{ + AccountInfoResp, AccountQuery as RpcAccountQuery, AccountSnapshotResp, BlockNumberResp, + BlockOnChainResp, BlockResp, ChainResp, FastWithdrawTxResp, ForwardTxResp, Page, + SubAccountBalances, SubAccountOrders, TokenResp, TxHashOrDetailResp, TxResp, ZkLinkTxHistory, +}; +use zklink_sdk_signers::zklink_signer::ZkLinkSignature; +use zklink_sdk_types::basic_types::bigunit_wrapper::BigUintSerdeWrapper; +use zklink_sdk_types::basic_types::tx_hash::TxHash; +use zklink_sdk_types::basic_types::{AccountId, BlockNumber, ChainId, SubAccountId, TokenId}; +use zklink_sdk_types::prelude::ZkLinkAddress; +use zklink_sdk_types::signatures::TxLayer1Signature as TypesTxLayer1Signature; +use zklink_sdk_types::tx_type::zklink_tx::ZkLinkTx; +use zklink_sdk_types::tx_type::zklink_tx::ZkLinkTxType; + +#[derive(Deserialize, Clone)] +pub struct ErrMsg { + #[allow(dead_code)] + code: i32, + message: String, +} + +macro_rules! rpc_request { + ($method:expr,$builder:expr, $server_url:expr, $resp_type: ty) => {{ + let params = $builder + .to_rpc_params() + .map_err(RpcError::ParseParamsError)?; + let request = Request::new( + $method.into(), + params.as_ref().map(|p| p.as_ref()), + Id::Str(uuid_str().into()), + ); + let res = reqwest::Client::new() + .post($server_url) + .json(&request) + .send() + .await + .map_err(RpcError::RequestError)? + .json::>() + .await + .map_err(RpcError::ResponseError)?; + if let Some(&ref result) = res.get("result") { + let resp = serde_json::from_value::<$resp_type>(result.clone()); + match resp { + Ok(resp) => Ok(serde_wasm_bindgen::to_value(&resp)?), + Err(e) => Err(RpcError::ParseJsonError(e.to_string()).into()), + } + } else if let Some(&ref error) = res.get("error") { + let err_msg = serde_json::from_value::(error.clone()); + match err_msg { + Ok(msg) => Err(RpcError::GetErrorResult(msg.message).into()), + Err(_) => Err(RpcError::GetErrorResult("Other error response".to_string()).into()), + } + } else { + Err(RpcError::GetErrorResult("Other error response".to_string()).into()) + } + }}; +} + +pub fn uuid_str() -> String { + let mut bytes = [0; 16]; + getrandom(&mut bytes).expect("RNG failure!"); + + let uuid = uuid::Builder::from_bytes(bytes) + .set_variant(uuid::Variant::RFC4122) + .set_version(uuid::Version::Random) + .build(); + + uuid.to_string() +} + +#[wasm_bindgen] +pub struct RpcClient { + server_url: String, +} + +#[wasm_bindgen] +impl RpcClient { + #[wasm_bindgen(constructor)] + pub fn new(network: &str) -> RpcClient { + RpcClient { + server_url: Network::from_str(network).unwrap().url().to_owned(), + } + } + + #[wasm_bindgen(js_name=getSupportTokens)] + pub async fn tokens(&self) -> Result { + let builder = ArrayParams::new(); + rpc_request!("getSupportTokens",builder,&self.server_url,HashMap) + } + + #[wasm_bindgen(js_name=getAccountSnapshot)] + pub async fn account_query( + &self, + account_query: AccountQuery, + sub_account_id: Option, + block_number: Option, + ) -> Result { + let mut builder = ArrayParams::new(); + let _ = builder.insert(RpcAccountQuery::from(account_query)); + let _ = builder.insert(sub_account_id.map(|id| SubAccountId(id))); + let _ = builder.insert(block_number.map(|number| BlockNumber(number))); + rpc_request!( + "getAccountSnapshot", + builder, + &self.server_url, + AccountSnapshotResp + ) + } + + #[wasm_bindgen(js_name=sendTransaction)] + pub async fn send_transaction( + &self, + tx: JsValue, + l1_signature: Option, + l2_signature: Option, + ) -> Result { + let mut builder = ArrayParams::new(); + let zklink_tx: ZkLinkTx = serde_wasm_bindgen::from_value(tx)?; + let _ = builder.insert(zklink_tx); + let _ = builder.insert(l1_signature.map(|t| TypesTxLayer1Signature::from(t))); + let _ = builder.insert(l2_signature.map(|s| ZkLinkSignature::from_hex(&s).unwrap())); + rpc_request!("sendTransaction", builder, &self.server_url, TxHash) + } + + #[wasm_bindgen(js_name=getSupportChains)] + pub async fn get_support_chains(&self) -> Result { + let builder = ArrayParams::new(); + rpc_request!( + "getSupportChains", + builder, + &self.server_url, + Vec + ) + } + + #[wasm_bindgen(js_name=getLatestBlockNumber)] + pub async fn block_info(&self) -> Result { + let builder = ArrayParams::new(); + rpc_request!( + "getLatestBlockNumber", + builder, + &self.server_url, + BlockNumberResp + ) + } + + #[wasm_bindgen(js_name=getBlockByNumber)] + pub async fn block_detail( + &self, + block_number: Option, + include_tx: bool, + include_update: bool, + ) -> Result { + let mut builder = ArrayParams::new(); + let _ = builder.insert(block_number.map(|b| BlockNumber(b))); + let _ = builder.insert(include_tx); + let _ = builder.insert(include_update); + rpc_request!("getBlockByNumber", builder, &self.server_url, BlockResp) + } + + #[wasm_bindgen(js_name=getPendingBlock)] + pub async fn pending_block_detail( + &self, + last_tx_timestamp_micro: u64, + include_tx: bool, + include_update: bool, + limit: Option, + ) -> Result { + let mut builder = ArrayParams::new(); + let _ = builder.insert(last_tx_timestamp_micro); + let _ = builder.insert(include_tx); + let _ = builder.insert(include_update); + let _ = builder.insert(limit); + rpc_request!( + "getPendingBlock", + builder, + &self.server_url, + Vec + ) + } + + #[wasm_bindgen(js_name=getBlockOnChainByNumber)] + pub async fn block_onchain_detail(&self, block_number: u32) -> Result { + let mut builder = ArrayParams::new(); + let _ = builder.insert(BlockNumber(block_number)); + rpc_request!( + "getBlockOnChainByNumber", + builder, + &self.server_url, + BlockOnChainResp + ) + } + + #[wasm_bindgen(js_name=getAccount)] + pub async fn account_info(&self, account_query: AccountQuery) -> Result { + let mut builder = ArrayParams::new(); + let _ = builder.insert(RpcAccountQuery::from(account_query)); + rpc_request!("getAccount", builder, &self.server_url, AccountInfoResp) + } + + #[wasm_bindgen(js_name=getAccountBalances)] + pub async fn account_balances( + &self, + account_id: u32, + sub_account_id: Option, + ) -> Result { + let mut builder = ArrayParams::new(); + let _ = builder.insert(AccountId(account_id)); + let _ = builder.insert(sub_account_id.map(|id| SubAccountId(id))); + rpc_request!( + "getAccountBalances", + builder, + &self.server_url, + SubAccountBalances + ) + } + + #[wasm_bindgen(js_name=getAccountOrderSlots)] + pub async fn account_order_slots( + &self, + account_id: u32, + sub_account_id: Option, + ) -> Result { + let mut builder = ArrayParams::new(); + let _ = builder.insert(AccountId(account_id)); + let _ = builder.insert(sub_account_id.map(|id| SubAccountId(id))); + rpc_request!( + "getAccountOrderSlots", + builder, + &self.server_url, + SubAccountOrders + ) + } + + #[wasm_bindgen(js_name=getTokenReserve)] + pub async fn token_remain(&self, token_id: u32, mapping: bool) -> Result { + let mut builder = ArrayParams::new(); + let _ = builder.insert(TokenId(token_id)); + let _ = builder.insert(mapping); + rpc_request!("getTokenReserve",builder,&self.server_url,HashMap) + } + + #[wasm_bindgen(js_name=getTransactionByHash)] + pub async fn tx_info(&self, hash: String, include_update: bool) -> Result { + let hash = TxHash::from_hex(&hash).map_err(|_e| RpcError::InvalidInputParameter)?; + let mut builder = ArrayParams::new(); + let _ = builder.insert(hash); + let _ = builder.insert(include_update); + rpc_request!("getTransactionByHash", builder, &self.server_url, TxResp) + } + + #[wasm_bindgen(js_name=getAccountTransactionHistory)] + pub async fn tx_history( + &self, + tx_type: ZkLinkTxType, + address: String, + page_index: u64, + page_size: u32, + ) -> Result { + let address = + ZkLinkAddress::from_hex(&address).map_err(|_e| RpcError::InvalidInputParameter)?; + let mut builder = ArrayParams::new(); + let _ = builder.insert(tx_type); + let _ = builder.insert(address); + let _ = builder.insert(page_index); + let _ = builder.insert(page_size); + rpc_request!( + "getAccountTransactionHistory", + builder, + &self.server_url, + Page + ) + } + + #[wasm_bindgen(js_name=getFastWithdrawTxs)] + pub async fn tx_fast_withdraw( + &self, + last_tx_timestamp: u64, + max_txs: u32, + ) -> Result { + let mut builder = ArrayParams::new(); + let _ = builder.insert(last_tx_timestamp); + let _ = builder.insert(max_txs); + rpc_request!( + "getFastWithdrawTxs", + builder, + &self.server_url, + Vec + ) + } + + #[wasm_bindgen(js_name=pullForwardTxs)] + pub async fn pull_forward_txs( + &self, + sub_account_id: u8, + offset_id: i64, + limit: i64, + ) -> Result { + let mut builder = ArrayParams::new(); + let _ = builder.insert(SubAccountId(sub_account_id)); + let _ = builder.insert(offset_id); + let _ = builder.insert(limit); + rpc_request!( + "pullForwardTxs", + builder, + &self.server_url, + Vec + ) + } + + #[wasm_bindgen(js_name=confirmFullExit)] + pub async fn confirm_full_exit( + &self, + tx_hash: String, + submitter_signature: String, + ) -> Result { + let hash = TxHash::from_hex(&tx_hash).map_err(|_e| RpcError::InvalidInputParameter)?; + let mut builder = ArrayParams::new(); + let _ = builder.insert(hash); + let _ = builder.insert(submitter_signature); + rpc_request!("confirmFullExit", builder, &self.server_url, bool) + } +} diff --git a/bindings/wasm/src/rpc_type_converter.rs b/bindings/wasm/src/rpc_type_converter.rs new file mode 100644 index 00000000..651cf904 --- /dev/null +++ b/bindings/wasm/src/rpc_type_converter.rs @@ -0,0 +1,122 @@ +use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use zklink_sdk_provider::response::AccountQuery as RpcAccountQuery; +use zklink_sdk_signers::eth_signer::{EIP1271Signature, PackedEthSignature}; +use zklink_sdk_signers::starknet_signer::StarkECDSASignature; +use zklink_sdk_types::basic_types::AccountId; +use zklink_sdk_types::prelude::ZkLinkAddress; +use zklink_sdk_types::signatures::TxLayer1Signature as TypesTxLayer1Signature; +use zklink_sdk_types::tx_type::change_pubkey::ChangePubKey; +use zklink_sdk_types::tx_type::transfer::Transfer; +use zklink_sdk_types::tx_type::zklink_tx::ZkLinkTx as TypesZkLinkTx; + +#[wasm_bindgen] +#[derive(Copy, Clone)] +pub enum AccountQueryType { + AccountId, + Address, +} + +#[wasm_bindgen] +#[derive(Copy, Clone)] +pub enum L1SignatureType { + Eth, + Eip1271, + Stark, +} + +#[wasm_bindgen] +pub struct AccountQuery { + query_type: AccountQueryType, + query_param: String, +} + +#[wasm_bindgen] +pub struct TxLayer1Signature { + sign_type: L1SignatureType, + signature: String, +} + +#[wasm_bindgen] +pub struct ZkLinkTx { + tx_type: u8, + tx: JsValue, +} + +#[wasm_bindgen] +impl AccountQuery { + #[wasm_bindgen(constructor)] + pub fn new(query_type: AccountQueryType, query_param: String) -> AccountQuery { + AccountQuery { + query_type, + query_param, + } + } +} + +impl From for RpcAccountQuery { + fn from(query: AccountQuery) -> RpcAccountQuery { + match query.query_type { + AccountQueryType::AccountId => { + RpcAccountQuery::Id(AccountId(query.query_param.parse::().unwrap())) + } + AccountQueryType::Address => { + RpcAccountQuery::Address(ZkLinkAddress::from_str(&query.query_param).unwrap()) + } + } + } +} + +#[wasm_bindgen] +impl TxLayer1Signature { + #[wasm_bindgen] + pub fn new(sign_type: L1SignatureType, signature: String) -> TxLayer1Signature { + TxLayer1Signature { + sign_type, + signature, + } + } +} + +impl From for TypesTxLayer1Signature { + fn from(signature: TxLayer1Signature) -> TypesTxLayer1Signature { + match signature.sign_type { + L1SignatureType::Eth => TypesTxLayer1Signature::EthereumSignature( + PackedEthSignature::from_hex(&signature.signature).unwrap(), + ), + L1SignatureType::Eip1271 => TypesTxLayer1Signature::EIP1271Signature(EIP1271Signature( + hex::decode(signature.signature).unwrap(), + )), + L1SignatureType::Stark => TypesTxLayer1Signature::StarkSignature(StarkECDSASignature( + hex::decode(signature.signature).unwrap(), + )), + } + } +} + +#[wasm_bindgen] +impl ZkLinkTx { + #[wasm_bindgen(constructor)] + pub fn new(tx_type: u8, tx: JsValue) -> ZkLinkTx { + ZkLinkTx { tx_type, tx } + } +} + +impl From for TypesZkLinkTx { + fn from(tx: ZkLinkTx) -> TypesZkLinkTx { + match tx.tx_type { + ChangePubKey::TX_TYPE => { + let change_pubkey: ChangePubKey = serde_wasm_bindgen::from_value(tx.tx).unwrap(); + TypesZkLinkTx::ChangePubKey(Box::new(change_pubkey)) + } + Transfer::TX_TYPE => { + let transfer: Transfer = serde_wasm_bindgen::from_value(tx.tx).unwrap(); + TypesZkLinkTx::Transfer(Box::new(transfer)) + } + _ => { + panic!("Not support tx type!") + } + } + } +} diff --git a/bindings/wasm/src/signer.rs b/bindings/wasm/src/signer.rs new file mode 100644 index 00000000..8e442339 --- /dev/null +++ b/bindings/wasm/src/signer.rs @@ -0,0 +1,116 @@ +use crate::tx_types::change_pubkey::{ChangePubKey, Create2Data}; +use crate::tx_types::forced_exit::ForcedExit; +use crate::tx_types::order_matching::{Order, OrderMatching}; +use crate::tx_types::transfer::Transfer; +use crate::tx_types::withdraw::Withdraw; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use zklink_sdk_interface::signer::Signer as InterfaceSigner; +use zklink_sdk_types::basic_types::ZkLinkAddress; +use zklink_sdk_types::tx_type::change_pubkey::ChangePubKey as TxChangePubKey; +use zklink_sdk_types::tx_type::change_pubkey::Create2Data as ChangePubKeyCreate2Data; +use zklink_sdk_types::tx_type::forced_exit::ForcedExit as TxForcedExit; +use zklink_sdk_types::tx_type::order_matching::{ + Order as TxOrder, OrderMatching as TxOrderMatching, +}; +use zklink_sdk_types::tx_type::transfer::Transfer as TxTransfer; +use zklink_sdk_types::tx_type::withdraw::Withdraw as TxWithdraw; +use zklink_sdk_types::tx_type::zklink_tx::ZkLinkTx; + +#[wasm_bindgen] +pub struct Signer { + inner: InterfaceSigner, +} + +#[wasm_bindgen] +impl Signer { + #[wasm_bindgen(constructor)] + pub fn new(private_key: &str) -> Result { + let inner = InterfaceSigner::new(private_key)?; + Ok(Signer { inner }) + } + + #[wasm_bindgen(js_name=signChangePubkeyWithEthEcdsaAuth)] + pub fn sign_change_pubkey_with_eth_ecdsa_auth( + &self, + tx: ChangePubKey, + l1_client_id: u32, + main_contract: &str, + ) -> Result { + let inner_tx = tx.get_inner_tx()?; + let change_pubkey: TxChangePubKey = serde_wasm_bindgen::from_value(inner_tx)?; + let contract_address = ZkLinkAddress::from_hex(main_contract)?; + let signature = self.inner.sign_change_pubkey_with_eth_ecdsa_auth( + change_pubkey, + l1_client_id, + contract_address, + )?; + Ok(serde_wasm_bindgen::to_value(&signature)?) + } + + #[wasm_bindgen(js_name=signChangePubkeyWithCreate2DataAuth)] + pub fn sign_change_pubkey_with_create2data_auth( + &self, + tx: ChangePubKey, + create2_data: Create2Data, + from_account: String, + ) -> Result { + let inner_tx = tx.get_inner_tx()?; + let change_pubkey: TxChangePubKey = serde_wasm_bindgen::from_value(inner_tx)?; + let inner_data = create2_data.get_inner_data()?; + let create2_data: ChangePubKeyCreate2Data = serde_wasm_bindgen::from_value(inner_data)?; + let signature = self.inner.sign_change_pubkey_with_create2data_auth( + change_pubkey, + create2_data, + ZkLinkAddress::from_hex(&from_account)?, + )?; + Ok(serde_wasm_bindgen::to_value(&signature)?) + } + + #[wasm_bindgen(js_name=signTransfer)] + pub fn sign_transfer(&self, tx: Transfer, token_symbol: &str) -> Result { + let inner_tx = tx.get_inner_tx()?; + let transfer: TxTransfer = serde_wasm_bindgen::from_value(inner_tx)?; + let signature = self.inner.sign_transfer(transfer, token_symbol)?; + Ok(serde_wasm_bindgen::to_value(&signature)?) + } + + #[wasm_bindgen(js_name=createSignedOrder)] + pub fn create_signed_order(&self, order: Order) -> Result { + let inner_order = order.get_inner_order()?; + let mut order: TxOrder = serde_wasm_bindgen::from_value(inner_order)?; + let signed_order = self.inner.sign_order(&mut order)?; + Ok(serde_wasm_bindgen::to_value(&signed_order)?) + } + + #[wasm_bindgen(js_name=signOrderMatching)] + pub fn sign_order_matching(&self, tx: OrderMatching) -> Result { + let inner_tx = tx.get_inner_tx()?; + let order_matching: TxOrderMatching = serde_wasm_bindgen::from_value(inner_tx)?; + let signature = self.inner.sign_order_matching(order_matching)?; + Ok(serde_wasm_bindgen::to_value(&signature)?) + } + + #[wasm_bindgen(js_name=signWithdraw)] + pub fn sign_withdraw(&self, tx: Withdraw, token_symbol: &str) -> Result { + let inner_tx = tx.get_inner_tx()?; + let withdraw: TxWithdraw = serde_wasm_bindgen::from_value(inner_tx)?; + let signature = self.inner.sign_withdraw(withdraw, token_symbol)?; + Ok(serde_wasm_bindgen::to_value(&signature)?) + } + + #[wasm_bindgen(js_name=signForcedExit)] + pub fn sign_forced_exit(&self, tx: ForcedExit) -> Result { + let inner_tx = tx.get_inner_tx()?; + let forced_exit: TxForcedExit = serde_wasm_bindgen::from_value(inner_tx)?; + let signature = self.inner.sign_forced_exit(forced_exit)?; + Ok(serde_wasm_bindgen::to_value(&signature)?) + } + + #[wasm_bindgen(js_name=submitterSignature)] + pub fn submitter_signature(&self, tx: JsValue) -> Result { + let zklink_tx: ZkLinkTx = serde_wasm_bindgen::from_value(tx)?; + let zklink_signature = self.inner.submitter_signature(&zklink_tx)?; + Ok(zklink_signature.as_hex()) + } +} diff --git a/bindings/wasm/src/tx_types/change_pubkey.rs b/bindings/wasm/src/tx_types/change_pubkey.rs new file mode 100644 index 00000000..49124548 --- /dev/null +++ b/bindings/wasm/src/tx_types/change_pubkey.rs @@ -0,0 +1,126 @@ +use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use zklink_sdk_signers::eth_signer::packed_eth_signature::PackedEthSignature; +use zklink_sdk_signers::zklink_signer::pubkey_hash::PubKeyHash; +use zklink_sdk_types::basic_types::{BigUint, ZkLinkAddress}; +use zklink_sdk_types::error::TypeError; +use zklink_sdk_types::prelude::H256; +use zklink_sdk_types::tx_builder::ChangePubKeyBuilder as TxChangePubKeyBuilder; +use zklink_sdk_types::tx_type::change_pubkey::{ + ChangePubKey as ChangePubkeyTx, Create2Data as ChangePubKeyCreate2Data, +}; + +#[wasm_bindgen] +pub enum EthAuthType { + OnChain, + EthECDSA, + EthCREATE2, +} + +#[wasm_bindgen] +pub struct Create2Data { + inner: ChangePubKeyCreate2Data, +} + +#[wasm_bindgen] +pub struct ChangePubKey { + inner: ChangePubkeyTx, +} + +#[wasm_bindgen] +impl Create2Data { + #[wasm_bindgen(constructor)] + pub fn new(creator_address: &str, salt: &str, code_hash: &str) -> Result { + let create2_data = ChangePubKeyCreate2Data { + creator_address: ZkLinkAddress::from_hex(creator_address)?, + salt_arg: H256::from_str(&salt) + .map_err(|e| TypeError::DecodeFromHexErr(e.to_string()))?, + code_hash: H256::from_str(code_hash) + .map_err(|e| TypeError::DecodeFromHexErr(e.to_string()))?, + }; + Ok(Create2Data { + inner: create2_data, + }) + } + + #[wasm_bindgen] + pub fn salt(&self, pubkey_hash: &str) -> String { + let salt_bytes = self.inner.salt(pubkey_hash.as_bytes()); + hex::encode(salt_bytes) + } + + pub fn get_inner_data(&self) -> Result { + Ok(serde_wasm_bindgen::to_value(&self.inner)?) + } +} + +#[wasm_bindgen] +impl ChangePubKey { + pub fn get_inner_tx(&self) -> Result { + Ok(serde_wasm_bindgen::to_value(&self.inner)?) + } + + pub fn get_change_pubkey_message( + &self, + layer_one_chain_id: u32, + verifying_contract: String, + ) -> Result { + let contract = ZkLinkAddress::from_str(&verifying_contract)?; + let typed_data = self + .inner + .to_eip712_request_payload(layer_one_chain_id, &contract)?; + Ok(typed_data.raw_data) + } +} + +#[wasm_bindgen] +pub struct ChangePubKeyBuilder { + inner: TxChangePubKeyBuilder, +} + +#[wasm_bindgen] +impl ChangePubKeyBuilder { + #[wasm_bindgen(constructor)] + pub fn new( + chain_id: u8, + account_id: u32, + sub_account_id: u8, + new_pubkey_hash: String, + fee_token: u16, + fee: String, + nonce: u32, + eth_signature: Option, + timestamp: u32, + ) -> Result { + let eth_signature = if let Some(s) = eth_signature { + Some(PackedEthSignature::from_hex(&s)?) + } else { + None + }; + let inner = TxChangePubKeyBuilder { + chain_id: chain_id.into(), + account_id: account_id.into(), + sub_account_id: sub_account_id.into(), + new_pubkey_hash: PubKeyHash::from_hex(&new_pubkey_hash)?, + fee_token: fee_token.into(), + fee: BigUint::from_str(&fee).unwrap(), + nonce: nonce.into(), + eth_signature, + timestamp: timestamp.into(), + }; + Ok(ChangePubKeyBuilder { inner }) + } + + #[wasm_bindgen] + pub fn build(self) -> ChangePubKey { + ChangePubKey { + inner: ChangePubkeyTx::new(self.inner), + } + } +} + +#[wasm_bindgen(js_name=newChangePubkey)] +pub fn new_change_pubkey(builder: ChangePubKeyBuilder) -> ChangePubKey { + builder.build() +} diff --git a/bindings/wasm/src/tx_types/forced_exit.rs b/bindings/wasm/src/tx_types/forced_exit.rs new file mode 100644 index 00000000..901f997a --- /dev/null +++ b/bindings/wasm/src/tx_types/forced_exit.rs @@ -0,0 +1,66 @@ +use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use zklink_sdk_types::basic_types::{BigUint, ZkLinkAddress}; +use zklink_sdk_types::tx_builder::ForcedExitBuilder as TxForcedExitBuilder; +use zklink_sdk_types::tx_type::forced_exit::ForcedExit as ForcedExitTx; + +#[wasm_bindgen] +pub struct ForcedExit { + inner: ForcedExitTx, +} + +#[wasm_bindgen] +impl ForcedExit { + pub fn get_inner_tx(&self) -> Result { + Ok(serde_wasm_bindgen::to_value(&self.inner)?) + } +} + +#[wasm_bindgen] +pub struct ForcedExitBuilder { + inner: TxForcedExitBuilder, +} + +#[wasm_bindgen] +impl ForcedExitBuilder { + #[wasm_bindgen(constructor)] + pub fn new( + to_chain_id: u8, + initiator_account_id: u32, + initiator_sub_account_id: u8, + target_sub_account_id: u8, + target: String, + l2_source_token: u32, + l1_target_token: u32, + exit_amount: String, + initiator_nonce: u32, + ts: u32, + ) -> Result { + let inner = TxForcedExitBuilder { + to_chain_id: to_chain_id.into(), + initiator_account_id: initiator_account_id.into(), + initiator_sub_account_id: initiator_sub_account_id.into(), + target: ZkLinkAddress::from_hex(&target)?, + l2_source_token: l2_source_token.into(), + timestamp: ts.into(), + l1_target_token: l1_target_token.into(), + initiator_nonce: initiator_nonce.into(), + target_sub_account_id: target_sub_account_id.into(), + exit_amount: BigUint::from_str(&exit_amount).unwrap(), + }; + Ok(ForcedExitBuilder { inner }) + } + + #[wasm_bindgen] + pub fn build(self) -> ForcedExit { + ForcedExit { + inner: ForcedExitTx::new(self.inner), + } + } +} + +#[wasm_bindgen(js_name=newForcedExit)] +pub fn new_forced_exit(builder: ForcedExitBuilder) -> ForcedExit { + builder.build() +} diff --git a/bindings/wasm/src/tx_types/mod.rs b/bindings/wasm/src/tx_types/mod.rs new file mode 100644 index 00000000..bcb8e962 --- /dev/null +++ b/bindings/wasm/src/tx_types/mod.rs @@ -0,0 +1,5 @@ +pub mod change_pubkey; +pub mod forced_exit; +pub mod order_matching; +pub mod transfer; +pub mod withdraw; diff --git a/bindings/wasm/src/tx_types/order_matching.rs b/bindings/wasm/src/tx_types/order_matching.rs new file mode 100644 index 00000000..c27ae35e --- /dev/null +++ b/bindings/wasm/src/tx_types/order_matching.rs @@ -0,0 +1,108 @@ +use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use zklink_sdk_types::basic_types::BigUint; +use zklink_sdk_types::tx_builder::OrderMatchingBuilder as TxOrderMatchingBuilder; +use zklink_sdk_types::tx_type::order_matching::{ + Order as OrderTx, OrderMatching as OrderMatchingTx, +}; +#[wasm_bindgen] +pub struct Order { + inner: OrderTx, +} + +#[wasm_bindgen] +pub struct OrderMatching { + inner: OrderMatchingTx, +} +#[wasm_bindgen] +impl Order { + #[wasm_bindgen(constructor)] + pub fn new( + account_id: u32, + sub_account_id: u8, + slot_id: u32, + nonce: u32, + base_token_id: u32, + quote_token_id: u32, + amount: String, + price: String, + is_sell: bool, + fee_ratio1: u8, + fee_ratio2: u8, + ) -> Order { + Order { + inner: OrderTx { + account_id: account_id.into(), + sub_account_id: sub_account_id.into(), + slot_id: slot_id.into(), + nonce: nonce.into(), + base_token_id: base_token_id.into(), + quote_token_id: quote_token_id.into(), + amount: BigUint::from_str(&amount).unwrap(), + price: BigUint::from_str(&price).unwrap(), + is_sell: is_sell as u8, + fee_ratio1, + fee_ratio2, + signature: Default::default(), + }, + } + } + + pub fn get_inner_order(&self) -> Result { + Ok(serde_wasm_bindgen::to_value(&self.inner)?) + } +} + +#[wasm_bindgen] +impl OrderMatching { + pub fn get_inner_tx(&self) -> Result { + Ok(serde_wasm_bindgen::to_value(&self.inner)?) + } +} + +#[wasm_bindgen] +pub struct OrderMatchingBuilder { + inner: TxOrderMatchingBuilder, +} + +#[wasm_bindgen] +impl OrderMatchingBuilder { + #[wasm_bindgen(constructor)] + pub fn new( + account_id: u32, + sub_account_id: u8, + taker: JsValue, + maker: JsValue, + fee: String, + fee_token: u32, + expect_base_amount: String, + expect_quote_amount: String, + ) -> Result { + let maker: OrderTx = serde_wasm_bindgen::from_value(maker)?; + let taker: OrderTx = serde_wasm_bindgen::from_value(taker)?; + let inner = TxOrderMatchingBuilder { + account_id: account_id.into(), + sub_account_id: sub_account_id.into(), + taker, + fee: BigUint::from_str(&fee).unwrap(), + fee_token: fee_token.into(), + expect_base_amount: BigUint::from_str(&expect_base_amount).unwrap(), + maker, + expect_quote_amount: BigUint::from_str(&expect_quote_amount).unwrap(), + }; + Ok(OrderMatchingBuilder { inner }) + } + + #[wasm_bindgen] + pub fn build(self) -> OrderMatching { + OrderMatching { + inner: OrderMatchingTx::new(self.inner), + } + } +} + +#[wasm_bindgen(js_name=newOrderMatching)] +pub fn new_order_matching(builder: OrderMatchingBuilder) -> OrderMatching { + builder.build() +} diff --git a/bindings/wasm/src/tx_types/transfer.rs b/bindings/wasm/src/tx_types/transfer.rs new file mode 100644 index 00000000..4b24c00a --- /dev/null +++ b/bindings/wasm/src/tx_types/transfer.rs @@ -0,0 +1,64 @@ +use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use zklink_sdk_types::basic_types::{BigUint, ZkLinkAddress}; +use zklink_sdk_types::tx_builder::TransferBuilder as TxTransferBuilder; +use zklink_sdk_types::tx_type::transfer::Transfer as TransferTx; + +#[wasm_bindgen] +pub struct Transfer { + inner: TransferTx, +} + +#[wasm_bindgen] +impl Transfer { + pub fn get_inner_tx(&self) -> Result { + Ok(serde_wasm_bindgen::to_value(&self.inner)?) + } +} + +#[wasm_bindgen] +pub struct TransferBuilder { + inner: TxTransferBuilder, +} + +#[wasm_bindgen] +impl TransferBuilder { + #[wasm_bindgen(constructor)] + pub fn new( + account_id: u32, + to_address: String, + from_sub_account_id: u8, + to_sub_account_id: u8, + token: u32, + fee: String, + amount: String, + nonce: u32, + ts: u32, + ) -> Result { + let inner = TxTransferBuilder { + account_id: account_id.into(), + to_address: ZkLinkAddress::from_hex(&to_address)?, + from_sub_account_id: from_sub_account_id.into(), + to_sub_account_id: to_sub_account_id.into(), + token: token.into(), + fee: BigUint::from_str(&fee).unwrap(), + nonce: nonce.into(), + timestamp: ts.into(), + amount: BigUint::from_str(&amount).unwrap(), + }; + Ok(TransferBuilder { inner }) + } + + #[wasm_bindgen] + pub fn build(self) -> Transfer { + Transfer { + inner: TransferTx::new(self.inner), + } + } +} + +#[wasm_bindgen(js_name=newTransfer)] +pub fn new_transfer(builder: TransferBuilder) -> Transfer { + builder.build() +} diff --git a/bindings/wasm/src/tx_types/withdraw.rs b/bindings/wasm/src/tx_types/withdraw.rs new file mode 100644 index 00000000..5e19ade6 --- /dev/null +++ b/bindings/wasm/src/tx_types/withdraw.rs @@ -0,0 +1,70 @@ +use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use zklink_sdk_types::basic_types::{BigUint, ZkLinkAddress}; +use zklink_sdk_types::tx_builder::WithdrawBuilder as TxWithdrawBuilder; +use zklink_sdk_types::tx_type::withdraw::Withdraw as WithdrawTx; + +#[wasm_bindgen] +pub struct Withdraw { + inner: WithdrawTx, +} + +#[wasm_bindgen] +impl Withdraw { + pub fn get_inner_tx(&self) -> Result { + Ok(serde_wasm_bindgen::to_value(&self.inner)?) + } +} + +#[wasm_bindgen] +pub struct WithdrawBuilder { + inner: TxWithdrawBuilder, +} + +#[wasm_bindgen] +impl WithdrawBuilder { + #[wasm_bindgen(constructor)] + pub fn new( + account_id: u32, + sub_account_id: u8, + to_chain_id: u8, + to_address: String, + l2_source_token: u32, + fee: String, + fast_withdraw: bool, + withdraw_fee_ratio: u16, + l1_target_token: u32, + amount: String, + nonce: u32, + ts: u32, + ) -> Result { + let inner = TxWithdrawBuilder { + account_id: account_id.into(), + sub_account_id: sub_account_id.into(), + to_chain_id: to_chain_id.into(), + to_address: ZkLinkAddress::from_hex(&to_address)?, + l2_source_token: l2_source_token.into(), + fee: BigUint::from_str(&fee).unwrap(), + nonce: nonce.into(), + fast_withdraw, + withdraw_fee_ratio, + timestamp: ts.into(), + amount: BigUint::from_str(&amount).unwrap(), + l1_target_token: l1_target_token.into(), + }; + Ok(WithdrawBuilder { inner }) + } + + #[wasm_bindgen] + pub fn build(self) -> Withdraw { + Withdraw { + inner: WithdrawTx::new(self.inner), + } + } +} + +#[wasm_bindgen(js_name=newWithdraw)] +pub fn new_withdraw(builder: WithdrawBuilder) -> Withdraw { + builder.build() +} diff --git a/bindings/wasm/src/wallet.rs b/bindings/wasm/src/wallet.rs deleted file mode 100644 index 9b0a527f..00000000 --- a/bindings/wasm/src/wallet.rs +++ /dev/null @@ -1,114 +0,0 @@ -use crate::error::ClientError; -use crate::signer::Signer; -use crate::{AccountType, Wallet}; -use num::BigUint; -use zklink_sdk_interface::{ChangePubKeyAuthRequest, TxSignature}; -use zklink_sdk_provider::response::{AccountQuery, ChainResp, TokenResp, AccountInfoResp}; -use zklink_sdk_provider::rpc::ZkLinkRpcClient; -use zklink_sdk_signers::eth_signer::eth_signature::TxEthSignature; -use zklink_sdk_signers::zklink_signer::pk_signer::ZkLinkSigner; - -use zklink_sdk_signers::zklink_signer::pubkey_hash::PubKeyHash; -use zklink_sdk_signers::zklink_signer::signature::ZkLinkSignature; -use zklink_sdk_types::basic_types::params::MAIN_SUB_ACCOUNT_ID; -use zklink_sdk_types::basic_types::tx_hash::TxHash; -use zklink_sdk_types::basic_types::{AccountId, ChainId, Nonce, SubAccountId, TokenId, ZkLinkAddress}; -use zklink_sdk_types::tx_type::order_matching::Order; -use std::collections::HashMap; -use wasm_bindgen::JsValue; - -#[wasm_bindgen] -pub struct Wallet { - pub provider: ZkLinkRpcProvider, - pub address: ZklinkAddress, - pub account_info: AccountInfoResp, - pub account_type: AccountType, - pub chains: HashMap, - pub tokens: HashMap, -} - -#[wasm_bindgen] -impl Wallet { - #[wasm_bindgen] - pub async fn new( - provider: ZkLinkRpcProvider, - address: ZkLinkAddress, - account_type: AccountType, - ) -> Result { - let chains = provider.get_support_chains().await?; - let chains = chains.iter().map(|c| (c.chain_id, c.clone())).collect(); - let tokens = provider.tokens().await?; - let account_info = provider - .account_info(AccountQuery::Address(address.clone())) - .await?; - - let wallet = Wallet { - provider, - address, - account_info, - account_type, - chains, - tokens, - }; - - Ok(wallet) - } - - /// Returns the wallet address. - #[wasm_bindgen] - pub fn address(&self) -> ZkLinkAddress { - self.address.clone() - } - - /// Returns the current account id - #[wasm_bindgen] - pub fn account_id(&self) -> AccountId { - self.account_info.id - } - - /// Returns the current account pub key hash - #[wasm_bindgen] - pub fn account_pubkey_hash(&self) -> PubKeyHash { - self.account_info.pub_key_hash - } - - #[wasm_bindgen] - pub fn account_nonce(&self) -> Nonce { - self.account_info.nonce - } - - /// Updates account info stored in the wallet. - #[wasm_bindgen] - pub async fn update_account_info(&mut self) -> Result<(), JsValue> { - self.account_info = self - .provider - .account_info(AccountQuery::Address(self.address.clone())) - .await?; - - Ok(()) - } - - /// Returns `true` if signing key for account was set in zkLink network. - /// In other words, returns `true` if `ChangePubKey` operation was performed for the - /// account. - /// - /// If this method has returned `false`, one must send a `ChangePubKey` transaction - /// via `Wallet::start_change_pubkey` method. - #[wasm_bindgen] - pub async fn is_signing_key_set(&self) -> Result { - let signer_pub_key_hash = self.signer.pub_key_hash; - - let key_set = self.account_pubkey_hash() == signer_pub_key_hash; - Ok(key_set) - } - - #[wasm_bindgen] - pub fn get_chain(&self, chain_id: &ChainId) -> Option { - self.chains.get(chain_id).cloned() - } - - #[wasm_bindgen] - pub fn get_token(&self, token_id: &TokenId) -> Option { - self.tokens.get(token_id).cloned() - } -} diff --git a/bindings/wasm/tests/test_rpc.rs b/bindings/wasm/tests/test_rpc.rs new file mode 100644 index 00000000..607dc807 --- /dev/null +++ b/bindings/wasm/tests/test_rpc.rs @@ -0,0 +1,101 @@ +#![cfg(target_arch = "wasm32")] +use std::str::FromStr; +use wasm_bindgen::JsValue; +use wasm_bindgen_test::wasm_bindgen_test; +use wasm_bindgen_test::wasm_bindgen_test_configure; +use zklink_sdk_signers::eth_signer::EthSigner; +use zklink_sdk_signers::zklink_signer::{PubKeyHash, ZkLinkSigner}; +use zklink_sdk_types::basic_types::{AccountId, BigUint, ChainId, Nonce, TokenId, ZkLinkAddress}; +use zklink_sdk_types::prelude::{SubAccountId, TimeStamp}; +use zklink_sdk_types::tx_builder::ChangePubKeyBuilder; +use zklink_sdk_types::tx_type::change_pubkey::ChangePubKey; +use zklink_sdk_types::tx_type::zklink_tx::ZkLinkTx as TypesZkLinkTx; +use zklink_sdk_types::tx_type::ZkSignatureTrait; +use zklink_sdk_wasm::rpc_client::RpcClient; +use zklink_sdk_wasm::rpc_type_converter::{AccountQuery, AccountQueryType}; + +wasm_bindgen_test_configure!(run_in_worker); +#[wasm_bindgen_test] +async fn test_get_tokens() { + let client = RpcClient::new("testnet"); + let ret = client.tokens().await; + if let Err(e) = ret { + web_sys::console::log_1(&JsValue::from_str(&format!("{:?}", e))); + } else { + web_sys::console::log_1(&JsValue::from_str(&format!("{:?}", ret.unwrap()))); + } + // assert!(ret.is_err()); +} + +#[wasm_bindgen_test] +async fn test_account_query() { + let client = RpcClient::new("testnet"); + let account_id = AccountQuery::new(AccountQueryType::AccountId, "5".to_string()); + let account_resp = client.account_query(account_id.into(), None, None).await; + if let Err(e) = account_resp { + web_sys::console::log_1(&JsValue::from_str(&format!("{:?}", e))); + } else { + web_sys::console::log_1(&JsValue::from_str(&format!("{:?}", account_resp.unwrap()))); + } +} + +#[wasm_bindgen_test] +async fn test_send_change_pubkey() { + let client = RpcClient::new("devnet"); + let private_key = "be725250b123a39dab5b7579334d5888987c72a58f4508062545fe6e08ca94f4"; + let eth_signer = EthSigner::try_from(private_key).unwrap(); + let zklink_signer = ZkLinkSigner::new_from_hex_eth_signer(private_key).unwrap(); + let main_contract = "0x5505a8cD4594Dbf79d8C59C0Df1414AB871CA896"; + let l1_client_id = 80001; + let new_pubkey_hash = "0xd8d5fb6a6caef06aa3dc2abdcdc240987e5330fe"; + let ts = 1696595303; + //auth type 'ECDSA' + let builder = ChangePubKeyBuilder { + chain_id: ChainId(1), + account_id: AccountId(10), + sub_account_id: SubAccountId(1), + new_pubkey_hash: PubKeyHash::from_hex(new_pubkey_hash).unwrap(), + fee_token: TokenId(18), + fee: BigUint::from(100000000000000u64), + nonce: Nonce(1), + eth_signature: None, + timestamp: TimeStamp(ts), + }; + let change_pubkey = ChangePubKey::new(builder); + let message = change_pubkey + .to_eip712_request_payload( + l1_client_id, + &ZkLinkAddress::from_str(&main_contract).unwrap(), + ) + .unwrap(); + let signature = eth_signer + .sign_message(message.raw_data.as_bytes()) + .unwrap(); + let builder_with_sig = ChangePubKeyBuilder { + chain_id: ChainId(1), + account_id: AccountId(10), + sub_account_id: SubAccountId(1), + new_pubkey_hash: PubKeyHash::from_hex(new_pubkey_hash).unwrap(), + fee_token: TokenId(18), + fee: BigUint::from(100000000000000u64), + nonce: Nonce(1), + eth_signature: Some(signature), + timestamp: TimeStamp(ts), + }; + let mut tx = ChangePubKey::new(builder_with_sig); + tx.sign(&zklink_signer).unwrap(); + let submitter_signature = tx.submitter_signature(&zklink_signer).unwrap(); + //send to zklink + let ret = client + .send_transaction( + serde_wasm_bindgen::to_value(&TypesZkLinkTx::ChangePubKey(Box::new(tx))).unwrap(), + None, + Some(submitter_signature.as_hex()), + ) + .await; + if let Err(e) = ret { + web_sys::console::log_1(&JsValue::from_str(&format!("{:?}", e))); + } else { + web_sys::console::log_1(&JsValue::from_str(&format!("{:?}", ret.unwrap()))); + } +} diff --git a/examples/Javascript/js-example/1_change_pubkey.js b/examples/Javascript/js-example/1_change_pubkey.js new file mode 100644 index 00000000..8eb9925a --- /dev/null +++ b/examples/Javascript/js-example/1_change_pubkey.js @@ -0,0 +1,68 @@ +import init, * as wasm from "./web-dist/zklink-sdk-web.js"; + +async function testEcdsaAuth() { + await init(); + const private_key = "be725250b123a39dab5b7579334d5888987c72a58f4508062545fe6e08ca94f4"; + const main_contract = "0x5505a8cD4594Dbf79d8C59C0Df1414AB871CA896"; + const l1_client_id = 80001; + const new_pubkey_hash = "0xd8d5fb6a6caef06aa3dc2abdcdc240987e5330fe"; + const ts = Math.floor(Date.now() / 1000); + try { + let tx_builder = new wasm.ChangePubKeyBuilder( + 1,5,1,new_pubkey_hash,18,"100000000000000", + 1,"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b", + ts); + let tx = wasm.newChangePubkey(tx_builder); + const signer = new wasm.Signer(private_key); + let tx_signature = signer.signChangePubkeyWithEthEcdsaAuth(tx,l1_client_id,main_contract); + console.log(tx_signature); + + let submitter_signature = signer.submitterSignature(tx_signature.tx); + console.log(submitter_signature); + //send to zklink + let rpc_client = new wasm.RpcClient("testnet"); + let tx_hash = await rpc_client.sendTransaction(tx_signature.tx,null,submitter_signature); + console.log(tx_hash); + + } catch (error) { + console.error(error); + } + +} + +async function testCreate2() { + await init(); + const private_key = "43be0b8bdeccb5a13741c8fd076bf2619bfc9f6dcc43ad6cf965ab489e156ced"; + const new_pubkey_hash = "0xd8d5fb6a6caef06aa3dc2abdcdc240987e5330fe"; + const ts = Math.floor(Date.now() / 1000); + try { + let tx_builder = new wasm.ChangePubKeyBuilder( + 1,5,1,new_pubkey_hash,18,"100000000000000", + 1,"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b", + ts); + let tx = wasm.newChangePubkey(tx_builder); + const signer = new wasm.Signer(private_key); + //auth type 'Create2' + const creator_address = "0x6E253C951A40fAf4032faFbEc19262Cd1531A5F5"; + const salt = "0x0000000000000000000000000000000000000000000000000000000000000000"; + const code_hash = "0x4f063cd4b2e3a885f61fefb0988cc12487182c4f09ff5de374103f5812f33fe7"; + let create2_data = new wasm.Create2Data(creator_address,salt,code_hash); + let from_account = "0x4504d5BE8634e3896d42784A5aB89fc41C3d4511"; + let tx_signature = signer.signChangePubkeyWithCreate2DataAuth(tx,create2_data,from_account); + console.log(tx_signature); + + let submitter_signature = signer.submitterSignature(tx_signature.tx); + console.log(submitter_signature); + //send to zklink + let rpc_client = new wasm.RpcClient("devnet"); + let tx_hash = await rpc_client.sendTransaction(tx_signature.tx,null,submitter_signature); + console.log(tx_hash); + + } catch (error) { + console.error(error); + } + +} + +await testEcdsaAuth(); +// await testCreate2(); diff --git a/examples/Javascript/js-example/2_transfer.js b/examples/Javascript/js-example/2_transfer.js new file mode 100644 index 00000000..e60a9e1a --- /dev/null +++ b/examples/Javascript/js-example/2_transfer.js @@ -0,0 +1,28 @@ +import init, * as wasm from "./web-dist/zklink-sdk-web.js"; + +async function main() { + await init(); + const private_key = "be725250b123a39dab5b7579334d5888987c72a58f4508062545fe6e08ca94f4"; + const to_address = "0x5505a8cD4594Dbf79d8C59C0Df1414AB871CA896"; + const ts = Math.floor(Date.now() / 1000); + try { + let tx_builder = new wasm.TransferBuilder(10, to_address, 1, 1, 18, "100000000000000", "10000000000000000", 1,ts); + let transfer = wasm.newTransfer(tx_builder); + let signer = new wasm.Signer(private_key); + let signature = signer.signTransfer(transfer,"USDC") + console.log(signature); + + let submitter_signature = signer.submitterSignature(signature.tx); + console.log(submitter_signature); + let rpc_client = new wasm.RpcClient("testnet"); + let l1_signature = new wasm.TxLayer1Signature(wasm.L1SignatureType.Eth,signature.eth_signature); + let tx_hash = await rpc_client.sendTransaction(signature.tx,l1_signature,submitter_signature); + console.log(tx_hash); + + } catch (error) { + console.error(error); + } + +} + +main(); diff --git a/examples/Javascript/js-example/3_order_matching.js b/examples/Javascript/js-example/3_order_matching.js new file mode 100644 index 00000000..c95712f5 --- /dev/null +++ b/examples/Javascript/js-example/3_order_matching.js @@ -0,0 +1,33 @@ +import init, * as wasm from "./web-dist/zklink-sdk-web.js"; + +async function main() { + await init(); + const private_key = "be725250b123a39dab5b7579334d5888987c72a58f4508062545fe6e08ca94f4"; + try { + + let signer = new wasm.Signer(private_key); + //maker = taker = submitter + let maker_order = new wasm.Order(5,1,1,1,18,17,"10000000000000","10000000000",true,5,3); + let maker = signer.createSignedOrder(maker_order); + console.log(maker); + let taker_order = new wasm.Order(5,1,1,1,18,17,"10000000000000","10000000000",false,5,3); + let taker = signer.createSignedOrder(taker_order); + console.log(taker); + let tx_builder = new wasm.OrderMatchingBuilder(10, 1, taker, maker, "1000000000", 18,"10000000000000000", "10000000000000000"); + let order_matching = wasm.newOrderMatching(tx_builder); + let signature = signer.signOrderMatching(order_matching); + console.log(signature); + + let submitter_signature = signer.submitterSignature(signature.tx); + console.log(submitter_signature); + let rpc_client = new wasm.RpcClient("testnet"); + let tx_hash = await rpc_client.sendTransaction(signature.tx,null,submitter_signature); + console.log(tx_hash); + + } catch (error) { + console.error(error); + } + +} + +main(); diff --git a/examples/Javascript/js-example/4_withdraw.js b/examples/Javascript/js-example/4_withdraw.js new file mode 100644 index 00000000..7028ea01 --- /dev/null +++ b/examples/Javascript/js-example/4_withdraw.js @@ -0,0 +1,28 @@ +import init, * as wasm from "./web-dist/zklink-sdk-web.js"; + +async function main() { + await init(); + const private_key = "be725250b123a39dab5b7579334d5888987c72a58f4508062545fe6e08ca94f4"; + const to_address = "0x5505a8cD4594Dbf79d8C59C0Df1414AB871CA896"; + const ts = Math.floor(Date.now() / 1000); + try { + let tx_builder = new wasm.WithdrawBuilder(10, 1, 1, to_address,18, "100000000000000", false,10,18,"10000000000000000", 1,ts); + let withdraw = wasm.newWithdraw(tx_builder); + let signer = new wasm.Signer(private_key); + let signature = signer.signWithdraw(withdraw,"USDC") + console.log(signature); + + let submitter_signature = signer.submitterSignature(signature.tx); + console.log(submitter_signature); + let rpc_client = new wasm.RpcClient("testnet"); + let l1_signature = new wasm.TxLayer1Signature(wasm.L1SignatureType.Eth,signature.eth_signature); + let tx_hash = await rpc_client.sendTransaction(signature.tx,l1_signature,submitter_signature); + console.log(tx_hash); + + } catch (error) { + console.error(error); + } + +} + +main(); diff --git a/examples/Javascript/js-example/5_forced_exit.js b/examples/Javascript/js-example/5_forced_exit.js new file mode 100644 index 00000000..185261c8 --- /dev/null +++ b/examples/Javascript/js-example/5_forced_exit.js @@ -0,0 +1,27 @@ +import init, * as wasm from "./web-dist/zklink-sdk-web.js"; + +async function main() { + await init(); + const private_key = "be725250b123a39dab5b7579334d5888987c72a58f4508062545fe6e08ca94f4"; + const to_address = "0x5505a8cD4594Dbf79d8C59C0Df1414AB871CA896"; + const ts = Math.floor(Date.now() / 1000); + try { + let tx_builder = new wasm.ForcedExitBuilder(1,10, 1, 1, to_address,18, 18,"100000000000000", 1,ts); + let forced_exit = wasm.newForcedExit(tx_builder); + let signer = new wasm.Signer(private_key); + let signature = signer.signForcedExit(forced_exit) + console.log(signature); + + let submitter_signature = signer.submitterSignature(signature.tx); + console.log(submitter_signature); + let rpc_client = new wasm.RpcClient("testnet"); + let tx_hash = await rpc_client.sendTransaction(signature.tx,null,submitter_signature); + console.log(tx_hash); + + } catch (error) { + console.error(error); + } + +} + +main(); diff --git a/examples/Javascript/js-example/6_rpc.js b/examples/Javascript/js-example/6_rpc.js new file mode 100644 index 00000000..764396db --- /dev/null +++ b/examples/Javascript/js-example/6_rpc.js @@ -0,0 +1,63 @@ +import init, * as wasm from "./web-dist/zklink-sdk-web.js"; +async function main() { + await init(); + try { + let client = new wasm.RpcClient("testnet"); + // 1.getSupportTokens + let tokens = await client.getSupportTokens(); + console.log(tokens); + // 2.getAccountSnapshot + let account_id = new wasm.AccountQuery(wasm.AccountQueryType.AccountId, "5"); + let sub_account_id = 1; + // let block_number = 100; + let account_resp = await client.getAccountSnapshot(account_id,sub_account_id,null); + console.log(account_resp); + // 3.sendTransaction(test on the tx example) + // 4.getSupportChains + let chains = await client.getSupportChains(); + console.log(chains); + console.log(chains.length) + // 5.getLatestBlockNumber + let block_info = await client.getLatestBlockNumber(); + console.log(block_info); + // 6.getBlockByNumber + let block_detail = await client.getBlockByNumber(100,true,true); + console.log(block_detail); + // 7.getPendingBlock + let pending_block_info = await client.getPendingBlock(1696743981000n,true,true,null); + console.log(pending_block_info); + // 8.getBlockOnChainByNumber + let on_chain_block_info = await client.getBlockOnChainByNumber(100); + console.log(on_chain_block_info); + // 9.getAccount + let get_account_id = new wasm.AccountQuery(wasm.AccountQueryType.AccountId, "10"); + let account = await client.getAccount(get_account_id); + console.log(account); + // 10.getAccountBalances + let balances = await client.getAccountBalances(20,1); + console.log(balances); + // 11.getAccountOrderSlots + let slots = await client.getAccountOrderSlots(20,1); + console.log(slots); + // 12.getTokenReserve + let reserve = await client.getTokenReserve(18,false); + console.log(reserve); + // 13.getTransactionByHash + let tx_hash = "0x0cbeabac1a2257fb095c2465e148570e32793345442b39bf64cad4ed87475f9b"; + let tx_info = await client.getTransactionByHash(tx_hash,false); + console.log(tx_info); + // 14.getAccountTransactionHistory + let history = await client.getAccountTransactionHistory(wasm.ZkLinkTxType.Deposit,"0x12aFF993702B5d623977A9044686Fa1A2B0c2147",0n,5); + console.log(history); + // 15.getFastWithdrawTxs + let fast_withdraw_txs = await client.getFastWithdrawTxs(1696743981000n,10); + console.log(fast_withdraw_txs); + // 16.pullForwardTxs + // 17.confirmFullExit + } catch (error) { + console.error(error); + } + +} + +main(); diff --git a/examples/Javascript/js-example/index.html b/examples/Javascript/js-example/index.html index 81c90a09..0c7edc07 100644 --- a/examples/Javascript/js-example/index.html +++ b/examples/Javascript/js-example/index.html @@ -6,7 +6,10 @@ - \ No newline at end of file diff --git a/examples/Javascript/js-example/index.js b/examples/Javascript/js-example/index.js deleted file mode 100644 index 06167478..00000000 --- a/examples/Javascript/js-example/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import init, { ZklinkSignerWasm } from "./web-dist/zklink-sdk-web.js"; - -async function main() { - await init(); - const signer = ZklinkSignerWasm.NewFromEthSigner("be725250b123a39dab5b7579334d5888987c72a58f4508062545fe6e08ca94f4"); - const msg_str = "hello world!"; - const msg = new TextEncoder().encode(msg_str); - try { - let signature = signer.sign(msg); - console.log(signature); - } catch (error) { - console.error(error); - } - -} - -main(); diff --git a/interface/Cargo.toml b/interface/Cargo.toml index 56143676..b60a9596 100644 --- a/interface/Cargo.toml +++ b/interface/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] thiserror = "1.0" +wasm-bindgen = { version = "0.2.87", features = ["serde-serialize"] } zklink_sdk_signers = { path = "../signers" } zklink_sdk_types = { path = "../types" } diff --git a/interface/src/error.rs b/interface/src/error.rs index 12e6ccd1..3d4b12d9 100644 --- a/interface/src/error.rs +++ b/interface/src/error.rs @@ -1,4 +1,6 @@ use thiserror::Error; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsValue; use zklink_sdk_signers::eth_signer::error::EthSignerError; use zklink_sdk_signers::zklink_signer::error::ZkSignerError; @@ -11,3 +13,10 @@ pub enum SignError { #[error("Incorrect tx format")] IncorrectTx, } + +#[cfg(target_arch = "wasm32")] +impl From for JsValue { + fn from(error: SignError) -> Self { + JsValue::from_str(&format!("error: {error}")) + } +} diff --git a/interface/src/sign_change_pubkey.rs b/interface/src/sign_change_pubkey.rs index 07636d5a..077c5b63 100644 --- a/interface/src/sign_change_pubkey.rs +++ b/interface/src/sign_change_pubkey.rs @@ -10,7 +10,6 @@ use zklink_sdk_signers::zklink_signer::pk_signer::ZkLinkSigner; use zklink_sdk_types::basic_types::ZkLinkAddress; #[cfg(not(feature = "ffi"))] use zklink_sdk_types::prelude::TxSignature; -#[cfg(feature = "ffi")] use zklink_sdk_types::tx_type::change_pubkey::Create2Data; use zklink_sdk_types::tx_type::change_pubkey::{ChangePubKey, ChangePubKeyAuthData}; use zklink_sdk_types::tx_type::TxTrait; @@ -78,6 +77,22 @@ pub fn check_create2data( } } +#[cfg(not(feature = "ffi"))] +pub fn check_create2data( + zklink_singer: &ZkLinkSigner, + data: Create2Data, + account_address: ZkLinkAddress, +) -> Result<(), SignError> { + let pubkey_hash = zklink_singer.public_key().public_key_hash(); + let from_address = data.get_address(&pubkey_hash.data); + println!("{:?} {:?}", from_address, account_address); + if from_address.as_bytes() != account_address.as_bytes() { + Err(SignError::IncorrectTx) + } else { + Ok(()) + } +} + #[cfg(feature = "ffi")] pub fn create_signed_change_pubkey( zklink_singer: Arc, @@ -89,3 +104,35 @@ pub fn create_signed_change_pubkey( tx.signature = zklink_singer.sign_musig(&tx.get_bytes())?; Ok(Arc::new(tx)) } + +#[cfg(test)] +mod test { + use super::*; + use std::str::FromStr; + use zklink_sdk_types::prelude::H256; + + #[test] + fn test_check_create2() { + let creator_address = + ZkLinkAddress::from_hex("0x6E253C951A40fAf4032faFbEc19262Cd1531A5F5").unwrap(); + let salt_arg = + H256::from_str("0x0000000000000000000000000000000000000000000000000000000000000000") + .unwrap(); + let code_hash = + H256::from_str("0x4f063cd4b2e3a885f61fefb0988cc12487182c4f09ff5de374103f5812f33fe7") + .unwrap(); + let create2_data = Create2Data { + creator_address, + code_hash, + salt_arg, + }; + let from_account = + ZkLinkAddress::from_hex("0x4504d5BE8634e3896d42784A5aB89fc41C3d4511").unwrap(); + let eth_private_key = "43be0b8bdeccb5a13741c8fd076bf2619bfc9f6dcc43ad6cf965ab489e156ced"; + let zk_signer = ZkLinkSigner::new_from_hex_eth_signer(eth_private_key).unwrap(); + + if let Err(e) = check_create2data(&zk_signer, create2_data, from_account) { + println!("{:?}", e) + } + } +} diff --git a/interface/src/sign_order.rs b/interface/src/sign_order.rs index ca380f0a..ad90bc20 100644 --- a/interface/src/sign_order.rs +++ b/interface/src/sign_order.rs @@ -5,10 +5,10 @@ use zklink_sdk_signers::zklink_signer::pk_signer::ZkLinkSigner; use zklink_sdk_types::tx_type::order_matching::Order; use zklink_sdk_types::tx_type::TxTrait; #[cfg(not(feature = "ffi"))] -pub fn sign(order: &mut Order, signer: &ZkLinkSigner) -> Result<(), ZkSignerError> { - let bytes = order.get_bytes(); - order.signature = signer.sign_musig(&bytes)?; - Ok(()) +pub fn sign_order(order: &Order, zklink_signer: &ZkLinkSigner) -> Result { + let mut order = order.clone(); + order.signature = zklink_signer.sign_musig(&order.get_bytes())?; + Ok(order) } #[cfg(feature = "ffi")] diff --git a/interface/src/signer.rs b/interface/src/signer.rs index e75c70db..c39ae236 100644 --- a/interface/src/signer.rs +++ b/interface/src/signer.rs @@ -1,10 +1,12 @@ use crate::error::SignError; use crate::sign_forced_exit::sign_forced_exit; +use crate::sign_order::sign_order; use crate::sign_order_matching::sign_order_matching; use crate::sign_transfer::sign_transfer; use crate::sign_withdraw::sign_withdraw; use zklink_sdk_types::prelude::TxSignature; +use crate::sign_change_pubkey::check_create2data; #[cfg(feature = "ffi")] use std::sync::Arc; use zklink_sdk_signers::eth_signer::error::EthSignerError; @@ -14,7 +16,7 @@ use zklink_sdk_signers::zklink_signer::signature::ZkLinkSignature; use zklink_sdk_types::basic_types::ZkLinkAddress; use zklink_sdk_types::tx_type::change_pubkey::{ChangePubKey, ChangePubKeyAuthData, Create2Data}; use zklink_sdk_types::tx_type::forced_exit::ForcedExit; -use zklink_sdk_types::tx_type::order_matching::OrderMatching; +use zklink_sdk_types::tx_type::order_matching::{Order, OrderMatching}; use zklink_sdk_types::tx_type::transfer::Transfer; use zklink_sdk_types::tx_type::withdraw::Withdraw; use zklink_sdk_types::tx_type::zklink_tx::ZkLinkTx; @@ -40,7 +42,9 @@ impl Signer { &self, mut tx: ChangePubKey, create2data: Create2Data, + from_account: ZkLinkAddress, ) -> Result { + check_create2data(&self.zklink_signer, create2data.clone(), from_account)?; tx.sign(&self.zklink_signer)?; let should_valid = tx.is_signature_valid(); assert!(should_valid); @@ -59,9 +63,10 @@ impl Signer { &self, tx: Arc, create2data: Create2Data, + from_account: ZkLinkAddress, ) -> Result { let tx = (*tx).clone(); - self.do_sign_change_pubkey_with_create2data_auth(tx, create2data) + self.do_sign_change_pubkey_with_create2data_auth(tx, create2data, from_account) } #[cfg(not(feature = "ffi"))] @@ -70,8 +75,9 @@ impl Signer { &self, tx: ChangePubKey, create2data: Create2Data, + from_account: ZkLinkAddress, ) -> Result { - self.do_sign_change_pubkey_with_create2data_auth(tx, create2data) + self.do_sign_change_pubkey_with_create2data_auth(tx, create2data, from_account) } #[cfg(feature = "ffi")] @@ -210,6 +216,12 @@ impl Signer { Ok(signature) } + #[cfg(not(feature = "ffi"))] + pub fn sign_order(&self, order: &Order) -> Result { + let signed_order = sign_order(order, &self.zklink_signer)?; + Ok(signed_order) + } + #[cfg(feature = "ffi")] pub fn sign_order_matching(&self, tx: Arc) -> Result { let tx = (*tx).clone(); diff --git a/provider/Cargo.toml b/provider/Cargo.toml index 481c0658..ca388e4e 100644 --- a/provider/Cargo.toml +++ b/provider/Cargo.toml @@ -6,15 +6,26 @@ edition = "2021" [dependencies] bigdecimal = { version = "0.3.0", features = ["serde"] } -chrono = { version = "0.4", features = ["serde", "rustc-serialize"] } -jsonrpsee = { version = "0.15.1", features = ["server", "client", "macros"] } +chrono = { version = "0.4", features = ["serde"] } +reqwest = { version = "0.11", default-features = false, features = ["blocking", "json", "rustls-tls"] } serde = "1.0.137" +thiserror = "1.0" +wasm-bindgen = { version = "0.2.87", features = ["serde-serialize"] } zklink_sdk_signers = { path = "../signers" } zklink_sdk_types = { path = "../types" } +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +jsonrpsee = { version = "0.20.1", features = ["http-client","macros"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +jsonrpsee = { version = "0.20.1", features = ["macros","jsonrpsee-types","client-core"] } +getrandom = { version = "0.2.10", features = ["js"] } +uuid = "0.8" + [dev-dependencies] serde_json = "1.0" tokio = { version = "1", features = ["full"] } +wasm-bindgen-test = "0.3" [features] default = [] diff --git a/provider/src/error.rs b/provider/src/error.rs new file mode 100644 index 00000000..fdc814c0 --- /dev/null +++ b/provider/src/error.rs @@ -0,0 +1,27 @@ +use jsonrpsee::core::error::Error as jsonrpseeError; +use thiserror::Error; +use wasm_bindgen::JsValue; + +#[derive(Debug, Error)] +pub enum RpcError { + #[error("Invalid network")] + InvalidNetwork, + #[error("Invalid input parameter")] + InvalidInputParameter, + #[error("Parse params error: {0}")] + ParseParamsError(jsonrpseeError), + #[error("HTTP request error: {0}")] + RequestError(reqwest::Error), + #[error("Parse response error: {0}")] + ResponseError(reqwest::Error), + #[error("Parse json value error: {0}")] + ParseJsonError(String), + #[error("Get error result: {0}")] + GetErrorResult(String), +} + +impl From for JsValue { + fn from(error: RpcError) -> Self { + JsValue::from_str(&format!("error: {error}")) + } +} diff --git a/provider/src/lib.rs b/provider/src/lib.rs index bd9eee94..afd32117 100644 --- a/provider/src/lib.rs +++ b/provider/src/lib.rs @@ -1,9 +1,10 @@ +pub mod error; pub mod network; pub mod response; -#[cfg(not(feature = "ffi"))] +#[cfg(not(any(feature = "ffi", target_arch = "wasm32")))] pub mod rpc; -#[cfg(not(feature = "ffi"))] +#[cfg(not(any(feature = "ffi", target_arch = "wasm32")))] mod not_ffi { use crate::network::Network; use jsonrpsee::http_client::{HttpClient, HttpClientBuilder}; @@ -35,9 +36,9 @@ mod not_ffi { } } -#[cfg(not(feature = "ffi"))] +#[cfg(not(any(feature = "ffi", target_arch = "wasm32")))] pub use crate::rpc::ZkLinkRpcClient; -#[cfg(not(feature = "ffi"))] +#[cfg(not(any(feature = "ffi", target_arch = "wasm32")))] pub use jsonrpsee::core::Error as RpcError; -#[cfg(not(feature = "ffi"))] +#[cfg(not(any(feature = "ffi", target_arch = "wasm32")))] pub use not_ffi::*; diff --git a/provider/src/network.rs b/provider/src/network.rs index f8a49752..f5379805 100644 --- a/provider/src/network.rs +++ b/provider/src/network.rs @@ -1,4 +1,6 @@ +use crate::error::RpcError; use serde::{Deserialize, Serialize}; +use std::str::FromStr; /// Network to be used for a zklink client. #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq)] @@ -8,6 +10,8 @@ pub enum Network { MainNet, /// Test network for testkit purposes TestNet, + /// Develop network + DevNet, } impl Network { @@ -15,6 +19,20 @@ impl Network { match self { Network::MainNet => "https://api-v1.zk.link", Network::TestNet => "https://aws-gw-v2.zk.link", + Network::DevNet => "https://dev-gw-v1.zk.link", + } + } +} + +impl FromStr for Network { + type Err = RpcError; + + fn from_str(s: &str) -> Result { + match s { + "mainet" => Ok(Network::MainNet), + "testnet" => Ok(Network::TestNet), + "devnet" => Ok(Network::DevNet), + _ => Err(RpcError::InvalidNetwork), } } } diff --git a/provider/src/response.rs b/provider/src/response.rs index a339ca32..49706a46 100644 --- a/provider/src/response.rs +++ b/provider/src/response.rs @@ -279,5 +279,6 @@ pub struct ZkLinkTxHistory { pub tx: ZkLinkTx, pub tx_hash: TxHash, pub tx_receipt: TxReceiptResp, + #[serde(with = "ts_microseconds")] pub created_at: DateTime, } diff --git a/provider/tests/test_rpc.rs b/provider/tests/test_rpc.rs new file mode 100644 index 00000000..de1ec6f7 --- /dev/null +++ b/provider/tests/test_rpc.rs @@ -0,0 +1,140 @@ +#[cfg(test)] +mod test { + use jsonrpsee::http_client::HttpClientBuilder; + use std::str::FromStr; + use zklink_sdk_provider::ZkLinkRpcClient; + use zklink_sdk_signers::eth_signer::EthSigner; + use zklink_sdk_signers::zklink_signer::{PubKeyHash, ZkLinkSigner}; + use zklink_sdk_types::basic_types::BigUint; + use zklink_sdk_types::basic_types::{ + AccountId, ChainId, Nonce, SubAccountId, TimeStamp, TokenId, ZkLinkAddress, + }; + use zklink_sdk_types::tx_builder::{ChangePubKeyBuilder, OrderMatchingBuilder}; + use zklink_sdk_types::tx_type::change_pubkey::ChangePubKey; + use zklink_sdk_types::tx_type::order_matching::{Order, OrderMatching}; + use zklink_sdk_types::tx_type::zklink_tx::ZkLinkTx; + use zklink_sdk_types::tx_type::{TxTrait, ZkSignatureTrait}; + + #[tokio::test] + async fn test_send_change_pubkey() { + let private_key = "be725250b123a39dab5b7579334d5888987c72a58f4508062545fe6e08ca94f4"; + let eth_signer = EthSigner::try_from(private_key).unwrap(); + let zklink_signer = ZkLinkSigner::new_from_hex_eth_signer(private_key).unwrap(); + let main_contract = "0x5505a8cD4594Dbf79d8C59C0Df1414AB871CA896"; + let l1_client_id = 80001; + let new_pubkey_hash = "0xd8d5fb6a6caef06aa3dc2abdcdc240987e5330fe"; + let ts = 1696595303; + //auth type 'ECDSA' + let builder = ChangePubKeyBuilder { + chain_id: ChainId(1), + account_id: AccountId(10), + sub_account_id: SubAccountId(1), + new_pubkey_hash: PubKeyHash::from_hex(new_pubkey_hash).unwrap(), + fee_token: TokenId(18), + fee: BigUint::from(100000000000000u64), + nonce: Nonce(1), + eth_signature: None, + timestamp: TimeStamp(ts), + }; + let change_pubkey = ChangePubKey::new(builder); + let message = change_pubkey + .to_eip712_request_payload( + l1_client_id, + &ZkLinkAddress::from_str(main_contract).unwrap(), + ) + .unwrap(); + let signature = eth_signer + .sign_message(message.raw_data.as_bytes()) + .unwrap(); + let builder_with_sig = ChangePubKeyBuilder { + chain_id: ChainId(1), + account_id: AccountId(10), + sub_account_id: SubAccountId(1), + new_pubkey_hash: PubKeyHash::from_hex(new_pubkey_hash).unwrap(), + fee_token: TokenId(18), + fee: BigUint::from(100000000000000u64), + nonce: Nonce(1), + eth_signature: Some(signature), + timestamp: TimeStamp(ts), + }; + let mut tx = ChangePubKey::new(builder_with_sig); + tx.sign(&zklink_signer).unwrap(); + let submitter_signature = tx.submitter_signature(&zklink_signer).unwrap(); + + //use jsonrpsee + let client = HttpClientBuilder::default() + .build("https://dev-gw-v1.zk.link") + .unwrap(); + let ret = client + .tx_submit( + ZkLinkTx::ChangePubKey(Box::new(tx.clone())), + None, + Some(submitter_signature), + ) + .await; + println!("{:?}", ret) + } + + #[tokio::test] + async fn test_send_order_matching() { + let private_key = "be725250b123a39dab5b7579334d5888987c72a58f4508062545fe6e08ca94f4"; + let zklink_signer = ZkLinkSigner::new_from_hex_eth_signer(private_key).unwrap(); + let maker_order = Order::new( + 5.into(), + 1.into(), + 1.into(), + 1.into(), + 18.into(), + 17.into(), + BigUint::from_str("10000000000000").unwrap(), + BigUint::from_str("10000000000").unwrap(), + true, + 5, + 3, + ); + let mut maker = maker_order.clone(); + maker.signature = zklink_signer.sign_musig(&maker_order.get_bytes()).unwrap(); + let taker_order = Order::new( + 5.into(), + 1.into(), + 1.into(), + 1.into(), + 18.into(), + 17.into(), + BigUint::from_str("10000000000000").unwrap(), + BigUint::from_str("10000000000").unwrap(), + false, + 5, + 3, + ); + let mut taker = taker_order.clone(); + taker.signature = zklink_signer.sign_musig(&taker_order.get_bytes()).unwrap(); + //auth type 'ECDSA' + let builder = OrderMatchingBuilder { + account_id: AccountId(10), + sub_account_id: SubAccountId(1), + taker, + fee_token: TokenId(18), + expect_base_amount: BigUint::from(10000000000000000u64), + fee: BigUint::from(100000000000000u64), + maker, + expect_quote_amount: BigUint::from(100000000000000u64), + }; + let mut order_matching = OrderMatching::new(builder); + order_matching.sign(&zklink_signer).unwrap(); + let submitter_signature = order_matching.submitter_signature(&zklink_signer).unwrap(); + + //use jsonrpsee + let client = HttpClientBuilder::default() + .build("https://aws-gw-v2.zk.link") + .unwrap(); + let ret = client + .tx_submit( + ZkLinkTx::OrderMatching(Box::new(order_matching.clone())), + None, + Some(submitter_signature), + ) + .await; + println!("{:?}", ret) + } +} diff --git a/signers/Cargo.toml b/signers/Cargo.toml index 5c2809c0..6ac4f1cf 100644 --- a/signers/Cargo.toml +++ b/signers/Cargo.toml @@ -24,7 +24,5 @@ default = [] ffi = [] [dev-dependencies] -actix-rt = "2.7" -actix-web = "4.2" futures = "0.3" tokio = { version = "1", features = ["full"] } diff --git a/signers/src/eth_signer/eip1271_signature.rs b/signers/src/eth_signer/eip1271_signature.rs index 3460e0f7..9938dba7 100644 --- a/signers/src/eth_signer/eip1271_signature.rs +++ b/signers/src/eth_signer/eip1271_signature.rs @@ -3,7 +3,7 @@ use std::fmt; use zklink_sdk_utils::serde::ZeroPrefixHexSerde; #[derive(Debug, Clone, PartialEq, Eq)] -pub struct EIP1271Signature(pub(crate) Vec); +pub struct EIP1271Signature(pub Vec); impl AsRef<[u8]> for EIP1271Signature { fn as_ref(&self) -> &[u8] { diff --git a/signers/src/eth_signer/error.rs b/signers/src/eth_signer/error.rs index cdabfa72..323a4b1a 100644 --- a/signers/src/eth_signer/error.rs +++ b/signers/src/eth_signer/error.rs @@ -1,4 +1,6 @@ use thiserror::Error; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsValue; #[derive(Debug, Error, PartialEq)] pub enum EthSignerError { @@ -31,3 +33,10 @@ pub enum EthSignerError { #[error("{0}")] CustomError(String), } + +#[cfg(target_arch = "wasm32")] +impl From for JsValue { + fn from(error: EthSignerError) -> Self { + JsValue::from_str(&format!("error: {error}")) + } +} diff --git a/signers/src/eth_signer/mod.rs b/signers/src/eth_signer/mod.rs index 3ba04b30..64c0cc4b 100644 --- a/signers/src/eth_signer/mod.rs +++ b/signers/src/eth_signer/mod.rs @@ -3,8 +3,6 @@ pub mod eip712; pub mod error; pub mod packed_eth_signature; pub mod pk_signer; -#[cfg(target_arch = "wasm32")] -pub mod wasm_binding; pub use primitive_types::{H160, H256, U256}; pub type Address = H160; @@ -13,8 +11,9 @@ pub use error::EthSignerError; pub use ethers_primitives::Address as EIP712Address; pub use packed_eth_signature::PackedEthSignature; pub use pk_signer::EthSigner; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct EthTypedData { pub raw_data: String, pub data_hash: H256, diff --git a/signers/src/eth_signer/wasm_binding.rs b/signers/src/eth_signer/wasm_binding.rs deleted file mode 100644 index 02def8df..00000000 --- a/signers/src/eth_signer/wasm_binding.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::eth_signer::error::EthSignerError; -use wasm_bindgen::JsValue; - -impl From for JsValue { - fn from(error: EthSignerError) -> Self { - JsValue::from_str(&format!("error: {error}")) - } -} diff --git a/signers/src/zklink_signer/error.rs b/signers/src/zklink_signer/error.rs index 9b905b2b..9361b6e0 100644 --- a/signers/src/zklink_signer/error.rs +++ b/signers/src/zklink_signer/error.rs @@ -1,5 +1,7 @@ use crate::eth_signer::error::EthSignerError; use thiserror::Error; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsValue; #[derive(Debug, Error)] pub enum ZkSignerError { @@ -30,3 +32,10 @@ impl ZkSignerError { Self::InvalidPrivKey(s.to_string()) } } + +#[cfg(target_arch = "wasm32")] +impl From for JsValue { + fn from(error: ZkSignerError) -> Self { + JsValue::from_str(&format!("error: {error}")) + } +} diff --git a/signers/src/zklink_signer/mod.rs b/signers/src/zklink_signer/mod.rs index 8234ea33..68af57f4 100644 --- a/signers/src/zklink_signer/mod.rs +++ b/signers/src/zklink_signer/mod.rs @@ -4,8 +4,6 @@ pub mod pubkey_hash; pub mod public_key; pub mod signature; pub mod utils; -#[cfg(target_arch = "wasm32")] -pub mod wasm_binding; pub use franklin_crypto::bellman::pairing::bn256::{Bn256 as Engine, Fr}; use franklin_crypto::rescue::bn256::Bn256RescueParams; diff --git a/signers/src/zklink_signer/wasm_binding.rs b/signers/src/zklink_signer/wasm_binding.rs deleted file mode 100644 index c7e08f2c..00000000 --- a/signers/src/zklink_signer/wasm_binding.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::zklink_signer::error::ZkSignerError; -use wasm_bindgen::JsValue; - -impl From for JsValue { - fn from(error: ZkSignerError) -> Self { - JsValue::from_str(&format!("error: {error}")) - } -} diff --git a/types/Cargo.toml b/types/Cargo.toml index 8bd93081..d07847ca 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -12,6 +12,7 @@ serde = "1.0.137" serde_json = "1.0.0" thiserror = "1.0" validator = { version = "0.15", features = ["derive"] } +wasm-bindgen = { version = "0.2.87", features = ["serde-serialize"] } zklink_sdk_signers = { path = "../signers" } zklink_sdk_utils = { path = "../utils" } diff --git a/types/src/error.rs b/types/src/error.rs index 6d72a5a6..bc448a42 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -1,4 +1,6 @@ use thiserror::Error; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::JsValue; #[derive(Debug, Error, PartialEq)] pub enum TypeError { @@ -15,3 +17,10 @@ pub enum TypeError { #[error("Integer is too big")] TooBigInteger, } + +#[cfg(target_arch = "wasm32")] +impl From for JsValue { + fn from(error: TypeError) -> Self { + JsValue::from_str(&format!("error: {error}")) + } +} diff --git a/types/src/tx_type/mod.rs b/types/src/tx_type/mod.rs index 96d6b821..ac60c677 100644 --- a/types/src/tx_type/mod.rs +++ b/types/src/tx_type/mod.rs @@ -155,4 +155,11 @@ pub trait ZkSignatureTrait: TxTrait { let signature = signer.sign_musig(&bytes)?; Ok(signature) } + + #[cfg(not(feature = "ffi"))] + fn submitter_signature(&self, signer: &ZkLinkSigner) -> Result { + let bytes = self.tx_hash(); + let signature = signer.sign_musig(&bytes)?; + Ok(signature) + } } diff --git a/types/src/tx_type/zklink_tx.rs b/types/src/tx_type/zklink_tx.rs index efb21123..052d35b5 100644 --- a/types/src/tx_type/zklink_tx.rs +++ b/types/src/tx_type/zklink_tx.rs @@ -10,8 +10,10 @@ use crate::tx_type::order_matching::OrderMatching; use crate::tx_type::transfer::Transfer; use crate::tx_type::withdraw::Withdraw; use crate::tx_type::TxTrait; +use wasm_bindgen::prelude::wasm_bindgen; /// A set of L2 transaction type supported by the zklink network. +#[wasm_bindgen] #[derive(Debug, Clone, Serialize, Deserialize)] pub enum ZkLinkTxType { Deposit,