# [ERC7417] ERCトークンを相互変換する「Token Converter」の仕組みを理解しよう！

> ERC7417は、Ethereum上で広く使われるトークン標準どうしを相互運用できるようにするための仕組みを提案しています。

- URL: https://cardene.dev/articles/3f20df95c12b6f7bd271
- 公開日: 2025-11-06
- 更新日: 2025-11-06
- トピック: Blockchain
- タグ: ethereum, solidity, smartcontract, ブロックチェーン

## はじめに

『DApps開発入門』という本や色々記事を書いているかるでねです。

https://amzn.asia/d/gxvJ0Pw

今回は、Ethereum上で異なるトークン規格（ERC20とERC223）を相互に変換できる仕組みを導入し、互換性と利便性を高める仕組みを提案している**ERC7417**についてまとめていきます！

:::message
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
:::

https://eips.ethereum.org/EIPS/eip-7417

他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4

## 概要

**ERC7417**は、Ethereum上で広く使われるトークン標準どうしを相互運用できるようにするための仕組みを提案しています。
具体的には、ある標準（**ERC20**）のトークンを、別の標準（**ERC223**）のトークンへいつでも相互変換できる「**トークン変換サービス（Token Converter）**」というスマートコントラクトを提供します。
**ERC223**に変換したトークンは、制限なく**ERC20**へ戻すこともできます。
これにより、古い標準との互換性を損なわず、異なる標準が同時に共存し、相互に入れ替えて利用できるようになります。

:::message
**ERC223**については以下の記事を参考にしてください。
:::

[\[ERC223\] ETHと同様の振る舞いをするERC20トークンの仕組みを理解しよう！](/articles/0e0ef7a1c17989689796)

この変換を行うユーザー操作はシンプルです。
ユーザーが元の標準のトークンをConverterコントラクトにデポジット（預け入れ）すると、Converterが自動で別標準のトークンをユーザーへ送り返します。

なお、ここで出てくる用語の意味は以下のとおりです。

* **ERC20** / **ERC223**
    Ethereumで定義されたトークンの仕様（インターフェース）です。
    ウォレットや取引所、DAppがトークンと安全にやり取りできるよう、関数やイベントの振る舞いが決められています。
* スマートコントラクト
    Ethereum上で自動実行されるプログラムです。
    トークンの発行や送受信ルール、変換処理などをコードで厳密に管理します。
* Token Converter
    **ERC7417**で中核となるスマートコントラクトです。
    ユーザーが預けたトークンを、対応する別標準のトークンへ1:1で変換して返します。
* ラッパー（wrapper）
    元の資産を担保に、別の形の資産を発行する仕組みです。
    ここでは、**ERC20**版と**ERC223**版の「**同じ経済的価値のトークン**」を相互にラップするイメージです。

```mermaid
sequenceDiagram
    participant U as ユーザー
    participant C as Token Converter
    participant T20 as ERC-20トークン
    participant T223 as ERC-223トークン

    U->>C: ERC-20トークンをデポジット
    C->>T20: 受領・ロック
    C-->>U: 等価のERC-223トークンを送付

    U->>C: ERC-223トークンをデポジット（巻き戻し）
    C->>T223: 受領・ロック
    C-->>U: 等価のERC-20トークンを送付
```

## 動機

**ERC7417**の狙いは、トークン標準の「**アップグレード**」を、特定のスマートコントラクトを介して随時行えるようにすることです。
既に**ERC20**は最も普及しており、新しい標準（ここでは**ERC223**）へ一斉に移行する明確なプロセスがないことが、採用の妨げになっています。
Converterを用意しておけば、どの時点でも**ERC20**と**ERC223**を並行運用でき、必要に応じて双方向に変換できます。

また、現状では同一銘柄のトークンが複数チェーンや複数標準で流通することが一般的です。
例えばUSDTは、Ethereum上の**ERC20**、TRON上の**TRC20**（本文では「**TRX USDT**」と表現）、BSC上の**BEP20**など、複数の形態で存在します。
**ERC7417**は、この「**同じ価値を持つが標準が異なるトークン**」をEthereumメインネット内だけでも共存させ、相互に変換できる状態を標準化するイメージです。
つまり、Ethereum上で「**ERC20版USDT**」と「**ERC223版USDT**」が同時に存在し、自由に入れ替えられる世界を想定しています。

信頼性の確保も重要な論点です。
Converterコントラクトのデプロイ先アドレスを提案本文に明記し、トークン開発者が「**正しいConverter**」と安全に連携できるようにします。
アドレスが公開・固定されることで、なりすましコントラクトへの誤接続や、相互運用の破綻を避けられます。

さらに、Converterが発行（あるいは包み替え）する**ERC223**トークンは、すべて同一の関数セットと戻り値仕様を備える前提です。
これにより、標準そのものの解釈の揺れや、実装のバラつきによる非互換を避けられます。
**ERC20**では、`transfer`関数の戻り値取り扱いの違いが長年の課題でした。
仕様上は「**成功時に `bool` を返す**」必要がありますが、実際のトークンには以下の3つの実装が混在してきました。

| カテゴリ  | 成功時の挙動 | 失敗時の挙動    |
| ---------- | ------------------ | --------------- |
| ① 戻り値あり・厳格 | `true` を返す | 例外（revert）で失敗を通知  |
| ② 戻り値あり・寛容 | `true`/`false` を返す | `false` を返し、revertしない場合がある |
| ③ 戻り値なし  | 何も返さない | 例外（revert）で失敗を通知        |

厳密には③は**ERC20**仕様に非準拠ですが、実際にはEthereumメインネットのUSDT（アドレス：0xdac17f958d2ee523a2206206994597c13d831ec7）がこの型であり、エコシステムの中核にあるため「**非準拠だから未対応**」という選択肢は現実的ではありません。
このような歴史的事情から、同一標準内での実装差分が無視できない問題となってきました。

Converterは、こうした実装差の吸収に寄与します。
Converterが一貫した仕様の**ERC223**版を提供することで、「**早期の流行期に独自拡張されたトークン**」や「**仕様の解釈が分かれる実装**」があっても、変換後は共通仕様のトークンとして扱えます。
これにより、周辺のスマートコントラクト（例：分散型取引所）側では、ラップ関係にある2つのトークンが「**本質的に同一でいつでも相互変換可能**」であることを前提に、ペアを1つの資産として扱いやすくなります。
結果として、同一資産の別標準版どうしを同一流動性として束ねる、といった設計が可能になります。

最後に、このサービスはEthereumメインネットに一度デプロイしたら恒久的に利用されることを想定しています。
Converterのアドレスを提案本文に明記することで、開発者は迷わず正規のConverterを参照でき、相互運用の前提が揺らがないようにします。

#### 変換フロー

手順は直感的で、ユーザー側の操作は送付先アドレスをConverterにするだけです。

1. 変換元の標準（例：**ERC20**）のトークンを、指定のConverterアドレスへ送付します。
2. Converterは受け取ったトークンをロックし、等価数量の変換先標準（例：**ERC223**）のトークンを自動でユーザーに送付します。
3. 元に戻したい場合は、逆方向（**ERC223** → **ERC20**）に同様の手順を実行します。

このように、Converterは「**価値の保存**」、「**1:1の交換**」、「**いつでも巻き戻せること**」を担保し、**ERC20**と**ERC223**の共存・相互運用を実現します。

## 仕様

### システム構成

トークン変換システムは、以下の2つの主要コンポーネントから構成されます。

| コンポーネント名   | 説明    |
| ------- | ----------------------- |
| **Converterコントラクト** | トークンの入出金、変換、ラップ／アンラップ（包み込み・解除）処理を行う中核的なスマートコントラクト。    |
| **Wrapperコントラクト**   | 元のトークンに対して作成されるラッパーコントラクトで、1つのトークンにつき1種類の標準ごとに1つだけ存在。 |

### Converterコントラクトの基本動作

各**ERC20**トークンに対して、最大1つの**ERC223**ラッパーが存在します。
**ERC20 → ERC223**変換時には、Converterが元のトークンを受け取り、同量の**ERC223**トークンを新規発行して送信者に返します。
逆に**ERC223 → ERC20**変換時には、Converterが受け取った**ERC223**トークンをBurn）し、元の**ERC20**トークンを送り返します。
変換比率は常に**1:1**で固定されます。

### Converterコントラクト

#### `getERC20WrapperFor`

```javascript
function getERC20WrapperFor(address _token) public view returns (address)
```

指定したトークンアドレスに対応する**ERC20**ラッパーのアドレスを取得する関数。
指定された**ERC223**トークンに対応する**ERC20**バージョンを返します。
該当するラッパーが存在しない場合は`0x0`を返します。
1つの**ERC223**トークンにつき、1つの**ERC20**ラッパーのみ作成されます。

**引数**

* `_token`
  * 対象トークンのアドレス。

**戻り値**

* `address`
  * 対応する**ERC20**ラッパーのアドレス。存在しない場合は`0x0`。

#### `getERC223WrapperFor`

```javascript
function getERC223WrapperFor(address _token) public view returns (address)
```

指定されたトークンに対応する**ERC223**ラッパーのアドレスを返す関数。
指定した**ERC20**トークンに対する**ERC223**ラッパーのアドレスを取得します。
存在しない場合は`0x0`が返されます。

**引数**

* `_token`
  * 元となる**ERC20**トークンのアドレス。

**戻り値**

* `address`
  * 対応する**ERC223**ラッパーのアドレス。

#### `getERC20OriginFor`

```javascript
function getERC20OriginFor(address _erc223Token) public view returns (address)
```

指定された**ERC223**ラッパーに対応する元の**ERC20**トークンを取得する関数。
指定アドレスがConverterによって生成された**ERC223**ラッパーである場合、そのラッパーに対応する元の**ERC20**トークンアドレスを返します。
該当がなければ`0x0`を返します。

**引数**

* `_erc223Token`
  * **ERC223**ラッパーのアドレス。

**戻り値**

* `address`
  * 元の**ERC20**トークンのアドレス。

#### `getERC223OriginFor`

```javascript
function getERC223OriginFor(address _erc20Token) public view returns (address)
```

指定された**ERC20**ラッパーに対応する元の**ERC223**トークンを返す関数。
**ERC20**トークンがConverterで作られたラッパーである場合、その元となる**ERC223**トークンアドレスを返します。
該当がなければ`0x0`。

**引数**

* `_erc20Token`
  * **ERC20**ラッパーのアドレス。

**戻り値**

* `address`
  * 元の**ERC223**トークンアドレス。

#### `predictWrapperAddress`

```javascript
function predictWrapperAddress(address _token, bool _isERC20) view external returns (address)
```

未デプロイのラッパーコントラクトのアドレスを予測する関数。
この関数は**CREATE2**オペコードを利用して、まだデプロイされていないラッパーのアドレスを事前に算出します。
生成アドレスはバイトコードの内容に依存します。

* `_isERC20 = true` の場合
    **ERC223**ラッパーのアドレスを予測。
* `_isERC20 = false` の場合
    **ERC20**ラッパーのアドレスを予測。

**引数**

* `_token`
  * 対象トークンのアドレス。
* `_isERC20`
  * トークンが**ERC20**であるかどうか。

**戻り値**

* `address`
  * 予測されるラッパーのアドレス。

#### `createERC223Wrapper`

```javascript
function createERC223Wrapper(address _erc20Token) public returns (address)
```

指定された**ERC20**トークンに対応する新しい**ERC223**ラッパーを作成する関数。
すでにラッパーが存在する場合はトランザクションを `revert` します。
作成後は標準的な**ERC223**仕様を満たし、互換性のために`approve`と`transferFrom`関数も実装されています。
`standard()`関数により「`223`」を返し、トークン標準の識別に使用されます。

**引数**

* `_erc20Token`
  * 元となる**ERC20**トークンアドレス。

**戻り値**

* `address`
  * 新規作成された**ERC223**ラッパーのアドレス。

#### `createERC20Wrapper`

```javascript
function createERC20Wrapper(address _erc223Token) public returns (address)
```

指定された**ERC223**トークンに対応する**ERC20**ラッパーを作成する関数。
すでにラッパーが存在する場合は `revert` します。
`_erc223Token`が実際に**ERC223**であるかどうかを検証しません。
よって、誤って同一標準のラッパーを作成することも技術的には可能です。

**引数**

* `_erc223Token`
  * 元の**ERC223**トークンアドレス。

**戻り値**

* `address`
  * 新しく作成された**ERC20**ラッパーのアドレス。

#### `wrapERC20toERC223`

```javascript
function wrapERC20toERC223(address _ERC20token, uint256 _amount) public returns (bool)
```

**ERC20**トークンを**ERC223**トークンへ変換する関数。
`transferFrom`を使って送信者の**ERC20**を引き出し、同量の**ERC223**ラッパートークンを送付します。
オリジナルのトークンはConverterコントラクト内で保管され、将来のアンラップに備えます。
ラッパーが存在しない場合、自動的に作成されます。

**引数**

* `_ERC20token`
  * 変換元の**ERC20**トークンアドレス。
* `_amount`
  * 変換するトークンの数量。

**戻り値**

* `bool`
  * 成功時は`true`。

#### `convertERC20`

```javascript
function convertERC20(address _token, uint256 _amount) public returns (bool)
```

トークンの種類を自動判定して、**ラップ／アンラップ**を実行する関数。
指定トークンがラッパーである場合は`unwrapERC20toERC223`を実行、
オリジントークンの場合は`wrapERC20toERC223`を実行します。
DAppや取引所などが両標準に対応する時に、変換処理を簡略化できます。

**引数**

* `_token`
  * トークンアドレス。
* `_amount`
  * 変換する数量。

**戻り値**

* `bool`
  * 成功時は`true`。

#### `isWrapper`

```javascript
function isWrapper(address _token) public view returns (bool)
```

指定トークンがConverterによって作られたラッパーかどうかを確認する関数。
指定アドレスがConverter生成のラッパーであれば`true`を返します。
標準の種類（**ERC20** / **ERC223**）は判別しません。

**引数**

* `_token`
  * トークンアドレス。

**戻り値**

* `bool`
  * ラッパーの場合`true`。

#### `tokenReceived`

```javascript
function tokenReceived(address _from, uint _value, bytes memory _data) public override returns (bytes4)
```

**ERC223**トークン受取時に自動的に呼び出されるハンドラー関数。
この関数は**ERC223**の`transfer`時に呼び出され、受け取ったトークンによって以下の処理を実行します。

* Converter生成の**ERC223**ラッパーの場合
    元の**ERC20**トークンを返送。
* Converter未登録の**ERC223**オリジンの場合
    対応する**ERC20**ラッパーを作成し、トークンを送信。

返り値として`0x8943ec02`を返します。

**引数**

* `_from`
  * 送信元アドレス。
* `_value`
  * トークン数量。
* `_data`
  * 追加データ。

**戻り値**

* `bytes4`
  * 固定値 `0x8943ec02`。

#### `extractStuckERC20`

```javascript
function extractStuckERC20(address _token)
```

誤ってConverterに直接送信された**ERC20**トークンを救出する関数。
Converterは、正規の変換記録（`convertERC20toERC223`経由）を保持しています。
`balanceOf(this)`との差分により、誤送信トークンを特定して返却可能にします。

**引数**

* `_token`
  * 対象トークンのアドレス。

### トークン変換の手順

#### ERC20 → ERC223 の変換手順

1. ユーザーが**ERC20**トークンの`approve`関数を呼び出し、Converterに指定数量の引き出しを許可します。
2. `approve`トランザクションがブロックチェーンに承認されるまで待機します。
3. その後、Converterコントラクトの`convertERC20toERC223`関数を呼び出し、変換を実行します。

#### ERC223 → ERC20 の変換手順

1. ユーザーが**ERC223**トークンの`transfer`関数を利用し、Converterアドレス宛に送信します。
2. Converterは受信時に`tokenReceived`を自動実行し、**ERC20**トークンをユーザーに返します。

## 補足

### **ERC223**オリジナルトークンのサポート

トークンコンバーターを設計するうえで、以下の2つの案が検討されました。

| 案  | 内容    | 想定される影響   |
| -- | ----------- | --------------- |
| 案1 | 既存の**ERC20**を**ERC223**に変換する片方向のみを提供              | すべての新規発行が**ERC20**前提になりがちで、**ERC223**をオリジンとして選ぶ開発者が不利になります。
同一資産の2標準版を扱う外部コントラクト（例：1つのプールで両版を束ねたいAMM）も、**ERC223**オリジン＋**ERC20**版のペアを正しく同一資産と認識できません。 |
| 案2 | 任意のオリジントークンに対して、**ERC20**版・**ERC223**版の両ラッパーを作成可能 | **ERC223**をオリジンとして採用したプロジェクトも、コンバーター経由で**ERC20**互換を保てます。DEXやプールが「**同一資産の2標準版**」を正しく同一視でき、相互運用性が向上します。          |

結論として、相互運用性と公平性を最大化するために「**案2**」を採用します。
これにより、**ERC223**をオリジンに選んだ開発者も、追加開発なしに**ERC20**版を得られ、外部サービスは「**同一資産の標準違い**」を正しく1つの資産として扱いやすくなります。

### **ERC223**ラッパーにおける `approve` / `transferFrom` サポート

本来、**ERC223**では受取側コントラクトにコールバック（`tokenReceived`）が届くため、単純なデポジットは`transfer`だけで安全に行えます。
そのため、**ERC223**において`approve` / `transferFrom`は「**機能重複**」に見えます。
しかし、現実には**ERC20**前提で作られたマルチシグや各種コントラクトが多数稼働しており、「**コールバック前提ではない受け取り**」を想定した設計が広く存在します。
エコシステム互換を最優先し、コンバーターが発行する**ERC223**ラッパーにも`approve` / `transferFrom`を実装します。
これにより、参照実装としてそのまま使いやすくなります。

一方で、`transferFrom`には典型的な注意点があります。
`approve`で自分自身に許可した後、`transferFrom(self, contract, X)`のようにコールバック非対応のコントラクトへトークンを押し込めてしまうと、救出ロジックがない限り資産が取り出せなくなる恐れがあります。
`approve` / `transferFrom`はウォレットの通常送金で直接使われる手段ではなく、本来は「**コントラクトが引き出す**」ためのAPIです。
結論として、**ERC223**では可能な限り`transfer`の利用を優先し、`approve` / `transferFrom`はやむを得ない互換性目的に限ってください。

### **ERC223**のイベント仕様を既存エコシステムに合わせる理由

純粋な**ERC223**実装では、以下のようなイベントが発行されます。

```javascript
// 純粋な ERC223 で一般的な Transfer イベント
event Transfer(address indexed _from, address indexed _to, uint256 _value, bytes _data);
```

このイベントは**ERC20**の`Transfer(address,address,uint256)`とはシグネチャが異なるため、ブロックチェーンエクスプローラやウォレット、各種履歴ビューアが正しく認識できない場合があります。
イベント自体はコントラクトの安全性や状態遷移に直接影響する「**必須ロジック**」ではありません。
**ERC7417**では、まずは既存ツール群との互換を優先する方針を採用します。
これにより、監視・分析・会計などの実運用でつまずきにくくなります。

### 標準判定における `standard()` 採用の理由

**ERC165**は「**関数シグネチャの有無**」を検査できますが、**ERC20**と**ERC223**は公開関数の集合が似通っており、表面上のインターフェースだけでは見分けが難しい問題があります。
最終的な挙動は`transfer`の内部ロジック（コントラクト宛の送金でコールバックするか否か）に依存するため、**ERC165**だけでは十分に厳密ではありません。

:::message
**ERC165**については以下の記事を参考にしてください。
:::

[\[ERC165\] コントラクトが特定のインターフェースをサポートしているか確認する仕組みを理解しよう！](/articles/541184977a5cba51b759)

#### 判別が難しい理由を示すサンプル

```javascript
abstract contract Token {
    function name() external virtual returns (string memory);
    function symbol() external virtual returns (string memory);
    function decimals() external virtual returns (uint8);

    function transfer(address, uint256) external virtual returns (bool);
    function approve(address, uint256) external virtual returns (bool);
    function transferFrom(address, address, uint256) external virtual returns (bool);
}
```

この抽象インターフェースでは、**ERC20**と**ERC223**を関数シグネチャだけで区別できません。
内部実装次第で振る舞いが変わるためです。

**ERC20**的な実装例は以下になります。

```javascript
function transfer(address _to, uint256 _amount) external virtual returns (bool) {
    balances[msg.sender] -= _amount;
    balances[_to] += _amount;
}
```

**ERC223**的な実装例（コントラクト宛なら`tokenReceived`を呼ぶ）は以下になります。

```javascript
function transfer(address _to, uint256 _amount) external virtual returns (bool) {
    balances[msg.sender] -= _amount;
    balances[_to] += _amount;
    if (_to.isContract()) {
        IERC223Recipient(_to).tokenReceived(msg.sender, _amount, hex"000000");
    }
}
```

多くのトークンは**ERC165**自体を実装していない場合もあります。
そこでコンバーターは、発行する**ERC223**ラッパーに`standard() returns (uint32)`を実装し、`223`を返す設計にします。
さらに、オリジン側の**ERC223**も任意で同関数を実装して自称できるようにし、未実装であれば「**ERC20とみなす**」という運用ルールを採用します。
この方法は、**ERC165**だけに頼るよりも実態に即した高精度な標準判定になります。

### コードに登場する関数の説明

以下は、上記サンプルコードに登場する関数を、用途が分かるように整理したものです。

#### `name`

```javascript
function name() external virtual returns (string memory);
```

トークン名を取得する関数。
ユーザーに表示するフルネームを返します。
多くのウォレットやエクスプローラで表示に使われます。

**戻り値**

* `string memory`
  * トークン名。

#### `symbol`

```javascript
function symbol() external virtual returns (string memory);
```

トークンシンボルを取得する関数。
取引画面や残高表示で使われる短い識別子（例：`USDT`、`DAI`）を返します。

**戻り値**

* `string memory`
  * トークンシンボル。

#### `decimals`

```javascript
function decimals() external virtual returns (uint8);
```

小数点桁数を取得する関数。
ユーザー表示や金額換算で用いるスケールを返します。`18`が一般的です。

**戻り値**

* `uint8`
  * 小数点桁数。

#### `transfer`

```javascript
function transfer(address _to, uint256 _amount) external virtual returns (bool);
```

トークンを指定アドレスへ送る関数。
送金者の残高から指定量を差し引き、宛先へ加算します。
**ERC223**的な実装では、宛先がコントラクトであれば`tokenReceived`を呼び出して安全な受け取りを担保します。**ERC20**的な実装ではコールバックを行いません。

**引数**

* `_to`
  * 送金先アドレス。
* `_amount`
  * 送金量。

**戻り値**

* `bool`
  * 成功時に`true`を返す実装が推奨です。

#### `approve`

```javascript
function approve(address _spender, uint256 _amount) external virtual returns (bool);
```

第三者が送信者の代わりにトークンを引き出す許可量を設定する関数。
スパイラル承認問題（古い許可量の上書き競合）や誤設定リスクがあるため、適切な手順や`permit`（**EIP2612**）等の利用が望ましいです。
**ERC223**では通常`transfer`が安全ですが、互換性のために用意されます。

:::message
**EIP2612**については以下の記事を参考にしてください。
:::

[\[ERC2612\] ERC20のapproveとtransferFromを1つのトランザクションで実行できる仕組みを理解しよう！](/articles/6809173b2ac428b1ccd0)

**引数**

* `_spender`
  * 引き出し可能者。
* `_amount`
  * 許可する数量。

**戻り値**

* `bool`
  * 成功時に`true`。

#### `transferFrom`

```javascript
function transferFrom(address _from, address _to, uint256 _amount) external virtual returns (bool);
```

事前承認にもとづいて第三者が送金を実行する関数。
`approve`で設定した許可枠内で、`_from`の残高から`_to`へ送金します。
**ERC223**においては、コールバック非対応のコントラクトに資産を押し込める誤用リスクがあるため、原則`transfer`の利用を優先します。

**引数**

* `_from`
  * 送付元アドレス。
* `_to`
  * 送付先アドレス。
* `_amount`
  * 送金量。

**戻り値**

* `bool`
  * 成功時に`true`。

## 互換性

**ERC7417**は、異なるトークン標準を相互に変換できるようにすることで、互換性に関する不安を取り除くことを目的としています。
**ERC20**と**ERC223**を1:1で入れ替えられるため、既存のDAppやツールがどちらか一方にしか対応していなくても、コンバーター経由で実運用を続けられます。

また、このサービス自体は先行する同種の仕組みがなく、過去仕様との互換性問題を引きずりません。そのため、歴史的経緯による制約を受けずに設計されています。

## 参考実装

```js

pragma solidity =0.8.19;

library Address {
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        // solhint-disable-next-line no-inline-assembly
        assembly { size := extcodesize(account) }
        return size > 0;
    }
}

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

interface IERC20Metadata is IERC20 {
    /// @return The name of the token
    function name() external view returns (string memory);

    /// @return The symbol of the token
    function symbol() external view returns (string memory);

    /// @return The number of decimal places the token has
    function decimals() external view returns (uint8);
}

abstract contract IERC223Recipient {
    function tokenReceived(address _from, uint _value, bytes memory _data) public virtual returns (bytes4)
    {
        return 0x8943ec02;
    }
}

abstract contract ERC165 {
    /*
     * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
     */
    bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
    mapping(bytes4 => bool) private _supportedInterfaces;

    constructor () {
        // Derived contracts need only register support for their own interfaces,
        // we register support for ERC165 itself here
        _registerInterface(_INTERFACE_ID_ERC165);
    }
    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return _supportedInterfaces[interfaceId];
    }
    function _registerInterface(bytes4 interfaceId) internal virtual {
        require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
        _supportedInterfaces[interfaceId] = true;
    }
}

abstract contract IERC223 {
    function name()        public view virtual returns (string memory);
    function symbol()      public view virtual returns (string memory);
    function decimals()    public view virtual returns (uint8);
    function totalSupply() public view virtual returns (uint256);
    function balanceOf(address who) public virtual view returns (uint);
    function transfer(address to, uint value) public virtual returns (bool success);
    function transfer(address to, uint value, bytes calldata data) public payable virtual returns (bool success);
    event Transfer(address indexed from, address indexed to, uint value, bytes data);
}

interface standardERC20
{
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

/**
 * @dev Interface of the ERC20 standard.
 */
interface IERC223WrapperToken {
    function name()     external view returns (string memory);
    function symbol()   external view returns (string memory);
    function decimals() external view returns (uint8);
    function standard() external view returns (string memory);
    function origin()   external  view returns (address);

    function totalSupply()                                            external view returns (uint256);
    function balanceOf(address account)                               external view returns (uint256);
    function transfer(address to, uint256 value)                      external payable returns (bool);
    function transfer(address to, uint256 value, bytes calldata data) external payable returns (bool);
    function allowance(address owner, address spender)                external view returns (uint256);
    function approve(address spender, uint256 value)                  external returns (bool);
    function transferFrom(address from, address to, uint256 value)    external returns (bool);

    function mint(address _recipient, uint256 _quantity) external;
    function burn(address _recipient, uint256 _quantity) external;
}

interface IERC20WrapperToken {
    function name()     external view returns (string memory);
    function symbol()   external view returns (string memory);
    function decimals() external view returns (uint8);
    function standard() external view returns (string memory);
    function origin()   external  view returns (address);

    function totalSupply()                                         external view returns (uint256);
    function balanceOf(address account)                            external view returns (uint256);
    function transfer(address to, uint256 value)                   external returns (bool);
    function allowance(address owner, address spender)             external view returns (uint256);
    function approve(address spender, uint256 value)               external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);

    function mint(address _recipient, uint256 _quantity) external;
    function burn(address _recipient, uint256 _quantity) external;
}

contract ERC20Rescue
{
    // ERC20 tokens can get stuck on a contracts balance due to lack of error handling.
    //
    // The author of the ERC7417 can extract ERC20 tokens if they are mistakenly sent
    // to the wrapper-contracts balance.
    // Contact dexaran@ethereumclassic.org
    address public extractor = 0x01000B5fE61411C466b70631d7fF070187179Bbf;
    
    function safeTransfer(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
    }

    function rescueERC20(address _token, uint256 _amount) external 
    {
        safeTransfer(_token, extractor, _amount);
    }
}

contract ERC223WrapperToken is IERC223, ERC165, ERC20Rescue
{
    address public creator = msg.sender;
    address private wrapper_for;

    mapping(address account => mapping(address spender => uint256)) private allowances;

    event Transfer(address indexed from, address indexed to, uint256 amount);
    event TransferData(bytes data);
    event Approval(address indexed owner, address indexed spender, uint256 amount);

    function set(address _wrapper_for) external
    {
        require(msg.sender == creator);
        wrapper_for = _wrapper_for;
    }

    uint256 private _totalSupply;

    mapping(address => uint256) private balances; // List of user balances.

    function totalSupply() public view override returns (uint256)             { return _totalSupply; }
    function balanceOf(address _owner) public view override returns (uint256) { return balances[_owner]; }


    /**
     * @dev The ERC165 introspection function.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return
            interfaceId == type(IERC20).interfaceId ||
            interfaceId == type(standardERC20).interfaceId ||
            interfaceId == type(IERC223WrapperToken).interfaceId ||
            interfaceId == type(IERC223).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev Standard ERC223 transfer function.
     *      Calls _to if it is a contract. Does not transfer tokens to contracts
     *      which do not explicitly declare the tokenReceived function.
     * @param _to    - transfer recipient. Can be contract or EOA.
     * @param _value - the quantity of tokens to transfer.
     * @param _data  - metadata to send alongside the transaction. Can be used to encode subsequent calls in the recipient.
     */
    function transfer(address _to, uint _value, bytes calldata _data) public payable override returns (bool success)
    {
        balances[msg.sender] = balances[msg.sender] - _value;
        balances[_to] = balances[_to] + _value;
        if (msg.value > 0) 
        {
            (bool sent, bytes memory data) = _to.call{value: msg.value}("");
            require(sent);
        }
        if(Address.isContract(_to)) {
            IERC223Recipient(_to).tokenReceived(msg.sender, _value, _data);
        }
        emit Transfer(msg.sender, _to, _value, _data);
        emit Transfer(msg.sender, _to, _value); // Old ERC20 compatible event. Added for backwards compatibility reasons.

        return true;
    }

    /**
     * @dev Standard ERC223 transfer function without _data parameter. It is supported for 
     *      backwards compatibility with ERC20 services.
     *      Calls _to if it is a contract. Does not transfer tokens to contracts
     *      which do not explicitly declare the tokenReceived function.
     * @param _to    - transfer recipient. Can be contract or EOA.
     * @param _value - the quantity of tokens to transfer.
     */
    function transfer(address _to, uint _value) public override returns (bool success)
    {
        bytes memory _empty = hex"00000000";
        balances[msg.sender] = balances[msg.sender] - _value;
        balances[_to] = balances[_to] + _value;
        if(Address.isContract(_to)) {
            IERC223Recipient(_to).tokenReceived(msg.sender, _value, _empty);
        }
        emit Transfer(msg.sender, _to, _value, _empty);
        emit Transfer(msg.sender, _to, _value); // Old ERC20 compatible event. Added for backwards compatibility reasons.

        return true;
    }

    function name() public view override returns (string memory)   { return IERC20Metadata(wrapper_for).name(); }
    function symbol() public view override returns (string memory) { return string.concat(IERC20Metadata(wrapper_for).symbol(), "223"); }
    function decimals() public view override returns (uint8)       { return IERC20Metadata(wrapper_for).decimals(); }
    function standard() public pure returns (uint32)               { return 223; }
    function origin() public view returns (address)                { return wrapper_for; }


    /**
     * @dev Minting function which will only be called by the converter contract.
     * @param _recipient - the address which will receive tokens.
     * @param _quantity  - the number of tokens to create.
     */
    function mint(address _recipient, uint256 _quantity) external
    {
        require(msg.sender == creator, "Wrapper Token: Only the creator contract can mint wrapper tokens.");
        balances[_recipient] += _quantity;
        _totalSupply += _quantity;
    }

    /**
     * @dev Burning function which will only be called by the converter contract.
     * @param _quantity  - the number of tokens to destroy. TokenConverter can only destroy tokens on it's own address.
     *                     Only the token converter is allowed to burn wrapper-tokens.
     */
    function burn(uint256 _quantity) external
    {
        require(msg.sender == creator, "Wrapper Token: Only the creator contract can destroy wrapper tokens.");
        balances[msg.sender] -= _quantity;
        _totalSupply -= _quantity;
    }

    // ERC20 functions for backwards compatibility.

    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return allowances[owner][spender];
    }

    function approve(address _spender, uint _value) public returns (bool) {
        require(_spender != address(0), "ERC223: Spender error.");

        allowances[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);

        return true;
    }

    function transferFrom(address _from, address _to, uint _value) public returns (bool) {

        require(allowances[_from][msg.sender] >= _value, "ERC223: Insufficient allowance.");

        balances[_from] -= _value;
        allowances[_from][msg.sender] -= _value;
        balances[_to] += _value;

        emit Transfer(_from, _to, _value);

        return true;
    }
}

contract ERC20WrapperToken is IERC20, ERC165, ERC20Rescue
{
    address public creator = msg.sender;
    address public wrapper_for;

    mapping(address account => mapping(address spender => uint256)) private allowances;

    function set(address _wrapper_for) external
    {
        require(msg.sender == creator);
        wrapper_for = _wrapper_for;
    }

    uint256 private _totalSupply;
    mapping(address => uint256) private balances; // List of user balances.


    function balanceOf(address _owner) public view override returns (uint256) { return balances[_owner]; }

    function name()        public view  returns (string memory) { return IERC20Metadata(wrapper_for).name(); }
    function symbol()      public view  returns (string memory) { return string.concat(IERC223(wrapper_for).symbol(), "20"); }
    function decimals()    public view  returns (uint8)         { return IERC20Metadata(wrapper_for).decimals(); }
    function totalSupply() public view override returns (uint256)       { return _totalSupply; }
    function origin()      public view returns (address)                { return wrapper_for; }

    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return
            interfaceId == type(IERC20).interfaceId ||
            interfaceId == type(IERC20WrapperToken).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    function transfer(address _to, uint _value) public override returns (bool success)
    {
        balances[msg.sender] = balances[msg.sender] - _value;
        balances[_to] = balances[_to] + _value;
        emit Transfer(msg.sender, _to, _value);
        return true;
    }

    function mint(address _recipient, uint256 _quantity) external
    {
        require(msg.sender == creator, "Wrapper Token: Only the creator contract can mint wrapper tokens.");
        balances[_recipient] += _quantity;
        _totalSupply += _quantity;
    }

    function burn(address _from, uint256 _quantity) external
    {
        require(msg.sender == creator, "Wrapper Token: Only the creator contract can destroy wrapper tokens.");
        balances[_from] -= _quantity;
        _totalSupply    -= _quantity;
    }

    function allowance(address owner, address spender) public view virtual returns (uint256) {
        return allowances[owner][spender];
    }

    function approve(address _spender, uint _value) public returns (bool) {

        // Safety checks.

        require(_spender != address(0), "ERC20: Spender error.");

        allowances[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);

        return true;
    }

    function transferFrom(address _from, address _to, uint _value) public returns (bool) {

        require(allowances[_from][msg.sender] >= _value, "ERC20: Insufficient allowance.");

        balances[_from] -= _value;
        allowances[_from][msg.sender] -= _value;
        balances[_to] += _value;

        emit Transfer(_from, _to, _value);

        return true;
    }
}

contract TokenStandardConverter is IERC223Recipient
{
    event ERC223WrapperCreated(address indexed _token, address indexed _ERC223Wrapper);
    event ERC20WrapperCreated(address indexed _token, address indexed _ERC20Wrapper);

    mapping (address => ERC223WrapperToken) public erc223Wrappers; // A list of token wrappers. First one is ERC20 origin, second one is ERC223 version.
    mapping (address => ERC20WrapperToken)  public erc20Wrappers;

    mapping (address => address)            public erc223Origins;
    mapping (address => address)            public erc20Origins;
    mapping (address => uint256)            public erc20Supply; // Token => how much was deposited.

    function getERC20WrapperFor(address _token) public view returns (address)
    {
        return address(erc20Wrappers[_token]);
    }

    function getERC223WrapperFor(address _token) public view returns (address)
    {
        return address(erc223Wrappers[_token]);
    }

    function getERC20OriginFor(address _token) public view returns (address)
    {
        return (address(erc20Origins[_token]));
    }

    function getERC223OriginFor(address _token) public view returns (address)
    {
        return (address(erc223Origins[_token]));
    }

    function predictWrapperAddress(address _token,
                                   bool    _isERC20 // Is the provided _token a ERC20 or not?
                                                    // If it is set as ERC20 then we will predict the address of a 
                                                    // ERC223 wrapper for that token.
                                                    // Otherwise we will predict ERC20 wrapper address.
                                  ) view external returns (address)
    {
        bytes memory _bytecode;
        if(_isERC20)
        {
            _bytecode = type(ERC223WrapperToken).creationCode;
        }
        else
        {
            _bytecode = type(ERC20WrapperToken).creationCode;
        }

        bytes32 hash = keccak256(
            abi.encodePacked(
                bytes1(0xff), address(this), keccak256(abi.encode(_token)), keccak256(_bytecode)
          )
        );

        return address(uint160(uint(hash)));
    }

    function tokenReceived(address _from, uint _value, bytes memory /* _data */) public override returns (bytes4)
    {
        require(erc223Origins[msg.sender] == address(0), "Error: creating wrapper for a wrapper token.");
        // There are two possible cases:
        // 1. A user deposited ERC223 origin token to convert it to ERC20 wrapper
        // 2. A user deposited ERC223 wrapper token to unwrap it to ERC20 origin.

        if(erc20Origins[msg.sender] != address(0))
        {
            // Origin for deposited token exists.
            // Unwrap ERC-223 wrapper.

            erc20Supply[erc20Origins[msg.sender]] -= _value;
            safeTransfer(erc20Origins[msg.sender], _from, _value);

            ERC223WrapperToken(msg.sender).burn(_value);

            return this.tokenReceived.selector;
        }
        // Otherwise origin for the sender token doesn't exist
        // There are two possible cases:
        // 1. ERC20 wrapper for the deposited token exists
        // 2. ERC20 wrapper for the deposited token doesn't exist and must be created.
        else if(address(erc20Wrappers[msg.sender]) == address(0))
        {
            // Create ERC-20 wrapper if it doesn't exist.
            createERC20Wrapper(msg.sender);
        }

        // Mint ERC-20 wrapper tokens for the deposited ERC-223 token
        // if the ERC-20 wrapper didn't exist then it was just created in the above statement.
        erc20Wrappers[msg.sender].mint(_from, _value);
        return this.tokenReceived.selector;
    }

    function createERC223Wrapper(address _token) public returns (address)
    {
        require(address(erc223Wrappers[_token]) == address(0), "ERROR: Wrapper exists");
        require(!isWrapper(_token), "Error: Creating wrapper for a wrapper token");
        
        ERC223WrapperToken _newERC223Wrapper     = new ERC223WrapperToken{salt: keccak256(abi.encode(_token))}();
        _newERC223Wrapper.set(_token);
        erc223Wrappers[_token]                   = _newERC223Wrapper;
        erc20Origins[address(_newERC223Wrapper)] = _token;

        emit ERC223WrapperCreated(_token, address(_newERC223Wrapper));
        return address(_newERC223Wrapper);
    }

    function createERC20Wrapper(address _token) public returns (address)
    {
        require(address(erc20Wrappers[_token]) == address(0), "ERROR: Wrapper already exists.");
        require(!isWrapper(_token), "Error: Creating wrapper for a wrapper token");

        ERC20WrapperToken _newERC20Wrapper       = new ERC20WrapperToken{salt: keccak256(abi.encode(_token))}();
        _newERC20Wrapper.set(_token);
        erc20Wrappers[_token]                    = _newERC20Wrapper;
        erc223Origins[address(_newERC20Wrapper)] = _token;

        emit ERC20WrapperCreated(_token, address(_newERC20Wrapper));
        return address(_newERC20Wrapper);
    }

    function wrapERC20toERC223(address _ERC20token, uint256 _amount) public returns (bool)
    {
        // If there is no active wrapper for a token that user wants to wrap
        // then create it.
        if(address(erc223Wrappers[_ERC20token]) == address(0))
        {
            createERC223Wrapper(_ERC20token);
        }
        uint256 _converterBalance = IERC20(_ERC20token).balanceOf(address(this)); // Safety variable.
        safeTransferFrom(_ERC20token, msg.sender, address(this), _amount);

        _amount = IERC20(_ERC20token).balanceOf(address(this)) - _converterBalance;
        erc20Supply[_ERC20token] += _amount;

        erc223Wrappers[_ERC20token].mint(msg.sender, _amount);

        return true;
    }

    function unwrapERC20toERC223(address _ERC20token, uint256 _amount) public returns (bool)
    {
        require(IERC20(_ERC20token).balanceOf(msg.sender) >= _amount, "Error: Insufficient balance.");
        require(erc223Origins[_ERC20token] != address(0), "Error: provided token is not a ERC-20 wrapper.");

        ERC20WrapperToken(_ERC20token).burn(msg.sender, _amount);

        safeTransfer(erc223Origins[_ERC20token], msg.sender, _amount);

        return true;
    }

    function convertERC20(address _token, uint256 _amount) public returns (bool)
    {
        if(isWrapper(_token)) return unwrapERC20toERC223(_token, _amount);
        else return wrapERC20toERC223(_token, _amount);
    }

    function isWrapper(address _token) public view returns (bool)
    {
        return erc20Origins[_token] != address(0) || erc223Origins[_token] != address(0);
    }

    function extractStuckERC20(address _token) external 
    {
        require(msg.sender == address(0x01000B5fE61411C466b70631d7fF070187179Bbf));

        safeTransfer(_token, address(0x01000B5fE61411C466b70631d7fF070187179Bbf), IERC20(_token).balanceOf(address(this)) - erc20Supply[_token]);
    }
    
    function safeTransfer(address token, address to, uint value) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FAILED');
    }

    function safeTransferFrom(address token, address from, address to, uint value) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'TransferHelper: TRANSFER_FROM_FAILED');
    }
}
```

## セキュリティ

#### 標準ごとにコンバーターを分ける理由

理論上は「**任意の標準 ⇄ 任意の標準**」を1つの仕組みで扱うこともできますが、標準ごとに内在するロジックが異なる場合があり、変換の前提や安全条件がずれてしまいます。
そこで**ERC7417**は対象を**ERC20**と**ERC223**に限定し、それぞれの特性に合った変換手法に集中します。
これにより、想定外の相互作用を避けて検証範囲を明確にできます。

#### **ERC20**の`transfer`による誤入金と`rescueERC20`

**ERC20**は受け取り側のコントラクトがトークン受領を検知しないまま`transfer`で直接入金できてしまいます。
受け取り側が取り出し口（救出ロジック）を持たない場合、資産が取り出せず永久にロックされる恐れがあります。
コンバーターが発行する**ERC20**ラッパーも**ERC20**互換である以上、この問題に晒されます。
そのため、コンバーターには誤入金を救出するための`rescueERC20`（仕様文では`extractStuckERC20`に相当）を用意し、正規の変換記録との差分から誤入金分を抽出できる設計とします。

#### `approve`と`transferFrom`依存のリスクと運用

コンバーターは**ERC20**での入金に`approve` → `transferFrom`の二段階を使います。
これは2つの独立したトランザクションであるため、`transferFrom`に依存する前に、承認が成功していることを確実に確認する必要があります。
UIやバックエンドは、承認のチェーン確定と許可量の検証を行い、競合や未反映による失敗を避けるべきです。

#### 無制限承認（Unlimited Approval）の非推奨

多くのUIは利便性を理由に「**無制限承認**」を促しますが、引き出し権限が広すぎると、バグやロール侵害、鍵漏えい時の被害が大きくなります。
**ERC7417**では無制限承認は推奨しません。
必要額に限定し、利用後は承認を取り消すか最小化する運用が望ましいです。

#### 標準の内省が困難であることとハイブリッド実装

現在、コントラクトがどの標準に実質準拠しているかを100%保証する信頼できる内省手段はありません。
例えば、見かけ上は**ERC20**（`approve` / `transferFrom`を備える）でありながら、`transfer`の内部は**ERC223**的にコールバック呼び出しを行う「**ハイブリッド**」実装も構成可能です。
このようなトークンに対しても、コンバーターは**ERC223**ラッパーを作れてしまいます。
ワークフロー自体に致命的な問題は生じませんが、「**コンバーターにERC223ラッパーがある＝オリジンが完全にERC20互換**」という意味にはなりません。
実用上は`standard()`などの補助的内省や、実送金テストなどの検証手順を組み合わせて扱う必要があります。

#### ラッパー作成時の標準検証を行わない前提

信頼できる自動判別法がないため、コンバーターは与えられたアドレスが本当に**ERC20**/**ERC223**かを検証しません。
その結果、同じオリジンに対して**ERC20**ラッパーと**ERC223**ラッパーの両方を作ることができます。
前提として、1つのラッパートークンには必ず1つのオリジンが対応しますが、「**ラッパーをさらにラップする**」ことはできません。
設計上、各オリジンに対し最大2種類（**ERC20**用、**ERC223**用）のラッパーが存在し得る点を、上流・下流のコントラクト双方で前提にしてください。

#### 供給が減衰・バーンされるトークンの非適合

コンバーターは「**変換のために預かったオリジントークンの残高が自律的に減らない**」ことを前提に設計されています。
もしオリジンが時間経過で供給が減衰したり、Burnや徴税等で残高が減少する仕様だと、コンバーター保有分が不足し、1:1の変換保証が破綻します。
この場合、そのトークンに対してコンバーターでの代替版（ラッパー）を提供すべきではありません。

#### セキュリティ論点の整理

| 論点 | 説明   | 影響       | 推奨運用          |
| ---------- | --------- | -------- | --------- |
| 標準別コンバーター    | **ERC20**と**ERC223**に対象を限定   | 想定外挙動を低減 | 対象標準ごとに変換ロジックを分離          |
| **ERC20**誤入金 | `transfer`で受領検知なく入金され得る      | 資産ロックの恐れ | `rescueERC20`相当の救出機能で差分回収 |
| 二段階承認        | `approve`と`transferFrom`は別Tx | タイミング競合  | 承認確定・許可量検証を徹底             |
| 無制限承認        | 便益はあるがリスク増大                  | 被害拡大     | 金額限定・使用後取り消し              |
| 標準内省困難       | 見かけと実挙動が乖離し得る                | 誤判定      | `standard()`や実送金テストを併用    |
| ラッパー検証なし     | 同一オリジンに2ラッパー可                | 設計前提の相違  | 「ラッパー≠ラッパーの対象」ルールを厳守      |
| 減衰供給         | 残高が自律的に減るトークン                | 1:1担保崩壊  | その種のトークンは対象外にする           |

## 引用

Dexaran (@Dexaran) <dexaran@ethereumclassic.org>, "ERC-7417: Token Converter [DRAFT]," Ethereum Improvement Proposals, no. 7417, July 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7417.

## 最後に

今回は「Ethereum上で異なるトークン規格（ERC20とERC223）を相互に変換できる仕組みを導入し、互換性と利便性を高める仕組みを提案している**ERC7417**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下のTwitterのDMなどからお気軽に質問してください！

[Twitter @cardene777](https://twitter.com/cardene777)

他の媒体でも情報発信しているのでぜひ他も見ていってください！

https://chaldene.net/

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://cardene.notion.site/ERC-EIP-2a03fa3ea33d43baa9ed82288f98d4a9?pvs=4
