ERC1155でownerOf関数を実装する2つの方法
ERC1155について
おそらく多くの方が以下のコード・記事を見ていると思います。ですので、以下のコードで今回はownerOf関数を実装できないか考えます。
問題点
ERC1155では、balanceOf関数が用意されています。これは引数にtokenIdの持ち主のaddressとtokenIdを渡してあげると、そのaddressがいくらそのtokenIdを持っているのかを返してくれる関数です。
contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
// Mapping from token ID to account balances
mapping(uint256 => mapping(address => uint256)) private _balances;
function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
require(account != address(0), "ERC1155: address zero is not a valid owner");
return _balances[id][account];
}
}
上記のようにERC1155ではaddressとtokenIdを渡すと保有トークン量を返してくれるのですが、ERC721などで用意されているownerOf関数はありません。これはERC1155が特定のトークンidを複数人のaddressが持つ設計で作られているため、1つのtokenIdで1つのaddressが決まる設計ではないからです。
ownerOf関数を実装する2つのパターン
ですが、実際はownerOf関数のようなあるtokenIdを引数で渡したらaddressが返ってくる関数をアプリ側で用意したいことがあると思います。
筆者が思うに2つのパターンが可能です。
- privateなownersといった変数をコントラクトに用意してownerOf関数を作る
- TransferSingleもしくはTransferBatchイベントの履歴から最新のownerを探す
ownersという変数を用意し、ownerOf関数を作る
// tokenIdから複数addressへマッピング
mapping(uint256 => address[]) private owners;
function onwnerOf(uint256 tokenId) public view virtual returns(address[]) {
address[] ownersArr = owners[tokenId];
return ownersArr;
}
上記のように自前でowners変数を設けてあげることで、ownerOf関数を実装できます。ただし注意点として、mintやburn、transferが実行され、tokenIdの持ち主のaddressに変更があるときはownersを書き換えてあげる処理が必要になります。ERC1155.solで定義されている_beforeTokenTransferフックスなどをoverrideしてあげるとスムーズです。
TransferSingleもしくはTransferBatchイベントから探す
あんまり自前で変数を用意したくない場合は、EFC1155.solで定義されているTransferSingleかTransferBatchイベントを検索して、最新のownerアドレスを探す方法があります。
やりたいこととしては以下のようなことです。
function ownerOf(uint256 tokenId) public view returns (address) {
// TransferSingleイベントをフィルタリング
event TransferSingle(
address indexed operator,
address indexed from,
address indexed to,
uint256 id,
uint256 value
);
// 最新のTransferSingleイベントを取得
TransferSingle[] memory transfers = this.TransferSingle({
id: tokenId
});
// 最新の転送先アドレスがオーナー
if (transfers.length > 0) {
address lastTo = transfers[transfers.length - 1].to;
// 残高チェックで確認
if (balanceOf(lastTo, tokenId) == 1) {
return lastTo;
}
}
revert("tokenIdのオーナーが見つかりません");
}
ただし、上記のようにsolidity内でevent情報をクエリすることはできません。そこでether.jsやThe Graphを使ってイベント履歴を検索することになります。
以下はサンプルです。
const filter = contract.filters.TransferSingle(null, null, null, tokenId);
const events = await contract.queryFilter(filter);
const lastEvent = events[events.length - 1];
const currentOwner = lastEvent.args.to;
The graphを使う場合は以下のようなクエリが考えられます。
{
transferSingles(
where: { id: $tokenId }
orderBy: blockNumber
orderDirection: desc
first: 1
) {
to
}
}
Discussion