🌈🦄 Создание собственного DApp NFT с искусственным интеллектом с помощью Bacalhau

🌈🦄 Создание собственного DApp NFT с искусственным интеллектом с помощью Bacalhau

9 февраля 2023 г.

Полное руководство по сборке, запуску и использованию развертывание DApp с вашим собственным скриптом преобразования текста в изображение для создания художественных NFT, созданных искусственным интеллектом, в тестовой сети FVM Hyperspace!

Оглавление

  • 👩‍💻 Что мы будем делать...
  • 🏛Архитектурная схема (типа)
  • 🥞 Стек технологий децентрализованных приложений
  • 🏗️ Создание скрипта преобразования текста в изображение на Python
  • ⚒️ Строительство & Развертывание скрипта Solidity NFT
  • 🎬 Создание интерфейсов взаимодействия
  • Полный поток
  • Взаимодействия с Бакальяу
  • NFT.Хранилище
  • Контрактные взаимодействия
  • 🌟 Заключительные мысли: возможности искусственного интеллекта и amp; Блокчейн
  • 🐠 Дорожная карта Бакальяу
  • ✍️ Оставайтесь на связи!

🦄 Быстрые ссылки:

<цитата>

👩‍💻 Что мы будем делать...

В этом блоге вы узнаете, как

  1. Создайте сценарий преобразования текста в изображение на основе Python с открытым исходным кодом на основе Tensorflow (вы также можете просто использовать конечную точку Bacalhau HTTP, если это вас не интересует)
  2. Запустите этот скрипт на Bacalhau (открытая вычислительная платформа p2p вне сети)
  3. Создайте контракт NFT в Solidity (на основе контракта Open Zeppelin ERC721)
  4. Разверните контракт NFT в гиперпространственной тестовой сети виртуальной машины Filecoin (FVM) с помощью Hardhat
  5. Взаимодействия с внешним интерфейсом: как взаимодействовать со сценарием Bacalhau для преобразования текста в изображение и вашим контрактом NFT в React
  6. Как сохранить метаданные NFT в NFT.Storage
  7. Как развернуть внешнее DApp на Fleek

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

Этот блог будет довольно длинным (эй - я хочу дать ВСЮ ИНФОРМАЦИЮ и убедиться, что мы ориентированы на начинающих и инклюзивны!) - так что не стесняйтесь переходить к частям, которые полезны для вас в таблице. содержания <3

🏛 Архитектурная схема (типа)

🥞 Стек технологий децентрализованных приложений

(понятно - это стопка блинов #sorrynotsorry)

Открытый исходный код и усилители; Ценность Web3 с нуля :)

* Смарт-контракт [Solidity, Open Zeppelin] * Solidity — это объектно-ориентированный язык программирования смарт-контрактов для блокчейнов, совместимых с Ethereum (EVM). * Open Zeppelin предлагает проверенную на безопасность библиотеку реализации общих компонентов смарт-контрактов и контрактов. * Среда разработки смарт-контрактов [Каска] * Hardhat — это среда разработки для редактирования, компиляции, отладки и т. д. развертывание программного обеспечения Ethereum * Тестовая сеть блокчейна [Гиперпространство виртуальной машины Filecoin] * FVM Hyperspace – это тестовая сеть, совместимая с EVM, построенная на блокчейне Filecoin. * Хранилище метаданных NFT [NFT.Storage] * NFT.Storage — это общественное благо, созданное на базе IPFS & Filecoin для неизменного и постоянного хранения метаданных NFT & предлагает бесплатное децентрализованное хранилище для NFT и javascript sdk. * Внешний интерфейс [NextJS / React + NPM] * Мы, наверное, все знаем это... верно? :П * Взаимодействия смарт-контрактов от клиента [Metamask, Ethers, узел Chainstack RPC] * Используя общедоступный RPC-узел, я могу получать взаимодействия только для чтения с моим блокчейн-контрактом. * С поставщиком Metamask (или аналогичным кошельком, который внедряет Ethereum API, указанный в EIP-1193, в браузер), мы разрешаем запись вызовов в контракт блокчейна. * Ethersjs — это библиотека для взаимодействия со смарт-контрактами, совместимыми с EVM. * AI Стабильный диффузионный скрипт преобразования текста в изображение [Python, Tensorflow] * TensorFlow – это платформа и библиотека машинного обучения с открытым исходным кодом, которая предоставляет предварительно обученные модели и другие данные и инструменты машинного обучения. * Децентрализованные вычисления вне сети для преобразования текста в изображение с помощью ИИ [Bacalhau] * Bacalhau – это открытая одноранговая вычислительная сеть, которая предоставляет платформу для общедоступных, прозрачных и при необходимости проверяемых вычислительных процессов. Это децентрализованный уровень вычисления данных вне сети. * Децентрализованное развертывание DApp [Fleek] * Fleek предлагает развертывание веб-сайтов на IPFS & Файлкойн. Это веб-версия Vercel или Netlify — не могу сказать, что у нас действительно есть децентрализованное приложение, а затем мы развертываем его в веб-2! :D

🏗️ Создание скрипта преобразования текста в изображение на Python

<цитата>

💡 Совет TLDR 💡

Этот скрипт уже доступен для использования через Bacalhau через интерфейс командной строки и конечную точку HTTP, поэтому можете пропустить эту часть.

Краткое введение в стабильную диффузию

Стабильная диффузия в настоящее время является ведущей моделью машинного обучения для преобразования текста в изображение (это та же модель, которую использует Dall-E). Это тип глубокого обучения — подмножество машинного обучения, которое самообучается выполнять определенную задачу — в данном случае преобразование ввода текста в вывод изображения.

В этом примере мы используем вероятностную модель диффузии, которая использует преобразователь для создания изображений из текста.

Не волнуйтесь, нам не нужно идти и обучать модель машинного обучения для этого (хотя, эй, если вам это нужно, вы вполне можете!)

Вместо этого мы собираемся использовать предварительно обученную модель из библиотеки машинного обучения TensorFlow с открытым исходным кодом Google в нашем скрипте Python, поскольку веса ML были предварительно рассчитаны для нас.

Точнее, мы используем оптимизированную ветвь реализации Keras/TensorFlow оригинальная модель машинного обучения.

Скрипт Python

<цитата>

🦄 Вы можете найти полное пошаговое руководство по созданию и докеризации этого скрипта преобразования текста в изображение и запуску его на Bacalhau как в Bacalhau docs и в этом @BacalhauProject Видео на YouTube.🦄 Вы также можете запустить его в этом Блокнот Google Collabs

Вот полный скрипт Python!

import argparse
from stable_diffusion_tf.stable_diffusion import Text2Image
from PIL import Image
import os

parser = argparse.ArgumentParser(description="Stable Diffusion")
parser.add_argument("--h",dest="height", type=int,help="height of the image",default=512)
parser.add_argument("--w",dest="width", type=int,help="width of the image",default=512)
parser.add_argument("--p",dest="prompt", type=str,help="Description of the image you want to generate",default="cat")
parser.add_argument("--n",dest="numSteps", type=int,help="Number of Steps",default=50)
parser.add_argument("--u",dest="unconditionalGuidanceScale", type=float,help="Number of Steps",default=7.5)
parser.add_argument("--t",dest="temperature", type=int,help="Number of Steps",default=1)
parser.add_argument("--b",dest="batchSize", type=int,help="Number of Images",default=1)
parser.add_argument("--o",dest="output", type=str,help="Output Folder where to store the Image",default="./")

args=parser.parse_args()
height=args.height
width=args.width
prompt=args.prompt
numSteps=args.numSteps
unconditionalGuidanceScale=args.unconditionalGuidanceScale
temperature=args.temperature
batchSize=args.batchSize
output=args.output

generator = Text2Image(
    img_height=height,
    img_width=width,
    jit_compile=False,  # You can try True as well (different performance profile)
)

img = generator.generate(
    prompt,
    num_steps=numSteps,
    unconditional_guidance_scale=unconditionalGuidanceScale,
    temperature=temperature,
    batch_size=batchSize,
)
for i in range(0,batchSize):
  pil_img = Image.fromarray(img[i])
  image = pil_img.save(f"{output}/image{i}.png")

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

Вся тяжелая работа, проделанная здесь, происходит в разделе ниже — именно здесь модель машинного обучения делает свое волшебство. 🪄

generator = Text2Image(
    img_height=height,
    img_width=width,
    jit_compile=False,
)

img = generator.generate(
    prompt,
    num_steps=numSteps,
    unconditional_guidance_scale=unconditionalGuidanceScale,
    temperature=temperature,
    batch_size=batchSize,
)

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

Если и есть что-то, с чем технология блокчейн не справляется, так это с обработкой больших объемов данных. Это связано со стоимостью вычислений в распределенной системе для обеспечения других мощных свойств, таких как отсутствие доверия и устойчивость к цензуре.

Возможно использование вашей локальной машины для небольших примеров - на самом деле мне удалось заставить этот конкретный пример работать на моем (очень недовольном этим) Mac M1, однако это было очень долгое ожидание результатов (кому-нибудь игра в настольный теннис?) поэтому, как только вы начнете обрабатывать большие объемы данных, вам понадобится больше газа (каламбур), и если у вас нет выделенного сервера, валяющегося дома, вам нужно будет использовать виртуальную машину на платформа облачных вычислений.

Это не только централизованно, но и неэффективно — из-за того, что данные находятся на неизвестном расстоянии от вычислительной машины, и это может быстро стать дорогостоящим. Мне не удалось найти какой-либо сервис облачных вычислений бесплатного уровня, который предлагал бы для этого обработку графического процессора (кто-то сказал о запрете крипто-майнинга?), и планы появились в > 400 долларов США в месяц (нет, спасибо).

Бакальяу!

К счастью, Бакальяу пытается решить именно эти проблемы. Сделать обработку данных и вычисления открытыми и доступными для всех, а также ускорить время обработки возможно в Bacalhau, во-первых, за счет использования пакетной обработки на нескольких узлах и, во-вторых, за счет размещения узлов обработки там, где хранятся данные!

Bacalhau стремится помочь демократизировать будущее обработки данных, обеспечивая возможность вычислений вне сети над данными, не отказываясь от ценностей децентрализации, присущих IPFS, Filecoin & Web3 в более широком смысле.

Bacalhau – это открытая одноранговая вычислительная сеть, которая предоставляет платформу для общедоступных, прозрачных и при необходимости проверяемых вычислительных процессов, на которых пользователи могут запускать Docker. контейнеры или образы веб-сборок в качестве задач против любых данных, включая данные, хранящиеся в IPFS (и вскоре Filecoin). У него даже есть поддержка заданий графического процессора, а не 400 долларов США и выше!

intro | Bacalhau Docs

Запуск скрипта на Bacalhau

Чтобы запустить этот скрипт, мы можем сделать его Dockerise для использования на Bacalhau. Вы можете изучить руководство здесь, если хотите научиться этому. Мы можем затем запустите его с помощью интерфейса командной строки Bacalhau с помощью всего одной строки кода (после установки Bacalhau с помощью еще одной строки):

bacalhau docker run --gpu 1 ghcr.io/bacalhau-project/examples/stable-diffusion-gpu:0.0.1 -- python main.py --o ./outputs --p "Rainbow Unicorn"

Однако в этом примере я собираюсь использовать конечную точку HTTP, которая подключает меня к этому докеризованному сценарию стабильной диффузии, который я покажу вам в разделе «Интеграции»!

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

Тем не менее, давайте перейдем к скрипту NFT! :)

⚒️ Строительство & Развертывание сценария Solidity NFT

Смарт-контракт

Смарт-контракт NFT основан на реализации Open Zeppelin ERC721, но использует версию ERC721URIStorage, которая включает стандартные расширения метаданных (чтобы мы могли передать наши метаданные с адресом IPFS, которые мы сохраним в NFT.Storage, в контракт).

Этот базовый контракт дополнительно дает нам общую функциональность контракта NFT с такими функциями, как mint() и transfer(), уже реализованными для нас.

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

<цитата>

💡 Попробуйте ремикс и посмотреть все доступные функции, нажав на эту ссылку! 💡

BacalhauFRC721.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@hardhat/console.sol"; 

contract BacalhauFRC721 is ERC721URIStorage {
/** @notice Counter keeps track of the token ID number for each unique NFT minted in the NFT collection */
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

/** @notice This struct stores information about each NFT minted */
    struct bacalhauFRC721NFT {
        address owner;
        string tokenURI;
        uint256 tokenId;
    }

 /** @notice Keeping an array for each of the NFT's minted on this contract allows me to get information on them all with a read-only front end call */
    bacalhauFRC721NFT[] public nftCollection;
/** @notice The mapping allows me to find NFT's owned by a particular wallet address. I'm only handling the case where an NFT is minted to an owner in this contract - but you'd need to handle others in a mainnet contract like sending to other wallets */
    mapping(address => bacalhauFRC721NFT[]) public nftCollectionByOwner;

/** @notice This event will be triggered (emitted) each time a new NFT is minted - which I will watch for on my front end in order to load new information that comes in about the collection as it happens */
    event NewBacalhauFRC721NFTMinted(
      address indexed sender,
      uint256 indexed tokenId,
      string tokenURI
    );

/** @notice Creates the NFT Collection Contract with a Name and Symbol */
    constructor() ERC721("Bacalhau NFTs", "BAC") {
      console.log("Hello Fil-ders! Now creating Bacalhau FRC721 NFT contract!");
    }

/** 
@notice The main function which will mint each NFT.
The ipfsURI is a link to the ipfs content identifier hash of the NFT metadata stored on NFT.Storage. This data minimally includes name, description and the image in a JSON.
*/
    function mintBacalhauNFT(address owner, string memory ipfsURI)
        public
        returns (uint256)
    {
        // get the tokenID for this new NFT
        uint256 newItemId = _tokenIds.current();

        // Format info for saving to our array
        bacalhauFRC721NFT memory newNFT = bacalhauFRC721NFT({
            owner: msg.sender,
            tokenURI: ipfsURI,
            tokenId: newItemId
        });

        //mint the NFT to the chain
        _mint(owner, newItemId);
        //Set the NFT Metadata for this NFT
        _setTokenURI(newItemId, ipfsURI);

        _tokenIds.increment();

        //Add it to our collection array & owner mapping
        nftCollection.push(newNFT);
        nftCollectionByOwner[owner].push(newNFT);

        // Emit an event on-chain to say we've minted an NFT
        emit NewBacalhauFRC721NFTMinted(
          msg.sender,
          newItemId,
          ipfsURI
        );

        return newItemId;
    }

    /**
     * @notice helper function to display NFTs for frontends
     */
    function getNFTCollection() public view returns (bacalhauFRC721NFT[] memory) {
        return nftCollection;
    }

    /**
     * @notice helper function to fetch NFT's by owner
     */
    function getNFTCollectionByOwner(address owner) public view returns (bacalhauFRC721NFT[] memory){
        return nftCollectionByOwner[owner];
    }

Требования

Я разверну этот контракт в Filecoin Virtual Machine Hyperspace Testnet, но вы можете развернуть этот контракт в любую совместимую с EVM цепочку, включая Polygon, BSC, Optimism, Arbitrum, Avalanche и другие. Вы даже можете настроить свой внешний интерфейс, чтобы создать NFT с несколькими цепочками (подсказка: этот репозиторий)!< /p>

Для развертывания в Hyperspace Testnet нам потребуется

  1. Настройка & подключить кошелек Metamask к Hyperspace Testnet
  2. Получите тестовые средства tFIL из крана (Yoga или Zondax)

Развертывание смарт-контракта с помощью Hardhat

Я использую каску для развертывания этого контракта в тестовой сети Hyperspace.

<цитата>

🛸 Hyperspace RPC & Параметры BlockExplorer:

Общедоступные конечные точки RPC

BlockExplorer

https://filecoin-hyperspace.chainstacklabs.com/rpc/v0

https://beryx.zondax.ch/

https://hyperspace.filfox.info/rpc/v0

https://fvm.starboard.ventures/contracts/

https://rpc.ankr.com/filecoin_testnet

https://explorer.gif.io/?network=hyperspacenet

Открытый API: beryx.zondax.ch

https://hyperspace.filfox.info/en

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

hardhat.config.ts

import '@nomicfoundation/hardhat-toolbox';
import { config as dotenvConfig } from 'dotenv';
import { HardhatUserConfig } from 'hardhat/config';
import { resolve } from 'path';

//Import our customised tasks
// import './pages/api/hardhat/tasks';

const dotenvConfigPath: string = process.env.DOTENV_CONFIG_PATH || './.env';
dotenvConfig({ path: resolve(__dirname, dotenvConfigPath) });

// Ensure that we have all the environment variables we need.
const walletPrivateKey: string | undefined = process.env.WALLET_PRIVATE_KEY;
if (!walletPrivateKey) {
  throw new Error('Please set your Wallet private key in a .env file');
}

const config: HardhatUserConfig = {
  solidity: '0.8.17',
  defaultNetwork: 'filecoinHyperspace',
  networks: {
    hardhat: {},
    filecoinHyperspace: {
      url: 'https://api.hyperspace.node.glif.io/rpc/v1',
      chainId: 3141,
      accounts: [process.env.WALLET_PRIVATE_KEY ?? 'undefined'],
    },
    // bleeding edge often-reset FVM testnet
    filecoinWallaby: {
      url: 'https://wallaby.node.glif.io/rpc/v0',
      chainId: 31415,
      accounts: [process.env.WALLET_PRIVATE_KEY ?? 'undefined'],
      //explorer: https://wallaby.filscan.io/ and starboard
    },
  },
// I am using the path mapping so I can keep my hardhat deployment within the /pages folder of my DApp and therefore access the contract ABI for use on my frontend
  paths: {
    root: './pages/api/hardhat',
    tests: './pages/api/hardhat/tests', //who names a directory in the singular?!!! Grammarly would not be happy
    cache: './pages/api/hardhat/cache',
  },
};

export default config;

Чтобы развернуть смарт-контракт, мы создаем сценарий развертывания. Обратите внимание, что я специально указываю здесь адрес кошелька в качестве подписавшего (владельца). это может привести к странному поведению.

развернуть/развернутьBacalhauFRC721.ts

import hre from 'hardhat';

import type { BacalhauFRC721 } from '../typechain-types/contracts/BacalhauFRC721';
import type { BacalhauFRC721__factory } from '../typechain-types/factories/contracts/BacalhauFRC721__factory';

async function main() {
  console.log('Bacalhau721 deploying....');

// !!!needed as hardhat's default does not map correctly to the FEVM
  const owner = new hre.ethers.Wallet(
    process.env.WALLET_PRIVATE_KEY || 'undefined',
    hre.ethers.provider
  );
  const bacalhauFRC721Factory: BacalhauFRC721__factory = <
    BacalhauFRC721__factory
  > await hre.ethers.getContractFactory('BacalhauFRC721', owner);

  const bacalhauFRC721: BacalhauFRC721 = <BacalhauFRC721>(
    await bacalhauFRC721Factory.deploy()
  );
  await bacalhauFRC721.deployed();
  console.log('bacalhauFRC721 deployed to ', bacalhauFRC721.address);
  // optionally log to a file here
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Чтобы развернуть, запустите указанный выше скрипт в терминале, используя следующий код (примечание: поскольку мы установили сеть по умолчанию на filecoinHyperspace в нашей конфигурации, нет необходимости передавать флаг для сети, хотя это показано ниже )

<код>> компакт-диск ./pages/каска/развертывание/

npx hardhat run ./deployBacalhauFRC721.ts --network filecoinHyperspace

Празднуйте! Мы только что развернули наш контракт NFT в гиперпространственной тестовой сети Filecoin!

🎬 Построение интерфейсных взаимодействий

Ура, самое красивое... а также клей, который держит все это вместе :)

Для создания внешнего интерфейса я использую NextJS и Typescript. Хотя, если честно, я не пользуюсь ни одной из функций NextJS SSR (рендеринг на стороне сервера) и даже не использую их маршрутизацию страниц (поскольку это одностраничное Dapp), так что вы действительно можете просто пойти с настройкой ванильного React (или, конечно, любого другого фреймворка на ваш выбор!).

Что касается машинописного текста... ну, я сделал его немного в спешке и должен признать, что это не очень хороший пример машинописного текста - хотя переменные кажутся счастливыми... ;)

Anyhoo — основная цель этого раздела не в том, чтобы показать вам, как кодировать внешний интерфейс, а в том, чтобы показать вам, как взаимодействовать со смарт-контрактом, Bacalhau (с нашей моделью стабильного диффузионного машинного обучения) и, конечно же, с NFT.Storage — # NotOnIPFSNotYourNFT.

Завершить процесс

[задача: построить блок-схему]

  • Пользователь вводит текстовое приглашение в поле ввода ->
  • Нажатие кнопки создания изображений -> Вызывает работу Bacalhau для создания изображений
  • Задание Бакальяу завершено -> форматы возвращаются в объект JSON метаданных NFT
  • Пользователь нажимает кнопку Mint NFT -> NFT.Storage вызывается для сохранения метаданных NFT и возвращается с CID IPFS для папки -> Функция чеканки NFT смарт-контракта вызывается с этим IPFS_URI для чеканки NFT с этими метаданными ->
  • !! [Попался FEVM] -> здесь мы обычно ждем возврата TX (хэш транзакции) этого результата, но в настоящее время он не работает, поэтому вместо этого мы используем прослушиватель событий контракта, чтобы узнать, когда это завершится.
  • Готово! -> Теперь можно повторно получать любые отображаемые данные и сообщать пользователю об успешном завершении чеканки.

Отлично — давайте посмотрим, как мы реализуем это в коде!

Взаимодействия с Бакальяу

Создание конечной точки интерфейсного API для Bacalhau описано в этом отчете по проекту инженером Люком Марсденом.

API в настоящее время только напрямую взаимодействует со сценариями стабильного распространения, задокументированными в этом блоге, однако команда находится в процессе расширения его до более общего API, чтобы вы могли вызывать любой из примеров, а также ваши собственные развернутые скрипты из HTTP REST API. Следите за этим здесь или на канале #bacalhau в Слабость FilecoinProject.

>запустить/проверить в терминале

curl -XPOST -d '{"prompt": "rainbow unicorn"}' 'http://dashboard.bacalhau.org:1000/api/v1/stablediffusion';

>реагировать/напечатать код

import { CID } from 'multiformats/cid';

export const callBacalhauJob = async (promptInput: string) => {
  //Bacalahau HTTP Stable Diffusion Endpoint
  const url = 'http://dashboard.bacalhau.org:1000/api/v1/stablediffusion';
  const headers = {
    'Content-Type': 'application/x-www-form-urlencoded',
  };
  const data = {
    prompt: promptInput, //The user text prompt!
  };
  /* FETCH FROM BACALHAU ENDPOINT */
  const cid = await fetch(url, {
    method: 'POST',
    body: JSON.stringify(data),
    headers: headers,
  })
    .then(async (res) => {
      let body = await res.json();
      if (body.cid) {
/* Bacalhau returns a V0 CID which we want to convert to a V1 CID for easier usage with http gateways (ie. displaying the image on web), so I'm using the IPFS multiformats package to convert it here */
        return CID.parse(body.cid).toV1().toString();
      }
    })
    .catch((err) => {
      console.log('error in bac job', err);
    });
  return cid;
};

Эта функция вернет CID IPFS (идентификатор контента) со структурой папок, подобной приведенной ниже. Затем изображение можно найти в папке /outputs/image0.png.

<цитата>

💡 Убедитесь сами, нажав здесь! 💡

Аааа радужные единороги... что тут не нравится!

NFT.Хранилище

NFT.Storage — это общественное достояние (также известное как бесплатное), которое упрощает постоянное хранение метаданных NFT в IPFS & Filecoin с JavaScript или HTTP SDK.

Метаданные NFT — это документ JSON, который выглядит примерно так, как показано в примере ниже, который взят непосредственно из документации Open Zeppelin:

При создании NFT важно отметить, что если вы не храните метаданные в сети (что может стать непомерно дорогим для больших файлов), то для того, чтобы соответствовать «невзаимозаменяемости» токена, вам нужно хранилище, которое стойкий, надежный и неизменяемый.

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

О чем предупреждает даже Open Zeppelin!

Использование NFT.Storage означает, что мы получаем неизменяемый CID файла IPFS (content — не местоположение — identifier) ​​для наших метаданных, которые не просто закрепляются в IPFS, но также затем сохраняются. в Filecoin для сохранения. Вам просто нужно зарегистрироваться в NFT.Storage и получить ключ API (для сохранения в вашем .env файл) для этого.

Пример .env

NEXT_PUBLIC_NFT_STORAGE_API_KEY=xxx

Нам также необходимо убедиться, что мы создали правильно сформированный метаданные JSON, потому что, хотя у FVM нет (пока!) торговых площадок NFT... мы хотим убедиться, что когда он будет принят, наш NFT все еще, надеюсь, соответствует стандарту. .

import { NFTStorage } from 'nft.storage';

//connect to NFT.Storage Client
const NFTStorageClient = new NFTStorage({
   token: process.env.NEXT_PUBLIC_NFT_STORAGE_API_KEY,
});

const createNFTMetadata = async (
    promptInput: string,
    imageIPFSOrigin: string, //the ipfs path eg. ipfs://[CID]
    imageHTTPURL: string //an ipfs address fetchable through http for the front end to use (ie. including an ipfs http gateway on it like https://[CID].ipfs.nftstorage.link)
  ) => {
    console.log('Creating NFT Metadata...');
    let nftJSON;
 // let's get the image data Blob from the IPFS CID that was returned from Bacalhau earlier...
    await getImageBlob(status, setStatus, imageHTTPURL).then(
      async (imageData) => {
// Now let's create a unique CID for that image data - since we don't really want the rest of the data returned from the Bacalhau job..
        await NFTStorageClient.storeBlob(imageData)
          .then((imageIPFS) => {
            console.log(imageIPFS);
//Here's the JSON construction - only name, description and image are required fields- but I also want to save some other properties like the ipfs link and perhaps you have other properties that give your NFT's rarity to add as well
            nftJSON = {
              name: 'Bacalhau Hyperspace NFTs 2023',
              description: promptInput,
              image: imageIPFSOrigin, 
              properties: {
                prompt: promptInput,
                type: 'stable-diffusion-image',
                origins: {
                  ipfs: `ipfs://${imageIPFS}`,
                  bacalhauipfs: imageIPFSOrigin,
                },
                innovation: 100,
                content: {
                  'text/markdown': promptInput,
                },
              },
            };
          })
          .catch((err) => console.log('error creating blob cid', err));
      }
    );
    return nftJSON;
  };

Теперь давайте сохраним эти метаданные в NFT.Storage!

await NFTStorageClient.store(nftJson)
  .then((metadata) => {
    // DONE! - do something with this returned metadata!
    console.log('NFT Data pinned to IPFS & stored on Filecoin!');
    console.log('Metadata URI: ', metadata.url);
    // once saved we can use it to mint the NFT
    // mintNFT(metadata);
  })
  .catch((err) => {
    console.log('error uploading to nft.storage');
  });

Woot - у нас есть наш образ от Bacalhau, мы сохранили наши метаданные неизменно и постоянно с помощью NFT.Strorage, теперь давайте отчеканим наш NFT!

<цитата>

💡 Быстрый совет 💡NFT.Storage также предлагает ряд других вызовы API, такие как storeCar & storeDirectory, а также функцию status(), которая возвращает операции закрепления IPFS и хранения Filecoin для CID -> это может быть довольно интересным дополнением для DApp FEVM (или реализации NFT на FEVM, как только FEVM выйдет в основной сети) для проверки статуса NFT.

Взаимодействия по контракту

Здесь есть 3 типа взаимодействий (и несколько замечаний FEVM — в бета-версии всегда будут какие-то причудливые функции ошибок!)

* вызовы только для чтения для извлечения данных из цепочки без ее изменения * писать звонки, которые требуют кошелька для подписи и оплаты газа, т.е. функции, которые изменяют состояние цепочки, такие как чеканка NFT! * прослушиватели событий — прослушивают события, исходящие от контракта

Для всех этих функций мы будем использовать библиотеку ethers.js — облегченную оболочку для Ethereum API, для подключения к нашему контракту и выполнения вызовов к нему.

Подключение к контракту в режиме чтения с общедоступным RPC:

//The compiled contract found in pages/api/hardhat/artifacts/contracts  
import BacalhauCompiledContract from '@Contracts/BacalhauFRC721.sol/BacalhauFRC721.json';
//On-chain address of the contract
const contractAddressHyperspace = '0x773d8856dd7F78857490e5Eea65111D8d466A646'; 
//A public RPC Endpoint (see table from contract section)
const rpc = 'https://api.hyperspace.node.glif.io/rpc/v1';

const provider = new ethers.providers.JsonRpcProvider(rpc);
const connectedReadBacalhauContract = new ethers.Contract(
      contractAddressHyperspace,
      BacalhauCompiledContract.abi,
      provider
    );

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

//use the read-only connected Bacalhau Contract
connectedReadBacalhauContract.on(
    // Listen for the specific event we made in our contract
    'NewBacalhauFRC721NFTMinted',
    (sender: string, tokenId: number, tokenURI: string) => {
        //DO STUFF WHEN AN EVENT COMES IN
        // eg. re-fetch NFT's, store in state and change page status
    }
);

Подключение к контракту в режиме записи — для этого требуется, чтобы объект Ethereum был внедрен в веб-браузер кошельком, чтобы пользователь мог подписать транзакцию и оплатить газ — поэтому мы повторная проверка объекта window.ethereum.

//Typescript needs to know window is an object with potentially and ethereum value. There might be a better way to do this? Open to tips!
declare let window: any;
//The compiled contract found in pages/api/hardhat/artifacts/contracts  
import BacalhauCompiledContract from '@Contracts/BacalhauFRC721.sol/BacalhauFRC721.json';
//On-chain address of the contract
const contractAddressHyperspace = '0x773d8856dd7F78857490e5Eea65111D8d466A646'; 

//check for the ethereum object
if (!window.ethereum) {
    //ask user to install a wallet or connect
    //abort this
}
// else there's a wallet provider
else {
// same function - different provider - this one has a signer - the user's connected wallet address
   const provider = new ethers.providers.Web3Provider(window.ethereum);
   const contract = new ethers.Contract(
      contractAddressHyperspace,
      BacalhauCompiledContract.abi,
      provider
    );
   const signer = provider.getSigner();
   const connectedWriteBacalhauContract = contract.connect(signer);
}

Вызов функции монетного двора с использованием контракта на запись.

Во-первых, убедитесь, что у нас есть адрес кошелька от пользователя и что мы находимся в цепочке FVM Hyperspace. Вот несколько полезных функций кошелька, которые могут вам понадобиться, в том числе как проверить chainId и как программно добавить сеть Hyperspace в Metamask/кошелек. Вы можете взаимодействовать с кошельками, используя объект Ethereum напрямую или используя ethers.js.

declare let window: any;

const fetchWalletAccounts = async () => {
  console.log('Fetching wallet accounts...');
  await window.ethereum //use ethers?
    .request({ method: 'eth_requestAccounts' })
    .then((accounts: string[]) => {
      return accounts;
    })
    .catch((error: any) => {
      if (error.code === 4001) {
        // EIP-1193 userRejectedRequest error
        console.log('Please connect to MetaMask.');
      } else {
        console.error(error);
      }
    });
};

const fetchChainId = async () => {
  console.log('Fetching chainId...');
  await window.ethereum
    .request({ method: 'eth_chainId' })
    .then((chainId: string[]) => {
      return chainId;
    })
    .catch((error: any) => {
      if (error.code === 4001) {
        // EIP-1193 userRejectedRequest error
        console.log('Please connect to MetaMask.');
      } else {
        console.error(error);
      }
    });
};

//!! This function checks for a wallet connection WITHOUT being intrusive to to the user or opening their wallet
export const checkForWalletConnection = async () => {
  if (window.ethereum) {
    console.log('Checking for Wallet Connection...');
    await window.ethereum
      .request({ method: 'eth_accounts' })
      .then(async (accounts: String[]) => {
        console.log('Connected to wallet...');
        // Found a user wallet
        return true;
      })
      .catch((err: Error) => {
        console.log('Error fetching wallet', err);
        return false;
      });
  } else {
    //Handle no wallet connection 
    return false;
  }
};

//Subscribe to changes on a user's wallet
export const setWalletListeners = () => {
  console.log('Setting up wallet event listeners...');
  if (window.ethereum) {
    // subscribe to provider events compatible with EIP-1193 standard.
    window.ethereum.on('accountsChanged', (accounts: any) => {
      //logic to check if disconnected accounts[] is empty
      if (accounts.length < 1) {
        //handle the locked wallet case
      }
      if (userWallet.accounts[0] !== accounts[0]) {
        //user has changed address
      }
    });

    // Subscribe to chainId change
    window.ethereum.on('chainChanged', () => {
      // handle changed chain case
    });
  } else { 
        //handle the no wallet case
    }
};

export const changeWalletChain = async (newChainId: string) => {
  console.log('Changing wallet chain...');
  const provider = window.ethereum;
  try {
    await provider.request({
      method: 'wallet_switchEthereumChain',
      params: [{ chainId: newChainId }], //newChainId
    });
  } catch (error: any) {
    alert(error.message);
  }
};

//AddHyperspaceChain
export const addHyperspaceNetwork = async () => {
  console.log('Adding the Hyperspace Network to Wallet...');
  if (window.ethereum) {
    window.ethereum
      .request({
        method: 'wallet_addEthereumChain',
        params: [
          {
            chainId: '0xc45',
            rpcUrls: [
              'https://hyperspace.filfox.info/rpc/v0',
              'https://filecoin-hyperspace.chainstacklabs.com/rpc/v0',
            ],
            chainName: 'Filecoin Hyperspace',
            nativeCurrency: {
              name: 'tFIL',
              symbol: 'tFIL',
              decimals: 18,
            },
            blockExplorerUrls: [
              'https://fvm.starboard.ventures/contracts/',
              'https://hyperspace.filscan.io/',
              'https://beryx.zondax.chfor',
            ],
          },
        ],
      })
      .then((res: XMLHttpRequestResponseType) => {
        console.log('added hyperspace successfully', res);
      })
      .catch((err: ErrorEvent) => {
        console.log('Error adding hyperspace network', err);
      });
  }
};

Вызвать функцию чеканки контракта в режиме записи....

// Pass in the metadata return from saving to NFT.Storage
const mintNFT = async (metadata: any) => {  
    await connectedWriteBacalhauContract
    // The name of our function in our smart contract
    .mintBacalhauNFT(
      userWallet.accounts[0], //users account to use
      metadata.url //test ipfs address
    )
    .then(async (data: any) => {
      console.log('CALLED CONTRACT MINT FUNCTION', data);
      await data
        .wait()
        .then(async (tx: any) => {
          console.log('tx', tx);
//CURRENTLY NOT RETURNING TX - (I use event triggering to know when this function is complete)
          let tokenId = tx.events[1].args.tokenId.toString();
          console.log('tokenId args', tokenId);
          setStatus({
            ...INITIAL_TRANSACTION_STATE,
            success: successMintingNFTmsg(data),
          });
        })
        .catch((err: any) => {
          console.log('ERROR', err);
          setStatus({
            ...status,
            loading: '',
            error: errorMsg(err.message, 'Error minting NFT'),
          });
        });
    })
    .catch((err: any) => {
      console.log('ERROR1', err);
      setStatus({
        ...status,
        loading: '',
        error: errorMsg(
          err && err.message ? err.message : null,
          'Error minting NFT'
        ),
      });
    });
}

Уууу - NFT отчеканен!! Время танца единорога!

🌟 Заключительные мысли: Возможности для ИИ и amp; Блокчейн

Bacalhau хорошо подходит для выполнения повторяющихся детерминированных заданий по обработке данных.

  • Процессы ETL
  • Машинное обучение и amp; ИИ
  • Интеграция данных Интернета вещей
  • Пакетная обработка, в том числе для
  • Финансовые и рыночные данные
  • Видео и видео Обработка изображений – отличный вариант для креативщиков.

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

Пока Bacalhau занят созданием интеграции для прямого вызова Bacalhau из смарт-контрактов FEVM, вот некоторые мысли о сотрудничестве Bacalhau x FVM:

* Помощь в подключении и удалении данных Filecoin в будущем * Помогите создать уровень репутации и качества обслуживания для Filecoin, обрабатывая полученные в сети данные о сделках и поставщиках хранилищ. * Bacalhau может предоставить расчеты для рынка &amp;amp;amp;amp;amp;amp; платежные данные * Bacalhau может помочь с обработкой данных из DAO’s & DataDAO * Bacalhau может помочь расширить возможности автоматизации для творческих начинаний, таких как обработка видео и изображений. * Bacalhau может включить обработку данных игры и метавселенной, в том числе для VR & АР. * Бакальяу, IOT & Возможно моделирование * ИИ и усилитель; Приложения машинного обучения

🐠 Дорожная карта Бакальяу

В настоящее время мы разрабатываем способ запуска Bacalhau прямо из ваших смарт-контрактов!!!! Этот проект называется Project Frog / Project Lilypad и станет уровнем интеграции, который позволит вызывать задания Bacalhau из смарт-контрактов FEVM.

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

✍️ Оставайтесь на связи!

Поздравляю, если вы дочитали до конца!!!

Я был бы признателен за лайк, комментарий, подписку или репост, если это было полезно для вас! <3

Оставайтесь на связи с Bacalhau!

* Твиттер @BacalhauProject * YouTube @BacalhauProject * Filecoin Project Slack #bacalhau @filecoinproject * Github @bacalhau.org * Форум github.com/filecoin-project/bacalhau/discussions

С ♥️ DeveloperAlly

Вы нашли эту статью полезной?

Поддержите Элисон Хейр, став спонсором. Любая сумма приветствуется!

Подробнее о спонсорах Hashnode

:::информация Также опубликовано здесь.

:::


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