Today, we’re excited to announce the release of ICS-20 fungible token transfer v2 in ibc-go v9.0.0. Transfer v2 introduces two key features:
Multi-token transfers within a single packet
Native packet forwarding and unwinding within the transfer protocol
These new features improve UX, increase composability between chains, and unlock new use cases for developers leveraging IBC. Additionally, the newly introduced FungibleTokenPacketDataV2
will be encoded with protobuf instead of JSON.
In this blog, we’ll cover how token transfer v2 works, what it can be used for, and other improvements that ibc-go v9 introduces such as the 02-client routing v2.
Multi-token functionality: Enables complex operations such as providing liquidity to both sides of a DEX pool in a single transaction.
Enhanced UX: Abstracts the token unwinding process, eliminating the need for users to manually route tokens back to their source chain before forwarding to the final destination.
Flexible routing: Allows indirect transfers between chains that don’t have direct connections (e.g., chain A to chain C via chain B).
This section outlines the key challenges addressed by transfer v2, including the token routing problem, limitations of single token packets, and inefficient multi-step operations.
Key concepts:
Unwinding: Automatically returning tokens from a different chain back to their origin chain. For example, if a token was sent from A (origin) -> B -> C, unwinding means returning it from C -> B -> A over the same channels they were initially sent over.
Forwarding: Transferring tokens to a final destination through a set of intermediary chains, specified by the source port/channel IDs.
IBC uniquely identifies assets based on their transfer path. Hence, the same asset, sent to the same destination over two different paths is not fungible with each other. For instance, ATOM sent from Cosmos Hub to Osmosis, and ATOM sent from Juno to Osmosis will not be treated as the same asset.
While this path-based identification is crucial for security (as each chain represents a different security model), it creates a UX challenge. Users must manually "unwind" tokens to their source chain before redirecting them to maintain fungibility (going back to the example above, users would need to send their ATOM from Juno -> Cosmos Hub -> Osmosis to retain fungibility).
ibc-go v9 solves this problem by performing packet unwinding and forwarding natively within the ICS-20 protocol, abstracting away the routing problems from end users.
In previous versions, users and applications could only send a single denomination/token within a transfer message. This restricted applications of the type that can for instance send two different tokens to a liquidity pool on a DEX chain, and provide liquidity on both sides of the poos, all within a single transfer.
This section details the technical implementation of transfer v2's key features: token forwarding/unwinding, multi-token packets, and send-all functionality.
To support token forwarding and unwinding within transfer, a new optional Forwarding
field has been added to MsgTransfer
, containing:
unwind
: A boolean for asset unwinding
hops
: A field for asset forwarding, consisting of source {port ID, channel ID}
pairs specifying the multi-hop transfer route
V9 introduces a new packet data type FungibleTokenPacketDataV2
that contains the forwarding information.
The forwarding/unwinding process is atomic: a packet is either successfully processed at the final destination or fails entirely. For example, in an A -> B -> C transfer, if an error or timeout occurs on chain B, the error/timeout propagates back to chain A, and intermediate chains revert to their pre-transfer state (excluding unrelated state changes).
Example Use Case: Consider a user (unaware of the token routing problem) who holds JUNO on the Cosmos Hub and wants to use them on Osmosis.
Without Token Forwarding/Unwinding:
User sends JUNO from Cosmos Hub to Osmosis
On Osmosis there are now two distinct representations of JUNO:
JUNO sent from the Juno chain to Osmosis, and
JUNO sent by the user via Cosmos Hub -> Osmosis
With Token Forwarding/Unwinding:
User initiates a single transaction to send JUNO from Cosmos Hub to Osmosis
The protocol automatically handles the routing:
JUNO is sent from Cosmos Hub to Juno
JUNO is then forwarded from Juno to Osmosis
On Osmosis, the user receives JUNO and proceeds with their desired action
Key considerations
Only ICS-20 v2 channels support forwarding/unwinding. v1 channels must upgrade using channel upgradability to use these features.
Packets can include both unwinding and forwarding information. Unwinding occurs before forwarding.
The maximum hop limit (unwinding + forwarding) is 8.
Unwinding is supported for packets with a single denom as well as packets with multiple tokens that have the same trace i.e. originating from the same place.
When using forwarding, timeout must be specified using only timeout_timestamp
(i.e. timeout_height
must be zero). Note that timeout_timestamp
must take into account the time that it may take tokens to be forwarded through the intermediary chains.
To support multi-token sends within a single transfer packet, a new tokens
field has been added to MsgTransfer
.
A new type for the packet payload FungibleTokenPacketDataV2
has also been introduced. Instead of specifying the denom and amount as done in v1 of the packet payload, in v2 a repeated list of tokens containing the same information is provided.
In transfer v1, the Denom
field consisted of a string of port ID/channel ID pairs that encoded the route taken by a particular token and the base denom. Transfer v2 improves the Denom
type, separating base denom and trace for improved dev UX and reduced string parsing errors.
// transfer v1 example: sending ATOM from Cosmos Hub -> Osmosis
FungibleTokenPacketData {
Denom: "transfer/channel-141/uATOM",
Amount: "10",
}
// transfer v2 example: sending ATOM and FOO from Cosmos Hub -> Osmosis
FungibleTokenPacketDataV2 {
Tokens: [
{
Denom: {
BaseDenom: "uATOM",
Trace: [
{PortID: "transfer",
ChannelID: "channel-141"},
],
},
Amount: "10",
},
{
Denom: {
BaseDenom: "uFOO",
Trace: [
{PortID: "transfer",
ChannelID: "channel-141"},
],
},
Amount: "10",
},
],
}
To use multi-token packets, chains must upgrade their channel version from ics20-v1
to ics20-v2
using channel upgradability. Chains on ibc-go v9 and above can still receive transfer v1 packets. In this case, the PacketDataV1ToV2
method is used to convert a v1 packet into a v2 packet.
Example Use Case: A user wants to provide liquidity to a new trading pair (e.g., USDC.axl/USDT.axl) on Osmosis.
Without Multi-token Packets:
User sends USDC.axl from Axelar to Osmosis (Transaction 1)
User sends USDT.axl from Axelar to Osmosis (Transaction 2)
User interacts with Osmosis DEX to add liquidity (Transaction 3)
With Multi-token Packets:
User sends a single multi-token transfer from Axelar to Osmosis, specifying both USDC.axl and USDT.axl tokens (Transaction 1)
User can immediately add liquidity to the USDC.axl/USDT.axl pool (Transaction 2)
Apart from transfer v2, ibc-go v9 also improves message routing at the 02-client level by decoupling routing from the encoding structures used by light clients. The benefits of using 02-client
routing v2 include:
Light clients can now use unique encoding structures without being tied to core IBC's assumptions, leading to more modular and isolated development of light clients.
Two different chains with 08-wasm
tendermint client (tendermint client implemented as a Rust contract using the Wasm client) can interact with each other.
The main benefit of this work is for light client developers using 08-wasm
. Now less Wasm-specific logic is required when interacting with light clients written as contracts.
The different light client implementations of the ClientState
interface are not used anymore to route requests to the right light client at the 02-client
layer. Instead, a light client module is registered for every light client type (tendermint, solomachine, etc.) and 02-client
routes the requests to the right light client module based on the client ID.
Note: Light client developers must implement the newly introduced LightClientModule
interface and are encouraged to move the logic implemented in the functions of their light client's implementation of the ClientState
interface to the equivalent function in the LightClientModule
interface. Refer to this table from our light client developer guide to see the equivalence between ClientState
interface functions that have been removed and the functions in the LightClientModule
interface.
ibc-go v7.7 and v8.4 introduced the send-all feature which allows the entire amount of a token balance to be transferred.
By setting the token amount in MsgTransfer
to UnboundedSpendLimit
, the user's entire balance can be transferred.
Example Use case: Consider chain A (ICA controller) and chain B (DEX). A user on chain A wants to:
Transfer assets from A to B
Swap the transferred amount on chain B
Send the resulting balance back to chain A
Previously, each step required a separate IBC packet. While combining steps 2 and 3 into a single ICA transaction is possible, slippage issues make predetermining the exact transfer amount available on the destination after the swap impractical.
The new send-all functionality allows steps 2 and 3 to be combined into a single packet, streamlining the process and improving efficiency.
Note that this feature was released in v7.7 and v8.4 and can be used without transfer v2.
To start using the new transfer v2 features on your chain:
Upgrade to ibc-go v9: Follow our migration docs for information on upgrading from v8 to v9.
Upgrade channels: To use multi-token packets, upgrade your channel versions from ics20-v1 to ics20-v2 using channel upgradability.
ibc-go v9 introduces significant improvements to the ICS-20 token transfer protocol with the release of its v2. This upgrade brings multi-token sends, native packet forwarding and unwinding, as well as the ability to transfer entire token balances. These enhancements improve UX, increase composability between chains, and unlock new use cases for developers leveraging IBC.
Additionally, 02-client
routing v2 offers more flexibility for light client developers. Together, these new features and improvements mark a substantial step forward in the growth and development of IBC.