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

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

27 апреля 2023 г.

Подробное руководство о том, как создать сайт с открытым исходным кодом, этичный сайт для создания произведений искусства, например Waterlily.ai, с Bacalhau и FVM.< /h2>

🎮 Попробуйте!

Прямая трансляция на FVM по адресу www.waterlily.ai

https://youtu.be/oXNaCs0eyXI

📌 Быстрые ссылки

:::подсказка Github: https://github.com/bacalhau-project/Waterlily

Документы: https://docs.bacalhau.org

:::

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

В этом руководстве я расскажу вам, как создать приложение, аналогичное Waterlily.ai на FEVM и Bacalhau, в том числе:< /p>

* Создание и развертывание смарт-контракта Solidity на виртуальной машине Filecoin. * Использование Lilypad для объединения FVM с функциями AI, такими как StableDiffusion. * Построение интерфейсных взаимодействий с пользовательским интерфейсом в Typescript. * Бонус: создание модели Stable Diffusion с открытым исходным кодом для запуска на Bacalhau. * Бонус: тонкая настройка моделей с Dreambooth * Бонус: создание частного кластера Bacalhau для запуска ваших скриптов и моделей

В этом руководстве предполагается некоторое знание JavaScript и Solidity.

🗺️ Обзор

Нет никаких сомнений в том, что ИИ достиг фазы массового внедрения технологического цикла и меняет отрасли и создает новые возможности в самых разных областях.

Использование моделей стабильной диффузии для генеративного искусственного интеллекта не является исключением: Stability.ai, Midjourney, Dall-E и другие создают модели стабильной диффузии. известное имя.

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

Getty Images Sues Stability AI over copyright Infringement

Waterlily.ai – это экспериментальное приложение, призванное предоставить альтернативное этическое решение путем создания новой парадигмы создания изображений с помощью ИИ, оригинальный автор и предлагает художникам новый источник дохода.

Для этого Waterlily использует прозрачность & ненадежность блокчейна, смарт-контракты FVM в сочетании с децентрализованной платформой граничных вычислений в Bacalhau, чтобы обеспечить интуитивно понятный пользовательский интерфейс для генеративного искусственного интеллекта, в то же время компенсируя создателям их оригинальную работу при каждом вызове создания изображения.

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

Waterlily.ai Rainbow Unicorn Generation with Claude Monet Model

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

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

User Generated Images in the style of specified artists

🥞 Стек технологий

Waterlily опирается на два основных технологических инструмента:

FVM (виртуальная машина Filecoin): сеть блокчейнов, совместимая с Filecoin EVM, в которой развернуты смарт-контракты и которая действует как платежный уровень, обеспечивает проверяемую запись транзакций для происхождения произведений искусства и размещает любые NFT. .

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

На изображении ниже показан отличный контекст для звездной карты Filecoin с FVM = Programmable Application и Bacalhau = Computation Services.

Filecoin - 4 key infrastructure components for a decentralised internet

Виртуальная машина Filecoin (FVM)

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

Забавный факт: 14 марта — это 3,14 в американской дате и времени или первые 3 цифры PI, которые также являются идентификатором сети основной сети FVM!

Luke Marsden: Bacalhau Project Report

Бакальяу

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

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

Сделать обработку данных и вычисления открытыми и доступными для всех & В Bacalhau возможно ускорить время обработки, во-первых, за счет использования пакетной обработки на нескольких узлах и, во-вторых, за счет размещения узлов обработки там, где хранятся данные!

Bacalhau Architecture Diagram

FVM + Bacalhau = лучше вместе

Подробнее об этом позже ;)

How Filecoin, FVM, Bacalhau & Lilypad Interact

Деталь инструмента Waterlily Tech

  • Смарт-контракты [Solidity, Open Zeppelin]
  • Solidity — это объектно-ориентированный язык программирования смарт-контрактов для блокчейнов, совместимых с Ethereum (EVM) (используется для контракты)
  • Open Zeppelin предлагает проверенную на безопасность библиотеку реализации распространенных компонентов смарт-контрактов и контрактов (мы используем это на основании договора NFT)
  • IDE Smart Contract [Hardhat, Remix]
  • Hardhat – это среда разработки для редактирования, компиляции, отладки и отладки. развертывание программного обеспечения Ethereum
  • Remix Ethereum IDE — это не требующий настройки инструмент с графическим интерфейсом в браузере для разработки смарт-контрактов.
  • Блокчейн-сеть [виртуальная машина Filecoin]
  • FVM — это EVM-совместимая основная сеть, построенная на блокчейне Filecoin.
  • Хранилище метаданных NFT [NFT.Storage]
  • NFT.Storage – это общественное благо, построенное на базе IPFS & Filecoin для неизменного и постоянного хранения метаданных NFT & предлагает бесплатное децентрализованное хранилище для NFT и пакет SDK для JavaScript.
  • Хранилище метаданных исполнителя [Web3.Storage]
  • Как и в случае с NFT.Storage, с web3.storage вы получаете все преимущества децентрализованного хранилища и других передовых протоколов, а также удобство работы, которое вы ожидаете от современного рабочего процесса разработки.
  • Внешний интерфейс [NextJS / Typescript + NPM]
  • Наверное, мы все это знаем... верно? :Р
  • Взаимодействия смарт-контрактов от клиента [Metamask, Ethers, узел Chainstack RPC]
  • Используя общедоступный RPC-узел, я могу получать взаимодействия только для чтения с моим блокчейн-контрактом.
  • С поставщиком Metamask (или аналогичным кошельком, который внедряет Ethereum API, указанный в EIP-1193. в браузере), мы разрешаем вызовы записи в контракт блокчейна.
  • Ethersjs — это библиотека для взаимодействия со смарт-контрактами, совместимыми с EVM
  • .
  • AI Стабильный скрипт распространения текста в изображение [Python, Tensorflow, Docker]
  • TensorFlow – это платформа и библиотека машинного обучения с открытым исходным кодом, которая предоставляет предварительно обученные модели и другие данные и инструменты машинного обучения.
  • Децентрализованное вычисление вне сети для создания искусственного интеллекта и преобразования текста в изображение. Обучение модели [Bacalhau]
  • Bacalhau – это открытая одноранговая вычислительная сеть, которая предоставляет платформу для общедоступных, прозрачных и при необходимости проверяемых вычислительных процессов. Это децентрализованный уровень вычисления данных вне сети.

🏗️ Создание смарт-контрактов для Waterlily.ai

Ура, преамбула завершена — давайте напишем код!

Создание смарт-контрактов на FEVM

FVM — это полностью совместимая с EVM сеть (отсюда и аббревиатура — FEVM). Это означает, что у нас есть не только доступ ко всем вызовам API сети децентрализованного хранения Filecoin, мы можем также используйте Solidity для написания наших контрактов и воспользуйтесь всеми замечательными инструментами & проверенные контракты также доступны нам в сети EVM (и улучшать их!).

Woo-hoo (dev-style) party time 🎉

Этот проект опирается на 2 основных контракта:

  1. Контракт артиста, который может добавлять и редактировать данные исполнителя.
  2. Контракт NFT для создания NFT.

Контракт NFT

Начнем с контракта NFT, о котором я уже писал в этом руководстве: https://developerally.com/build-your-own-ai-generated-art-nft-dapp.

Когда я создавал демонстрационное приложение выше, моей главной целью было помочь людям научиться использовать как FVM, так и Bacalhau. Тем не менее, пока я его делал, меня очень беспокоил тот факт, что я использовал ИИ для создания рисунков, не зная, откуда берутся эти наборы данных (т. е. рисунки), используемые для обучения используемой мной модели машинного обучения OSS. Это было одним из мотивов создания Waterlily.ai.

Контракт NFT в этом приложении на самом деле ничем не отличается от описанного выше, так что не стесняйтесь читать раздел «Создание и развертывание контракта NFT Solidity», если вы хотите воспроизвести контракт NFT. 😎🫘

Контракт артиста

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
import "hardhat/console.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

/**
    @notice An experimental contract for POC work to call Bacalhau jobs from FVM smart contracts
*/
contract Waterlily is Ownable {
    //ok now what...
}

Контракт артиста требует следующей информации:

Модели данных

  • Модель исполнителя (структура) для хранения данных, которые нам нужны об исполнителе, таких как его имя.
  • адрес платежного кошелька
  • идентификатор для их конкретной модели машинного обучения, которая будет запускаться при выборе (мы специально использовали этот идентификатор и сделали его CID IPFS, который содержит метаданные, которые художник вводит при регистрации в Waterlily)
  • подробности об их доходах и платежах FIL от Waterlily
  • несколько удобных помощников в виде сопоставления id=>Artist и массива для чтения пользовательским интерфейсом
struct Artist {
    string id; // A CID containing all metadata about an artist
    address wallet; // Their FEVM wallet address for payments
    uint256 escrow; // amount of FIL owed to an Artist 
    uint256 revenue; // total amt of revenue earned since joining
    uint256 numJobsRun; // total numbner of jobs run on the artist
    bool isTrained; // a flag for if the Artist Model is ready
}
// mapping of artist id onto artist struct
mapping (string => Artist) artists;
// we need this so we can itetate over the artists in the UI
string[] artistIDs;

* Модель изображения (структура) для хранения информации о сгенерированных изображениях, включая * Владелец изображения * Оригинальный художник, из которого было создано изображение * Быстрый ввод, используемый для создания изображения * Ссылка на сгенерированное изображение или статус изображения в противном случае (например, если есть ошибка) * Помощники для удобства: Сопоставление клиента с изображениями, принадлежащими покупателю, и открытие контракта Zeppelin Counters для создания идентификатора изображения

/* ===Imports === */
import "@openzeppelin/contracts/utils/Counters.sol";

/* === Contract vars below === */
using Counters for Counters.Counter; 
Counters.Counter private _jobIds; // the image id - this is technically a Bacalhau job - hence the name _jobIds

struct Image {
    uint id; // each image created has a unique id like an NFT
            // we're leveraging OpenZeppelin counter contract to make                                 
            // these id's (see end of code here)
    uint jobId; // the id of the bacalhau job returned from lilypad bridge
    address customer; // the wallet owner for this image
    string artist; // the original artist model id of this image
    string prompt; // the text prompt used to generate the image
    string ipfsResult; // the image result: this is an IPFS CID returned by the script
    string errorMessage; // if error occurred running the job
    bool isComplete; // is the Stable Diffusion job complete?
    bool isCancelled; // if the job was cancelled.
}

mapping (uint => Image) images; // mapping of id to the Image
mapping (address => uint[]) customerImages; // a map of customer address onto images they have submitted

Методы (сеттеры, геттеры и события) — вещи, которые нам нужны для взаимодействия с контрактом

  • Исполнитель:
  • getArtistIds() * нам нужно знать всех артистов в контракте, чтобы пользовательский интерфейс мог их отобразить
  • getArtistById() * получить определенного исполнителя
  • artistWithdrawFunds(адрес, подлежащий выплате _кому) *Функция onlyArtist — эта функция предоставляет художнику возможность в любое время снять свои доходы. В противном случае контракт хранит средства на условном депонировании до тех пор, пока артист не заработает 5FIL, чтобы расходы на газ не уменьшили его общий доход.
  • CreateArtist(string calldata _ArtistId) * Регистрация исполнителя автоматизирована. Эта функция вызывается, когда новый исполнитель регистрируется в пользовательском интерфейсе.
  • DeleteArtist() *функция *ownerOnly – гарантирует, что злоумышленники, загружающие незаконные произведения искусства или произведения, не принадлежащие интеллектуальной собственности, могут быть удалены с платформы
  • UpdateArtistWallet() Функция *ownerOnly или artistOnly - только сам артист или владелец контракта (на всякий случай смены публичного благотворительного адреса или утраты приватного ключа проверенным Артистом) можно изменить адрес кошелька исполнителя!
  • События. Давайте также создадим несколько событий для полезных для регистрации вхождений в контракте, таких как
    • При добавлении исполнителя
    • Когда исполнитель удален
    • При обновлении кошелька исполнителя
    • Когда артисту платят
/** The events tracked by the contract *//
event ArtistAdded(string indexed artistId);
event ArtistDeleted(string indexed artistId);
event ArtistWalletUpdated(string indexed artistId, address prevWallet, address curWallet);
event ArtistPaid(string indexed artistId, address _paidTo, uint256 amountPaid);

/** self explanatory XD **/     
function getArtistIds() public view returns (string[] memory) {
   return artistIDs; // we defined this earlier!
}

/** self explanatory XD **/ 
function getArtistById(string calldata _artistId) public view returns (Artist memory) {
   return artists[_artistId]; // we defined this earlier!
}

/**
    @notice This function allows an Artist to withdraw their earnings at any time. Which will be possible to do through the UI in future.
*/
function artistWithdrawFunds(string calldata _artistId) public payable {
   require(bytes(artists[_artistId].id).length > 0, "artist does not exist"); // check that the artistID exists
   Artist storage artist = artists[_artistId];
   require(artist.wallet == msg.sender, "only the artist's wallet can call this function"); // only the artist wallet associated with this artistId can withdraw the funds!
   require(artist.escrow > 0, "artist does not have any money to withdraw"); 
   uint256 escrowToSend = artist.escrow;
   artist.escrow = 0; // No reentrancy issues for us please!
   address payable to = payable(msg.sender);
   to.transfer(escrowToSend);
   emit ArtistPaid(_artistId, to, escrowToSend);
}

/**
    @notice This function is used by the UI. The UI takes in all the artist details from an onboarding form and generates an IPFS CID from their public metadata. It then uses this as the unique artist Id. To disincentivise bad actors, we've also add a small upfront payment to sign on as an artist, which also covers our costs to generate images and update contract details of a new artist. While it's true that anyone could call this function with any string, there is no real value to doing so as no model will be trained nor will it display on the UI. We'll see more on that later!
*/
function createArtist(string calldata _artistId) external payable {
   require(bytes(_artistId).length > 0, "please provide an id");
   require(bytes(artists[_artistId].id).length == 0, "artist already exists");
   require(msg.value >= artistCost, "not enough FIL sent to pay for training artist"); // this is a variable defined in the contract by the admin of the contract & can be changed as needed for gas cost coverage.
   if(bytes(artists[_artistId].id).length == 0) {
        artistIDs.push(_artistId);
    }
   artists[id] = Artist({
        id: _artistId,
        wallet: msg.sender,
        escrow: 0,
        revenue: 0,
        numJobsRun: 0,
        isTrained: false
    });
    emit ArtistAdded(_artistId);
}

/**
    @notice This function takes advantage of Open Zeppelin's Ownable contact to ensure that only the contract deployer can remove an Artist. -> import "@openzeppelin/contracts/access/Ownable.sol";
        -> contract Waterlily is Ownable {}
*/
function deleteArtist(string calldata id) public onlyOwner {
    require(bytes(artists[id].id).length > 0, "artist does not exist");
    Artist storage artist = artists[id];
    require(artist.escrow == 0, "please have the artist withdraw escrow first"); // they have money still to claim
    delete(artists[id]);
        // remove from artistIDs
    for (uint i = 0; i < artistIDs.length; i++) {
        if (keccak256(abi.encodePacked((artistIDs[i]))) ==       
                keccak256(abi.encodePacked((id)))) 
        {
             artistIDs[i] = artistIDs[artistIDs.length - 1];
             artistIDs.pop();
             break;
         }
     }
}

/**
    @notice Example of UpdateArtist function to change an Artist's wallet address. Passing in the artistId helps us find the Artist details
*/
function updateArtistWallet(string calldata _artistId, address _newWalletAddress) public {
    require(bytes(artists[_artistId].id).length > 0, "artist does not exist");
    Artist storage artist = artists[id];
    require(artist.wallet == msg.sender || msg.sender == owner(), "only the artist's wallet can call this function"); //artist or owner
    artist.wallet = _newWalletAddress;
}

Здоровенькая дори!!!

Эээ... подождите, а как насчет сгенерированных изображений?!

Кроме того, побочный момент - hunky dory - действительно странное выражение ... откуда оно вообще взялось? Была ли где-нибудь особенно красивая рыба Дори? Но я отвлекся...

These folks look pretty hunky dory...#sorrynotsorry

* Изображений! * getCustomerImages(адрес _ownerAddress) * createImage(string calldata artistId, string calldata _textPrompt) * События: ImageJobCall (подожди... как это называется... читай, друг!)

event ImageJobCalled(Image image)

/** Takes the owner address & returns images associated with it **/
function getCustomerImages(address customerAddress) public view returns (uint[] memory) {
    return customerImages[customerAddress];
}

/** Creates a Stable Diffusion image from a text prompt input with the specific Artist ML model... or does it...? **/
function createImage(string calldata _artistId, string calldata _prompt) external payable {
    require(bytes(artists[_artistId].id).length > 0, "Artist does not exist"); // make sure the Artist model exists
    require(artists[_artistId].isTrained, "Artist has not been trained"); // make sure the Artist ML model is trained
    require(msg.value >= imageCost, "Not enough FIL sent to pay for image generation"); // the _imageCost is another variable set by the contract. This is the money the artist will receive (outside of gas fees for returning the image.... more on that soon)

    _imageIds.increment(); // increment the image id aka Bacalhau jobId
    uint id = _imageIds.current();

    // create the details of this new AI-generated image
    images[id] = Image({
        id: id,
        jobId: 0,
        customer: msg.sender,
        artist: _artistId,
        prompt: _prompt,
        ipfsResult: "",
        errorMessage: "",
        isComplete: false,
        isCancelled: false
    });
    customerImages[msg.sender].push(id); // add the image to the customer owned images
    emit ImageJobCalled(images[id]); // emit an event saying the image generation function has been called... 

    // if they have paid too much then refund the difference assuming it's a mistake (a user can always donate to an Artist from the Waterlily UI :) )
    uint excess = msg.value - imageCost;
    if (excess > 0) {
        address payable to = payable(msg.sender);
        to.transfer(excess);
    }
}

Сообразительные читатели могут задаться вопросом... но где и как именно генерируется изображение? Этот код функции createImage() просто создает структуру данных типа изображения и сохраняет ее в контракте — он ничего не делает для фактического создания изображения... 🤔

Вот тут-то и появляется Project Lilypad!

Lilypad — подключение смарт-контрактов FEVM для запуска Stable Diffusion на Bacalhau

Project Lilypad – это "ретранслятор" или "мост", который позволяет пользователям вызывать задания Bacalhau (например, стабильное распространение). из смарт-контракта и вернуть результаты детерминированной работы в свой контракт.

Вспоминаем: Bacalhau — это одноранговая сеть узлов (включая узлы с поддержкой графического процессора), которая может выполнять любое задание Docker или образ WASM.

:::подсказка Очень важное примечание. вам, вероятно, следует прекратить прокрутку & прочитай меня 🚩🫶 !

Lilypad в настоящее время находится на стадии альфа-тестирования. Это означает, что, хотя он полностью функционален, нет никаких гарантий надежности или безопасности сеть. Он развертывается в калибровочной сети FVM, тестовой сети FVM Hyperspace и основной сети FVM и подключается к общедоступному p2p Сеть Бакальяу.

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

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

:::

How Lilypad Works

На практике Lilypad соединяет FVM и Bacalhau (покажите мне код!):

  • Разработчик реализует интерфейс Lilypad в своем собственном смарт-контракте.

```javascript / === Интерфейс Lilypad === / прочность прагмы >=0,8,4; перечисление LilypadResultType { CID, стандартный выход, StdErr, код выхода

интерфейс LilypadCallerInterface { функция lilypadFulfilled(адрес _from, uint _jobId, LilypadResultType _resultType, строка calldata _result) внешняя; функция lilypadCancelled(адрес _from, uint _jobId, строка calldata _errorMsg) внешняя;

/ === Пример пользовательского контракта === / контракт MyContract — это LilypadCallerInterface {

  function lilypadFulfilled(address _from, uint _jobId,   
      LilypadResultType _resultType, string calldata _result)        
      external override {
      // Do something when the LilypadEvents contract returns    
      // results successfully
  }

  function lilypadCancelled(address _from, uint _jobId, string 
      calldata _errorMsg) external override {
      // Do something if there's an error returned by the
      // LilypadEvents contract
  }

} ```

* Разработчик также должен вызвать функцию LilypadEvents 'runLilypadJob()' из своего контракта с допустимой спецификацией, которая соответствует образу Docker.

💡 Если вы хотите узнать больше о том, как будет выглядеть действительное изображение для Bacalhau, или увидеть несколько примеров, ознакомьтесь с документами Bacalhau или ознакомьтесь с пример кода стабильной диффузии представлен на Lilypad github.

```javascript импортировать "./LilypadEvents.sol"; //пожалуйста, проверьте github на предмет актуального контракта, инструкций и усилителей; нужные адреса! импортировать "./LilypadCallerInterface.sol";

/ === Пример пользовательского контракта === / контракт MyContract — это LilypadCallerInterface { адрес оплачиваемый public lilypadEventsAddress;

  constructor (address payable _eventsContractAddress) {
      lilypadEventsAddress = _eventsContractAddress;
  }

  function callLilypadEventsToRunBacalhauJob() external payable     {
      // Require at least 0.03 FIL to be sent
      require(msg.value >= 30000000000000000, "Insufficient payment"); //this is the default amount 0.03FIL required to run a job. Check the github docs for more info
       string memory spec = "StableDiffusion";

       // Call the function in the other contract and send fee
      (bool success, uint256 jobId) = lilypadBridgeAddress.call{value: lilypadFee}(abi.encodeWithSignature("runLilypadJob(address, bytes, uint256)", address(this), spec, uint256(LilypadResultType.CID)));
      require(success, "Failed to call the lilypad Events function to run the job.");
      //do something with the returned jobId
  }

} ```

:::подсказка 👉 См. контракт LilypadEvents в ремиксе

👉 См. LilypadCallerInterface в ремиксе

:::

Это все, что нужно на стороне контракта, чтобы вызвать задание Bacalhau для запуска из вашего собственного смарт-контракта FEVM! Мы только что создали децентрализованное приложение, которое не только децентрализовано внутри сети, но и использует децентрализованный сервис вычислений для запуска модели машинного обучения!

^ Totally you right now

Все, что нужно сделать сейчас, это составить полный контракт и развернуть его!

Примечание об учебном пособии по сравнению с рабочей кодовой базой:

В приведенных выше примерах показано, как можно создать DApp с функциональностью, аналогичной Waterlily.ai.

На практике, однако, Waterlily использует частный контракт LilypadEvents и сервис моста (который обеспечивает выполнение только вычислительных заданий, вызываемых из контракта Waterlily), и который подключается к частному кластеру узлов Bacalhau для запуска уникальных стабильных заданий распространения каждого исполнителя из FEVM. смарт-контракт. Кроме того, это упрощает логику, необходимую для работы с отдельными моделями машинного обучения художников. Waterlily.ai был разработан таким образом, чтобы мы могли защитить модели Artist ML (которые не являются общедоступной интеллектуальной собственностью) и гарантировать, что изображения не могут быть созданы без платы за оплату для оригинальных художников. ❤️

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

Вы можете перейти к дополнительным разделам ниже, чтобы увидеть более подробную информацию о создании частного кластера Bacalhau, а также о том, как вы можете обучать уникальные модели художников и amp; передайте их спецификацию, хотя! 😉

The world is your oyster fren...

Полный контракт

Ниже приведена полная копия рабочего контракта Solidity, использующего спецификацию Lilypad StableDiffusion.

Brace yourself... Lengthy code snippet incoming...

:::подсказка 👉 Нажмите здесь, чтобы открыть этот контракт в ремиксе!

:::

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
import "hardhat/console.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
import "./LilypadEventsUpgradeable.sol";
import "./LilypadCallerInterface.sol";

error WaterlilyError();

contract Waterlily is LilypadCallerInterface, Ownable {
    /** General Variables */
    address public lilypadBridgeAddress;
    uint256 lilypadFee = 0.03 * 10**18; //default fee

    // These are used in order to ensure the lilypad wallet can cover gas fees
    // for returning an image to the contract on FVM mainnet. (no charge on testnet, we auto topup tFIL)
    // The bacalhau network doesn't currently charge for compute use.
    uint256 public computeProviderEscrow;
    uint256 public computeProviderRevenue;

    uint256 public imageCost = 0.13 * 10**18; 

    uint256 public artistCommission = 0.1 * 10**18;
    uint256 public artistCost = 0.1 * 10**18;
    uint256 public artistAutoPaymentMin = 3 * 10**18;


    /** Contract Events **/
    event ArtistCreated(string indexed artistId);
    event ArtistDeleted(string indexed artistId);
    event ArtistWalletUpdated(string indexed artistId, address _prevWallet, address _curWallet);
    event ArtistPaid(string indexed artistId, address _paidTo, uint256 amountPaid);
    event ImageJobCalled(Image image);
    event ImageComplete(Image image); //we shouldn't broadcast the results publicly in reality.
    event ImageCancelled(Image image);

    /** Artist Data Structures / vars **/
    struct Artist {
        string id;
        address wallet;
        uint256 escrow;
        uint256 revenue;
        uint256 numJobsRun;
        bool isTrained;
    }
    mapping (string => Artist) artists;
    string[] artistIDs;

    /** Image Events **/
    event ImageCreated(Image image);

    /** Image Data Structures / vars **/
    using Counters for Counters.Counter; 
    Counters.Counter private _imageIds;

    struct Image {
        uint id;
        uint jobId; //the returned id for a job run by Lilypad.
        address customer;
        string artist; 
        string prompt; 
        string ipfsResult;
        string errorMessage;
        bool isComplete;
        bool isCancelled;
    }

    mapping (uint => Image) images; 
    mapping (address => uint[]) customerImages;

    /** Initialise our contract vars **/
    constructor(address _lilypadEventsContractAddress){
        lilypadBridgeAddress = _lilypadEventsContractAddress;
    }

        /** Artist Functions **/
    function getArtistIds() public view returns (string[] memory) {
        return artistIDs; 
    }

    function getArtistById(string calldata _artistId) public view returns (Artist memory) {
        return artists[_artistId];
    }

    function artistWithdrawFunds(string calldata _artistId) public payable {
        require(bytes(artists[_artistId].id).length > 0, "artist does not exist");
        Artist storage artist = artists[_artistId];
        require(artist.wallet == msg.sender, "only the artist's wallet can call this function");
        require(artist.escrow > 0, "artist does not have any money to withdraw"); 
        uint256 escrowToSend = artist.escrow;
        artist.escrow = 0;
        address payable to = payable(msg.sender);
        to.transfer(escrowToSend);
    }

    function createArtist(string calldata _artistId) external payable {
        require(bytes(_artistId).length > 0, "please provide an id");
        require(bytes(artists[_artistId].id).length == 0, "artist already exists");
        require(msg.value >= artistCost, "not enough FIL sent to pay for training artist");
        if(bytes(artists[_artistId].id).length == 0) {
            artistIDs.push(_artistId);
        }
        artists[_artistId] = Artist({
                id: _artistId,
                wallet: msg.sender,
                escrow: 0,
                revenue: 0,
                numJobsRun: 0,
                isTrained: false
         });
        emit ArtistCreated(_artistId); 
        // we don't directly trigger training of models via this function
        // though it is possible to do with lilypad
    }

    function deleteArtist(string calldata id) public onlyOwner {
        require(bytes(artists[id].id).length > 0, "artist does not exist");
        Artist storage artist = artists[id];
        require(artist.escrow == 0, "please have the artist withdraw escrow first"); // they have money still to claim
        delete(artists[id]);
        for (uint i = 0; i < artistIDs.length; i++) {
            if (keccak256(abi.encodePacked((artistIDs[i]))) ==       
                    keccak256(abi.encodePacked((id)))) 
            {
                artistIDs[i] = artistIDs[artistIDs.length - 1];
                artistIDs.pop();
                break;
            }
        }
    }

    function updateArtistWallet(string calldata _artistId, address _newWalletAddress) public {
        require(bytes(artists[_artistId].id).length > 0, "artist does not exist");
        Artist storage artist = artists[_artistId];
        require(artist.wallet == msg.sender || msg.sender == owner(), "only the artist's wallet can call this function"); //artist or owner
        artist.wallet = _newWalletAddress;
    }

    /** Image Functions **/
    function getCustomerImages(address customerAddress) public view returns (uint[] memory) {
        return customerImages[customerAddress];
    }

    function createImage(string calldata _artistId, string calldata _prompt) external payable {
        require(bytes(artists[_artistId].id).length > 0, "Artist does not exist");
        require(artists[_artistId].isTrained, "Artist has not been trained");
        require(msg.value >= imageCost, "Not enough FIL sent to pay for image generation");

        _imageIds.increment();
        uint id = _imageIds.current();

    /** This is where we call the lilypad bridging contract */
        /** NOTE: In reality each of your artists would need a different bacalhau job spec as each artist uses a unique model to generate images in their specific style. Note the "Image" provided - a generic stable diffusion job **/
    string constant specStart = '{'
        '"Engine": "docker",'
        '"Verifier": "noop",'
        '"Publisher": "estuary",'
        '"Docker": {'
        '"Image": "ghcr.io/bacalhau-project/examples/stable-diffusion-gpu:0.0.1",'
        '"Entrypoint": ["python", "main.py", "--o", "./outputs", "--p", "';

    string constant specEnd =
        '"]},'
        '"Resources": {"GPU": "1"},'
        '"Outputs": [{"Name": "outputs", "Path": "/outputs"}],'
        '"Deal": {"Concurrency": 1}'
        '}';

        //How the spec is managed to add the prompt is a little awkward atm, but alpha's always improve :)
        string memory spec = string.concat(specStart, _prompt, specEnd);

        // Cast enum value to uint8 before passing as argument (yes this is DX we want to fix :P)
        uint8 resultType = uint8(LilypadResultType.CID);

         // Call the function in the other contract and send funds
        (bool success, bytes memory result) = lilypadBridgeAddress.call{value: lilypadFee}(abi.encodeWithSignature("runLilypadJob(address, string, uint8)", address(this), _spec, resultType));
        require(success, "Failed to call the lilypad Events function to run the job.");
        uint jobId;
        assembly {
            jobId := mload(add(result, 32))
        }

        images[id] = Image({
            id: id,
            jobId: jobId,
            customer: msg.sender,
            artist: _artistId,
            prompt: _prompt,
            ipfsResult: "",
            errorMessage: "",
            isComplete: false,
            isCancelled: false
        });

        customerImages[msg.sender].push(id);

        emit ImageJobCalled(images[id]);
        uint excess = msg.value - imageCost;
        if (excess > 0) {
            address payable to = payable(msg.sender);
            to.transfer(excess);
        }
    }

    /** Lilypad Functions (as I said - this is in alpha!) **/
    function lilypadFulfilled(address _from, uint _jobId, LilypadResultType _resultType, string calldata _result) external override {
    //the image has come back successfully
        Image storage image = images[_jobId];
        // sanity check that the image exists with that ID
        require(image.id > 0, "image does not exist");
        require(image.isComplete == false, "image already complete");
        require(image.isCancelled == false, "image was cancelled");

        // get a reference to the artist for this image
        Artist storage artist = artists[image.artist];

        // edge case where we deleted the artist in between the job
        // starting and completing
        require(bytes(artist.id).length > 0, "artist does not exist");

         // update the result of the image
        image.ipfsResult = _result;
        image.isComplete = true;

        // update the artist details accordingly
        artist.escrow += artistCommission;
        artist.revenue += artistCommission;
        artist.numJobsRun += 1;
        computeProviderEscrow += imageCost - artistCommission;
        computeProviderRevenue += imageCost - artistCommission;

        //auto-pay the artist if their escrow is > AutopayMinValue
        if(artist.escrow > artistAutoPaymentMin){
            uint256 escrowToSend = artist.escrow;
            address payable recipient = payable(artist.wallet);
            //should check contract balance here before proceeding
            artist.escrow = 0;
            recipient.transfer(escrowToSend);
            emit ArtistPaid(artist.id, recipient, escrowToSend);
        }

        emit ImageComplete(image);

    }

    function lilypadCancelled(address _from, uint _jobId, string calldata _errorMsg) external override {
        //something went wrong with the job - refund customer as much as possible
        Image storage image = images[_jobId];

        // sanity check that the image exists with that ID
        require(image.id > 0, "image does not exist");
        require(image.isComplete == false, "image already complete");
        require(image.isCancelled == false, "image was cancelled");

        // mark the image as cancelled and refund the customer
        image.isCancelled = true;
        image.errorMessage = _errorMsg;

        // in reality you might want to subtract the cost of the newtork gas costs for the bridge return here
        uint256 amountRefundable = imageCost; //-lilypad costs
        address payable to = payable(image.customer);
        to.transfer(amountRefundable);

        emit ImageCancelled(image);
    }
}

Вы могли заметить, что в этом контракте есть несколько дополнительных пунктов, которые мы пока не обсуждали, в том числе:

  • Платежи артиста в LilypadFulfilled: этот код выплачивает артисту, когда его условное депонирование достигает определенной суммы. Было принято техническое решение против мгновенных платежей, чтобы артист не терял слишком много денег на оплату газа. Однако в будущих итерациях микроплатежи можно будет включить через замечательный проект в сети FVM под названием State Channels.
  • На практике вы также, вероятно, захотите включить способ обновления глобальных переменных lilypadBridgeAddress, lilypadFee, imageCost, artistCommission, artistCost и artistAutoPaymentMin.

Дополнительно отмечу, что было бы полезно также сделать этот контракт обновляемым с помощью отличного руководства OpenZeppelin по обновляемым контрактам здесь, и вы также можете воспользоваться преимуществами Контракт OpenZeppelin PaymentSplitter для платежей исполнителям вместо того, чтобы обрабатывать это вручную 😎

Развертывание (полного) смарт-контракта в FVM

Правильно, у нас есть контракт! Давайте развернем это на FVM!

Подробности сети FVM

См. https://docs.filecoin.io/networksСм. также chainlist.org

| Название цепи | ПКП | идентификатор цепи | Обозреватель блоков | Смеситель | |----|----|----|----|----| | Калибровочная сеть Filecoin (testnet) | https://api.dication.node.gif.io/rpc/v0 | 314159 | https://калибровка.filscan.io/, | https://faucet.calibration.fildev.network/ | | Filecoin Hyperspace (testnet) | https://api.hyperspace.node.gif.io/rpc/v1, https://hyperspace.filfox.info/rpc/v1, https://filecoin-hyperspace.chainstacklabs.com/rpc/v1, https://rpc. ankr.com/filecoin_testnet | 3141 | https://fvm.starboard.ventures/hyperspace/explorer/tx/, https://explorer.gif.io/, | https://hyperspace.yoga/#faucet | | Сеть Filecoin | https://api.node.gif.io/rpc/v1, https://filecoin-mainnet.chainstacklabs.com/rpc/v1, https://rpc.ankr.com/filecoin | 314 | https://fvm.starboard.ventures/, https ://explorer.gif.io/, https://beryx.zondax.ch/, https://filfox.io/ | |

Существует довольно много способов развертывания этого контракта — я рассмотрю развертывание как с помощью ремикса (доступного в вашем браузере), так и с использованием Hardhat — инструмент среды разработки Ethereum.

Предварительные требования:

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

  1. Настройка & подключить Metamask кошелек (вы можете использовать другие совместимые кошельки, хотя здесь я буду использовать Metamask)
  2. Получите средства для развертывания (в тестовой сети вы можете использовать сборщики, в противном случае см. это руководство)

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

Чтобы добавить его вручную, перейдите в свой кошелек Metamask и нажмите кнопку "Добавить сеть"

Metamask Add Network

Заполните следующие данные и нажмите "Сохранить"

Metamask Add Newtork

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

Обратите внимание, что для крана Calibration Net требуется адрес типа файловой монеты, который вы можете найти, подключив свой кошелек метамаски по адресу https://www.gif.io/.

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

Развертывание в Remix

Развертывание на FVM в режиме ремикса с помощью Metamask Wallet довольно просто. Для этого откройте договор, нажав

:::подсказка 👉 открыть в ремиксе!

:::

Вам также понадобятся контракты Lilypad, поэтому скопируйте их в открытое окно браузера ремиксов (вы можете найти их здесь - мы работаем над импортом npm!).

Когда у вас есть 2 контракта и LilypadCallerInterface в ремиксе, убедитесь, что ваш кошелек метамаски подключен к нужной сети, и у вас есть средства в вашем кошельке.

Перейдите на вкладку компилятора (значок солидности) и нажмите скомпилировать

Remix Compile Contract

Затем перейдите на вкладку Ethereum (значок Ethereum) и выберите «Injected Provider — Metamask» — это приведет к вашей текущей сети Metamask и настройкам кошелька.

Remix Contract with Injected Provider - Metamask

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

Confirm deployment transaction

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

Deployed Contract on Remix

:::подсказка ⚠️ Не забудьте сохранить адрес для использования в нашем интерфейсе!! ⚠️

:::

Развертывание в каске

Hardhat – это среда разработки для редактирования, компиляции, отладки и т. д. развертывание программного обеспечения Ethereum. Поскольку FVM совместим с EVM, мы можем воспользоваться этим инструментом

Hardhat также позволяет использовать локальную сеть и набор для тестирования.

Чтобы настроить проект каски, см. их документы здесь – у них даже есть расширение для кода VS. редакторы.

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

:::

Приватный ключ кошелька, необходимый в конфиге для каски, доступен через Metamask -> данные учетной записи -> экспортировать закрытый ключ, и здесь он хранится в переменной .env.

Find your private key on Metamask

hardhat.config.ts

import { HardhatUserConfig, task } from 'hardhat/config';
import '@nomicfoundation/hardhat-toolbox';
import '@openzeppelin/hardhat-upgrades';
import 'hardhat-deploy';
import { config as dotenvConfig } from 'dotenv';
import { resolve } from 'path';

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

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: 'hardhat',
  namedAccounts: {
    admin: 0,
  },
  networks: {
    hardhat: {},
    localhost: {},
    filecoinHyperspace: {
      url: 'https://api.hyperspace.node.glif.io/rpc/v1', //'https://rpc.ankr.com/filecoin_testnet', //https://filecoin-hyperspace.chainstacklabs.com/rpc/v1, "https://hyperspace.filfox.info/rpc/v1", wss://wss.hyperspace.node.glif.io/apigw/lotus/rpc/v1
      chainId: 3141,
      accounts: [walletPrivateKey],
    },
    filecoinCalibrationNet: {
      url: 'https://api.calibration.node.glif.io/rpc/v0',
      chainId: 314159,
      accounts: [walletPrivateKey],
    },
    filecoinMainnet: {
      url: 'https://api.node.glif.io', //'https://rpc.ankr.com/filecoin_testnet', //https://filecoin-hyperspace.chainstacklabs.com/rpc/v1
      chainId: 314,
      accounts: [walletPrivateKey],
    },
  },
};

export default config;

Теперь воспользуемся скриптом для развертывания Waterlily.sol!

скрипты/deploy.ts

import hre, { ethers } from 'hardhat';

import type { Waterlily } from '../typechain-types/contracts/Waterlily';
import type { Waterlily__factory } from '../typechain-types/factories/contracts/Waterlily__factory';

async function main() {
    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 owner = new ethers.Wallet(
    walletPrivateKey,
    hre.ethers.provider
  );

  console.log('Waterlily deploying from account: ', owner);
  console.log("Account balance:", (await owner.getBalance()).toString());

  let LILYPAD_ADDRESS;
  if(hre.network.name == 'filecoinHyperspace') {
    LILYPAD_ADDRESS = "", //define me
  }
  if(hre.network.name == 'filecoinCalibrationNet') {
    LILYPAD_ADDRESS = "", //define me
  }
  if(hre.network.name == 'filecoinMainnet') {
    LILYPAD_ADDRESS = "", //define me
  }

  const WaterlilyFactory: Waterlily__factory = <Waterlily__factory>await ethers.getContractFactory('Waterlily')

  const waterlilyContract: Waterlily = <Waterlily>await WaterlilyFactory.connect(owner).deploy(
    LILYPAD_ADDRESS
  )
  await waterlilyContract.deployed()

  console.log('Waterlily deployed to ', waterlilyContract.address)
}

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

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

npx hardhat run scripts/deploy.js --network <network-name>

Адрес контракта будет отображаться при успешном развертывании.

:::подсказка ⚠️ Не забудьте сохранить адрес для использования в нашем интерфейсе!! ⚠️

:::

Празднуйте!

Вы только что развернули свои контракты в FVM!

Dancing Unicorn

📺 Создание пользовательского интерфейса

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

Обзор пользовательского интерфейса

В настоящее время пользовательский интерфейс состоит из двух страниц, построенных на NextJS и Typescript. Главная страница – это домашняя страница. Здесь пользователь может создавать новые изображения из подсказок и просматривать профили художников, чтобы выбрать стиль для генеративного искусства. Это также страница, которая показывает коллекцию ранее сгенерированных изображений и NFT пользователя.

Waterlily Home Page

Вторая страница – это форма регистрации исполнителей, с помощью которой новые исполнители могут присоединиться к платформе.

ArtistSignup Form (it's automated!)

Контрактные соединения пользовательского интерфейса

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

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

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

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

//The compiled contract found in hardhat/artifacts/contracts  
import WaterlilyCompiledContract from '@Contracts/Waterlily.sol/Waterlily.json';
//On-chain address of the contract
const contractAddress = '<address here>'; 
//A public RPC Endpoint (see table from contract section)
const rpc = '<chain rpc http';

const provider = new ethers.providers.JsonRpcProvider(rpc);
const connectedReadWaterlilyContract = new ethers.Contract(
      contractAddress,
      WaterlilyCompiledContract.abi,
      provider
    );

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

const imageIDs = await connectedReadWaterlilydContract?.getCustomerImages(customerWallet);

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

//use the read-only connected Bacalhau Contract
connectedReadWaterlilyContract.on(
    // Listen for the specific event we made in our contract
    'ImageComplete',
    (Image: image) => {
        //DO STUFF WHEN AN IMAGEEVENT COMES IN
        // eg. filter if it is this user's and reload generated imagese-display or
        //
    }
);

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

//Typescript needs to know window is an object with potentially and ethereum value. 
declare let window: any;
//The compiled contract found in hardhat/artifacts/contracts  
import WaterlilyCompiledContract from '@Contracts/Waterlily.sol/Waterlily.json';
//On-chain address of the contract
const contractAddress = '<address here>'; 

//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(
      contractAddress,
      WaterlilyCompiledContract.abi,
      provider
    );
   const signer = provider.getSigner();
   const connectedWriteWaterlilyContract = contract.connect(signer);
}

Вызов функции CreateImage() в контракте Waterlily со значением $imageCost:

  const runStableDiffusionJob = async (prompt: string, artistid: string) => {
    try {
      console.log('Calling stable diffusion function in Waterlily contract...');
      const tx = await connectedWriteWaterlilyContract.CreateImage(artistid, prompt, { value: imageCost }); //wait for netowrk to include tx in a block
      const receipt = await tx.wait(); //wait for transaction to be run and return a receipt
    } catch (error: any) {
      console.error(error);
    }
}

Вызов функции чеканки для контракта NFT также описан в этом блоге.< /p>

Подключения кошелька пользовательского интерфейса

Вот несколько полезных функций кошелька, которые могут вам понадобиться, в том числе как проверить chainId или баланс кошелька, а также как программно добавить сеть в Metamask/кошелек. Вы можете напрямую взаимодействовать с кошельками, используя объект Metamask Ethereum, как это делаю я. ниже или с помощью ethers.js или web3.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);
      }
    });
};

const fetchWalletBalance = async (
    account: string
  ): Promise<any> => {
    let balanceNumber: number;
    try {
        const balance = await window.ethereum.request({
            method: 'eth_getBalance',
            params: [account],
          });
        const formattedBalance = ethers.utils.formatEther(balance);
        balanceNumber = parseFloat(formattedBalance);    
    } catch (error) {
      console.error('error getting balance', 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
    });

    // Subscribe to chainId change
    window.ethereum.on('balanceChanged', () => {
      // handle changed balance 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);
  }
};

//AddChain Example
export const addWalletNetwork = async () => {
  console.log('Adding the Hyperspace Network to Wallet...');
  if (window.ethereum) {
    window.ethereum
      .request({
        method: 'wallet_addEthereumChain',
        params: [
          {
            chainId: '',
            rpcUrls: [
              'https://',
              'https://',
            ],
            chainName: 'Filecoin ...',
            nativeCurrency: {
              name: 'tFIL',
              symbol: 'tFIL',
              decimals: 18,
            },
            blockExplorerUrls: [
              'https://fvm.starboard.ventures/contracts/',
              'https://hyperspace.filscan.io/',
            ],
          },
        ],
      })
      .then((res: XMLHttpRequestResponseType) => {
        console.log('added new network successfully', res);
      })
      .catch((err: ErrorEvent) => {
        console.error('Error adding new network', err);
      });
  }
};

💫 Раздел бонусного кода

Бонус: создание & Обучение моделей Artist ML

Ранее я писал о том, как создать собственный скрипт Stable Diffusion с открытым исходным кодом и запустить его на Bacalhau здесь.

https://www.youtube.com/watch?v=53uY48e1lis&t =1454s&embedable=true

В основе тонкой настройки лежит Dreambooth, который делает стабильное распространение еще более мощным благодаря возможности создавать реалистичные изображения людей, животных или любых других объектов. объект, просто обучив их на 20-30 изображениях.

Однако конкретные модели, используемые для Waterlily, были созданы Ричардом Блитманом из Алговера (он делает потрясающую работу — посмотрите на него и его команду!).

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

Проверьте следующие репозитории для большего вдохновения:

Бонус: создание частного кластера узлов Bacalhau

Частный кластер — это сеть узлов Bacalhau, полностью изолированных от любого общедоступного узла. Это означает, что вы можете безопасно обрабатывать частные задания и данные в своем облаке или на локальных хостах. start-pvt-cluster">здесь.

Magical Powers

🌟 Возможности для AI & Блокчейн

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

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

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

https://twitter.com/rpnickson/status/1639813074176679938?s=20&embedable =правда

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

🗺️ Дорожная карта

Waterlily.ai — это репозиторий с открытым исходным кодом. Мы приветствуем вклад через github или представление идей и отзывов. Расскажи нам что ты думаешь! Расскажите нам, что, по вашему мнению, должно произойти дальше, или помогите нам исправить ошибки 🐛🪲🐞

Если вы художник, мы также будем рады услышать от вас! Пожалуйста, свяжитесь :)

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

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

:::подсказка Большое спасибо команде, которая помогла мне разработать и развернуть Waterlily & Lilypad, включая Люка Марсдена, Кая Давенпорта, < a href="https://twitter.com/weswfloyd">@Уэс Флойд, Саймон Уортингтон, Ричард Блитман из Algovera и остальные Bacalhau команда, без которой это приложение было бы невозможно.

:::

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

May the force be with you

С ♥️ DeveloperAlly

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

:::


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