# [ERC2019] トークンの発行申請をオンチェーンで行う仕組みを理解しよう！

> ERC2019は、ERC20トークンに「ファンドリクエスト（資金要求）」の機能を追加することで、トークンウォレットの所有者がスマートコントラクトを通じてトークンの発行依頼を行えるようにする拡張仕様です。

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

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、トークン保有者がスマートコントラクトを通じてトークンの発行申請を行えるようにする仕組みを提案している**ERC2019**についてまとめていきます！

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

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

他にも様々なEIPについてまとめています。

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

## 概要

**ERC2019**は、**ERC20**トークンに「**ファンドリクエスト（資金要求）**」の機能を追加することで、トークンウォレットの所有者がスマートコントラクトを通じてトークンの発行依頼を行えるようにする拡張仕様です。
ファンドリクエストは、ウォレット所有者自身か権限を持つ代理人が、資金の発行を依頼するためにコントラクト関数 `orderFund` 関数または `orderFundFrom` 関数を呼び出すことで始まります。

このリクエスト時には、発行元に対する「**ファンドインストラクション（資金調達の指示）**」を付与する必要があります。
この指示に基づき、トークンの発行者（もしくは代理人）は、オフチェーンで確認された資金情報と照合して要求されたウォレットへのトークン送付（主にMint処理）を実行します。

重要なのは、この「**ファンドインストラクション**」は、原文のままオンチェーンに記録するのではなくプライベートなチャネルや暗号化ストレージなど、外部での秘匿性を保った形で取り扱うことが推奨されている点です。
どうしてもオンチェーンに記録する場合は、インストラクションフィールドを暗号化した上で記録するのが望ましいとされています。

## 動機

現在、法定通貨（フィアット）ベースのトークン発行においては、ほとんどの場合まず中央集権的な手続き（例：銀行振込）を経てからトークンが発行されるという流れになっています。
このようなプロセスは、ブロックチェーン本来の「**分散型・オープン性**」とは相反する性質を持っており、プロセス全体の一貫性や透明性に課題が残ります。

**ERC2019**は、トークンのライフサイクルの中で特に「**発行依頼**」フェーズをブロックチェーン上に取り込むことで、より多くの処理をオンチェーンで行えるようにしようというものです。
これにより、以下のような効果が期待されます。

* 資金の発行プロセスがブロックチェーン上に記録され、より高い透明性とトレーサビリティが確保される。
* ユーザーが自身のウォレットから直接発行を依頼できるため、プロセス全体の整合性が向上する。
* オフチェーンでのやり取り（支払い確認など）は残るものの、それを補完する形でオンチェーンの記録が一部の役割を担うことにより、全体として分散的な運用が実現される。

このようにして、中央集権的な発行プロセスを減らし、分散型金融（DeFi）や分散型証券（セキュリティトークン）といった領域でも活用しやすい、より透明で一貫した運用モデルを構築することが可能になります。

## 仕様

```js
interface IFundable /* is ERC-20 */ {
    enum FundStatusCode {
        Nonexistent,
        Ordered,
        InProcess,
        Executed,
        Rejected,
        Cancelled
    }
    function authorizeFundOperator(address orderer) external returns (bool);
    function revokeFundOperator(address orderer) external returns (bool) ;
    function orderFund(string calldata operationId, uint256 value, string calldata instructions) external returns (bool);
    function orderFundFrom(string calldata operationId, address walletToFund, uint256 value, string calldata instructions) external returns (bool);
    function cancelFund(string calldata operationId) external returns (bool);
    function processFund(string calldata operationId) external returns (bool);
    function executeFund(string calldata operationId) external returns (bool);
    function rejectFund(string calldata operationId, string calldata reason) external returns (bool);

    function isFundOperatorFor(address walletToFund, address orderer) external view returns (bool);
    function retrieveFundData(address orderer, string calldata operationId) external view returns (address walletToFund,       uint256 value, string memory instructions, FundStatusCode status);

    event FundOrdered(address indexed orderer, string indexed operationId, address indexed , uint256 value,         string instructions);
    event FundInProcess(address indexed orderer, string indexed operationId);
    event FundExecuted(address indexed orderer, string indexed operationId);
    event FundRejected(address indexed orderer, string indexed operationId, string reason);
    event FundCancelled(address indexed orderer, string indexed operationId);
    event FundOperatorAuthorized(address indexed walletToFund, address indexed orderer);
    event FundOperatorRevoked(address indexed walletToFund, address indexed orderer);
}
```

### 列挙型

#### `FundStatusCode`

```javascript
enum FundStatusCode {
    Nonexistent,
    Ordered,
    InProcess,
    Executed,
    Rejected,
    Cancelled
}
```

資金リクエストの現在の状態を表す列挙型。
各ステータスは、ファンドリクエストのライフサイクルを示します。
未作成、発注済、処理中、完了、拒否、キャンセルの6段階を管理します。

**パラメータ**

* `Nonexistent`
    * リクエストが存在しない状態。
* `Ordered`
    * リクエストが作成された直後の状態。
* `InProcess`
    * 処理中の状態。
* `Executed`
    * リクエストが正常に処理（発行）された状態。
* `Rejected`
    * リクエストが拒否された状態。
* `Cancelled`
    * リクエストがキャンセルされた状態。

### 関数

#### `authorizeFundOperator`

```javascript
function authorizeFundOperator(address orderer) external returns (bool);
```

ウォレット所有者が、ファンドリクエストを代行できるOrdererを承認する関数。
指定されたアドレスに対して、リクエスト作成権限（ファンドオペレーター権限）を与えます。

**引数**

* `orderer`
    * ファンドリクエストの代理発注を許可するアドレス。

**戻り値**

* `bool`
    * 承認に成功したかどうかを示します。

#### `revokeFundOperator`

```javascript
function revokeFundOperator(address orderer) external returns (bool);
```

ウォレット所有者が、ファンドオペレーター権限を取り消す関数。
以前に承認したOperatorのファンドリクエスト権限を無効化します。

**引数**

* `orderer`
    * 権限を取り消す対象のアドレス。

**戻り値**

* `bool`
    * 取り消し処理が成功したかどうかを示します。

#### `orderFund`

```javascript
function orderFund(string calldata operationId, uint256 value, string calldata instructions) external returns (bool);
```

ファンドリクエストを作成する関数。
自分自身のウォレットに対するトークン発行依頼を送信します。
すでに使用された `operationId` は使用できません。

**引数**

* `operationId`
    * 各リクエストを一意に識別するID。
* `value`
    * 発行を希望するトークン量。
* `instructions`
    * オフチェーンで処理するための資金調達指示（例：銀行情報など）。

**戻り値**

* `bool`
    * 作成が成功したかどうかを示します。

#### `orderFundFrom`

```javascript
function orderFundFrom(string calldata operationId, address walletToFund, uint256 value, string calldata instructions) external returns (bool);
```

他者のウォレットに対してファンドリクエストを作成する関数。
承認されたOperatorが、他のウォレットのためにトークン発行を依頼できます。

**引数**

* `operationId`
    * 各リクエストを一意に識別するID。
* `walletToFund`
    * トークンを受け取る対象のウォレット。
* `value`
    * 発行を希望するトークン量。
* `instructions`
    * 資金調達のための指示。

**戻り値**

* `bool`
    * 作成が成功したかどうかを示します。

#### `cancelFund`

```javascript
function cancelFund(string calldata operationId) external returns (bool);
```

既存のファンドリクエストをキャンセルする関数。
ファンドリクエストが「**処理中**」になる前であれば、Ordererまたはウォレット所有者がキャンセルできます。

**引数**

* `operationId`
    * キャンセルしたいリクエストの識別子。

**戻り値**

* `bool`
    * キャンセルが成功したかどうかを示します。

#### `processFund`

```javascript
function processFund(string calldata operationId) external returns (bool);
```

リクエストの状態を「**処理中**」に更新する関数。
トークンオペレーターが指示内容の確認後、処理に入るタイミングで呼び出します。

**引数**

* `operationId`
    * 処理対象となるリクエストID。

**戻り値**

* `bool`
    * ステータス更新が成功したかどうかを示します。

#### `executeFund`

```javascript
function executeFund(string calldata operationId) external returns (bool);
```

トークンを発行してリクエストを完了扱いにする関数。
トークンオペレーターがリクエストを確認後、該当するウォレットにトークンをMintしてリクエストを完了させます。

**引数**

* `operationId`
    * 完了させるリクエストID。

**戻り値**

* `bool`
    * 実行が成功したかどうかを示します。

#### `rejectFund`

```javascript
function rejectFund(string calldata operationId, string calldata reason) external returns (bool);
```

リクエストを拒否する関数。
不正確・不適切な指示内容などがある場合に、理由を添えてリクエストを拒否します。
`reason`には**ERC1066**コードなどの標準的な理由コードを使うことが推奨されます。

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

[\[ERC1066\] コントラクトの実行結果を詳細に取得する仕組みを理解しよう！](/articles/f46ae4018f20a2f76c4b)

**引数**

* `operationId`
    * 拒否対象のリクエストID。
* `reason`
    * 拒否理由。

**戻り値**

* `bool`
    * 拒否処理が成功したかどうかを示します。

#### `isFundOperatorFor`

```javascript
function isFundOperatorFor(address walletToFund, address orderer) external view returns (bool);
```

指定されたOrdererが、対象ウォレットに対してリクエストを送信できるかを確認する関数。
ファンドオペレーター権限が与えられているかをチェックします。

**引数**

* `walletToFund`
    * 対象ウォレット。
* `orderer`
    * チェック対象のOperator。

**戻り値**

* `bool`
    * 権限があるかどうかを示します。

#### `retrieveFundData`

```javascript
function retrieveFundData(address orderer, string calldata operationId) external view returns (
  address walletToFund,
  uint256 value,
  string memory instructions,
  FundStatusCode status
);
```

指定されたリクエストの詳細情報を取得する関数。
Orderer、対象ウォレット所有者、オペレーターのみが呼び出すことができます。

**引数**

* `orderer`
    * リクエストを行ったアドレス。
* `operationId`
    * 対象のリクエストID。

**戻り値**

* `walletToFund`
    * 対象ウォレット。
* `value`
    * 発行予定のトークン量。
* `instructions`
    * 資金指示の内容。
* `status`
    * 現在のリクエストステータス。

### イベント

#### `FundOrdered`

```javascript
event FundOrdered(
    address indexed orderer,
    string indexed operationId,
    address indexed walletToFund,
    uint256 value,
    string instructions
);
```

ファンドリクエストが作成された時に発行されるイベント。
Ordererがファンドリクエストを作成した時に発行され、リクエストの識別情報と資金指示が記録されます。

**パラメータ**

* `orderer`
    * ファンドリクエストを作成したアドレス。
* `operationId`
    * リクエストの一意な識別子。
* `walletToFund`
    * トークンを受け取る対象ウォレット。
* `value`
    * 発行を希望するトークン量。
* `instructions`
    * 資金調達のための指示。

#### `FundInProcess`

```javascript
event FundInProcess(
    address indexed orderer,
    string indexed operationId
);
```

ファンドリクエストが処理中に移行した時に発行されるイベント。
トークンオペレーターがリクエストの処理を開始したことを示すイベントで、キャンセルが不可となるタイミングです。

**パラメータ**

* `orderer`
    * リクエストを発注したOrdererのアドレス。
* `operationId`
    * 対象のファンドリクエスト識別子。

#### `FundExecuted`

```javascript
event FundExecuted(
    address indexed orderer,
    string indexed operationId
);
```

ファンドリクエストが完了し、トークンが発行された際に発行されるイベント。
オペレーターによってリクエストが処理され、トークンの発行（Mint）が実行されたことを示します。

**パラメータ**

* `orderer`
    * リクエストを発注したOrderer。
* `operationId`
    * 実行されたリクエストの識別子。

#### `FundRejected`

```javascript
event FundRejected(
    address indexed orderer,
    string indexed operationId,
    string reason
);
```

ファンドリクエストが拒否された時に発行されるイベント。
オペレーターがリクエストを拒否した時にその理由とともに記録されます。
理由は**ERC1066**に準拠したコードなどが使われる場合があります。

**パラメータ**

* `orderer`
    * 拒否されたリクエストのOrderer。
* `operationId`
    * 対象のリクエスト識別子。
* `reason`
    * 拒否理由。

#### `FundCancelled`

```javascript
event FundCancelled(
    address indexed orderer,
    string indexed operationId
);
```

ファンドリクエストがキャンセルされた時に発行されるイベント。
リクエストが「**処理中**」になる前に、Ordererもしくはウォレット所有者がキャンセルを行った場合に記録されます。

**パラメータ**

* `orderer`
    * キャンセルを実行したOrderer。
* `operationId`
    * 対象のリクエスト識別子。

#### `FundOperatorAuthorized`

```javascript
event FundOperatorAuthorized(
    address indexed walletToFund,
    address indexed orderer
);
```

Ordererに対してファンドリクエストの権限が付与された時に発行されるイベント。

**詳細**

指定されたアドレスが、あるウォレットに対してリクエストを行うファンドオペレーターとして承認されたことを示します。

**パラメータ**

* `walletToFund`
    * 権限を付与された対象のウォレット。
* `orderer`
    * ファンドオペレーターとして承認されたアドレス。

#### `FundOperatorRevoked`

```javascript
event FundOperatorRevoked(
    address indexed walletToFund,
    address indexed orderer
);
```

Ordererに付与されていたファンドオペレーター権限が取り消された際に発行されるイベント。
ウォレット所有者が、以前に承認したOrdererの権限を無効化した場合に記録されます。

**パラメータ**

* `walletToFund`
    * 対象となるウォレット。
* `orderer`
    * 権限を取り消されたオーダラーのアドレス。

## 補足

### 分散型での資金調達リクエストを可能にする目的

**ERC2019**は、**ERC20**トークンを保有するユーザーが中央集権的な管理者を介さずにトークン発行の依頼（ファンドリクエスト）を自らブロックチェーン上で開始できるようにすることを目的としています。
従来のトークン発行は、事前に銀行振込などのオフチェーン支払いを行い、それを管理者が確認してからトークンを発行するという形でした。
**ERC2019**では、資金調達の意思表示をオンチェーンで明示し、透明性とトレーサビリティを確保する仕組みを提供します。

### オペレーターによる処理責任とステータス管理

ファンドリクエストは自動で完了するわけではありません。
トークンのオペレーター（運用者）が、実際に支払いが完了したことをオフチェーンで確認した上で、リクエストのステータスを更新する必要があります。
この更新により、「**発注済み（Ordered）**」→「**処理中（InProcess）**」→「**実行済み（Executed）**」や「**拒否（Rejected）**」といったステートの遷移が行われます。

### 指示内容のフォーマット自由度と標準化の方向性

ファンドリクエスト時に入力する「**instructions（資金指示）**」のフォーマットについては、仕様として厳密には定められていません。
自由な形式で入力できますが、将来的な相互運用性を考慮するならば、**ISO20022**などの国際的な送金メッセージ標準に沿った形式を使うのが望ましいとされています。
これにより、外部システムとの統合が容易になります。

### `operationId` の設計と扱い方

ファンドリクエストを一意に識別するための `operationId` は文字列として設計されています。
これは、よりガス効率の良い `bytes32` 型ではなく、人間が読みやすくログなどで容易に追跡可能にするためです。
実装においては、オンチェーンに文字列そのものを保存しても問題ないですし、識別に十分であればそのハッシュ値だけを保存する実装でも問題ありません。

### `operationId` の衝突回避の推奨

`operationId` はグローバルに一意である必要がありますが、仕様上はその衝突を強制的に防ぐ仕組みまでは設けられていません。
そのため、各リクエスト発行者は、ユニークなプレフィックス（例：「`KYC123_`」「`CLIENT01_`」など）を付けて命名することで、他の発行者と識別子が重複しないようにすることが推奨されています。
これにより、複数組織が同じスマートコントラクトを使う状況でも、識別子の競合を回避しやすくなります。

## 互換性

この仕様（IFundable インターフェース）は、既存の**ERC20**規格を拡張する形で設計されているため、互換性を完全に維持しています。

## 引用

Fernando Paris <fer@io.builders>, Julio Faura <julio@adhara.io>, Daniel Lehrner <daniel@io.builders>, "ERC-2019: Fundable Token [DRAFT]," Ethereum Improvement Proposals, no. 2019, May 2019. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2019.

## 最後に

今回は「トークン保有者がスマートコントラクトを通じてトークンの発行申請を行えるようにする仕組みを提案している**ERC2019**」についてまとめてきました！
いかがだったでしょうか？

質問などがある方は以下の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
