はじめに
『DApps開発入門』という本や色々記事を書いているかるでねです。
今回は、ERC1155のNFTをウォレットに保持したまま、tokenId単位で移転を制限できるロックの仕組みを提案しているERC7721についてまとめていきます!
以下にまとめられているものを翻訳・要約・補足しながらまとめていきます。
他にも様々なEIP・BIP・SLIP・CAIP・ENSIP・RFC・ACPについてまとめています。
概要
ERC7721は、ERC1155に「ロック(lock)/アンロック(unlock)」の仕組みを追加する拡張です。
対象はERC1155の中でも、とくにNFTとして扱う「特定のtokenId」です。
ERC1155については以下の記事を参考にしてください。
ロックされている間、その tokenId は売買や譲渡(transfer)ができなくなります。
一方で、NFTが持つそれ以外の性質(ゲーム内での利用、会員証としての利用など)は、所有者のウォレット内にあるまま維持できます。
この仕組みの中心は、以下の3つの権限と状態です。
- 所有者(
owner)がNFTをロックできること - ロック解除の権限を持つ「解除担当(locker)」というアドレスを設定できること
- ロックに関する権限や承認(
approval)がトークン移転時にリセット(purge)されること
ここで出てくる用語は以下です。
| 用語 | 説明 |
|---|---|
| EOA | 個人が秘密鍵で操作する通常のウォレットアドレス(例: MetaMaskのアカウント)です。 |
| コントラクトアドレス | スマートコントラクトとして動作するアドレスです。プログラムに従って自動実行できます。 |
locker(解除担当) |
ロックされたNFTをアンロックできる唯一のアドレスです。EOAでもコントラクトでも構いません。 |
approval(承認) |
所有者が、特定のアドレスに対してトークン操作を許可する仕組みです(ERC1155の承認概念を前提にしています)。 |
tokenId |
ERC1155で「どの種類のトークンか」を表すIDです。同じコントラクト内で複数種のトークンを区別します。 |
purge(権利の消去) |
トークンが移転されたときに、ロック/承認などの付随権限が消えることです。前の所有者の設定が残らないようにします。 |
ERC7721の特徴は、「所有者のセルフカストディ(self-custody)」を崩さずに、NFTの移転だけを止められる点です。 セルフカストディとは、NFTをエスクロー(第三者管理の預かり)コントラクトに送らず、所有者自身のウォレットに保持し続けることです。
さらに、ロック解除の権限を持つ locker を設定できるため、例えば「普段使うホットウォレットにNFTを置いたまま、解除権限はコールドウォレットに持たせる」といった運用が可能になります。
これにより、盗難リスクを下げながらユーティリティ利用を継続できます。
ロックと解除の権限モデル
ERC7721では、ロックと解除に関して以下のような権限分離が想定されています。
所有者は、NFTをロックするときにlockerを指定します。
ロックされたNFTは、lockerだけがアンロックできます。
つまり、所有者がロックしても、解除権限は所有者ではなく locker に渡す設計にできます(もちろん、所有者自身をlockerにすることも可能です)。
また、所有者は tokenId に対する承認を与えることができます。
承認を持つアドレスは、その承認を使って「資産をロックする」運用が可能になります。
さらに、承認を持つ側が自分自身を locker として設定してロックすることもできます。
これにより、レンタルや貸付のように「所有者以外がロックを開始する」形も作れます。
ただし、NFTが移転された瞬間に、ロックや承認に関する権利は消去されます。
これは、前の所有者や第三者が設定した locker や承認が、新しい所有者に引き継がれてしまう事故を防ぐための重要な仕様です。
「ロックされたトークンからの引き出し」という考え方
概要には「ロックされたトークンから、承認されたユーザーがあらかじめ決めた量だけ引き出せる」という表現があります。
ここで言う「引き出し」は、現実の金庫のように中に入れているトークンを部分的に取り出すというより、ロック状態のトークンについて、特定の tokenId と数量(amount)を条件に操作できるようにする、という意図です。
ERC1155は「同じ tokenId を複数枚(amount)持てる」標準なので、NFT用途でも「1枚=amount 1」として扱うのが一般的ですが、標準としては数量の概念があるため、tokenId 単位・数量単位で制御できる設計が意識されています。
動機
ERC1155の普及によりNFT需要は大きく伸びましたが、NFT経済には「二次流動性(secondary liquidity)の弱さ」があります。 ここでいう二次流動性は、単に売れる/売れないではなく、「所有者のウォレット内にあるNFTが、金融的に活用しづらく眠ってしまう」状態を指しています。
NFTの流動性を高めるプロジェクトはすでに存在しますが、多くは「NFTをそのプロジェクトのコントラクトへ移転して預け入れる」ことを前提にします。 これが以下の不便やリスクにつながります。
課題1: ユーティリティの喪失
NFTをエスクローに移してしまうと、所有者のウォレットにNFTが存在しなくなります。 その結果、NFTが鍵となっているユーティリティが使えなくなることがあります。
例えば、Discordの認証や、ゲーム内でのアイテム利用などが「ウォレットがNFTを保持していること」を条件にしている場合、エスクローに移した瞬間に利用権が失われます。 つまり、NFTを金融的に活用するために預ける行為が、NFTとしての価値(機能)を落とす行為になり得ます。
課題2: コンポーザビリティの不足
コンポーザビリティは、「部品のように組み合わせて使える性質」です。 DeFiでは、資産を担保に借りて、その借りた資産を別のプロトコルで運用するといった組み合わせが一般的です。
しかしNFTはエスクロー型の仕組みに預けると、別の用途に同時活用しにくくなります。 本来であれば、NFT所有者がローン、レンタル、その他の金融ツールを同時に使い分けられると市場効率は上がりますが、預け入れが前提だとそれが難しくなります。
課題3: スマートコントラクト脆弱性のリスク
NFTを預け入れるコントラクトにバグや脆弱性があると、NFTが失われたり盗まれたりする可能性があります。 NFTを外部コントラクトへ移転すること自体が、攻撃面(attack surface)を広げます。
この3つの課題はUX(ユーザー体験)を悪くします。 そこでERC7721は、NFTをコントラクトへ移転せず、所有者のウォレットに置いたままロックする「ネイティブなロック機構」を入れることで解決を狙います。
ロック中もユーティリティを維持するという考え方
ERC7721では、ロック期間中は譲渡だけが制限されます。 NFT自体は所有者のウォレットに残り、ユーティリティは基本的に変わりません。 つまり「担保にしている間も、会員証として使う」、「ゲーム内で装備として使う」といったことが成り立ちます。
これは不動産の例と同じ発想です。家を担保にローンを組んでも、その家に住み続けられます。 NFTも同様に、担保としての役割を持ちながら、所有者が価値を使い続けられる状態が望ましい、という動機です。
想定ユースケース
以下は、ERC7721が挙げているユースケースです。
| ユースケース | 説明 |
|---|---|
| NFT担保ローン | NFTをレンディングプロトコルに送らず、所有者のウォレットでロックして担保として使います。担保性(勝手に売れない)とユーティリティ継続を両立します。 |
| 無担保に近いNFTレンタル | 借り手は手数料を払いNFTを使えますが、譲渡はできません。貸し手の安全性が上がります。レンタル期間終了後、サービスコントラクトが貸し手へ自動返却する想定です。 |
| BNPL(Buy Now Pay Later) | 購入者はロックされたNFTを受け取り、すぐ使えますが、完済まで売却できません。支払い不能ならNFTは売り手に戻り、手数料も得られる想定です。 |
| コンポーザビリティ | NFTを担保に借りつつ、同じNFTをレンタルに回して収益を得るなど、複数の金融ツールを組み合わせやすくします。 |
| 一次販売(Primary sales) | NFTを一部支払いでミントし、残額を後で精算する形を取りやすくします。 |
| Soulbound | 譲渡不可に近いNFTの運用です。組織がミントし、自分自身をlockerに指定してユーザーへ送付し、資産をロックします。 |
| 安全性の向上 | 所有者はホットウォレット(例: MetaMask)にNFTを置いて使い、解除権限はコールドウォレットに持たせられます。ホットウォレットが侵害されても、コールドウォレットがなければ譲渡できません。 |
「Soulbound」は、一般に譲渡できない(または極めて制限された)NFTを指す言い方です。
ここでは、locker を組織側に固定することで、ユーザー側が勝手に移転できない状態を作る意図です。
他のロック提案との違い
ERC7721は、同じ「NFTロック」を扱う他の提案と比べて、以下の点が異なるとしています。
最小限のlock/unlockに絞る方針
ERC7721は、時間制限付き(time-bound)のロック解除のような具体条件を標準仕様に含めません。 ロック解除の条件は、返済完了、期限到来、その他の合意条件など、プロトコル側のスマートコントラクトで実現できるためです。
つまりERC7721は「ロック/解除という原語的な機能だけを提供し、解除条件の多様性は上位レイヤーに任せる」という設計思想です。
locker と approver の権限分離
ERC7721では、ロック解除の権限(locker)と、承認による操作権限(approver)を分けて考えます。
- 承認を使ってロックし、承認者が解除や引き出しをできる形は、レンタル・レンディング・BNPLのような「第三者が運用する」仕組みに向きます。
- 一方で、ロックはできても「権利を取り消せない(revocationできない)状態」を作りつつ、必要なら解除できる、という形は「アカウントに紐づくNFT(account-boundに近い運用)」に向きます。
ここでの「revocation」は「取り消し」です。 例えば、誰かに与えた権限を後から無効化することを指します。
`transferAndLock の導入
ERC7721はtransferAndLockという機能を持てる設計を提案しています。
これは、送付(transfer)とロック(lock)を一連の操作として実行し、必要なら承認(approve)もまとめて設定できる、という考え方です。
これにより、NFTを受け取った直後からロック状態にして、さらに「あとから取り消し(revocation)できる余地」を持たせるなど、移転後の運用設計がしやすくなります。
仕様
概要
ERC7721は、ERC1155に準拠したトークンコントラクトに対して、「トークンを現在の所有者のウォレット内でロック・アンロックする標準的な方法」を提供します。
ここで重要なのは、「ロックされてもトークンは所有者のウォレットから移動しない」という点です。 ERC7721を実装したコントラクトは、ロックされたトークンについて、移転可能な数量を制限します。
ロックは tokenId 単位かつ amount(数量)単位で行われます。
ERC1155は同じ tokenId を複数枚持てるため、「どれだけロックされているか」を数量で管理します。
ロックとアンロックの基本動作
ロックの方法
トークンのロックは以下の2通りがあります。
1つ目は、所有者が locker を指定してロックする方法です。
lock(tokenId, account, _locker, amount) を使い、解除権限を持つアドレス(locker)を明示的に設定します。
2つ目は、所有者または承認済みアドレスが自分自身を locker としてロックする方法です。
lock(tokenId, account, amount) を使うと、locker は msg.sender に設定されます。
どちらの場合も、ロック対象のアカウントが十分な残高を持っていなければなりません。
また、呼び出し元が所有者または承認済みでなければ、処理は revert します。
アンロックの方法
アンロックは、locker に設定されたアドレスのみが実行できます。
unlock(tokenId, account, amount) により、指定した数量分のロックを解除します。
ロックされていない数量を解除しようとした場合は revert します。
承認(Approval)の拡張
ERC7721では、ERC1155の通常の承認とは別に、「tokenId 単位の承認」が導入されています。
setApprovalForId(tokenId, operator, amount) により、特定の tokenId について、operator が使える数量を設定します。
getApprovalForId(tokenId, account, operator) は、その承認数量を返します。
これにより、「この tokenId だけ」、「この数量まで」という細かい制御が可能になります。
レンタルや分割利用などのユースケースを想定した設計です。
ロック状態の取得
getLocked(tokenId, account, operator) は、指定された operator が account 上でロックしている数量を返します。
- ロックされていない場合は 0 を返します。
- ロックされている場合は、ロック中の数量を返します。
この戻り値を使って、「今どれだけ移転できるか」を判断します。
トークン移転時の制約
ERC7721を実装したコントラクトでは、ERC1155の safeTransferFrom に制約がかかります。
ロックされている数量は移転できません。 移転可能な最大数量は次の式になります。
移転可能数量 = 残高 - getLocked(...)ロック数量を超えて移転しようとすると、トランザクションは revert します。
transferAndLock の考え方
ERC7721は transferAndLock 関数を提供します。
これは「トークンの移転」と「ロック」を1つの操作として実行する関数です。
さらに、必要に応じて locker に対する承認も同時に設定できます。
この関数は、以下のようなケースで推奨されています。
- 移転直後に必ずロックしたい場合
- 移転後にロック解除や権限取り消し(
revocation)が必要な場合
インターフェース
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.7.0 <0.9.0;
/// @title Lockable Extension for ERC1155
/// @dev Interface for the Lockable extension
/// @author piyush-chittara
interface IERCLockable1155 is IERC1155{
/**
* @dev Emitted when tokenId is locked
*/
event Lock(uint256 indexed tokenId, address account, address _locker, uint256 amount);
/**
* @dev Emitted when tokenId is unlocked
*/
event Unlock (uint256 indexed tokenId, address account, address _locker, uint256 amount);
/**
* @dev Lock the tokenId if msg.sender is owner or approved and set locker to msg.sender
*/
function lock(uint256 tokenId, address account, uint256 amount) external;
/**
* @dev Lock the tokenId if msg.sender is owner and set locker to _locker
*/
function lock(uint256 tokenId, address account, address _locker, uint256 amount) external;
/**
* @dev Unlocks the tokenId if msg.sender is locker
*/
function unlock(uint256 tokenId, address account, uint256 amount) external;
/**
* @dev Tranfer and lock the token if the msg.sender is owner or approved.
* Lock the token and set locker to caller
* Optionally approve caller if bool setApprove flag is true
*/
function transferAndLock(address from, address to, uint256 tokenId, uint256 amount, bool setApprove) external;
/**
* @dev Returns the wallet, that is stated as unlocking wallet for the tokenId.
* If (0) returned, that means token is not locked. Any other result means token is locked.
*/
function getLocked(uint256 tokenId, address account, address operator) external view returns (uint256);
function setApprovalForId(uint256 tokenId, address operator, uint256 amount) external;
}イベント
Lock
event Lock(uint256 indexed tokenId, address account, address _locker, uint256 amount);tokenId がロックされた時に発行されるイベント。
トークンがロックされたことを外部に通知するためのイベントです。
どの tokenId が、どのアカウント上で、誰を locker として、どの数量ロックされたかが分かります。
パラメータ
tokenId- ロック対象のトークンID。
account- トークンを保有しているアカウント。
_locker- アンロック権限を持つアドレス。
amount- ロックされた数量。
Unlock
event Unlock(uint256 indexed tokenId, address account, address _locker, uint256 amount);tokenId のロックが解除された時に発行されるイベント。
ロック解除が行われたことを示すイベントです。
どの locker によって、どの tokenId がどの数量解除されたかを確認できます。
パラメータ
tokenId- 対象のトークンID。
account- トークンを保有しているアカウント。
_locker- ロック解除を実行したアドレス。
amount- 解除された数量。
関数
lock
function lock(uint256 tokenId, address account, uint256 amount) external;トークンをロックし、呼び出し元を locker に設定する関数。
所有者または承認済みアドレスが実行できます。
locker は msg.sender に設定されます。
指定した数量分のトークンが移転不可になります。
引数
tokenId- ロック対象のトークンID。
account- トークンを保有しているアカウント。
amount- ロックする数量。
lock
function lock(uint256 tokenId, address account, address _locker, uint256 amount) external;トークンをロックし、任意の locker を設定する関数。
所有者のみが実行できます。
解除権限を第三者に委譲したい場合に使用します。
引数
tokenId- ロック対象のトークンID。
account- トークンを保有しているアカウント。
_locker- アンロック権限を持つアドレス。
amount- ロックする数量。
unlock
function unlock(uint256 tokenId, address account, uint256 amount) external;ロックされたトークンを解除する関数。
locker に設定されたアドレスのみが実行できます。
ロックされていない数量を解除しようとすると revert します。
引数
tokenId- 対象のトークンID。
account- トークンを保有しているアカウント。
amount- 解除する数量。
transferAndLock
function transferAndLock(
address from,
address to,
uint256 tokenId,
uint256 amount,
bool setApprove
) external;トークンを移転し、そのままロックする関数。
移転とロックを1トランザクションで行います。
setApprove が true の場合、locker に対して承認も設定されます。
移転後の revocation を想定したユースケースで推奨されます。
引数
from- 送信元アドレス。
to- 受信先アドレス。
tokenId- 対象のトークンID。
amount- 移転・ロックする数量。
setApprove- 承認を同時に設定するかどうか。
getLocked
function getLocked(
uint256 tokenId,
address account,
address operator
) external view returns (uint256);指定された条件でロックされている数量を返す関数。
operator が account 上でロックしている数量を返します。
ロックされていない場合は 0 を返します。
引数
tokenId- 対象のトークンID。
account- トークン保有アドレス。
operatorlockerまたは確認対象のアドレス。
戻り値
uint256- ロックされている数量。
setApprovalForId
function setApprovalForId(
uint256 tokenId,
address operator,
uint256 amount
) external;tokenId 単位で承認数量を設定する関数。
特定の tokenId について、operator が使える数量を指定します。
ERC1155の全体承認よりも細かい制御が可能です。
引数
tokenId- 対象のトークンID。
operator- 承認を与えるアドレス。
amount- 承認する数量。
補足
ERC7721は transferAndLock(address from, address to, uint256 tokenId, uint256 amount, bool setApprove) を公開します。
これは「トークンを移転する」と「受け取り側でロックする」を一度に行うための関数です。
受け取った側はトークンを持てますが、ロックされているので勝手に譲渡できません。
つまり、受け取り側に条件付きの所有を与えるための仕組みです。
setApprove は追加のスイッチで、true にすると「locker に承認(approval)を付与する」動きになります。
これにより locker は、単にロック解除できるだけでなく、承認を根拠にトークンを動かせる立場になれます。
ここで重要なのは、実際の取り消し(revocation)の条件はERC7721が決めるのではなく、周辺コントラクト側で定義できる点です。
例えば、「分割払いが滞ったら回収」、「レンタル期限を過ぎたら返却」、「返済が完了したら解放」といった条件をサービスコントラクトが判断し、承認を持つ locker(多くはそのサービスコントラクト)が回収や返却処理を実行できます。
まとめると、transferAndLock は次の状態を作るために用意されています。
| 作りたい状態 | transferAndLock が担う部分 |
|---|---|
受け取り側に tokenId を渡し、すぐ使えるようにする |
transfer(移転) |
| ただし売買や第三者への譲渡はさせない | lock(ロック) |
| 条件次第で回収や解除などの運用を可能にする | setApprove=true による承認付与(条件は周辺コントラクトで定義) |
この結果、受け取り側は「使えるが、自由に転売できない」状態になります。BNPLやレンタルで欲しい挙動に寄せやすい設計です。
互換性
ERC7721はERC1155と互換です。 ここでいう互換は、ERC1155が定める基本的なインターフェースや挙動を崩さずに、ロック機能を追加できるという意味です。
そのため、既存のUpgradeable(アップグレード可能)なERC1155コントラクトは、実装を更新することでERC7721のロック機能を取り込めます。 こうすると、今まで「NFTを外部コントラクトに預けないとできなかった流動性の仕組み(貸付、レンタル、分割払いなど)」を、トークン側の機能として持てるようになります。
参考実装
セキュリティ
ロックされた数量は移転できない
一度ロックされた amount は、ロックされたアカウントから移転できません。
これはERC7721の中核で、safeTransferFrom などでロック分を超えて動かそうとすると revert する前提です。
レンタルや担保で「勝手に逃げられない」性質を作れます。
locker かつ承認済みの場合、移転できる
「トークンがロックされていて、呼び出し元が locker であり、かつ承認も持っている場合、その呼び出し元はトークンを移転できる」とあります。
これは、ロック解除の権限だけでなく、承認(approval)もセットで持たせると、locker 側がトークン移転を実行できる、という意味です。
transferAndLock の setApprove=true がまさにこの前提を作ります。
結果として、回収・返却など revocation 的な動きを locker(サービスコントラクト)が行えるようになります。
ここは便利な一方、設計を誤ると「想定より強い権限」を locker に与えることにもなります。
運用側は、どのケースで承認を付けるのかを明確に決める必要があります。
アクセス不能な locker を指定すると永久ロックになり得る
「in-accesible account」は「アクセスできないアカウント」という意味です。
例えば、秘密鍵を紛失したEOAや想定通りに動かないコントラクトアドレスを locker に指定すると、ロック解除ができず、結果として永久にロックされたままになる可能性があります。
また「un-verified contract address」は「検証されていない(中身が不明な)コントラクトアドレス」というニュアンスです。
実装が公開されていない、あるいは期待する unlock を提供していないコントラクトを locker にすると、解除不能になるリスクがあります。
この点は、ロックが安全装置である一方、取り返しのつかない設定にもなり得ることを示しています。
とくに lock(tokenId, account, _locker, amount) のように所有者が locker を自由指定できる場合、設定ミスがそのまま資産ロックにつながります。
MEV(最大抽出可能価値)に関する考慮はない
MEVは、トランザクションの順序操作などで利益を抜く行為・構造のことです。 「ロック・アンロックができるのは権限を持つ当事者だけなので、ロック可能トークン自体にMEVの考慮はない」としています。
つまり、誰でもロックできたり解除できたりする公開操作ではないため、第三者が順序を操作して得をする余地が基本的に生まれない、という整理です。
引用
Piyush Chittara (@piyush-chittara), "ERC-7721: Lockable Extension for ERC-1155 [DRAFT]," Ethereum Improvement Proposals, no. 7721, May 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7721.
最後に
今回は「ERC1155のNFTをウォレットに保持したまま、tokenId単位で移転を制限できるロックの仕組みを提案しているERC7721」についてまとめてきました! いかがだったでしょうか?
質問などがある方は以下のTwitterのDMなどからお気軽に質問してください!
他の媒体でも情報発信しているのでぜひ他も見ていってください!


