NFT: Token não-fungível — Ethereum
O ano de 2017 foi o ano dos ICOs e por consequência houve uma explosão no número de novos tokens no mercado (a maioria deles construídos na plataforma Ethereum). Estes tokens (fungíveis), possuem características de criptomoedas.
Fungibilidade é o atributo pertencente aos bens móveis que podem ser substituídos por outros da mesma espécie, qualidade e quantidade. O dinheiro é o bem fungível por excelência, dado que quando se empresta uma quantia a alguém (por exemplo, R$100,00), não se está exigindo de volta aquelas mesmas cédulas, mas sim um valor, que pode ser pago com quaisquer notas de Real (moeda). — Wikipédia
Recentemente têm havido uma ascensão dos NFTs (ou Non-Fungible Tokens). Talvez o mais conhecido deles seja o CryptoKitties, trata-se de um jogo descentralizado aonde os participantes podem comprar, vender e colecionar gatos digitais. Este jogo tornou-se viral no final do ano de 2017 e foi responsável por sobrecarregar a blockchain do Ethereum.
NFTs são tokens que se caracterizam por serem distintos entre si. Se formos comparar com ativos financeiros, os NFTs seriam obras de arte ao invés de barras de ouro (fungíveis).
Caso de Uso — Rastreamento de Produtos
Em nosso exemplo, iremos implementar uma aplicação básica de supply chain. O objetivo é possibilitar o rastreio dos produtos ao longo da cadeia de logística.
O uso da tecnologia blockchain é interessante nesta área pois permite maior transparência e confiabilidade aos dados utilizados pelos vários atores (fabricantes, empresas de transporte, centros de distribuição e etc).
Smart Contracts
Primeiramente, criaremos um projeto truffle que conterá os contratos que permitirão a criação de produtos (NFTs) e transferência entre accounts Ethereum.
mkdir supply-chain-contracts
cd supply-chain-contracts
truffle init
npm init -y
npm install -E openzeppelin-solidity
Iremos criar agora os nossos contratos, começando pelo contracts/NFT.sol:
pragma solidity ^0.4.18;contract NFT {
event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);function balanceOf(address _owner) public view returns (uint256 _balance);
function ownerOf(uint256 _tokenId) public view returns (address _owner);
function transfer(address _to, uint256 _tokenId) public;}
Esta é a interface do nosso token. A principal diferença em relação a um token fungível (ERC20) é a existência de ID único para cada token.
contracts/ProductFactory.sol
pragma solidity ^0.4.18;contract ProductFactory {event NewProduct(uint productId, string name);struct Product {
string name;
}Product[] public products;
mapping (uint => address) public productToOwner;
mapping (address => uint) ownerProductCount;modifier onlyOwnerOf(uint _productId) {
require(msg.sender == productToOwner[_productId]);
_;
}function createProduct(string _name) public {
uint id = products.push(Product(_name)) — 1;
productToOwner[id] = msg.sender;
ownerProductCount[msg.sender]++;
emit NewProduct(id, _name);
}
}
Neste contrato há dois elementos da linguagem Solidity que ainda não foram abortados nos posts anteriores: struct, array e modifier.
Não há novidades quanto aos dois primeiros.
Os modificadores são utilizados para alterarmos o comportamento de funções. Este contrato está declarando o modificador onlyOwnerOf que verifica se o account que está interagindo com o contrato é o dono de um determinado produto. A função require lança exceção caso a condição não seja satisfeita.
A seguir veremos como o modificador é utilizado.
contracts/ProductOwnership.sol
pragma solidity ^0.4.19;import "./NFT.sol";
import "./ProductFactory.sol";contract ProductOwnership is ProductFactory, NFT {function balanceOf(address _owner) public view returns (uint256 _balance) {
return ownerProductCount[_owner];
}function ownerOf(uint256 _tokenId) public view returns (address _owner) {
return productToOwner[_tokenId];
}function _transfer(address _from, address _to, uint256 _tokenId) private {
ownerProductCount[_to] = ownerProductCount[_to] + 1;
ownerProductCount[msg.sender] = ownerProductCount[msg.sender] - 1;
productToOwner[_tokenId] = _to;
emit Transfer(_from, _to, _tokenId);
}function transfer(address _to, uint256 _tokenId) public onlyOwnerOf(_tokenId) {
_transfer(msg.sender, _to, _tokenId);
}
}
Podemos observar no contrato acima que a linguagem Solidity permite Herança Múltipla.
A função transfer só pode ser executada pelo dono do token (produto). A validação é feita pelo modificador onlyOwnerOf. O comando “_” indica a execução do corpo da função modificada (transfer). Logo, a função require será executada antes da função _transfer.
Estes três contratos são o suficiente para nosso exemplo. A interface NFT possui os métodos e eventos que devem ser implementados pelo nosso token, o ProductFactory possui a estrutura de dados e a função responsável pela criação de novos produtos e o contrato ProductOwnership implementa os métodos da interface NFT.
ERC721
Com a popularização de aplicações utilizando NFT (em sua maioria jogos) em set/2017 propuseram a criação de um padrão para este tipo de token. Assim como o ERC20, ele foi bem aceito pelos desenvolvedores e vem sendo utilizado em muitos projetos. Inclusive o OpenZeppelin já o possui em sua biblioteca. Alguns dos métodos utilizados em nosso exemplo são os mesmos definidos pelo ERC721.
Conclusão
Além dos tokens fungíveis (criptos) vê-se a criação de muitos projetos que fazem uso de tokens não-fungíveis. Vimos neste post a diferença entre eles e como implementar nosso próprio NFT. Num próximo post iremos interagir com nossos produtos através de interface web (javascript). Até breve!