ic-web3

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.

Features

Usage

Add the following to your Cargo.toml:

[dependencies] ic-web3 = "0.1.0"

Examples

Note: you should have dfx 0.11.2 or above.

Please refer to example for the complete example.

```rust use candid::candidmethod; use iccdk_macros::{self, update}; use std::str::FromStr;

use icweb3::transports::ICHttp; use icweb3::Web3; use icweb3::ic::{getethaddr, KeyInfo}; use icweb3::{ contract::{Contract, Options}, ethabi::ethereum_types::{U64, U256}, types::{Address, TransactionParameters, BlockId, BlockNumber, Block}, };

const URL: &str = "https://goerli.infura.io/v3/93ca33aa55d147f08666ac82d7cc69fd"; const CHAINID: u64 = 5; const KEYNAME: &str = "dfxtestkey"; const TOKENABI: &[u8] = includebytes!("../src/contract/res/token.json");

type Result = std::result::Result;

[update(name = "getethgas_price")]

[candidmethod(update, rename = "getethgasprice")]

async fn getethgasprice() -> Result { let w3 = match ICHttp::new(URL, None) { Ok(v) => { Web3::new(v) }, Err(e) => { return Err(e.tostring()) }, }; let gasprice = w3.eth().gasprice().await.maperr(|e| format!("get gas price failed: {}", e))?; iccdk::println!("gas price: {}", gasprice); Ok(format!("{}", gasprice)) }

// get canister's ethereum address

[update(name = "getcanisteraddr")]

[candidmethod(update, rename = "getcanister_addr")]

async fn getcanisteraddr() -> Result { match getethaddr(None, None, KEYNAME.tostring()).await { Ok(addr) => { Ok(hex::encode(addr)) }, Err(e) => { Err(e) }, } }

// send tx to eth

[update(name = "send_eth")]

[candidmethod(update, rename = "sendeth")]

async fn sendeth(to: String, value: u64) -> Result { // ecdsa key info let derivationpath = vec![iccdk::id().asslice().tovec()]; let keyinfo = KeyInfo{ derivationpath: derivationpath, keyname: KEYNAME.to_string() };

// get canister eth address
let 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 count
let w3 = match ICHttp::new(URL, None) {
    Ok(v) => { Web3::new(v) },
    Err(e) => { return Err(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 transaction
let to = Address::from_str(&to).unwrap();
let tx = TransactionParameters {
    to: Some(to),
    nonce: Some(tx_count), // remember to fetch nonce first
    value: U256::from(value),
    gas_price: Some(U256::exp10(10)), // 10 gwei
    gas: U256::from(21000),
    ..Default::default()
};
// sign the transaction and get serialized transaction + signature
let 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

Acknowledgment

This repo is modified from the rust-web3 project.