RPC client for canisters on the Internet Computer to access Ethereum networks, powered by the Internet Computer's threshold ECDSA signature and outbound http call features.
- Perform RPC calls to Ethereum networks within canisters
- Sign messages with IC's threshold ECDSA
- Send transactions to Ethereum networks within canisters
- Query/call Ethereum contracts within canisters
Add the following to your Cargo.toml:
[dependencies]
ic-web3 = { git = "https://github.com/rocklabs-io/ic-web3" }
Note: you should have dfx 0.11.2 or above.
Please refer to example for the complete example.
use candid::candid_method;use ic_cdk_macros::{self, update};use std::str::FromStr;use ic_web3::transports::ICHttp;use ic_web3::Web3;use ic_web3::ic::{get_eth_addr,KeyInfo};use ic_web3::{
contract::{Contract,Options},
ethabi::ethereum_types::{U64,U256},
types::{Address,TransactionParameters,BlockId,BlockNumber,Block},};constURL:&str = "<GOERLI-RPC-URL>";constCHAIN_ID:u64 = 5;constKEY_NAME:&str = "dfx_test_key";constTOKEN_ABI:&[u8] = include_bytes!("../src/contract/res/token.json");typeResult<T,E> = std::result::Result<T,E>;#[update(name = "get_eth_gas_price")]#[candid_method(update, rename = "get_eth_gas_price")]asyncfnget_eth_gas_price() -> Result<String,String>{let w3 = matchICHttp::new(URL,None){Ok(v) => {Web3::new(v)},Err(e) => {returnErr(e.to_string())},};let gas_price = w3.eth().gas_price().await.map_err(|e| format!("get gas price failed: {}", e))?;
ic_cdk::println!("gas price: {}", gas_price);Ok(format!("{}", gas_price))}// get canister's ethereum address#[update(name = "get_canister_addr")]#[candid_method(update, rename = "get_canister_addr")]asyncfnget_canister_addr() -> Result<String,String>{matchget_eth_addr(None,None,KEY_NAME.to_string()).await{Ok(addr) => {Ok(hex::encode(addr))},Err(e) => {Err(e)},}}// send tx to eth#[update(name = "send_eth")]#[candid_method(update, rename = "send_eth")]asyncfnsend_eth(to:String,value:u64) -> Result<String,String>{// ecdsa key infolet derivation_path = vec![ic_cdk::id().as_slice().to_vec()];let key_info = KeyInfo{derivation_path: derivation_path,key_name:KEY_NAME.to_string()};// get canister eth addresslet from_addr = get_eth_addr(None,None,KEY_NAME.to_string()).await.map_err(|e| format!("get canister eth addr failed: {}", e))?;// get canister the address tx countlet w3 = matchICHttp::new(URL,None){Ok(v) => {Web3::new(v)},Err(e) => {returnErr(e.to_string())},};let tx_count = w3.eth().transaction_count(from_addr,None).await.map_err(|e| format!("get tx count error: {}", e))?;
ic_cdk::println!("canister eth address {} tx count: {}", hex::encode(from_addr), tx_count);// construct a transactionlet to = Address::from_str(&to).unwrap();let tx = TransactionParameters{to:Some(to),nonce:Some(tx_count),// remember to fetch nonce firstvalue:U256::from(value),gas_price:Some(U256::exp10(10)),// 10 gweigas:U256::from(21000),
..Default::default()};// sign the transaction and get serialized transaction + signaturelet signed_tx = w3.accounts().sign_transaction(tx, key_info,CHAIN_ID).await.map_err(|e| format!("sign tx error: {}", e))?;match w3.eth().send_raw_transaction(signed_tx.raw_transaction).await{Ok(txhash) => { ic_cdk::println!("txhash: {}", hex::encode(txhash.0));Ok(format!("{}", hex::encode(txhash.0)))},Err(e) => {Err(e.to_string())},}}Start a local replica:
dfx start --background --clean --enable-canister-http
Deploy the example canister:
dfx deploy
This repo is modified from the rust-web3 project.