O que são tokens Soulbound (SBT)?
Os tokens soulbound são essencialmente tokens que estão associados à sua conta, ou seja, são tokens não transferíveis. A única coisa que você pode fazer é queimar o token ou revogá-lo, ou seja, enviá-lo de volta para o remetente.
A lógica por trás dos tokens soulbound tem origem no popular jogo online World of Warcraft. Os jogadores não podem vender ou transferir itens soulbound. Uma vez adquiridos, esses itens estão para sempre "vinculados" à "alma" do jogador.
O objetivo do token soulbound é transformar os NFTs em algo além de simples imagens que podem ser compradas e vendidas, gerar dinheiro ou mostrar status. Ele representa um token que é único e também não transferível.
Embora não tenha valor monetário real, o SBT representa a reputação de uma pessoa ou entidade.
Qual é a utilidade dos tokens soulbound (SBTs)?
Existem várias aplicações para os tokens soulbound, alguns exemplos que podem ser implementados na vida cotidiana incluem:
- Prova de trabalho - Você recebe certificados ao concluir um curso ou obter um diploma.
- Prova de identificação - Licenças ou documentos KYC específicos do usuário.
- Associação exclusiva.
- Verificação de crédito.
- Prova de presença - Os POAPs são o melhor exemplo.
Mostrei apenas a ponta do iceberg, mas as aplicações são infinitas. Se você quiser entender mais, aqui está um excelente blog do próprio rei, Vitalik Buterin.
A única diferença entre nossos NFTs tradicionais e os SBTs é que os SBTs não são transferíveis, pois o objetivo dos SBTs é criar a identidade digital de um indivíduo, para que ela possa ser verificada na cadeia (blockchain), já que os dados na cadeia não podem ser alterados e, também porque não têm valor monetário, já que não são transferíveis e, portanto, não podem ser negociados.
O que vamos desenvolver hoje?
Hoje, vamos escrever e implantar um contrato inteligente NFT compatível com a EIP4973, ou seja, um token vinculado à conta/soulbound, e como proprietários, poderemos criar e enviar os tokens para pessoas elegíveis, para que elas possam visualizá-los no Opensea ou em qualquer outro mercado.
Para isso, primeiro criaremos um contrato inteligente NFT ERC721 e depois o modificaremos ao longo do caminho para torná-lo compatível com a EIP4973.
Vamos começar.
Pré-requisitos
- Conhecimentos básicos de Solidity
- Compreensão básica de NFTs
- Curiosidade
Escrevendo um contrato básico ERC721
Antes de criar nosso token Soulbound, precisaremos de um contrato que servirá como nosso contrato base. Vamos modificar este contrato para transformá-lo em nosso contrato inteligente NFT vinculado à conta/Soulbound. Não se preocupe se você não souber como escrever contratos inteligentes; já temos projetos detalhados sobre contratos inteligentes NFT, como: Iniciando no desenvolvimento de NFT (em inglês) e Como criar um contrato NFT com metadados on-chain (em inglês). Estes podem ser um bom ponto de partida se você estiver procurando entender mais sobre contratos inteligentes NFT. Além disso, fique ligado, pois vamos lançar muitos projetos em breve.
Vamos acessar o OpenZeppelin Wizard e obter um modelo de contrato:
- Aqui, primeiro adicionamos o nome (name) e o símbolo (symbol) do contrato. Eles serão usados como identificadores no Etherscan e nos mercados de NFT.
- Em seguida, temos o URI base. Para entender como fazer upload de arquivos no IPFS e obter o hash deles, você pode conferir nosso Tutorial de NFT de Música, onde explicamos isso em detalhes. Por enquanto, você pode usar meu link IPFS hospedado: ipfs://bafkreidwx6v4vb4tkhhcjdjnmpndxqqcxp6bnowtkzehmncpknx3x2rfhi/, mas lembre-se de que este é um link de referência para nossos NFTs; ele contém os metadados de nossos NFTs e parece algo assim quando aberto com um gateway dedicado - link.
Em seguida, temos os recursos, e vamos selecionar "Mintable", que permitirá aos usuários cunhar tokens, e "Auto-increment Ids", que será usado para incrementar os tokenIds dos NFTs toda vez que um novo for criado.
Agora vamos abrir isso no Remix, que é um ambiente de desenvolvimento online para compilar e implantar contratos inteligentes.
Vamos fazer algumas alterações no contrato; o contrato final deve se parecer com algo assim:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/[email protected]/token/ERC721/ERC721.sol";
import "@openzeppelin/[email protected]/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/[email protected]/access/Ownable.sol";
import "@openzeppelin/[email protected]/utils/Counters.sol";
contract BlocktrainSoulbound is ERC721, ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
constructor() ERC721("BlocktrainSoulbound", "BST") {}
function _baseURI() internal pure override returns (string memory) {
return "ipfs://bafkreidwx6v4vb4tkhhcjdjnmpndxqqcxp6bnowtkzehmncpknx3x2rfhi";
}
function safeMint(address to) public onlyOwner {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
}
// As seguintes funções são substituições necessárias no Solidity.
function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}
function tokenURI(uint256)
public
pure
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return _baseURI();
}
}
Aqui, acabamos de alterar as funções safeMint() e tokenURI().
- Na safeMint, removemos o URI dos argumentos da função e excluímos o _setTokenURI.
- Na tokenURI, em vez de anexar o tokenId ao final do URI, queremos os mesmos metadados de NFT para todos os NFTs na coleção. Como isso é apenas para fins de demonstração, retornamos apenas o _baseURI(), que retorna o URI.
Agora, compile seu contrato no Remix e implante-o lá mesmo para testar se funciona:
Você precisa pressionar Ctrl+S para compilar seu contrato.
Depois de implantado, teste usando todas as funções disponíveis:
Compreendendo a EIP4973
De acordo com a Interface ERC4973, existem 5 coisas necessárias para criar um token soulbound/conta.
- Evento Attest- Esse evento é emitido sempre que um novo token é emitido e enviado ou vinculado a uma conta.
- Evento Revoke - Revoke é emitido sempre que um usuário devolve o token ao proprietário ou o queima.
- As funções balanceOf, ownerOf e burn já estão disponíveis na ERC721.
Para o padrão de metadados, seguiremos o padrão de metadados ERC721 conforme mencionado na EIP.
Você pode ler a proposta oficial da EIP4973, lembre-se de que ainda está em fase de revisão e pode mudar no futuro, mas os conceitos fundamentais não mudarão.
Modificar contrato para torná-lo compatível com a EIP4973
Agora, o primeiro passo que precisamos fazer é tornar nosso contrato base ERC721 não transferível. Para isso, o que precisamos fazer é substituir duas funções: _beforeTokenTransfer
e _afterTokenTransfer
, que estão presentes no contrato ERC721 e podem ser encontradas nos contratos do OpenZepplin.
Essas funções atuam como um gancho, que é executado antes ou depois da transferência.
Apenas copie estas duas funções e cole-as em nosso contrato no Remix, algo assim:
....
function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal override virtual {}
function _afterTokenTransfer(
address from,
address to,
uint256 firstTokenId
) internal override virtual {}
Lembre-se de adicionar override
para que ela substitua a função presente no contrato base, que é a ERC721.
Agora, para a função _beforeTokenTransfer
, precisamos verificar se o usuário está recebendo o token ou queimando o token para prosseguir. Isso pode ser feito verificando os endereços from
e to
. Se o endereço from
for address(0)
, significa que o usuário está recebendo o token, e se o to
for address(0)
, significa que o usuário está queimando o token. Precisamos adicionar uma declaração require
para isso:
function _beforeTokenTransfer(
address from,
address to,
uint256 /* TokenId */
) internal override virtual {
require(from == address(0) || to == address(0), "Você não pode transferir este token");
}
Agora, a função _afterTokenTransfer
será chamada apenas quando o token for emitido ou queimado, então precisamos chamar os dois eventos mencionados na EIP4973, Attest e Revoke, de acordo com a transferência.
function _afterTokenTransfer(
address from,
address to,
uint256 firstTokenId
) internal override virtual {
if(from == address(0)) {
emit Attest(to, firstTokenId);
} else if (to == address(0)) {
emit Revoke(from, firstTokenId);
}
}
Attest será emitido quando o endereço from
for address(0)
, pois significa que o token está sendo emitido, enquanto Revoke será emitido quando o endereço to
for address(0)
, o que significa que o token está sendo queimado.
Não temos esses dois eventos inicializados ou herdados, portanto, vamos pegá-los da EIP e colá-los em nosso código na parte superior, acima do constructor (construtor).
event Attest(address indexed to, uint256 indexed tokenId);
event Revoke(address indexed to, uint256 indexed tokenId);
Agora, temos nosso contrato inteligente de NFT não transferível pronto, mas ainda há algumas modificações a serem feitas.
Primeiro, temos a função _burn
herdada da ERC721. É uma função interna, mas de acordo com a EIP4973, precisamos de uma função burn
externa e apenas o proprietário do tokenId
pode chamar a função burn
:
function burn(uint256 tokenId) external {
require(ownerOf(tokenId) == msg.sender, "Você não é o proprietário do tokenId");
_burn(tokenId);
}
A última e definitiva etapa que resta é permitir que o emissor ou criador do NFT recupere/queime o token Soulbound.
function revoke(uint256 tokenId) external onlyOwner {
_burn(tokenId);
}
Esta é uma função onlyOwner
que permite ao criador do contrato queimar qualquer token que deseje.
Voilá, concluímos nosso contrato de NFT do token Soulbound, que garante que os tokens não sejam transferíveis e o usuário só possa queimar o token.
O contrato completo deve se parecer com algo assim:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract BlocktrainSoulbound is ERC721, ERC721URIStorage, Ownable {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
event Attest(address indexed to, uint256 indexed tokenId);
event Revoke(address indexed to, uint256 indexed tokenId);
constructor() ERC721("BlocktrainSoulbound", "BST") {}
function _baseURI() internal pure override returns (string memory) {
return "ipfs://bafkreidwx6v4vb4tkhhcjdjnmpndxqqcxp6bnowtkzehmncpknx3x2rfhi";
}
function safeMint(address to) public onlyOwner {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
}
// As seguintes funções são substituições necessárias em Solidity.
function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) {
super._burn(tokenId);
}
function burn(uint256 tokenId) external {
require(ownerOf(tokenId) == msg.sender, "Você não é o proprietário do tokenId");
_burn(tokenId);
}
function revoke(uint256 tokenId) external onlyOwner {
_burn(tokenId);
}
function tokenURI(uint256)
public
pure
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return _baseURI();
}
function _beforeTokenTransfer(
address from,
address to,
uint256 /* TokenId */
) internal override virtual {
require(from == address(0) || to == address(0), "Você não pode transferir este token");
}
function _afterTokenTransfer(
address from,
address to,
uint256 firstTokenId
) internal override virtual {
if(from == address(0)) {
emit Attest(to, firstTokenId);
} else if (to == address(0)) {
emit Revoke(to, firstTokenId);
}
}
}
Agora é hora de implantá-lo na rede de testes Goerli e testá-lo nós mesmos.
Implantar nosso contrato na rede de testes Goerli
Para implantar o contrato na rede de testes Goerli, primeiro precisaremos de algum ether de teste; vamos lá e conseguir isso na goerlifaucet, que é administrada pela Alchemy.
Basta ir para goerlifaucet.com, adicionar seu endereço lá e clicar em enviar fundos.
Assim que a transação for concluída, você deverá ver algum ether de teste em sua carteira Metamask quando mudar a rede para Goerli. Se você não encontrar Goerli em sua Metamask, uma rápida pesquisa no Google pode ajudar:)
Quando tivermos fundos em nossa carteira, vamos voltar ao Remix e implantar o contrato. Clique na última guia à esquerda e mude a rede para Injected Provider - Metamask no menu suspenso. Deve abrir uma janela para conectar sua carteira; basta clicar em “confirm” (confirmar) e “connect” (conectar).
Depois de conectar sua carteira, certifique-se de que está na rede de testes Goerli e clique em Deploy (implantar). Assim que a transação for concluída, você deverá ver seus contratos na parte inferior esquerda, onde está escrito deployed contracts (contratos implantados).
Apenas copie o endereço clicando no botão ao lado do nome do contrato e cole-o em goerli.etherscan.io; você deverá ver algo parecido com isso:
Cunhar NFTs pelo Remix
Agora, a primeira coisa que vou testar é tentar cunhar os NFTs de uma conta Metamask diferente. Para fazer isso, basta trocar a carteira na Metamask e clicar em mint function.
Você receberá este aviso do Remix, que diz que o chamador não é o proprietário, o que significa que apenas o proprietário do contrato pode cunhar os NFTs.
Agora, vamos tentar cunhar um NFT da conta original para minha conta secundária; abrirá a Metamask para confirmar a transação, pois custará algum gás para cunhar.
Agora que temos nosso NFT cunhado, vamos dar uma olhada nele no Opensea.
Confira nossos tokens no Opensea
Agora copie o endereço do seu contrato do Remix, que usamos anteriormente para verificar no Goerli Etherscan, e cole-o em testnets.opensea.io.
É assim que meu contrato está parecendo:
E é assim que o NFT individual está parecendo:
Agora vamos testá-lo. Vou fazer login no Opensea com a carteira que possui este NFT e tentar transferi-lo para outra carteira. Vamos ver o que acontece.
Clique no ícone do avião no canto superior direito após fazer login com a carteira que possui o NFT.
Ao clicar nele, o Opensea o levará para esta página.
Adicione o endereço da carteira de uma de suas contas extras ou algum endereço aleatório de 40 caracteres começando com 0x e clique no botão Transfer (Transferir).
Haha, a transação foi revertida, isso significa que você não pode transferir o NFT para esse endereço aleatório.
Queime os tokens
Para permitir que o usuário queime o token, você pode enviar o token para o address(0) ou criar um botão em seu site de cunhagem que aciona a função de queima para o usuário.
Por enquanto, vamos queimar os tokens usando o Remix. Basta mudar sua carteira na Metamask para aquela que possui o NFT e ir para o Remix. Adicione o ID do token no argumento da função de queima e pressione queimar. Ele solicitará uma transação que envolve pequenas taxas de gás que você precisa pagar para queimar o token.
Feito, você queimou com sucesso o token, ou seja, agora ele pertence ao address(0). Você pode verificar isso no Opensea ou Etherscan. Vamos dar uma olhada no Opensea.
Se você rolar um pouco na página individual do NFT, encontrará a atividade do item (Item Activity). Você verá que o NFT foi enviado do meu endereço para o address(0). Verifique o meu aqui - Link
Tudo pronto!
Ufa, foi muita coisa. Dê um tapinha nas costas se você chegou até aqui.
Espero que você tenha aprendido algo novo e interessante neste tutorial, pois não vai demorar muito para que tokens soulbound/conta se tornem populares e as pessoas comecem a usá-los no dia a dia. Então, certifique-se de entrar na tendência antes de todo mundo e construa coisas incríveis.
Fique ligado, pois vamos lançar mais tutoriais incríveis com níveis intermediários e avançados.
Artigo escrito por dhanush. Traduzido por Marcelo Panegali.
Latest comments (0)