WEB3DEV

Cover image for Como Construir um Dapp de Cunhagem de NFTs na Flow
Paulo Gio
Paulo Gio

Posted on • Atualizado em

Como Construir um Dapp de Cunhagem de NFTs na Flow

Se você acompanhou a série de artigos da Flow até agora, já sabe que a Blockchain Flow se destaca na gestão de ativos digitais, como NFTs. Ela foi construída do zero como uma melhor alternativa às questões de congestionamento de rede e altas taxas da Ethereum.

Além disso, a linguagem de contratos inteligentes Cadence é a primeira linguagem de programação orientada a recursos que torna fácil e eficiente a criação e o gerenciamento de ativos digitais. Embora o Solidity seja excelente para facilitar a Web3 através de contratos inteligentes, existem desvantagens. O Cadence melhora as falhas do Solidity, fornecendo a capacidade de atualizar contratos inteligentes e recursos que reduzem o risco de erro humano, entre outras melhorias.

E finalmente, a lista de ferramentas e bibliotecas disponíveis para os desenvolvedores que desejam começar é bem extensa. Então, vamos juntar tudo e construir algo na Flow.

Este artigo é um tutorial sobre como criar um dapp completo de cunhagem de NFTs para a Blockchain Flow.

Vamos lá!

No restante deste artigo, vamos percorrer o processo de criação de um dapp de cunhagem de NFTs na blockchain Flow.

Começaremos configurando e implantando um contrato inteligente do Cadence. Em seguida, construiremos um front-end para conectar ao nosso contrato inteligente e cunhar um NFT na conta do usuário.

A funcionalidade que construiremos permitirá que os usuários conectem sua conta da Flow, criem uma conta se ainda não possuírem uma e, em seguida, selecionem uma das três imagens para cunhar em um NFT. A seguir, o dapp exibirá os NFTs de nossa coleção que estão na conta do usuário. Será um excelente projeto para destacar como é fácil e eficiente criar NFTs na Flow e como a Biblioteca do Cliente Flow (Flow Client Library, FCL) é eficaz para interagir com a blockchain.

Para acompanhar este tutorial, você precisará do seguinte:

Com tudo isso instalado, vamos começar!

1. Configurar conta da Flow

Antes de começarmos a construir, precisaremos configurar uma conta na blockchain Flow para podermos implantar nosso contrato inteligente. Execute o seguinte comando para gerar um novo par de chaves pública e privada:

flow keys generate
Enter fullscreen mode Exit fullscreen mode

Certifique-se de anotar os valores que o seu console exibe, pois precisaremos deles nas etapas seguintes.

Em seguida, vamos ao Flow Faucet (torneira de tokens FLOW) para criar um novo endereço baseado em nossas chaves e adicionar fundos à nossa conta com alguns tokens de teste. Siga os seguintes passos para criar sua conta:

  1. Cole sua chave pública no campo de entrada especificado;
  2. Mantenha os Algoritmos de Assinatura e Hash definidos como padrão;
  3. Complete o captcha;
  4. Clique em Create Account (Criar conta).

https://res.cloudinary.com/practicaldev/image/fetch/s--88EPTBaB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5huns4ze6xjnlfl16oan.png

Com uma geração de conta bem-sucedida, obtemos uma caixa de diálogo com nosso novo endereço Flow contendo 1.000 tokens FLOW.

https://res.cloudinary.com/practicaldev/image/fetch/s--y-Cuw5dX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oy6ahmy46ctizson35wa.png

Copie o endereço para uso na próxima etapa.

2. Configurando o contrato inteligente

Antes de construirmos o front-end do projeto, vamos criar o contrato inteligente com o qual vamos interagir mais tarde.

No terminal de comando, navegue até a pasta na qual deseja trabalhar e digite o seguinte comando para iniciar um projeto:

flow init
Enter fullscreen mode Exit fullscreen mode

Este comando cria um arquivo flow.json dentro da pasta, onde colocaremos todas as informações necessárias para implantar nosso contrato inteligente.

Abra o arquivo flow.json em seu editor de código e então vamos configurar uma conta de na rede de testes (testnet). Dentro da seção accounts, adicionaremos uma nova entrada chamada testnet-account, que contém nosso novo endereço e a chave privada gerada anteriormente no comando flow keys generate.

{
    "emulators": {
        "default": {
            "port": 3569,
            "serviceAccount": "emulator-account"
        }
    },
    "contracts": {},
    "networks": {
        "emulator": "127.0.0.1:3569",
        "mainnet": "access.mainnet.nodes.onflow.org:9000",
        "testnet": "access.devnet.nodes.onflow.org:9000"
    },
    "accounts": {
        "emulator-account": {
            "address": "f8d6e0586b0a20c7",
            "key": "2becfbede2fb89796ab68df3ec2a23c3627235ec250a3e5da41df850a8dd4349"
        },
        "testnet-account": {
            "address": "0x8e0dac5df6e8489e",
            "key": "c91f4716a51a66683ccb090ca3eb3e213b90e9f9ae2b1edd12defffe06c57edc"
        }
    },
    "deployments": {}
}
Enter fullscreen mode Exit fullscreen mode

Em seguida, criaremos um novo arquivo para escrever nosso contrato inteligente.

Ao escrever o código, você pode notar algumas diferenças em como o Cadence lida com a criação de NFTs em comparação com o Solidity. Por exemplo, os NFTs no Cadence são criados como um recurso e cunhados diretamente na conta do usuário. Em contraste, os NFTs do Solidity são essencialmente apenas um número de ID referenciado em um mapeamento para um endereço específico no livro-razão digital.

Com isso em mente, na mesma pasta do arquivo flow.json, crie um novo arquivo chamado FlowTutorialMint.cdc e digite o seguinte código:

/* 
*
*  Este é um exemplo de implementação de um Token Não Fungível da Flow. 
*  Este contrato não implementa nenhum sistema de classificação 
*  sofisticado para seus NFTs. Ele define um NFT simples com metadados 
*  mínimos.
*   
*/

import NonFungibleToken from 0x631e88ae7f1d7c20
import MetadataViews from 0x631e88ae7f1d7c20

pub contract FlowTutorialMint: NonFungibleToken {

    pub var totalSupply: UInt64

    pub event ContractInitialized()
    pub event Withdraw(id: UInt64, from: Address?)
    pub event Deposit(id: UInt64, to: Address?)

    pub let CollectionStoragePath: StoragePath
    pub let CollectionPublicPath: PublicPath
    pub let MinterStoragePath: StoragePath

    pub struct FlowTutorialMintData{
        pub let id: UInt64
        pub let type: String
        pub let url: String

        init(_id: UInt64, _type: String, _url: String){
            self.id = _id
            self.type = _type
            self.url = _url
        }
    }

    pub resource NFT: NonFungibleToken.INFT, MetadataViews.Resolver {
        pub let id: UInt64
        pub let type: String
        pub let url: String

        init(
            id: UInt64,
            type: String,
            url: String,
        ) {
            self.id = id
            self.type = type
            self.url = url
        }

        pub fun getViews(): [Type] {
            return [ Type<FlowTutorialMintData>() ]
        }

        pub fun resolveView(_ view: Type): AnyStruct? {
            switch view {
                case Type<FlowTutorialMintData>():
                return FlowTutorialMintData(
                    _id: self.id,
                    _type: self.type,
                    _url: self.url
                )
            }
            return nil
        }
    }

    pub resource interface FlowTutorialMintCollectionPublic {
        pub fun deposit(token: @NonFungibleToken.NFT)
        pub fun getIDs(): [UInt64]
        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT
        pub fun borrowFlowTutorialMint(id: UInt64): &FlowTutorialMint.NFT? {
            post {
                (result == nil) || (result?.id == id):
                    "Não é possível obter a referência FlowTutorialMint: o ID da referência retornada está incorreto"
            }
        }
    }

    pub resource Collection: FlowTutorialMintCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
        // Dicionário de tokens em conformidade com NFT
        // NFT é um tipo de recurso com um campo de ID `UInt64`
        pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}

        init () {
            self.ownedNFTs <- {}
        }

        // withdraw remove um NFT da coleção e o move para o chamador
        pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("faltando NFT")

            emit Withdraw(id: token.id, from: self.owner?.address)

            return <-token
        }

        // deposit pega um NFT e o adiciona ao dicionário de coleções e adiciona o ID ao array 
        // de IDs
        pub fun deposit(token: @NonFungibleToken.NFT) {
            let token <- token as! @FlowTutorialMint.NFT

            let id: UInt64 = token.id

            // Adicionar o novo token ao dicionário remove o antigo
            let oldToken <- self.ownedNFTs[id] <- token

            emit Deposit(id: id, to: self.owner?.address)

            destroy oldToken
        }

        // getIDs retorna um array dos IDs que estão na coleção
        pub fun getIDs(): [UInt64] {
            return self.ownedNFTs.keys
        }

        // borrowNFT obtém uma referência a um NFT na coleção
        // para que o chamador possa ler seus metadados e chamar seus métodos
        pub fun borrowNFT(id: UInt64): &NonFungibleToken.NFT {
            return (&self.ownedNFTs[id] as &NonFungibleToken.NFT?)!
        }

        pub fun borrowFlowTutorialMint(id: UInt64): &FlowTutorialMint.NFT? {
            if self.ownedNFTs[id] != nil {
                // Para permitir o downcasting, é necessário criar uma referência autorizada.
                let ref = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
                return ref as! &FlowTutorialMint.NFT
            }

            return nil
        }

        pub fun borrowViewResolver(id: UInt64): &AnyResource{MetadataViews.Resolver} {
            let nft = (&self.ownedNFTs[id] as auth &NonFungibleToken.NFT?)!
            let flowTutorialMintNFT = nft as! &FlowTutorialMint.NFT
            return flowTutorialMintNFT as &AnyResource{MetadataViews.Resolver}
        }

        destroy() {
            destroy self.ownedNFTs
        }
    }

    // Função pública que qualquer pessoa pode chamar para criar uma nova coleção vazia
    pub fun createEmptyCollection(): @NonFungibleToken.Collection {
        return <- create Collection()
    }

     pub fun mintNFT(
            recipient: &{NonFungibleToken.CollectionPublic},
            type: String,
            url: String,
        ) {

            // Crie um novo NFT
            var newNFT <- create NFT(
                id: FlowTutorialMint.totalSupply,
                type: type,
                url: url
            )

            // Deposite na conta do destinatário usando sua referência
            recipient.deposit(token: <-newNFT)

            FlowTutorialMint.totalSupply = FlowTutorialMint.totalSupply + UInt64(1)
        }

    init() {
        // Inicialize o suprimento total
        self.totalSupply = 0

        // Defina os caminhos (paths) nominados
        self.CollectionStoragePath = /storage/flowTutorialMintCollection
        self.CollectionPublicPath = /public/flowTutorialMintCollection
        self.MinterStoragePath = /storage/flowTutorialMintMinter

        // Crie um recurso de coleção e salve-o no armazenamento
        let collection <- create Collection()
        self.account.save(<-collection, to: self.CollectionStoragePath)

        // Crie uma capacidade pública para a coleção.
        self.account.link<&FlowTutorialMint.Collection{NonFungibleToken.CollectionPublic, FlowTutorialMint.FlowTutorialMintCollectionPublic, MetadataViews.ResolverCollection}>(
            self.CollectionPublicPath,
            target: self.CollectionStoragePath
        )

        emit ContractInitialized()
    }
}
Enter fullscreen mode Exit fullscreen mode

Coisas importantes a serem observadas no contrato inteligente acima:

  • Estamos importando os contratos NonFungibleToken e MetadataViews para criar nossos NFTs usando os padrões da Flow;
  • Definimos nossa recurso NFT na função pub resource NFT;
  • A função mintNFT cunha um NFT na conta que chama a função.

Agora precisamos voltar para o nosso arquivo flow.json para adicionar algumas coisas:

  • Na seção contracts, adicione o contrato e o caminho;
  • Na seção deployments, adicione a rede (testnet), a conta que usaremos para realizar a implantação (testnet-account) e o nome do contrato (FlowTutorialMint).
{
    "emulators": {
        "default": {
            "port": 3569,
            "serviceAccount": "emulator-account"
        }
    },
    "contracts": {
        "FlowTutorialMint": "./FlowTutorialMint.cdc"
    },
    "networks": {
        "emulator": "127.0.0.1:3569",
        "mainnet": "access.mainnet.nodes.onflow.org:9000",
        "testnet": "access.devnet.nodes.onflow.org:9000"
    },
    "accounts": {
        "emulator-account": {
            "address": "f8d6e0586b0a20c7",
            "key": "2becfbede2fb89796ab68df3ec2a23c3627235ec250a3e5da41df850a8dd4349"
        },
        "testnet-account": {
            "address": "0x8e0dac5df6e8489e",
            "key": "c91f4716a51a66683ccb090ca3eb3e213b90e9f9ae2b1edd12defffe06c57edc"
        }
    },
    "deployments": {
        "testnet": {
            "testnet-account": [
                "FlowTutorialMint"
            ]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

A última etapa da configuração é a implantação do contrato inteligente na rede de testes. Para fazer isso, digite o seguinte comando na pasta do projeto em seu terminal:

flow project deploy -n=testnet
Enter fullscreen mode Exit fullscreen mode

Devemos receber uma saída informando que o contrato foi implantado com sucesso:

https://res.cloudinary.com/practicaldev/image/fetch/s--mK3ATzRL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myan5f9ydzmfoacwp3xe.jpg

É importante observar aqui que os contratos inteligentes do Cadence existem no armazenamento da conta que os implanta, enquanto com o Solidity, o contrato inteligente existe em seu próprio endereço na blockchain.

Embora existam limites para a capacidade de armazenamento da conta, estes são relativos à quantidade de tokens FLOW reservados dentro da conta. Você pode aprender mais sobre o armazenamento de contas no Portal do Desenvolvedor Flow (Flow Developer Portal).

Massa! Agora vamos construir um front-end simples para interagir com nosso contrato.

3. Criando o front-end

Para o front-end deste projeto, usaremos o React. Primeiro, navegue até uma nova pasta e execute o seguinte comando para criar um projeto React:

npx create-react-app flow-tutorial
Enter fullscreen mode Exit fullscreen mode

Em seguida, navegue até a pasta flow-tutorial e instale a Biblioteca do Cliente Flow (FCL):

npm i -S @onflow/fcl
Enter fullscreen mode Exit fullscreen mode

A FCL nos permitirá comunicar com a blockchain Flow, chamar transações e integrar todas as outras carteiras compatíveis com a FCL sem a necessidade de adicionar integrações personalizadas. Assim que isso finalizar, instalaremos algumas dependências adicionais:

npm i elliptic sha3 styled-components
Enter fullscreen mode Exit fullscreen mode

Depois de instalar todas as nossas dependências, estamos prontos para começar a trabalhar no front-end do dapp.

3.a. Configurar a FCL

Antes de começarmos a estruturar e estilizar as coisas, vamos criar um arquivo de configuração FCL onde definiremos configurações importantes, como se iremos interagir com a rede de testes ou com a rede principal.

No diretório src, crie uma nova pasta chamada flow. Dentro desta nova pasta, crie um arquivo chamado config.js.

Neste arquivo config.js, importaremos a FCL, chamaremos a função fcl.config e criaremos algumas configurações para o nosso dapp, como:

  • app.detail.title
  • accessNode.api
  • discovery.wallet

Abra o arquivo config.js e preencha-o com o seguinte código:

const fcl = require("@onflow/fcl");

fcl.config({
   // Isso adiciona um nome personalizado à nossa carteira
  "app.detail.title": "Flow Mint Page Tutorial",
   // Isso é para o emulador local
  "accessNode.api": "https://rest-testnet.onflow.org",
   // Isso é para carteira de desenvolvimento local
  "discovery.wallet": "https://fcl-discovery.onflow.org/testnet/authn", 
})
Enter fullscreen mode Exit fullscreen mode

Existem ajustes adicionais que podemos configurar em nosso dapp, mas, por enquanto, isso é tudo de que precisamos.

Feita a configuração, vamos à construção!

3.b. A Estrutura Inicial

Primeiro, navegue até o arquivo App.js na pasta src e substitua o código por este:

import './App.css';

function App() {

  return (
    <div className="App">
        <h1>Cunhe seu cachorro!</h1>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Isso nos dará a estrutura inicial do nosso dapp, a partir da qual iremos expandir.

A seguir, vamos estilizar essa estrutura. Abra o arquivo index.css e substitua tudo pelo código a seguir:

@import url('https://fonts.googleapis.com/css2?family=Michroma&family=Montserrat:wght@200;300;600;700&display=swap');

body {
  margin: 0;
  font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}
Enter fullscreen mode Exit fullscreen mode

Se você executar npm start, verá uma página em branco com o título “Cunhe seu cachorro!".

Agora, vamos criar alguns componentes!

3.c. O Componente de Navegação

Dentro do diretório src, crie uma nova pasta chamada components, onde construiremos todos os nossos componentes personalizados do React.

O primeiro componente que iremos criar é a Navbar (barra de navegação), que mostrará o botão Login caso o usuário não esteja logado, ou o botão Logout ao lado do endereço do usuário e a quantidade de tokens FLOW que a conta possui caso o usuário esteja logado.

Crie um arquivo chamado Navbar.jsx e preencha com o seguinte código:

import * as fcl from "@onflow/fcl";
import styled from "styled-components";
import { useState, useEffect } from "react";
import "../flow/config";

const Wrapper = styled.nav`
  width: -webkit-fill-available;
  background-color: #8dfe89;
  position: fixed;
  top: 0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 50px;

  button {
    background-color: white;
    padding: 5px 40px;
    max-width: 200px;
    border: none;
    border-radius: 20px;
    font-size: 18px;
    height: 50px;

    &:hover {
      color: white;
      background-color: black;
      cursor: pointer;
    }
  }

  div {
    display: flex;
    gap: 15px;
  }
  box {
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
`;

function Navbar() {
  const [user, setUser] = useState({ loggedIn: false, addr: undefined });
  const [flow, setFlow] = useState(0);

  useEffect(() => {
    fcl.currentUser.subscribe(setUser);
    if (user.addr !== "") getFlow(user.addr);
  }, [user.addr]);

  const logOut = async () => {
    await fcl.unauthenticate();
    setUser({ addr: undefined, loggedIn: false });
  };

  const logIn = async () => {
    await fcl.authenticate();
  };

  async function getFlow(address) {
    try {
      const res = await fcl.query({
        cadence: `
                  import FlowToken from 0x7e60df042a9c0868
                  import FungibleToken from 0x9a0766d93b6608b7

                  pub fun main(address: Address): UFix64{
                    let balanceVault =  getAccount(address).getCapability(/public/flowTokenBalance).borrow<&FlowToken.Vault{FungibleToken.Balance}>()!
                    return balanceVault.balance
                  }`,
        args: (arg, t) => [arg(address, t.Address)],
      });
      setFlow(res);
    } catch (error) {
      console.log("err:", error);
    }
  }
  return (
    <Wrapper>
      <h1>Flow Tutorial Mint</h1>
      {user.loggedIn ? (
        <div>
          <button onClick={() => logOut()}>Logout</button>
          <box>
            <span>Address - {user.addr}</span>
            <span>Flow Balance - {flow}</span>
          </box>
        </div>
      ) : (
        <button onClick={() => logIn()}>Login</button>
      )}
    </Wrapper>
  );
}

export default Navbar;
Enter fullscreen mode Exit fullscreen mode

Vamos percorrer o código para ver o que está acontecendo aqui.

  • Primeiro, importamos a FCL, que nos fornecerá funções para autenticar (authenticate), desautenticar (unauthenticate) e determinar o usuário atual (currentUser);
  • Em seguida, importamos as outras dependências de que precisamos e usamos o styled-components para criar o estilo básico da nossa navbar dentro da variável Wrapper;
  • A seguir, definimos algumas variáveis de estado do React (user e flow);
  • Depois disso está a funcionalidade do dapp, como logOut, logIn e getFlow (obtenha o saldo de FLOW da conta conectada);
  • Em seguida, retornamos o html para a barra de navegação envolvida em nosso estilo.

Com um componente Navbar completo, agora podemos importá-lo para o arquivo App.js:

import './App.css';
import Navbar from './components/Navbar.jsx';

function App() {

  return (
    <div className="App">
        <Navbar />
        <h1>Cunhe seu cachorro!</h1>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Agora, se executarmos o projeto com npm start, veremos que nossa Navbar nos dá a funcionalidade que definimos em nosso código. Incrível!

A seguir, vamos construir nosso componente de cunhagem do NFT!

3.d. O componente de cunhagem do NFT

Dentro da pasta components, crie um novo arquivo chamado MintComponent.jsx e copie o seguinte código:

import styled from "styled-components";
import * as fcl from "@onflow/fcl";

const Wrapper = styled.div`
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: center;
  justify-content: center;
  margin-top: 80px;
  padding: 100px;

  main{
    display: flex;
  }

  div{
    width: 300px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 5px;
  }

  button{
    width: 100px;
    padding: 10px;
    border: none;
    background-color: #8dfe89;
    border-radius: 20px;
    font-weight: 500;
    &:hover {
      color: white;
      background-color: black;
      cursor: pointer;
    }
  }

  img{
    width: 200px;
  }
`;

function MintComponent() {
  async function mintNFT(type, url) {
    try {
      const res = await fcl.mutate({
        cadence: `
            import FlowTutorialMint from 0x8e0dac5df6e8489e
            import NonFungibleToken from 0x631e88ae7f1d7c20
            import MetadataViews from 0x631e88ae7f1d7c20

            transaction(type: String, url: String){
                let recipientCollection: &FlowTutorialMint.Collection{NonFungibleToken.CollectionPublic}

                prepare(signer: AuthAccount){

                if signer.borrow<&FlowTutorialMint.Collection>(from: FlowTutorialMint.CollectionStoragePath) == nil {
                signer.save(<- FlowTutorialMint.createEmptyCollection(), to: FlowTutorialMint.CollectionStoragePath)
                signer.link<&FlowTutorialMint.Collection{NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection}>(FlowTutorialMint.CollectionPublicPath, target: FlowTutorialMint.CollectionStoragePath)
                }

                self.recipientCollection = signer.getCapability(FlowTutorialMint.CollectionPublicPath)
                                            .borrow<&FlowTutorialMint.Collection{NonFungibleToken.CollectionPublic}>()!
                }
                execute{
                    FlowTutorialMint.mintNFT(recipient: self.recipientCollection, type: type, url: url)
                }
            }
            `,
        args: (arg, t) => [arg(type, t.String), arg(url, t.String)],
        limit: 9999,
      });
      fcl.tx(res).subscribe((res) => {
        if (res.status === 4 && res.errorMessage === "") {
            window.alert("NFT Cunhado!")
            window.location.reload(false);
        }
      });

      console.log("txid", res);
    } catch (error) {
      console.log("err", error);
    }
  }

  return (
  <Wrapper>
    <h1>Cunhe seu cachorro!</h1>
    <main>
        <div>
            <img src="https://images.unsplash.com/photo-1517849845537-4d257902454a" alt="Cachorro Louco"/>
            <h3>Cachorro Louco</h3>
            <button onClick={() => mintNFT("Cachorro Louco", "https://images.unsplash.com/photo-1517849845537-4d257902454a")}>Cunhar</button>
        </div>

        <div>
            <img src="https://images.unsplash.com/photo-1517423568366-8b83523034fd" alt="Cachorro Estiloso"/>
            <h3>Cachorro Estiloso</h3>
            <button onClick={() => mintNFT("Cachorro Estiloso", "https://images.unsplash.com/photo-1517423568366-8b83523034fd")}>Cunhar</button>
        </div>

        <div>
            <img src="https://images.unsplash.com/photo-1517519014922-8fc06b814a0e" alt="Cachorro Francês"/>
            <h3>Cachorro Francês</h3>
            <button onClick={() => mintNFT("Cachorro Francês", "https://images.unsplash.com/photo-1517519014922-8fc06b814a0e")}>Cunhar</button>
        </div>
    </main>
  </Wrapper>
  )
}

export default MintComponent;
Enter fullscreen mode Exit fullscreen mode

Mais uma vez, vamos percorrer o código para garantir que entendemos o que está acontecendo.

  • Precisamos importar a FCL neste componente para ter acesso à função que nos permitirá criar nosso NFT;
  • Novamente, usamos o styled-components para adicionar um pouco de estilo;
  • A função mintNFT usa a função fcl.mutate para realizar a cunhagem em si:
    • Validando se o usuário tem uma coleção de NFTs Flow Tutorial Mint na sua conta e criando uma se não houver;
    • Chamando a função de cunhagem existente dentro do contrato FlowTutorialMint e passando os parâmetros;
    • A função retorna o recurso (NFT), que depositamos na conta do usuário.
  • Na função fcl.mutate, estamos importando o contrato inteligente que implantamos com a linha: import FlowTutorialMint from 0x8e0dac5df6e8489e;
  • Também importamos os padrões NonFungibleToken e MetadataViews;
  • Na transação, especificamos o tipo de NFT (type) e o url da imagem;
  • As transações do Cadence têm duas fases: prepare e execute
    • prepare - Pedimos a assinatura do usuário para acessar sua conta e realizar funções privadas. Neste caso, criando uma nova coleção FlowTutorial Mint se não existir uma. Também inicializamos uma capacidade (capability) pública restrita a NonFungibleToken.CollectionPublic. Para mais contexto sobre capacidades, confira este link;
    • execute - Chamamos a função mintNFT dentro de nosso contrato na rede de testes.

Na parte do html no código, exibimos três imagens das quais o usuário pode cunhar um NFT.

Com nosso MintComponent completo, podemos importá-lo para o arquivo App.js:

import './App.css';
import Navbar from './components/Navbar.jsx';
import MintComponent from './components/MintComponent.jsx';

function App() {

  return (
    <div className="App">
        <Navbar />
        <h1>Cunhe seu cachorro!</h1>
        <MintComponent />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Agora o usuário pode logar no dapp e cunhar um NFT para sua conta!

A peça final do quebra-cabeça é criar um componente que irá buscar os NFTs do usuário e exibi-los.

3.e. Exibindo os NFTs do usuário

Na pasta components, crie um novo arquivo chamado ShowNfts.jsx, e usaremos o seguinte código:

import * as fcl from "@onflow/fcl";
import { useState, useEffect } from "react";
import styled from "styled-components";

const Wrapper = styled.div`
  background-color: #e5e5e5;
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: center;
  justify-content: center;
  padding: 50px;

  button {
    width: 100px;
    padding: 10px;
    border: none;
    background-color: #8dfe89;
    border-radius: 10px;
    font-weight: 700;
    &:hover {
      color: white;
      background-color: black;
      cursor: pointer;
    }
  }

  section {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap: wrap;
    gap: 30px;
    padding: 10%;
  }

  .nftDiv{
    padding: 10px;
    background-color: #141414;
    border-radius: 20px;
    color: white;
    box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.25);
    img{
        width: 140px;
        border-radius: 10px;
    }
    p{
        font-size: 14px;
    }
  }
`;

export default function ShowNfts() {
  const [nfts, setNfts] = useState([]);
  const [user, setUser] = useState({ loggedIn: false, addr: undefined });

    useEffect(() => {
    fcl.currentUser.subscribe(setUser);
    getNFTs(user.addr)
  }, [user.addr]);

  async function getNFTs(addr) {
    try {
      const result = await fcl.query({
        cadence: `
                import FlowTutorialMint from 0x8e0dac5df6e8489e
                import MetadataViews from 0x631e88ae7f1d7c20

                pub fun main(address: Address): [FlowTutorialMint.FlowTutorialMintData] {
                  let collection = getAccount(address).getCapability(FlowTutorialMint.CollectionPublicPath)
                                    .borrow<&{MetadataViews.ResolverCollection}>()
                                    ?? panic("Não foi possível obter uma referência à coleção de NFTs")

                  let ids = collection.getIDs()

                  let answer: [FlowTutorialMint.FlowTutorialMintData] = []

                  for id in ids {

                    let nft = collection.borrowViewResolver(id: id)
                    let view = nft.resolveView(Type<FlowTutorialMint.FlowTutorialMintData>())!

                    let display = view as! FlowTutorialMint.FlowTutorialMintData
                    answer.append(display)
                  }

                  return answer
                }
                `,
        args: (arg, t) => [arg(addr, t.Address)],
      });
      setNfts(result);
    } catch (error) {
      console.log("err", error);
    }
  }

  return (
    <Wrapper>
      <h1>Meus NFTs</h1>
      <main>
        <button onClick={() => getNFTs(user.addr)}>Obtenha os NFTs</button>
        <section>
          {nfts.map((nft, index) => {
            return (
              <div key={index} className="nftDiv">
                <img src={nft.url} alt="nft" />
                <p>Type: {nft.type}</p>
                <p>Id: {nft.id}</p>
              </div>
            );
          })}
        </section>
      </main>
    </Wrapper>
  );
}
Enter fullscreen mode Exit fullscreen mode

Essencialmente, o que estamos fazendo neste código é consultando a blockchain Flow usando a FCL e coletando os NFTs na conta conectada que são da nossa coleção FlowTutorialMint.

Só precisamos adicionar este componente ao nosso App.js e estamos prontos para começar!

import './App.css';
import Navbar from './components/Navbar.jsx';
import MintComponent from './components/MintComponent.jsx';
import ShowNfts from './components/ShowNfts';

function App() {

  return (
    <div className="App">
      <Navbar />
      <h1>Cunhe seu cachorro!</h1>
      <MintComponent />
      <ShowNfts />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

E isso é tudo! Agora vamos testar nosso dapp e garantir que podemos cunhar alguns NFTs.

4. Vamos cunhar alguns NFTs!

Primeiro, vamos iniciar o aplicativo com npm start e abrir nosso navegador em http://localhost:3000/.

Se tudo correr bem, sua tela deve ficar assim:

https://res.cloudinary.com/practicaldev/image/fetch/s--Zk4P0jWc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m1hsg6o8dicytaod51mq.png

A beleza de usar a FCL em nossa sequência de login é que ela oferece aos nossos usuários acesso fácil para criar uma conta imediatamente usando apenas um endereço de e-mail. Vamos percorrer o processo para garantir que tudo funcione corretamente. Ao clicar no botão de Login, uma caixa de diálogo aparecerá, dando-nos duas opções para fazer o login. Vamos escolher Blocto.

https://res.cloudinary.com/practicaldev/image/fetch/s--zJ0CCrFa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1gr8up5mfxwzyup7hpnl.png

O Blocto nos pedirá para inserir um endereço de e-mail e, ao fazê-lo, nos permitirá registrar uma nova conta. Então, assim que inserirmos o código enviado para o nosso endereço de e-mail, o Blocto nos dará um endereço da Flow novinho em folha!!

https://res.cloudinary.com/practicaldev/image/fetch/s---wDI5171--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e24ho0b7540cj68f7zl3.png

A partir daqui, podemos escolher qual imagem de cachorro queremos cunhar em um NFT. Eu escolhi o Cachorro Estiloso porque ele me lembra um pouco de mim!

https://res.cloudinary.com/practicaldev/image/fetch/s--Fb6KsIMo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2eo65vw4x6tyrsab2zkf.png

Ao pressionar o botão Cunhar, outro diálogo aparecerá nos informando sobre a transação que estamos prestes a realizar. Podemos ver que o Blocto está generosamente cobrindo as taxas de cunhagem e, se quisermos verificar o script que estamos chamando, podemos fazê-lo.

https://res.cloudinary.com/practicaldev/image/fetch/s--RJ-73SdA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xf4wphluj21nlzf96fua.png

Vários segundos depois de clicar em Approve (Aprovar), devemos receber uma mensagem de que nossa cunhagem foi bem-sucedida e nosso recém-criado Cachorro Estiloso será exibido na seção Meus NFTs de nosso dapp.

Aqui está um link para o nosso dapp em ação:

https://s1.gifyu.com/images/flow_tutorial-min.gif

Todo o código-fonte deste projeto pode ser encontrado neste repositório.

Conclusão

Como você pode ver, construir um dapp de cunhagem de NFTs na Flow Blockchain é simples, uma vez que você entende como tudo funciona. Além disso, a FCL é uma ferramenta poderosa à nossa disposição que nos dá acesso a uma ampla funcionalidade integrada e ajuda a proporcionar ao nosso dapp uma melhor experiência do usuário.

Ao contrário da Ethereum, a Flow lida com a criação e o gerenciamento de NFTs com muito mais eficiência e segurança. Isso é obtido com a implantação de contratos inteligentes e a cunhagem de NFTs diretamente na conta do usuário, em vez de criar uma referência a endereços ou mapeamentos armazenados no livro-razão digital.

Para obter mais informações sobre como criar na Flow, confira o Portal do Desenvolvedor da Flow (Flow Developer Portal).

Tenha um ótimo dia!

Artigo original publicado por John Vester. Traduzido por Paulinho Giovannini.

Top comments (0)