Как создать децентрализованное приложение NFT Minting On Flow

Как создать децентрализованное приложение NFT Minting On Flow

1 декабря 2022 г.

Если вы до сих пор следили за серией Flow, вы уже знаете, что Flow Blockchain отлично справляется с обработкой цифровых активов, таких как NFT. Он был создан с нуля как лучшая альтернатива перегрузке сети Ethereum и проблемам с высокими комиссиями.

Кроме того, язык смарт-контрактов Cadence является первым в своем роде ориентированный на ресурсы язык программирования, который делает создание и управление цифровыми активами простым и эффективным. Хотя Solidity отлично помогает Web3 через смарт-контракты, у него есть недостатки. Cadence исправляет недостатки Solidity, предоставляя возможность обновления смарт-контрактов и функций, которые снижают риск человеческой ошибки, среди прочих улучшений.

И, наконец, список инструментов и библиотек, доступных разработчикам, желающим начать работу, обширен. Итак, давайте соберем все вместе и создадим что-нибудь на Flow.

Эта статья представляет собой руководство по созданию полноценного децентрализованного приложения NFT-minting для блокчейна Flow.

Давайте приступим

В оставшейся части этой статьи мы рассмотрим процесс создания децентрализованного приложения для майнинга NFT на блокчейне Flow.

Мы начнем с настройки и развертывания смарт-контракта Cadence. Затем мы создадим внешний интерфейс для подключения к нашему смарт-контракту и внесем NFT в учетную запись пользователя.

Функциональность, которую мы создадим, позволит пользователям подключить свою учетную запись Flow, создать учетную запись, если у них ее еще нет, а затем выбрать одно из трех изображений для создания NFT. Затем децентрализованное приложение отобразит NFT из нашей коллекции, которые находятся в учетной записи пользователя. Это будет отличный проект, чтобы показать, насколько просто и эффективно создавать NFT в Flow и насколько эффективна клиентская библиотека Flow (FCL) для взаимодействия с блокчейном.

Чтобы следовать этому руководству, вам понадобятся следующие вещи:

* NodeJs и NPM * Интерфейс командной строки Flow (CLI Flow) * Ваша любимая среда разработки

Теперь, когда все это установлено, приступим!

1. Настройка учетной записи Flow

Прежде чем мы начнем строить, нам нужно настроить учетную запись в блокчейне Flow, чтобы мы могли развернуть наш смарт-контракт. Выполните следующую команду, чтобы сгенерировать новую пару открытого и закрытого ключей:

flow keys generate

Обязательно запишите значения, которые выводит ваша консоль, так как они понадобятся нам в следующих шагах.

Далее мы перейдем к Flow Faucet, чтобы создать новый адрес на основе наших ключей и пополнить наш счет с помощью несколько тестовых токенов. Выполните следующие шаги, чтобы создать учетную запись:

  1. Вставьте свой открытый ключ в указанное поле ввода.
  2. Оставьте для алгоритмов подписи и хеширования значения по умолчанию.
  3. Введите код проверки.
  4. Нажмите Создать аккаунт
  5. .

При успешном создании учетной записи мы получаем диалог с нашим новым адресом Flow, содержащим 1000 токенов FLOW.

Скопируйте адрес для использования на следующем шаге.

2. Настройте смарт-контракт

Прежде чем мы создадим интерфейс проекта, давайте создадим смарт-контракт, с которым мы будем взаимодействовать позже.

В командном терминале перейдите к папке, из которой вы хотите работать, и введите следующую команду, чтобы инициировать проект:

flow init

Эта команда создает файл flow.json внутри папки, куда мы поместим всю информацию, необходимую для развертывания нашего смарт-контракта.

Откройте файл flow.json в редакторе кода, и мы настроим учетную запись тестовой сети. В разделе accounts мы добавим новую запись с именем testnet-account, которая содержит наш новый адрес и закрытый ключ, сгенерированный в команда 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": {}
}

Далее мы создадим новый файл для записи нашего смарт-контракта.

При написании кода вы можете заметить некоторые различия в том, как Cadence обрабатывает создание NFT по сравнению с Solidity. Например, NFT в Cadence создаются как ресурс и чеканятся непосредственно в учетной записи пользователя. Напротив, NFT Solidity — это, по сути, просто идентификационный номер, указанный в сопоставлении с конкретным адресом в цифровой книге.

Имея это в виду, в той же папке, что и файл flow.json, создайте новый файл с именем FlowTutorialMint.cdc и введите следующий код:

/* 
*
*  This is an example implementation of a Flow Non-Fungible Token.
*  This contract does not implement any sophisticated classification
*  system for its NFTs. It defines a simple NFT with minimal metadata.
*   
*/

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):
                    "Cannot borrow FlowTutorialMint reference: the ID of the returned reference is incorrect"
            }
        }
    }

    pub resource Collection: FlowTutorialMintCollectionPublic, NonFungibleToken.Provider, NonFungibleToken.Receiver, NonFungibleToken.CollectionPublic, MetadataViews.ResolverCollection {
        // dictionary of NFT conforming tokens
        // NFT is a resource type with an `UInt64` ID field
        pub var ownedNFTs: @{UInt64: NonFungibleToken.NFT}

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

        // withdraw removes an NFT from the collection and moves it to the caller
        pub fun withdraw(withdrawID: UInt64): @NonFungibleToken.NFT {
            let token <- self.ownedNFTs.remove(key: withdrawID) ?? panic("missing NFT")

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

            return <-token
        }

        // deposit takes an NFT and adds it to the collections dictionary
        // and adds the ID to the id array
        pub fun deposit(token: @NonFungibleToken.NFT) {
            let token <- token as! @FlowTutorialMint.NFT

            let id: UInt64 = token.id

            // add the new token to the dictionary which removes the old one
            let oldToken <- self.ownedNFTs[id] <- token

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

            destroy oldToken
        }

        // getIDs returns an array of the IDs that are in the collection
        pub fun getIDs(): [UInt64] {
            return self.ownedNFTs.keys
        }

        // borrowNFT gets a reference to an NFT in the collection
        // so that the caller can read its metadata and call its methods
        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 {
                // Create an authorized reference to allow downcasting
                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
        }
    }

    // public function that anyone can call to create a new empty collection
    pub fun createEmptyCollection(): @NonFungibleToken.Collection {
        return <- create Collection()
    }

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

            // create a new NFT
            var newNFT <- create NFT(
                id: FlowTutorialMint.totalSupply,
                type: type,
                url: url
            )

            // deposit it in the recipient's account using their reference
            recipient.deposit(token: <-newNFT)

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

    init() {
        // Initialize the total supply
        self.totalSupply = 0

        // Set the named paths
        self.CollectionStoragePath = /storage/flowTutorialMintCollection
        self.CollectionPublicPath = /public/flowTutorialMintCollection
        self.MinterStoragePath = /storage/flowTutorialMintMinter

        // Create a Collection resource and save it to storage
        let collection <- create Collection()
        self.account.save(<-collection, to: self.CollectionStoragePath)

        // create a public capability for the collection
        self.account.link<&FlowTutorialMint.Collection{NonFungibleToken.CollectionPublic, FlowTutorialMint.FlowTutorialMintCollectionPublic, MetadataViews.ResolverCollection}>(
            self.CollectionPublicPath,
            target: self.CollectionStoragePath
        )

        emit ContractInitialized()
    }
}

Важные вещи, которые следует отметить в приведенном выше смарт-контракте:

* Мы импортируем контракты NonFungibleToken и MetadataViews для создания наших NFT с использованием стандартов Flow. * Мы определяем наш ресурс NFT в функции pub resource NFT * Функция mintNFT вводит NFT в учетную запись, которая вызывает функцию

Теперь нам нужно вернуться в наш файл flow.json, чтобы добавить несколько вещей:

* В разделе contracts добавьте контракт и путь к нему. * В разделе deployments добавьте сеть (testnet), учетную запись, которую мы будем использовать для выполнения развертывания (testnet-account), и имя контракта (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"
            ]
        }
    }
}

Последним шагом в настройке смарт-контракта является его развертывание в тестовой сети. Для этого введите следующую команду в папке проекта в терминале:

flow project deploy -n=testnet

Мы должны получить вывод о том, что контракт был успешно развернут:

Здесь важно отметить, что смарт-контракты Cadence существуют в хранилище учетной записи, которая их развертывает, тогда как в Solidity смарт-контракт существует по собственному адресу в блокчейне.

Хотя существуют ограничения на емкость хранилища учетной записи, они зависят от количества токенов FLOW, зарезервированных в учетной записи. Подробнее о хранилище аккаунта можно узнать на портале разработчиков Flow.

Потрясающий! Теперь давайте создадим простой интерфейс для взаимодействия с нашим контрактом.

3. Создание внешнего интерфейса

Для внешнего интерфейса этого проекта мы будем использовать React. Сначала перейдите в новую папку и выполните следующую команду, чтобы создать проект React:

npx create-react-app flow-tutorial

Затем перейдите в папку flow-tutorial и установите клиентскую библиотеку Flow (FCL):

npm i -S @onflow/fcl

FCL позволит нам взаимодействовать с блокчейном Flow, вызывать транзакции и интегрировать все другие FCL-совместимые кошельки без необходимости добавления пользовательских интеграций. Как только это будет завершено, мы установим несколько дополнительных зависимостей:

npm i elliptic sha3 styled-components

После установки всех наших зависимостей мы готовы начать работу над интерфейсом децентрализованного приложения.

3.а. Настройте FCL

Прежде чем мы начнем структурировать и стилизовать вещи, давайте создадим файл конфигурации FCL, в котором мы определим важные настройки, например, будем ли мы взаимодействовать с тестовой сетью или основной сетью.

В каталоге src создайте новую папку с именем flow. В этой новой папке создайте файл с именем config.js.

В этом файле config.js мы импортируем FCL, вызываем функцию fcl.config и создаем некоторые настройки для нашего децентрализованного приложения, например:

* приложение.деталь.название * AccessNode.api * открытие.кошелек

Откройте файл config.js и заполните его следующим кодом:

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

fcl.config({
  "app.detail.title": "Flow Mint Page Tutorial", // this adds a custom name to our wallet
  "accessNode.api": "https://rest-testnet.onflow.org", // this is for the local emulator
  "discovery.wallet": "https://fcl-discovery.onflow.org/testnet/authn", // this is for the local dev wallet
})

Есть дополнительные настройки, которые мы можем настроить для нашего децентрализованного приложения, но пока , это все, что нам нужно.

Разобравшись с конфигурацией, давайте перейдем к сборке!

3.б. Исходная структура

Сначала перейдите к файлу App.js в папке src и замените код следующим:

import './App.css';

function App() {

  return (
    <div className="App">
        <h1>Mint Your Dog!</h1>
    </div>
  );
}

export default App;

Это даст нам первоначальную структуру нашего децентрализованного приложения, которую мы будем расширять.

Далее мы стилизуем эту структуру. Откройте файл index.css и замените код следующим:

@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;
}

Если вы запустите npm start, вы увидите пустую страницу с заголовком Mint Your Dog!

Далее создадим несколько компонентов!

3.в. Компонент навигации

Внутри каталога src создайте новую папку с именем components, где мы будем создавать все наши пользовательские компоненты React.

Первым компонентом, который мы создадим, является панель навигации, на которой будет отображаться кнопка входа, если пользователь не подключен, или кнопка выхода рядом с адресом пользователя и количеством токенов FLOW, которые есть у учетной записи, если они подключены.

Создайте файл с именем Navbar.jsx и заполните его следующим кодом:

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;

Давайте пройдемся по коду, чтобы увидеть, что здесь происходит.

* Во-первых, мы импортируем клиентскую библиотеку Flow, которая предоставит нам функции для аутентификации, отмены аутентификации и определения currentUser. * Затем мы импортируем другие необходимые нам зависимости, а затем используем styled-components для создания основного стиля нашей панели навигации внутри переменной Wrapper. * Затем мы определяем некоторые переменные состояния React (user и flow). * Далее идут функциональные возможности децентрализованного приложения, такие как logOut, logIn и getFlow (получение баланса FLOW подключенной учетной записи). * После этого мы возвращаем html для панели навигации, обернутой в наш стиль.

Теперь, когда у нас есть готовый компонент Navbar, мы можем импортировать его в файл App.js:

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

function App() {

  return (
    <div className="App">
        <Navbar />
        <h1>Mint your Dog!</h1>
    </div>
  );
}

export default App;

Теперь, если мы запустим проект с помощью npm start, мы увидим, что наша Navbar предоставляет нам функции, определенные в нашем коде. Потрясающе!

Теперь давайте создадим наш компонент чеканки NFT!

3.г. Компонент майнинга NFT

В папке components создайте новый файл с именем MintComponent.jsx, затем скопируйте следующий код:

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 Minted!")
            window.location.reload(false);
        }
      });

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

  return (
  <Wrapper>
    <h1>Mint your Dog!</h1>
    <main>
        <div>
            <img src="https://images.unsplash.com/photo-1517849845537-4d257902454a" alt="Mad Dog"/>
            <h3>Mad Dog</h3>
            <button onClick={() => mintNFT("Mad Dog", "https://images.unsplash.com/photo-1517849845537-4d257902454a")}>Mint</button>
        </div>

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

        <div>
            <img src="https://images.unsplash.com/photo-1517519014922-8fc06b814a0e" alt="French Dog"/>
            <h3>French Dog</h3>
            <button onClick={() => mintNFT("French Dog", "https://images.unsplash.com/photo-1517519014922-8fc06b814a0e")}>Mint</button>
        </div>
    </main>
  </Wrapper>
  )
}

export default MintComponent;

Опять же, давайте пройдемся по коду, чтобы убедиться, что мы понимаем, что происходит.

* Нам нужно импортировать FCL в этот компонент, чтобы получить доступ к функции, которая позволит нам создать наш NFT. * Опять же, мы используем styled-components для добавления стиля. * Функция mintNFT использует функцию fcl.mutate для фактической чеканки: * Проверка наличия у пользователя коллекции Flow Tutorial Mint NFT в его учетной записи и создание ее, если нет. * Вызов существующей функции монетного двора внутри контракта FlowTutorialMint и передача параметров. * Функция возвращает ресурс (NFT), который мы вносим на счет пользователя. * В функции fcl.mutate мы импортируем развернутый нами смарт-контракт со строкой: import FlowTutorialMint from 0x8e0dac5df6e8489e * Мы также импортируем стандарты NonFngibleToken и MetadataViews. * В транзакции мы указываем NFT type и url изображения. * Транзакции Cadence состоят из двух фаз: подготовка и выполнение. * prepare — мы просим подпись пользователя для доступа к своей учетной записи и выполнения приватных функций. В этом случае необходимо создать новую коллекцию FlowTutorial Mint, если ее еще нет. Мы также инициализируем общедоступную Capability, ограниченную NonFungibleToken.CollectionPublic. Подробнее о возможностях можно узнать по этой ссылке. * execute — вызвать функцию mintNFT внутри нашего контракта в тестовой сети. * В части кода html мы показываем три изображения, из которых пользователь может создать NFT.

Когда наш MintComponent готов, мы можем импортировать его в файл 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>Mint your Dog!</h1>
        <MintComponent />
    </div>
  );
}

export default App;

Теперь пользователь может войти в децентрализованное приложение и добавить NFT в свою учетную запись!

Последним элементом головоломки является создание компонента, который будет извлекать NFT пользователя и отображать их.

3.e. Отображение NFT пользователя

В папке components создайте новый файл с именем ShowNfts.jsx, и мы будем использовать следующий код:

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("Could not borrow a reference to the nft collection")

                  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>My NFTs</h1>
      <main>
        <button onClick={() => getNFTs(user.addr)}>Get 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>
  );
}

По сути, в этом коде мы запрашиваем блокчейн Flow с помощью FCL и собираем NFT в подключенной учетной записи из нашей коллекции FlowTutorialMint.

Нам просто нужно добавить этот компонент в наш App.js, и все готово!

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>Mint your Dog!</h1>
      <MintComponent />
      <ShowNfts />
    </div>
  );
}

export default App;

Это все! Теперь давайте протестируем наше децентрализованное приложение и убедимся, что мы можем создать несколько NFT.

4. Давайте создадим несколько NFT!

Итак, сначала запустим приложение с помощью npm start, а затем откроем в браузере http://localhost:3000/< /а>.

Если все в порядке, ваш экран должен выглядеть так:

Прелесть использования FCL в нашей последовательности входа в систему заключается в том, что она дает нашим пользователям легкий доступ к созданию учетной записи прямо на месте, используя только адрес электронной почты. Давайте пройдемся по процессу, чтобы убедиться, что он работает правильно. При нажатии кнопки Войти открывается диалоговое окно с двумя вариантами входа. Мы выберем Blocto.

Blocto предложит нам ввести адрес электронной почты и после этого даст нам возможность зарегистрировать новую учетную запись. Затем, как только мы вводим код, отправленный по электронной почте на наш адрес, Blocto назначает нам новый блестящий адрес Flow!

n Здесь мы можем выбрать, какое изображение собаки мы хотим использовать в качестве NFT. Я выбрал Swag Dog, потому что он немного напоминает мне меня самого!

При нажатии кнопки Отчеканить появится другое диалоговое окно, сообщающее нам о транзакции, которую мы собираемся выполнить. Мы видим, что Blocto любезно покрывает комиссию за чеканку, и если мы хотим посмотреть на скрипт, который мы вызываем, мы можем это сделать.

Через несколько секунд после нажатия Подтвердить мы должны получить сообщение о том, что наша чеканка прошла успешно, и наша недавно созданная Swag Dog отобразится в разделе Мои NFT нашего децентрализованного приложения. .

Вот ссылка на наше децентрализованное приложение в действии:

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

Весь исходный код этого проекта можно найти в этом репозитории.

Заключение

Как видите, создать децентрализованное приложение для чеканки NFT на блокчейне Flow несложно, если вы понимаете, как все это работает вместе. Кроме того, клиентская библиотека Flow — это мощный инструмент в нашем распоряжении, который дает нам доступ к расширенным встроенным функциям и помогает сделать наше децентрализованное приложение более удобным для пользователей.

В отличие от Ethereum, Flow обрабатывает создание и управление NFT намного эффективнее и безопаснее. Это достигается за счет развертывания смарт-контрактов и создания NFT непосредственно в учетной записи пользователя, а не создания ссылки на адреса или сопоставления, хранящиеся в цифровой книге.

Для получения дополнительной информации о создании Flow посетите портал разработчиков Flow.

Хорошего дня!


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE