# [ERC6809] 3つのウォレットでNFTを安全に管理するNFKBTの仕組みを理解しよう！

> Non-FungibleKeyBoundToken（NFKBT）は、KeyBoundToken（KBT＝鍵に紐付くトークンという総称）の一種で、ERC721との互換性を保ちつつ「資産そのものに任意で付与できるセキュリティ」を備えるためのイン

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

## はじめに

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

https://amzn.asia/d/gxvJ0Pw

今回は、トークンコントラクト自体に保護機能を組み込み、2つの補助ウォレットによって「**Transfer・Approce**」や「**緊急時の退避・再設定**」を制御できる**NFKBT**という仕組みを提案している**ERC6809**についてまとめていきます！

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

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

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

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

## 概要

Non-FungibleKeyBoundToken（NFKBT）は、KeyBoundToken（KBT＝鍵に紐付くトークンという総称）の一種で、**ERC721**との互換性を保ちつつ「**資産そのものに任意で付与できるセキュリティ**」を備えるためのインターフェース標準です。
スマートコントラクト内で用いるAPIを定義し、とくに`addBindings`の基本動作を明確化します。
`addBindings`は2つのKeyWallet（KeyWallet1／KeyWallet2）をNFKBTに紐付け、これらのKeyWalletがSafeTransfer（NFTを安全にTransferできるようにする手続き）の可否を制御します。
この過程で、NFKBTはユーザー本人またはオンチェーンの第三者（スマートコントラクトなど）が利用できるように、安全な`approve`状態を設定します。

NFKBTのコアは、`allowTransfer`と`allowApproval`という「**allow（許可）**」機能を持つ2つの関数です。
これらはKeyWalletが呼び出し、HoldingWallet（資産を実際に保持するウォレット）が従来の**ERC721**の`transferFrom`や`approve`を呼べるように事前許可を与えます。
つまり、資産の移動や承認を直接行うのではなく、「**この条件下でのみ送付や承認を許可する**」という制御をKeyWallet側で設定できるようにするのが、このallow機能の役割です。

想定されるユースケースは、個人が自分のNFTに追加のセキュリティを持たせたい場合に加え、第三者のウォレット・ブローカー・銀行・保険・ギャラリー等への委託（consignment：一時預けや寄託）です。
NFKBTは、自己保管（self-custodial）という前提を維持したまま、資産そのものに保護機能を組み込むことで盗難や詐欺に対する耐性を高めます。

#### 用語

| 用語 | 説明 |
| --- | ---- |
| KeyWallet | 資産の使い方（TransferやApprovalの可否）を決める権限を持つウォレット。本文では2つ想定（KeyWallet1/KeyWallet2）。 |
| HoldingWallet | NFT資産そのものを保有するウォレット。KeyWalletからの許可があれば`transferFrom`や`approve`を実行できる。     |
| SafeTransfer | 安全な送付を指す一般語。本文ではKeyWalletが主導する安全な送付プロセスのことを指す。                            |
| DefaultBehaviors | `addBindings`を使わないときの従来の**ERC721**としての既定動作。                                    |
| DefaultValues | 機能に与えられる既定値。ユーザーやdAppがカスタム値または既定値を渡せる。 |

#### 主な関数とパラメータ

| 関数 | 目的 | 主なパラメータ |
| --- | --- | ----------- |
| `addBindings`  | NFKBTにKeyWallet1/KeyWallet2を紐づけ、資産側のセキュリティ機能を有効化する。 |  |
| `allowTransfer`  | 指定条件のもとでTransferを許可する「**事前許可**」を付与する。KeyWalletが呼ぶ。 | `_tokenId`（対象トークンID）、`_time`（許可の時間条件）、`_address`（許可相手アドレス）、`_anyToken`（任意トークン許可の有無） |
| `allowApproval`  | Approval（承認）を許可する「**事前許可**」を付与する。KeyWalletが呼ぶ。 | `_time`（許可の時間条件）、`_numberOfTransfers`（許可される送付回数） |
| `safeFallback` | HoldingWalletに不正アクセスの疑いがある場合、KeyWalletから操作を行うことで、資産をもう一方のKeyWalletに移動させて安全を確保できます。   |  |
| `resetBindings` | どちらかのKeyWalletに不正アクセスの疑いがあるとき、KeyWalletからセキュリティ設定をリセットして再設定可能にする。 | 呼び出し元：KeyWallet1またはKeyWallet2 |
| `transferFrom`/`approve` | 従来の**ERC721**の機能。KeyWalletによる`allow...`で事前許可がある場合、HoldingWalletがこれらを実行できる。 | （**ERC721**に準拠） |

## 動機

ブロックチェーンの利用者は年齢や背景が多様で学習速度もまちまちです。
自己保管（self-custodial）資産は「**自分で完全に管理できる**」という大きな利点がある一方、誤操作・鍵の流出・詐欺被害が起きたときに取り戻せないという重大な欠点も抱えます。
米国連邦取引委員会（FTC）には2021年1月〜2022年3月のあいだに46,000件超の暗号資産詐欺報告が寄せられ、消費者損失は10億ドル超に達しました。
こうした被害は規制強化を招きがちですが、強い規制はこの領域の中核的価値観と相容れない面もあります。
中央集権・分散型の双方でセキュリティ向上の試みはありましたが、両者の長所を取り込み短所を避ける決定打はありませんでした。

そこで観点を「**ウォレットをどう守るか？**」から「**資産そのものをどう守るか？**」に切り替えます。
ウォレットを作るのは無料ですが、価値があるのは資産側です。
この発想からKBTが生まれ、ユーザーが望む範囲で完全に任意で資産へセキュリティを持たせられる設計を目指しました。
機能を正しく有効化しておけば、たとえシードフレーズや秘密鍵が流出しても資産に設定した保護機能がそのまま働き続けます。

NFKBT（Non-FungibleKeyBoundToken）は、KBT（KeyBoundToken：鍵に紐づくトークン）の一種で、**ERC721**と互換性を保ったまま「**資産そのものに任意で追加できる保護機能**」を提供するインターフェースです。
**ERC6809**は、スマートコントラクトで用いるAPIを規格として提示します。
`addBindings`は、2つのKeyWallet（KeyWallet1／KeyWallet2）をNFKBTに登録し、これらのKeyWalletがSafeTransfer（NFTを安全に送付できるようにする手続き）を許可するかどうかを管理します。
あわせて、ユーザー本人やオンチェーンの第三者（スマートコントラクトなど）が利用できるように、NFKBTの`approve`（承認）を安全に設定します。

NFKBTでは、`allowTransfer`と`allowApproval`という2つの「**許可（allow）**」関数を用います。
これらはKeyWalletから呼び出され、HoldingWallet（資産を保持するウォレット）が、**ERC721**の`transferFrom`や`approve`を実行できるようにする事前許可を与えます。
資産の保管はHoldingWalletが担い、送付や承認の条件設定はKeyWalletが担うという役割分担になります。
なお、`addBindings`を使わない場合は、通常の**ERC721**と同じ挙動で動作します。

想定されるユースケースは、個人が自分のNFTに追加の保護を付けたいケースに限らず、第三者ウォレット・ブローカー・銀行・保険・ギャラリー等への委託（consignment）も含みます。

| 利点 | 説明 |
| ---- | --- |
| 完全に分散型 | 保護機能を有効化しても第三者が資産へアクセスしません。自己保管の考えに合致します。 |
| スケーラビリティ  | 地域制限やアカウント作成が不要です。専用ハードウェアの配布や在庫などの制約も避けられます。 |
| 任意に利用可能  | 保護機能はオン・オフや細かな調整が可能で、ユーザーの希望に合わせて使えます。 |
| 既定動作の維持   | `addBindings`を使わなければ従来の**ERC721**と同じです。有効化しても既定値やカスタム値で挙動を調整できます。  |
| 保護の強化 | `allowTransfer`（`_tokenId`、`_time`、`_address`、`_anyToken`）と`allowApproval`（`_time`、`_numberOfTransfers`）で、KeyWallet経由の事前許可が必須になります。さらに`safeFallback`と`resetBindings`を備え、適切に使えば、従来の**ERC721**で発生しやすいリスクやトラブルを防止できます。 |
| 安全な退避と再設定 | HoldingWalletに不正アクセスの疑いがあるときは、KeyWalletの操作で資産をもう一方のKeyWalletに移し安全を確保できます（`safeFallback`）。KeyWallet側に疑いがあるときは、設定を一度リセットして新しいKeyWalletを紐づけ直せます（`resetBindings`）。単一の故障点に依存しない設計です。  |
| 匿名性の維持    | 個人情報の提出や配送情報を前提とせず、オンチェーンで仮名のまま利用できます。 |
| 低コスト  | かかる費用はオンチェーン手数料（GWEI）に連動します。単独の保護手段として現実的なコストです。 |
| 環境負荷が小さい  | 中央サーバや物理デバイスの製造・配送が不要で、EthereumのPoS移行とも整合します。 |

## 仕様

**ERC6809**は、**Non-Fungible Key Bound Token（NFKBT）**の実装を前提とした**IKBT721**インターフェースを定義しています。
**ERC721**と互換性を持ちつつ、「**KeyWallet**」と「**HoldingWallet**」による二重保護構造を導入することで、資産（トークン）自体に安全機能を付与します。

### インターフェース

```javascript
interface IKBT721 {
    event AccountSecured(address indexed _account, uint256 _noOfTokens);
    event AccountResetBinding(address indexed _account);
    event SafeFallbackActivated(address indexed _account);
    event AccountEnabledTransfer(
        address _account,
        uint256 _tokenId,
        uint256 _time,
        address _to,
        bool _anyToken
    );
    event AccountEnabledApproval(
        address _account,
        uint256 _time,
        uint256 _numberOfTransfers
    );
    event Ingress(address _account, uint256 _tokenId);
    event Egress(address _account, uint256 _tokenId);

    struct AccountHolderBindings {
        address firstWallet;
        address secondWallet;
    }

    struct FirstAccountBindings {
        address accountHolderWallet;
        address secondWallet;
    }

    struct SecondAccountBindings {
        address accountHolderWallet;
        address firstWallet;
    }

    struct TransferConditions {
        uint256 tokenId;
        uint256 time;
        address to;
        bool anyToken;
    }

    struct ApprovalConditions {
        uint256 time;
        uint256 numberOfTransfers;
    }

    function addBindings(
        address _keyWallet1,
        address _keyWallet2
    ) external returns (bool);

    function getBindings(
        address _account
    ) external view returns (AccountHolderBindings memory);

    function resetBindings() external returns (bool);

    function safeFallback() external returns (bool);

    function allowTransfer(
        uint256 _tokenId,
        uint256 _time,
        address _to,
        bool _allTokens
    ) external returns (bool);

    function getTransferableFunds(
        address _account
    ) external view returns (TransferConditions memory);

    function allowApproval(
        uint256 _time,
        uint256 _numberOfTransfers
    ) external returns (bool);

    function getApprovalConditions(
        address account
    ) external view returns (ApprovalConditions memory);

    function getNumberOfTransfersAllowed(
        address _account,
        address _spender
    ) external view returns (uint256);

    function isSecureWallet(address _account) external returns (bool);

    function isSecureToken(uint256 _tokenId) external returns (bool);
}
```

#### イベント

##### `AccountSecured`

```javascript
event AccountSecured(address _account, uint256 _amount);
```

`addBindings`関数を呼び出して、ウォレットをセキュリティ保護したときに発行されるイベント。

ユーザーが`addBindings`を呼ぶことで、2つのKeyWalletが登録されます。
このイベントは、保護対象のアカウント（`_account`）がセキュア化されたことを示します。
第二引数は、セキュリティ保護時点のトークン残高を表します。

**パラメータ**

* `_account`
  * セキュア化されたアカウントアドレス。
* `_amount`
  * セキュア化時点のトークン保有量。

##### `AccountResetBinding`

```javascript
event AccountResetBinding(address _account);
```

`resetBindings`関数の呼び出し時に発行されるイベント。

KeyWalletの1つが危険にさらされた場合、この関数を使って登録をリセットします。
このイベントは、アカウントに紐づくKeyWallet設定がリセットされたことを通知します。

**パラメータ**

* `_account`
  * リセット対象のアカウントアドレス。

##### `SafeFallbackActivated`

```javascript
event SafeFallbackActivated(address _account);
```

`safeFallback`関数の呼び出し時に発行されるイベント。

HoldingWalletが危険にさらされた場合、KeyWalletの一方からすべての資産をもう一方に移動させます。
このイベントは、資産の移動が開始されたことを示します。

**パラメータ**

* `_account`
  * 処理対象のアカウントアドレス。

##### `AccountEnabledTransfer`

```javascript
event AccountEnabledTransfer(
    address _account,
    uint256 _tokenId,
    uint256 _time,
    address _to,
    bool _anyToken
);
```

`allowTransfer`関数の呼び出し時に発行されるイベント。

KeyWalletが特定条件下でTransferを許可した時に発行されます。
特定のトークンID、期間、宛先、または任意トークンの送付を許可したことを示します。

**パラメータ**

* `_account`
  * 許可を設定したアカウント。
* `_tokenId`
  * 対象トークンID（0の場合は制限なし）。
* `_time`
  * 許可の有効期間（ブロック時間）。
* `_to`
  * Transferを許可するアドレス。
* `_anyToken`
  * 任意のトークンを送付可能かどうかのフラグ。

##### `AccountEnabledApproval`

```javascript
event AccountEnabledApproval(
    address _account,
    uint256 _time,
    uint256 _numberOfTransfers
);
```

`allowApproval`関数の呼び出し時に発行されるイベント。

KeyWalletが、特定期間およびTransfer回数に基づく承認を許可した時に発行されます。

**パラメータ**

* `_account`
  * 承認許可を設定したアカウント。
* `_time`
  * 承認の有効期限（ブロック時間）。
* `_numberOfTransfers`
  * 許可されるTransfer回数（0は無制限）。

##### `Ingress`

```javascript
event Ingress(address _account, uint256 _tokenId);
```

アカウントが新たにトークンを保有したときに発行されるイベント。
指定アカウントがトークンを初めて受け取ったことを通知します。

**パラメータ**

* `_account`
  * 新規保有者のアカウント。
* `_tokenId`
  * 受け取ったトークンID。

##### `Egress`

```javascript
event Egress(address _account, uint256 _tokenId);
```

アカウントがすべてのトークンを手放した時に発行されるイベント。

指定アカウントがすべての保有トークンをTransferして保有者でなくなったことを通知します。

**パラメータ**

* `_account`
  * 保有を終了したアカウント。
* `_tokenId`
  * 最後にTransferしたトークンID。

#### 構造体

##### `AccountHolderBindings`

```javascript
struct AccountHolderBindings {
    address firstWallet;
    address secondWallet;
}
```

アカウントに紐づく2つのKeyWalletを管理する構造体。

HoldingWalletに対して、2つのKeyWalletを安全設定として保持します。

**パラメータ**

* `firstWallet`
  * 登録された1つ目のKeyWallet。
* `secondWallet`
  * 登録された2つ目のKeyWallet。

##### `TransferConditions`

```javascript
struct TransferConditions {
    uint256 tokenId;
    uint256 time;
    address to;
    bool anyToken;
}
```

Transfer許可条件を定義する構造体。

`allowTransfer`関数で設定される、送付対象・有効期間・宛先・任意トークン可否を保持します。

**パラメータ**

* `tokenId`
  * 対象となるトークンID。
* `time`
  * Transfer可能な期限（ブロック時間）。
* `to`
  * Transfer許可されたアドレス。
* `anyToken`
  * 任意のトークン送付を許可するかどうか。

##### `ApprovalConditions`

```javascript
struct ApprovalConditions {
    uint256 time;
    uint256 numberOfTransfers;
}
```

Approval許可条件を定義する構造体。

`allowApproval`関数で設定される、承認有効期間とTransfer回数制限を保持します。

**パラメータ**

* `time`
  * 承認が有効な期限。
* `numberOfTransfers`
  * 承認されたTransferの回数。

#### 関数

##### `addBindings`

```javascript
function addBindings(address _keyWallet1, address _keyWallet2) external returns (bool);
```

2つのKeyWalletを登録し、アカウントをセキュア化する関数。

呼び出し元（Holderアカウント）をKeyWallet1／KeyWallet2と結びつけ、アカウントを保護対象にします。
登録完了時に`AccountSecured`イベントが発行されます。

以下の条件に該当する場合、処理はrevertします。

* 呼び出し元がHolderでない場合。
* すでにセキュリティ保護済みの場合。
* 2つのKeyWalletが同一アドレス。
* 呼び出し元とKeyWalletのいずれかが同じ。
* KeyWalletが`0x0`（無効アドレス）。
* 既に他のHolderにKeyWalletとして登録済み。

**引数**

* `_keyWallet1`
  * 1つ目のKeyWallet。
* `_keyWallet2`
  * 2つ目のKeyWallet。

**戻り値**

* `bool`
  * 成功時は`true`、失敗時は`false`。

##### `getBindings`

```javascript
function getBindings(address _account) external view returns (AccountHolderBindings memory);
```

指定されたアカウントに紐づくKeyWallet情報を取得する関数。

`_account`アドレスに対して登録済みのKeyWallet1とKeyWallet2を、`AccountHolderBindings`構造体の形式で返します。
これにより、対象のアカウントがどのKeyWalletと連携しているかを確認できます。

**引数**

* `_account`
  * KeyWallet情報を取得したいアカウントアドレス。

**戻り値**

* `AccountHolderBindings`
  * `firstWallet`および`secondWallet`を含む構造体。

##### `resetBindings`

```javascript
function resetBindings() external returns (bool);
```

既存のKeyWallet設定をリセットする関数。

KeyWalletの1つが不正アクセスされた、または使用不能になった場合に利用します。
KeyWallet（`_keyWallet1`または`_keyWallet2`）から呼び出され、対象HolderのKeyWallet設定をリセットします。
呼び出し後、Holderは再度`addBindings`を使用して新しいKeyWalletを設定できます。
処理成功時には`AccountResetBinding`イベントが発行されます。
KeyWallet以外のアドレスから呼び出された場合はrevertします。

**戻り値**

* `bool`
  * 成功時は`true`、失敗時は`false`。

##### `safeFallback`

```javascript
function safeFallback() external returns (bool);
```

HoldingWalletに不正アクセスが発生した時に資産を安全なKeyWalletへ移す関数。

Holderアカウントが危険にさらされたとき、KeyWalletのいずれかから呼び出して資産をもう一方のKeyWalletへ移動します。
これにより、HoldingWalletが完全に乗っ取られても、KeyWallet側で資産を安全に退避できます。
実行後、`SafeFallbackActivated`イベントが発行されます。
KeyWallet以外のアドレスが呼び出した場合はrevertします。

**戻り値**

* `bool`
  * 成功時は`true`、失敗時は`false`。

##### `allowTransfer`

```javascript
function allowTransfer(
    uint256 _tokenId,
    uint256 _time,
    address _to,
    bool _anyToken
) external returns (bool);
```

KeyWalletから特定のTransfer条件を事前許可する関数。

`transferFrom`や`safeTransferFrom`の呼び出し前に使用され、特定のトークン送付条件を事前に許可します。
この許可により、HoldingWalletが指定の条件を満たすTransferを安全に実行可能になります。
パラメータを柔軟に設定でき、0値を指定すると制限を解除できます。

* `_tokenId = 0`
    トークンIDの制限なし
* `_time = 0`
    有効期限の制限なし
* `_to = 0x0`
    宛先制限なし
* `_anyToken = true`
    すべてのトークン送付を許可

条件が設定されると、`AccountEnabledTransfer`イベントが発行されます。
KeyWallet以外が呼び出した場合、またはトークンの所有者がHolderでない場合はrevertします。

**引数**

* `_tokenId`
  * Transferを許可するトークンID。
* `_time`
  * Transfer許可の有効期限（ブロック時間）。
* `_to`
  * Transferを許可するアドレス。
* `_anyToken`
  * 任意のトークンTransferを許可するかどうか。

**戻り値**

* `bool`
  * 成功時は`true`、失敗時は`false`。

##### `getTransferableFunds`

```javascript
function getTransferableFunds(address _account) external view returns (TransferConditions memory);
```

指定アカウントのTransfer許可条件を取得する関数。

`allowTransfer`で設定された条件（トークンID、有効期限、宛先、任意許可）を、構造体`TransferConditions`で返します。
これにより、どの条件下でTransferが実行可能かを確認できます。

**引数**

* `_account`
  * Transfer条件を取得したいアカウント。

**戻り値**

* `TransferConditions`
  * 設定されたTransfer条件の構造体。

##### `allowApproval`

```javascript
function allowApproval(
    uint256 _time,
    uint256 _numberOfTransfers
) external returns (bool);
```

KeyWalletからApproval（承認）条件を事前許可する関数。

`approve`または`setApprovalForAll`の実行前に使用し、指定期間内に指定回数分のTransferを許可します。
`_numberOfTransfers`に0を指定した場合は、期間中無制限に許可されます。
設定が完了すると`AccountEnabledApproval`イベントが発行されます。
KeyWallet以外のアドレスが呼び出した場合はrevertします。

**引数**

* `_time`
  * 承認が有効な期限（ブロック時間）。
* `_numberOfTransfers`
  * 承認されたTransfer回数（0は無制限）。

**戻り値**

* `bool`
  * 成功時は`true`、失敗時は`false`。

##### `getApprovalConditions`

```javascript
function getApprovalConditions(address _account) external view returns (ApprovalConditions memory);
```

指定アカウントの承認条件を取得する関数。

`allowApproval`で設定された承認条件を、`ApprovalConditions`構造体で返します。
構造体には、有効期限（`time`）と許可されたTransfer回数（`numberOfTransfers`）が含まれます。

**引数**

* `_account`
  * 条件を取得したいアカウントアドレス。

**戻り値**

* `ApprovalConditions`
  * 承認条件を含む構造体。

##### `getNumberOfTransfersAllowed`

```javascript
function getNumberOfTransfersAllowed(
    address _account,
    address _spender
) external view returns (uint256);
```

指定アカウントが特定のSpenderに許可しているTransfer回数を取得する関数。

`allowApproval`で設定された回数制限を確認するために使用します。
この関数により、Spenderがあと何回Transfer可能かを把握できます。

**引数**

* `_account`
  * 承認を設定したHolderアカウント。
* `_spender`
  * 承認を受けているアドレス。

**戻り値**

* `uint256`
  * 残りTransfer回数。

##### `isSecureWallet`

```javascript
function isSecureWallet(address _account) external returns (bool);
```

指定アカウントがセキュリティ機能を有効化しているかを判定する関数。

`addBindings`を使用してKeyWalletを設定している場合、この関数は`true`を返します。
未設定の場合や通常のウォレットは`false`となります。

**引数**

* `_account`
  * 判定対象のアカウント。

**戻り値**

* `bool`
  * セキュリティ有効化状態（true=有効、false=無効）。

##### `isSecureToken`

```javascript
function isSecureToken(uint256 _tokenId) external returns (bool);
```

特定のトークンがセキュリティ保護対象かどうかを判定する関数。

`addBindings`によってセキュア化されたアカウントに属するトークンは`true`を返します。
通常の**ERC721**トークンは`false`となります。

**引数**

* `_tokenId`
  * 判定対象のトークンID。

**戻り値**

* `bool`
  * セキュリティ保護状態（`true`=保護済み、`false`=未保護）。


## 補足

NFKBTの各技術判断は、**ERC721**との一貫性と互換性を保ちつつ、自己保管（self-custodial）で使えるセキュリティ機能をユーザーに提供することを重視しています。
NFKBTは**ERC721**の機能をそのまま受け継ぐことで、既存のNFT対応dAppに求められる要件を満たし、既存エコシステム側の調整を必要としない形で利用できるようにしています。
その結果、ユーザーはNFKBTを「**従来の動作**」で使うか、NFKBTのセキュリティ機能を有効にして使うかを選べます。
これにより、実装が即時に進められ、既存の分散型エコシステムが変更を行う必要がなくなります。

開発者とユーザーの双方にとって扱いやすいよう、`allowTransfer`と`allowApproval`は成功時に`bool`を返し、失敗時はrevertします。
これは**ERC721**で一般的な挙動に合わせた決定です。

## 互換性

KBT（KeyBoundToken）は、既存のトークン標準やウォレットと互換を持つように設計されています。
既存のトークンやウォレットは、NFKBTが導入されても従来どおり機能して影響を受けません。
NFKBTは**ERC721**に準拠するため、セキュリティ機能を使わない限り従来同様の使い方ができ、機能を有効にする場合でも既存のワークフローを阻害しない構成になっています。

## テスト

テスト一式は`assets`ディレクトリに配置されています。
平均的なガスについては以下のとおりです。

| 機能              |               平均ガス使用（原文記載） |
| --------------- | -------------------------: |
| `addBindings`   |                    155,096 |
| `resetBindings` |                     30,588 |
| `safeFallback`  | 72,221（Holderが保有するNFT数に依存） |
| `allowTransfer` |                     50,025 |
| `allowApproval` |                     44,983 |

これらの値は、各機能を実行した際の平均的なコスト感を示す目安です。
特に`safeFallback`は、保有NFTの数によって移動対象が増減するため、使用量が変動します。

## 参考実装

実装は[`assets`](https://eips.ethereum.org/assets/eip-6809/)ディレクトリに配置されています。

以下はフロー図です。

![参考実装の図](https://eips.ethereum.org/assets/eip-6809/Contract%20Interactions%20diagram.svg)

このフロー図は、**HoldingWallet**・**KeyWallet1**・**KeyWallet2**・**IKBT721コントラクト**の間で行われる代表的な操作（`addBindings`・`allowTransfer`・`allowApproval`・`safeFallback`・`resetBindings`）のやり取りを時系列で表しています。

#### ステップ1：ウォレットの保護設定（`addBindings`）

1. **HoldingWallet**（NFTを保有するアカウント）が、セキュリティ機能を有効化するために、`addBindings`を呼び出します。
2. 引数として2つのKeyWalletアドレス（`_keyWallet1`, `_keyWallet2`）を指定します。
3. コントラクトはこの設定を登録し、**HoldingWallet**が「**セキュアアカウント**」として扱われるようになります。
4. 処理成功後、`AccountSecured`イベントが発行されます。

結果、HoldingWalletはNFKBTとしてセキュリティが有効化され、KeyWallet1とKeyWallet2が紐づけられます。

#### ステップ2：Transfer許可設定（`allowTransfer`）

1. **KeyWallet**（1または2のいずれか）が、`allowTransfer`を呼び出します。
2. パラメータには以下の4つを指定します。
    * `_tokenId`
        対象となるNFTのID（0を指定すればすべて許可）。
    * `_time`
        Transferが許可される期限。
    * `_to`
        Transferを許可する宛先。
    * `_anyToken`
        すべてのトークン送付を許可するかどうか。
3. コントラクトは許可条件を登録し、`AccountEnabledTransfer`イベントを発行します。

結果、HoldingWalletは、指定条件のもとで`transferFrom`を実行できるようになります。

#### ステップ3：Approval許可設定（`allowApproval`）

1. **KeyWallet**が`allowApproval`を呼び出します。
2. パラメータには以下を指定します。
    * `_time`
        承認の有効期限。
    * `_numberOfTransfers`
        許可するTransfer回数（0は無制限）
3. コントラクトは条件を保存し、`AccountEnabledApproval`イベントを発行します。

結果、HoldingWalletは、指定期間・指定回数の範囲で`approve`または`setApprovalForAll`を行うことができます。

#### ステップ4：トークンの送付・承認（`transferFrom` / `approve`）

1. **HoldingWallet**が、`transferFrom`または`approve`を呼び出します。
2. コントラクトは事前に設定された許可条件をチェックします。
3. 条件が満たされていれば実行、満たされなければrevertします。
4. 実行時、標準の`Transfer`または`Approval`イベントが発行されます。
    * `transferFrom`では、許可回数を1減らし、0になれば承認を取り消します。
    * `approve`や`setApprovalForAll`も同様に、期間や回数制限を適用します。

#### ステップ5：不正アクセス検知時の資産移動（`safeFallback`）

1. **KeyWallet**が、HoldingWalletの不正アクセスを検知した場合、`safeFallback`を呼び出します。
2. コントラクトはHoldingWalletの保有資産を、もう一方のKeyWalletへ移動します。
3. 処理完了後、`SafeFallbackActivated`イベントが発行されます。

結果、HoldingWalletが乗っ取られても、資産はKeyWallet側に避難できます。

#### ステップ6：KeyWalletの入れ替え（`resetBindings`）

1. **KeyWallet1**または**KeyWallet2**が、もう一方のKeyWalletに問題があると判断した場合、`resetBindings`を呼び出します。
2. コントラクトはKeyWallet設定を初期化し、HoldingWalletが再び`addBindings`を実行できる状態に戻します。
3. 処理完了後、`AccountResetBinding`イベントが発行されます。

結果、新しいKeyWalletを登録できるようになり、単一障害点を防止します。

## 全体フローまとめ

| ステップ | 呼び出し元 | 関数 | 目的 | 結果 |
| ---- | -------- | ---------- | ------ | ------- |
| 1  | HoldingWallet | `addBindings`  | KeyWalletを登録  | セキュリティ機能を有効化  |
| 2  | KeyWallet     | `allowTransfer`            | Transfer条件を許可        | 条件付き送付を承認          |
| 3    | KeyWallet     | `allowApproval`            | Approval条件を許可        | 条件付き承認を許可          |
| 4    | HoldingWallet | `transferFrom` / `approve` | 実際の送付・承認処理           | 条件に応じた操作を実行        |
| 5    | KeyWallet     | `safeFallback`             | HoldingWalletの危険時に退避 | 資産をKeyWalletへ移動    |
| 6    | KeyWallet     | `resetBindings`            | KeyWalletのリセット       | 新しいKeyWalletを登録可能に |

この一連の流れにより、**NFKBT**は「**資産を直接保護する**」仕組みをスマートコントラクト上で完結させています。
ユーザーはウォレット単位ではなくトークン単位で安全性を制御でき、鍵漏えいやウォレット乗っ取りといった問題に対して、柔軟かつオンチェーンでの対応が可能になります。

## セキュリティ

### KeyWalletの設計（3ウォレット構成の理由）

NFKBTでは、`addBindings`関数を呼び出す時に2つのKeyWalletを登録する必要があります。
これにより、NFKBTは以下の3ウォレット構成で動作します。

| 種類            | 説明                        |
| ------------- | ------------------------- |
| **HoldingWallet** | トークン（NFKBT）を実際に保有するウォレット。 |
| **KeyWallet1**    | 保護・許可・復旧操作を行うウォレット（1つ目）。  |
| **KeyWallet2**    | 同上（2つ目）。                  |

このように「**3ウォレット構成**」を採用した理由は以下です。

- **登録を同時に行うことでガスコストを削減できること。**
- **1つずつKeyWalletを追加する場合に起きやすい設定ミスを防げること。**
- **安全機能（`safeFallback`など）の悪用を防止するため。**

もしKeyWalletを複数回に分けて追加できる仕様にしてしまうと、設定ミスやガスコストの増大が発生するだけでなく、`safeFallback`機能を悪用される恐れも生じます。
そのため、NFKBTでは必ず2つのKeyWalletを同時に登録する仕様としています。

### 想定シナリオごとの対応策

NFKBTは、どのウォレットが侵害されたかによって、ユーザーが取るべき行動を明確に定義しています。

| シナリオ | 想定される状況 | 推奨アクション | 使用する関数 |
| ------- | ----------- | ------------ | -------------- |
| HoldingWalletが侵害された  | 保有ウォレットが乗っ取られた・アクセス不能    | KeyWalletから資産を退避         | `safeFallback`  |
| KeyWallet1または2が侵害された | 保護ウォレットの1つが乗っ取られた・アクセス不能 | KeyWalletからセキュリティ設定をリセット | `resetBindings` |

### HoldingWalletが侵害された場合（`safeFallback`）

HoldingWalletが不正アクセスを受けた、またはアクセス不能になった場合、ユーザーはKeyWalletのどちらかから`safeFallback`関数を呼び出すことで、資産をもう一方のKeyWalletへ退避できます。

`safeFallback`の呼び出しにより、NFKBTはHoldingWalletからKeyWalletへ安全に移動します。
この仕組みにより、HoldingWalletが乗っ取られても、資産が攻撃者の手に渡る前に保護できます。
特に、NFKBTが呼び出し元のKeyWallet自身に送られないように設計されている点が重要です。
もし呼び出し元KeyWalletに移動してしまうと、攻撃者がKeyWalletを奪っていた場合、すべてのNFKBTが一括で奪われる危険があります。

そのため、`safeFallback`では必ずもう一方のKeyWalletに資産を移すよう設計されています。

### KeyWalletが侵害された場合（`resetBindings`）

KeyWallet1またはKeyWallet2が不正アクセスを受けた場合、残っているKeyWalletから`resetBindings`関数を呼び出します。

`resetBindings`の呼び出しにより、コントラクトに登録されているKeyWallet情報がリセットされ、NFKBTは一時的に通常の**ERC721**トークンのように動作します。
これにより、新しいKeyWalletの再設定（`addBindings`）が可能になります。

この関数は、KeyWalletのみが呼び出せるようになっています。
もしHoldingWalletが`resetBindings`を呼び出せる設計であれば、攻撃者がHoldingWalletを乗っ取った瞬間にセキュリティ機能を解除し、NFKBTを自由にTransferできてしまいます。
このような事態を防ぐため、`resetBindings`の呼び出し権限はKeyWalletのみに限定されています。

3つのウォレットのうち2つが同時に侵害された場合、NFKBTの所有者にできることはありません。
しかし、1つだけ侵害された場合は、NFKBTによってリカバリの機会（second chance）が与えられます。
これは、従来のNFT標準では存在しなかった大きな利点です。

### `allowTransfer`のセキュリティ設計

`allowTransfer`は、NFKBTの送付を安全に実行するための関数であり、ユーザーがTransfer条件を細かく指定できます。
また、dAppが既定値（Default Values）を設定すれば、従来の**ERC721**と同じ動作（Default Behaviors）を再現することも可能です。
この柔軟性により、ユーザーは「**高い制限を課す安全なTransfer**」から「**無制限Transfer**」まで、自分に合ったセキュリティレベルを選択できます。

#### パラメータと挙動

この関数は4つのパラメータを取り、組み合わせによって異なるレベルの保護を実現します。

| パラメータ | 型 | 説明 |
| ----------- | --------- | -------------- |
| `_tokenId`  | `uint256` | 対象となるNFKBTのID。特定のトークンに制限。|
| `_time`   | `uint256` | Transferが可能な期間（ブロック数単位）。現在のタイムスタンプを基準に指定。 |
| `_address`  | `address` | 送付を許可するアドレス。 |
| `_anyToken` | `bool`    | trueの場合、制限を無効化し従来の**ERC721**のように振る舞う。     |

`_anyToken`が`false`の場合は、上記3つの条件（トークンID、期限、宛先）をすべて満たしたTransferが行われます。
一方、`_anyToken`が`true`の場合は、従来の**ERC721**と同様のTransferが可能です。

#### 呼び出し要件

`allowTransfer`はKeyWallet（1または2）から呼び出す必要があります。
これにより、HoldingWalletが勝手にTransferを行えないように制御され、知らない間いに資産が動かされるリスクを軽減します。

### `allowApproval`のセキュリティ設計

`allowApproval`は、第三者（dAppやスマートコントラクト）にNFKBTの利用を許可する時の安全性を高める関数です。
悪意のあるdAppによるトークンの不正操作（例：ドレイン攻撃）を防ぐために設計されています。

#### パラメータと挙動

この関数は2つのパラメータを取り、組み合わせによって許可の範囲を柔軟に制御します。

| パラメータ | 型 | 説明                                 |
| ----------- | --------- | ------------- |
| `_time`              | `uint256` | 承認が有効な期間（ブロック数単位）。指定時間を過ぎると無効化。    |
| `_numberOfTransfers` | `uint256` | 承認された第三者が実行できるTransfer回数。0の場合は無制限。 |

これにより、ユーザーは「**どのくらいの期間・どのくらいの回数、第三者が操作できるか**」を明示的に制限できます。

#### 呼び出し要件

`allowApproval`もKeyWallet（1または2）から呼び出す必要があります。
HoldingWalletが直接`approve`を呼び出せるわけではなく、KeyWalletによる事前許可を経ることで安全性を確保します。

## 最後に

今回は「トークンコントラクト自体に保護機能を組み込み、2つの補助ウォレットによって「**Transfer・Approce**」や「**緊急時の退避・再設定**」を制御できる**NFKBT**という仕組みを提案している**ERC6809**」についてまとめてきました！
いかがだったでしょうか？

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