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 Cryptokitties . C'est un jeu au-dessus de la blockchain, découvrez ce jeu incroyable pour apprendre avec plaisir.
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, tokenId
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.
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 tokenid
. Par exemple, mon adresse a le solde de tokenid
4, maintenant l'image ou tout type de données attachées à ce tokenid
sera à moi. Je possède ces données car je possède le 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.
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.
Voyons à quoi ressemble l'interface ERC-721.
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
function balanceOf(address _owner) external view returns (uint256);
2. ownerOf 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.
function ownerOf(uint256 _tokenId) external view returns (address);
3. safeTransferFrom 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 balances
. Il devrait renvoyer une erreur dans les cas suivants :
msg.sender
est le propriétaire actuel, un opérateur autorisé ou l'adresse approuvée pour ce NFT_from
n'est pas le propriétaire actuel__to_
est l'adresse zéro.__tokenId_
n'est pas un NFT valide
Lorsque le transfert est terminé, cette fonction vérifie si __to_
est un contrat intelligent (taille de code > 0). Si tel est le cas, il appelle la fonction onERC721Received
sur __to_
et renvoie si la valeur de retour n'est pas égale à ceci : 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;
4. safeTransferFrom : 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 " ".
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
Comment ces deux fonctions ont-elles le même nom ?
Fait amusant : 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. Lien pour répondre
5. transferFrom transfère la propriété d'un NFT. Fonctionne de la même manière que la fonction safeTransferFrom
, à la seule différence qu'il n'appelle pas l'appel de sécurité ( onERC721Received
) sur le destinataire. Ainsi, créant un risque de perdre le NFT.
function transferFrom(address _from, address _to, uint256 _tokenId) external payable;
6. approuver 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é : mapping(uint =>address) internal allowance;
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 msg.sender
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.
function approve(address _approved, uint256 _tokenId) external payable;
7. setApprovalForAll 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 : mapping(address => mapping(address=>bool))internal operator;
La première adresse définit le booléen vrai ou faux pour que la deuxième adresse approuve ou révoque respectivement.
function setApprovalForAll(address _operator, bool _approved) external;
8. getApproved est similaire à la fonction d'allocation dans l'interface ERC-20. Renvoie l' address
approuvée pour un seul NFT. Il vérifie le mappage que nous avons mis à jour à la fonction d'approbation ci-dessus.
function getApproved(uint256 _tokenId) external view returns (address);
9. isApprovedForAll similaire à la fonction ci-dessus. Il demande si une address
est un operator
autorisé pour une autre address
. Au lieu de renvoyer l' address
approuvée d'un tokenId
particulier, cette fonction doit renvoyer l' address
de l' operator
pour l' address
donnée que nous avons mise à jour à la fonction setApprovalForAll
function isApprovedForAll(address _owner, address _operator) external view returns (bool);
Transfert : é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 (from == 0)
et détruits (to == 0).
Exception : 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.
event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
L'approbation 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.
event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);
ApproveForAll é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.
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.
Avant la mise en œuvre, il y a encore une chose. Rappelez-vous que nous avons parlé d'une fonction appelée onERC721Received
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.
Cette interface ne contient qu'une seule fonction à implémenter et c'est onERC721Received, 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 transfer
. 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.
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 ERC721TokenReceiver
ou non ? Donc, avant d'envoyer les jetons, vous devez d'abord vérifier cette partie. Pour cela, regardons l'implémentation openzeppelin d'ERC-721. Voici la fonction privée que vous devez appeler dans safeTransfer()
avant d'envoyer les jetons. Si cela renvoie true, seule la transaction doit avoir lieu.
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.
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 ERC721Metadata et ERC721Enumerable. Attrapons-les un par un.
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);
ERC721Enumerable- Cela donne juste les données sur les NFT.
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);
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 ERC-165, qui a une fonction :
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
;
interfaceID est réalisé par deux opérations principales.
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 XOR prennent des entrées et donnent des sorties après les avoir comparées. Il renvoie true
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 false
.
Vous n'avez pas besoin de comprendre les mathématiques derrière XOR. N'oubliez pas que vous prenez le hachage keccak256
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.
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.
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 tokenURI()
. Regardons le contrat openzeppelin pour cela.
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,
https://gateway.pinata.cloud/ipfs/QmYLwrqMmzC3k4eZu7qJ4MZJ4SNYMgqbRJFLkyiPtUBZUP/ Ce lien pointe vers le dossier, ce qui signifie qu'il s'agit de l'URI de base.
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 https://ipfs.filebase.io/ipfs/Qma65D75em77UTgP5TYXT4ZK5Scwv9ZaNyJPdX9DNCXRWc
pour obtenir ce lien, mais ce lien doit être directement attaché au tokenId sur la chaîne. Et pour cela, regardez le contrat ERC721URIStorage
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 tokenURI
remplace la fonction parent en ajoutant une nouvelle fonctionnalité de retour de l'URI mappé.
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 .