# [ERC8161] まだ完了していない入金と償還を譲渡できる仕組みを理解しよう！

> ERC8161は、ERC7540の非同期入金リクエストと非同期償還リクエストに対して、Pending状態の残高だけを別のcontrollerへ譲渡できるようにする拡張仕様です。

- URL: https://cardene.dev/articles/7935e7c3fb192c791a81
- 公開日: 2026-05-04
- 更新日: 2026-05-04
- トピック: Blockchain
- タグ: solidity, ブロックチェーン, defi, erc

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、**ERC7540**の非同期リクエストに対して、保留中のポジションを別アドレスへ移せるようにする**ERC8161**についてまとめていきます！

:::message
以下にまとめられているものを解説しながらまとめていきます。
:::

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

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

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

https://twitter.com/cardene777

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

## 概要

**ERC8161**は、**ERC7540**の非同期入金リクエストと非同期償還リクエストに対して、**Pending**状態の残高だけを別の`controller`へ譲渡できるようにする拡張仕様です。

**ERC7540**では、リクエストは`requestDeposit`や`requestRedeem`で作成されたあと、`Pending`、`Claimable`、`Claimed`という順で進みます。
しかし標準の**ERC7540**だけでは、`Pending`のあいだにウォレットを移したい時や、別アカウントへポジションを付け替えたい時に、その保留中ポジションを直接移せません。
**ERC8161**は、その隙間を埋めるための拡張です。

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

:::message
[ERC7540] ERC4626の入金と償還のリクエストと処理実行を分離する仕組みを理解しよう！
:::

[\[ERC7540\] ERC4626の入金と償還のリクエストと処理実行を分離する仕組みを理解しよう！](/articles/4aa8cf3f1806f966ce4a)

以下の図は、**ERC7540**の通常フローのどこに**ERC8161**の譲渡が差し込まれるかを示しています。

![概要の図](/images/qiita-1ac83555-d090-4ae8-9eb2-9bfa20d0585c.webp)

この図で重要なのは、譲渡が許されるのは`Pending`の間だけだという点です。
`Claimable`に入った残高はそのまま請求できる状態にあるため、**ERC8161**はそこへ手を触れません。

## 動機

**ERC7540**の非同期Vaultでは、リクエストが長時間`Pending`のまま残ることがあります。
現実資産を扱うVault、償還の処理に時間がかかるVault、清算や審査をはさむVaultでは、リクエストの完了まで待機時間が発生しやすいです。

このあいだに、利用者側ではいくつかの不都合が生まれます。
たとえばウォレットを新しいアドレスへ移したい場合、保留中のリクエストだけは古い`controller`に残り続けます。
会計上の都合でアカウントを分けたい場合や、保留中ポジションを他プロトコルへ受け渡したい場合も、標準の**ERC7540**だけでは扱いづらいです。

**ERC8161**は、この問題をキャンセルと再申請で回避するのではなく、**保留中残高そのものを移す**という形で解決します。
これにより、ウォレット移行、アカウント再編、保留中ポジションの受け渡しがやりやすくなります。

#### なぜ入金用と償還用を分けるのか

**ERC8161**は、入金側の譲渡と償還側の譲渡を1つの必須インターフェースにまとめていません。
これは、元になっている**ERC7540**自体が、非同期入金と非同期償還を独立して採用できる設計だからです。

入金リクエストでは、すでに基礎資産がVaultへ入っていることが多いです。
一方で償還リクエストでは、シェアが焼却済みか、あるいは一時的にロックされているかなど、会計上の扱いが実装ごとに異なります。
そのため、片方だけ譲渡を許したいVaultがあっても不自然ではありません。

以下の表は、**ERC8161**が想定する実装の選択肢です。

| 実装パターン | 内容 |
|------|------|
| 入金だけ対応 | `IERC7540DepositTransferable`だけ実装する |
| 償還だけ対応 | `IERC7540RedeemTransferable`だけ実装する |
| 両方対応 | 2つのインターフェースを両方実装する |
| 非対応 | **ERC7540**のまま運用する |

以下の図は、入金側と償還側を分けている理由を整理したものです。

![なぜ入金用と償還用を分けるのかの図](/images/qiita-4d5e5baf-1e29-4ecc-b298-297e0f0551bc.webp)

この図で見たいのは、どちらも「待機中の権利を移す」という点では似ていても、背後にある会計や資産拘束の事情が同じではないことです。
そのため提案は、両方を一括で必須にせず、Vaultが片方だけ対応する余地を残しています。

## 仕様

この提案では、まず「どの残高が動くのか」を固定し、そのうえで入金用と償還用の2つのインターフェースを定義しています。
流れとしては、`requestId`と`controller`で識別される**ERC7540**の保留中リクエスト残高に対して、譲渡メソッドがその残高を旧`controller`から新`controller`へ付け替える形です。

### どの残高が移るのか

**ERC7540**では、同じ`requestId`でも`controller`が違えば別のリクエストとして管理されます。
**ERC8161**は、この識別方法をそのまま前提にします。
つまり、`requestId`と`oldController`で指される`Pending`残高を、`newController`側へ移す仕様です。

ただし**ERC7540**には、`requestId == 0`を常に返す実装もあります。
その場合は`requestId`で細かく分けず、`controller`単位で保留中残高を集約して扱います。
**ERC8161**でも、そのVaultが採用している**ERC7540**側の識別ルールに従って、実際に移る残高が決まります。

譲渡の対象になるのは、左から右へ進む流れのうち`Pending`にいる間の残高です。
`requestDeposit`や`requestRedeem`で作られた保留中残高は別アドレスへ移せますが、Vaultの処理が終わって`Claimable`へ進んだ後は、通常の受け取りフローへ戻ります。

以下の図は、`requestId`と`controller`の組み合わせで残高が分かれる場合と、`requestId == 0`として`controller`単位で集約する場合の違いを整理したものです。

![どの残高が移るのかの図](/images/qiita-93be13e6-a22f-4c56-8300-5d7289c38672.webp)

この違いがあるため、同じ譲渡メソッドでも「どの単位の残高が移るのか」はVaultの**ERC7540**実装に依存します。
**ERC8161**は、その識別規則を上書きせず、そのVaultがもともと持っている保留中残高の切り方を前提にします。
統合側が見るべきなのは、`requestId`の値そのものより、「そのVaultがどの粒度で保留中残高を持っているか」です。
図の左側のように`requestId`と`controller`の組ごとに切る実装もあれば、右側のように`controller`単位で集約する実装もあります。

### `IERC7540DepositTransferable`

入金リクエストの譲渡に対応するVaultは、`IERC7540DepositTransferable`を実装します。
このインターフェースでは、メソッド1つとイベント1つが追加されます。

```js
interface IERC7540DepositTransferable {
    event TransferDepositRequest(uint256 indexed requestId, address indexed from, address indexed to);

    function transferDepositRequest(
        uint256 requestId,
        address oldController,
        address newController
    ) external;
}
```

#### `transferDepositRequest`

この呼び出しは、指定した`requestId`に属する保留中の入金リクエスト残高を、`oldController`から`newController`へ丸ごと移します。
部分移転ではなく、**その時点の保留中残高を全量移す**のが前提です。

- 引数

| 項目 | 型 | 内容 |
|------|------|------|
| `requestId` | `uint256` | 対象リクエストの識別子 |
| `oldController` | `address` | いま保留中残高を持っている側 |
| `newController` | `address` | 譲渡先 |

実行後は、`pendingDepositRequest(requestId, oldController)`が減少し、`pendingDepositRequest(requestId, newController)`が同額ぶん増えます。
ただし提案本文では、Vaultが手数料を差し引く設計もあり得るため、増加量は「同額または手数料控除後」として扱われています。

`msg.sender`になれるのは、`oldController`本人か、`oldController`からオペレーター権限を与えられたアカウントだけです。
勝手な第三者が保留中ポジションを横取りできないように、呼び出し主体はここで制限されます。

以下の図は、誰が譲渡メソッドを呼べて、誰が呼べないのかを整理したものです。

![transferDepositRequestの図](/images/qiita-ad19d761-6c15-43af-9b90-9625665c9043.webp)

この境界があるため、譲渡は自由な売買APIというより、まず`controller`本人の意思か、その委任を受けた`operator`の操作として扱われます。
この図が示しているのは**メソッドを叩ける主体**です。
後のセキュリティ節で出てくる図は、承認済みの`operator`が持つ権限の重さを示していて、こちらとは役割が違います。

#### `TransferDepositRequest`

入金リクエストの譲渡が成功した時は、`TransferDepositRequest`イベントを発火しなければなりません。
インデックス付きの項目は、`requestId`、`from`、`to`の3つです。

- 引数

| 項目 | 型 | 内容 |
|------|------|------|
| `requestId` | `uint256` | どのリクエストを動かしたか |
| `from` | `address` | 元の`controller` |
| `to` | `address` | 新しい`controller` |

このイベントがあることで、インデクサーやフロントエンドは、保留中ポジションの持ち主が切り替わったことを追跡できます。

### `IERC7540RedeemTransferable`

償還リクエストの譲渡に対応するVaultは、`IERC7540RedeemTransferable`を実装します。
構造は入金版と同じですが、対象が`pendingRedeemRequest`になる点だけが違います。

```js
interface IERC7540RedeemTransferable {
    event TransferRedeemRequest(uint256 indexed requestId, address indexed from, address indexed to);

    function transferRedeemRequest(
        uint256 requestId,
        address oldController,
        address newController
    ) external;
}
```

#### `transferRedeemRequest`

この呼び出しは、指定した`requestId`に属する保留中の償還リクエスト残高を、`oldController`から`newController`へ丸ごと移します。
ここでも、動いてよいのは`Pending`状態の残高だけです。

- 引数

| 項目 | 型 | 内容 |
|------|------|------|
| `requestId` | `uint256` | 対象リクエストの識別子 |
| `oldController` | `address` | いま保留中残高を持っている側 |
| `newController` | `address` | 譲渡先 |

実行後は、`pendingRedeemRequest(requestId, oldController)`が減少し、`pendingRedeemRequest(requestId, newController)`が増加します。
こちらも`msg.sender`は`oldController`本人か、そのオペレーターである必要があります。

償還側では、すでにシェアが焼却済みであるか、あるいは一時的にロックされているだけかが実装ごとに違います。
そのため提案は、譲渡メソッドの形だけを標準化し、内部会計の方法までは固定していません。

#### `TransferRedeemRequest`

償還リクエストの譲渡が成功した時は、`TransferRedeemRequest`イベントを発火しなければなりません。
項目構成は入金版と同じです。

- 引数

| 項目 | 型 | 内容 |
|------|------|------|
| `requestId` | `uint256` | どのリクエストを動かしたか |
| `from` | `address` | 元の`controller` |
| `to` | `address` | 新しい`controller` |

### 全量移転だけにしている理由

提案は、部分的な残高移転を許さず、**保留中残高の全量移転**だけを定めています。
これは、同じ`requestId`のリクエストが**ERC7540**上では同質として扱われるためです。
同じグループの保留中残高を細かく分割し始めると、実装側は端数処理、部分的な請求可能化、追跡ロジックの複雑化に向き合うことになります。

以下の図は、提案が避けたい複雑さを示しています。

![全量移転だけにしている理由の図](/images/qiita-7f67ef0b-21e8-4766-aa67-6cf5aa5c17d6.webp)

この設計なら、保留中ポジションを「いまの持ち主から別の持ち主へ付け替える」という目的に絞れます。
Claimableまで含めた完全な権利売買基盤にするのではなく、あくまで**Pending**の管理改善に範囲を限定しています。

### ERC165サポート

この拡張を実装するコントラクトは、**ERC165**の`supportsInterface`も実装しなければなりません。
そのうえで、どちらの譲渡インターフェースを持っているかを`interfaceId`で外部から判定できるようにします。

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

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

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

| インターフェース | `supportsInterface`で返すべきID |
|------|------|
| `IERC7540DepositTransferable` | `0x53b3bb0a` |
| `IERC7540RedeemTransferable` | `0x7846f5bd` |

インテグレーターはこの判定によって、そのVaultが入金リクエストの譲渡に対応しているのか、償還リクエストの譲渡に対応しているのか、両方なのかを実行前に確認できます。

以下の図は、`supportsInterface`でどの譲渡機能が使えるかを事前に見分ける流れを示しています。

![ERC165サポートの図](/images/qiita-cbe63e7f-467a-4160-a755-031046961a7b.webp)

この判定を先に入れておけば、入金側だけに対応したVaultへ償還側の譲渡呼び出しを送る、といった無駄を避けられます。
同時に、UI側でも「このVaultではどの譲渡ボタンを見せるべきか」を安全に分けられます。

## 補足

#### `Claimable`を譲渡対象にしない理由

提案は、譲渡対象を`Pending`だけに限定しています。
`Claimable`の残高は、すでに請求可能量と交換比率が決まっており、`controller`が通常のクレーム呼び出しをすれば受け取れる状態です。
ここまで来た残高をさらに譲渡対象へ含めると、部分請求との競合や、どの時点の価値で受け渡したのかという論点が増えます。

そのため**ERC8161**は、値決めがまだ確定していない待機中ポジションの移動だけを扱い、請求可能になった残高は既存のフローへ委ねます。
範囲を絞ることで、インターフェースの責務も明確になります。

以下の図は、`Pending`と`Claimable`で譲渡の扱いを分けている理由をまとめたものです。

![Claimableを譲渡対象にしない理由の図](/images/qiita-38388fc2-e9cf-442e-8d4e-4c291996290d.webp)

この切り分けによって、**ERC8161**は「待機中の権利の移動」だけを担当し、請求可能になった後の受け取りや価値確定までは既存フローに任せています。
左側は、まだ結果が確定していない待機中権利だからこそ「持ち主を変えたい」という需要があります。
右側は、すでに受け取りに進める段階なので、ここまで譲渡対象に含めると、請求処理と権利移転がぶつかりやすくなります。

#### 二次流通では価格参照を自前で持つ必要がある

Vaultシェアには`convertToShares`や`convertToAssets`のような参照点がありますが、保留中リクエストにはそれがありません。
とくに`Pending`の償還リクエストでは、最終的にどれだけの資産が返るかが、処理完了まで確定しないことがあります。

そのため、保留中ポジションを売買する二次市場を作る場合は、価格の決め方を別途設計する必要があります。
標準は譲渡の手段を与えるだけで、価格発見や決済の方法までは定めません。

以下の図は、通常のVaultシェアと違って、保留中リクエストには標準の価格参照が付いていないことを示しています。

![二次流通では価格参照を自前で持つ必要があるの図](/images/qiita-47f13f65-1f2c-4c9c-a87b-b60cf6013980.webp)

ここで言いたいのは、譲渡可能になったことと、すぐ適正価格が分かることは別だという点です。
通常のVaultシェアならその時点の参照値を価格の出発点にできますが、保留中リクエストにはその共通の物差しがありません。
そのため二次流通を作る側は、待機時間、手数料、将来の受取量の不確実性まで織り込んだ価格モデルを自前で設計しなければなりません。

## 後方互換性

**ERC8161**は、**ERC7540**に対する後方互換な拡張です。
この拡張を実装しないVaultは、従来どおり非同期リクエストを扱えます。
つまり、既存の**ERC7540**実装を壊さずに、必要なVaultだけが譲渡機能を追加できます。

利用側は、譲渡メソッドの存在を決め打ちせず、**ERC165**の`supportsInterface`で確認してから使えば十分です。
標準に対応していないVaultへ誤って譲渡呼び出しを送る必要はありません。

## セキュリティ

#### オペレーター権限は保留中ポジションの持ち出し権限でもある

**ERC7540**と同様に、`controller`が認可したオペレーターは、その人の代わりにリクエストを操作できます。
**ERC8161**では、この権限の中に「保留中リクエストを別アドレスへ移す」能力も含まれます。

以下の図は、その権限関係を示しています。

![オペレーター権限は保留中ポジションの持ち出し権限でもあるの図](/images/qiita-602af6d6-5b37-41cc-ac70-2d7ea46f25d2.webp)

そのため利用者は、オペレーター承認を単なる補助操作権限としてではなく、**待機中の資産やシェア相当の権利を外へ動かせる権限**として理解する必要があります。
ウォレットやフロントエンドも、どこまで権限が及ぶかを明確に見せた方が安全です。
前の図が「第三者は呼べない」という入口の境界を示していたのに対して、この図は「承認してしまった相手はどこまでできるか」という深さを示しています。
つまり安全性の論点は、無関係な第三者を防げるかだけでなく、承認済み`operator`にどこまで任せるかにもあります。

#### 保留中ポジションは価格が固定されていない

保留中リクエストには、オンチェーンで確定した標準価格参照がありません。
とくに償還側では、`requestRedeem`した時点の`convertToAssets`相当値が、そのまま最終受取額になる保証はありません。

このため、譲渡可能になったからといって、そのポジションの価値が自明になるわけではありません。
マーケットプレイスや仲介プロトコルがこれを扱うなら、どの時点の期待価値を前提に売買するか、処理失敗や遅延をどう価格へ織り込むかを慎重に設計する必要があります。

## 最後に

今回は「**ERC7540**の非同期リクエストに対して、保留中のポジションを別アドレスへ移せるようにする**ERC8161**」についてまとめてきました！

**ERC8161**は大きな新機能を増やす仕様ではなく、**ERC7540**で長く残り得る`Pending`ポジションに移転手段を与える小さな拡張です。
ただしこの小ささが重要で、ウォレット移行、アカウント再編、二次流通、プロトコル合成といった実務上の困りごとに直接効きます。
一方で、譲渡できるのは`Pending`だけであり、価格参照も標準には含まれないため、実装側はオペレーター権限と価格設計を丁寧に扱う必要があります。

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

https://amzn.asia/d/gxvJ0Pw

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

https://zenn.dev/heku

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

https://twitter.com/cardene777

https://cardene.substack.com/
