ICS20
Cross-chain token transfers via IBC (Inter-Blockchain Communication) protocol
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
Gas Costs
Gas costs are approximated and may vary based on the transfer complexity and chain settings.
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")
You can verify channel status using the IBC module queries or chain explorers.
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., `Ontomir1...`). 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 the memo 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](/docs/evm/next/documentation/Ontomir-sdk/modules/ibc) and [Callbacks Interface](/docs/evm/next/documentation/smart-contracts/precompiles/callbacks).
Transaction Methods
transfer
transferInitiates a cross-chain token transfer using the IBC protocol.
**Receiver Address Format**: Currently only accepts bech32 addresses (e.g., `Ontomir1...`) for the receiver parameter. Hex address support (e.g., `0x...`) will be added in a future release. ```solidity Solidity expandable lines // 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
denomQueries denomination information for an IBC token by its hash.
```solidity Solidity expandable lines // 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
denomsRetrieves a paginated list of all denomination traces registered on the chain.
```solidity Solidity expandable lines // 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; uint256 index = 0; for (uint i = 0; i < allDenoms.length; i++) { if (allDenoms[i].trace.length > 0) { ibcDenoms[index++] = allDenoms[i]; } } return ibcDenoms; }
}
denomHash
denomHashComputes the hash of a denomination trace path.
```solidity Solidity expandable lines // 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)); }
}
