From dbe8c353605018385569b7ac3081e151ae984c24 Mon Sep 17 00:00:00 2001 From: jonghwan lee Date: Mon, 29 May 2023 16:38:34 +0800 Subject: [PATCH] Problem: not exposing c++ api for wc 2.0 sign , send tx #453 working print qr port wc 1.0 apis reformat tidy up testing send tx add makefile add qrcode move use send_tx code added add signing --- demo/extra.cc | 102 +++++- demo/main.cc | 18 +- extra-cpp-bindings/src/lib.rs | 33 ++ extra-cpp-bindings/src/walletconnect2.rs | 390 +++++++++++++++++++++++ wallet-connect/Cargo.toml | 2 + wallet-connect/Makefile | 2 + wallet-connect/examples/web3_v2.rs | 208 +++++++++++- wallet-connect/src/v2/client.rs | 3 + 8 files changed, 745 insertions(+), 13 deletions(-) create mode 100644 wallet-connect/Makefile diff --git a/demo/extra.cc b/demo/extra.cc index b3570938..c083c247 100644 --- a/demo/extra.cc +++ b/demo/extra.cc @@ -5,6 +5,7 @@ #include "sdk/include/rust/cxx.h" #include "third_party/easywsclient/easywsclient.hpp" #include "third_party/json/single_include/nlohmann/json.hpp" +#include #include #include #include @@ -364,7 +365,7 @@ void test_wallet_connect() { * @description basic test for wallet connect 2.0 */ -void test_wallet_connect2() { +void test_wallet_connect2_old() { std::string mycronosrpc = getEnv("CRONOSRPC").c_str(); bool test_personal = true; bool test_basic = false; @@ -430,6 +431,105 @@ void test_wallet_connect2() { } } +void test_wallet_connect2() { + std::string mycronosrpc = getEnv("CRONOSRPC").c_str(); + bool test_personal = false; + bool test_basic = true; + bool test_nft = false; + std::string filename = "sessioninfo2.json"; + bool exit_program = false; + try { + Box client = make_new_client2(filename); + String uri = client->print_uri(); + std::cout << "uri= " << uri.c_str() << std::endl; + WalletConnect2EnsureSessionResult result = + client->ensure_session_blocking(60000); + std::cout << "session result=" << result.eip155.accounts.size() + << std::endl; + + std::cout << "ping" << endl; + String pingresult = client->ping_blocking(60000); + std::cout << "ping result=" << pingresult.c_str() << std::endl; + + // spawn thread + std::thread pollingthread([&]() { + try { + bool *exitthread = &exit_program; + while (!(*exitthread)) { + try { + String ret = client->poll_events_blocking(1000); + std::cout << "poll events result=" << ret.c_str() + << std::endl; + } catch (const std::exception &e) { + } + } + } catch (const std::exception &e) { + std::cout << "wallet connect error=" << e.what() << std::endl; + } + }); + String sessioninfo = client->save_client(); + { + ofstream outfile(filename); + outfile.write(sessioninfo.c_str(), sessioninfo.size()); + } + + assert(result.eip155.accounts.size() > 0); + + if (test_personal) { + Vec sig1 = client->sign_personal_blocking( + "hello", result.eip155.accounts.at(0).address.address); + std::cout << "signature length=" << sig1.size() << endl; + } + + if (test_basic) { + std::string fromaddress = getenv("MYFROMADDRESS"); + std::cout << "mycronosrpc=" << mycronosrpc << endl; + std::cout << "fromaddress=" << fromaddress << endl; + std::string toaddress = getenv("MYTOADDRESS"); + std::cout << "toaddress=" << toaddress << endl; + std::string mynonce = org::defi_wallet_core::get_eth_nonce( + fromaddress.c_str(), mycronosrpc) + .c_str(); + std::cout << "nonce=" << mynonce << endl; + WalletConnectTxEip155 info; + info.to = toaddress; + info.common.gas_limit = "21000"; // gas limit + info.common.gas_price = "10000"; // gas price + info.value = "100000000000000"; // 0.0001 eth + info.data = Vec(); + info.common.nonce = mynonce; + info.common.chainid = 1; + + assert(result.eip155.accounts.size() > 0); + Vec rawtx = client->sign_eip155_transaction_blocking( + info, result.eip155.accounts[0].address.address); + + auto receipt = org::defi_wallet_core::broadcast_eth_signed_raw_tx( + rawtx, mycronosrpc, 3000); + std::cout << "transaction_hash=" + << bytes_to_hex_string(receipt.transaction_hash).c_str() + << endl; + } + + + + std::cout << "enter q to exit" << std::endl; + while (true) { + // read input, if q is pressed, quit + char c = getchar(); + if (c == 'q') { + exit_program = true; + std::cout << "exit program" << endl; + break; + } + } + pollingthread.join(); + + } catch (const std::exception &e) { + std::cout << "wallet connect error=" << e.what() << std::endl; + } +} + // pay api examples void test_crypto_pay() { if (PAY_API_KEY == "") diff --git a/demo/main.cc b/demo/main.cc index e84e4dbb..7807aece 100644 --- a/demo/main.cc +++ b/demo/main.cc @@ -18,14 +18,16 @@ int main(int argc, char *argv[]) { try { - chainmain_process(); // chain-main - test_chainmain_nft(); // chainmain nft tests - test_login(); // decentralized login - cronos_process(); // cronos - test_cronos_testnet(); // cronos testnet - test_interval(); - test_blackscout_cronoscan(); - test_wallet_connect(); + /* chainmain_process(); // chain-main + test_chainmain_nft(); // chainmain nft tests + test_login(); // decentralized login + cronos_process(); // cronos + test_cronos_testnet(); // cronos testnet + test_interval(); + test_blackscout_cronoscan(); + test_wallet_connect();I*/ + std::cout << "hello world" << std::endl; + test_wallet_connect2(); } catch (const std::exception &e) { // Use `Assertion failed`, the same as `assert` function diff --git a/extra-cpp-bindings/src/lib.rs b/extra-cpp-bindings/src/lib.rs index e0c6b85a..7a4b6034 100644 --- a/extra-cpp-bindings/src/lib.rs +++ b/extra-cpp-bindings/src/lib.rs @@ -334,6 +334,7 @@ mod ffi { pub fn save_client(self: &mut Walletconnect2Client) -> Result; /// print qrcode in termal, for debugging pub fn print_uri(self: &mut WalletconnectClient) -> Result; + pub fn print_uri(self: &mut Walletconnect2Client) -> Result; /// sign message pub fn sign_personal_blocking( self: &mut WalletconnectClient, @@ -354,6 +355,11 @@ mod ffi { info: &WalletConnectTxEip155, address: [u8; 20], ) -> Result>; + pub fn sign_eip155_transaction_blocking( + self: &mut Walletconnect2Client, + info: &WalletConnectTxEip155, + address: [u8; 20], + ) -> Result>; /// send cronos(eth) eip155 transaction /// Supported Wallets: Trust Wallet, MetaMask and Crypto.com Mobile Defi Wallet @@ -362,6 +368,11 @@ mod ffi { info: &WalletConnectTxEip155, address: [u8; 20], ) -> Result>; + pub fn send_eip155_transaction_blocking( + self: &mut Walletconnect2Client, + info: &WalletConnectTxEip155, + address: [u8; 20], + ) -> Result>; /// eip1559_transaction_request: json string of Eip1559TransactionRequest /// return signed transaction bytes @@ -370,6 +381,11 @@ mod ffi { eip1559_transaction_request: String, address: [u8; 20], ) -> Result>; + pub fn sign_transaction( + self: &mut Walletconnect2Client, + eip1559_transaction_request: String, + address: [u8; 20], + ) -> Result>; /// eip1559_transaction_request: json string of Eip1559TransactionRequest /// return transaction hash bytes @@ -378,6 +394,11 @@ mod ffi { eip1559_transaction_request: String, address: [u8; 20], ) -> Result>; + pub fn send_transaction( + self: &mut Walletconnect2Client, + eip1559_transaction_request: String, + address: [u8; 20], + ) -> Result>; /// sign a contract transaction /// contract_action is a json string of `ContractAction` type, for example: @@ -398,6 +419,12 @@ mod ffi { common: &WalletConnectTxCommon, address: [u8; 20], ) -> Result>; + pub fn sign_contract_transaction( + self: &mut Walletconnect2Client, + contract_action: String, + common: &WalletConnectTxCommon, + address: [u8; 20], + ) -> Result>; // send a contract transaction /// contract_action is a json string of `ContractAction` type @@ -418,6 +445,12 @@ mod ffi { common: &WalletConnectTxCommon, address: [u8; 20], ) -> Result>; + pub fn send_contract_transaction( + self: &mut Walletconnect2Client, + contract_action: String, + common: &WalletConnectTxCommon, + address: [u8; 20], + ) -> Result>; /// returns the transactions of a given address. /// The API key can be obtained from https://cronoscan.com diff --git a/extra-cpp-bindings/src/walletconnect2.rs b/extra-cpp-bindings/src/walletconnect2.rs index 58b5f4ac..6822e710 100644 --- a/extra-cpp-bindings/src/walletconnect2.rs +++ b/extra-cpp-bindings/src/walletconnect2.rs @@ -1,9 +1,22 @@ use crate::ffi::WalletConnect2Eip155Accounts; use crate::ffi::WalletConnect2EnsureSessionResult; use crate::ffi::WalletConnectAddress; +use crate::ffi::WalletConnectTxCommon; use anyhow::{anyhow, Result}; use defi_wallet_connect::v2::Namespaces; use defi_wallet_connect::v2::{Client, ClientOptions, SessionInfo}; +use qrcodegen::{QrCode, QrCodeEcc}; + +use defi_wallet_connect::v2::WCMiddleware; + +use ethers::core::types::transaction::eip2718::TypedTransaction; + +use ethers::prelude::{Address, Eip1559TransactionRequest, NameOrAddress, U256}; +use ethers::prelude::{Middleware, Signature, TxHash}; +use ethers::types::H160; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + pub struct Walletconnect2Client { pub client: Option, pub rt: tokio::runtime::Runtime, // need to use the same runtime, otherwise c++ side crash @@ -11,6 +24,28 @@ pub struct Walletconnect2Client { pub rx: tokio::sync::mpsc::UnboundedReceiver, // receiver } +#[derive(Serialize, Deserialize)] +enum ContractAction { + ContractApproval(defi_wallet_core_common::ContractApproval), + ContractTransfer(defi_wallet_core_common::ContractTransfer), +} + +async fn sign_typed_tx( + client: Client, + tx: &TypedTransaction, + address: Address, +) -> Result { + let middleware = WCMiddleware::new(client); + let signature = middleware.sign_transaction(tx, address).await?; + Ok(signature) +} + +async fn send_typed_tx(client: Client, tx: TypedTransaction, address: Address) -> Result { + let middleware = WCMiddleware::new(client).with_sender(address); + let receipt = middleware.send_transaction(tx, None).await?.tx_hash(); + Ok(receipt) +} + pub async fn restore_client( contents: String, callback_sender: Option>, @@ -65,6 +100,37 @@ impl Walletconnect2Client { }, ) } + + fn print_qr(qr: &QrCode) { + let border: i32 = 1; + for y in -border..qr.size() + border { + for x in -border..qr.size() + border { + let c = if qr.get_module(x, y) { + "\x1b[40m \x1b[0m" + } else { + "\x1b[47m \x1b[0m" + }; + print!("{c}"); + } + println!(); + } + println!(); + } + + /// print uri(qrcode) for debugging + pub fn print_uri(&mut self) -> Result { + if let Some(client) = self.client.as_ref() { + let result = self.rt.block_on(client.get_session_info()); + let uristring = result.uri(); + if let Ok(qr) = QrCode::encode_text(&uristring, QrCodeEcc::Medium) { + Self::print_qr(&qr); + } + Ok(uristring) + } else { + anyhow::bail!("no client"); + } + } + pub fn sign_personal_blocking( &mut self, message: String, @@ -152,4 +218,328 @@ impl Walletconnect2Client { }, ) } + + /// build cronos(eth) eip155 transaction + pub fn sign_eip155_transaction_blocking( + &mut self, + userinfo: &crate::ffi::WalletConnectTxEip155, + address: [u8; 20], + ) -> Result> { + if self.client.is_none() { + anyhow::bail!("no client"); + } + + let client = self + .client + .as_ref() + .ok_or_else(|| anyhow!("get walllet-connect client error"))?; + let signeraddress = Address::from_slice(&address); + + let mut tx = Eip1559TransactionRequest::new(); + + if !userinfo.to.is_empty() { + tx = tx.to(NameOrAddress::Address(Address::from_str(&userinfo.to)?)); + } + if !userinfo.data.is_empty() { + tx = tx.data(userinfo.data.as_slice().to_vec()); + } + if !userinfo.common.gas_limit.is_empty() { + tx = tx.gas(U256::from_dec_str(&userinfo.common.gas_limit)?); + } + if !userinfo.common.gas_price.is_empty() { + tx = tx + .max_priority_fee_per_gas(U256::from_dec_str(&userinfo.common.gas_price)?) + .max_fee_per_gas(U256::from_dec_str(&userinfo.common.gas_price)?); + } + if !userinfo.common.nonce.is_empty() { + tx = tx.nonce(U256::from_dec_str(&userinfo.common.nonce)?); + } + if !userinfo.common.chainid == 0 { + tx = tx.chain_id(userinfo.common.chainid); + } + if !userinfo.value.is_empty() { + tx = tx.value(U256::from_dec_str(&userinfo.value)?); + } + let newclient = client.clone(); + let typedtx = TypedTransaction::Eip1559(tx); + + let sig = self + .rt + .block_on(sign_typed_tx(newclient, &typedtx, signeraddress)) + .map_err(|e| anyhow!("sign_typed_transaction error {}", e.to_string()))?; + + let signed_tx = &typedtx.rlp_signed(&sig); + Ok(signed_tx.to_vec()) + } + + /// send cronos(eth) eip155 transaction + pub fn send_eip155_transaction_blocking( + &mut self, + userinfo: &crate::ffi::WalletConnectTxEip155, + address: [u8; 20], + ) -> Result> { + if self.client.is_none() { + anyhow::bail!("no client"); + } + + let client = self + .client + .as_ref() + .ok_or_else(|| anyhow!("get walllet-connect client error"))?; + let signeraddress = Address::from_slice(&address); + + let mut tx = Eip1559TransactionRequest::new(); + + if !userinfo.to.is_empty() { + tx = tx.to(NameOrAddress::Address(Address::from_str(&userinfo.to)?)); + } + if !userinfo.data.is_empty() { + tx = tx.data(userinfo.data.as_slice().to_vec()); + } + if !userinfo.common.gas_limit.is_empty() { + tx = tx.gas(U256::from_dec_str(&userinfo.common.gas_limit)?); + } + if !userinfo.common.gas_price.is_empty() { + tx = tx + .max_priority_fee_per_gas(U256::from_dec_str(&userinfo.common.gas_price)?) + .max_fee_per_gas(U256::from_dec_str(&userinfo.common.gas_price)?); + } + if !userinfo.common.nonce.is_empty() { + tx = tx.nonce(U256::from_dec_str(&userinfo.common.nonce)?); + } + if !userinfo.common.chainid == 0 { + tx = tx.chain_id(userinfo.common.chainid); + } + if !userinfo.value.is_empty() { + tx = tx.value(U256::from_dec_str(&userinfo.value)?); + } + + let newclient = client.clone(); + let typedtx = TypedTransaction::Eip1559(tx); + + let tx_bytes = self + .rt + .block_on(send_typed_tx(newclient, typedtx, signeraddress)) + .map_err(|e| anyhow!("send_typed_transaction error {}", e.to_string()))?; + + Ok(tx_bytes.0.to_vec()) + } + + fn get_signed_tx_raw_bytes( + &self, + newclient: Client, + signeraddress: H160, + typedtx: &mut TypedTransaction, + common: &WalletConnectTxCommon, + ) -> Result> { + let mynonce = U256::from_dec_str(&common.nonce)?; + if !mynonce.is_zero() { + typedtx.set_nonce(mynonce); + } + typedtx.set_from(signeraddress); + if !common.chainid == 0 { + typedtx.set_chain_id(common.chainid); + } + if !common.gas_limit.is_empty() { + typedtx.set_gas(U256::from_dec_str(&common.gas_limit)?); + } + if !common.gas_price.is_empty() { + typedtx.set_gas_price(U256::from_dec_str(&common.gas_price)?); + } + + let sig = self + .rt + .block_on(sign_typed_tx(newclient, typedtx, signeraddress)) + .map_err(|e| anyhow!("sign_typed_transaction error {}", e.to_string()))?; + + let signed_tx = &typedtx.rlp_signed(&sig); + Ok(signed_tx.to_vec()) + } + + fn get_sent_tx_raw_bytes( + &self, + newclient: Client, + signeraddress: H160, + typedtx: &mut TypedTransaction, + common: &WalletConnectTxCommon, + ) -> Result> { + let mynonce = U256::from_dec_str(&common.nonce)?; + if !mynonce.is_zero() { + typedtx.set_nonce(mynonce); + } + typedtx.set_from(signeraddress); + if !common.chainid == 0 { + typedtx.set_chain_id(common.chainid); + } + if !common.gas_limit.is_empty() { + typedtx.set_gas(U256::from_dec_str(&common.gas_limit)?); + } + if !common.gas_price.is_empty() { + typedtx.set_gas_price(U256::from_dec_str(&common.gas_price)?); + } + + let tx_bytes = self + .rt + .block_on(send_typed_tx(newclient, typedtx.clone(), signeraddress)) + .map_err(|e| anyhow!("send_typed_transaction error {}", e.to_string()))?; + + Ok(tx_bytes.0.to_vec()) + } + + pub fn sign_transaction( + &mut self, + eip1559_transaction_request: String, + address: [u8; 20], + ) -> Result> { + if self.client.is_none() { + anyhow::bail!("no client"); + } + + let client = self + .client + .as_ref() + .ok_or_else(|| anyhow!("get walllet-connect client error"))?; + let signeraddress = Address::from_slice(&address); + + // parse json string transaction_info to TransactionRequest + let tx: Eip1559TransactionRequest = serde_json::from_str(&eip1559_transaction_request)?; + let typedtx = TypedTransaction::Eip1559(tx); + + let newclient = client.clone(); + let sig = self + .rt + .block_on(sign_typed_tx(newclient, &typedtx, signeraddress)) + .map_err(|e| anyhow!("sign_typed_transaction error {}", e.to_string()))?; + + let signed_tx = &typedtx.rlp_signed(&sig); + Ok(signed_tx.to_vec()) + } + + pub fn send_transaction( + &mut self, + eip1559_transaction_request: String, + address: [u8; 20], + ) -> Result> { + if self.client.is_none() { + anyhow::bail!("no client"); + } + + let client = self + .client + .as_ref() + .ok_or_else(|| anyhow!("get walllet-connect client error"))?; + let signeraddress = Address::from_slice(&address); + + // parse json string transaction_info to TransactionRequest + let tx: Eip1559TransactionRequest = serde_json::from_str(&eip1559_transaction_request)?; + let typedtx = TypedTransaction::Eip1559(tx); + + let newclient = client.clone(); + let tx_bytes = self + .rt + .block_on(send_typed_tx(newclient, typedtx, signeraddress)) + .map_err(|e| anyhow!("send_typed_transaction error {}", e.to_string()))?; + + Ok(tx_bytes.0.to_vec()) + } + + pub fn sign_contract_transaction( + &mut self, + contract_action: String, + common: &WalletConnectTxCommon, + address: [u8; 20], + ) -> Result> { + if self.client.is_none() { + anyhow::bail!("no client"); + } + let signeraddress = Address::from_slice(&address); + let client = self + .client + .as_ref() + .ok_or_else(|| anyhow!("get walllet-connect client error"))?; + let newclient = client.clone(); + + let action: ContractAction = serde_json::from_str(&contract_action)?; + // parse json string transaction_info to TransactionRequest + // let tx: ContractTransfer = serde_json::from_str(&contract_transaction_info)?; + + let mut typedtx = match action { + ContractAction::ContractApproval(approval) => { + self.rt + .block_on(defi_wallet_core_common::construct_contract_approval_tx( + approval, + defi_wallet_core_common::EthNetwork::Custom { + chain_id: common.chainid, + legacy: false, + }, + common.web3api_url.as_str(), + ))? + } + ContractAction::ContractTransfer(transfer) => { + self.rt + .block_on(defi_wallet_core_common::construct_contract_transfer_tx( + transfer, + defi_wallet_core_common::EthNetwork::Custom { + chain_id: common.chainid, + legacy: false, + }, + // TODO unnessary for walletconnect + common.web3api_url.as_str(), + ))? + } + }; + + let tx = self.get_signed_tx_raw_bytes(newclient, signeraddress, &mut typedtx, common)?; + Ok(tx.to_vec()) + } + + pub fn send_contract_transaction( + &mut self, + contract_action: String, + common: &WalletConnectTxCommon, + address: [u8; 20], + ) -> Result> { + if self.client.is_none() { + anyhow::bail!("no client"); + } + let signeraddress = Address::from_slice(&address); + let client = self + .client + .as_ref() + .ok_or_else(|| anyhow!("get walllet-connect client error"))?; + let newclient = client.clone(); + + let action: ContractAction = serde_json::from_str(&contract_action)?; + // parse json string transaction_info to TransactionRequest + // let tx: ContractTransfer = serde_json::from_str(&contract_transaction_info)?; + + let mut typedtx = match action { + ContractAction::ContractApproval(approval) => { + self.rt + .block_on(defi_wallet_core_common::construct_contract_approval_tx( + approval, + defi_wallet_core_common::EthNetwork::Custom { + chain_id: common.chainid, + legacy: false, + }, + common.web3api_url.as_str(), + ))? + } + ContractAction::ContractTransfer(transfer) => { + self.rt + .block_on(defi_wallet_core_common::construct_contract_transfer_tx( + transfer, + defi_wallet_core_common::EthNetwork::Custom { + chain_id: common.chainid, + legacy: false, + }, + // TODO unnessary for walletconnect + common.web3api_url.as_str(), + ))? + } + }; + + let tx = self.get_sent_tx_raw_bytes(newclient, signeraddress, &mut typedtx, common)?; + Ok(tx.to_vec()) + } } diff --git a/wallet-connect/Cargo.toml b/wallet-connect/Cargo.toml index c34b4e19..bc32797c 100644 --- a/wallet-connect/Cargo.toml +++ b/wallet-connect/Cargo.toml @@ -34,6 +34,8 @@ url = { version = "2", features = ["serde"] } x25519-dalek = "1" zeroize = "1" hex = "0.4" +qrcode = "0.12" +image = "0.23" [dev-dependencies] quickcheck = "1" diff --git a/wallet-connect/Makefile b/wallet-connect/Makefile new file mode 100644 index 00000000..4c42293d --- /dev/null +++ b/wallet-connect/Makefile @@ -0,0 +1,2 @@ +all: + cargo run --example web3_v2 $(NEXT_PUBLIC_PROJECT_ID) diff --git a/wallet-connect/examples/web3_v2.rs b/wallet-connect/examples/web3_v2.rs index 4010ba2f..2ab10d88 100644 --- a/wallet-connect/examples/web3_v2.rs +++ b/wallet-connect/examples/web3_v2.rs @@ -1,6 +1,49 @@ +use eyre::Result; +use image::jpeg::JpegEncoder; +use image::Luma; +use image::Rgb; +use qrcode::QrCode; +use std::fs::File; + +use ethers::core::types::transaction::eip2718::TypedTransaction; +use ethers::abi::Address; +//use ethers::ethers_providers::Middleware; +use ethers::prelude::*; +use defi_wallet_connect::v2::WCMiddleware; + +use ethers::core::types::{BlockNumber, Chain}; +use ethers::etherscan::{ + account::{ + ERC20TokenTransferEvent, ERC721TokenTransferEvent, NormalTransaction, TokenQueryOption, + }, + //Client, +}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; + use defi_wallet_connect::v2::{Client, ClientOptions, Metadata, RequiredNamespaces, SessionInfo}; use std::error::Error; use std::io::BufRead; + +#[derive(Debug, Default)] +pub struct WalletConnectTxCommon { + pub gas_limit: String, // decimal string, "1" + pub gas_price: String, // decimal string + pub nonce: String, // decimal string + pub chainid: u64, // integer u64 + pub web3api_url: String, // string +} + +/// wallet connect cronos(eth) eip155-tx signing info +#[derive(Debug, Default)] +pub struct WalletConnectTxEip155 { + pub to: String, // hexstring, "0x..." + pub value: String, // decimal string, in wei units + pub data: Vec, // data, as bytes + + pub common: WalletConnectTxCommon, +} + async fn make_client( callback_sender: Option>, ) -> Result { @@ -11,11 +54,11 @@ async fn make_client( vec![ "eth_sendTransaction".to_owned(), "eth_signTransaction".to_owned(), - "eth_sign".to_owned(), + "eth_sign".to_owned(), "personal_sign".to_owned(), "eth_signTypedData".to_owned(), ], - vec!["eip155:5".to_owned()], + vec!["eip155:1".to_owned()], vec!["chainChanged".to_owned(), "accountsChanged".to_owned()], ), client_meta: Metadata { @@ -42,8 +85,132 @@ async fn save(info: &SessionInfo) -> eyre::Result<()> { Ok(()) } +async fn sign_typed_tx( + client: Client, + tx: &TypedTransaction, + address: Address, +) -> Result { + let middleware = WCMiddleware::new(client); + let signature = middleware.sign_transaction(tx, address).await?; + Ok(signature) +} + +async fn send_typed_tx(client: Client, tx: TypedTransaction, address: Address) -> Result { + let middleware = WCMiddleware::new(client).with_sender(address); + let receipt = middleware.send_transaction(tx, None).await?.tx_hash(); + Ok(receipt) +} + + +pub async fn sign_eip155_transaction_blocking( + client: &mut Client, + userinfo: &WalletConnectTxEip155, + address: [u8; 20], +) -> Result> { + + + let signeraddress = Address::from_slice(&address); + + let mut tx = Eip1559TransactionRequest::new(); + + if !userinfo.to.is_empty() { + tx = tx.to(NameOrAddress::Address(Address::from_str(&userinfo.to)?)); + } + if !userinfo.data.is_empty() { + tx = tx.data(userinfo.data.as_slice().to_vec()); + } + if !userinfo.common.gas_limit.is_empty() { + tx = tx.gas(U256::from_dec_str(&userinfo.common.gas_limit)?); + } + if !userinfo.common.gas_price.is_empty() { + tx = tx + .max_priority_fee_per_gas(U256::from_dec_str(&userinfo.common.gas_price)?) + .max_fee_per_gas(U256::from_dec_str(&userinfo.common.gas_price)?); + } + if !userinfo.common.nonce.is_empty() { + tx = tx.nonce(U256::from_dec_str(&userinfo.common.nonce)?); + } + if !userinfo.common.chainid == 0 { + tx = tx.chain_id(userinfo.common.chainid); + } + if !userinfo.value.is_empty() { + tx = tx.value(U256::from_dec_str(&userinfo.value)?); + } + let newclient = client.clone(); + let typedtx = TypedTransaction::Eip1559(tx); + + /*let sig = self + .rt + .block_on(sign_typed_tx(newclient, &typedtx, signeraddress)) + .map_err(|e| eyre::eyre!("sign_typed_transaction error {}", e.to_string()))?;*/ + + let sig= sign_typed_tx(newclient, &typedtx, signeraddress).await.map_err(|e| eyre::eyre!("sign_typed_transaction error {}", e.to_string()))?; + + let signed_tx = &typedtx.rlp_signed(&sig); + Ok(signed_tx.to_vec()) +} + + +pub async fn send_eip155_transaction_blocking( + client: &mut Client, + userinfo: &WalletConnectTxEip155, + address: [u8; 20], +) -> Result> { + + let signeraddress = Address::from_slice(&address); + + let mut tx = Eip1559TransactionRequest::new(); + + if !userinfo.to.is_empty() { + tx = tx.to(NameOrAddress::Address(Address::from_str(&userinfo.to)?)); + } + if !userinfo.data.is_empty() { + tx = tx.data(userinfo.data.as_slice().to_vec()); + } + if !userinfo.common.gas_limit.is_empty() { + tx = tx.gas(U256::from_dec_str(&userinfo.common.gas_limit)?); + } + if !userinfo.common.gas_price.is_empty() { + tx = tx + .max_priority_fee_per_gas(U256::from_dec_str(&userinfo.common.gas_price)?) + .max_fee_per_gas(U256::from_dec_str(&userinfo.common.gas_price)?); + } + if !userinfo.common.nonce.is_empty() { + tx = tx.nonce(U256::from_dec_str(&userinfo.common.nonce)?); + } + if userinfo.common.chainid != 0 { + tx = tx.chain_id(userinfo.common.chainid); + } + if !userinfo.value.is_empty() { + tx = tx.value(U256::from_dec_str(&userinfo.value)?); + } + + let newclient = client.clone(); + let typedtx = TypedTransaction::Eip1559(tx); + + // print typedtx + println!("typedtx: {:?}", typedtx); + println!("send tx: {:?}", typedtx); + let tx_bytes=send_typed_tx(newclient, typedtx, signeraddress).await.map_err(|e| eyre::eyre!("send_typed_transaction error {}", e.to_string()))?; + + //Ok(tx_bytes.0.to_vec()) + Ok(tx_bytes.0.to_vec()) +} + + +async fn make_qrcode(uri: &str) -> Result<()> { + // Generate the QR code for the data you want + let code = QrCode::new(uri)?; + + // Create an empty image buffer + let image = code.render::>().build(); + image.save("qrcode.png")?; + + Ok(()) +} #[tokio::main] async fn main() -> Result<(), Box> { + println!("walletconnect v2.0"); let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel::(); tokio::spawn(async move { while let Some(msg) = rx.recv().await { @@ -60,11 +227,15 @@ async fn main() -> Result<(), Box> { make_client(callback_sender).await? }; - let test_ping = true; - let test_signing = true; + let test_ping = false; + let test_signing = false; + let test_tx= true; let test_event_listening = false; let uri = client.get_connection_string().await; + // make qrimage with uri + make_qrcode(&uri).await?; + println!("uri= {}", uri); let namespaces = client.ensure_session().await?; println!( @@ -84,6 +255,35 @@ async fn main() -> Result<(), Box> { println!("sig1: {:?}", sig1); } + if test_tx { + // read env MYTOADDRESS + let to = std::env::var("MYTOADDRESS").expect("MYTOADDRESS not set"); + let fromaddress = namespaces.get_ethereum_addresses()[0].address.clone(); + // print fromaddress + println!("fromaddress= {:?}", fromaddress); + let mut txinfo=WalletConnectTxEip155 { + common: WalletConnectTxCommon { + chainid: 5, + gas_limit: "21000".into(), + gas_price: "1000000000".into(), + nonce: "0".into(), + web3api_url: "".into(), + + }, + to: to.into(), + data: vec![], + value: "1000".into(), + }; + // let txhash=send_eip155_transaction_blocking(&mut client, &txinfo, (*fromaddress.as_fixed_bytes()).into()).await?; + // print txhash + //println!("txhash= {:?}", txhash); + let sig=sign_eip155_transaction_blocking(&mut client, &txinfo, (*fromaddress.as_fixed_bytes()).into()).await?; + println!("sig= {:?}", sig); + + + + } + if test_event_listening { println!("press anykey to exit"); loop { diff --git a/wallet-connect/src/v2/client.rs b/wallet-connect/src/v2/client.rs index 75ade543..27337ce3 100644 --- a/wallet-connect/src/v2/client.rs +++ b/wallet-connect/src/v2/client.rs @@ -195,6 +195,9 @@ impl WCMiddleware> { pub fn new(client: Client) -> Self { WCMiddleware(Provider::new(client)) } + pub fn with_sender(self, address: impl Into
) -> Self { + WCMiddleware(self.0.with_sender(address)) + } } /// The wrapper error type for `ethers` middleware-related issues