# [ERC2018] オフチェーン審査を実現するClearableTokenの仕組みを理解しよう！

> 「クリアリング（Clearing）」とは、金融・銀行業務において、取引の約束がなされてから実際に資金が決済されるまでの一連の手続きを指します。

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

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、オフチェーンのKYCやAMLチェックを可能にし、トークン送金を「**注文・審査・実行**」の3段階で安全に処理できる仕組みをを提案している**ERC2018**についてまとめていきます！

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

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

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

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

## 概要

「**クリアリング（Clearing）**」とは、金融・銀行業務において、取引の約束がなされてから実際に資金が決済されるまでの一連の手続きを指します。
ブロックチェーンにおいても同様に、トランザクションの実行前にその内容を確認・検証し、最終的に処理する役割を担う仕組みが求められます。

この仕組みにおいては、クリアリングエージェント（Clearing Agent）という特別なアカウントが、送金の実行可否を判断します。
送金が承認されるまで、送金予定額はアカウント残高から減少はしないものの、他の送金には利用できないように確保されます。
これにより、後からトランザクションが失敗するリスクを排除し、実行の確実性が保証されます。

また、送金を発注するアカウントは必ずしも本人である必要はなく、オペレーター（Operator）として事前に承認された第三者アカウントが代理で送金を依頼することも可能です。

### 動機

現在、ブロックチェーン上で実装されている**ERC20**などの標準トークン仕様では、KYC（本人確認）やAML（マネーロンダリング対策）などの法的な要求事項を十分に満たすことができません。
特に、これらのチェックは多くの場合オフチェーン（ブロックチェーンの外部）で行う必要があります。
しかし現状では、オフチェーンチェックの結果に基づいてトランザクションの実行を制御する標準的な方法が存在していません。

**ERC2018**では、ユーザーがトークンのTransferを「**注文（オーダー）**」する形式を採用し、その内容をClearing Agentが検証した上で、必要な法的要件を満たしている場合にのみ送金を実行できるようにします。
もし問題がある場合は、送金を拒否してその理由を明示することも可能です。

これにより、KYCやAMLに準拠しなければならない規制対象トークン（Regulated Tokens）でも、安全かつ柔軟にトランザクションの事前審査を行い、透明性とコンプライアンスを両立した仕組みを構築することができます。

## 仕様

```js
interface ClearableToken /* is ERC-1996 */ {
    enum ClearableTransferStatusCode { Nonexistent, Ordered, InProcess, Executed, Rejected, Cancelled }

    function orderTransfer(string calldata operationId, address to, uint256 value) external returns (bool);
    function orderTransferFrom(string calldata operationId, address from, address to, uint256 value) external returns (bool);
    function cancelTransfer(string calldata operationId) external returns (bool);
    function processClearableTransfer(string calldata operationId) external returns (bool);
    function executeClearableTransfer(string calldata operationId) external returns (bool);
    function rejectClearableTransfer(string calldata operationId, string calldata reason) external returns (bool);
    function retrieveClearableTransferData(string calldata operationId) external view returns (address from, address to, uint256 value, ClearableTransferStatusCode status);

    function authorizeClearableTransferOperator(address operator) external returns (bool);
    function revokeClearableTransferOperator(address operator) external returns (bool);
    function isClearableTransferOperatorFor(address operator, address from) external view returns (bool);

    event ClearableTransferOrdered(address indexed orderer, string operationId, address indexed from, address indexed to, uint256 value);
    event ClearableTransferInProcess(address indexed orderer, string operationId);
    event ClearableTransferExecuted(address indexed orderer, string operationId);
    event ClearableTransferRejected(address indexed orderer, string operationId, string reason);
    event ClearableTransferCancelled(address indexed orderer, string operationId);
    event AuthorizedClearableTransferOperator(address indexed operator, address indexed account);
    event RevokedClearableTransferOperator(address indexed operator, address indexed account);
}
```

### イベント

#### `ClearableTransferOrdered`

```javascript
event ClearableTransferOrdered(
    address indexed orderer,
    string operationId,
    address indexed from,
    address indexed to,
    uint256 value
);
```

ClearableなTransferが注文された時に発行されるイベント。
ユーザーまたはオペレーターが、クリアリング処理を必要とする送金を注文（order）した時に発行されます。
処理が確定していない段階で、送金予定額がロックされることを意味します。

**パラメータ**

* `orderer`
    * 送金を注文したアカウントアドレス。
* `operationId`
    * 送金処理の一意な識別子。
* `from`
    * トークンを支払う予定のアカウントアドレス。
* `to`
    * トークンを受け取る予定のアカウントアドレス。
* `value`
    * 実行予定の送金額。

#### `ClearableTransferInProcess`

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

ClearableなTransferが「処理中」ステータスに変更された時に発行されるイベント。
Clearing Agentが処理を開始することを表す中間ステータスで、これ以降はオーダーのキャンセルができなくなります。

**パラメータ**

* `orderer`
    * 送金を注文したアカウントアドレス。
* `operationId`
    * 送金処理の一意な識別子。

#### `ClearableTransferExecuted`

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

ClearableなTransferが正常に実行された時に発行されるイベント。
Clearing Agentの判断により、実際のトークン送付が行われたことを表します。
`from` から `to` へのTransferが完了します。

**パラメータ**

* `orderer`
    * 送金を注文したアカウントアドレス。
* `operationId`
    * 送金処理の一意な識別子。

#### `ClearableTransferRejected`

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

ClearableなTransferが却下された時に発行されるイベント。
Clearing AgentがKYC/AMLなどの審査の結果、送金を拒否したことを示します。
理由は `reason` に文字列として明示されます。

**パラメータ**

* `orderer`
    * 送金を注文したアカウントアドレス。
* `operationId`
    * 送金処理の一意な識別子。
* `reason`
    * 却下の理由。

#### `ClearableTransferCancelled`

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

ClearableなTransferがキャンセルされた時に発行されるイベント。
注文者自身がまだ実行されていない送金注文をキャンセルした場合に発行されます。
処理ステータスが「InProcess」以降でなければキャンセル可能です。

**パラメータ**

* `orderer`
    * 送金を注文したアカウントアドレス。
* `operationId`
    * 送金処理の一意な識別子。

#### `AuthorizedClearableTransferOperator`

```javascript
event AuthorizedClearableTransferOperator(
    address indexed operator,
    address indexed account
);
```

Clearable Transferのオペレーターとして承認された時に発行されるイベント。
`account` が `operator` に対して、自身の代わりにTransferを注文できる権限を与えたことを示します。

**パラメータ**

* `operator`
    * 承認されたオペレーターのアドレス。
* `account`
    * オペレーターが代行する対象アカウントのアドレス。

#### `RevokedClearableTransferOperator`

```javascript
event RevokedClearableTransferOperator(
    address indexed operator,
    address indexed account
);
```

Clearable Transferのオペレーター権限が取り消された時に発行されるイベント。
`account` が `operator` に対する代理送金権限を無効化したことを示します。

**パラメータ**

* `operator`
    * 削除されたオペレーターのアドレス。
* `account`
    * 権限を無効化した対象アカウントのアドレス。

### 関数

#### `orderTransfer`

```javascript
function orderTransfer(string calldata operationId, address to, uint256 value) external returns (bool);
```

送金を注文する関数。
送信者自身（`msg.sender`）から `to` に対してトークンの送付を予約する処理を行います。
この時点では実際の送金は行われませんが、指定額は確保され、他の送金に使えなくなります。
同一の `operationId` がすでに使われていた場合は処理を失敗（revert）させます。

**引数**

* `operationId`
    - クリアリング対象の送金を一意に識別するIDです。
* `to`
    - トークンの受取人となるアドレスです。
* `value`
    - 送金予定額です。
    - 送金者の残高以下である必要があります。

**戻り値**

* `bool`
    - 処理の成功可否を示す真偽値です。

#### `orderTransferFrom`

```javascript
function orderTransferFrom(string calldata operationId, address from, address to, uint256 value) external returns (bool);
```

他のアカウントからの送金を代理注文する関数。
`from` のアカウントから `to` に対して、送金を注文する処理です。
`msg.sender` は、`from` によって承認されたオペレーターである必要があります。
同一の `operationId` がすでに使われていた場合は失敗します。

**引数**

* `operationId`
    - クリアリング対象の送金を一意に識別するIDです。
* `from`
    - トークンを送付するアドレスです。
* `to`
    - トークンの受取人アドレスです。
* `value`
    - 送金予定額です。
    - 送金元の残高以下である必要があります。

**戻り値**

* `bool`
    - 処理の成功可否を示す真偽値です。

#### `cancelTransfer`

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

注文した送金をキャンセルする関数。
送金がまだ「**実行中（InProcess）**」状態に入っていない場合に限り、注文者がその送金をキャンセルできます。
処理中またはすでに実行済みの注文はキャンセルできません。

**引数**

* `operationId`
    - キャンセル対象の送金注文を一意に識別するIDです。

**戻り値**

* `bool`
    - キャンセルが成功したかどうかを示す真偽値です。

#### `processClearableTransfer`

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

送金を「**処理中**」状態に移行する関数。
Clearing Agent が対象の送金を審査中であることを表すために使用します。
この状態になると、注文者は送金をキャンセルできなくなります。
この呼び出しはClearing Agentのみに許可されます。

**引数**

* `operationId`
    - 対象の送金注文を一意に識別するIDです。

**戻り値**

* `bool`
    - ステータス変更が成功したかを示す真偽値です。

#### `executeClearableTransfer`

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

送金を実行する関数。
Clearing Agent によって送金注文が承認された場合に実行されます。
`from` から `to` に対してトークンが実際に送付されます。
Clearing Agentのみが実行できます。

**引数**

* `operationId`
    - 実行対象の送金注文を一意に識別するIDです。

**戻り値**

* `bool`
    - 実行が成功したかを示す真偽値です。

#### `rejectClearableTransfer`

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

送金を却下する関数。
Clearing Agent が審査の結果、注文された送金を拒否するときに使用します。
保持されていたトークンの予約が解除され、再び使用可能になります。
却下理由は文字列で指定できます。

**引数**

* `operationId`
    - 却下対象の送金注文を一意に識別するIDです。
* `reason`
    - 却下理由を表す文字列です。

**戻り値**

* `bool`
    - 却下が成功したかを示す真偽値です。

#### `retrieveClearableTransferData`

```javascript
function retrieveClearableTransferData(string calldata operationId) external view returns (address from, address to, uint256 value, ClearableTransferStatusCode status);
```

送金注文の詳細情報を取得する関数。
指定された `operationId` に紐づく送金注文の状態や送金元・送金先・金額などを参照できます。

**引数**

* `operationId`
    - 取得対象の送金注文を一意に識別するIDです。

**戻り値**

* `from`
    - 送金元アドレス。
* `to`
    - 送金先アドレス。
* `value`
    - 送金額。
* `status`
    - 送金注文の現在のステータスコード。

#### `authorizeClearableTransferOperator`

```javascript
function authorizeClearableTransferOperator(address operator) external returns (bool);
```

Clearable Transferのオペレーターを承認する関数。
自分の代わりに送金を注文できるオペレーターを登録するために使用します。

**引数**

* `operator`
    - 承認するオペレーターのアドレスです。

**戻り値**

* `bool`
    - 承認が成功したかを示す真偽値です。

#### `revokeClearableTransferOperator`

```javascript
function revokeClearableTransferOperator(address operator) external returns (bool);
```

オペレーター権限を解除する関数。
以前に承認したオペレーターの権限を取り消すために使用します。

**引数**

* `operator`
    - 権限を取り消す対象のオペレーターアドレスです。

**戻り値**

* `bool`
    - 解除が成功したかを示す真偽値です。

#### `isClearableTransferOperatorFor`

```javascript
function isClearableTransferOperatorFor(address operator, address from) external view returns (bool);
```

指定のオペレーターが代理送金権限を持っているか確認する関数。
`operator` が `from` に対してClearable Transferの注文権限を持っているかどうかを確認します。

**引数**

* `operator`
    - 確認するオペレーターのアドレスです。
* `from`
    - オペレーター権限が与えられている可能性のあるアカウントです。

**戻り値**

* `bool`
    - オペレーターが権限を持っているかを示す真偽値です。

## 補足

### EIP1996との連携による資金の一時保留

**ERC2018**は、**ERC1996**で定義された「Hold」機構を活用しています。これにより、トークンの送金が「注文」された時点で、実際にはTransferは行われず、その金額が一時的にホールドされる仕組みになっています。
この設計によって、送金の実行可否を判断するまでの間、その金額を二重に使用されるリスクを防ぎ、Transferが承認された場合にのみ確実に送金が実施されることを保証します。

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

[\[ERC1996\] 将来の送金を実行できるHold機能の仕組みを理解しよう！](/articles/828581bf1626b4d8d48c)

### Clearing Agentはオフチェーンで判断を行う

送金の実行可否は、**Clearing Agent**と呼ばれる事前に指定されたエンティティによって判断されます。
Clearing Agentの具体的な実装はこの提案には含まれていませんが、原則としてこの役割はKYC/AMLなどのオフチェーンチェックを行う存在です。
つまり、スマートコントラクトレベルでは判断できない外部情報（例：本人確認や制裁対象チェック）を基に、送金を承認または拒否するという重要な役割を果たします。

### operationIdは文字列で管理される理由

各送金注文を一意に識別するための `operationId` は、数値やハッシュではなく文字列（string）として設計されています。
これはガス効率の面では劣りますが、以下のような利点があります。

* 人間が読みやすく追跡しやすいIDを使える
* オフチェーンでの照合やログ分析において利便性が高い

なお、実装者の判断で、この文字列をオンチェーンにそのまま保存するか、またはハッシュだけを保存する設計にすることも可能です。
識別さえできれば、ハッシュでも運用は成り立ちます。

### operationIdの競合リスクと命名ルール

`operationId` はすべての送金注文においてユニークである必要があります。
複数の送金を同時に扱う場合、IDの重複による競合が起きる可能性があります。
そのため、各発行者がプレフィックス（例："userA-"や"corpX-2025/"）をつけて名前空間を分ける設計が推奨されています。
これは仕様上の必須要件ではありませんが、実運用上の安全性を高めるために重要です。

### 関数命名の方針はERC777に倣っている

`authorizeClearableTransferOperator`、`revokeClearableTransferOperator`、`isClearableTransferOperatorFor` といった関数名は、**ERC777**で定義されているオペレーター機能の命名ルールに従っています。
これにより、他のERC規格との互換性や可読性が向上し、開発者が理解しやすいAPI設計になっています。

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

[\[ERC777\] トークンコントラクトの標準インターフェースの仕組みを理解しよう！](/articles/c0fcb8f3122f66985226)

## 引用

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

## 最後に

今回は「オフチェーンのKYCやAMLチェックを可能にし、トークン送金を「**注文・審査・実行**」の3段階で安全に処理できる仕組みをを提案している**ERC2018**」についてまとめてきました！
いかがだったでしょうか？

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