diff --git a/rust/basic_ethereum/Cargo.lock b/rust/basic_ethereum/Cargo.lock index 7cc70636f..568c2be1a 100644 --- a/rust/basic_ethereum/Cargo.lock +++ b/rust/basic_ethereum/Cargo.lock @@ -345,9 +345,11 @@ dependencies = [ "ic-crypto-extended-bip32", "ic-crypto-sha3", "ic-ethereum-types", + "num", "num-traits", "serde", "serde_bytes", + "serde_json", ] [[package]] @@ -2078,6 +2080,20 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2106,6 +2122,15 @@ dependencies = [ "smallvec", ] +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2132,6 +2157,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" diff --git a/rust/basic_ethereum/Cargo.toml b/rust/basic_ethereum/Cargo.toml index f9c3b3d97..b4b2591a4 100644 --- a/rust/basic_ethereum/Cargo.toml +++ b/rust/basic_ethereum/Cargo.toml @@ -21,5 +21,7 @@ ic-crypto-extended-bip32 = { git = "https://github.com/dfinity/ic", tag = "relea ic-crypto-sha3 = { git = "https://github.com/dfinity/ic", tag = "release-2024-06-26_23-01-base", package = "ic-crypto-sha3" } ic-ethereum-types = { git = "https://github.com/dfinity/ic", tag = "release-2024-06-26_23-01-base", package = "ic-ethereum-types" } serde = "1.0" +serde_json = "1.0" serde_bytes = "0.11.15" -num-traits = "0.2.19" \ No newline at end of file +num-traits = "0.2.19" +num = "0.4.3" diff --git a/rust/basic_ethereum/basic_ethereum.did b/rust/basic_ethereum/basic_ethereum.did index 723e6d14f..c704e4174 100644 --- a/rust/basic_ethereum/basic_ethereum.did +++ b/rust/basic_ethereum/basic_ethereum.did @@ -42,6 +42,9 @@ service : (opt InitArg) -> { // If the owner is not set, it defaults to the caller's principal. ethereum_address : (owner: opt principal) -> (text); + // Returns the balance of the given Ethereum address. + get_balance : (address: text) -> (Wei); + // Returns the transaction count for the Ethereum address derived for the given principal. // // If the owner is not set, it defaults to the caller's principal. diff --git a/rust/basic_ethereum/src/lib.rs b/rust/basic_ethereum/src/lib.rs index 49695fa57..34f6484cd 100644 --- a/rust/basic_ethereum/src/lib.rs +++ b/rust/basic_ethereum/src/lib.rs @@ -8,12 +8,13 @@ use alloy_consensus::{SignableTransaction, TxEip1559, TxEnvelope}; use alloy_primitives::{hex, Signature, TxKind, U256}; use candid::{CandidType, Deserialize, Nat, Principal}; use evm_rpc_canister_types::{ - BlockTag, EvmRpcCanister, GetTransactionCountArgs, GetTransactionCountResult, - MultiGetTransactionCountResult, + BlockTag, EthMainnetService, EthSepoliaService, EvmRpcCanister, GetTransactionCountArgs, + GetTransactionCountResult, MultiGetTransactionCountResult, RequestResult, RpcService, }; use ic_cdk::api::management_canister::ecdsa::{EcdsaCurve, EcdsaKeyId}; use ic_cdk::{init, update}; use ic_ethereum_types::Address; +use num::{BigUint, Num}; use std::str::FromStr; pub const EVM_RPC_CANISTER_ID: Principal = @@ -35,6 +36,47 @@ pub async fn ethereum_address(owner: Option) -> String { wallet.ethereum_address().to_string() } +#[update] +pub async fn get_balance(address: String) -> Nat { + let _caller = validate_caller_not_anonymous(); + let json = format!( + r#"{{ "jsonrpc": "2.0", "method": "eth_getBalance", "params": ["{}", "latest"], "id": 1 }}"#, + address + ); + + let max_response_size_bytes = 500_u64; + let num_cycles = 1_000_000_000u128; + + let ethereum_network = read_state(|s| s.ethereum_network()); + + let rpc_service = match ethereum_network { + EthereumNetwork::Mainnet => RpcService::EthMainnet(EthMainnetService::PublicNode), + EthereumNetwork::Sepolia => RpcService::EthSepolia(EthSepoliaService::PublicNode), + }; + + let (response,) = EVM_RPC + .request(rpc_service, json, max_response_size_bytes, num_cycles) + .await + .expect("RPC call failed"); + + let hex_balance = match response { + RequestResult::Ok(balance_result) => { + // The response to a successful `eth_getBalance` call has the following format: + // { "id": "[ID]", "jsonrpc": "2.0", "result": "[BALANCE IN HEX]" } + let response: serde_json::Value = serde_json::from_str(&balance_result).unwrap(); + response + .get("result") + .and_then(|v| v.as_str()) + .unwrap() + .to_string() + } + RequestResult::Err(e) => panic!("Received an error response: {:?}", e), + }; + + // Remove the "0x" prefix before converting to a decimal number. + Nat(BigUint::from_str_radix(&hex_balance[2..], 16).unwrap()) +} + #[update] pub async fn transaction_count(owner: Option, block: Option) -> Nat { let caller = validate_caller_not_anonymous();