# Avalanche ICTT

> Avalanche Interchain Token Transfer (ICTT)は、異なるL1ブロックチェーン間でトークンを送付するためのアプリケーションです。

- URL: https://cardene.dev/articles/f1a879d1792ba0
- 公開日: 2025-10-03
- 更新日: 2025-10-03
- トピック: Blockchain
- タグ: solidity, ethereum, smartcontract, zennfes2025free

# はじめに

初めまして。
『DApps開発入門』という本や色々記事を書いているAva Labsのかるでねです。

https://amzn.asia/d/gxvJ0Pw

以下でも情報発信しているので、興味ある記事があればぜひ読んでみてください！

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9

https://twitter.com/cardene777

https://qiita.com/cardene

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

今回は『Avalanche ICTT』についてまとめていきます。
Avalanche ICTTの各処理フローについて図を元にまとめているので、この記事を読むことで非エンジニアでも全体像が掴めます。
また、エンジニア向けにソースコードを元に詳しく説明しているので、技術的にも理解を深められると思います。

:::message
Avalanche ICTTについて、より詳しく知りたい場合は以下を読んでください。
:::

https://zenn.dev/heku/books/657e304323fc92

# ICTT概要

Avalanche Interchain Token Transfer (ICTT)は、異なるL1ブロックチェーン間でトークンを送付するためのアプリケーションです。
このシステムは、複数のL1にデプロイされた一連のスマートコントラクトによって実装されており、クロスチェーン通信には**ICM（Inter-Chain Messaging）** を利用します。

:::message
ICM（Inter-Chain Messaging）については以下の記事を読んでください。
:::

https://zenn.dev/ava_labs_jp/articles/c6edd8d46ec9bc

https://zenn.dev/heku/books/f8e824e148b5de

ICTTは、1つの**TokenHome**コントラクトと、1つ以上の**TokenRemote**コントラクトから構成されます。

**Home**コントラクトは、送付元となるトークンが存在するL1（**Homeチェーン**）上にデプロイされて、トークンの管理を担当します。
**TokenRemote**コントラクトは、送付先となるL1（**Remoteチェーン**）上にデプロイされ、送付したいトークンのラップトークンのコントラクトです。

![ICTT概要の図](https://storage.googleapis.com/zenn-user-upload/15871bd5db54-20251003.png)

# コントラクト構成

コントラクトの継承関係は以下のようになっています。

![コントラクト構成の図](https://storage.googleapis.com/zenn-user-upload/676b1a82cf86-20251003.png)

**TokenHome**コントラクトに、コアの実装や**ERC20**とネイティブトークンで共通する機能の実装や必要な関数の定義などがされています。
**TokenHome**コントラクトを継承している**〇〇UpgradeableContract**では、**ERC20**とネイティブトークンそれぞれ特有の処理をしています。
さらにそれを継承している**ERC20TokenHome**コントラクトと**NativeTokenHome**コントラクトは、**〇〇UpgradeableContract**をそのまま実装してあり、カスタマイズ実装をしたい場合などにはここに実装を追加します。

# トークン送付

トークン送付のプロセスは、Homeチェーンでトークンをロックし、 Remoteチェーンでそのトークンをラップしたトークンを新規に発行します。

![トークン送付の図](https://storage.googleapis.com/zenn-user-upload/1309eba1ce9f-20251003.png)

1. ユーザーが保有しているトークンの `transfer` する権限を **TokenHome**コントラクトに付与するリクエスト。
2. `transfer` する権限を付与。
3. Remoteチェーン上にトークンを送付するリクエスト。
4. トークンを**TokenHome**コントラクトにロックして、Remoteチェーン上にトークンを送付。

**TokenHome**コントラクトは、各L1上の**TokenRemote**コントラクトに送付されたトークン残高を追跡し、トークンが**TokenHome**コントラクトに戻される時には、**TokenHome**コントラクトにロックされていた元のトークンをユーザーに返却します。

![トークン送付の図](https://storage.googleapis.com/zenn-user-upload/353784c4f498-20251003.png)

1. ユーザーがRemoteチェーンに送付したトークンを戻すリクエスト。
2. RemoteチェーンからHomeチェーンにトークンを送付。
3. ロックしていたトークンをユーザーに返す。

**TokenHome**コントラクトは、**ERC20**トークンかネイティブトークンのいずれかを送付し、事前に登録されている任意の**TokenRemote**コントラクトにトークンを送付できます。
Remoteチェーン上のトークンも**ERC20**かネイティブトークンのいずれかであり、ユーザーはHomeチェーンとRemoteチェーン間で以下のような**ERC20**とネイティブトークンの組み合わせでトークンを交換できます。

![トークン送付の図](https://storage.googleapis.com/zenn-user-upload/afd30cb89a5a-20251003.png)

また、マルチホップTransferもサポートしています。
例えば、同じ**TokenHome**コントラクトに接続された2つのRemoteチェーンと各チェーン上の**TokenRemote**コントラクトへの送付は、まず1つ目のRemoteチェーンの**TokenRemote**コントラクトから**TokenHome**コントラクトへリクエストが送られて、そのリクエストを受け取った**TokenHome**コントラクトが2つ目のRemoteチェーンの**TokenRemote**コントラクトにトークンの送付を行います。

![トークン送付の図](https://storage.googleapis.com/zenn-user-upload/1f0467218df3-20251003.png)

基本的なトークン送付に加えて、`sendAndCall`インターフェースが提供されており、これを利用するとトークンの送付とスマートコントラクトでの利用を単一のICMメッセージ内で完結させることができます。
もし宛先のスマートコントラクトの呼び出しが失敗した場合、送付されたトークンは送付先チェーン上のフォールバック用アドレスに送られます。
`sendAndCall`インターフェースにより、送付されたトークンを他のチェーン上のdAppsで直接使用すること（スワップの実行、サービス利用時の手数料支払いなど）が可能になります。

# フロー

:::message
ICTTはICMを活用した仕組みのため、前提としてクロスチェーン通信を行うときには、以下のように「**Teleporter**」、「**Warp Precompile**」、「**Relayer**」を経由しています。
上記を含めたICMの詳しいフローは以下の記事を読んでください。
:::

https://zenn.dev/ava_labs_jp/articles/c6edd8d46ec9bc#%E3%83%95%E3%83%AD%E3%83%BC

## コントラクトのデプロイ

まずは、以下のコントラクトを一式デプロイしていきます。

![コントラクトのデプロイの図](https://storage.googleapis.com/zenn-user-upload/88ee100c72be-20251003.png)

- **Homeチェーン**
    - **TokenHome**コントラクト
    - **ERC20**コントラクト
- **Remoteチェーン**
    - **TokenRemote**コントラクト
 
### TokenHomeコントラクト

**TokenHome**コントラクトをデプロイする時は、以下のように**Teleporter**コントラクトの情報と**ERC20**コントラクトのアドレスを渡します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L112-L124

:::message
`teleporterRegistryAddress` と `teleporterManager` は、**Teleporter**コントラクトのバージョン管理を行うコントラクトです。
:::

:::message
**Teleporter**コントラクトについては以下の記事を読んでください。
:::

https://zenn.dev/heku/books/fe10e09f39a517

ネイティブトークンを使用する場合は、以下のようにネイティブトークンのラップトークンコントラクトのアドレスを渡しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/NativeTokenHomeUpgradeable.sol#L80-L89

これは、ネイティブトークンでも**ERC20**トークンと同じフローで処理されるように、ラップトークンに変換するようにして抽象化されています。

### TokenRemoteコントラクト

**TokenRemote**コントラクトのデプロイ時には、以下のように**ERC20**トークンを継承しているので、**ERC20**のトークン名やシンボルを渡す必要があります。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L30

以下の `tokenName` と `tokenSymbol` に**ERC20**のトークン名とシンボルを渡しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L78-L85

ネイティブトークンの場合は、そのままネイティブトークンがMintされます。

また、`TokenRemoteSettings` には、以下のように**Teleporter**コントラクトの情報とHomeチェーンのチェーンIDと**TokenHome**コントラクトのアドレスを登録しています。
これはICM（Interchain Messaging）で必要になる情報です。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/interfaces/ITokenRemote.sol#L22-L29

## Remoteコントラクトの登録

**TokenHome**コントラクトには、**TokenRemote**コントラクトの情報を登録していないため、事前に登録しておく必要があります。

![Remoteコントラクトの登録の図](https://storage.googleapis.com/zenn-user-upload/22a0d4dc0cdf-20251003.png)

**TokenRemote**コントラクトの方は、**TokenRemote**コントラクトデプロイ時に、**TokenHome**コントラクトの登録は完了しています。

![Remoteコントラクトの登録の図](https://storage.googleapis.com/zenn-user-upload/af0f273ca4c9-20251003.png)

**TokenRemote**コントラクトの登録は、`registerWithHome` 関数を実行することで行われます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L207-L235

ICMを使用した場合、クロスチェーン通信時に呼ばれる関数は、以下の`_receiveTeleporterMessage` 関数です。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L495-L595

受け取ったメッセージタイプ（今回は `REGISTER_REMOTE` ）ごとに処理を分岐させており、**TokenHome**コントラクトで**TokenRemote**コントラクトの登録を行うときは `_registerRemote` 関数が実行されます。
引数には、送付元のL1チェーンのIDと送付元のコントラクト（**TokenRemote**コントラクト）のアドレスを渡しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L590-L594

`_registerRemote` 関数ではいくつかバリデーションを行った後に、**TokenRemote**コントラクトの登録を行なっています。
この時、**ERC20**トークンの桁数が異なる場合に揃えるように

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L170-L225

## 担保額の供給

![担保額の供給の図](https://storage.googleapis.com/zenn-user-upload/46c81891e5dd-20251003.png)

**TokenRemote**コントラクトのデプロイ時に、`initialReserveImbalance_` という値を指定することができます。
この値は、初期発行トークン量として指定することができる値です。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L151-L163

ここで指定された値は、**TokenRemote**コントラクトを**TokenHome**コントラクトへ登録するときに一緒に渡されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L215

そして、**TokenHome**コントラクトでは、`_addCollateral` 関数が実行されて、指定された値分の**TokenHome**コントラクトに紐づいている**ERC20**トークンを担保として預けることで、初期発行を満たしたと判断されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L449C14-L486

以下の `collateralNeeded` という値が `true` になっていれば、初期発行分の担保を預けてもらったと判断します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L476-L477C13

**TokenHome**コントラクト側では、Remoteチェーンのトークン（**TokenRemote**コントラクト）を発行するリクエストを送る場合、以下のように `collateralNeeded` の値を確認しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L766

そのため、ユーザーはこの初期発行が満たされないうちは、該当のRemoteチェーン上にトークンを送付することはできません。

`0` を指定することもでき、この場合は初期発行はなしとみなされて、**TokenRemote**コントラクトのデプロイ後直後にユーザーがRemoteチェーン上にトークンを送付することも可能です。

## Approve

Homeチェーン上の**ERC20**トークンをRemoteチェーンに送付する場合、事前に**TokenHome**コントラクトに紐づいている**ERC20**トークンを**TokenHome**コントラクトに送付額分 `approve` しておく必要があります。
この `approve` をしておくことで、**TokenHome**コントラクトがユーザーの資金を代わりに `transfer` （`transferFrom`）することができるようになります。

:::message
ネイティブトークンの場合は、`approve` は不要です。
:::

## 送付の実行

ユーザーがHomeチェーンからRemoteチェーンに**ERC20**トークンのリクエストを出します。

:::message
ネイティブトークンの送付もほとんど同じフローです。
:::

### Homeチェーン

**TokenHome**コントラクトの `send` 関数を実行します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/ERC20TokenHomeUpgradeable.sol#L122-L124

内部関数である `_send` を実行します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L240-L276

実行条件を確認して、`_prepareSend` 関数で**ERC20**トークンを**TokenHome**コントラクトに送付してロックしています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L755-L788

実行している `safeTransferFrom` 関数は、`SafeERC20TransferFrom` というライブラリの中で実装されています。
`from` に指定したアドレス（ユーザーのアドレス）から、`address(this)` （**TokenHome**コントラクト）に ` amount` で指定されたトークン量を送付しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/utilities/SafeERC20TransferFrom.sol#L46-L60

その後、Teleporterコントラクト経由でRemoteチェーンにリクエストを送信しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L261-L273

この時、以下のように `SINGLE_HOP_SEND` というメッセージタイプを指定しています。
これは、Remoteチェーン側でどのリクエストが識別できるようにするためです。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L253-L258

### Remoteチェーン

ICMでは、宛先チェーンの指定されたコントラクトの `_receiveTeleporterMessage` が呼び出されるようになっています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L329-L368

`SINGLE_HOP_SEND` が指定されているので、以下のように `_withdraw` 関数が呼び出されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L353-L356

`_withdraw` 関数では、以下のように `_mint` 関数を呼び出して、指定されたアドレス（`recipient`）に指定されたトークン量（`amount`）の**ERC20**トークンを送付しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L136-L139

**Teleporter**コントラクトやRelayerを含めたフロー図は以下のようになります。


![Remoteチェーンの図](https://storage.googleapis.com/zenn-user-upload/8e141d0df1ee-20251003.png)

:::message
ICMのフローについて詳しくは以下の記事を読んでください。
:::

https://zenn.dev/ava_labs_jp/articles/c6edd8d46ec9bc#%E3%83%95%E3%83%AD%E3%83%BC

## トークンを戻す

Remoteチェーン上に存在する**ERC20**トークンを、Homeチェーン上の**ERC20**トークンに戻すフローを確認していきます。

実行する関数は、**TokenHome**コントラクトと同じで `send` 関数です。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L114-L116

内部関数である `_send` 関数が呼び出されて、特定のL1チェーン宛の場合は `_processSend` が呼び出されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L291-L299

実行条件を確認して、ICMを活用してHomeチェーン上の**TokenHome**コントラクトにメッセージを送付しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L449-L483

**TokenHome**コントラクトでは、**Teleporter**コントラクトから `_receiveTeleporterMessage` 関数が呼び出され、メッセージタイプに `SINGLE_HOP_SEND` が指定されているため、以下の処理が実行されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L495-L512

内部では `_withdraw` 関数が呼び出されて、指定されたリクエスト元（ユーザー）のアドレス（`recipient`）に指定されたトークン量（`amount` ）の**ERC20**を発行します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/ERC20TokenHomeUpgradeable.sol#L163-L167

フローとしては、**TokenHome**コントラクトからのトークン発行と逆になります。

![トークンを戻すの図](https://storage.googleapis.com/zenn-user-upload/1ce4df7fa11f-20251003.png)

## 追加処理実行

トークン送付に合わせて、任意の処理を実行することもできます。
この時には、以下の `sendAndCall` 関数を実行します。
実行したい情報（例えばRemoteチェーン上の特定のコントラクトの特定の関数を実行など）を `input` という引数に渡します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/ERC20TokenHomeUpgradeable.sol#L129-L137

`input` は以下の `SendAndCallInput` という情報を格納できるようになっています。
`recipientContract` に実行対象のコントラクトアドレスを渡し、`recipientPayload` に実行情報をエンコードしたバイトデータを渡します。

https://github.com/ava-labs/icm-contracts/blob/dac65983fb956586aebeadab1c4290d2f87927b4/contracts/ictt/interfaces/ITokenTransferrer.sol#L64-L76

Remoteチェーンでは、**TokenRemote**コントラクトの `_receiveTeleporterMessage` 関数内で、メッセージタイプが `SINGLE_HOP_CALL` の処理（`_handleSendAndCall` 関数）が呼び出されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L357-L368

`_handleSendAndCall` 内では以下でトークン発行処理後に追加処理を実行しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L192-L194

Remoteチェーンの**TokenRemote**コントラクトからリクエストを送付する時も、同じように `sendAndCall` 関数を実行します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L121-L123

フローは、以下のようにトークン送付時のフローに1つ処理が追加されます。

![追加処理実行の図](https://storage.googleapis.com/zenn-user-upload/2f85619b22fa-20251003.png)

## マルチホップ

2つのRemoteチェーン間で**ERC20**トークンの送付を行うフローを確認していきます。

まず、1つ目のRemoteチェーンの**TokenRemote**コントラクトの `send` 関数を実行します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/ERC20TokenRemoteUpgradeable.sol#L114-L116

この時、引数の `input` の `destinationBlockchainID` にもう1つのRemoteチェーンのチェーンIDを指定します。
合わせて、`destinationTokenTransferrerAddress` にももう1つに**TokenRemote**コントラクトのアドレスを指定します。

https://github.com/ava-labs/icm-contracts/blob/dac65983fb956586aebeadab1c4290d2f87927b4/contracts/ictt/interfaces/ITokenTransferrer.sol#L33-L42

`_send` 関数内で、事前に登録されているHomeチェーンのチェーンIDと引数に設定されている `input.destinationBlockchainID` の値が異なるため、マルチホップTransferと判断されて `_processSendMultiHop` 関数が呼び出されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L291-L299

ICMメッセージの送付先は、事前に登録されているHomeチェーン上の**TokenHome**コントラクトになります。
**TokenHome**コントラクトがハブとなり、Remoteチェーン間の送付リクエストを中継します。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenRemote/TokenRemote.sol#L488-L534

Homeチェーン上の**TokenHome**コントラクトでは、**Teleporter**コントラクトから `_receiveTeleporterMessage 関数が呼び出されます。
今回はメッセージタイプに `MULTI_HOP_CALL` が指定されているため、以下の処理が実行されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L558C77-L590

`_routeMultiHopSendAndCall` 関数が内部で呼び出されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L390-L444

渡された情報を元に、もう1つのRemoteチェーンの**TokenRemote**コントラクトにリクエストが送付されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/ictt/TokenHome/TokenHome.sol#L429-L441

このあとは、通常の**TokenHome**コントラクトから**TokenRemote**コントラクトへのリクエストと同じフローになります。
このように、**TokenHome**コントラクトを経由することで、常に**TokenRemote**コントラクトは**TokenHome**コントラクトをもとにトークンを発行する形式にできます。

フロー図は以下のようになります。

![マルチホップの図](https://storage.googleapis.com/zenn-user-upload/ada525ab2279-20251003.png)

# 最後に

今回は『Avalanche ICTT』についてまとめてきました。

他でも色々記事を書いているのでぜひよろしければ読んでいってください！

https://amzn.asia/d/gxvJ0Pw

https://cardene.notion.site/EIP-2a03fa3ea33d43baa9ed82288f98d4a9EIP-2a03fa3ea33d43baa9ed82288f98d4a9

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://chaldene.net/

https://twitter.com/cardene777

https://cardene.substack.com/
