Web 3.0 tem o potencial de mudar para sempre a internet que conhecemos.
— Adrian ( JavaScript Mastery )
Neste artigo, eu explicarei Solidity e como se pode criar um mercado de NFT com apenas 200 linhas de código.
Este, no entanto, é um contrato inteligente. Um contrato inteligente poderia ser definido como uma entidade que pode enviar e receber moeda (cripto) além de ser apenas usuários na web. É um código que gerencia seu dinheiro e como você o direciona para executar certas funções - logo, programas na blockchain!
Estes programas, uma vez implantados, não podem ser modificados e são finais. Não vamos nos aprofundar nos muitos aspectos benéficos dos contratos inteligentes, pois são muitos e o tempo é relativo.
Sumário
1 . Começando com a demo da aplicação
2 . Código Solidity
3 . Links
Começando com a demo da aplicação
- Conecte-se com nossa carteira
metamask
( você pode continuar lendo esse artigo mesmo que você não tenha uma conta metamask ou não tenha ideia do que seja isso :|, apenas assuma que é uma carteira online )
- Crie uma NFT
Carregue sua imagem e preencha os detalhes.
Você enviará 0.00025 ether ao proprietário do mercado como taxa de listagem. A imagem acima é a aprovação da transação. Pense nela como digitando seu código PIN ou OTP para uma transação on-line.
- Vamos comprar essa NFT que você listou. Agora vamos mudar nossa conta na metamask, porque vamos ser sinceros aqui, não podemos comprar nosso próprio NFT.
- Compre o NFT.
Agora que a demonstração terminou, podemos começar com o código Solidity.
Código Solidity
Para iniciantes, se você sabe JavaScript, então você se sentirá confortável em compreender o código de Solidity. Contratos inteligentes para a Ethereum são programados em Solidity, que é uma linguagem de alto nível, orientada ao objeto.
Vamos passar por este código linha por linha:
Linha 2 — É obrigatório mencionar a versão de Solidity antes de escrever nosso contrato. Aqui estaremos trabalhando com a versão 0.8.4.
Linhas 4,5,6 — Como qualquer outra linguagem, aqui estamos importando outros contratos que estamos usando para construir sobre nosso código, pense neles como bibliotecas.
Counters.sol
: Para manter um contador de - ( será discutido mais adiante ).
ERC721.sol
: Padrão para escrever contratos relevantes para as NFTs.
ERC721URIStorage.sol
: Para herdar _tokenURI
, deve deliberar um documento JSON
Linha 8 — Definindo um contrato NFTMarketplace
que herda ERC721URIStorage
( outro contrato ), nós não herdamos ERC721.sol já que ERC721URIStorage
já a herda. Esses contratos são feitos por OpenZepplin.
using Counters for Counters.Counter;
Inicializando os Counters que herdamos.
Counters.Counter private _tokenIds;
Counters.Counter private _itemsSold;
Aqui o contrato mantém a contagem de duas variáveis — _tokenIds e _itemsSold. Sempre que chamamos _tokenIds.increment();
o valor dos _tokenIds
será incrementado de, digamos, 1 → 2, da mesma forma para _itemsSold
.
uint256 listingPrice = 0.00025 ether;
Especificando o preço de listagem, sempre que alguém quiser criar seu NFT e listá-lo no mercado, pagará a taxa de listagem, ou seja, 0,00025 ETH. Aqui uint256
é um tipo de dado. u → não assinado, int → número inteiro com base 256.
address payable owner;
A variável do tipo endereço (só recebe endereços) chamou o proprietário. Aqui é definida como payable
, significando que esse endereço pode envolver transações.
constructor() ERC721("Metaverse Tokens", "METT") {
owner = payable(msg.sender);
}
Sempre que nosso contrato é chamado, um constructor é invocado. Em nosso caso, precisamos invocar 2 constructors - nossos contratos e o contrato ERC721.sol, já que estamos herdando dele.
Neste constructor, estamos dizendo ao contrato que o proprietário é a pessoa que está chamando o contrato, mas na blockchain, transmitimos isto por endereço, obtendo assim o endereço do remetente. msg.sender
é um argumento incorporado que recebe o endereço da conta que está invocando/chamando o contrato.
Resumindo, estamos informando ao contrato que nós ( msg.sender) somos o proprietário, definindo a variável de proprietário! Quando implantamos nosso contrato, ele recebe nosso endereço, de modo que somente nós temos a autoridade de propriedade.
ufa...
struct MarketItem {
uint256 tokenId;
address payable seller;
address payable owner;
uint256 price;
bool sold;
}
Criando uma estrutura Market Item para armazenar vários tipos de dados.
-
tokenID
→ Id do token envolvido na venda -
seller
→ para armazenar o endereço do vendedor -
owner
→ para armazenar o endereço do proprietário (Se um NFT é criado, inicialmente ele pertence ao mercado, ou seja, endereço do Contrato) -
price
→ preço do NFT -
sold
→ valor booleano (verdadeiro/falso ) para mencionar se o NFT está vendido ou não.
mapping(uint256 => MarketItem) private idToMarketItem;
mapping
— outro tipo de dados da Solidity. Ele armazena uma chave (um tipo de dados) e seu valor correspondente também um tipo de dados), portanto — Key — pares de valores.
mapping ( <data-type> => <data-type/structure>/ ) visibility name;
Nesse caso, estamos mapeando um tipo de dados uint256
para o struct MarketItem.
Aqui, a visibilidade é privada, ou seja, ela só pode ser chamada de dentro do contrato e não de fora.
function updateListingPrice(uint _listingPrice) public payable {
require(owner == msg.sender);
listingPrice = _listingPrice;
}
function getListingPrice() public view returns (uint256) {
return listingPrice;
}
Muito bem, vamos melhorar o ritmo agora.
-
updateListingPrice()
— Recebe um uint →_listingPrice
como um argumento e atualiza o preço atual da listagem.getListingPrice()
— Uma função de retorno que retorna o preço atual da listagem do contrato.
Aqui, require é uma condição que é executada antes que o código sob ela seja executado. Portanto, em teoria, se alguém além do proprietário chama as funções acima, simplesmente não funciona.
Fazendo um desvio agora...
Você deve se perguntar o que são privados, públicos, etc.
- público, privado, interno, externo → Estes são apenas tipos de visibilidade que são dados a cada função/variável informando ao contrato quem pode chamar estas funções/variáveis e de onde.
-
public
→ Pode ser chamada por qualquer pessoa. -
private
→ Pode ser chamado somente de dentro do contrato e não por qualquer outra pessoa fora dele. -
external
→ Pode ser chamado por contratos que herdam a função com visibilidade externa. -
internal (default )
→ Só podem ser acessíveis dentro do contrato em que foram declarados e também a partir de contratos derivados.
Prosseguindo com o contrato…
function createToken(string memory tokenURI, uint256 price) public payable returns (uint) {
_tokenIds.increment();
uint256 newTokenId = _tokenIds.current();
_mint(msg.sender, newTokenId);
_setTokenURI(newTokenId, tokenURI);
createMarketItem(newTokenId, price);
return newTokenId;
}
Como essa função é chamada em JavaScript:
createToken(url, price, { value: listingPrice.toString() })
-
_tokenIds.increment()
— aumenta o valor de_tokenIds
no contrato por 1. -
uint256 newTokenId = _tokenIds.current()
— armazena o valor atual de_tokenIds
do contrato para a variávelnewTokenId
. -
_mint(msg.sender, newTokenId)
— Esta função_mint()
é herdada do contrato ERC721.sol. É usada para cunhar um novo token para o contrato e na blockchain.
_setTokenURI(newTokenId, tokenURI)
— Esta função _setTokenURI()
também é herdada do contrato ERC721URIStorage.sol. É usada para definir um token URI e verificar se o token já existe.
createMarketItem(newTokenId, price)
— Esta é a função dentro do contrato, que será explicada a seguir.
return newTokenId
— retorna o ID do novo token.
Finalmente, esta função transfere 0,00025 ETH - (taxa de listagem) para o contrato como preço de listagem.
function createMarketItem(uint256 tokenId, uint256 price) private {
require(price > 0, "Price must be at least 1 wei");
require(msg.value == listingPrice);
idToMarketItem[tokenId] = MarketItem(tokenId,payable(msg.sender),payable(address(this)),price,false);
_transfer(msg.sender, address(this), tokenId);
emit MarketItemCreated( tokenId , msg.sender, address(this),price,false);
}
A função acima é chamada pela função createToken
. Uma vez que um token é criado, ele precisa estar no Mercado, para isso chamamos esta função. Observe que mencionamos aqui a visibilidade como privada, o que diz ao contrato que ele só pode ser chamado de dentro do contrato.
As duas primeiras linhas verificam que o preço do token a ser listado no mercado deve ser maior que 0 e que o valor enviado junto com a função deve ser exatamente igual à taxa de listagem mencionada, ou seja, 0,00025 ether.
Próxima linha idToMarketItem[tokenId] = MarketItem (tokenId, payable(msg.sender) ,payable(address(this)),price,false)
. Como mencionado acima, idToMarketItem é um mapeamento que mapeia a estrutura MarketItem para uma chave específica, aqui, o tokenId. Portanto, para um ID específico do token, temos a estrutura que ele aponta. Podemos passar os parâmetros da estrutura como mencionado acima.
payable(msg.sender)
— endereço da carteira que chama o contrato.
payable(address(this))
— endereço do contrato.
price
— o preço do NFT.
false
— referente ao bool sold
.
_transfer(msg.sender, address(this), tokenId);
— Esta também é uma função que foi herdada do contrato ERC721.sol. É usada para mencionar a transferência de token de um endereço para outro. Aqui nesse caso, é a transferência de msg.sender
→address(this)
.
emit MarketItemCreated(tokenId,msg.sender,address(this),price,false);
— Isto emite um evento. Os eventos são como registros no contrato que notificam se algo aconteceu. Para isso, é preciso definir o evento MarketItemCreated
em nosso código.
event MarketItemCreated (
uint256 indexed tokenId,
address seller,
address owner,
uint256 price,
bool sold
);
function resellToken(uint256 tokenId, uint256 price) public payable {
require(idToMarketItem[tokenId].owner == msg.sender);
require(msg.value == listingPrice);
idToMarketItem[tokenId].sold = false;
idToMarketItem[tokenId].price = price;
idToMarketItem[tokenId].seller = payable(msg.sender);
idToMarketItem[tokenId].owner = payable(address(this));
_itemsSold.decrement();
_transfer(msg.sender, address(this), tokenId);
}
Praticamente semelhante à função createToken
, esta função já possui o URL IPFS que especifica os dados. Portanto, em nosso código, apenas mudamos o proprietário de nosso endereço para o endereço do contrato. Além disso, também mencionamos o novo preço da NTF juntamente com a mudança do bool
vendido para false
.
Penúltimo, diminuímos a variável _itemsSold
de 1. Por último, chamamos a função _transfer
do contrato ERC721.sol
que herdamos para transferir a propriedade.
function createMarketSale(uint256 tokenId) public payable {
uint price = idToMarketItem[tokenId].price;
address payable creator = idToMarketItem[tokenId].seller;
require(msg.value == price);
idToMarketItem[tokenId].owner = payable(msg.sender);
idToMarketItem[tokenId].sold = true;
idToMarketItem[tokenId].seller = payable(address(0));
_itemsSold.increment();
_transfer(address(this), msg.sender, tokenId);
payable(owner).transfer(listingPrice);
payable(creator).transfer(msg.value);
}
Esta função trata da transferência de dinheiro (ETH) de um endereço para outro. Isto cria uma venda no mercado - o que acontece quando alguém está comprando um NFT.
.transfer()
— função embutida para transferir cripto.
function fetchMarketItems() public view returns (MarketItem[] memory) {
uint itemCount = _tokenIds.current();
uint unsoldItemCount = _tokenIds.current() - _itemsSold.current();
uint currentIndex = 0;
MarketItem[] memory items = new MarketItem[](unsoldItemCount);
for (uint i = 0; i < itemCount; i++)
{
if (idToMarketItem[i + 1].owner == address(this))
{
uint currentId = i + 1;
MarketItem storage currentItem = idToMarketItem[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
Nesta função de retorno, buscamos os NFTs que são atualmente propriedade do contrato. — idToMarketItem[i + 1].owner == address(this)
.
Para isso, nós primeiro criamos um _array _(vetor) temporário MarketItem
chamado items
e damos a ele um tamanho fixo unsoldItemCount,
o qual nós obtemos subtraindo o índice atual de _tokenIds
pelo número atual de itens vendidos em nosso contrato— _itemsSold.current()
.
Depois, iteramos por meio desse vetor e aplicamos a condição para verificar se o proprietário do NFT é o contrato ou não. Se for, então passamos os detalhes do NFT para o vetor temporário criado (items
). No final, retorna o vetor items
.
function fetchMyNFTs() public view returns (MarketItem[] memory) {
uint totalItemCount = _tokenIds.current();
uint itemCount = 0;
uint currentIndex = 0;
for (uint i = 0; i < totalItemCount; i++) {
// verifique se a NFT é minha
if (idToMarketItem[i + 1].owner == msg.sender)
{
itemCount += 1;
}
}
MarketItem[] memory items = new MarketItem[](itemCount);
for (uint i = 0; i < totalItemCount; i++) {
if (idToMarketItem[i + 1].owner == msg.sender)
{
uint currentId = i + 1;
MarketItem storage currentItem = idToMarketItem[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
Similar à função acima, aqui acrescentamos um passo adicional, ou seja, para encontrar o número de NFTs que possuímos, fazemos um loop através da lista de todas as NFTs.
Isso é feito para fornecer o vetor temporário items
, seu tamanho — itemCount
.
function fetchItemsListed() public view returns (MarketItem[] memory) {
uint totalItemCount = _tokenIds.current();
uint itemCount = 0;
uint currentIndex = 0;
for (uint i = 0; i < totalItemCount; i++) {
if (idToMarketItem[i + 1].seller == msg.sender) {
itemCount += 1;
}
}
MarketItem[] memory items = new MarketItem[](itemCount);
for (uint i = 0; i < totalItemCount; i++) {
if (idToMarketItem[i + 1].seller == msg.sender) {
uint currentId = i + 1;
MarketItem storage currentItem = idToMarketItem[currentId];
items[currentIndex] = currentItem;
currentIndex += 1;
}
}
return items;
}
Aqui também, semelhante à função acima, obtemos a lista de NFTs que estamos vendendo, idToMarketItem[i + 1].seller == msg.sender .
Aqui está o código Solidity completo.
https://gist.github.com/Khanisic/ac54144abd4eea043ff31953a98ab29f
Links
Agradecimentos especiais ao Adrian da Javascript Mastery que me ensinou esse projeto em seu curso NFT Marketplace.
Este artigo foi escrito por [Moid Khan](https://skhan852000.medium.com/?source=post_page-----2323abca6346--------------------------------), traduzido por Fátima Lima e seu original pode ser lido [aqui.](https://betterprogramming.pub/creating-an-nft-marketplace-solidity-2323abca6346)
16 de julho de 2022
Top comments (0)