[ERC4886] 資産保有アドレスとは別に特典などを受け取るアドレスを管理する仕組みを理解しよう！

# はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、資産の保有アドレスとは別に代理人となるアドレスを管理し、エアドロなどを受け取るアドレスを受け取れるようにする仕組みを提案している**ERC4886**についてまとめていきます！

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

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

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

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

# 概要

**ERC4886**は、ユーザーが別のウォレットアドレスを代理人（Proxy）として指定できるプロトコルを提案しています。
また、新しい資産の受け取り先として別のアドレス（デリバリーアドレス）も設定できます。
スマートコントラクトやDappsは、このプロキシアドレスを通じて本来の保有者アドレス（ノミネーター）の資産情報を参照することができます。

この仕組みにより、ユーザーは資産を安全なコールドウォレットに保管したまま、ホットウォレットを通じてスマートコントラクトとやり取りできます。
資産があるアドレス自体は一切コントラクトと接触しないため、悪意あるコントラクトによって資産が盗まれるリスクを大きく低減できます。
さらに、新しい資産の送付先を自由に指定できることで、例えばエアドロップなどをコールドウォレットで直接受け取るといった柔軟な管理が可能になります。

# 動機

Ethereumの多くの機能を活用するには、ユーザーは自分の資産の保有を証明する必要があります。
例えば以下のような場面があります。

- Discordコミュニティに参加するために、トークンやNFTを保有しているウォレットで署名を行う
- NFTのミントやエアドロップへの参加のために、特定の資産を保有しているアドレスでコントラクトとやり取りする
- DAOでの投票の時に、対象資産を保有しているアドレスから署名を行う

これらの行動にはすべて、資産を保有するアドレスからの署名やトランザクションが必要です。
しかし、これはコールドウォレットによる安全な資産管理と両立しません。
ホットウォレットで署名やコントラクトとのやり取りを行う必要があることで、資産を攻撃者に狙われるリスクが高まります。

例えば、新しいプロジェクトが特定のNFT保有者に向けてフリーミントを提供する場合、そのNFT保有アドレスでミントを行う必要があります。
攻撃者は、そのアセットを持っているユーザーが必ずコントラクトとやり取りすることを知っているため、悪意ある仕組みを仕込んだコントラクトを提供して資産を奪うことができます。

このような状況が続けば、Ethereum全体に対するユーザーの信頼も損なわれます。
セキュリティのためにNFTを「`vault`」に保管することでDiscordにアクセスできなくなったり、エアドロップの対象外になるなど利便性と安全性のジレンマに陥ります。

しかし本来Ethereumは、信頼不要（トラストレス）な証明を可能にするプラットフォームです。
資産を送付する場合を除き、ユーザーは資産の保有をオンチェーンで証明できるべきです。
また、署名やトランザクションを行ったウォレットではなく、指定したウォレットへ資産を受け取る機能も求められます。

**ERC4886**の目的は、このセキュリティ上の課題を解決し、Ethereumの機能をより安全に広く活用できるようにすることです。
ユーザーは、長期保有用のコールドウォレットを用意し、最初だけプロキシウォレットの登録を行えば、その後はすべてのやり取りをホットウォレットで安全に実行でき、コールドウォレットで資産を受け取ることが可能になります。

# 仕様

## 用語

- **Delivery address（デリバリーアドレス）**
    - 新しい資産（例: NFT）が送られるアドレス。
    プロキシアドレスが代理でアセットを受け取った場合、実際にはこのアドレスに配送される。
- **Nomination（ノミネーション）**
    - ノミネーターがプロキシアドレスを指定する行為。
    - プロキシが受諾して初めて有効となる。
- **Nominator address（ノミネーター）**
    - 代理人（プロキシ）を指定する元のアドレス。
- **Proxy address（プロキシ）**
    - ノミネーターの代理としてブロックチェーン上で活動するアドレス。
- **Proxy Record（プロキシレコード）**
    - ノミネーター・プロキシ・デリバリーアドレスを含む有効な代理関係の記録。
- **Register（レジスター）**
    - 全てのノミネーションとプロキシレコードを保持するメインのEPSスマートコントラクト。

## プロキシの登録手順

プロキシ関係は以下の手順で確立されます。

1. ノミネーターがプロキシアドレスを指定してノミネーションを作成する。
2. プロキシアドレスがそのノミネーションを承諾すると、プロキシレコードが作成される。
3. 承諾時にプロキシがデリバリーアドレスを設定し、それ以降はそのプロキシが更新を管理する。
4. ノミネーターとプロキシのどちらでも、いつでもこの関係（ノミネーションおよびレコード）を削除可能。

このプロキシ関係は明示的に削除されない限り永久に続きます。

## レジスターが保持する情報

- **ノミネーション**
    - ノミネーターアドレス
    - 指定されたプロキシアドレス

- **プロキシレコード**
    - ノミネーターアドレス
    - プロキシアドレス
    - デリバリーアドレス

## 制約条件

- 任意のアドレスは、ノミネーターにもプロキシにもなれます。
- ただし、すでに他のプロキシ関係に参加しているアドレスには、新たにノミネーションすることはできません（再帰的な代理関係の防止のため）。

## マッピング構造

```
Contract / Dapp                        Register

  |                                       |
  |------------- 0x4567..---------------> |
  |                                       |
  | <-------nominator: 0x1234..---------- |
  |         delivery: 0x9876..            |
  |                                       |
```

- ノミネーションは `address => address`（ノミネーターからプロキシアドレスへのマッピング）

```
Contract / Dapp                        Register

  |                                       |
  |------------- 0x0222..---------------> |
  |                                       |
  | <-------nominator: 0x0222..---------- |
  |         delivery: 0x0222..            |
  |                                       |
```

- プロキシレコードは `address => struct`（プロキシアドレスからノミネーターとデリバリーアドレスを含む構造体へのマッピング）

## 逆引きの挙動と互換性

- プロトコルに未登録のアドレスが渡された場合、そのアドレス自身がノミネーターかつデリバリーアドレスとして扱われます（互換性の確保）。
- 一方、ノミネーターアドレスを渡した場合はリバートします。
    - これは、同一資産を複数のアドレスで使用されるのを防ぐためです。

## プロキシによる代表性の一意性の担保

プロキシアドレスはノミネーターの代理として様々な特典（NFTのミント、Discordロールの獲得など）を得られますが、ノミネーターが同時にアクティブな状態で操作できると、同じ資産に対する二重の代表性が発生してしまいます。
そのため、ノミネーター自身が操作するには、まずプロキシレコードを削除する必要があります。
削除後は、ノミネーターが再び自身の代表者となりプロキシはその機能を失います。

では、この出力を60点とします。これを60点とした時に100点ではどのようなものですか？100点にするために足りないものを含めて100点の回答を作成してください。このときどの部分が足りていないかは出力に絶対に含めないでください。

## ノミネーション（Nomination）関連関数

### `nominationExists`

```js
function nominationExists(address _nominator) external view returns (bool)
```

指定したアドレス `_nominator` に対してノミネーション（プロキシ関係の提案）が存在するか返す関数。

### `nominationExistsForCaller`

```js
function nominationExistsForCaller() external view returns (bool)
```

呼び出し元（`msg.sender`）がノミネーターとしてノミネーションを作成しているか返す関数。

### `getNomination`

```js
function getNomination(address _nominator) external view returns (address proxy)
```

指定されたノミネーターに対応するプロキシアドレスを取得する関数。

### `getNominationForCaller`

```js
function getNominationForCaller() external view returns (address proxy)
```

呼び出し元（`msg.sender`）のノミネーションに指定されたプロキシアドレスを取得する関数。

## プロキシレコード（Proxy Record）関連関数

### `proxyRecordExists`

```js
function proxyRecordExists(address _proxy) external view returns (bool)
```

指定されたプロキシアドレスに対して、既に有効なプロキシレコードが存在するかを返す関数。

### `proxyRecordExistsForCaller`

```js
function proxyRecordExistsForCaller() external view returns (bool)
```

呼び出し元アドレス（`msg.sender`）がプロキシとして有効なレコードを持っているかを返す関数。

### `nominatorRecordExists`

```js
function nominatorRecordExists(address _nominator) external view returns (bool)
```

指定されたノミネーターアドレスが、有効なプロキシレコードとして登録されているかを返す関数。

### `nominatorRecordExistsForCaller`

```js
function nominatorRecordExistsForCaller() external view returns (bool)
```

呼び出し元（`msg.sender`）がノミネーターとして有効なレコードを持っているかを返す関数。

### `getProxyRecord`

```js
function getProxyRecord(address _proxy) external view returns (address nominator, address proxy, address delivery)
```

指定されたプロキシアドレスに対応するプロキシレコード（ノミネーター、プロキシ、デリバリー）を返す関数。

### `getProxyRecordForCaller`

```js
function getProxyRecordForCaller() external view returns (address nominator, address proxy, address delivery)
```

呼び出し元アドレスをプロキシとするレコード情報を返す関数。

### `getNominatorRecord`

```js
function getNominatorRecord(address _nominator) external view returns (address nominator, address proxy, address delivery)
```

指定されたノミネーターアドレスに対応するプロキシレコード（ノミネーター、プロキシ、デリバリー）を返す関数。

### `getNominatorRecordForCaller`

```js
function getNominatorRecordForCaller() external view returns (address nominator, address proxy, address delivery)
```

呼び出し元（`msg.sender`）のノミネーターとしてのプロキシレコード情報を返す関数。

## アクティブ状態の確認

### `addressIsActive`

```js
function addressIsActive(address _receivedAddress) external view returns (bool)
```

指定されたアドレスが、現在有効なプロキシレコードにおいてノミネーターまたはプロキシとして関与しているか返す関数。

### `addressIsActiveForCaller`

```js
function addressIsActiveForCaller() external view returns (bool)
```

呼び出し元アドレスが、現在有効なプロキシレコードに関与しているかを返す関数。

## アドレス情報の取得

### `getAddresses`

```js
function getAddresses(address _receivedAddress) external view returns (address nominator, address delivery, bool isProxied)
```

指定されたアドレスがプロキシである場合、対応するノミネーターアドレスとデリバリーアドレス、プロキシであるかを示すブール値を返す関数。
プロキシでなければ全て `address(0)` と `false` を返します。

### `getAddressesForCaller`

```js
function getAddressesForCaller() external view returns (address nominator, address delivery, bool isProxied)
```

呼び出し元アドレスがプロキシであるかを確認して情報を返す関数。

## アドレスのロール確認

### `getRole`

```js
function getRole(address _roleAddress) external view returns (string memory currentRole)
```

指定アドレスのEPSにおけるロールを文字列で返す関数。
値は以下のいずれかになります。

- `None`
    - ロールがない。
- `Nominator - Pending`
    - ノミネーションを行っているが未受諾。
- `Nominator - Active`
    - 有効なプロキシレコードのノミネーター。
- `Proxy - Active`
    - 有効なプロキシレコードのプロキシ。

### `getRoleForCaller`

```js
function getRoleForCaller() external view returns (string memory currentRole)
```

呼び出し元アドレスのEPSにおけるロールを返す関数。

## プロキシ関係の操作関数

### `makeNomination`

```js
function makeNomination(address _proxy, uint256 _provider) external payable
```

`msg.sender` が指定した `_proxy` アドレスを代理人としてノミネーションを作成する関数。  
`_provider` はEPSプロバイダーIDで、未使用の場合は `0` を指定します。

### `acceptNomination`

```js
function acceptNomination(address _nominator, address _delivery, uint256 _provider) external
```

`msg.sender`（プロキシ）が指定された `_nominator` からのノミネーションを受諾し、 `_delivery` をデリバリーアドレスとして登録する関数。
`_provider` はEPSプロバイダーIDです。

### `updateDeliveryAddress`

```js
function updateDeliveryAddress(address _delivery, uint256 _provider) external
```

`msg.sender` がプロキシとして登録されている場合に、現在のプロキシレコードのデリバリーアドレスを更新する関数。

## レコード削除関数

### `deleteRecordByNominator`

```js
function deleteRecordByNominator(uint256 _provider) external
```

`msg.sender` がノミネーターである場合、そのノミネーションとプロキシレコードを削除する関数。
まだ受諾されていないノミネーションのみの場合も削除可能です。

### `deleteRecordByProxy`

```js
function deleteRecordByProxy(uint256 _provider) external
```

`msg.sender` がプロキシである場合、そのプロキシレコードおよびノミネーションを削除する関数。

# 補足

**ERC4886**の主な目的は、Ethereum上の全てのアセット（既存・将来問わず）に対して、「**保有アドレス**」とは別に「**実質的所有者**」としてプロキシアドレスを設定できる仕組みを提供することです。

従来の資産モデルでは、所有と利用は同じアドレスに紐づいており、コールドウォレットに保管した資産を活用するにはセキュリティリスクを冒してアドレスを接続する必要がありました。

この問題に対して**ERC4886**では、スマートコントラクトである「**レジスター**」が代理関係（ノミネーションと承認）を管理することによって、トークン仕様そのものを変更することなく信頼不要な資産の所有証明を記録します。
この署名は、ノミネーターとプロキシ双方が承認したものであり、資産の実質的な所有者としての関係を示す信頼できるデータとなります。

# セキュリティ

**ERC4886**は、ユーザーの資産をより安全に保護しコールドウォレットでの保管と実用性の両立を可能にしています。

特に重要なのは、プロキシレコードが一方的には作成されないという点です。
すなわち、ノミネーション（代理指定）はノミネーターから提案されますが、それが有効なレコードとして登録されるのは、プロキシ側がその関係を受諾した場合に限られます。
この仕組みにより、両者の署名による明示的な同意が存在することが保証され、悪意ある操作や成りすましが防止されます。

セキュリティ上の懸念点として想定されるのは、ユーザーが資産の受け取り先（デリバリーアドレス）を誤って指定するケースです。
しかし、これは現行のEthereumネットワークでも起こりうるリスクと本質的に同じであり、新たな攻撃経路が追加されるわけではありません。
つまり、仕様上は新たなセキュリティリスクを導入していないと評価できます。

**ERC4886**は、署名ベースの関係構築と明確な権限設計により、Ethereumエコシステム全体の安全性を高めることを目的としています。

# 引用

Omnus Sunmo (@omnus), "ERC-4886: Proxy Ownership Register [DRAFT]," Ethereum Improvement Proposals, no. 4886, September 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4886.

# 最後に

今回は「資産の保有アドレスとは別に代理人となるアドレスを管理し、エアドロなどを受け取るアドレスを受け取れるようにする仕組みを提案している**ERC4886**」についてまとめてきました！
いかがだったでしょうか？

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