Getting Started
Smart contracts on ICP can directly interact with the Ethereum network and other networks that are using the Ethereum Virtual Machine (EVM), such as Polygon and Avalanche. This integration is possible thanks to ICP's HTTPS outcalls and threshold ECDSA features.
Alloy implements high-performance, well-tested & documented libraries for interacting with Ethereum and other EVM-based chains.
This fork of Alloy adds support for the Internet Computer (ICP) both as a transport layer and as a signer. Using Alloy with ICP makes interacting with the EVM from inside an ICP canister easier and more secure as ic-alloy
abstracts away a lot of complexities.
Before diving in and adding ic-alloy
to your project, we recommend you read the Alloy documentation to get familiar with the library.
See examples
- ic-alloy-toolkit: A collection of examples on how to perform common EVM operations. Live demo
- ic-alloy-basic-wallet: A basic Ethereum multi-user wallet. Live demo
- ic-alloy-dca: A semi-autonomous agent, swapping ERC-20 tokens on Uniswap for you.
Setup
To use the ICP enabled fork of Alloy in your project, add this to Cargo.toml
:
alloy = { git = "https://github.com/ic-alloy/ic-alloy.git", tag = "v0.3.5-icp.0", features = ["icp"]}
getrandom = { version = "0.2.15", features = ["custom"] }
To use the sol!()
macro, add the following crate features:
sol-types
json
contract
Example - Get ETH balance
As a first example, let's see how to get the balance of an Ethereum address.
#[ic_cdk::update]
async fn get_balance(address: String) -> Result<String, String> {
let address = address.parse::<Address>().map_err(|e| e.to_string())?;
let rpc_service = RpcService::EthSepolia(EthSepoliaService::Alchemy);
let config = IcpConfig::new(rpc_service);
let provider = ProviderBuilder::new().on_icp(config);
let result = provider.get_balance(address).await;
match result {
Ok(balance) => Ok(balance.to_string()),
Err(e) => Err(e.to_string()),
}
}
1. Parse address
let address = address.parse::<Address>().map_err(|e| e.to_string())?;
First, we parse the address string into an Alloy Address
type. This ensures that the address is valid and causes the function to return an error if it is not.
2. Create a RPC service
let rpc_service = RpcService::EthSepolia(EthSepoliaService::Alchemy);
Next, we create a RpcService
that instructs the EVM RPC canister to use Alchemy as the RPC provider. See the list of RPC providers the EVM RPC canister supports.
3. Create a config object
let config = IcpConfig::new(rpc_service);
The config object determines the behaviour of the ICP provider and transport when making the request. The new
function takes the RpcService
we created in the previous step and uses default values for the other fields.
See more configuration options
4. Create a provider
let provider = ProviderBuilder::new().on_icp(config);
The ProviderBuilder
is a helper that allows you to create a provider with a specific configuration. In this case, we use the on_icp
method to create a provider that uses the ICP transport layer.
5. Get the balance
let result = provider.get_balance(address).await;
Finally, we call the get_balance
method on the provider to get the balance of the address. The method returns a Result
that we can match on to get the balance or an error.
Read more about the Provider