From b01646cc723c7c8a8424f4f36008e5c8c4be25bc Mon Sep 17 00:00:00 2001 From: nickwest Date: Thu, 12 Oct 2023 00:53:07 -0700 Subject: [PATCH 1/8] add wasm support for sdk --- Makefile | 3 + bindings/wasm/Cargo.toml | 21 +- bindings/wasm/src/lib.rs | 11 +- bindings/wasm/src/rpc_client.rs | 336 ++++++++++++++++++ bindings/wasm/src/rpc_type_converter.rs | 122 +++++++ bindings/wasm/src/signer.rs | 116 ++++++ bindings/wasm/src/tx_types/change_pubkey.rs | 126 +++++++ bindings/wasm/src/tx_types/forced_exit.rs | 66 ++++ bindings/wasm/src/tx_types/mod.rs | 5 + bindings/wasm/src/tx_types/order_matching.rs | 108 ++++++ bindings/wasm/src/tx_types/transfer.rs | 64 ++++ bindings/wasm/src/tx_types/withdraw.rs | 70 ++++ bindings/wasm/tests/test_rpc.rs | 101 ++++++ .../Javascript/js-example/1_change_pubkey.js | 68 ++++ examples/Javascript/js-example/2_transfer.js | 28 ++ .../Javascript/js-example/3_order_matching.js | 33 ++ examples/Javascript/js-example/4_withdraw.js | 28 ++ .../Javascript/js-example/5_forced_exit.js | 27 ++ examples/Javascript/js-example/6_rpc.js | 63 ++++ examples/Javascript/js-example/index.html | 5 +- interface/Cargo.toml | 1 + interface/src/error.rs | 9 + interface/src/sign_change_pubkey.rs | 49 ++- interface/src/sign_order.rs | 8 +- interface/src/signer.rs | 18 +- provider/Cargo.toml | 15 +- provider/src/error.rs | 27 ++ provider/src/lib.rs | 11 +- provider/src/network.rs | 18 + provider/src/response.rs | 1 + provider/src/rpc.rs | 1 - provider/tests/test_rpc.rs | 142 ++++++++ signers/Cargo.toml | 2 - signers/src/eth_signer/eip1271_signature.rs | 2 +- signers/src/eth_signer/error.rs | 9 + signers/src/eth_signer/mod.rs | 5 +- signers/src/zklink_signer/error.rs | 9 + signers/src/zklink_signer/mod.rs | 2 - types/Cargo.toml | 1 + types/src/error.rs | 9 + types/src/tx_type/mod.rs | 7 + types/src/tx_type/zklink_tx.rs | 2 + 42 files changed, 1713 insertions(+), 36 deletions(-) create mode 100644 bindings/wasm/src/rpc_client.rs create mode 100644 bindings/wasm/src/rpc_type_converter.rs create mode 100644 bindings/wasm/src/signer.rs create mode 100644 bindings/wasm/src/tx_types/change_pubkey.rs create mode 100644 bindings/wasm/src/tx_types/forced_exit.rs create mode 100644 bindings/wasm/src/tx_types/mod.rs create mode 100644 bindings/wasm/src/tx_types/order_matching.rs create mode 100644 bindings/wasm/src/tx_types/transfer.rs create mode 100644 bindings/wasm/src/tx_types/withdraw.rs create mode 100644 bindings/wasm/tests/test_rpc.rs create mode 100644 examples/Javascript/js-example/1_change_pubkey.js create mode 100644 examples/Javascript/js-example/2_transfer.js create mode 100644 examples/Javascript/js-example/3_order_matching.js create mode 100644 examples/Javascript/js-example/4_withdraw.js create mode 100644 examples/Javascript/js-example/5_forced_exit.js create mode 100644 examples/Javascript/js-example/6_rpc.js create mode 100644 provider/src/error.rs create mode 100644 provider/tests/test_rpc.rs diff --git a/Makefile b/Makefile index 25a90bb5..1f050c83 100644 --- a/Makefile +++ b/Makefile @@ -78,6 +78,9 @@ build_wasm: prepare_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/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/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/interface/Cargo.toml b/interface/Cargo.toml index 1f0ca6f5..8fec6cce 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 38f5bdd4..2966b8f3 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/src/rpc.rs b/provider/src/rpc.rs index 6e723114..e7845bf3 100644 --- a/provider/src/rpc.rs +++ b/provider/src/rpc.rs @@ -1,4 +1,3 @@ -use jsonrpsee::core::RpcResult; use jsonrpsee::proc_macros::rpc; use std::collections::HashMap; diff --git a/provider/tests/test_rpc.rs b/provider/tests/test_rpc.rs new file mode 100644 index 00000000..f9c67c84 --- /dev/null +++ b/provider/tests/test_rpc.rs @@ -0,0 +1,142 @@ +#[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, + None, + ); + 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, + None, + ); + 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/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/types/Cargo.toml b/types/Cargo.toml index aae48246..e64ea4e6 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, From fb6dbbb53fbd07d1e18a592a59e91c81796bb6ad Mon Sep 17 00:00:00 2001 From: nickwest Date: Thu, 12 Oct 2023 02:36:52 -0700 Subject: [PATCH 2/8] fix test_go error --- interface/src/sign_change_pubkey.rs | 1 - interface/src/signer.rs | 14 ++++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/interface/src/sign_change_pubkey.rs b/interface/src/sign_change_pubkey.rs index 077c5b63..8e800fca 100644 --- a/interface/src/sign_change_pubkey.rs +++ b/interface/src/sign_change_pubkey.rs @@ -85,7 +85,6 @@ pub fn check_create2data( ) -> 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 { diff --git a/interface/src/signer.rs b/interface/src/signer.rs index c39ae236..f63ca8a2 100644 --- a/interface/src/signer.rs +++ b/interface/src/signer.rs @@ -1,11 +1,13 @@ use crate::error::SignError; use crate::sign_forced_exit::sign_forced_exit; +#[cfg(not(feature = "ffi"))] 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; +#[cfg(not(feature = "ffi"))] use crate::sign_change_pubkey::check_create2data; #[cfg(feature = "ffi")] use std::sync::Arc; @@ -16,7 +18,9 @@ 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::{Order, OrderMatching}; +#[cfg(not(feature = "ffi"))] +use zklink_sdk_types::tx_type::order_matching::Order; +use zklink_sdk_types::tx_type::order_matching::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; @@ -42,9 +46,7 @@ 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); @@ -63,10 +65,9 @@ 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, from_account) + self.do_sign_change_pubkey_with_create2data_auth(tx, create2data) } #[cfg(not(feature = "ffi"))] @@ -77,7 +78,8 @@ impl Signer { create2data: Create2Data, from_account: ZkLinkAddress, ) -> Result { - self.do_sign_change_pubkey_with_create2data_auth(tx, create2data, from_account) + check_create2data(&self.zklink_signer, create2data.clone(), from_account)?; + self.do_sign_change_pubkey_with_create2data_auth(tx, create2data) } #[cfg(feature = "ffi")] From 177971380d5ee5e5d5bb92a4748c895ed3297e3f Mon Sep 17 00:00:00 2001 From: nickwest Date: Thu, 12 Oct 2023 02:48:55 -0700 Subject: [PATCH 3/8] fix cargo test error --- interface/src/sign_change_pubkey.rs | 1 + provider/tests/test_rpc.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/interface/src/sign_change_pubkey.rs b/interface/src/sign_change_pubkey.rs index 8e800fca..e2f12c6b 100644 --- a/interface/src/sign_change_pubkey.rs +++ b/interface/src/sign_change_pubkey.rs @@ -104,6 +104,7 @@ pub fn create_signed_change_pubkey( Ok(Arc::new(tx)) } +#[cfg(not(feature = "ffi"))] #[cfg(test)] mod test { use super::*; diff --git a/provider/tests/test_rpc.rs b/provider/tests/test_rpc.rs index f9c67c84..4ec87ebf 100644 --- a/provider/tests/test_rpc.rs +++ b/provider/tests/test_rpc.rs @@ -1,3 +1,4 @@ +#[cfg(not(feature = "ffi"))] #[cfg(test)] mod test { use jsonrpsee::http_client::HttpClientBuilder; From 8adbbe61fec195fc9fe9840ce43e35141368ecdf Mon Sep 17 00:00:00 2001 From: nickwest Date: Thu, 12 Oct 2023 03:05:13 -0700 Subject: [PATCH 4/8] fix lint command in Makefile --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1f050c83..469dfc1d 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ lint: cargo fmt - cargo clippy --all-targets + cargo clippy --all --all-features -- -D warnings cargo sort bash -c "cd ./interface && cargo sort" bash -c "cd ./types && cargo sort" @@ -8,6 +8,8 @@ lint: bash -c "cd ./provider && cargo sort" bash -c "cd ./signers && cargo sort" cargo machete + cargo test --all + make test_go lint-check: cargo fmt -- --check From 01965447f06b4f449e05b82fd6a926f27c4a4bbd Mon Sep 17 00:00:00 2001 From: nickwest Date: Thu, 12 Oct 2023 20:01:52 -0700 Subject: [PATCH 5/8] add closestPackableTransactionAmount/fee --- Makefile | 2 ++ bindings/sdk/src/ffi.udl | 1 + bindings/wasm/src/lib.rs | 1 + bindings/wasm/src/utils.rs | 23 ++++++++++++++ bindings/wasm/tests/test_rpc.rs | 32 ++++++++++++++++++++ examples/Javascript/js-example/2_transfer.js | 4 ++- types/src/basic_types/mod.rs | 2 +- types/src/basic_types/pack.rs | 24 +++++++-------- types/src/error.rs | 2 ++ 9 files changed, 77 insertions(+), 14 deletions(-) create mode 100644 bindings/wasm/src/utils.rs diff --git a/Makefile b/Makefile index 469dfc1d..400626d6 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,5 @@ +SHELL := /bin/bash + lint: cargo fmt cargo clippy --all --all-features -- -D warnings diff --git a/bindings/sdk/src/ffi.udl b/bindings/sdk/src/ffi.udl index 462b96b7..15e1f90e 100644 --- a/bindings/sdk/src/ffi.udl +++ b/bindings/sdk/src/ffi.udl @@ -7,6 +7,7 @@ enum TypeError { "SizeMismatch", "DecodeFromHexErr", "TooBigInteger", + "InvalidBigIntStr" }; [Custom] diff --git a/bindings/wasm/src/lib.rs b/bindings/wasm/src/lib.rs index 5894b515..4d63a896 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -3,5 +3,6 @@ pub mod rpc_client; pub mod rpc_type_converter; pub mod signer; pub mod tx_types; +pub mod utils; extern crate getrandom; diff --git a/bindings/wasm/src/utils.rs b/bindings/wasm/src/utils.rs new file mode 100644 index 00000000..39a0c395 --- /dev/null +++ b/bindings/wasm/src/utils.rs @@ -0,0 +1,23 @@ +use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; +use zklink_sdk_types::basic_types::pack::{ + closest_packable_fee_amount, closest_packable_token_amount, +}; +use zklink_sdk_types::basic_types::BigUint; +use zklink_sdk_types::error::TypeError; + +#[wasm_bindgen(js_name=closestPackableTransactionAmount)] +pub fn closest_packable_transaction_amount(amount: &str) -> Result { + let amount = + BigUint::from_str(amount).map_err(|e| TypeError::InvalidBigIntStr(e.to_string()))?; + let packable_amount = closest_packable_token_amount(&amount); + Ok(packable_amount.to_string()) +} + +#[wasm_bindgen(js_name=closestPackableTransactionFee)] +pub fn closest_packable_transaction_fee(fee: &str) -> Result { + let fee = BigUint::from_str(fee).map_err(|e| TypeError::InvalidBigIntStr(e.to_string()))?; + let packable_fee = closest_packable_fee_amount(&fee); + Ok(packable_fee.to_string()) +} diff --git a/bindings/wasm/tests/test_rpc.rs b/bindings/wasm/tests/test_rpc.rs index 607dc807..e886df7a 100644 --- a/bindings/wasm/tests/test_rpc.rs +++ b/bindings/wasm/tests/test_rpc.rs @@ -5,6 +5,7 @@ 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::pack::{is_fee_amount_packable, is_token_amount_packable}; 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; @@ -13,6 +14,9 @@ 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}; +use zklink_sdk_wasm::utils::{ + closest_packable_transaction_amount, closest_packable_transaction_fee, +}; wasm_bindgen_test_configure!(run_in_worker); #[wasm_bindgen_test] @@ -39,6 +43,34 @@ async fn test_account_query() { } } +#[wasm_bindgen_test] +fn test_closest_packable_amount() { + match closest_packable_transaction_amount("10000000012345678") { + Ok(amount) => { + web_sys::console::log_1(&JsValue::from_str(&amount)); + let amount = BigUint::from_str(&amount).unwrap(); + assert_eq!(is_token_amount_packable(&amount), true); + } + Err(e) => { + web_sys::console::log_1(&JsValue::from_str(&format!("{:?}", e))); + } + } +} + +#[wasm_bindgen_test] +fn test_closest_packable_fee() { + match closest_packable_transaction_fee("1234567876543") { + Ok(amount) => { + web_sys::console::log_1(&JsValue::from_str(&amount)); + let amount = BigUint::from_str(&amount).unwrap(); + assert_eq!(is_fee_amount_packable(&amount), true); + } + Err(e) => { + web_sys::console::log_1(&JsValue::from_str(&format!("{:?}", e))); + } + } +} + #[wasm_bindgen_test] async fn test_send_change_pubkey() { let client = RpcClient::new("devnet"); diff --git a/examples/Javascript/js-example/2_transfer.js b/examples/Javascript/js-example/2_transfer.js index e60a9e1a..6db25f75 100644 --- a/examples/Javascript/js-example/2_transfer.js +++ b/examples/Javascript/js-example/2_transfer.js @@ -6,7 +6,9 @@ async function main() { 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 amount = wasm.closestPackableTransactionAmount("1234567899808787"); + let fee = wasm.closestPackableTransactionFee("10000567777") + let tx_builder = new wasm.TransferBuilder(10, to_address, 1, 1, 18, fee, amount, 1,ts); let transfer = wasm.newTransfer(tx_builder); let signer = new wasm.Signer(private_key); let signature = signer.signTransfer(transfer,"USDC") diff --git a/types/src/basic_types/mod.rs b/types/src/basic_types/mod.rs index c62e0c2f..6da87b4d 100644 --- a/types/src/basic_types/mod.rs +++ b/types/src/basic_types/mod.rs @@ -14,7 +14,7 @@ pub use zklink_address::ZkLinkAddress; mod macros; pub mod bigunit_wrapper; pub(crate) mod float_convert; -pub(crate) mod pack; +pub mod pack; pub(crate) mod params; pub mod tx_hash; pub mod zklink_address; diff --git a/types/src/basic_types/pack.rs b/types/src/basic_types/pack.rs index 1717eac1..0ef1b13d 100644 --- a/types/src/basic_types/pack.rs +++ b/types/src/basic_types/pack.rs @@ -75,12 +75,12 @@ pub fn unpack_fee_amount(data: &[u8]) -> Option { .and_then(BigUint::from_u128) } -// /// Returns the closest possible packable token amount. -// /// Returned amount is always less or equal to the provided amount. -// pub fn closest_packable_fee_amount(amount: &BigUint) -> BigUint { -// let fee_packed = pack_fee_amount(amount); -// unpack_fee_amount(&fee_packed).expect("fee repacking") -// } +/// Returns the closest possible packable token amount. +/// Returned amount is always less or equal to the provided amount. +pub fn closest_packable_fee_amount(amount: &BigUint) -> BigUint { + let fee_packed = pack_fee_amount(amount); + unpack_fee_amount(&fee_packed).expect("fee repacking") +} // /// Returns the closest possible packable token amount. // /// Returned amount is always greater or equal to the provided amount. @@ -89,12 +89,12 @@ pub fn unpack_fee_amount(data: &[u8]) -> Option { // unpack_fee_amount(&fee_packed).expect("fee repacking") // } -// /// Returns the closest possible packable fee amount. -// /// Returned amount is always less or equal to the provided amount. -// pub fn closest_packable_token_amount(amount: &BigUint) -> BigUint { -// let fee_packed = pack_token_amount(amount); -// unpack_token_amount(&fee_packed).expect("token amount repacking") -// } +/// Returns the closest possible packable fee amount. +/// Returned amount is always less or equal to the provided amount. +pub fn closest_packable_token_amount(amount: &BigUint) -> BigUint { + let fee_packed = pack_token_amount(amount); + unpack_token_amount(&fee_packed).expect("token amount repacking") +} // /// Returns the closest possible packable fee amount. // /// Returned amount is always greater or equal to the provided amount. diff --git a/types/src/error.rs b/types/src/error.rs index bc448a42..f5b96064 100644 --- a/types/src/error.rs +++ b/types/src/error.rs @@ -16,6 +16,8 @@ pub enum TypeError { DecodeFromHexErr(String), #[error("Integer is too big")] TooBigInteger, + #[error("{0}")] + InvalidBigIntStr(String), } #[cfg(target_arch = "wasm32")] From 8ec56bb51551f404de33022ac9f143abb69fee93 Mon Sep 17 00:00:00 2001 From: nickwest Date: Fri, 13 Oct 2023 00:01:59 -0700 Subject: [PATCH 6/8] fix http error of nodejs example --- .../node-example/1_change_pubkey.js | 51 +++++++++++++++++++ examples/Javascript/node-example/README.md | 2 +- examples/Javascript/node-example/package.json | 6 +++ examples/Javascript/node-example/sign.js | 20 -------- 4 files changed, 58 insertions(+), 21 deletions(-) create mode 100644 examples/Javascript/node-example/1_change_pubkey.js create mode 100644 examples/Javascript/node-example/package.json delete mode 100644 examples/Javascript/node-example/sign.js diff --git a/examples/Javascript/node-example/1_change_pubkey.js b/examples/Javascript/node-example/1_change_pubkey.js new file mode 100644 index 00000000..ca94461f --- /dev/null +++ b/examples/Javascript/node-example/1_change_pubkey.js @@ -0,0 +1,51 @@ +const {ChangePubKeyBuilder,Signer,newChangePubkey,RpcClient } = require('./node-dist/zklink-sdk-node'); +// CommonJS +const fetch = require('node-fetch'); +const AbortController = require('abort-controller') + +// @ts-ignore +global.fetch = fetch; +// @ts-ignore +global.Headers = fetch.Headers; +// @ts-ignore +global.Request = fetch.Request; +// @ts-ignore +global.Response = fetch.Response; +// @ts-ignore +global.AbortController = AbortController; + +async function testEcdsaAuth() { + 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 ChangePubKeyBuilder( + 1,5,1,new_pubkey_hash,18,"100000000000000", + 1,"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001b", + ts); + let tx = newChangePubkey(tx_builder); + const signer = new 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 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 main() { + console.log(global); + await testEcdsaAuth(); +} + +main(); diff --git a/examples/Javascript/node-example/README.md b/examples/Javascript/node-example/README.md index 59eea116..3c143209 100644 --- a/examples/Javascript/node-example/README.md +++ b/examples/Javascript/node-example/README.md @@ -6,5 +6,5 @@ $ wasm-pack build --target nodejs --out-name=zklink-sdk-node --out-dir=not-dist ``` copy the node-dist to the dir,then run command: ``` -$ node sign.js +$ node 1_change_pubkey.js ``` diff --git a/examples/Javascript/node-example/package.json b/examples/Javascript/node-example/package.json new file mode 100644 index 00000000..a078f5a1 --- /dev/null +++ b/examples/Javascript/node-example/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "abort-controller": "^3.0.0", + "node-fetch": "^2.7.0" + } +} diff --git a/examples/Javascript/node-example/sign.js b/examples/Javascript/node-example/sign.js deleted file mode 100644 index 59aa4249..00000000 --- a/examples/Javascript/node-example/sign.js +++ /dev/null @@ -1,20 +0,0 @@ -const {ZklinkSignerWasm,ZklinkSignatureWasm} = require('./node-dist/zklink-sdk-node'); - -function main() { - try { - const msg = new Uint8Array([1, 2, 3, 4, 5]); - const zklink_signer = ZklinkSignerWasm.NewFromEthSigner("be725250b123a39dab5b7579334d5888987c72a58f4508062545fe6e08ca94f4"); - console.log(zklink_signer); - let l2_signature = zklink_signer.sign(msg); - console.log(l2_signature); - - let signature = ZklinkSignatureWasm.NewFromHexStr(l2_signature); - console.log(signature); - let verify = signature.verify(msg); - console.log(verify); - } catch (e) { - console.error(e); - } -} - -main(); From 0933e16d3c634474f1126db00d80be8e8a632814 Mon Sep 17 00:00:00 2001 From: nickwest Date: Tue, 17 Oct 2023 23:14:56 -0700 Subject: [PATCH 7/8] add support for json_rpc_signer in wasm --- Makefile | 5 +- bindings/wasm/Cargo.toml | 1 + bindings/wasm/src/json_rpc_signer.rs | 128 ++++++++++++++ bindings/wasm/src/lib.rs | 3 + bindings/wasm/src/tx_types/change_pubkey.rs | 9 +- bindings/wasm/src/tx_types/forced_exit.rs | 7 +- bindings/wasm/src/tx_types/transfer.rs | 7 +- bindings/wasm/src/tx_types/withdraw.rs | 7 +- .../Javascript/js-example/1_change_pubkey.js | 6 +- examples/Javascript/js-example/2_transfer.js | 9 +- .../Javascript/js-example/3_order_matching.js | 4 +- examples/Javascript/js-example/4_withdraw.js | 7 +- .../Javascript/js-example/5_forced_exit.js | 3 +- interface/Cargo.toml | 1 + interface/src/json_rpc_signer.rs | 160 ++++++++++++++++++ interface/src/lib.rs | 3 + interface/src/sign_change_pubkey.rs | 43 ++++- interface/src/sign_order.rs | 2 +- interface/src/sign_transfer.rs | 21 +++ interface/src/sign_withdraw.rs | 21 +++ interface/src/signer.rs | 2 +- provider/Cargo.toml | 1 + signers/Cargo.toml | 4 + signers/src/eth_signer/json_rpc_signer.rs | 95 +++++++++++ signers/src/eth_signer/mod.rs | 18 +- signers/src/lib.rs | 3 + signers/src/zklink_signer/pk_signer.rs | 11 ++ types/Cargo.toml | 1 + 28 files changed, 552 insertions(+), 30 deletions(-) create mode 100644 bindings/wasm/src/json_rpc_signer.rs create mode 100644 interface/src/json_rpc_signer.rs create mode 100644 signers/src/eth_signer/json_rpc_signer.rs diff --git a/Makefile b/Makefile index 400626d6..6f4c1b5c 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,8 @@ SHELL := /bin/bash lint: cargo fmt - cargo clippy --all --all-features -- -D warnings + cargo clippy --features ffi -- -D warnings + cargo clippy --features web -- -D warnings cargo sort bash -c "cd ./interface && cargo sort" bash -c "cd ./types && cargo sort" @@ -79,7 +80,7 @@ test_go: build_binding_files build_binding_lib build_wasm: prepare_wasm cd ${ROOT_DIR}/bindings/wasm && \ - wasm-pack build --release --target=web --out-name=zklink-sdk-web --out-dir=web-dist && \ + wasm-pack build --release --target=web --out-name=zklink-sdk-web --out-dir=web-dist -- --features web && \ 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: diff --git a/bindings/wasm/Cargo.toml b/bindings/wasm/Cargo.toml index 86277e24..7a3bc58e 100644 --- a/bindings/wasm/Cargo.toml +++ b/bindings/wasm/Cargo.toml @@ -29,6 +29,7 @@ uuid = "0.8" [features] default = [] ffi = [] +web = ["zklink_sdk_interface/web","zklink_sdk_signers/web"] [dev-dependencies] wasm-bindgen-test = "0.3" diff --git a/bindings/wasm/src/json_rpc_signer.rs b/bindings/wasm/src/json_rpc_signer.rs new file mode 100644 index 00000000..8858fb6d --- /dev/null +++ b/bindings/wasm/src/json_rpc_signer.rs @@ -0,0 +1,128 @@ +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::json_rpc_signer::JsonRpcSigner as InterfaceJsonRpcSigner; +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 JsonRpcSigner { + inner: InterfaceJsonRpcSigner, +} + +#[wasm_bindgen] +impl JsonRpcSigner { + #[wasm_bindgen(constructor)] + pub fn new() -> Result { + let inner = InterfaceJsonRpcSigner::new()?; + Ok(JsonRpcSigner { inner }) + } + + #[wasm_bindgen(js_name = initZklinkSigner)] + pub async fn init_zklink_signer(&mut self) -> Result<(), JsValue> { + Ok(self.inner.init_zklink_signer().await?) + } + + #[wasm_bindgen(js_name = signTransfer)] + pub async 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).await?; + Ok(serde_wasm_bindgen::to_value(&signature)?) + } + + #[wasm_bindgen(js_name=signChangePubkeyWithEthEcdsaAuth)] + pub async 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) + .await?; + 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=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 async 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).await?; + 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/lib.rs b/bindings/wasm/src/lib.rs index 4d63a896..12072955 100644 --- a/bindings/wasm/src/lib.rs +++ b/bindings/wasm/src/lib.rs @@ -1,6 +1,9 @@ #![cfg(target_arch = "wasm32")] +#[cfg(feature = "web")] +pub mod json_rpc_signer; pub mod rpc_client; pub mod rpc_type_converter; +#[cfg(not(feature = "web"))] pub mod signer; pub mod tx_types; pub mod utils; diff --git a/bindings/wasm/src/tx_types/change_pubkey.rs b/bindings/wasm/src/tx_types/change_pubkey.rs index 49124548..907a622f 100644 --- a/bindings/wasm/src/tx_types/change_pubkey.rs +++ b/bindings/wasm/src/tx_types/change_pubkey.rs @@ -91,8 +91,13 @@ impl ChangePubKeyBuilder { fee: String, nonce: u32, eth_signature: Option, - timestamp: u32, + ts: Option, ) -> Result { + let ts = if let Some(time_stamp) = ts { + time_stamp + } else { + std::time::UNIX_EPOCH.elapsed().unwrap().as_millis() as u32 + }; let eth_signature = if let Some(s) = eth_signature { Some(PackedEthSignature::from_hex(&s)?) } else { @@ -107,7 +112,7 @@ impl ChangePubKeyBuilder { fee: BigUint::from_str(&fee).unwrap(), nonce: nonce.into(), eth_signature, - timestamp: timestamp.into(), + timestamp: ts.into(), }; Ok(ChangePubKeyBuilder { inner }) } diff --git a/bindings/wasm/src/tx_types/forced_exit.rs b/bindings/wasm/src/tx_types/forced_exit.rs index 901f997a..4a6b92c5 100644 --- a/bindings/wasm/src/tx_types/forced_exit.rs +++ b/bindings/wasm/src/tx_types/forced_exit.rs @@ -35,8 +35,13 @@ impl ForcedExitBuilder { l1_target_token: u32, exit_amount: String, initiator_nonce: u32, - ts: u32, + ts: Option, ) -> Result { + let ts = if let Some(time_stamp) = ts { + time_stamp + } else { + std::time::UNIX_EPOCH.elapsed().unwrap().as_millis() as u32 + }; let inner = TxForcedExitBuilder { to_chain_id: to_chain_id.into(), initiator_account_id: initiator_account_id.into(), diff --git a/bindings/wasm/src/tx_types/transfer.rs b/bindings/wasm/src/tx_types/transfer.rs index 4b24c00a..a4d5454b 100644 --- a/bindings/wasm/src/tx_types/transfer.rs +++ b/bindings/wasm/src/tx_types/transfer.rs @@ -34,8 +34,13 @@ impl TransferBuilder { fee: String, amount: String, nonce: u32, - ts: u32, + ts: Option, ) -> Result { + let ts = if let Some(time_stamp) = ts { + time_stamp + } else { + std::time::UNIX_EPOCH.elapsed().unwrap().as_millis() as u32 + }; let inner = TxTransferBuilder { account_id: account_id.into(), to_address: ZkLinkAddress::from_hex(&to_address)?, diff --git a/bindings/wasm/src/tx_types/withdraw.rs b/bindings/wasm/src/tx_types/withdraw.rs index 5e19ade6..b7b01fd3 100644 --- a/bindings/wasm/src/tx_types/withdraw.rs +++ b/bindings/wasm/src/tx_types/withdraw.rs @@ -37,8 +37,13 @@ impl WithdrawBuilder { l1_target_token: u32, amount: String, nonce: u32, - ts: u32, + ts: Option, ) -> Result { + let ts = if let Some(time_stamp) = ts { + time_stamp + } else { + std::time::UNIX_EPOCH.elapsed().unwrap().as_millis() as u32 + }; let inner = TxWithdrawBuilder { account_id: account_id.into(), sub_account_id: sub_account_id.into(), diff --git a/examples/Javascript/js-example/1_change_pubkey.js b/examples/Javascript/js-example/1_change_pubkey.js index 8eb9925a..05a426e1 100644 --- a/examples/Javascript/js-example/1_change_pubkey.js +++ b/examples/Javascript/js-example/1_change_pubkey.js @@ -2,7 +2,6 @@ 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"; @@ -13,8 +12,9 @@ async function testEcdsaAuth() { 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); + const signer = new wasm.JsonRpcSigner(); + await signer.initZklinkSigner(); + let tx_signature = await signer.signChangePubkeyWithEthEcdsaAuth(tx,l1_client_id,main_contract); console.log(tx_signature); let submitter_signature = signer.submitterSignature(tx_signature.tx); diff --git a/examples/Javascript/js-example/2_transfer.js b/examples/Javascript/js-example/2_transfer.js index 6db25f75..c9d65ab5 100644 --- a/examples/Javascript/js-example/2_transfer.js +++ b/examples/Javascript/js-example/2_transfer.js @@ -2,16 +2,17 @@ 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 amount = wasm.closestPackableTransactionAmount("1234567899808787"); let fee = wasm.closestPackableTransactionFee("10000567777") - let tx_builder = new wasm.TransferBuilder(10, to_address, 1, 1, 18, fee, amount, 1,ts); + let tx_builder = new wasm.TransferBuilder(10, to_address, 1, + 1, 18, fee, amount, 1,ts); let transfer = wasm.newTransfer(tx_builder); - let signer = new wasm.Signer(private_key); - let signature = signer.signTransfer(transfer,"USDC") + let signer = new wasm.JsonRpcSigner(); + await signer.initZklinkSigner(); + let signature = await signer.signTransfer(transfer,"USDC") console.log(signature); let submitter_signature = signer.submitterSignature(signature.tx); diff --git a/examples/Javascript/js-example/3_order_matching.js b/examples/Javascript/js-example/3_order_matching.js index c95712f5..1ac15399 100644 --- a/examples/Javascript/js-example/3_order_matching.js +++ b/examples/Javascript/js-example/3_order_matching.js @@ -2,10 +2,10 @@ 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); + let signer = new wasm.JsonRpcSigner(); + await signer.initZklinkSigner(); //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); diff --git a/examples/Javascript/js-example/4_withdraw.js b/examples/Javascript/js-example/4_withdraw.js index 7028ea01..cc5cad74 100644 --- a/examples/Javascript/js-example/4_withdraw.js +++ b/examples/Javascript/js-example/4_withdraw.js @@ -2,14 +2,15 @@ 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") + let signer = new wasm.JsonRpcSigner(); + await signer.initZklinkSigner(); + let signature = await signer.signWithdraw(withdraw,"USDC") console.log(signature); let submitter_signature = signer.submitterSignature(signature.tx); diff --git a/examples/Javascript/js-example/5_forced_exit.js b/examples/Javascript/js-example/5_forced_exit.js index 185261c8..3fca3b4e 100644 --- a/examples/Javascript/js-example/5_forced_exit.js +++ b/examples/Javascript/js-example/5_forced_exit.js @@ -8,7 +8,8 @@ async function main() { 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 signer = new wasm.JsonRpcSigner(); + await signer.initZklinkSigner(); let signature = signer.signForcedExit(forced_exit) console.log(signature); diff --git a/interface/Cargo.toml b/interface/Cargo.toml index 8fec6cce..fcd071bd 100644 --- a/interface/Cargo.toml +++ b/interface/Cargo.toml @@ -13,3 +13,4 @@ zklink_sdk_types = { path = "../types" } [features] default = [] ffi = [] +web = [] diff --git a/interface/src/json_rpc_signer.rs b/interface/src/json_rpc_signer.rs new file mode 100644 index 00000000..395acde7 --- /dev/null +++ b/interface/src/json_rpc_signer.rs @@ -0,0 +1,160 @@ +use crate::error::SignError; +use crate::sign_change_pubkey::check_create2data; +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_signers::eth_signer::json_rpc_signer::JsonRpcSigner as EthJsonRpcSigner; +use zklink_sdk_signers::zklink_signer::{ZkLinkSignature, ZkLinkSigner}; +use zklink_sdk_types::basic_types::ZkLinkAddress; +use zklink_sdk_types::signatures::TxSignature; +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::{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; +use zklink_sdk_types::tx_type::ZkSignatureTrait; + +pub struct JsonRpcSigner { + zklink_signer: ZkLinkSigner, + eth_signer: EthJsonRpcSigner, +} + +impl JsonRpcSigner { + pub fn new() -> Result { + let eth_json_rpc_signer = EthJsonRpcSigner::new()?; + let default_zklink_signer = ZkLinkSigner::new()?; + Ok(Self { + zklink_signer: default_zklink_signer, + eth_signer: eth_json_rpc_signer, + }) + } + + pub async fn init_zklink_signer(&mut self) -> Result<(), SignError> { + let zklink_signer = ZkLinkSigner::new_from_eth_json_rpc_signer(&self.eth_signer).await?; + self.zklink_signer = zklink_signer; + Ok(()) + } + + pub async fn sign_transfer( + &self, + tx: Transfer, + token_symbol: &str, + ) -> Result { + sign_transfer(&self.eth_signer, &self.zklink_signer, tx, token_symbol).await + } + + fn do_sign_change_pubkey_with_create2data_auth( + &self, + mut tx: ChangePubKey, + create2data: Create2Data, + ) -> Result { + tx.sign(&self.zklink_signer)?; + let should_valid = tx.is_signature_valid(); + assert!(should_valid); + + // create onchain auth data + tx.eth_auth_data = ChangePubKeyAuthData::EthCreate2 { data: create2data }; + Ok(TxSignature { + tx: tx.into(), + eth_signature: None, + }) + } + + pub fn sign_change_pubkey_with_create2data_auth( + &self, + tx: ChangePubKey, + create2data: Create2Data, + from_account: ZkLinkAddress, + ) -> Result { + check_create2data(&self.zklink_signer, create2data.clone(), from_account)?; + self.do_sign_change_pubkey_with_create2data_auth(tx, create2data) + } + + pub fn sign_change_pubkey_with_onchain_auth_data( + &self, + mut tx: ChangePubKey, + ) -> Result { + tx.sign(&self.zklink_signer)?; + let should_valid = tx.is_signature_valid(); + assert!(should_valid); + // create onchain auth data + tx.eth_auth_data = ChangePubKeyAuthData::Onchain; + Ok(TxSignature { + tx: tx.into(), + eth_signature: None, + }) + } + + pub async fn do_sign_change_pubkey_with_eth_ecdsa_auth( + &self, + mut tx: ChangePubKey, + l1_client_id: u32, + main_contract_address: ZkLinkAddress, + ) -> Result { + tx.sign(&self.zklink_signer)?; + let should_valid = tx.is_signature_valid(); + assert!(should_valid); + + // create auth data + let typed_data = tx.to_eip712_request_payload(l1_client_id, &main_contract_address)?; + let eth_signature = self + .eth_signer + .sign_message_eip712(typed_data.raw_data.as_bytes()) + .await?; + + tx.eth_auth_data = ChangePubKeyAuthData::EthECDSA { eth_signature }; + + Ok(TxSignature { + tx: tx.into(), + eth_signature: None, + }) + } + + pub async fn sign_change_pubkey_with_eth_ecdsa_auth( + &self, + tx: ChangePubKey, + l1_client_id: u32, + main_contract_address: ZkLinkAddress, + ) -> Result { + self.do_sign_change_pubkey_with_eth_ecdsa_auth(tx, l1_client_id, main_contract_address) + .await + } + + pub async fn sign_withdraw( + &self, + tx: Withdraw, + l2_source_token_symbol: &str, + ) -> Result { + sign_withdraw( + &self.eth_signer, + &self.zklink_signer, + tx, + l2_source_token_symbol, + ) + .await + } + + pub fn sign_forced_exit(&self, tx: ForcedExit) -> Result { + let signature = sign_forced_exit(&self.zklink_signer, tx)?; + Ok(signature) + } + + pub fn sign_order(&self, order: &Order) -> Result { + let signed_order = sign_order(order, &self.zklink_signer)?; + Ok(signed_order) + } + + pub fn sign_order_matching(&self, tx: OrderMatching) -> Result { + let signature = sign_order_matching(&self.zklink_signer, tx)?; + Ok(signature) + } + + pub fn submitter_signature(&self, zklink_tx: &ZkLinkTx) -> Result { + let tx_hash = zklink_tx.tx_hash(); + let signature = self.zklink_signer.sign_musig(tx_hash.as_ref())?; + Ok(signature) + } +} diff --git a/interface/src/lib.rs b/interface/src/lib.rs index b26e34c8..13f29125 100644 --- a/interface/src/lib.rs +++ b/interface/src/lib.rs @@ -1,12 +1,15 @@ use zklink_sdk_types::tx_type::change_pubkey::Create2Data; pub mod error; +#[cfg(feature = "web")] +pub mod json_rpc_signer; pub mod sign_change_pubkey; pub mod sign_forced_exit; pub mod sign_order; pub mod sign_order_matching; pub mod sign_transfer; pub mod sign_withdraw; +#[cfg(not(feature = "web"))] pub mod signer; pub enum ChangePubKeyAuthRequest { diff --git a/interface/src/sign_change_pubkey.rs b/interface/src/sign_change_pubkey.rs index e2f12c6b..39e066ea 100644 --- a/interface/src/sign_change_pubkey.rs +++ b/interface/src/sign_change_pubkey.rs @@ -3,8 +3,11 @@ use crate::error::SignError; use crate::ChangePubKeyAuthRequest; #[cfg(feature = "ffi")] use std::sync::Arc; +#[cfg(feature = "web")] +use zklink_sdk_signers::eth_signer::json_rpc_signer::JsonRpcSigner; #[cfg(feature = "ffi")] use zklink_sdk_signers::eth_signer::packed_eth_signature::PackedEthSignature; +#[cfg(not(feature = "web"))] use zklink_sdk_signers::eth_signer::pk_signer::EthSigner; use zklink_sdk_signers::zklink_signer::pk_signer::ZkLinkSigner; use zklink_sdk_types::basic_types::ZkLinkAddress; @@ -14,7 +17,7 @@ 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; -#[cfg(not(feature = "ffi"))] +#[cfg(not(any(feature = "ffi", feature = "web")))] pub fn sign_change_pubkey( eth_signer: &EthSigner, zklink_singer: &ZkLinkSigner, @@ -50,6 +53,44 @@ pub fn sign_change_pubkey( }) } +#[cfg(feature = "web")] +pub async fn sign_change_pubkey( + eth_signer: &JsonRpcSigner, + zklink_singer: &ZkLinkSigner, + mut tx: ChangePubKey, + main_contract: ZkLinkAddress, + l1_client_id: u32, + account_address: ZkLinkAddress, + auth_request: ChangePubKeyAuthRequest, +) -> Result { + let eth_auth_data: Result = match auth_request { + ChangePubKeyAuthRequest::Onchain => Ok(ChangePubKeyAuthData::Onchain), + ChangePubKeyAuthRequest::EthECDSA => { + let typed_data = tx.to_eip712_request_payload(l1_client_id, &main_contract)?; + let eth_signature = eth_signer + .sign_message_eip712(typed_data.raw_data.as_bytes()) + .await?; + Ok(ChangePubKeyAuthData::EthECDSA { eth_signature }) + } + ChangePubKeyAuthRequest::EthCreate2 { data } => { + // check create2 data + let pubkey_hash = zklink_singer.public_key().public_key_hash(); + let from_address = data.get_address(pubkey_hash.data.as_ref()); + if from_address.as_bytes() != account_address.as_bytes() { + Err(SignError::IncorrectTx) + } else { + Ok(ChangePubKeyAuthData::EthCreate2 { data }) + } + } + }; + tx.eth_auth_data = eth_auth_data?; + tx.signature = zklink_singer.sign_musig(&tx.get_bytes())?; + Ok(TxSignature { + tx: tx.into(), + eth_signature: None, + }) +} + #[cfg(feature = "ffi")] pub fn eth_signature_of_change_pubkey( l1_client_id: u32, diff --git a/interface/src/sign_order.rs b/interface/src/sign_order.rs index ad90bc20..e3572760 100644 --- a/interface/src/sign_order.rs +++ b/interface/src/sign_order.rs @@ -4,7 +4,7 @@ use zklink_sdk_signers::zklink_signer::error::ZkSignerError; 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(order: &Order, zklink_signer: &ZkLinkSigner) -> Result { let mut order = order.clone(); order.signature = zklink_signer.sign_musig(&order.get_bytes())?; diff --git a/interface/src/sign_transfer.rs b/interface/src/sign_transfer.rs index 5847068f..1a1126de 100644 --- a/interface/src/sign_transfer.rs +++ b/interface/src/sign_transfer.rs @@ -1,12 +1,16 @@ use crate::error::SignError; #[cfg(feature = "ffi")] use std::sync::Arc; +#[cfg(feature = "web")] +use zklink_sdk_signers::eth_signer::json_rpc_signer::JsonRpcSigner; +#[cfg(not(feature = "web"))] use zklink_sdk_signers::eth_signer::pk_signer::EthSigner; use zklink_sdk_signers::zklink_signer::pk_signer::ZkLinkSigner; use zklink_sdk_types::prelude::TxSignature; use zklink_sdk_types::tx_type::transfer::Transfer; use zklink_sdk_types::tx_type::TxTrait; +#[cfg(not(feature = "web"))] pub fn sign_transfer( eth_signer: &EthSigner, zklink_syner: &ZkLinkSigner, @@ -23,6 +27,23 @@ pub fn sign_transfer( }) } +#[cfg(feature = "web")] +pub async fn sign_transfer( + eth_signer: &JsonRpcSigner, + zklink_syner: &ZkLinkSigner, + mut tx: Transfer, + token_symbol: &str, +) -> Result { + tx.signature = zklink_syner.sign_musig(&tx.get_bytes())?; + let message = tx.get_eth_sign_msg(token_symbol).as_bytes().to_vec(); + let eth_signature = eth_signer.sign_message(&message).await?; + + Ok(TxSignature { + tx: tx.into(), + eth_signature: Some(eth_signature), + }) +} + #[cfg(feature = "ffi")] pub fn create_signed_transfer( zklink_syner: Arc, diff --git a/interface/src/sign_withdraw.rs b/interface/src/sign_withdraw.rs index 67022c39..63b5d77e 100644 --- a/interface/src/sign_withdraw.rs +++ b/interface/src/sign_withdraw.rs @@ -1,6 +1,9 @@ use crate::error::SignError; #[cfg(feature = "ffi")] use std::sync::Arc; +#[cfg(feature = "web")] +use zklink_sdk_signers::eth_signer::json_rpc_signer::JsonRpcSigner; +#[cfg(not(feature = "web"))] use zklink_sdk_signers::eth_signer::pk_signer::EthSigner; use zklink_sdk_signers::zklink_signer::pk_signer::ZkLinkSigner; use zklink_sdk_types::prelude::TxSignature; @@ -9,6 +12,7 @@ use zklink_sdk_types::tx_type::withdraw::Withdraw; use zklink_sdk_types::tx_type::TxTrait; use zklink_sdk_types::tx_type::ZkSignatureTrait; +#[cfg(not(feature = "web"))] pub fn sign_withdraw( eth_signer: &EthSigner, zklink_singer: &ZkLinkSigner, @@ -25,6 +29,23 @@ pub fn sign_withdraw( }) } +#[cfg(feature = "web")] +pub async fn sign_withdraw( + eth_signer: &JsonRpcSigner, + zklink_singer: &ZkLinkSigner, + mut tx: Withdraw, + l2_source_token_symbol: &str, +) -> Result { + tx.sign(zklink_singer)?; + let message = tx.get_eth_sign_msg(l2_source_token_symbol); + let eth_signature = eth_signer.sign_message(message.as_bytes()).await?; + + Ok(TxSignature { + tx: tx.into(), + eth_signature: Some(eth_signature), + }) +} + #[cfg(feature = "ffi")] pub fn create_signed_withdraw( zklink_singer: Arc, diff --git a/interface/src/signer.rs b/interface/src/signer.rs index f63ca8a2..3c9e3b7c 100644 --- a/interface/src/signer.rs +++ b/interface/src/signer.rs @@ -191,7 +191,7 @@ impl Signer { ) } - #[cfg(not(feature = "ffi"))] + #[cfg(not(any(feature = "ffi")))] pub fn sign_withdraw( &self, tx: Withdraw, diff --git a/provider/Cargo.toml b/provider/Cargo.toml index 2966b8f3..0ec2721c 100644 --- a/provider/Cargo.toml +++ b/provider/Cargo.toml @@ -30,3 +30,4 @@ wasm-bindgen-test = "0.3" [features] default = [] ffi = [] +web =[] diff --git a/signers/Cargo.toml b/signers/Cargo.toml index 6ac4f1cf..a9c73fc5 100644 --- a/signers/Cargo.toml +++ b/signers/Cargo.toml @@ -9,19 +9,23 @@ ethers = "2.0.10" ethers_primitives = "0.2.2" franklin_crypto = { package = "franklin-crypto", version = "0.0.5", git = "https://github.com/zkLinkProtocol/franklin-crypto.git", branch = "beta" } hex = "0.4" +js-sys = "0.3.64" k256 = { version = "0.13.1", features = ["ecdsa","sha256"] } primitive-types = { version = "0.12", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } +serde-wasm-bindgen = "0.5" serde_eip712 = "0.2.2" serde_json = "1.0" sha2 = "0.10" thiserror = "1.0" wasm-bindgen = { version = "0.2.87", features = ["serde-serialize"] } +wasm-bindgen-futures = "0.4" zklink_sdk_utils = { path = "../utils" } [features] default = [] ffi = [] +web = [] [dev-dependencies] futures = "0.3" diff --git a/signers/src/eth_signer/json_rpc_signer.rs b/signers/src/eth_signer/json_rpc_signer.rs new file mode 100644 index 00000000..6fae3d5a --- /dev/null +++ b/signers/src/eth_signer/json_rpc_signer.rs @@ -0,0 +1,95 @@ +use crate::eth_signer::{EthSignerError, PackedEthSignature}; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +#[derive(Serialize, Deserialize)] +struct RequestArguments { + method: String, + params: Vec, +} + +#[wasm_bindgen] +// Rustfmt removes the 'async' keyword from async functions in extern blocks. It's fixed +// in rustfmt 2. +#[rustfmt::skip] +extern "C" { + #[derive(Clone, Debug)] + /// An EIP-1193 provider object. Available by convention at `window.ethereum` + pub type Provider; + + #[wasm_bindgen(catch, method)] + async fn request(_: &Provider, args: JsValue) -> Result; + + #[wasm_bindgen(method,getter)] + fn selectedAddress(this: &Provider) -> Option; +} + +#[wasm_bindgen(inline_js = "export function get_provider_js() {return window.ethereum}")] +extern "C" { + #[wasm_bindgen(catch)] + fn get_provider_js() -> Result, JsValue>; +} + +pub fn get_provider() -> Result, EthSignerError> { + get_provider_js().map_err(|_e| EthSignerError::MissingEthSigner) +} + +pub struct JsonRpcSigner { + provider: Provider, +} + +impl JsonRpcSigner { + pub fn new() -> Result { + let provider = get_provider()?; + if provider.is_none() { + return Err(EthSignerError::MissingEthSigner); + } + Ok(JsonRpcSigner { + provider: provider.unwrap(), + }) + } + + pub async fn sign_message(&self, message: &[u8]) -> Result { + let provider_address = self.provider.selectedAddress(); + let mut params = Vec::new(); + let msg_str = + std::str::from_utf8(message).map_err(|e| EthSignerError::CustomError(e.to_string()))?; + params.push(serde_json::to_value(msg_str).unwrap()); + params.push(serde_json::to_value(provider_address).unwrap()); + let req_params = RequestArguments { + method: "personal_sign".to_string(), + params, + }; + let params = serde_wasm_bindgen::to_value(&req_params) + .map_err(|e| EthSignerError::CustomError(e.to_string()))?; + let signature = self.provider.request(params).await.map_err(|e| { + EthSignerError::SigningFailed(serde_wasm_bindgen::from_value::(e).unwrap()) + })?; + let signature = serde_wasm_bindgen::from_value::(signature).unwrap(); + PackedEthSignature::from_hex(&signature) + } + + pub async fn sign_message_eip712( + &self, + message: &[u8], + ) -> Result { + let provider_address = self.provider.selectedAddress(); + let mut params = Vec::new(); + let msg_str = + std::str::from_utf8(message).map_err(|e| EthSignerError::CustomError(e.to_string()))?; + params.push(serde_json::to_value(provider_address).unwrap()); + params.push(serde_json::to_value(msg_str).unwrap()); + let req_params = RequestArguments { + method: "eth_signTypedData_v4".to_string(), + params, + }; + let params = serde_wasm_bindgen::to_value(&req_params) + .map_err(|e| EthSignerError::CustomError(e.to_string()))?; + let signature = self.provider.request(params).await.map_err(|e| { + EthSignerError::SigningFailed(serde_wasm_bindgen::from_value::(e).unwrap()) + })?; + let signature = serde_wasm_bindgen::from_value::(signature).unwrap(); + PackedEthSignature::from_hex(&signature) + } +} diff --git a/signers/src/eth_signer/mod.rs b/signers/src/eth_signer/mod.rs index 64c0cc4b..350a257f 100644 --- a/signers/src/eth_signer/mod.rs +++ b/signers/src/eth_signer/mod.rs @@ -1,17 +1,21 @@ +pub use ethers_primitives::Address as EIP712Address; +pub use primitive_types::{H160, H256, U256}; +use serde::{Deserialize, Serialize}; + +pub use eip1271_signature::EIP1271Signature; +pub use error::EthSignerError; +pub use packed_eth_signature::PackedEthSignature; +pub use pk_signer::EthSigner; + pub mod eip1271_signature; pub mod eip712; pub mod error; pub mod packed_eth_signature; pub mod pk_signer; +// #[cfg(feature = "web")] +pub mod json_rpc_signer; -pub use primitive_types::{H160, H256, U256}; pub type Address = H160; -pub use eip1271_signature::EIP1271Signature; -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, Serialize, Deserialize)] pub struct EthTypedData { diff --git a/signers/src/lib.rs b/signers/src/lib.rs index d8941e57..71379ddb 100644 --- a/signers/src/lib.rs +++ b/signers/src/lib.rs @@ -5,3 +5,6 @@ pub mod eth_signer; pub mod starknet_signer; pub mod zklink_signer; + +extern crate js_sys; +extern crate wasm_bindgen_futures; diff --git a/signers/src/zklink_signer/pk_signer.rs b/signers/src/zklink_signer/pk_signer.rs index 1ff4f3c7..28272c63 100644 --- a/signers/src/zklink_signer/pk_signer.rs +++ b/signers/src/zklink_signer/pk_signer.rs @@ -13,6 +13,8 @@ use franklin_crypto::eddsa::{PrivateKey as FLPrivateKey, PrivateKey, PublicKey, use sha2::{Digest, Sha256}; use std::fmt; +#[cfg(feature = "web")] +use crate::eth_signer::json_rpc_signer::JsonRpcSigner; use crate::eth_signer::pk_signer::EthSigner; pub struct ZkLinkSigner(EddsaPrivKey); @@ -96,6 +98,15 @@ impl ZkLinkSigner { Self::new_from_seed(&seed) } + #[cfg(feature = "web")] + pub async fn new_from_eth_json_rpc_signer(eth_signer: &JsonRpcSigner) -> Result { + let signature = eth_signer + .sign_message(Self::SIGN_MESSAGE.as_bytes()) + .await?; + let seed = signature.serialize_packed(); + Self::new_from_seed(&seed) + } + pub fn new_from_bytes(bytes: &[u8]) -> Result { let mut fs_repr = FsRepr::default(); fs_repr diff --git a/types/Cargo.toml b/types/Cargo.toml index e64ea4e6..cd8a50e6 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -22,3 +22,4 @@ zklink_sdk_utils = { path = "../utils" } default = [] #ffi = ["uniffi", "uniffi_macros"] ffi = [] +web = [] From 085fca0ee10c9fa34f4bad482dcac26817f6aa9d Mon Sep 17 00:00:00 2001 From: nickwest Date: Wed, 18 Oct 2023 01:23:51 -0700 Subject: [PATCH 8/8] add clippy check for default-features on lint task --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 6f4c1b5c..ead3f5ef 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ lint: cargo fmt cargo clippy --features ffi -- -D warnings cargo clippy --features web -- -D warnings + cargo clippy -- -D warnings cargo sort bash -c "cd ./interface && cargo sort" bash -c "cd ./types && cargo sort"