はじめに

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

今回は、NFTごとに複数のERC20トークンを担保として管理し、引き出しや分配まで可能にする仕組みを提案しているERC7595についてまとめていきます!

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

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

概要

ERC7595は、既存のERC721(NFT)規格を拡張し、NFTを担保(コラテラル)として扱えるようにするための提案です。 ここでいう担保とは、NFTに価値の裏付けとなる資産を紐づけ、その価値を利用できる状態にすることを指します。

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

ERC7595では、NFTコレクションのコントラクトが、ネイティブコイン(EthereumであればETH)に加えて、複数のERC20トークンを保有できるように設計されています。 そして、特定のNFT(tokenId)の所有者は、そのNFTに対応付けられたERC20トークンの一部、または全部をアンロックして引き出す権利を持ちます。

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

ここで重要なのは、「NFTそのものがERC20トークンを内包している」という考え方です。 NFTは単なる画像やメタデータへの参照ではなく、実際にオンチェーン上で検証可能な資産を裏付けとして持つ存在になります。 これにより、NFTの価値が外部市場の評価だけに依存せず、コントラクト上の残高として明確に示されるようになります。

結果として、NFTは「所有権を示すトークン」であると同時に、「実体のある暗号資産へのアクセス権」を表すものになります。

動機

近年、NFTファイナンスと呼ばれる分野が拡大しています。 これは、NFTを担保として貸し借りを行うレンディングプロトコルや、NFTの価値を前提とした金融サービス全般を指します。 現在主流となっている仕組みでは、NFTの「フロアプライス」が担保価値の基準として使われています。

フロアプライスとは、あるNFTコレクションの中で市場に出ている最低価格のことです。 しかし、この価格はNFT市場特有の需給バランスに大きく左右されます。 NFT市場は暗号資産市場全体と比べても価格変動が激しく、短期間で大きく上下することが珍しくありません。

さらに問題となるのが、価格操作の可能性です。 特定のNFTコレクションでは、意図的に高値で売買を行うことで、市場価格やフロアプライスを不自然に吊り上げることができます。 こうした価格がそのままレンディングプロトコルに採用されると、本来の価値以上の担保評価が行われ、プロトコル全体のリスクにつながります。

このように、NFTの市場価格、特にフロアプライスのみに依存した担保評価は、不安定で信頼性が高いとは言えません。

ERC7595は、こうした課題に対する解決策として設計されています。 NFTにERC20トークンなどの実体資産を直接紐づけることで、オンチェーン上で誰でも検証できる最低価値(オンチェーンで検証可能なフロアプライス)を提供します。 これにより、外部マーケットの価格変動や操作の影響を受けにくいNFT評価が可能になります。

また、この仕組みはNFTコレクション制作者にとっても新たな利点をもたらします。 NFTに裏付け資産があることで、ロイヤリティ(NFTが取引されるたびに制作者が受け取る手数料)を、単なる市場価格依存ではなく、実体価値に支えられた形で持続的に得られるようになります。

加えて、NFTを単なるコレクティブルから、価値を生み出し、管理して分配できる金融的な資産へと進化させることで、NFTコレクション制作者にとってのマネタイズ手段も広がります。

ERC7595は、ERC721ベースのNFTが抱えてきた「価値の不透明さ」や「価格操作への弱さ」といった課題に正面から向き合い、NFTと暗号資産をより安全かつ実用的に結びつけることを目的とした拡張規格です。

仕様

IERC721Envious

IERC721Envious.sol
interface IERC721Envious is IERC721 {
	event Collateralized(uint256 indexed tokenId, uint256 amount, address tokenAddress);
	event Uncollateralized(uint256 indexed tokenId, uint256 amount, address tokenAddress);
	event Dispersed(address indexed tokenAddress, uint256 amount);
	event Harvested(address indexed tokenAddress, uint256 amount, uint256 scaledAmount);

	/**
	 * @dev An array with two elements. Each of them represents percentage from collateral
	 * to be taken as a commission. First element represents collateralization commission.
	 * Second element represents uncollateralization commission. There should be 3 
	 * decimal buffer for each of them, e.g. 1000 = 1%.
	 *
	 * @param uint 256 index of value in array.
	 */
	function commissions(uint256 index) external view returns (uint256);

	/**
	 * @dev 'Black hole' is any address that guarantees that tokens sent to it will not be 
	 * retrieved from it. Note: some tokens revert on transfer to zero address.
	 *
	 * @return address address of black hole.
	 */
	function blackHole() external view returns (address);

	/**
	 * @dev Token that will be used to harvest collected commissions.
	 *
	 * @return address address of token.
	 */
	function communityToken() external view returns (address);

	/**
	 * @dev Pool of available tokens for harvesting.
	 *
	 * @param uint256 index in array.
	 * @return address address of token.
	 */
	function communityPool(uint256 index) external view returns (address);

	/**
	 * @dev Token balance available for harvesting.
	 *
	 * @param address address of token.
	 * @return uint256 token balance.
	 */
	function communityBalance(address tokenAddress) external view returns (uint256);

	/**
	 * @dev Array of tokens that have been dispersed.
	 *
	 * @param uint256 index in array.
	 * @return address address of dispersed token.
	 */
	function disperseTokens(uint256 index) external view returns (address);

	/**
	 * @dev Amount of tokens that has been dispersed.
	 *
	 * @param address address of token.
	 * @return uint256 token balance.
	 */
	function disperseBalance(address tokenAddress) external view returns (uint256);

	/**
	 * @dev Amount of tokens that was already taken from the disperse.
	 *
	 * @param address address of token.
	 * @return uint256 total amount of tokens already taken.
	 */
	function disperseTotalTaken(address tokenAddress) external view returns (uint256);

	/**
	 * @dev Amount of disperse already taken by each tokenId.
	 *
	 * @param tokenId unique identifier of unit.
	 * @param address address of token.
	 * @return uint256 amount of tokens already taken.
	 */
	function disperseTaken(uint256 tokenId, address tokenAddress) external view returns (uint256);

	/**
	 * @dev Mapping of `tokenId`s to token addresses that have collateralized before.
	 *
	 * @param tokenId unique identifier of unit.
	 * @param index in array.
	 * @return address address of token.
	 */
	function collateralTokens(uint256 tokenId, uint256 index) external view returns (address);

	/**
	 * @dev Token balances that are stored under `tokenId`.
	 *
	 * @param tokenId unique identifier of unit.
	 * @param address address of token.
	 * @return uint256 token balance.
	 */
	function collateralBalances(uint256 tokenId, address tokenAddress) external view returns (uint256);

	/**
	 * @dev Calculator function for harvesting.
	 *
	 * @param amount of `communityToken`s to spend
	 * @param address address of token to be harvested
	 * @return amount to harvest based on inputs
	 */
	function getAmount(uint256 amount, address tokenAddress) external view returns (uint256);

	/**
	 * @dev Collect commission fees gathered in exchange for `communityToken`.
	 *
	 * @param amounts[] array of amounts to collateralize
	 * @param address[] array of token addresses
	 */
	function harvest(uint256[] memory amounts, address[] memory tokenAddresses) external;

	/**
	 * @dev Collateralize NFT with different tokens and amounts.
	 *
	 * @param tokenId unique identifier for specific NFT
	 * @param amounts[] array of amounts to collateralize
	 * @param address[] array of token addresses
	 */
	function collateralize(
		uint256 tokenId,
		uint256[] memory amounts,
		address[] memory tokenAddresses
	) external payable;

	/**
	 * @dev Withdraw underlying collateral.
	 *
	 * Requirements:
	 * - only owner of NFT
	 *
	 * @param tokenId unique identifier for specific NFT
	 * @param amounts[] array of amounts to collateralize
	 * @param address[] array of token addresses
	 */
	function uncollateralize(
		uint256 tokenId, 
		uint256[] memory amounts, 
		address[] memory tokenAddresses
	) external;

	/**
	 * @dev Split collateral among all existent tokens.
	 *
	 * @param amounts[] to be dispersed among all NFT owners
	 * @param address[] address of token to be dispersed
	 */
	function disperse(uint256[] memory amounts, address[] memory tokenAddresses) external payable;
}

変数

commissions
javascript
function commissions(uint256 index) external view returns (uint256);

担保化および担保解除時に発生する手数料率を管理する変数です。 ERC7595では、担保を預け入れる際と引き出す時に、それぞれ手数料が設定されています。 この関数は、その手数料率を配列形式で管理しています。 数値はパーセンテージを1000倍した形式で扱われ、例えば「1000」は1%を意味します。 indexが0の場合は担保化手数料、1の場合は担保解除手数料を表します。

パラメータ

  • index
    • 取得したい手数料のインデックス番号。
blackHole
javascript
function blackHole() external view returns (address);

トークンが回収不可能になるアドレスを返す変数です。 black holeとは、一度トークンを送ると二度と取り出せないアドレスのことです。 多くの場合、Burn用途に使われますが、ERC20トークンによってはゼロアドレスへの送金で失敗するものもあるため、専用のアドレスが使われます。

communityToken
javascript
function communityToken() external view returns (address);

コミュニティ報酬の回収に使用されるトークンのアドレスを返します。 ERC7595では、手数料として集められたトークンを、特定のERC20トークンと交換して回収する仕組みがあります。 その時に使用される基準トークンがcommunityTokenです。

communityPool
javascript
function communityPool(uint256 index) external view returns (address);

回収対象となるトークンプールの一覧を管理する変数です。 コミュニティプールには、手数料として蓄積された複数種類のERC20トークンが登録されます。 この関数は、その一覧を配列として参照するためのものです。

パラメータ

  • index
    • 配列内のインデックス番号。
communityBalance
javascript
function communityBalance(address tokenAddress) external view returns (uint256);

回収可能なトークン残高を返す変数です。 指定したERC20トークンについて、現在コミュニティプールにどれだけの残高があるかを確認できます。

パラメータ

  • tokenAddress
    • 対象となるERC20トークンのアドレス。
disperseTokens
javascript
function disperseTokens(uint256 index) external view returns (address);

分配対象となったトークンの一覧を返します。 disperse処理によってNFT保有者全体に分配されるトークンは、この配列に記録されます。

disperseBalance
javascript
function disperseBalance(address tokenAddress) external view returns (uint256);

分配対象として確保されているトークン量を返します。 指定したトークンが、NFT保有者全体への分配用としてどれだけ確保されているかを確認できます。

javascript
function disperseTotalTaken(address tokenAddress) external view returns (uint256);

これまでに引き出された分配トークンの合計量を返します。 NFT保有者全体が、これまでにどれだけの分配トークンを受け取ったかを把握するための情報です。

disperseTaken
javascript
function disperseTaken(uint256 tokenId, address tokenAddress) external view returns (uint256);

特定のNFTが受け取った分配量を返します。 各NFTごとに、どのトークンをどれだけ受け取ったかを個別に管理しています。

collateralTokens
javascript
function collateralTokens(uint256 tokenId, uint256 index) external view returns (address);

NFTに担保として登録されたトークンの一覧を返します。 1つのNFTは複数のERC20トークンを担保として持てます。 この関数は、その履歴を配列として参照するためのものです。

collateralBalances
javascript
function collateralBalances(uint256 tokenId, address tokenAddress) external view returns (uint256);

NFTごとに保管されている担保トークン残高を返します。 NFT単位で、どのERC20トークンがどれだけ担保として紐づいているかを確認できます。

イベント

Collateralized
javascript
event Collateralized(uint256 indexed tokenId, uint256 amount, address tokenAddress);

NFTが担保化された時に発行されるイベント。 特定のNFTに対して、ERC20トークンやネイティブコインが担保として追加されたことを示します。

パラメータ

  • tokenId
    • 対象となるNFTのID。
  • amount
    • 担保化された数量。
  • tokenAddress
    • 担保として使用されたトークンのアドレス。
Uncollateralized
javascript
event Uncollateralized(uint256 indexed tokenId, uint256 amount, address tokenAddress);

担保が引き出された時に発行されるイベント。 NFTの所有者が担保資産を解除したことを示します。

Dispersed
javascript
event Dispersed(address indexed tokenAddress, uint256 amount);

担保資産が分配用に登録された時に発行されるイベント。 NFT保有者全体への分配処理が行われたことを示します。

Harvested
javascript
event Harvested(address indexed tokenAddress, uint256 amount, uint256 scaledAmount);

手数料が回収された時に発行されるイベント。 コミュニティトークンと交換して、手数料が回収されたことを示します。

関数

getAmount
javascript
function getAmount(uint256 amount, address tokenAddress) external view returns (uint256);

回収に必要な交換量を計算する関数。 指定したcommunityTokenの量に対して、どれだけの対象トークンを回収できるかを計算します。

引数

  • amount
    • 使用するcommunityTokenの数量。
  • tokenAddress
    • 回収対象のトークンアドレス。

戻り値

  • 回収できるトークン量。
harvest
javascript
function harvest(uint256[] memory amounts, address[] memory tokenAddresses) external;

手数料を回収する関数。 コミュニティトークンを支払い、プールに蓄積された手数料トークンを回収します。

collateralize
javascript
function collateralize(
	uint256 tokenId,
	uint256[] memory amounts,
	address[] memory tokenAddresses
) external payable;

NFTに担保を追加する関数。 NFTに対して、複数種類のトークンをまとめて担保として預け入れられます。 ネイティブトークンも対応しています。

uncollateralize
javascript
function uncollateralize(
	uint256 tokenId, 
	uint256[] memory amounts, 
	address[] memory tokenAddresses
) external;

担保を引き出す関数。 NFTの所有者のみが実行でき、指定した担保トークンを引き出します。

disperse
javascript
function disperse(uint256[] memory amounts, address[] memory tokenAddresses) external payable;

担保をNFT保有者全体に分配する関数。 指定したトークンを、存在するすべてのNFTに対して均等またはルールに基づいて分配します。

IEnviousHouse

IEnviousHouse.sol
interface IEnviousHouse {
	event Collateralized(
		address indexed collection,
		uint256 indexed tokenId,
		uint256 amount,
		address tokenAddress
	);
	
	event Uncollateralized(
		address indexed collection,
		uint256 indexed tokenId,
		uint256 amount,
		address tokenAddress
	);
	
	event Dispersed(
		address indexed collection,
		address indexed tokenAddress,
		uint256 amount
	);
	
	event Harvested(
		address indexed collection,
		address indexed tokenAddress,
		uint256 amount,
		uint256 scaledAmount
	);

	/**
	 * @dev totalCollections function returns the total count of registered collections.
	 *
	 * @return uint256 number of registered collections.
	 */
	function totalCollections() external view returns (uint256);

	/**
	 * @dev 'Black hole' is any address that guarantees that tokens sent to it will not be 
	 * retrieved from it. Note: some tokens revert on transfer to zero address.
	 *
	 * @param address collection address.
	 * @return address address of black hole.
	 */
	function blackHole(address collection) external view returns (address);

	/**
	 * @dev collections function returns the collection address based on the collection index input.
	 *
	 * @param uint256 index of a registered collection.
	 * @return address address collection.
	 */
	function collections(uint256 index) external view returns (address);

	/**
	 * @dev collectionIds function returns the collection index based on the collection address input.
	 * 
	 * @param address collection address.
	 * @return uint256 collection index.
	 */
	function collectionIds(address collection) external view returns (uint256);
	
	/**
	 * @dev specificCollections function returns whether a particular collection follows the ERC721 standard or not.
	 * 
	 * @param address collection address.
	 * @return bool specific collection or not.
	 */
	function specificCollections(address collection) external view returns (bool);
	
	/**
	 * @dev An array with two elements. Each of them represents percentage from collateral
	 * to be taken as a commission. First element represents collateralization commission.
	 * Second element represents uncollateralization commission. There should be 3 
	 * decimal buffer for each of them, e.g. 1000 = 1%.
	 *
	 * @param address collection address.
	 * @param uint256 index of value in array.
	 * @return uint256 collected commission.
	 */
	function commissions(address collection, uint256 index) external view returns (uint256);
	
	/**
	 * @dev Token that will be used to harvest collected commissions.
	 *
	 * @param address collection address.
	 * @return address address of token.
	 */
	function communityToken(address collection) external view returns (address);

	/**
	 * @dev Pool of available tokens for harvesting.
	 *
	 * @param address collection address.
	 * @param uint256 index in array.
	 * @return address address of token.
	 */
	function communityPool(address collection, uint256 index) external view returns (address);
	
	/**
	 * @dev Token balance available for harvesting.
	 *
	 * @param address collection address.
	 * @param address address of token.
	 * @return uint256 token balance.
	 */
	function communityBalance(address collection, address tokenAddress) external view returns (uint256);

	/**
	 * @dev Array of tokens that have been dispersed.
	 *
	 * @param address collection address.
	 * @param uint256 index in array.
	 * @return address address of dispersed token.
	 */
	function disperseTokens(address collection, uint256 index) external view returns (address);
	
	/**
	 * @dev Amount of tokens that has been dispersed.
	 *
	 * @param address collection address.
	 * @param address address of token.
	 * @return uint256 token balance.
	 */
	function disperseBalance(address collection, address tokenAddress) external view returns (uint256);
	
	/**
	 * @dev Amount of tokens that was already taken from the disperse.
	 *
	 * @param address collection address.
	 * @param address address of token.
	 * @return uint256 total amount of tokens already taken.
	 */
	function disperseTotalTaken(address collection, address tokenAddress) external view returns (uint256);
	
	/**
	 * @dev Amount of disperse already taken by each tokenId.
	 *
	 * @param address collection address.
	 * @param tokenId unique identifier of unit.
	 * @param address address of token.
	 * @return uint256 amount of tokens already taken.
	 */
	function disperseTaken(address collection, uint256 tokenId, address tokenAddress) external view returns (uint256);
	
	/**
	 * @dev Mapping of `tokenId`s to token addresses that have collateralized before.
	 *
	 * @param address collection address.
	 * @param tokenId unique identifier of unit.
	 * @param index in array.
	 * @return address address of token.
	 */
	function collateralTokens(address collection, uint256 tokenId, uint256 index) external view returns (address);

	/**
	 * @dev Token balances that are stored under `tokenId`.
	 *
	 * @param address collection address.
	 * @param tokenId unique identifier of unit.
	 * @param address address of token.
	 * @return uint256 token balance.
	 */
	function collateralBalances(address collection, uint256 tokenId, address tokenAddress) external view returns (uint256);
	
	/**
	 * @dev Calculator function for harvesting.
	 *
	 * @param address collection address.
	 * @param amount of `communityToken`s to spend.
	 * @param address address of token to be harvested.
	 * @return amount to harvest based on inputs.
	 */
	function getAmount(address collection, uint256 amount, address tokenAddress) external view returns (uint256);
	
	/**
	 * @dev setSpecificCollection function enables the addition of any collection that is not compatible with the ERC721 standard to the list of exceptions.
	 *
	 * @param address collection address.
	 */
	function setSpecificCollection(address collection) external;
	
	/**
	 * @dev registerCollection function grants Envious functionality to any ERC721-compatible collection and streamlines
	 * the distribution of an initial minimum disbursement to all NFT holders.
	 *
	 * @param address collection address.
	 * @param address address of `communityToken`.
	 * @param uint256 collateralization fee, incoming / 1e5 * 100%.
	 * @param uint256 uncollateralization fee, incoming / 1e5 * 100%.
	 */
	function registerCollection(
		address collection,
		address token,
		uint256 incoming,
		uint256 outcoming
	) external payable;	

	/**
	 * @dev Collect commission fees gathered in exchange for `communityToken`.
	 *
	 * @param address collection address.
	 * @param amounts[] array of amounts to collateralize.
	 * @param address[] array of token addresses.
	 */
	function harvest(
		address collection,
		uint256[] memory amounts,
		address[] memory tokenAddresses
	) external;
	
	/**
	 * @dev Collateralize NFT with different tokens and amounts.
	 *
	 * @param address collection address.
	 * @param tokenId unique identifier for specific NFT.
	 * @param amounts[] array of amounts to collateralize.
	 * @param address[] array of token addresses.
	 */
	function collateralize(
		address collection,
		uint256 tokenId,
		uint256[] memory amounts,
		address[] memory tokenAddresses
	) external payable;
	
	/**
	 * @dev Withdraw underlying collateral.
	 *
	 * Requirements:
	 * - only owner of NFT
	 *
	 * @param address collection address.
	 * @param tokenId unique identifier for specific NFT.
	 * @param amounts[] array of amounts to collateralize.
	 * @param address[] array of token addresses.
	 */
	function uncollateralize(
		address collection,
		uint256 tokenId,
		uint256[] memory amounts,
		address[] memory tokenAddresses
	) external;
	
	/**
	 * @dev Split collateral among all existent tokens.
	 *
	 * @param address collection address.
	 * @param amounts[] to be dispersed among all NFT owners.
	 * @param address[] address of token to be dispersed.
	 */
	function disperse(
		address collection,
		uint256[] memory amounts,
		address[] memory tokenAddresses
	) external payable;
}

イベント

Collateralized
javascript
event Collateralized(
	address indexed collection,
	uint256 indexed tokenId,
	uint256 amount,
	address tokenAddress
);

指定したコレクションのNFTが担保化された時に発行されるイベント。 既存NFTコレクションに対して、tokenId単位で担保が追加されたことを通知します。 collectionが入っているため、どのコレクションのどのNFTに担保が付いたのかを、ログだけで一意に追える設計です。

パラメータ

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • tokenId
    • 対象となるNFTのID。
  • amount
    • 担保として追加された数量。
  • tokenAddress
    • 担保として追加されたトークンのアドレス。
Uncollateralized
javascript
event Uncollateralized(
	address indexed collection,
	uint256 indexed tokenId,
	uint256 amount,
	address tokenAddress
);

指定したコレクションのNFTから担保が引き出された時に発行されるイベント。 tokenIdに紐づく担保残高が減ったことを示します。 担保解除は「NFT所有者が担保を引き出した」操作に対応します。

パラメータ

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • tokenId
    • 対象となるNFTのID。
  • amount
    • 引き出された数量。
  • tokenAddress
    • 引き出されたトークンのアドレス。
Dispersed
javascript
event Dispersed(
	address indexed collection,
	address indexed tokenAddress,
	uint256 amount
);

指定したコレクションに対して分配が行われた時に発行されるイベント。 NFT保有者全体に分配されるトークンが登録されたことを示します。 実際の受け取りは各NFT(tokenId)ごとの引き出し状況で管理されるため、ここでは「分配の原資が積まれた」ことが中心です。

パラメータ

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • tokenAddress
    • 分配対象のトークンアドレス。
  • amount
    • 分配対象として追加された数量。
Harvested
javascript
event Harvested(
	address indexed collection,
	address indexed tokenAddress,
	uint256 amount,
	uint256 scaledAmount
);

指定したコレクションに関する手数料が回収された時に発行されるイベント。 担保化・担保解除で集められた手数料のプールから、communityTokenを対価として、指定トークンが回収されたことを示します。 scaledAmountは、回収計算で使われたスケール後の値を表す想定です。 スケールとは、トークンの小数桁(decimals)差や内部計算の倍率を吸収するために用いられることが多い調整値です。

パラメータ

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • tokenAddress
    • 回収対象のトークンアドレス。
  • amount
    • 回収された数量。
  • scaledAmount
    • 計算上の調整後数量。

関数

totalCollections
javascript
function totalCollections() external view returns (uint256);

登録済みコレクションの総数を返す関数。 抽象化レイヤー側に登録されているNFTコレクションが何件あるかを取得します。 フロントエンドで一覧表示を作る場合などに使いやすい情報です。

戻り値

  • 登録済みコレクション数。
blackHole
javascript
function blackHole(address collection) external view returns (address);

指定コレクションに設定されたブラックホールアドレスを返す関数。 ブラックホールとは、送ったトークンが二度と回収できないことが保証されるアドレスです。 ゼロアドレスへの送金で失敗するERC20トークンもあるため、コレクションごとに適切なブラックホールアドレスを持てるようにしています。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。

戻り値

  • ブラックホールアドレス。
collections
javascript
function collections(uint256 index) external view returns (address);

インデックスから登録済みコレクションのアドレスを返す関数。 登録済みコレクション一覧を配列のように扱うための参照関数です。 totalCollectionsと組み合わせると全件走査ができます。

引数

  • index
    • 登録済みコレクションのインデックス。

戻り値

  • コレクションアドレス。
collectionIds
javascript
function collectionIds(address collection) external view returns (uint256);

コレクションアドレスから登録インデックスを返す関数。 collections(index)の逆引きです。 フロントや他コントラクトが「このアドレスのコレクションは何番目として登録されているか」を知りたい時に使います。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。

戻り値

  • 登録インデックス。
specificCollections
javascript
function specificCollections(address collection) external view returns (bool);

指定コレクションが例外扱いかどうかを返す関数。 ここでいう「例外」とは、一般的なERC721の挙動に完全には一致しないコレクションを指す想定です。 例えば ownerOf の仕様が特殊だったり、標準関数が期待どおり動かないケースに対して、抽象化レイヤー側で個別対応するためのフラグとして使われます。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。

戻り値

  • 例外扱いなら true、そうでなければ false。
commissions
javascript
function commissions(address collection, uint256 index) external view returns (uint256);

指定コレクションの手数料率を返す関数。 手数料率は2要素配列として管理されます。 indexが0の場合は担保化手数料、1の場合は担保解除手数料を表します。 小数3桁のバッファを持つ表現で、例えば「1000」は1%を意味します。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • index
    • 手数料の種類を示すインデックス。

戻り値

  • 手数料率。
communityToken
javascript
function communityToken(address collection) external view returns (address);

指定コレクションで手数料回収に使用するコミュニティトークンを返す関数。 手数料として蓄積されたトークンを回収する際の支払いトークン(基準トークン)です。 コレクションごとに異なるコミュニティトークンを設定できる設計です。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。

戻り値

  • コミュニティトークンのアドレス。
communityPool
javascript
function communityPool(address collection, uint256 index) external view returns (address);

指定コレクションの回収対象トークンプールを参照する関数。 手数料として集まった複数のERC20トークンを、回収可能な対象として配列で持ちます。 この関数は、その一覧をインデックスで取得します。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • index
    • 配列のインデックス。

戻り値

  • 回収対象トークンのアドレス。
communityBalance
javascript
function communityBalance(address collection, address tokenAddress) external view returns (uint256);

指定コレクションの回収可能残高を返す関数。 指定したトークンが、手数料プールとしてどれだけ蓄積されているかを確認できます。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • tokenAddress
    • 対象となるトークンのアドレス。

戻り値

  • 回収可能なトークン残高。
disperseTokens
javascript
function disperseTokens(address collection, uint256 index) external view returns (address);

指定コレクションで分配対象になったトークン一覧を返す関数。 分配処理に使われたトークンを履歴として配列に保持し、インデックスで参照します。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • index
    • 配列のインデックス。

戻り値

  • 分配対象トークンのアドレス。
disperseBalance
javascript
function disperseBalance(address collection, address tokenAddress) external view returns (uint256);

指定コレクションで分配用に確保されたトークン量を返す関数。 分配の原資としてどれだけ積まれているかを確認できます。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • tokenAddress
    • 対象となるトークンのアドレス。

戻り値

  • 分配用残高。
disperseTotalTaken
javascript
function disperseTotalTaken(address collection, address tokenAddress) external view returns (uint256);

指定コレクションで、これまでに引き出された分配トークン合計を返す関数。 分配プールから全NFT保有者が合計でどれだけ受け取ったかを追跡します。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • tokenAddress
    • 対象となるトークンのアドレス。

戻り値

  • 引き出し済み合計量。
disperseTaken
javascript
function disperseTaken(address collection, uint256 tokenId, address tokenAddress) external view returns (uint256);

指定コレクションの特定NFTが受け取った分配量を返す関数。 tokenId ごとに分配の受け取り状況を管理し、二重受け取りを防ぎます。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • tokenId
    • 対象となるNFTのID。
  • tokenAddress
    • 対象となるトークンのアドレス。

戻り値

  • そのNFTが受け取った分配量。
collateralTokens
javascript
function collateralTokens(address collection, uint256 tokenId, uint256 index) external view returns (address);

指定コレクションの特定NFTに紐づいた担保トークン一覧を返す関数。 1つのNFTが複数種類の担保トークンを持てるため、過去に担保化されたトークンを配列として記録します。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • tokenId
    • 対象となるNFTのID。
  • index
    • 配列のインデックス。

戻り値

  • 担保トークンのアドレス。
collateralBalances
javascript
function collateralBalances(address collection, uint256 tokenId, address tokenAddress) external view returns (uint256);

指定コレクションの特定NFTに紐づいた担保残高を返す関数。 tokenId とトークンアドレスの組み合わせで、現在の担保残高を参照できます。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • tokenId
    • 対象となるNFTのID。
  • tokenAddress
    • 対象となるトークンのアドレス。

戻り値

  • 担保残高。
getAmount
javascript
function getAmount(address collection, uint256 amount, address tokenAddress) external view returns (uint256);

回収時の交換量を計算する関数。 指定コレクションのルールに従い、communityToken をいくら使うと、対象トークンをいくら回収できるかを算出します。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • amount
    • 使用するcommunityTokenの数量。
  • tokenAddress
    • 回収対象トークンのアドレス。

戻り値

  • 回収できるトークン量。
setSpecificCollection
javascript
function setSpecificCollection(address collection) external;

ERC721互換でないコレクションを例外リストに追加する関数。 標準的なERC721の前提で処理できないコレクションを登録し、抽象化レイヤー側で特別扱いするために使います。 「ERC721互換でない」というのは、一般に期待される標準的な関数や挙動と一致しない状態を指します。

引数

  • collection
    • 例外として登録するNFTコレクションのコントラクトアドレス。
registerCollection
javascript
function registerCollection(
	address collection,
	address token,
	uint256 incoming,
	uint256 outcoming
) external payable;

ERC721互換コレクションを登録し、担保機能を付与する関数。 抽象化レイヤーにコレクションを登録することで、そのコレクションのNFTに対して担保化・担保解除・分配・回収といった機能が利用できるようになります。 コメントには「初期の最小分配をすべてのNFT保有者に配るのを簡単にする」とあり、登録時に分配処理を同時に行える設計が示唆されています。 手数料は incoming / 1e5 * 100% のように説明されているため、分母が100,000の固定小数点表現として扱う意図が読み取れます。

引数

  • collection
    • 登録するNFTコレクションのコントラクトアドレス。
  • token
    • communityTokenとして使うトークンのアドレス。
  • incoming
    • 担保化手数料率を表す値。
  • outcoming
    • 担保解除手数料率を表す値。
harvest
javascript
function harvest(
	address collection,
	uint256[] memory amounts,
	address[] memory tokenAddresses
) external;

手数料として蓄積されたトークンを回収する関数。 指定コレクションのコミュニティプールに溜まった手数料を、communityToken を支払うことで回収します。 複数トークンをまとめて回収できるよう、数量配列とトークンアドレス配列を受け取ります。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • amounts
    • 回収に使う数量の配列。
  • tokenAddresses
    • 回収対象トークンアドレスの配列。
collateralize
javascript
function collateralize(
	address collection,
	uint256 tokenId,
	uint256[] memory amounts,
	address[] memory tokenAddresses
) external payable;

指定コレクションのNFTに担保を追加する関数。 既存のNFTコレクションの tokenId に対して、複数種類のトークンを担保として追加します。 payable なので、ネイティブコインも担保として扱える設計です。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • tokenId
    • 対象となるNFTのID。
  • amounts
    • 担保として追加する数量の配列。
  • tokenAddresses
    • 担保として追加するトークンアドレスの配列。
uncollateralize
javascript
function uncollateralize(
	address collection,
	uint256 tokenId,
	uint256[] memory amounts,
	address[] memory tokenAddresses
) external;

指定コレクションのNFTから担保を引き出す関数。 コメントに「only owner of NFT」とあるとおり、NFT所有者のみが担保解除できます。 抽象化レイヤーは既存コレクション側の所有者情報(通常はERC721のownerOf)を参照し、所有権に基づいて引き出し可否を判断する想定です。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • tokenId
    • 対象となるNFTのID。
  • amounts
    • 引き出す数量の配列。
  • tokenAddresses
    • 引き出すトークンアドレスの配列。
disperse
javascript
function disperse(
	address collection,
	uint256[] memory amounts,
	address[] memory tokenAddresses
) external payable;

指定コレクションのNFT保有者全体に担保を分配する関数。 分配用のトークンを預け入れ、存在するすべてのNFT保有者へ配るための原資として登録します。 payable なので、ネイティブコインによる分配も想定されています。

引数

  • collection
    • 対象となるNFTコレクションのコントラクトアドレス。
  • amounts
    • 分配に回す数量の配列。
  • tokenAddresses
    • 分配対象トークンアドレスの配列。

補足

「Envious」という用語の採用理由

ERC7595では、この規格を用いて発行されたNFTコレクション、または既存のERC721ベースNFTであってもEnviousHouse抽象化レイヤーを利用しているものを総称して「Envious」と呼びます。

この名称は、NFTが単なる所有証明ではなく、裏側に担保資産を保持している点を強調するために選ばれています。 NFTを所有していない第三者から見ると、そのNFTは内部に暗号資産を抱え込んでいる存在であり、ある意味で「うらやましい(Envious)」状態にあります。

また、名称を統一することで、「このNFTは担保機能を持つかどうか」をプロジェクト名や説明文ではなく、概念レベルで明確に区別できるようになります。 新規コレクションと既存コレクションのどちらであっても、同じ「Envious NFT」として扱える点も重要です。

複数トークンによるNFT担保化

一部のWeb3プロジェクトでは、NFTを担保化する際に、特定のERC20トークン1種類のみを使う設計が採用されています。 この方法はシンプルですが、実運用ではいくつかの問題が出てきます。

まず、複数のトークンを担保に入れたい場合、トークンごとに別トランザクションが必要になるケースがあります。 これはガス代の増加につながり、ユーザー体験(UX)を悪化させます。また、担保資産を柔軟に組み合わせたい場合にも制約になります。

ERC7595では、1つのNFTに対して複数のERC20トークンをまとめて担保化できるように設計されています。 しかも、それを単一トランザクションで実行できます。 これにより、ガス効率が改善されるだけでなく、ユーザーは自分の保有資産に応じて、自然な形で担保構成を選べるようになります。

ネイティブコインによるNFT担保化

ERC20トークンは必ずコントラクトアドレスを持っていますが、ETHのようなネイティブコインにはアドレスが存在しません。 この違いは、担保対象を「トークンアドレスで管理する」設計において問題になります。

ERC7595では、この問題を解決するために、ネイティブコインを識別するための特別なアドレスとして、以下のヌルアドレスを使用します。

javascript
0x0000000000000000000000000000000000000000

このアドレスは、スマートコントラクトの実体として使われることがないため、ERC20トークンのアドレスと衝突する心配がありません。 その結果、 「ERC20トークンもネイティブトークンも同じ仕組みで担保として扱う」という統一的な設計が可能になります。

分配(Disperse)機能の考え方

ERC7595では、NFTコレクション全体に対して担保資産を一括で分配する仕組みが用意されています。 これは、すべてのNFT保有者に対して報酬や価値を均等、またはルールに基づいて配りたい場合を想定しています。

分配処理では、まず全体分の担保資産をスマートコントラクトにまとめて預け入れます。その後、各NFT保有者は、自分が保有しているNFTに対応する取り分を、担保の追加や引き出しのタイミングで請求できます。

この方式の利点は、分配時点で全員に即座に送金する必要がない点です。 オンチェーンでの一斉送金はガスコストが非常に高くなりがちですが、請求型にすることで、必要な人だけが必要なタイミングで受け取れる設計になっています。

ハーベスト(Harvest)機能の考え方

ERC7595では、担保化や担保解除の際に手数料が発生します。 この手数料は、そのまま単一トークンとして固定的に徴収されるのではなく、柔軟に回収できる仕組みになっています。

各Envious NFTコレクションは、コミュニティERC20トークンを1つ設定できます。このトークンを支払うことで、これまでに蓄積された手数料(担保化・担保解除で集まった資産)を回収できます。

この設計により、以下の効果が期待できます。

  • コミュニティトークンに実需を持たせる
  • NFTエコシステム内でトークンが循環する

単なる手数料徴収ではなく、トークン経済とNFTを結びつける役割を果たします。

BlackHoleインスタンスの必要性

一部のERC20トークンでは、ゼロアドレスへの送金が禁止されています。 この仕様は、意図しないトークン消失を防ぐ目的ではありますが、Burn処理を行いたい場合には問題になります。

ERC7595では、ハーベスト処理の中で、コミュニティトークンを流通量から除外する必要があります。 そのため、ゼロアドレスの代わりに「BlackHole」と呼ばれる専用コントラクトを使用します。

BlackHoleコントラクトには以下の特徴があります。

  • トークンを受け取ることはできるが、外部へ送信する機能を持たない
  • ステート変更を伴う操作ができず、基本的に読み取り専用
  • 事実上、トークンを永久にロックする役割を持つ

これにより、ERC20トークンの実装差異に左右されることなく、安全で確実なBurn処理が可能になります。 BlackHoleは単体で使うものではなく、ERC7595の手数料回収や経済設計と組み合わせて使われる前提のコンポーネントです。

引用

571nKY (@571nKY), Cosmos (@Cosmos4k), f4t50 (@f4t50), Harpocrates (@harpocrates555), "ERC-7595: Collateralized NFT [DRAFT]," Ethereum Improvement Proposals, no. 7595, March 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7595.

最後に

今回は「NFTごとに複数のERC20トークンを担保として管理し、引き出しや分配まで可能にする仕組みを提案しているERC7595」についてまとめてきました! いかがだったでしょうか?

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

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