diff --git a/Cargo.lock b/Cargo.lock index ebeca657..7a253cb2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1141,6 +1141,7 @@ dependencies = [ "defi-wallet-core-proto", "ethers", "eyre", + "hex", "ibc", "ibc-proto", "itertools", diff --git a/bindings/cpp/src/lib.rs b/bindings/cpp/src/lib.rs index 2f2f044f..b55137ec 100644 --- a/bindings/cpp/src/lib.rs +++ b/bindings/cpp/src/lib.rs @@ -473,7 +473,7 @@ pub mod ffi { ) -> Result>; /// recovers/imports HD wallet from a BIP39 backup phrase (English words) and password - /// from secure storage + /// from secure storage fn restore_wallet_load_from_securestorage( servicename: String, username: String, @@ -600,6 +600,18 @@ pub mod ffi { /// sent from it. pub fn get_eth_nonce(address: &str, api_url: &str) -> Result; + #[cxx_name = "get_eth_transaction_receipt_blocking"] + pub fn get_eth_transaction_receipt_by_vec_blocking( + tx_hash: Vec, + api_url: &str, + ) -> Result; + + #[cxx_name = "get_eth_transaction_receipt_blocking"] + pub fn get_eth_transaction_receipt_by_string_blocking( + tx_hash: String, + api_url: &str, + ) -> Result; + /// broadcast signed cronos tx pub fn broadcast_eth_signed_raw_tx( raw_tx: Vec, @@ -1289,6 +1301,36 @@ pub fn get_eth_nonce(address: &str, api_url: &str) -> Result { Ok(res.to_string()) } +/// Get eth transaction receipt with transaction hash, return json string +/// TODO: Provide TransactionReceipt type binding +pub fn get_eth_transaction_receipt_by_vec_blocking( + tx_hash: Vec, + api_url: &str, +) -> Result { + let receipt = + defi_wallet_core_common::get_eth_transaction_receipt_by_vec_blocking(tx_hash, api_url)?; + if receipt.is_none() { + Ok("".to_string()) + } else { + Ok(serde_json::to_string(&receipt)?) + } +} + +/// Get eth transaction receipt with transaction hash, return json string +/// TODO: Provide TransactionReceipt type binding +pub fn get_eth_transaction_receipt_by_string_blocking( + tx_hash: String, + api_url: &str, +) -> Result { + let receipt = + defi_wallet_core_common::get_eth_transaction_receipt_by_string_blocking(tx_hash, api_url)?; + if receipt.is_none() { + Ok("".to_string()) + } else { + Ok(serde_json::to_string(&receipt)?) + } +} + /// broadcast signed cronos tx pub fn broadcast_eth_signed_raw_tx( raw_tx: Vec, diff --git a/common/Cargo.toml b/common/Cargo.toml index ff8fb65d..c99d8917 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -55,6 +55,7 @@ thiserror = "1" uniffi = { version = "^0.23", optional = true } uniffi_macros = { version = "^0.23", optional = true } url = "2" +hex="0.4.3" [target.'cfg(target_arch = "wasm32")'.dependencies] cosmos-sdk-proto = { git = "https://github.com/crypto-com/cosmos-rust.git", default-features = false, features = ["cosmwasm", "grpc"] } diff --git a/common/src/common.udl b/common/src/common.udl index e47dc706..8e8374f3 100644 --- a/common/src/common.udl +++ b/common/src/common.udl @@ -403,11 +403,14 @@ enum EthError { "NodeUrl", "SendTxFail", "BroadcastTxFail", + "GetTransactionReceiptError", + "InvalidTxHash", "MempoolDrop", "BalanceFail", "AsyncRuntimeError", "ContractSendError", "ContractCallError", + "GetTransactionError", "SignatureError", "ChainidError", "IncorrectChainidError", diff --git a/common/src/node/ethereum/utils.rs b/common/src/node/ethereum/utils.rs index b505707e..ce21e3c6 100644 --- a/common/src/node/ethereum/utils.rs +++ b/common/src/node/ethereum/utils.rs @@ -3,7 +3,7 @@ use crate::{ WalletCoinFunc, }; use cosmrs::bip32::secp256k1::ecdsa::SigningKey; -use ethers::prelude::{Address, LocalWallet, Middleware, Signer, SignerMiddleware}; +use ethers::prelude::{Address, LocalWallet, Middleware, Signer, SignerMiddleware, TxHash}; use ethers::types::transaction::eip2718::TypedTransaction; use std::{str::FromStr, sync::Arc, time::Duration}; // use ethers Http @@ -211,6 +211,90 @@ pub async fn get_eth_transaction_count(address: &str, web3api_url: &str) -> Resu Ok(result) } +// Wrapper of TxHash to implement TryFrom +pub struct TxHashWrapper(TxHash); + +impl TryFrom> for TxHashWrapper { + type Error = EthError; + + fn try_from(value: Vec) -> Result { + if value.len() != 32 { + return Err(EthError::InvalidTxHash); + } + + let mut tx_hash = [0u8; 32]; + tx_hash.copy_from_slice(&value); + Ok(TxHashWrapper(TxHash::from(tx_hash))) + } +} + +impl TryFrom for TxHashWrapper { + type Error = EthError; + + fn try_from(value: String) -> Result { + let value = hex::decode(value).map_err(|_| EthError::HexConversion)?; + if value.len() != 32 { + return Err(EthError::InvalidTxHash); + } + + let mut tx_hash = [0u8; 32]; + tx_hash.copy_from_slice(&value); + Ok(TxHashWrapper(TxHash::from(tx_hash))) + } +} + +// TODO Don't expose to wasm32, to avoid clippy issue, no plan to support wasm32 for now +#[cfg(not(target_arch = "wasm32"))] +async fn get_eth_transaction_receipt_by_vec( + tx_hash: Vec, + web3api_url: &str, +) -> Result, EthError> { + let client = get_ethers_provider(web3api_url).await?; + let tx_hash = TxHashWrapper::try_from(tx_hash)?; + + let receipt = client + .get_transaction_receipt(tx_hash.0) + .await + .map_err(EthError::GetTransactionReceiptError)?; + + Ok(receipt) +} + +// TODO Don't expose to wasm32, to avoid clippy issue, no plan to support wasm32 for now +#[cfg(not(target_arch = "wasm32"))] +async fn get_eth_transaction_receipt_by_string( + tx_hash: String, + web3api_url: &str, +) -> Result, EthError> { + let client = get_ethers_provider(web3api_url).await?; + let tx_hash = TxHashWrapper::try_from(tx_hash)?; + + let receipt = client + .get_transaction_receipt(tx_hash.0) + .await + .map_err(EthError::GetTransactionReceiptError)?; + + Ok(receipt) +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn get_eth_transaction_receipt_by_string_blocking( + tx_hash: String, + web3api_url: &str, +) -> Result, EthError> { + let rt = tokio::runtime::Runtime::new().map_err(|_err| EthError::AsyncRuntimeError)?; + rt.block_on(get_eth_transaction_receipt_by_string(tx_hash, web3api_url)) +} + +#[cfg(not(target_arch = "wasm32"))] +pub fn get_eth_transaction_receipt_by_vec_blocking( + tx_hash: Vec, + web3api_url: &str, +) -> Result, EthError> { + let rt = tokio::runtime::Runtime::new().map_err(|_err| EthError::AsyncRuntimeError)?; + rt.block_on(get_eth_transaction_receipt_by_vec(tx_hash, web3api_url)) +} + /// given the account address and contract information, it returns the amount of ERC20/ERC721/ERC1155 token it owns pub async fn get_contract_balance( account_address: &str, diff --git a/common/src/transaction/ethereum/error.rs b/common/src/transaction/ethereum/error.rs index 0f1cd0b0..178e9246 100644 --- a/common/src/transaction/ethereum/error.rs +++ b/common/src/transaction/ethereum/error.rs @@ -28,6 +28,10 @@ pub enum EthError { SendTxFail(SignerMiddlewareError, Wallet>), #[error("Transaction sending failed: {0}")] BroadcastTxFail(ProviderError), + #[error("Get Transaction Receipt failed: {0}")] + GetTransactionReceiptError(ProviderError), + #[error("Invliad Transaction Hash")] + InvalidTxHash, #[error("Transaction dropped from the mempool")] MempoolDrop, #[error("Failed to obtain an account balance")] @@ -38,6 +42,8 @@ pub enum EthError { ContractSendError(String), #[error("Contract Call Error: {0}")] ContractCallError(String), + #[error("Get Transaction Error: {0}")] + GetTransactionError(String), #[error("Signature error")] SignatureError, #[error("Chainid error: {0}")]