Skip to main content

Overview

IFT (Interchain Fungible Token) is a standard for tokens that can move between chains over IBC v2. Tokens are burned on the source chain and minted on the destination chain. This differs from other escrow-based token standards, which lock tokens in escrow and issue a wrapped representation on the destination chain. IFT enables fungible transfers across chains: the same token exists natively on every chain it is bridged to. IFT is built on ICS-27 GMP. Each transfer is a GMP packet whose payload instructs the destination IFT contract to mint tokens to the receiver. EVM implementation: solidity-ibc-eureka/contracts/utils/IFTBaseUpgradeable.sol
To see IFT in action end-to-end, follow the Cosmos ↔ EVM Interoperability Tutorial.

How it works

Connecting a bridge

Before any tokens can move, each IFT contract must be told which chain it can talk to and who to trust on that chain. Two chains deploy IFT and register each other as counterparties. This is done by calling registerIFTBridge, which stores an IFTBridge record keyed by IBC client ID:
clientId identifies an IBC light client that lives on the chain. A light client tracks the state of a specific remote chain. It receives and verifies state attestations so the local chain can trust that events (like a burn) actually happened on the other side. iftTransfer sends the GMP packet over the IBC client identified by that clientId. Every bridge can be scoped to a chain pair: a light client and a counterparty IFT contract. To bridge to a second chain, register a second bridge with a different clientId.

Mutual registration

Registration must happen on both sides before transfers succeed. The counterpartyIFTAddress field is used by iftMint as an authorization check. When a GMP packet arrives, the IFT contract verifies that the GMP account’s sender matches the registered counterpartyIFTAddress. If the remote chain registers a different address, or hasn’t registered at all, the mint reverts. In practice, both IFT contracts are deployed and each is registered on the other chain; then transfers can flow in both directions.

Sending (iftTransfer)

  1. Caller calls iftTransfer(clientId, receiver, amount, timeoutTimestamp) on the source IFT contract.
  2. The IFT contract burns amount tokens from the caller.
  3. It calls ICS27GMP.sendCall with a mint payload for the counterparty chain, sending a GMP packet over IBC.
  4. On the destination chain, the GMP module routes the packet to the IFT contract via a derived GMP account.
  5. The IFT contract mints amount tokens to receiver.

Receiving (iftMint)

iftMint is called by the GMP account, which is an address on the destination chain derived deterministically from (destinationClientId, senderAddress, salt). Before minting, the IFT contract checks:
  • The caller is a known GMP account (via ICS27GMP.getAccountIdentifier).
  • The GMP account’s sender field matches the counterpartyIFTAddress registered for the bridge.
  • The account’s salt is empty (IFT only trusts unsalted accounts).
No external caller can trigger a mint; only the registered counterparty IFT contract can, through the GMP account it controls.

Failure and refunds

Every in-flight transfer is stored as a PendingTransfer. If the packet times out or the destination call returns an error, the IBC callbacks mechanism calls back into the IFT contract, which re-mints the burned tokens to the original sender. From the sender’s perspective: either tokens arrive on the destination, or they are returned. There is no case where tokens are permanently lost.

Transferring tokens

iftTransfer

The IIFT interface defines two iftTransfer overloads:
  • clientId: the IBC client ID on the local chain that represents the destination chain. Call getIFTBridge(clientId) to confirm a bridge is registered for that client.
  • receiver: the address on the destination chain as a string. For EVM counterparties, pass a hex address. The EVMIFTSendCallConstructor uses Strings.tryParseAddress to parse it into a Solidity address type when encoding the mint call.
  • amount: in the ERC20’s smallest unit (equivalent to wei for 18-decimal tokens).
  • timeoutTimestamp: absolute Unix timestamp in seconds. If the packet is not relayed before this time, it will time out and the sender is refunded.

Events

Checking in-flight transfers

Returns PendingTransfer{sender, amount} if the transfer is still in-flight. Reverts with IFTPendingTransferNotFound if the sequence has no pending transfer (already completed, refunded, or never existed). The sequence is included in the IFTTransferInitiated event.

EVM integration

The IBC demo shows a reference implementation of IFT integration between an EVM and a Cosmos chain. Note that this is a demo and not a production-ready implementation, and the Cosmos IFT module is provided as reference-only. The steps below use the demo as a reference.

Contracts

Prerequisites

  • ICS26Router and ICS27GMP must already be deployed and wired (see EVM deployment).
  • An IBC client must be created on the local chain representing the counterparty chain.
  • The account performing bridge registration must hold the authority role on the IFT contract (owner for IFTOwnable).

1. Deploy IFTOwnable

IFTOwnable is the reference EVM implementation: a UUPS upgradeable logic contract extending IFTBaseUpgradeable, with the deployer as owner. It is deployed behind an ERC1967Proxy. From MinimalDeploy.s.sol:
ics27Gmp is the address of the already-deployed ICS27GMP proxy.

2. Deploy a SendCall constructor

The SendCall constructor encodes the mint payload for the counterparty chain. For an EVM counterparty, deploy EVMIFTSendCallConstructor:

3. Register the bridge

Call registerIFTBridge on the IFT contract (lib/ibc.sh):
The three arguments are:
  • clientId: the IBC client ID on the local EVM chain representing the counterparty chain.
  • counterpartyIFTAddress: the address that will appear as the GMP packet’s sender from the counterparty chain. For an EVM counterparty, this is the EIP-55 checksummed address of the counterparty IFT contract. ICS27GMP.sendCall records the caller using EIP-55 checksum casing, and iftMint does an exact string match against this value.
  • iftSendCallConstructor: the address of the constructor deployed in step 2.

4. Send a transfer

The following command illustrates sending an IFT transfer:
From the demo (lib/demo.sh), $AMOUNT is in the ERC20’s base unit.