# [ERC7246] 所有権を手放さずに権利だけ渡す仕組みを理解しよう！

> ERC20に「エンカンバー」という機能を追加し、アカウントが自分の保有残高の一部について、特定のアカウントに対して「排他的に移動させる権利」を与えられるようにする内容ですここでいう排他的な権利とは、他の誰でもなく指定した相手だけが、その範囲

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

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、所有権を失わずに別のアカウントへ移動権を保証できる仕組みを提案している**ERC7246**についてまとめていきます！

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

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

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

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

## 概要

**ERC20**に「**エンカンバー**」という機能を追加し、アカウントが自分の保有残高の一部について、特定のアカウントに対して「**排他的に移動させる権利**」を与えられるようにする内容です
ここでいう排他的な権利とは、他の誰でもなく指定した相手だけが、その範囲内のトークンを動かせる状態を指します。

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

[\[ERC20\] ERC20を1からわかりやすく解説](/articles/aeefe2d89dd5332c9f43)

一般的に使われている**ERC20**の承認（`allowance`）は、指定した量のトークンを移動できる許可を与える仕組みですが、実際に移動されるまでトークンが確保されている保証はありません。
残高が減ってしまえば、承認された側は移動できなくなります。

エンカンバーは、承認と同様に移動権限を与える点は同じですが、指定したトークンが「**必要なときに確実に存在する**」ことを保証します。
つまり、承認よりも強い仕組みであり、残高が確保されたまま移動権だけを付与できるようにします。

この仕組みは**ERC20**に限らず、NFTである**ERC721**などにもそのまま近い形で適用できる拡張性があります。

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

[\[ERC721\] ERC721を1からわかりやすく解説](/articles/0570a8d05bc7c9b9593f)

## 動機

**ERC20**に柔軟性を持たせ、トークンを手放さずにロックする必要がある場面に対応できるようにすることが目的です。
トークン保有者は、特定の条件を満たしたときに返却される前提で、トークンをスマートコントラクトへ送ることがよくあります。
ただし、実際にはスマートコントラクトがトークンを保管する必要がなく、「**必要な場合に確実に利用できる状態**」が確保できれば十分なケースがあります。

現在の承認では、その保証が不十分なため、スマートコントラクトにトークンを移すしか方法がありません。
エンカンバーがあれば、トークンを移動せずにロックでき、所有者が変わらないことが明確になります。
これにより、エアドロップなど、所有者に対して行われる恩恵が正しく届きます。

さらに、安全性の観点でも利点があります。
大量のトークンが保管されているアドレスから、一度の操作で全て流出してしまうリスクがありますが、エンカンバーされたトークンはアカウントごとに確保されており、一括で移動することが難しくなります。
そのため、攻撃に対してより強い抑止力が働きます。

## 仕様

```js:IERC7246.sol
/**
 * @dev Interface of the ERC-7246 standard.
 */
interface IERC7246{
    /**
     * @dev Emitted when `amount` tokens are encumbered from `owner` to `taker`.
     */
    event Encumber(address indexed owner, address indexed taker, uint amount);

    /**
     * @dev Emitted when the encumbrance of a `taker` to an `owner` is reduced by `amount`.
     */
    event Release(address indexed owner, address indexed taker, uint amount);

    /**
     * @dev Returns the total amount of tokens owned by `owner` that are currently encumbered.
     * MUST never exceed `balanceOf(owner)`
     *
     * Any function which would reduce balanceOf(owner) below encumberedBalanceOf(owner) MUST revert
     */
    function encumberedBalanceOf(address owner) external returns (uint);

    /**
     * @dev Returns the number of tokens that `owner` has encumbered to `taker`.
     *
     * This value increases when {encumber} or {encumberFrom} are called by the `owner` or by another permitted account.
     * This value decreases when {release} and {transferFrom} are called by `taker`.
     */
    function encumbrances(address owner, address taker) external returns (uint);

    /**
     * @dev Increases the amount of tokens that the caller has encumbered to `taker` by `amount`.
     * Grants to `taker` a guaranteed right to transfer `amount` from the caller's balance by using `transferFrom`.
     *
     * MUST revert if caller does not have `amount` tokens available
     * (e.g. if `balanceOf(caller) - encumbrances(caller) < amount`).
     *
     * Emits an {Encumber} event.
     */
    function encumber(address taker, uint amount) external;

    /**
     * @dev Increases the amount of tokens that `owner` has encumbered to `taker` by `amount`.
     * Grants to `taker` a guaranteed right to transfer `amount` from `owner` using transferFrom
     *
     * The function SHOULD revert unless the owner account has deliberately authorized the sender of the message via some mechanism.
     *
     * MUST revert if `owner` does not have `amount` tokens available
     * (e.g. if `balanceOf(owner) - encumbrances(owner) < amount`).
     *
     * Emits an {Encumber} event.
     */
    function encumberFrom(address owner, address taker, uint amount) external;

    /**
     * @dev Reduces amount of tokens encumbered from `owner` to caller by `amount`
     *
     * Emits a {Release} event.
     */
    function release(address owner, uint amount) external;


    /**
     * @dev Convenience function for reading the unencumbered balance of an address.
     * Trivially implemented as `balanceOf(owner) - encumberedBalanceOf(owner)`
     */
    function availableBalanceOf(address owner) public view returns (uint);
}
```

### イベント

#### `Encumber`

```javascript
event Encumber(address indexed owner, address indexed taker, uint amount);
```

`owner` から `taker` に対して指定量のトークンがエンカンバーされた時に発行されるイベント。
`owner` が保有している残高のうち、`taker` が排他的に移動できる量が新しく確保された時に通知されます。
エンカンバーされた量は保証付きであり、後から別の操作によって消費されるまでは保持されます。
`indexed` が付いているため、特定のアドレスを基準にログ検索しやすくなっています。

**パラメータ**

* `owner`
  * トークンを保有しているアドレス。
* `taker`
  * 移動権を与えられたアドレス。
* `amount`
  * エンカンバーされたトークン量。

#### `Release`

```javascript
event Release(address indexed owner, address indexed taker, uint amount);
```

`taker` に対して設定されたエンカンバー量が減少した時に発行されるイベント。
`taker` が保持していた移動権が部分的または全量解放された時に通知されます。
これは`taker` が `transferFrom` によってエンカンバー分を実際に移動した場合や、`release` 関数により明示的に解放された場合に起こります。
`indexed` によりアドレスベースで追跡が可能です。

**パラメータ**

* `owner`
  * トークンを保有しているアドレス。
* `taker`
  * エンカンバー解除の対象アドレス。
* `amount`
  * 減少したトークン量。

### 関数

#### `encumberedBalanceOf`

```javascript
function encumberedBalanceOf(address owner) external returns (uint);
```

`owner` が現在エンカンバーしている合計トークン量を返す関数。
`owner` に紐づく全ての `taker` へのエンカンバー量を合算して返します。
この値は `balanceOf(owner)` を超えてはならず、残高をこれより下回らせる操作は必ず失敗します。
エンカンバーされたトークンはロック状態となり、自由に移動できません。

**引数**

* `owner`
  * 対象となるアドレス。

**戻り値**

* `uint`
  * エンカンバー済みの合計トークン量。

#### `encumbrances`

```javascript
function encumbrances(address owner, address taker) external returns (uint);
```

`owner` から `taker` に設定されているエンカンバー量を返す関数。
特定の `taker` に割り当てられた保証付き移動権の量を取得します。
この値は `encumber` または `encumberFrom` によって増加し、`release` や `transferFrom` によって減少します。
単一の関係のみ確認したい場合に使用します。

**引数**

* `owner`
  * トークンを保有しているアドレス。
* `taker`
  * 移動権を与えられているアドレス。

**戻り値**

* `uint`
  * エンカンバーされているトークン量。

#### `encumber`

```javascript
function encumber(address taker, uint amount) external;
```

呼び出し元が `taker` に対してエンカンバー量を増加させる関数。
呼び出し元が持つ未使用の残高から `amount` をエンカンバーし、`taker` に保証付きの移動権を与えます。
残高が不足している場合や、既に他のエンカンバーによって利用可能残高が足りない場合は処理が失敗します。
成功時には `Encumber` イベントが発行されます。

**引数**

* `taker`
  * 移動権を付与されるアドレス。
* `amount`
  * 新しくエンカンバーするトークン量。

#### `encumberFrom`

```javascript
function encumberFrom(address owner, address taker, uint amount) external;
```

`owner` の代わりに `taker` へのエンカンバー量を増加させる関数。
メッセージ送信者が正当な手段で `owner` から許可されている場合に使用されます。
`owner` の未使用残高が `amount` 未満であれば処理は必ず失敗します。
成功すると `Encumber` イベントが発行されます。
承認の仕組みは規定されておらず、外部の認可ロジックに委ねられます。

**引数**

* `owner`
  * トークンを保有しているアドレス。
* `taker`
  * 移動権を付与されるアドレス。
* `amount`
  * 新しくエンカンバーするトークン量。

#### `release`

```javascript
function release(address owner, uint amount) external;
```

`owner` から呼び出し元に設定されているエンカンバー量を減少させる関数。
`taker` が不要になったエンカンバー分を解放する際に使用します。
利用可能な範囲内で `amount` を減らし、結果としてエンカンバー済み残高が更新されます。
成功すると `Release` イベントが発行されます。

**引数**

* `owner`
  * トークンを保有しているアドレス。
* `amount`
  * 解放するトークン量。

#### `availableBalanceOf`

```javascript
function availableBalanceOf(address owner) public view returns (uint);
```

`owner` の自由に使える残高を取得する関数。
`balanceOf(owner)` から `encumberedBalanceOf(owner)` を差し引いた値を返します。
エンカンバーによってロックされた量を除いた実際に移動可能な残高を確認できます。

**引数**

* `owner`
  * 対象となるアドレス。

**戻り値**

* `uint`
  * エンカンバーされていない利用可能な残高。

## 補足

#### ERC20とそろえた設計方針

**ERC7246**は、既存の**ERC20**の仕様と「**補い合い**」、「**似た形にする**」ことを意識して設計されています。
目的は、すでに**ERC20**に慣れている開発者が、新しいインターフェイスも自然に理解して導入できるようにすることです。

そのため、完全に別物の仕組みとしてではなく、あくまで**ERC20**の拡張として扱えるように意図されています。

#### 最小限の必須条件と設計のスタンス

**ERC7246**の仕様は、どのように**ERC20**と結びつけるかについて、あえて細かくルールを決めすぎないようにされています。
設計として「**最小限だけ縛る**」というスタンスです。

唯一はっきり求められている必須条件は、「**オーナーがエンカンバーされているトークンを `transfer` できてはならない**」という点です。
つまり、あるアカウントのトークンが他者に対してエンカンバーされている場合、その分についてはオーナー自身が自由に `transfer` などで動かしてはいけない、ということだけが強い制約として課されています。

そのうえで、**ERC20**との具体的な結合方法については、実装者が自由に設計できる余地を残しています。

#### `approve` と `encumberFrom` の関係

**ERC7246**では、「**サンプル実装**」の中で `encumberFrom` をどのように実現しているかについても触れています。
サンプル実装では、`encumberFrom` を動かすために**ERC20**の `approve` に依存していると説明されています。

つまり、誰かが他人のトークンをエンカンバーするとき、その元となる権限管理に `approve` を使っている、という前提になっています。

ただし **ERC7246**自体は、必ずしも `approve` に依存しなければならないとはしていません。
実装者が望むなら、`approveEncumber` のような、エンカンバー専用の承認関数を用意してもよいとされています。
この専用関数は、**ERC20**の `allowance` と同じような振る舞いをするイメージで書かれています。

ここで重要なのは、仕様として「`approve` を必須とはしていない」という点です。
`encumberFrom` の権限をどう設計するかは、実装側で選べるようになっています。

#### `transferFrom` と `encumbrances` / `allowance` の結び付け方

`transferFrom(src, dst, amount)` がエンカンバーとどう連携するかについても、サンプル実装の方針と、別の設計案の両方が説明されています。

分かりやすく比較すると、以下のようになります。

| 観点  | サンプル実装の方針  | 代替案             |
| ---------- | --------------- | -------------- |
| `transferFrom(src, dst, amount)` の順序 | まず `encumbrances(src, amount)` を減らし、そのあとに `allowance(src, msg.sender)` を消費する。 | `encumbrances` を消費するのと同時に `allowance` も消費する実装にする。  |
| `approve` に必要なチェック  | 特に追加の説明なし。  | `approve` が、承認後の残高がエンカンバーで必要とされる量を下回らないことを確認する必要があるとされている。 |
| `encumber` 呼び出しの前提  | 特に前提条件には触れていない。   | `encumber` を呼ぶ前に、先に `approve` を行っておくことが前提になると説明されている。   |

代替案の説明では、`transferFrom` を「**エンカンバー分**」と「**`allowance` 分**」の両方から同時に消費するように実装する可能性が述べられています。

その場合、以下のようになります。

* `approve` は、承認した後の残高が、すでに存在しているエンカンバー分の要求量を下回らないことをチェックしなければならない。
* そして `encumber` を呼ぶ前に、あらかじめ `approve` によって承認を作成しておくことが前提になる。

#### ERC721への拡張可能性

**ERC7246**で定義されている **Encumber**インターフェイスは、もともとの主な用途として**ERC20**を想定していますが、説明では**ERC721**にも「**引き伸ばして**」適用できると述べられています。

**ERC20**では `amount` パラメータがトークン量を表す `uint` 型ですが、**ERC721**では各トークンが `tokenId` という `uint` で識別されます。
この共通点を利用して、「**`amount` の代わりに `tokenId` を使う**」という形で**ERC721**にもこのインターフェイスをあてはめることが可能とされています。

ただし、インターフェイスとしては、もっとも典型的な利用ケースである**ERC20**を前提とした分かりやすい形を優先して選んでいる、と明記されています。
つまり、他のフォーマットでも理論上使えるが、見た目や意味が一番自然になる**ERC20**向けの表現を採用している、という整理です。

## 互換性

**ERC7246**は、既存の**ERC20**標準との後方互換性があるとされています。
つまり、**ERC20**の基本仕様を壊さず、その上に**Encumber**の機能を追加する位置づけです。

そのうえで、実装において明確に求められていることは、以下の一点です。

* あるアカウントのトークンが別のアカウントに対してエンカンバーされている場合、そのエンカンバーされているトークンは `transfer` できないようにしなければならない。

これにより、エンカンバーによって「**保証されたトークン**」が、意図せず動かされてしまうことを防ぐ必要がある、と説明されています。

## 参考実装

```js:EncumberableERC20.sol
// An erc-20 token that implements the encumber interface by blocking transfers.

pragma solidity ^0.8.0;
import {ERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import { IERC7246 } from "./IERC7246.sol";

contract EncumberableERC20 is ERC20, IERC7246 {
    // Owner -> Taker -> Amount that can be taken
    mapping (address => mapping (address => uint)) public encumbrances;

    // The encumbered balance of the token owner. encumberedBalance must not exceed balanceOf for a user
    // Note this means rebasing tokens pose a risk of diminishing and violating this prototocol
    mapping (address => uint) public encumberedBalanceOf;

    address public minter;

    constructor(string memory name, string memory symbol) ERC20(name, symbol) {
        minter = msg.sender;
    }

    function mint(address recipient, uint amount) public {
        require(msg.sender == minter, "only minter");
        _mint(recipient, amount);
    }

    function encumber(address taker, uint amount) external {
        _encumber(msg.sender, taker, amount);
    }

    function encumberFrom(address owner, address taker, uint amount) external {
        require(allowance(owner, msg.sender) >= amount);
       _encumber(owner, taker, amount);
    }

    function release(address owner, uint amount) external {
        _release(owner, msg.sender, amount);
    }

    // If bringing balance and encumbrances closer to equal, must check
    function availableBalanceOf(address a) public view returns (uint) {
        return (balanceOf(a) - encumberedBalanceOf[a]);
    }

    function _encumber(address owner, address taker, uint amount) private {
        require(availableBalanceOf(owner) >= amount, "insufficient balance");
        encumbrances[owner][taker] += amount;
        encumberedBalanceOf[owner] += amount;
        emit Encumber(owner, taker, amount);
    }

    function _release(address owner, address taker, uint amount) private {
        if (encumbrances[owner][taker] < amount) {
          amount = encumbrances[owner][taker];
        }
        encumbrances[owner][taker] -= amount;
        encumberedBalanceOf[owner] -= amount;
        emit Release(owner, taker, amount);
    }

    function transfer(address dst, uint amount) public override returns (bool) {
        // check but dont spend encumbrance
        require(availableBalanceOf(msg.sender) >= amount, "insufficient balance");
        _transfer(msg.sender, dst, amount);
        return true;
    }

    function transferFrom(address src, address dst, uint amount) public override returns (bool) {
        uint encumberedToTaker = encumbrances[src][msg.sender];
        bool exceedsEncumbrance = amount > encumberedToTaker;
        if (exceedsEncumbrance)  {
            uint excessAmount = amount - encumberedToTaker;

            // check that enough enencumbered tokens exist to spend from allowance
           require(availableBalanceOf(src) >= excessAmount, "insufficient balance");

           // Exceeds Encumbrance , so spend all of it
            _spendEncumbrance(src, msg.sender, encumberedToTaker);

            _spendAllowance(src, dst, excessAmount);
        } else {
            _spendEncumbrance(src, msg.sender, amount);
        }

        _transfer(src, dst, amount);
        return true;
    }

    function _spendEncumbrance(address owner, address taker, uint256 amount) internal virtual {
        uint256 currentEncumbrance = encumbrances[owner][taker];
        require(currentEncumbrance >= amount, "insufficient encumbrance");
        uint newEncumbrance = currentEncumbrance - amount;
        encumbrances[owner][taker] = newEncumbrance;
        encumberedBalanceOf[owner] -= amount;
    }
}
```

## 引用

Coburn Berry (@coburncoburn), Mykel Pereira (@mykelp), Scott Silver (@scott-silver), "ERC-7246: Encumber - Splitting Ownership & Guarantees [DRAFT]," Ethereum Improvement Proposals, no. 7246, June 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7246.

## 最後に

今回は「所有権を失わずに別のアカウントへ移動権を保証できる仕組みを提案している**ERC7246**」についてまとめてきました！
いかがだったでしょうか？

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

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

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

https://zenn.dev/heku

https://cardene.substack.com/

https://mirror.xyz/0xcE77b9fCd390847627c84359fC1Bc02fC78f0e58

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