Criando nosso primeiro Token ERC721

porMatheusem05/06/2022

Nesse artigo iremos aprender a criar nosso primeiro Token ERC721 e uma aplicação que irá interagir com nosso token.

Token ERC721

Qualquer contrato que siga o padrão ERC721 é considerado um token ERC721.

Os tokens ERC721 fornecem funcionalidades para

  • transferir tokens
  • permitir que outros transfiram tokens em nome do titular do token

Um exemplo de interface para o token ERC721

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
interface IERC165 {
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
interface IERC721 is IERC165 {
function balanceOf(address owner) external view returns (uint balance);
function ownerOf(uint tokenId) external view returns (address owner);
function safeTransferFrom(
address from,
address to,
uint tokenId
) external;
function safeTransferFrom(
address from,
address to,
uint tokenId,
bytes calldata data
) external;
function transferFrom(
address from,
address to,
uint tokenId
) external;
function approve(address to, uint tokenId) external;
function getApproved(uint tokenId) external view returns (address operator);
function setApprovalForAll(address operator, bool _approved) external;
function isApprovedForAll(address owner, address operator)
external
view
returns (bool);
}
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint tokenId,
bytes calldata data
) external returns (bytes4);
}
contract ERC721 is IERC721 {
event Transfer(address indexed from, address indexed to, uint indexed id);
event Approval(address indexed owner, address indexed spender, uint indexed id);
event ApprovalForAll(
address indexed owner,
address indexed operator,
bool approved
);
// Mapping from token ID to owner address
mapping(uint => address) internal _ownerOf;
// Mapping owner address to token count
mapping(address => uint) internal _balanceOf;
// Mapping from token ID to approved address
mapping(uint => address) internal _approvals;
// Mapping from owner to operator approvals
mapping(address => mapping(address => bool)) public isApprovedForAll;
function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}
function ownerOf(uint id) external view returns (address owner) {
owner = _ownerOf[id];
require(owner != address(0), "token não existe");
}
function balanceOf(address owner) external view returns (uint) {
require(owner != address(0), "owner = endereço 0");
return _balanceOf[owner];
}
function setApprovalForAll(address operator, bool approved) external {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
function approve(address spender, uint id) external {
address owner = _ownerOf[id];
require(
msg.sender == owner || isApprovedForAll[owner][msg.sender],
"não autorizado"
);
_approvals[id] = spender;
emit Approval(owner, spender, id);
}
function getApproved(uint id) external view returns (address) {
require(_ownerOf[id] != address(0), "token não existe");
return _approvals[id];
}
function _isApprovedOrOwner(
address owner,
address spender,
uint id
) internal view returns (bool) {
return (spender == owner ||
isApprovedForAll[owner][spender] ||
spender == _approvals[id]);
}
function transferFrom(
address from,
address to,
uint id
) public {
require(from == _ownerOf[id], "from != owner");
require(to != address(0), "transferência para endereço 0");
require(_isApprovedOrOwner(from, msg.sender, id), "não autorizado");
_balanceOf[from]--;
_balanceOf[to]++;
_ownerOf[id] = to;
delete _approvals[id];
emit Transfer(from, to, id);
}
function safeTransferFrom(
address from,
address to,
uint id
) external {
transferFrom(from, to, id);
require(
to.code.length == 0 ||
IERC721Receiver(to).onERC721Received(msg.sender, from, id, "") ==
IERC721Receiver.onERC721Received.selector,
"destinatário inseguro"
);
}
function safeTransferFrom(
address from,
address to,
uint id,
bytes calldata data
) external {
transferFrom(from, to, id);
require(
to.code.length == 0 ||
IERC721Receiver(to).onERC721Received(msg.sender, from, id, data) ==
IERC721Receiver.onERC721Received.selector,
"destinatário inseguro"
);
}
function _mint(address to, uint id) internal {
require(to != address(0), "mint para endereço 0");
require(_ownerOf[id] == address(0), "já cunhado");
_balanceOf[to]++;
_ownerOf[id] = to;
emit Transfer(address(0), to, id);
}
function _burn(uint id) internal {
address owner = _ownerOf[id];
require(owner != address(0), "não cunhado");
_balanceOf[owner] -= 1;
delete _ownerOf[id];
delete _approvals[id];
emit Transfer(owner, address(0), id);
}
}

Criando seu próprio token ERC721

Para facilitar e padronizar a criação de tokens ERC721, você pode utilizar a ferramenta OpenZeppelin, repositório do OpenZeppelin no GitHub com vários exemplos de contratos ERC721 e ERC20. Você pode ler a documentação completa sobre tokens ERC721 aqui.

Através do OpenZeppelin, você cria seu token de forma automatizada passando apenas alguns parâmetros como

  • name - nome do token
  • symbol - símbolo do seu token
  • base uri - url base onde seus arquivos IPFS serão armazenados

Entre outras opções, acesse o site da OpenZeppelin e confira a ferramenta completa.

Abaixo, um simples exemplo de token ERC721

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "./IERC721.sol";
contract MyNFT is ERC721 {
constructor() ERC721("Token PRATICA", "PRATICA") {}
// Substitua base_uri pela url base de onde os dados
// do seu token ERC721 serão armazenados.
// Geralmente é utilizando o site do IPFS: https://ipfs.io/
function _baseURI() internal pure override returns (string memory) {
return "base_uri";
}
function mint(address to, uint id) external {
_mint(to, id);
}
function burn(uint id) external {
require(msg.sender == _ownerOf[id], "não é o proprietário");
_burn(id);
}
}

O token ERC721 é muito utilizando em jogos NFT estilo SunFlower Land, Axie Infinity, Era7, BombCrypto, MineraLand, Monopolio, Light Nite, The Crypto You, DeepSpace entre diversos outros jogos NFT.

Também é muito utilizado na criação de coleções NFT, gerando uma lista de tokens numerados dentro do contrato, fazendo com que assim cada token gerado identificado como
token original através do contrato que o crio, podendo também ser transferido para outro endereço de carteira através das funções do contrato, Dapp's e Marketplaces.

Alguns dos Marketplaces disponíveis no mercado hoje são: OpenSea, Binance Marketplace, Coinbase, Mintable, Enjin, Rarible, Nifty Gateway, Crypto.


Testar no Remix