Overview
The ICS20 precompile provides an interface to the Inter-Blockchain Communication (IBC) protocol, allowing smart contracts to perform cross-chain token transfers. It enables sending tokens to other IBC-enabled chains and querying information about IBC denominations. Precompile Address:0x0000000000000000000000000000000000000802
Related Module: x/ibc-transfer
Gas Costs
Gas costs are approximated and may vary based on the transfer complexity and chain settings.
Method | Gas Cost |
---|---|
Transfer | 50,000 + (100 × memo length) |
Queries | 1000 + (3 × bytes of input) |
Channel Validation
Before initiating a transfer, ensure that:
- The source channel exists and is in an OPEN state
- The channel is connected to the intended destination chain
- The port ID matches the expected value (typically “transfer”)
Timeout Mechanism
IBC transfers include two timeout options to prevent tokens from being locked indefinitely:- Height Timeout: Specified as
{revisionNumber, revisionHeight}
. Set both to 0 to disable. - Timestamp Timeout: Unix timestamp in nanoseconds. Set to 0 to disable.
At least one timeout mechanism must be set. Recommended practice is to use timestamp timeout set to 1 hour from the current time.
Address Format Requirements
Current Limitation: Receiver addresses must be in bech32 format (e.g.,
cosmos1...
). Hex addresses (e.g., 0x...
) are not currently supported for the receiver parameter, though this limitation will be removed in a future release.The sender parameter is automatically converted from hex to bech32 format internally.EVM Callbacks Support
The ICS20 precompile supports EVM callbacks through thememo
field, enabling smart contracts to:
- Execute automatically when receiving cross-chain transfers
- Handle acknowledgments and timeouts for sent transfers
- Implement complex cross-chain contract interactions
For detailed callback implementation, see IBC Module and Callbacks Interface.
Transaction Methods
transfer
Initiates a cross-chain token transfer using the IBC protocol.
Receiver Address Format: Currently only accepts bech32 addresses (e.g.,
cosmos1...
) for the receiver parameter. Hex address support (e.g., 0x...
) will be added in a future release.Copy
Ask AI
/ SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ICS20Example {
address constant ICS20_PRECOMPILE = 0x0000000000000000000000000000000000000802;
struct Height {
uint64 revisionNumber;
uint64 revisionHeight;
}
event IBCTransferInitiated(
address indexed sender,
string indexed receiver,
string sourceChannel,
string denom,
uint256 amount,
uint64 sequence
);
function transfer(
string calldata sourceChannel,
string calldata denom,
uint256 amount,
string calldata receiver,
uint64 timeoutTimestamp,
string calldata memo
) external payable returns (uint64 sequence) {
require(bytes(sourceChannel).length > 0, "Source channel required");
require(bytes(denom).length > 0, "Denom required");
require(amount > 0, "Amount must be greater than 0");
require(bytes(receiver).length > 0, "Receiver address required");
require(timeoutTimestamp > 0, "Timeout timestamp required");
/ Default port for ICS20 transfers
string memory sourcePort = "transfer";
/ Disable height-based timeout (using timestamp instead)
Height memory timeoutHeight = Height({
revisionNumber: 0,
revisionHeight: 0
});
(bool success, bytes memory result) = ICS20_PRECOMPILE.call{value: msg.value}(
abi.encodeWithSignature(
"transfer(string,string,string,uint256,address,string,tuple(uint64,uint64),uint64,string)",
sourcePort,
sourceChannel,
denom,
amount,
msg.sender,
receiver,
timeoutHeight,
timeoutTimestamp,
memo
)
);
require(success, "IBC transfer failed");
sequence = abi.decode(result, (uint64));
emit IBCTransferInitiated(msg.sender, receiver, sourceChannel, denom, amount, sequence);
return sequence;
}
/ Helper function to calculate timeout (1 hour from now)
function calculateTimeout(uint256 durationSeconds) external view returns (uint64) {
return uint64((block.timestamp + durationSeconds) * 1e9); / Convert to nanoseconds
}
/ Quick transfer with 1-hour timeout
function quickTransfer(
string calldata sourceChannel,
string calldata denom,
uint256 amount,
string calldata receiver
) external payable returns (uint64) {
uint64 timeoutTimestamp = this.calculateTimeout(3600); / 1 hour
return this.transfer{value: msg.value}(
sourceChannel,
denom,
amount,
receiver,
timeoutTimestamp,
""
);
}
}
Query Methods
denom
Queries denomination information for an IBC token by its hash.
Copy
Ask AI
/ SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ICS20DenomQuery {
address constant ICS20_PRECOMPILE = 0x0000000000000000000000000000000000000802;
struct Hop {
string portId;
string channelId;
}
struct Denom {
string base;
Hop[] trace;
}
function getDenom(string memory hash) external view returns (Denom memory denom) {
require(bytes(hash).length > 0, "Hash cannot be empty");
(bool success, bytes memory result) = ICS20_PRECOMPILE.staticcall(
abi.encodeWithSignature("denom(string)", hash)
);
require(success, "Failed to get denom");
denom = abi.decode(result, (Denom));
return denom;
}
/ Helper function to check if a denom is native (no trace)
function isNativeDenom(string memory hash) external view returns (bool) {
Denom memory denom = this.getDenom(hash);
return denom.trace.length == 0;
}
/ Helper function to get the base denomination
function getBaseDenom(string memory hash) external view returns (string memory) {
Denom memory denom = this.getDenom(hash);
return denom.base;
}
}
denoms
Retrieves a paginated list of all denomination traces registered on the chain.
Copy
Ask AI
/ SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ICS20DenomsList {
address constant ICS20_PRECOMPILE = 0x0000000000000000000000000000000000000802;
struct PageRequest {
bytes key;
uint64 offset;
uint64 limit;
bool countTotal;
bool reverse;
}
struct PageResponse {
bytes nextKey;
uint64 total;
}
struct Hop {
string portId;
string channelId;
}
struct Denom {
string base;
Hop[] trace;
}
function getDenoms(PageRequest memory pageRequest)
external
view
returns (Denom[] memory denoms, PageResponse memory pageResponse)
{
(bool success, bytes memory result) = ICS20_PRECOMPILE.staticcall(
abi.encodeWithSignature(
"denoms((bytes,uint64,uint64,bool,bool))",
pageRequest
)
);
require(success, "Failed to get denoms");
(denoms, pageResponse) = abi.decode(result, (Denom[], PageResponse));
return (denoms, pageResponse);
}
/ Helper function to get all IBC denoms (with trace)
function getIBCDenoms(uint64 limit) external view returns (Denom[] memory) {
PageRequest memory pageRequest = PageRequest({
key: "",
offset: 0,
limit: limit,
countTotal: false,
reverse: false
});
(Denom[] memory allDenoms,) = this.getDenoms(pageRequest);
/ Count IBC denoms (those with trace)
uint256 ibcCount = 0;
for (uint i = 0; i < allDenoms.length; i++) {
if (allDenoms[i].trace.length > 0) {
ibcCount++;
}
}
/ Filter IBC denoms
Denom[] memory ibcDenoms = new Denom[](/docs/evm/next/documentation/smart-contracts/precompiles/ibcCount);
uint256 index = 0;
for (uint i = 0; i < allDenoms.length; i++) {
if (allDenoms[i].trace.length > 0) {
ibcDenoms[index++] = allDenoms[i];
}
}
return ibcDenoms;
}
}
denomHash
Computes the hash of a denomination trace path.
Copy
Ask AI
/ SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ICS20DenomHash {
address constant ICS20_PRECOMPILE = 0x0000000000000000000000000000000000000802;
function getDenomHash(string memory trace) external view returns (string memory hash) {
require(bytes(trace).length > 0, "Trace cannot be empty");
(bool success, bytes memory result) = ICS20_PRECOMPILE.staticcall(
abi.encodeWithSignature("denomHash(string)", trace)
);
require(success, "Failed to compute denom hash");
hash = abi.decode(result, (string));
return hash;
}
/ Helper function to build and hash a trace path
function buildAndHashTrace(
string memory portId,
string memory channelId,
string memory baseDenom
) external view returns (string memory) {
/ Build trace in format: "port/channel/denom"
string memory trace = string(abi.encodePacked(
portId,
"/",
channelId,
"/",
baseDenom
));
return this.getDenomHash(trace);
}
/ Helper function to verify if a hash matches a trace
function verifyDenomHash(
string memory trace,
string memory expectedHash
) external view returns (bool) {
string memory actualHash = this.getDenomHash(trace);
return keccak256(bytes(actualHash)) == keccak256(bytes(expectedHash));
}
}
Full Solidity Interface & ABI
ICS20 Solidity Interface
Copy
Ask AI
/ SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.18;
import "../common/Types.sol";
/ @dev The ICS20I contract's address.
address constant ICS20_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000802;
/ @dev The ICS20 contract's instance.
ICS20I constant ICS20_CONTRACT = ICS20I(ICS20_PRECOMPILE_ADDRESS);
/ @dev Denom contains the base denomination for ICS20 fungible tokens and the
/ source tracing information path.
struct Denom {
/ base denomination of the relayed fungible token.
string base;
/ trace contains a list of hops for multi-hop transfers.
Hop[] trace;
}
/ @dev Hop defines a port ID, channel ID pair specifying where
/ tokens must be forwarded next in a multi-hop transfer.
struct Hop {
string portId;
string channelId;
}
/ @author Evmos Team
/ @title ICS20 Transfer Precompiled Contract
/ @dev The interface through which solidity contracts will interact with IBC Transfer (ICS20)
/ @custom:address 0x0000000000000000000000000000000000000802
interface ICS20I {
/ @dev Emitted when an ICS-20 transfer is executed.
/ @param sender The address of the sender.
/ @param receiver The address of the receiver.
/ @param sourcePort The source port of the IBC transaction, For v2 packets, leave it empty.
/ @param sourceChannel The source channel of the IBC transaction, For v2 packets, set the client ID.
/ @param denom The denomination of the tokens transferred.
/ @param amount The amount of tokens transferred.
/ @param memo The IBC transaction memo.
event IBCTransfer(
address indexed sender,
string indexed receiver,
string sourcePort,
string sourceChannel,
string denom,
uint256 amount,
string memo
);
/ @dev Transfer defines a method for performing an IBC transfer.
/ @param sourcePort the port on which the packet will be sent
/ @param sourceChannel the channel by which the packet will be sent
/ @param denom the denomination of the Coin to be transferred to the receiver
/ @param amount the amount of the Coin to be transferred to the receiver
/ @param sender the hex address of the sender
/ @param receiver the bech32 address of the receiver (hex addresses not yet supported)
/ @param timeoutHeight the timeout height relative to the current block height.
/ The timeout is disabled when set to 0
/ @param timeoutTimestamp the timeout timestamp in absolute nanoseconds since unix epoch.
/ The timeout is disabled when set to 0
/ @param memo optional memo
/ @return nextSequence sequence number of the transfer packet sent
function transfer(
string memory sourcePort,
string memory sourceChannel,
string memory denom,
uint256 amount,
address sender,
string memory receiver,
Height memory timeoutHeight,
uint64 timeoutTimestamp,
string memory memo
) external returns (uint64 nextSequence);
/ @dev denoms Defines a method for returning all denoms.
/ @param pageRequest Defines the pagination parameters to for the request.
function denoms(
PageRequest memory pageRequest
)
external
view
returns (
Denom[] memory denoms,
PageResponse memory pageResponse
);
/ @dev Denom defines a method for returning a denom.
function denom(
string memory hash
) external view returns (Denom memory denom);
/ @dev DenomHash defines a method for returning a hash of the denomination info.
function denomHash(
string memory trace
) external view returns (string memory hash);
}
ICS20 ABI
Copy
Ask AI
{
"_format": "hh-sol-artifact-1",
"contractName": "ICS20I",
"sourceName": "solidity/precompiles/ics20/ICS20I.sol",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "string",
"name": "receiver",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "sourcePort",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "sourceChannel",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "denom",
"type": "string"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "string",
"name": "memo",
"type": "string"
}
],
"name": "IBCTransfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "string",
"name": "hash",
"type": "string"
}
],
"name": "denom",
"outputs": [
{
"components": [
{
"internalType": "string",
"name": "base",
"type": "string"
},
{
"components": [
{
"internalType": "string",
"name": "portId",
"type": "string"
},
{
"internalType": "string",
"name": "channelId",
"type": "string"
}
],
"internalType": "struct Hop[]",
"name": "trace",
"type": "tuple[]"
}
],
"internalType": "struct Denom",
"name": "denom",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "trace",
"type": "string"
}
],
"name": "denomHash",
"outputs": [
{
"internalType": "string",
"name": "hash",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"components": [
{
"internalType": "bytes",
"name": "key",
"type": "bytes"
},
{
"internalType": "uint64",
"name": "offset",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "limit",
"type": "uint64"
},
{
"internalType": "bool",
"name": "countTotal",
"type": "bool"
},
{
"internalType": "bool",
"name": "reverse",
"type": "bool"
}
],
"internalType": "struct PageRequest",
"name": "pageRequest",
"type": "tuple"
}
],
"name": "denoms",
"outputs": [
{
"components": [
{
"internalType": "string",
"name": "base",
"type": "string"
},
{
"components": [
{
"internalType": "string",
"name": "portId",
"type": "string"
},
{
"internalType": "string",
"name": "channelId",
"type": "string"
}
],
"internalType": "struct Hop[]",
"name": "trace",
"type": "tuple[]"
}
],
"internalType": "struct Denom[]",
"name": "denoms",
"type": "tuple[]"
},
{
"components": [
{
"internalType": "bytes",
"name": "nextKey",
"type": "bytes"
},
{
"internalType": "uint64",
"name": "total",
"type": "uint64"
}
],
"internalType": "struct PageResponse",
"name": "pageResponse",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "sourcePort",
"type": "string"
},
{
"internalType": "string",
"name": "sourceChannel",
"type": "string"
},
{
"internalType": "string",
"name": "denom",
"type": "string"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"internalType": "string",
"name": "receiver",
"type": "string"
},
{
"components": [
{
"internalType": "uint64",
"name": "revisionNumber",
"type": "uint64"
},
{
"internalType": "uint64",
"name": "revisionHeight",
"type": "uint64"
}
],
"internalType": "struct Height",
"name": "timeoutHeight",
"type": "tuple"
},
{
"internalType": "uint64",
"name": "timeoutTimestamp",
"type": "uint64"
},
{
"internalType": "string",
"name": "memo",
"type": "string"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "uint64",
"name": "nextSequence",
"type": "uint64"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
]
}