NFT(Non-Fungible Token)のERC-721規格が提案されました。 NFT は一意のトークンを指します。つまり、スマート コントラクト内のすべてのトークンは他のトークンとは異なります。たとえば、ある種の画像、宝くじ、収集品、絵画、さらには音楽など、一意のものを識別するために使用できます。 ERC-20 に関する記事を読んでから、最初にそれを読むことを検討してください。 ERC-721 の例に最適な実際のプロジェクトは です。これはブロックチェーン上のゲームです。楽しく知識を得るために、この素晴らしいゲームをチェックしてください。 Cryptokitties しかし、これはどのように機能しますか?これらの画像、音楽、および絵画は、スマート コントラクトにどのように保存されますか? これらはコントラクト内に保存されるのではなく、コントラクトは外部アセットを指すだけです。所有権を示す 数字があります。主なアセットは、URI を介して tokenId に添付された画像またはその他のデータです。もう少し潜りましょう。 tokenId JSON テキストである URI をトークン ID に割り当てます。そして、その JSON テキストには、画像またはその他のアセットへの詳細とパスが含まれています。そして、ユーザーは のみを所有します。たとえば、私のアドレスには 4 の残高があり、その に添付された画像またはあらゆる種類のデータが私のものになります。 所有しているため、そのデータを所有しています。 tokenid tokenid tokenid tokenid ここで、画像と JSON テキスト (URI) の 2 つをどこかに保存して、NFT マーケットプレイスが画像を取得して Web サイトに表示できるようにする必要があります。なぜメタデータ URI が必要なのか、画像 URI をトークン ID に直接割り当てないのか、と考えているかもしれません。ええと、そもそもERC標準が必要だった理由を覚えているかもしれません。そうです、クライアント側のアプリケーションがコントラクトに簡単に接続できるようにするためです。同様に、NFT 用に作成するメタデータは、JSON ファイルからデータを簡単に取得してサイトに表示できるように、NFT マーケットプレイスが推奨する適切な形式にする必要があります。 さっそく収納を見ていきましょう。 データの保存 画像と URI を保存する方法は 2 つあります。オンチェーン (ブロックチェーン内) またはオフチェーン (ブロックチェーン外) でホストできます。 オンチェーン データはブロックチェーンに保存されますが、多くのスペースを占有するため、手頃な価格ではありません。契約の導入時に数千ドルを支払うことを考えてみてください。よく聞こえません。これとは別に、ブロックチェーンが提供するストレージも限られており、大きなファイルを保存することはできません. そのため、ブロックチェーンの外部でデータをホストするオプションがあります。 AWS またはその他のクラウド サービスを使用して、当社のウェブサイトを通じて。しかし、これはブロックチェーンの主要なアイデア、つまり分散化を殺してしまいます。サーバーがクラッシュしたり、ハッキングされたりした場合はどうなりますか?すべてのデータをホストする単一のサーバーがあるためです。そのため、攻撃に対して堅牢で分散化された別のものが必要です。それは、分散ストレージ システムである IPFS (Inter Planetary File System) です。 IPFS には、データを格納するブロックチェーンの考え方に似た複数のノードがあります。しかし、料金を支払う必要はありません。画像と JSON ファイル (URI) を IPFS でホストできます。そうすることで、データを直接指す一意の CID (ランダムな意味不明) が得られ、特定の CID を特定のトークン ID に割り当てます。技術的な部分については後で詳しく説明します。まず、ERC-721 標準のインターフェイスを理解しましょう。 ERC-721 インターフェース ERC-721 インターフェースがどのように見えるか見てみましょ 。 う この規格には次の機能があります。 あるアカウントから別のアカウントへのトークンの転送。 アカウントの現在のトークン残高を取得します。 特定のトークンの所有者を取得します。 ネットワーク上で利用可能なトークンの総供給量。 これらに加えて、アカウントからのトークンの量がサードパーティのアカウントによって移動できることを承認するなど、他の機能もあります。 それでは、ERC-721 のインターフェースを見てみましょう。 pragma solidity ^0.4.20; interface ERC721 { event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); function balanceOf(address _owner) external view returns (uint256); function ownerOf(uint256 _tokenId) external view returns (address); function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; function transferFrom(address _from, address _to, uint256 _tokenId) external payable; function approve(address _approved, uint256 _tokenId) external payable; function setApprovalForAll(address _operator, bool _approved) external; function getApproved(uint256 _tokenId) external view returns (address); function isApprovedForAll(address _owner, address _operator) external view returns (bool); } Break 壊す 所有者のアカウントのトークン数を返します。残高を追跡するにはどうすればよいですか?いつも同じです。ウォレット内のトークンの総数を追跡するマッピングを定義します。 mapping(address => uint) 公的残高;トークンの総数を合計して同じものを返すだけであることを思い出してください。このマッピングは、アドレスが持つトークン ID の数を示すだけなので、トークンの所有者についてはわかりません。 balanceOf function balanceOf(address _owner) external view returns (uint256); NFT の所有者を検索します。さて、この関数は特定の NFT の所有者について教えてくれます。このデータは上記と同様に保持されます。 mapping( uint => address) 公的残高;ゼロ アドレスに割り当てられた NFT は無効と見なされ、それらに関するクエリはエラーをスローする必要があります。 2. ownerOf function ownerOf(uint256 _tokenId) external view returns (address); NFT の所有権をあるアドレスから別のアドレスに転送します。 tokenId の所有者を新しいアドレスに設定し、 マッピングも更新するだけです。次の場合にエラーをスローする必要があります。 3. safeTransferFrom balances は、この NFT の現在の所有者、承認されたオペレーター、または承認されたアドレスです。 msg.sender 現在の所有者ではありません _from ゼロアドレスです。 __to_ は有効な NFT ではありません __tokenId_ 転送が完了すると、この関数は がスマート コントラクト (コード サイズ > 0) であるかどうかをチェックします。その場合、 で 関数を呼び出し、戻り値がこれと等しくない場合はスローします: __to_ __to_ onERC721Received bytes4(keccak256("onERC721Received(address, address,uint256, bytes)")). それが何だった?これが、この関数が安全な転送と呼ばれる理由です。これについては後で詳しく説明します。 function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; : これは、追加のデータ パラメータを使用して上記の関数と同じように機能しますが、この関数はデータを「 」に設定するだけです。 4. safeTransferFrom function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; これら2つの関数が同じ名前を持っているのはなぜですか? Javascript とは異なり、Solidity は関数のオーバーロードをサポートしています。は、同じ名前の 2 つの関数を定義できることを意味します。唯一の条件は、パラメーターが異なり、関数のシグネチャに違いが生じることです。関数シグネチャが何かわかりませんか?どうぞ。 豆知識: 回答へのリンク NFT の所有権を譲渡します。 関数と同じように機能しますが、唯一の違いは、受信側でセーフティ コール ( ) を呼び出さないことです。したがって、NFT を失うリスクが生じます。 5. transferFrom は、 safeTransferFrom onERC721Received function transferFrom(address _from, address _to, uint256 _tokenId) external payable; が、より多くの機能とロジックを備えています。この関数が機能するために、ネストされたマッピングを定義します このマッピングでは、uint はトークン ID と、その特定の tokenId で操作を実行することが承認されているアドレスを参照します。この承認機能は、 がトークンの現在の所有者または所有者のオペレーターであることを確認する必要があります。このチェックに合格した場合は、マッピングのみを更新する必要があります。ここのオペレーターは何ですか?次の関数を参照してください。 6. ERC20 承認機能と同じ概念を承認します mapping(uint =>address) internal allowance; msg.sender function approve(address _approved, uint256 _tokenId) external payable; 前の承認関数と同様に機能しますが、単一のトークン ID のアドレスを承認する代わりに、この関数は特定のアドレスが所有するすべてのトークンを処理するために承認およびアドレス指定する必要があります。どういう意味ですか?これは、NFT のアドレス演算子を作成できることを意味します。所有権を取り消すまで、そのアドレスが NFT の所有者になります。今回もマッピングを使用します: 最初のアドレスは、2 番目のアドレスが承認または取り消すブール値 true または false をそれぞれ設定します。 7. setApprovalForAll は mapping(address => mapping(address=>bool))internal operator; function setApprovalForAll(address _operator, bool _approved) external; ERC-20 インターフェイスの Allowance 関数に似ています。単一の NFT の承認済み を返します。上記の承認機能で更新したマッピングをチェックします。 8. getApproved は、 address function getApproved(uint256 _tokenId) external view returns (address); 。 別の の承認された であるかどうかを照会します。特定の の承認された 返す代わりに、この関数は、 関数で更新した指定された の の 返す必要があります。 9. 上記の関数と同様の isApprovedForAll address address operator tokenId address setApprovalForAll address operator address function isApprovedForAll(address _owner, address _operator) external view returns (bool); ERC-721のイベント NFT の所有権が何らかのメカニズムによって変更されたときに発行されます。このイベントは、NFT が作成されたとき および破棄されたとき : コントラクトの作成中に、NFT をいくつでも作成して、Transfer を発行せずに割り当てることができます。転送時に、その NFT の承認済みアドレス (存在する場合) はなしにリセットされます。 Transfer: (from == 0) (to == 0). 例外 event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); NFT の承認済みアドレスが変更または再確認されたときに発行されます。住所ゼロは、承認された住所がないことを示します。 Transfer イベントが発行されると、これは、その NFT の承認済みアドレス (存在する場合) が none にリセットされたことも示します。 承認は、 event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); 、オペレーターが所有者に対して有効または無効になったときに発行されます。オペレーターは所有者のすべての NFT を管理できます。 ApproveForAll は event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); これは、ERC 721 コントラクトのインターフェイスでした。それでは、その実装とルールについて詳しく学びましょう。 ERC721TokenReceiver 実装の前に、もう 1 つのことがあります。トークンの転送について話したときに、 という関数について話したのを覚えていますか?それについて今話しましょう。 ERC-721 トークンの受信者がコントラクトである場合、ERC-721 トークンで機能する必要があります。そのレシーバー コントラクトにトークンを操作する機能がない場合はどうなるでしょうか。トークンはそのコントラクトに永久にロックされます。機能がないため、これらのトークンを任意のアドレスに持ち出すことはできません。この脆弱性を克服するために、ERC-721 トークンの受信者は受信者インターフェイスを実装する必要があります。そうしないと、どのコントラクトもそのコントラクトに ERC-721 トークンを送信できなくなります。受信機のインターフェースを見てみましょう。 onERC721Received インターフェイスには実装する関数が 1 つだけ含まれており、それは この関数は NFT の受信を処理する必要があります。 ERC-721 スマート コントラクトは、 後に受信側でこの関数を呼び出します。この関数は、転送を元に戻し、拒否するためにスローする場合があります。マジック値以外を返すと、トランザクションが元に戻される必要があります。 この onERC721Received です。 transfer interface ERC721TokenReceiver { function onERC721Received(address _operator,address _from,uint256 _tokenId,bytes _data) external returns(bytes4); } ここで、受信者が インターフェースを実装しているかどうかを、トークン コントラクトがどのように知るのでしょうか?したがって、トークンを送信する前に、まずこの部分を確認する必要があります。このために、ERC-721 の 実装を見てみましょう。これは、トークンを送信する前に 内で呼び出す必要があるプライベート関数です。これが true を返す場合、トランザクションのみが発生するはずです。 ERC721TokenReceiver openzeppelin safeTransfer() これは、NFT を受信者コントラクトから取得する機能を保証するものではありません。これは、受信者機能の実装のみをチェックしており、NFT をコントラクトから転送する機能をチェックしていないためです。では、その意味は何ですか?このメソッドは、レシーバー コントラクトの作成者が少なくともこのメソッドを認識していたことを示しています。つまり、コントラクトから NFT を転送する機能を実装している必要があります。そしてもちろん、自分のトークンがコントラクト内で動かなくなることを望む人はいません。 と ERC721Metadata ERC721Enumerable 上記のインターフェイスは、ERC-721 トークン コントラクトに必須でした。現在、一部のインターフェースは理論的にはオプションですが、コントラクトをより明確にし、より使いやすくしています。 これらは、 と 一つ一つ捕まえていきましょう。 ERC721Metadata ERC721Enumerable です。 メタデータ拡張は、主にトークン名のコントラクトを調べるために使用されます。次に、メタデータ インターフェイスを見てみましょう。これを理解するのは非常に簡単です。 ERC721 メタデータ: interface ERC721Metadata/* is ERC721 */ { /// @notice A descriptive name for a collection of NFTs in this /// contract function name()external view returns (string _name); /// @notice An abbreviated name for NFTs in this contract function symbol()external view returns (string _symbol); /// @notice A distinct Uniform Resource Identifier (URI) for a given /// asset. /// @dev Throws if `_tokenId` is not a valid NFT. URIs are defined /// in RFC3986. The URI may point to a JSON file that conforms to /// the "ERC721Metadata JSON Schema". function tokenURI(uint256 _tokenId)external view returns (string); これは NFT に関するデータを提供するだけです。 ERC721Enumerable - interface ERC721Enumerable/* is ERC721 */ { /// @notice Count NFTs tracked by this contract /// @return A count of valid NFTs tracked by this contract, where /// each one of them has an assigned and queryable owner not equal /// to the zero address function totalSupply()external view returns (uint256); /// @notice Enumerate valid NFTs /// @dev Throws if `_index` >= `totalSupply()`. /// @param _index A counter less than `totalSupply()` /// @return The token identifier for the `_index`th NFT, /// (sort order not specified) function tokenByIndex(uint256 _index)external view returns (uint256); /// @notice Enumerate NFTs assigned to an owner /// @dev Throws if `_index` >= `balanceOf(_owner)` or if /// `_owner` is the zero address, representing invalid NFTs. /// @param _owner An address where we are interested in NFTs owned /// by them /// @param _index A counter less than `balanceOf(_owner)` /// @return The token identifier for the `_index`th NFT assigned to /// `_owner`, /// (sort order not specified) function tokenOfOwnerByIndex(address _owner,uint256 _index)external view returns(uint256); ERC165 では、インターフェイスがコントラクトによって実装されているかどうかをどのように知るのでしょうか?このために、トピックから外れる必要があります。つまり には次の機能があります。 、ERC-165 function supportsInterface(bytes4 interfaceID) external view returns (bool); この関数は、確認する interfaceId を引数として取り、それを確認する interfaceId に一致させます。 interfaceId とは何かを説明しますが、ご容赦ください。まず、openzeppelin によるこの関数の実装を見てください。 この関数 の interfaceId について詳しく理解しましょう。 type(IERC721).interfaceId 、2 つの主な操作によって取得されます。 interfaceID は 1. ケッカ 256 ハッシュ 2. XOR演算 keccak256 は、入力を受け取り、ランダムな文字列をバイト単位で吐き出すアルゴリズムです。それでは、この関数の keccak ハッシュを見つけてみましょう。 function supportsInterface(bytes4 interfaceID) external view returns (bool); 構文は ṭthis のようになります。 ; bytes4(keccak256('supportsInterface(bytes4)') これで、この関数の keccak ハッシュが得られました。 keccak256 のパラメーター内で関数全体を渡す必要はなく、署名のみを渡す必要があることに注意してください。署名は、名前とパラメーターの型のみを意味し、パラメーターの名前さえ含まれていません。すべての関数の keccak256 ハッシュを取得した後、それらの間で XOR 演算を実行し、取得した結果が interfaceId です。方法を見てみましょう。 操作は入力を受け取り、それらを比較した後にいくつかの出力を返します。入力の 1 つだけが 場合、true を出力します。両方の入力が false または両方が true の場合、出力は になります。 XOR true false XOR の背後にある数学を理解する必要はありません。すべての関数の ハッシュを取得して XOR ゲートに渡し、取得した値が interfaceID であることを覚えておいてください。したがって、主なアイデアは、関数の keccak ハッシュを取得し、それらの XOR 出力を取得することです。その出力はinterfaceIdであり、その後、コントラクトが同じinterfaceIDを持っているかどうかを確認できます。そうであれば、コントラクトが目的のインターフェースを実装していることを意味します。 keccak256 堅牢性により、これが簡単になったので心配しないでください。 ERC721Metadata のインターフェースを例に学習しましょう。 interface ERC721Metadata/* is ERC721 */ { function name()external view returns (string _name); function symbol()external view returns (string _symbol); function tokenURI(uint256 _tokenId)external view returns (string); } ここには 3 つの関数があります。だから、あなたが理解できるようにこのコードを書きました。ここでキャレット記号 に注意してください。この記号は、2 つの値の間の XOR 演算を表しています。 ^ // SPDX-License-Identifier: MIT pragma solidity ^0.8.16 ; interface metadata { function name()external view returns (string memory _name ); function symbol()external view returns (string memory _symbol); function tokenURI(uint256 _tokenId)external view returns (string memory a); } contract { //old and lengthy way by applying the keccak256 algorithm and performing XOR. function getHashOldWay ()public pure returns(bytes4){ return bytes4(keccak256('name()')) ^ bytes4(keccak256('symbol()'))^ bytes4(keccak256('tokenURI(uint256)')); } //Improved way function getHashNewWay ()public pure returns(bytes4){ metadata a; return a.name.selector ^ a.symbol.selector ^ a.tokenURI.selector; } //this is the latest simplest way to get the interfaceId function getHashLatestWay ()public pure returns(bytes4){ return type(metadata).interfaceId; } } おっと、ERC165 に行きすぎて、ERC721 のことを忘れていました。話を戻しましょう。 opensea (または他のマーケットプレイス) はどのようにスマート コントラクトからデータを取得しますか? これは、さまざまな NFT マーケットプレイスが特定の tokenId で割り当てたデータを識別するのに役立つトークン URI を理解するための適切な時期です。 Opensea は最も有名な市場であるため、例として取り上げます。 Opensea は、ERC721 コントラクトが 関数を呼び出して tokenURI を返すことを期待しています。このための openzeppelin 契約を見てみましょう。 tokenURI() TokenURI には 2 つのタイプがあります。 1. tokenId によって異なる、すべてのトークンの同じベース URI。 このようにして、すべてのトークンにベース URI を割り当て、それに tokenId を連結して、メタデータ URI を取得します。これは、リンクがすべてのメタデータ JSON ファイルで同じになるようにコレクションを割り当てた場合にのみ可能です。唯一の違いは tokenId です。例えば、 このリンクはフォルダーを指しており、これが baseURI であることを意味します。 https://gateway.pinata.cloud/ipfs/QmYLwrqMmzC3k4eZu7qJ4MZJ4SNYMgqbRJFLkyiPtUBZUP/ 単一のメタデータを取得するには、 に移動する必要があります https://gateway.pinata.cloud/ipfs/QmYLwrqMmzC3k4eZu7qJ4MZJ4SNYMgqbRJFLkyiPtUBZUP/1.json 次に、その特定のトークンのメタデータに直接移動する方法でコントラクトから tokenURI を返す必要があり、マーケットプレイスはそれを取得できます。これを行うには、baseURI を tokenId と連結し、それを abi エンコードします。以下のこの関数を見てください。 function tokenURI(uint tokenId) override public view returns(string memory) { return (string(abi.encodePacked( "https://gateway.pinata.cloud/ipfs/QmYLwrqMmzC3k4eZu7qJ4MZJ4SNYMgqbRJFLkyiPtUBZUP/",Strings.toString(tokenId),".json")) ); } これは、URI を取得し、JSON ファイルで提供されたすべてのデータを表示するためにマーケットプレイスが呼び出す関数です。 2. トークンごとに異なる URI。 これは、異なる場所にある URI を割り当てることができる別の方法です。また、個々のファイルの IPFS リンクは次のようになります。 この方法では、tokenId を baseURI と連結してこのリンクを取得することはできません。代わりに、このリンクをチェーン上の tokenId に直接接続する必要があります。そのために、特定の tokenId に URI を割り当てるプライベート マッピングを定義している上記の コントラクトを見てください。その後、特定の URI でマッピングを設定する関数も定義します。また、 関数は、マップされた URI を返す新しい機能を追加することで、親関数をオーバーライドします。 https://ipfs.filebase.io/ipfs/Qma65D75em77UTgP5TYXT4ZK5Scwv9ZaNyJPdX9DNCXRWc ERC721URIStorage tokenURI もう 1 つ注意すべき点は、JSON スキーマの形式です。 JSON には、マーケットプレイスが目的のデータを取得して表示できるように、標準化された方法でデータを含める必要があります。 ERC721 の標準 JSON スキーマ。 { "title": "Asset Metadata", "type": "object", "properties": { "name": { "type": "string", "description": "Identifies the asset to which this NFT represents" }, "description": { "type": "string", "description": "Describes the asset to which this NFT represents" }, "image": { "type": "string", "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive." } } } これらは、ERC-721 (NFT) スマート コントラクトの開発に興味がある場合に知っておくべきことです。 : 問題 ERC721 標準にはスケーラビリティに関するいくつかの問題があります。 ERC-721では一度に1つのトークンを転送できるため、トークンを転送するときに問題が発生します。これは、誰かに 5 つの NFT を送信する場合、5 つのトランザクションを実行する必要があることを意味します。これは、ブロックチェーンでどれだけのスペースが必要かを明確に示しています。これが、強気相場でネットワークが問題に直面する理由です。ネットワークにあまりにも多くのラッシュがあり、ガス料金が急速に上昇するからです。 ERC-1155 はこれらの問題を解決します。どうやって?これについては別の記事でお話ししましょう。 ERC721 標準を完全に理解していれば、ERC1155 を理解するのはそれほど難しくありません。 にも掲載されています。 ここ