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
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:
- Cole sua chave pública no campo de entrada especificado;
- Mantenha os Algoritmos de Assinatura e Hash definidos como padrão;
- Complete o captcha;
- Clique em Create Account (Criar conta).
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.
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
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": {}
}
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()
}
}
Coisas importantes a serem observadas no contrato inteligente acima:
- Estamos importando os contratos
NonFungibleToken
eMetadataViews
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"
]
}
}
}
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
Devemos receber uma saída informando que o contrato foi implantado com sucesso:
É 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
Em seguida, navegue até a pasta flow-tutorial
e instale a Biblioteca do Cliente Flow (FCL):
npm i -S @onflow/fcl
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
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",
})
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;
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;
}
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;
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 nossanavbar
dentro da variável Wrapper; - A seguir, definimos algumas variáveis de estado do React (
user
eflow
); - 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;
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;
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çãofcl.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
eMetadataViews
; - Na transação, especificamos o tipo de NFT (
type
) e ourl
da imagem; - As transações do Cadence têm duas fases:
prepare
eexecute
-
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 aNonFungibleToken.CollectionPublic
. Para mais contexto sobre capacidades, confira este link; -
execute
- Chamamos a funçãomintNFT
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;
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>
);
}
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;
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:
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.
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!!
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!
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.
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.
Latest comments (0)