Example on how to use the IBC precompile.
Table of contents
Open Table of contents
Context
After the release of Evmos v16, the intended way to interact with the cosmos transactions is using the precompiles.
In this example we are going to send an IBC transfer using a solidity contract instead of sending a Cosmos transaction with a Cosmos wallet.
Requirements
- Node
Example
This example will be limited to the creation of the Ethereum transaction, to sign it and broadcast it with wallet extensions in the browser, any tutorial on how to send an Ethereum transaction with Metamask will work.
Set up
mkdir /tmp/example
cd /tmp/example
nvm use v20.12.0
npm init -y
npm install ethers
The IBC precompile
You can get the code from the extensions’ repo.
curl https://raw.githubusercontent.com/evmos/extensions/main/precompiles/abi/ics20.json >> ics20.json
Configure the package.json
file
It should look like this.
{
"name": "example",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"sendtx": "tsc && node main.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"ethers": "^6.13.2",
"typescript": "^5.5.4"
}
}
I added the sendtx
script to simple build and execute the code.
Configure the tsconfig.json
file
This is a very simple configuration file, just to enable us to build the typescript file.
{
"compilerOptions": {
"target": "es2020",
"lib": ["es2020"],
"module": "CommonJS",
"baseUrl": "./",
"moduleResolution": "node"
},
"include": ["./**/*.ts"],
"exclude": ["node_modules/**/*"]
}
Interact with the Precompile
import { ethers } from "ethers";
import * as fs from "fs";
const abi = JSON.parse(fs.readFileSync("./ics20.json", "utf8"));
const provider = new ethers.JsonRpcProvider("http://localhost:8545");
const privateKey =
"53ED37ED51B785D6E197033AA1325541A802F2994EF801680AD0EBEACAB25122";
const wallet = new ethers.Wallet(privateKey, provider);
// IBC Precompile Address
const contractAddress = "0x0000000000000000000000000000000000000802";
const contract = new ethers.Contract(contractAddress, abi, wallet);
const ONE_DAY_IN_MILLISECONDS = 60 * 60 * 24 * 1000;
function getTimeoutTimestamp() {
return BigInt(Date.now() + ONE_DAY_IN_MILLISECONDS) * 1000000n;
}
async function sendTransaction() {
try {
const tx = await contract.transfer(
"transfer", // port
"channel-0", // channel
"aevmos", //denom
1, // amount
"0x914369752aC051F66581Fdeba6Ea5DC9602c7EA6", // sender
"cosmos1nqy85a3uu75vz8gpejxedm5dfxn8zs0l4q8a8l", // receiver
{
revisionNumber: 0n,
revisionHeight: 0n,
},
getTimeoutTimestamp(),
"memo"
);
// Wait for the transaction to be mined
const receipt = await tx.wait();
console.log("Transaction successful with hash:", receipt.transactionHash);
} catch (error) {
console.error("Error sending transaction:", error);
}
}
sendTransaction();
Production
When running in production, you want to connect to a provider using a WalletConnect
or Metamask
wallet extension.
The only thing that you need to change from the given example is the ethers.Wallets
object and directly use the wallet extension.