# Avalanche ICM

> Avalanche ICM（Interchain Messaging）とは、AvalancheのL1ブロックチェーン間でメッセージを送受信する仕組みです。ここでのメッセージとは、単純な文字列や関数の実行情報などです。

- URL: https://cardene.dev/articles/c6edd8d46ec9bc
- 公開日: 2025-09-27
- 更新日: 2025-09-27
- トピック: Blockchain
- タグ: solidity, ethereum, smartcontract, ＃zennfes2025free

# はじめに

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

https://amzn.asia/d/gxvJ0Pw

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

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

https://twitter.com/cardene777

https://chaldene.net/

https://qiita.com/cardene

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

今回は『Avalanche ICM』についてまとめていきます。
Avalache ICMの構成要素や処理フローについてまとめているので、この記事を読むことで非エンジニアでも全体像が掴めます。
また、エンジニア向けに詳しく説明しているので、技術的にも理解を深められると思います。

# ICMとは

Avalanche ICM（Interchain Messaging）とは、AvalancheのL1ブロックチェーン間でメッセージを送受信する仕組みです。
ここでのメッセージとは、単純な文字列や関数の実行情報などです。
例えば、単純な文字列を宛先L1上のコントラクトに送付したり、リクエスト元のL1上で**ERC20**トークンをBurnして、宛先L1上で**ERC20**トークンをMintするなどの処理を実行できます。

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

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

# 前提

Avalanche ICMのフローを確認する前に、前提として知っておくべき情報をまとめて説明していきます。

## P-Chain

Avalancheには、C-Chainや作成されたL1ブロックチェーンの他に「**P-Chain**」が存在します。
このP-Chainは、各C-Chainや各L1のブロック生成などを行うバリデータの管理を行ったり、バリデータがステーキングしたAVAXの管理などを行います。
また、新たにL1を作成するとき、P-Chainにその情報（バリデータやチェーンIDなど）を登録する必要があります。
このように、P-ChainはAvalanche上でのバリデータ管理・ステーキング・L1のライフサイクル管理を担う中核的なチェーンです。

## バリデータ

![バリデータの図](https://storage.googleapis.com/zenn-user-upload/67b19556b940-20250927.png)
https://build.avax.network/academy/interchain-messaging/02-interoperability/03-multi-chain-networks

Avalanche上のL1には、それぞれバリデータが存在します。
このバリデータがブロックの生成などを行っています。

![バリデータの図](https://storage.googleapis.com/zenn-user-upload/62109067cfd3-20250927.png)
https://medium.com/coinmonks/avalanche-fundamentals-subnet-architecture-explained-e69f5aa3d75a

このバリデータは1つのL1のバリデータにしかなれないわけではなく、複数のL1のバリデータになることができます。

![バリデータの図](https://storage.googleapis.com/zenn-user-upload/9d02aadad46b-20250927.png)
https://build.avax.network/blog/etna-upgrade-motivation

各L1のバリデータになるには、各L1が定めているバリデータになるために必要なそのL1のネイティブトークンのステーキング要件を満たす必要があります。

![バリデータの図](https://storage.googleapis.com/zenn-user-upload/5bfaddec12a4-20250927.png)
https://build.avax.network/blog/etna-upgrade-motivation

また、P-Chainにも情報登録料として、毎月少額のAVAXを支払う必要があります。

## BLS署名

![BLS署名の図](https://storage.googleapis.com/zenn-user-upload/a60406e4fa64-20250927.png)
https://build.avax.network/academy/avalanche-fundamentals/05-interoperability/08-signature-aggregation

**BLS署名（Boneh–Lynn–Shacham Signature）**　は、複数の署名を1つにまとめられる（集約できる）暗号署名方式です。
Avalanche ICM以外にEthereum2.0（BeaconChain）でも採用されています。

1. **署名の集約**
    複数の署名者が同じメッセージに署名した場合、それらを1つの署名にまとめることができます。
    ネットワーク上のデータ量を削減でき、ブロックサイズの効率化につながります。
2. **検証コストの削減**
    集約署名は1回の検証で済むため効率的です。
3. **ペアリング暗号**
    BLS署名は「**ペアリング**」と呼ばれる特殊な暗号計算を利用して署名の正当性を確認します。

例えば、3人のバリデータ（Validator A, B, C）が同じメッセージ `"Block123"` に署名する場合を考えます。

1. 各バリデータが秘密鍵で署名します。
    `sigA = sign(privA, "Block123")`
    `sigB = sign(privB, "Block123")`
    `sigC = sign(privC, "Block123")`
2. それぞれの署名を1つにまとめます（集約）。
    `aggSig = aggregate(sigA, sigB, sigC)`
3. 検証者は1回の検証で確認できます。
    `verify([pubA, pubB, pubC], "Block123", aggSig)`
    これにより、「**A・B・Cが同じメッセージに署名している**」ことを1回の演算で確かめられます。

# Avalanche ICMのフロー

Avalanche ICMのフローについて詳しく説明していきます。

## 構成要素

まずは構成要素について紹介していきます。

### Source Chain & Destination Chain

| Source Chain | Message | Destination Chain |
| ------------ | ------- | ----------------- |
|![Source Chain & Destination Chainの図](https://storage.googleapis.com/zenn-user-upload/41e297eb9385-20250927.png) | ![Source Chain & Destination Chainの図](https://storage.googleapis.com/zenn-user-upload/e4a1df1449b7-20250927.png) | ![Source Chain & Destination Chainの図](https://storage.googleapis.com/zenn-user-upload/2d3fd6f3f232-20250927.png) |
| リクエストを送るL1チェーン | 送付するメッセージ | リクエストの送り先L1チェーン |

https://build.avax.network/academy/interchain-messaging/02-interoperability/02-source-message-destination

メッセージの送付を行うL1チェーンは「**Source Chain**」と呼ばれ、メッセージを受け取るL1チェーンを「**Destination Chain**」と呼びます。
この2つのL1チェーンは事前にP-Chainに登録をしておく必要があります。

:::message
通常、Ava CloudなどでL1チェーン作成時にP-Chainに登録されます。
:::

https://www.avacloud.io/

![Source Chain & Destination Chainの図](https://storage.googleapis.com/zenn-user-upload/19099b73dc92-20250927.png)
https://build.avax.network/academy/interchain-messaging/05-two-way-communication/01-two-way-communication

Source Chainから、ユーザーがDestination Chainに送りたいメッセージのリクエストを行います。

![Source Chain & Destination Chainの図](https://storage.googleapis.com/zenn-user-upload/af5f6eb33b84-20250927.png)

Source ChainとDestination Chain上には、メッセージを他のL1チェーンに送受信したいコントラクト（図の「**Cross-Subnet dApp**」）とメッセージの送受信を中継するTeleporterというコントラクトがデプロイされている必要があります。

「**Cross-Subnet dApp**」は、複数のL1チェーン上にデプロイされている**ERC20**こんとらくとや他のL1チェーンとメッセージをやり取りするコントラクトやそのコントラクトとやり取りするオフチェーンアプリケーションです。
「**Teleporter**」は、他のL1チェーンにメッセージを送付するときのメッセージを含むデータを適切な形式にフォーマットしてくれたり、メッセージの送付が失敗した時に再送する処理を行います。

:::message
「**Teleporter**」について詳しく知りたい方はは以下の記事を読んでください。
:::

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

### Message

送付するメッセージの形式について説明します。

#### シンプルなデータ

送付するメッセージは、そのままの文字列ではなくエンコードを行う必要があります。

![シンプルなデータの図](https://storage.googleapis.com/zenn-user-upload/10aafeb5ebb0-20250927.png)
https://build.avax.network/academy/interchain-messaging/04-icm-basics/02-recap-bytes-encoding-decoding

上記では、「`test`」と「`42`」という2つのデータをまとめてエンコードしています。

:::message
「**エンコード（encode）**」とは、あるデータを特定の形式（フォーマット）に変換することを意味します。
:::

図では、「`("test", 42)`」というメッセージをバイトデータに変換しています。
バイトデータに変換することで、メッセージの取り扱いやすくしています。

```solidity
string memory someString = "test";
uint someNumber = 42;
 
bytes message = abi.encode(someString, someNumber);
```

上記では、`someString` という値に `test` という文字列データを格納しています。
加えて、`someNumber` という値に `42` という数値データを格納しています。
`someString` と `someNumber` の2つの値を `abi.encode` を使用してエンコードしています。

エンコードしたデータは、宛先のL1チェーンで元のデータに戻す（デコード）する必要があります。
デコードする時は、以下のようにデータの型情報とエンコードしたバイトデータを使用します。

```solidity
( 
  string memory someString,
  uint someNumber
) = abi.decode(message, (string, uint));
```

上記は、先ほどエンコードしたデータである `message` とエンコードしたデータの型情報である `string` と `uint` を `abi.decode` に渡すことで、元のデータである `someString` と `someNumber` を取得しています。
`someString` と `someNumber` には、それぞれ、 `test` と `42` の値が入っています。

このようにして、SourceチェーンからDestinationチェーンにメッセージを送付しています。

:::message
Destinationチェーンのデコードを行うコントラクトは、エンコード前のデータがどういった型のデータかを事前に知っておく必要あります。
なぜなら、デコード時に `abi.decode` に `message` のエンコードされる前の型情報がないと適切にデコードができないからです。
:::

#### 関数実行データ

宛先チェーンに関数の実行データをメッセージとして渡したい場合もあります。

![関数実行データの図](https://storage.googleapis.com/zenn-user-upload/f30c3867fd26-20250927.png)
https://build.avax.network/academy/interchain-messaging/06-invoking-functions/01-invoking-functions

この時、データの渡し方として以下の2つの方法があります。

- 関数の引数データをメッセージとして渡す
- 実行したい関数の名前と関数の引数データをメッセージとして渡す

![関数実行データの図](https://storage.googleapis.com/zenn-user-upload/ccb98aafc2b4-20250927.png)
https://build.avax.network/academy/interchain-messaging/06-invoking-functions/02-encoding-multiple-values

関数の引数データをメッセージとして渡す場合、「**シンプルなデータ**」の章で説明したように、関数に渡したい引数のデータをエンコードします。

```sol
string someString = "test";
uint someNumber = 43;
address someAddress = address(0);

bytes message = abi.encode(
  someString,
  someNumber,
  someAddress
);
```

![関数実行データの図](https://storage.googleapis.com/zenn-user-upload/16473f93d1b0-20250927.png)
https://build.avax.network/academy/interchain-messaging/06-invoking-functions/06-encoding-function-name

もう1つの方法が、実行したい関数名と関数の引数データをメッセージとして渡す方法です。

```sol
enum CalculatorAction {
    add,
    concatenate
}
```

上記のようにEnum型で関数名を定義しておきます。

```bash
function encodeAddData(uint256 a, uint256 b) public pure returns (bytes memory) {
    bytes memory paramsData = abi.encode(a, b);
    return abi.encode(CalculatorAction.add, paramsData);
}

function encodeConcatenateData(string memory a, string memory b) public pure returns (bytes memory) {
    bytes memory paramsData = abi.encode(a, b);
    return abi.encode(CalculatorAction.concatenate, paramsData);
}
```

そして、上記のような関数をコントラクト内で用意しておき、先ほど定義した関数名もメッセージ内に含めます。
`paramsData` 内に関数の引数に渡したいデータが格納され、さらにそのバイトデータと関数名（`CalculatorAction.add` もしくは `CalculatorAction.concatenate`）でエンコードしています。
デコードする時は、まず以下のように関数名と関数の引数に渡すデータ（バイトデータ）の型情報を渡してデータを取得します。

```sol
(CalculatorAction actionType, bytes memory paramsData) = abi.decode(message, (CalculatorAction, bytes));
```

宛先チェーンのコントラクトでも、関数名を一覧にしたEnum型を定義しておき、その型情報と関数の引数に渡すデータの型情報（2重でエンコードしたのでここでは `bytes` 型）を `abi.decode` に渡して、関数名（`actionType`）と関数の引数に渡すデータのバイトデータ（`paramsData`）を取得します。

```sol
if (actionType == CalculatorAction.add) {
        (uint256 a, uint256 b) = abi.decode(paramsData, (uint256, uint256));
        _calculatorAdd(a, b);
    } else if (actionType == ...) {
        (string memory text1, string memory text2) = abi.decode(paramsData, (string, string));
        _calculatorConcatenateStrings(text1, text2);
    } else {
        revert("CalculatorReceiverOnDispatch: invalid action");
    }
```

実行する関数は、受け取った関数名によって分岐させます。
最後に関数の引数に渡すデータをデコードして取得します。

このようにメッセージをバイトデータにすることで、柔軟に様々なデータを宛先L1チェーンに送付することができます。
また、先ほど2重にエンコードしましたが、3重、4重...とエンコードすることもでき、複雑な構造にすることも可能です。
ただし、その分処理が重くなったり、管理が複雑になるので注意が必要です。

### Relayer

![Relayerの図](https://storage.googleapis.com/zenn-user-upload/e44bda733e34-20250927.png)
https://build.avax.network/academy/interchain-messaging/08-avalanche-warp-messaging/04-awm-relayer

実際に2つのL1間でメッセージを中継するのがRelayerになります。
これはオフチェーンのコンポーネントで、接続している各L1チェーンからメッセージの送付依頼がないか監視して、メッセージを見つけたら各ノードからBLS署名を収集して、宛先L1チェーンにメッセージを送信します。

:::message
詳しくは以下の記事を読んでください。
:::

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

### Warp Precompile

AvalancheのPrecompileは、L1チェーン作成時にデフォルトで組み込まれているスマートコントラクトです。
「**Warp Precompile**」は、「**Teleporter**」コントラクトから渡されたメッセージを元にRelayerに処理を中継します。
また、宛先L1チェーンの「**Warp Precompile**」は、Relayerから渡されたデータの検証を行います。

:::message
「**Warp Precompile**」やPrecompileについて、詳しくは以下の記事を読んでください。
:::

https://zenn.dev/heku/books/f18290c7ddac09/viewer/11e259

### データ

宛先チェーンに何らかのデータを渡す時は、メッセージだけでは不十分です。
例えば、どのL1チェーンに送るのか、どのコントラクトを呼び出すのかなどがわからないためです。
「**Teleporter**」コントラクトでは、このように他のL1チェーンに送付する時に必要となるデータを受け取って処理をするように定義されています。
「**Cross-Subnet dApp**」のコントラクトは、以下の情報を「**Teleporter**」コントラクトに渡す必要があります。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/teleporter/TeleporterMessenger.sol#L450-L460

- `destinationBlockchainID`
    宛先L1チェーンのチェーンID。
    これは一般的に呼ばれているチェーンIDではなく、L1チェーンを作成時にP-Chainに登録を行なったトランザクションのハッシュ値です。
- `destinationAddress`
    宛先L1チェーン上にデプロイされていて、メッセージを渡したいコントラクトのアドレス。
- `feeInfo`
    Relayerに渡す手数料の情報。
    以下のように定義されています。
https://github.com/ava-labs/icm-contracts/blob/main/contracts/teleporter/ITeleporterMessenger.sol#L44-L47
    - `feeTokenAddress`
        手数料として支払う**ERC20**トークンのコントラクトアドレスかネイティブトークンのアドレス（`0x00...`）。
    - `amount`
        手数料として支払うトークン量。
- `requiredGasLimit`
    実行時のガスリミット。
    この値以上のガスを消費しないように設定できます。
- `allowedRelayerAddresses`
    このメッセージを中継できるRelayerのリスト。
    デフォルトは空で、どのRelayerがメッセージを処理しても問題ないですが、特定のRelayerにのみメッセージを処理させる必要がある場合に指定します。
- `message`
    宛先L1チェーンに送付したいメッセージ。

Relayerはこれらの情報を受け取ってメッセージを宛先L1チェーンに送付しています。
また、「**Cross-Subnet dApp**」のコントラクトは、この定義をせずとも「**Teleporter**」コントラクト側で処理を行ってくれるため、必要な情報を渡せば良い設計になっています。

## フロー

構成要素について説明してきたので、実際にどのようなフローでICMが処理を実行しているかを見ていきます。

![フローの図](https://storage.googleapis.com/zenn-user-upload/28e8e5e1379d-20250927.png)

### 1. メッセージの送付

まずは、Teleporterに準拠したSourceチェーン上のコントラクトからメッセージの送付リクエストをします。

Sourceチェーン上にデプロイされているコントラクトで、以下のようにリクエストに必要な情報をまとめて、Teleporterコントラクトの `sendCrossChainMessage` 関数を呼び出します。

https://github.com/cardene777/avalanche-sample/blob/main/icm-erc20/src/SimpleSender.sol#L38-L50

### 2. Teleporterの中継

`sendCrossChainMessage` 関数が呼び出されると、過去に宛先L1チェーンから現在SourceチェーンとなっているL1チェーンのこのTeleporterコントラクトにメッセージを送付した結果情報を一覧で取得しています。
これは、その処理結果が成功したことを宛先L1チェーンに返すことで、Relayerが報酬を受け取るための処理です。
Relayerはメッセージの送付が完了してから報酬を受け取るため、この処理が必要になります。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/teleporter/TeleporterMessenger.sol#L122-L132

`sendCrossChainMessage` 関数では、具体的な処理を実行する `_sendTeleporterMessage` 関数を呼び出しています。
`_sendTeleporterMessage` 関数では以下の処理を行っています。

- `messageID` の取得
    一意のIDとメッセージを紐づけることで、再送など管理をしやすくしています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/teleporter/TeleporterMessenger.sol#L636-L637

- `TeleporterMessage` の構築
    宛先L1チェーンに送付するデータの形式である `TeleporterMessage` を作成しています。
    `originSenderAddress` はTeleporterコントラクトを呼び出したコントラクトアドレスが格納されるようになっています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/teleporter/TeleporterMessenger.sol#L640-L650

- 手数料徴収
    Teleporterコントラクトを呼び出したコントラクトで設定されている手数料情報を元に、手数料に設定している**ERC20**トークンをTeleporterコントラクトに送付しています。
    Teleporterコントラクトに送付された手数料トークンは、宛先L1チェーンにメッセージを送付できたことを確認したのち、Relayerに送付されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/teleporter/TeleporterMessenger.sol#L656-L667

- イベントの発行
    他のL1チェーンへメッセージを送付したいということを通知するイベントを発行します。
    Relayerはこのイベントを監視しています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/teleporter/TeleporterMessenger.sol#L680-L682

- Warp Precompileの呼び出し
    Warp Precompileを呼び出して、イベントには含めることができなかった以下の情報を別途イベントとして発行するようにしています。
    - `sourceChainID`
        送信元L1のチェーンID。
    - `payload (teleporterMessageBytes)`
        送付したいメッセージのバイトデータ。
    - `originSenderAddress`
        送信元L1でリクエストを投げたコントラクトのアドレス。
    - `messageIndex`
        一意なID。
    - BLS署名
    Relayerは、Teleporterからのイベントの通知を元にWarp Precompileから発行されている上記の情報を含むイベントを取得しにいきます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/teleporter/TeleporterMessenger.sol#L685

### 3. イベントの取得

Teleporterが `SendCrossChainMessage` イベントを発行すると、Relayerはそのイベントを取得しにいきます。
Relayerは接続しているL1チェーン上で `SendCrossChainMessage` イベントを監視しており、イベントが発行されるとすぐにそれを取得しにいきます。

細かい処理については以下で説明しています。

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

先ほど説明したように、Relayerはイベントを取得した後、Warp Precompileから発行されている必要な情報を含むイベントも取得します。
Teleporterのイベント発行と同じトランザクション内で、Warp Precompileの呼び出しを行なっているため、同じトランザクション内のイベントを取得しています。
この時、仮に同じトランザクション内に複数のTeleporterからのイベントとWarp Precompileからのイベントがある場合、イベントの発行順と以下の情報を元に導出できる `messageID` が一致しているかを検証して対応づけしています。

- `networkID`
    送信元ネットワークID。
- `sourceChainID`
    送信元L1チェーンのID。
- `payload`
    `TeleporterMessage`を含む実際の送信データ。

### 4. BLS署名の収集

![4. BLS署名の収集の図](https://storage.googleapis.com/zenn-user-upload/2c704b714d5f-20250927.png)
https://build.avax.network/academy/interchain-messaging/08-avalanche-warp-messaging/06-message-pickup

RelayerはSourceチェーン上のバリデータにBLS署名を依頼します。

![4. BLS署名の収集の図](https://storage.googleapis.com/zenn-user-upload/03474dc37e51-20250927.png)
![4. BLS署名の収集の図](https://storage.googleapis.com/zenn-user-upload/fffec04fba11-20250927.png)

各バリデータはステーキング額に応じて重みがあります。
各L1チェーンの設定に応じて、一定数の重み以上の署名がないとBLS署名検証時に失敗するようになっています。

![4. BLS署名の収集の図](https://storage.googleapis.com/zenn-user-upload/c0fb05687c63-20250927.png)
https://build.avax.network/academy/avalanche-fundamentals/05-interoperability/08-signature-aggregation

各バリデータから署名を収集して1つにまとめることで、バリデータの数が増えても常に一定の長さの署名に集約することができます。

![4. BLS署名の収集の図](https://storage.googleapis.com/zenn-user-upload/17a72ea7ca6a-20250927.png)
https://build.avax.network/academy/avalanche-fundamentals/05-interoperability/08-signature-aggregation

また、検証で使用するために、署名に参加したバリデータの公開鍵も集約しておく必要があります。

### 5. 宛先チェーンにメッセージを送付

BLSの署名を収集できたら、取得したイベントの情報をもとに、指定された宛先L1チェーン上にメッセージを送信します。
この時、宛先L1チェーン上のTeleporterコントラクトを呼び出します。
Relayerは事前に接続しているL1チェーン上のTeleporterコントラクトを登録しています。
各L1チェーンごとにTeleporterコントラクトは1つだけ登録できるため、このTeleporterコントラクトに対してメッセージを送付します。

### 6. 検証

![6. 検証の図](https://storage.googleapis.com/zenn-user-upload/144edbabaa80-20250927.png)
https://build.avax.network/academy/interchain-messaging/08-avalanche-warp-messaging/07-message-delivery

Relayerが宛先L1チェーン上のTeleporterコントラクトを呼び出した時、`receiveCrossChainMessage` 関数が呼び出されます。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/teleporter/TeleporterMessenger.sol#L245C14-L326

`receiveCrossChainMessage` 関数の先頭で、Warp Precompileが呼び出されます。
以下のWarp Precompileの `VerifyPredicate` 関数でBLSの署名検証や宛先L1チェーンが設定したバリデータの署名が重みを満たしているかを検証しています。

https://github.com/ava-labs/subnet-evm/blob/7d679b76c309013f870f24dc7bc635d0773956e2/precompile/contracts/warp/config.go#L189-L240

BLS署名の検証では、バリデータから集約した署名と公開鍵を元に検証を行います。
加えて、事前に宛先L1チェーン上で設定されている重み以上の署名がされているかの検証も行います。
例えば、閾値が50%以上だった場合、署名を行ったバリデータの合計の重みが全体の50%に満たない時は処理が失敗します。

:::message
重みとは、バリデータがそのL1チェーン上でステーキングしているトークン量に応じて決まる値です。
ステーク量が多いほど重みは大きくなります。
:::

`receiveCrossChainMessage` 関数では、他に以下のような検証も行なっています。

- **Teleporterコントラクトからの実行か**
    呼び出し元のTeleporterコントラクトは、呼び出されているTeleporterコントラクトと同じコントラクトアドレスかを検証しています。
    通常、TeleporterコントラクトはCREATE2という仕組みを使用して、同じコントラクトアドレスで各L1チェーンにデプロイされるため、この検証を行なっています。

:::message
CREATE2については以下を読んでください。
:::

https://github.com/cardene777/smart-contract/blob/develop/docs/create2.md

- **宛先チェーンの検証**
    メッセージが呼び出されたTeleporterコントラクトが存在しているL1チェーン宛かを検証します。

- **`messageID` の検証**
    既に実行されている `messageID` ではないかを検証します。
    `messageID` は一意であるため、2回実行あsれることはありません。

- **`allowedRelayerAddresses` の検証**
    メッセージ内に含まれている `allowedRelayerAddresses` でRelayerを指定している場合、指定されたRelayerからの実行かを確認しています。

これらの検証が全て問題なければメッセージが指定されたコントラクトに渡されます。

### 7. メッセージの受信

宛先L1チェーン上の指定されたコントラクトが呼びだれます。
この時、`receiveTeleporterMessage` という関数が呼び出されるため、呼び出されるContractではこの関数を実装しておく必要があります。

https://github.com/cardene777/avalanche-sample/blob/main/icm-erc20/src/SimpleReceiver.sol#L46-L63

`receiveTeleporterMessage` 関数の引数は以下の3つです。

- `originChainID`
    送信元L1チェーンのチェーンID。
- `originSenderAddress`
    送信元L1チェーンでメッセージ送付のリクエストを行なったコントラクトのアドレス。
- `message`
    送付されたメッセージ。

# その他

## 手数料のフロー

![手数料のフローの図](https://storage.googleapis.com/zenn-user-upload/6f14f3362953-20250927.png)
https://build.avax.network/academy/interchain-messaging/11-incentivizing-a-relayer/02-fee-data-flow

Sourceチェーンでメッセージを送付するときに、手数料を設定することができます。
Relayerによっては、一定額の手数料を渡さないと処理を行わないなどの制限をかけることもできるため、この手数料が必須な場合もあります。
この手数料は、実際に宛先L1チェーンにメッセージが送付されたことを確認したのち、Relauerに支払われます。
図のように、まずリクエストされてからSourceチェーンに一度指定された**ERC20**トークンが預けられます。
その後、宛先L1チェーンにメッセージが送付され、その情報をレシートとしてTeleporterコントラクトに渡されてから、最終的にRelayerに手数料が支払われます。

:::message
注意が必要なのが、レシートはDestinationチェーンのTeleporterコントラクトが呼び出された時に、やっとSourceチェーンのTeleporterコントラクトに送付される点です。
そのため、DestinationチェーンのTeleporterコントラクトが呼び出されない限り、一生手数料が支払われないことになります。
:::

## ICM Registry

デフォルトでは、TeleporterコントラクトはUpgradeできないようになっています。
しかし、今後Teleporterの仕様が変化したり、何かしらの脆弱性が見つかるなどした場合に、TeleporterコントラクトをUpgradeしたくなることがあります。
そのために用意されているのが「**ICM Registry**」です。

![ICM Registryの図](https://storage.googleapis.com/zenn-user-upload/2c520833e720-20250927.png)
https://build.avax.network/academy/interchain-messaging/07-icm-registry/02-how-the-icm-registry-works

上記の図では、**Teleporter Registry**というコントラクトを定義して、`getLatestTeleporter` という関数を呼び出すことで、最新のTeleporterコントラクトのアドレスが取得できるようになっています。

https://github.com/ava-labs/icm-contracts/blob/main/contracts/teleporter/registry/TeleporterRegistry.sol#L133C14-L135

各Dappのコントラクトは、この`getLatestTeleporter` 関数を呼び出すことで、常に最新のバージョンのTeleporterコントラクトにアクセスすることができるようになります。
新しいバージョンのTeleporterコントラクトをデプロイした時は、**Teleporter Registry**コントラクトにそのアドレスを登録するだけで、そのコントラクトを最新のTeleporterコントラクトとして使用することができます。

:::message
**Teleporter Registry**について、詳しくは以下の記事を読んでください。
:::

https://zenn.dev/heku/books/fe10e09f39a517/viewer/7b4fb2

# 最後に

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

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

https://amzn.asia/d/gxvJ0Pw

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

https://qiita.com/cardene

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://chaldene.net/

https://twitter.com/cardene777

https://cardene.substack.com/
