La norme ERC-721 a été proposée pour les NFT (Non-Fungible Tokens). NFT fait référence aux jetons qui sont uniques, ce qui signifie que chaque jeton à l'intérieur du contrat intelligent sera différent des autres. Il peut être utilisé pour identifier des choses uniques, par exemple, une sorte d'image, des billets de loterie, des objets de collection, des peintures ou même de la musique, etc. Pour vous rappeler, ERC-20 était pour les jetons fongibles, si vous n'avez pas lu le article sur ERC-20, alors envisagez de le lire en premier. Le meilleur projet du monde réel pour l'exemple ERC-721 est . C'est un jeu au-dessus de la blockchain, découvrez ce jeu incroyable pour apprendre avec plaisir. Cryptokitties Mais comment cela fonctionne-t-il ? Comment ces images, musiques et peintures sont-elles stockées dans des contrats intelligents ? Eh bien, ces éléments ne sont pas stockés dans le contrat, mais le contrat pointe simplement vers un actif externe. Il y a un nombre, indiquant la propriété. L'actif principal est l'image ou d'autres données attachées au tokenId via un URI. Plongeons un peu plus. tokenId Nous attribuons un URI, qui est un texte JSON, à l'ID de jeton. Et ce texte JSON contient les détails et le chemin d'accès à l'image ou à tout autre élément. Et l'utilisateur ne possède que le . Par exemple, mon adresse a le solde de 4, maintenant l'image ou tout type de données attachées à ce sera à moi. Je possède ces données car je possède le . tokenid tokenid tokenid tokenid Nous devons maintenant stocker ces deux éléments, à savoir l'image et le texte JSON (URI), quelque part afin que n'importe quel marché NFT puisse récupérer l'image et l'afficher sur son site Web. Vous vous demandez peut-être pourquoi avons-nous besoin de l'URI des métadonnées, pourquoi ne pas attribuer directement l'URI de l'image à l'identifiant du jeton ? Eh bien, vous vous souvenez peut-être pourquoi nous avions besoin des normes ERC en premier lieu, oui, afin qu'il devienne facile pour les applications côté client de se connecter aux contrats. De même, les métadonnées que nous écrivons pour les NFT doivent être dans un format approprié recommandé par le marché NFT afin qu'ils puissent facilement récupérer les données de ce fichier JSON et afficher les données sur leur site. Voyons maintenant le stockage. Stockage des données Il existe deux manières de stocker l'image et l'URI. Nous pouvons l'héberger en chaîne (à l'intérieur de la blockchain) ou hors chaîne (en dehors de la blockchain). Les données en chaîne sont stockées dans la blockchain mais elles occupent beaucoup d'espace, ce qui n'est pas abordable. Pensez simplement à payer des milliers de dollars au moment du déploiement de votre contrat. Ça ne sonne pas bien. En dehors de cela, la blockchain fournit également un stockage limité, vous ne pouvez pas y stocker de gros fichiers. Nous avons donc la possibilité d'héberger les données en dehors de la blockchain. Via notre site Web en utilisant AWS ou tout autre service cloud. Mais cela tue l'idée principale d'une blockchain, c'est-à-dire la décentralisation. Que se passe-t-il si le serveur tombe en panne ou si quelqu'un le pirate ? Comme il y aurait un seul serveur hébergeant toutes les données. Nous avons donc besoin de quelque chose d'autre qui soit robuste aux attaques et également décentralisé et ce quelque chose est IPFS (Inter Planetary File System) qui est un système de stockage distribué. IPFS a plusieurs nœuds similaires à l'idée de blockchain qui stocke les données. Mais vous n'avez pas à payer de frais. Nous pouvons héberger nos images ainsi que le fichier JSON (URI) sur IPFS et cela donnera un CID unique (charabia aléatoire) qui pointe directement vers les données et nous attribuons un CID particulier à l'ID de jeton particulier. Nous parlerons plus en détail de la partie technique plus tard, d'abord comprenons l'interface pour la norme ERC-721. INTERFACE ERC-721 à quoi ressemble l'interface ERC-721. Voyons Cette norme a les fonctionnalités suivantes : Transfert de jetons d'un compte à un autre. Obtenir le solde actuel du jeton d'un compte. Obtenir le propriétaire d'un jeton spécifique. L'offre totale du jeton disponible sur le réseau. En plus de cela, il a également d'autres fonctionnalités comme approuver qu'un montant de jeton d'un compte peut être déplacé par un compte tiers. Examinons maintenant l'interface de l'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 Panne Renvoie le nombre de jetons dans le compte du propriétaire. Comment gardons-nous la trace des soldes? Bon, c'est toujours pareil, on définit un mapping qui suit le nombre total de tokens dans un wallet. mapping(address => uint) balances publiques ; N'oubliez pas qu'il résume simplement le nombre total de jetons et renvoie la même chose, ce mappage ne connaît pas les propriétaires du jeton car il indique simplement le nombre d'identifiants de jeton qu'une adresse possède. balanceOf function balanceOf(address _owner) external view returns (uint256); Trouve le propriétaire d'un NFT. Maintenant, cette fonction nous renseigne sur le propriétaire d'un NFT particulier. Nous conservons ces données comme ci-dessus. mappage( uint => adresse) soldes publics ; Les NFT attribués à zéro adresse sont considérés comme non valides et les requêtes à leur sujet doivent renvoyer une erreur. 2. ownerOf function ownerOf(uint256 _tokenId) external view returns (address); Transfère la propriété d'un NFT d'une adresse à une autre. Il doit simplement définir le propriétaire d'un tokenId sur la nouvelle adresse et également mettre à jour le mappage . Il devrait renvoyer une erreur dans les cas suivants : 3. safeTransferFrom balances est le propriétaire actuel, un opérateur autorisé ou l'adresse approuvée pour ce NFT msg.sender n'est pas le propriétaire actuel _from est l'adresse zéro. __to_ n'est pas un NFT valide __tokenId_ Lorsque le transfert est terminé, cette fonction vérifie si est un contrat intelligent (taille de code > 0). Si tel est le cas, il appelle la fonction sur et renvoie si la valeur de retour n'est pas égale à ceci : __to_ onERC721Received __to_ bytes4(keccak256("onERC721Received(address, address,uint256, bytes)")). Qu'est-ce que c'était? C'est pour cela que la fonction s'appelle transfert sécurisé, nous en reparlerons plus tard. function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable; : Cela fonctionne de manière identique à la fonction ci-dessus avec un paramètre de données supplémentaire, sauf que cette fonction définit simplement les données sur " ". 4. safeTransferFrom function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable; Comment ces deux fonctions ont-elles le même nom ? contrairement à Javascript, Solidity prend en charge la surcharge de fonctions. signifie que nous pouvons définir deux fonctions avec les mêmes noms, la seule condition est que les paramètres doivent différer, ce qui fait une différence dans la signature de la fonction. Vous ne savez pas ce que sont les signatures de fonction ? Voici. Fait amusant : Lien pour répondre transfère la propriété d'un NFT. Fonctionne de la même manière que la fonction , à la seule différence qu'il n'appelle pas l'appel de sécurité ( ) sur le destinataire. Ainsi, créant un risque de perdre le NFT. 5. transferFrom safeTransferFrom onERC721Received function transferFrom(address _from, address _to, uint256 _tokenId) external payable; le même concept que la fonction d'approbation ERC20 mais avec plus de fonctionnalité et de logique. Pour que cette fonction fonctionne, nous définissons un mappage imbriqué : Dans ce mappage, l'uint fait référence à l'ID de jeton et à l'adresse qui est approuvée pour effectuer des opérations sur ce tokenId particulier. Cette fonction d'approbation doit vérifier que est le propriétaire actuel ou un opérateur pour le propriétaire du jeton. Si cette vérification réussit, seul le mappage doit être mis à jour. Quel est l'opérateur ici ? Voir la fonction suivante. 6. approuver mapping(uint =>address) internal allowance; msg.sender function approve(address _approved, uint256 _tokenId) external payable; fonctionne comme la fonction d'approbation précédente, mais au lieu d'approuver une adresse pour un seul identifiant de jeton, cette fonction doit approuver et adresser pour gérer tous les jetons appartenant à une adresse particulière. Qu'est-ce que ça veut dire? Cela signifie que vous pouvez créer un opérateur d'adresse pour vos NFT. Cette adresse sera le propriétaire de vos NFT jusqu'à ce que vous révoquiez la propriété. Cette fois, nous utilisons à nouveau le mapping : La première adresse définit le booléen vrai ou faux pour que la deuxième adresse approuve ou révoque respectivement. 7. setApprovalForAll mapping(address => mapping(address=>bool))internal operator; function setApprovalForAll(address _operator, bool _approved) external; est similaire à la fonction d'allocation dans l'interface ERC-20. Renvoie l' approuvée pour un seul NFT. Il vérifie le mappage que nous avons mis à jour à la fonction d'approbation ci-dessus. 8. getApproved address function getApproved(uint256 _tokenId) external view returns (address); similaire à la fonction ci-dessus. Il demande si une est un autorisé pour une autre . Au lieu de renvoyer l' approuvée d'un particulier, cette fonction doit renvoyer l' de l' pour l' donnée que nous avons mise à jour à la fonction 9. isApprovedForAll address operator address address tokenId address operator address setApprovalForAll function isApprovedForAll(address _owner, address _operator) external view returns (bool); Événements pour ERC-721 émis lorsque la propriété d'un NFT change par n'importe quel mécanisme. Cet événement est émis lorsque des NFT sont créés et détruits : lors de la création du contrat, n'importe quel nombre de NFT peut être créé et attribué sans émettre de transfert. Au moment de tout transfert, l'adresse approuvée pour ce NFT (le cas échéant) est réinitialisée à aucune. Transfert : (from == 0) (to == 0). Exception event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId); est émise lorsque l'adresse approuvée pour un NFT est modifiée ou réaffirmée. L'adresse zéro indique qu'il n'y a pas d'adresse approuvée. Lorsqu'un événement de transfert est émis, cela indique également que l'adresse approuvée pour ce NFT (le cas échéant) est réinitialisée à aucune. L'approbation event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId); émet lorsqu'un opérateur est activé ou désactivé pour un propriétaire. L'opérateur peut gérer tous les NFT du propriétaire. ApproveForAll event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); C'était l'interface pour les contrats ERC 721. Apprenons maintenant plus sur ses implémentations et ses règles. Récepteur de jetons ERC721 Avant la mise en œuvre, il y a encore une chose. Rappelez-vous que nous avons parlé d'une fonction appelée lorsque nous avons parlé de transfert de jetons ? Parlons-en maintenant. Si le récepteur d'un jeton ERC-721 est un contrat, il doit être fonctionnel avec les jetons ERC-721. Car que se passe-t-il s'il n'y a pas de fonctionnalité pour interagir avec les jetons dans ce contrat de récepteur ? Les jetons seront enfermés dans ce contrat pour toujours. Personne ne peut sortir ces jetons à n'importe quelle adresse car il n'y a aucune fonctionnalité. Pour surmonter cette vulnérabilité, le récepteur du jeton ERC-721 doit implémenter l'interface du récepteur, sinon aucun des contrats ne peut envoyer de jeton ERC-721 à ce contrat. Regardons l'interface du récepteur. onERC721Received interface ne contient qu'une seule fonction à implémenter et c'est cette fonction doit gérer la réception d'un NFT. Le contrat intelligent ERC-721 appelle cette fonction sur le destinataire après un . Cette fonction PEUT lancer pour annuler et rejeter le transfert. Les retours autres que la valeur magique DOIVENT entraîner l'annulation de la transaction. Cette onERC721Received, transfer interface ERC721TokenReceiver { function onERC721Received(address _operator,address _from,uint256 _tokenId,bytes _data) external returns(bytes4); } Ici, vous pensez peut-être que comment le contrat de jeton saurait-il si le récepteur a implémenté l'interface ou non ? Donc, avant d'envoyer les jetons, vous devez d'abord vérifier cette partie. Pour cela, regardons l'implémentation d'ERC-721. Voici la fonction privée que vous devez appeler dans avant d'envoyer les jetons. Si cela renvoie true, seule la transaction doit avoir lieu. ERC721TokenReceiver openzeppelin safeTransfer() Eh bien, cela ne garantit pas la fonctionnalité de retrait des NFT du contrat du récepteur, car il ne vérifie que la mise en œuvre de la fonction du récepteur, mais pas la fonctionnalité de transfert des NFT hors du contrat. Quelle en est donc la signification ? Cette méthode peut nous dire que l'auteur du contrat du récepteur était au moins au courant de cette méthode, ce qui signifie qu'il doit avoir implémenté la fonctionnalité pour transférer les NFT hors du contrat. Et bien sûr, personne ne voudrait que ses jetons soient bloqués dans un contrat. et ERC721Métadonnées ERC721Enumerable Les interfaces ci-dessus étaient obligatoires pour un contrat de jeton ERC-721. Désormais, certaines interfaces sont théoriquement facultatives mais rendent le contrat plus clair et plus utilisable. Ce sont et Attrapons-les un par un. ERC721Metadata ERC721Enumerable. L'extension de métadonnées est utilisée pour interroger principalement le contrat pour le nom du jeton. Regardons maintenant l'interface des métadonnées. C'est assez simple à comprendre. Métadonnées 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); Cela donne juste les données sur les 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 Maintenant, comment saurions-nous si une interface a été implémentée par contrat ou non ? Pour cela, nous devons sortir du sujet, c'est-à-dire qui a une fonction : ERC-165, function supportsInterface(bytes4 interfaceID) external view returns (bool); Cette fonction prend l'interfaceId que vous voulez vérifier comme argument et le fait correspondre à l'interfaceId avec lequel vous voulez vérifier. Je vais vous dire ce qu'est un interfaceId, soyez patient. Regardez d'abord l'implémentation de cette fonction par openzeppelin. Comprenons plus sur interfaceId dans ce ; type(IERC721).interfaceId est réalisé par deux opérations principales. interfaceID 1. keccak 256 hachage 2. Opération XOR keccak256 est un algorithme qui prend une entrée et crache une chaîne aléatoire en octets. Découvrons maintenant le hachage keccak pour cette fonction. function supportsInterface(bytes4 interfaceID) external view returns (bool); La syntaxe ressemble à ceci. ; bytes4(keccak256('supportsInterface(bytes4)') Nous avons maintenant le hachage keccak pour cette fonction. N'oubliez pas que vous n'avez pas besoin de transmettre toute la fonction à l'intérieur du paramètre pour keccak256, mais uniquement la signature. La signature signifie uniquement le nom et le type de paramètre, même le nom du paramètre n'est pas inclus. Après avoir obtenu le hachage keccak256 de toutes les fonctions, nous effectuons l'opération XOR entre elles et le résultat que nous obtenons est l'interfaceId. Voyons comment. Les opérations prennent des entrées et donnent des sorties après les avoir comparées. Il renvoie si une, et une seule, des entrées est vraie. Si les deux entrées sont fausses ou si les deux sont vraies, la sortie est . XOR true false Vous n'avez pas besoin de comprendre les mathématiques derrière XOR. N'oubliez pas que vous prenez le hachage de chaque fonction et que vous le transmettez à la porte XOR et la valeur que vous obtenez est l'interfaceID. L'idée principale est donc de prendre le hachage keccak des fonctions et d'obtenir leur sortie XOR. cette sortie est l'interfaceId et après cela, vous pouvez vérifier pour un contrat s'il a le même interfaceID, si oui, cela signifie que le contrat implémente l'interface souhaitée. keccak256 Ne vous inquiétez pas, la solidité vous a facilité la tâche. Apprenons en prenant l'interface pour ERC721Metadata comme exemple. 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); } Ici, nous avons trois fonctions. J'ai donc écrit ce morceau de code pour vous faire comprendre. Remarquez ici le symbole caret . Ce symbole représente l'opération XOR entre deux valeurs. ^ // 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; } } Ohh, nous sommes allés trop loin dans l'ERC165 que nous avons oublié l'ERC721, revenons-y. Comment opensea (ou tout autre marché) récupère-t-il les données du contrat intelligent ? C'est le bon moment pour comprendre les URI de jetons qui aident les différents marchés NFT à identifier les données que vous avez attribuées avec un tokenId particulier. Comme Opensea est la place de marché la plus connue, nous la prendrons comme exemple. Opensea s'attend à ce que votre contrat ERC721 renvoie un tokenURI en appelant une fonction . Regardons le contrat openzeppelin pour cela. tokenURI() Les TokenURI peuvent être de deux types. 1. Même URI de base pour tous les jetons, variant selon le tokenId. De cette façon, nous attribuons un URI de base à tous les jetons, puis nous y concaténons le tokenId, en obtenant l'URI des métadonnées. Cela n'est possible que lorsque vous avez attribué votre collection de manière à ce que le lien soit le même pour chaque fichier JSON de métadonnées, la seule différence étant le tokenId. Par exemple, Ce lien pointe vers le dossier, ce qui signifie qu'il s'agit de l'URI de base. https://gateway.pinata.cloud/ipfs/QmYLwrqMmzC3k4eZu7qJ4MZJ4SNYMgqbRJFLkyiPtUBZUP/ Pour obtenir une seule métadonnée, nous devons aller sur https://gateway.pinata.cloud/ipfs/QmYLwrqMmzC3k4eZu7qJ4MZJ4SNYMgqbRJFLkyiPtUBZUP/1.json Et maintenant, vous devez renvoyer le tokenURI du contrat de manière à ce qu'il aille directement aux métadonnées de ce jeton particulier, et le marché peut le récupérer. Nous faisons cela en concaténant le baseURI avec le tokenId, et en le codant abi. Regardez cette fonction ci-dessous. function tokenURI(uint tokenId) override public view returns(string memory) { return (string(abi.encodePacked( "https://gateway.pinata.cloud/ipfs/QmYLwrqMmzC3k4eZu7qJ4MZJ4SNYMgqbRJFLkyiPtUBZUP/",Strings.toString(tokenId),".json")) ); } C'est la fonction que le marché appellera pour obtenir l'URI et afficher toutes les données que nous avons fournies dans le fichier JSON. 2. différents URI pour chaque jeton. C'est une autre façon d'attribuer des URI situés à différents endroits. Et le lien IPFS pour un fichier individuel ressemble à ceci. De cette façon, vous ne pouvez pas simplement concaténer le tokenId avec pour obtenir ce lien, mais ce lien doit être directement attaché au tokenId sur la chaîne. Et pour cela, regardez le contrat ci-dessus où nous définissons un mappage privé pour attribuer un URI à un tokenId particulier. Et plus tard, définir également une fonction qui remplit le mappage avec un URI donné. Et la fonction remplace la fonction parent en ajoutant une nouvelle fonctionnalité de retour de l'URI mappé. https://ipfs.filebase.io/ipfs/Qma65D75em77UTgP5TYXT4ZK5Scwv9ZaNyJPdX9DNCXRWc ERC721URIStorage tokenURI Une autre chose à noter est le format du schéma JSON. Le JSON doit contenir les données de manière standardisée afin que le marché puisse récupérer les données souhaitées et les afficher. Le schéma JSON standard pour ERC721. { "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." } } } Ce sont donc les choses que vous devez savoir si vous êtes intéressé par le développement de contrats intelligents ERC-721 (NFT). : Problèmes Il existe plusieurs problèmes d'évolutivité dans la norme ERC721. Le problème survient lors du transfert des jetons car l'ERC-721 vous permet de transférer un jeton à la fois. Cela signifie que si vous souhaitez envoyer 5 NFT à quelqu'un, vous devrez effectuer 5 transactions. C'est assez clair pour vous combien d'espace cela prendrait dans la blockchain. C'est la raison pour laquelle, dans les courses haussières, le réseau rencontre des problèmes, car il y a trop de précipitations dans le réseau et les frais de gaz augmentent rapidement. ERC-1155 résout ces problèmes. Comment? Parlons-en dans un article séparé. Si vous avez parfaitement compris la norme ERC721, ERC1155 ne sera pas très difficile à comprendre. Également publié . ici