Eine vollständige Anleitung zum Erstellen, Ausführen und Bereitstellen einer DApp mit Ihrem eigenen Text-to-Image-Skript, um KI-generierte Kunst-NFTs auf dem FVM Hyperspace Testnet zu prägen!
In diesem Blog erfahren Sie, wie es geht
Erstellen Sie ein Open-Source-Python-basiertes Text-zu-Bild-Skript basierend auf Tensorflow (Sie können auch einfach den Bacalhau-HTTP-Endpunkt verwenden, wenn dies für Sie nicht von Interesse ist).
Führen Sie dieses Skript auf Bacalhau (einer offenen P2P-Off-Chain-Rechnerplattform) aus.
Erstellen Sie einen NFT-Vertrag in Solidity (basierend auf einem Open Zeppelin ERC721-Vertrag)
Stellen Sie den NFT-Vertrag mit Hardhat im Hyperspace-Testnetz der Filecoin Virtual Machine (FVM) bereit
Front-End-Interaktionen – So interagieren Sie mit dem Text-zu-Bild-Skript von Bacalhau und Ihrem NFT-Vertrag in React
So speichern Sie Ihre NFT-Metadaten in NFT.Storage
So stellen Sie Ihre Front-End-DApp auf Fleek bereit
Ich habe mich bewusst dafür entschieden, so viel Open-Source- und dezentrale Technologie zu verwenden, wie in diesem Stack verfügbar ist.
Dieser Blog wird ziemlich lang sein (hey – ich möchte ALLE INFORMATIONEN geben und sicherstellen, dass wir anfängerfreundlich und inklusiv sind!) – also überspringen Sie einfach die Teile in der Tabelle, die für Sie nützlich sind Inhalt <3
(Verstehen Sie es – es ist ein Pfannkuchenstapel #sorrynotsorry)
Open Source & Web3 von Grund auf wertgeschätzt :)
💡 TLDR-Tipp 💡
Dieses Skript kann bereits über Bacalhau über die CLI und einen HTTP-Endpunkt verwendet werden. Sie können diesen Teil also gerne überspringen.
Kurze Einführung in die stabile Diffusion
Stable Diffusion ist derzeit das führende maschinelle Lernmodell für die Text-zu-Bild-Verarbeitung (und ist das gleiche Modell, das Dall-E verwendet). Es handelt sich um eine Art Deep Learning – eine Teilmenge des maschinellen Lernens, die sich selbst beibringt, eine bestimmte Aufgabe auszuführen – in diesem Fall die Umwandlung einer Texteingabe in eine Bildausgabe.
In diesem Beispiel verwenden wir ein Diffusions-Wahrscheinlichkeitsmodell, das einen Transformator verwendet, um Bilder aus Text zu generieren.
Aber keine Sorge – wir müssen dafür kein Modell für maschinelles Lernen trainieren (obwohl, wenn das Ihr Ding ist, könnten Sie das durchaus tun!)
Stattdessen verwenden wir in unserem Python-Skript ein vorab trainiertes Modell aus der Open-Source-Bibliothek für maschinelles Lernen TensorFlow von Google, da die ML-Gewichte für uns vorberechnet wurden.
Genauer gesagt verwenden wir einen optimierten Keras/TensorFlow-Implementierungszweig des ursprünglichen ML-Modells.
Das Python-Skript
🦄 Eine vollständige Anleitung zum Erstellen und Dockerisieren dieses Text-zu-Bild-Skripts und zum Ausführen auf Bacalhau finden Sie sowohl in den Bacalhau-Dokumenten als auch in diesem YouTube-Video von @BacalhauProject . 🦄 Sie können es auch in diesem Google Collabs-Notizbuch ausführen
Hier ist das vollständige Python-Skript!
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")
Das obige Skript nimmt einfach ein Texteingabeargument und einige andere optionale Parameter auf und ruft dann die gespaltene TensorFlow-Bibliothek auf, um die Bilder zu generieren und sie in einer Ausgabedatei zu speichern.
Die ganze schwere Arbeit, die hier geleistet wird, findet im folgenden Abschnitt statt – hier entfaltet das Modell des maschinellen Lernens seine Wirkung. 🪄
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, )
Großartig, wir können Bilder aus einer Textaufforderung generieren, aber ähm ... wo soll dieses GPU-erforderliche Skript ausgeführt werden ... 🤔🤔
Wenn es eine Sache gibt, die die Blockchain-Technologie von Natur aus nicht gut kann, dann ist es die Verarbeitung großer Datenmengen. Dies liegt an den Kosten für die Rechenleistung über ein verteiltes System, um andere leistungsstarke Eigenschaften wie Vertrauenslosigkeit und Zensurresistenz bereitzustellen.
Es ist möglich, Ihren lokalen Computer für kleine Beispiele zu verwenden – tatsächlich habe ich es geschafft, dieses spezielle Beispiel auf meinem (sehr unzufriedenen) Mac M1 zum Laufen zu bringen, allerdings musste ich sehr lange auf die Ergebnisse warten (jemand Tischtennis spielen?) Sobald Sie also mit der Verarbeitung größerer Datenmengen beginnen, benötigen Sie mehr Gas (Wortspiel beabsichtigt) und wenn Sie keinen dedizierten Server im Haus haben, müssen Sie eine virtuelle Maschine auf einem verwenden Cloud-Computing-Plattform.
Das ist nicht nur zentralisiert, sondern auch ineffizient – da sich die Daten in einer unbekannten Entfernung von der Rechenmaschine befinden, und es kann schnell kostspielig werden. Ich habe keinen kostenlosen Cloud-Computing-Dienst gefunden, der dafür GPU-Verarbeitung anbietet (hat jemand gesagt, dass es Krypto-Mining-Verbote gibt?), und die Pläne kosten > 400 US-Dollar pro Monat (nein danke).
Bacalhau!
Glücklicherweise sind diese Probleme jedoch einige der Probleme, die Bacalhau zu lösen versucht. In Bacalhau ist es möglich, die Datenverarbeitung und -berechnung für jedermann offen und verfügbar zu machen und die Verarbeitungszeiten zu beschleunigen, erstens durch die Verwendung der Stapelverarbeitung über mehrere Knoten hinweg und zweitens dadurch, dass die Verarbeitungsknoten dort platziert werden, wo die Daten gespeichert sind!
Bacalhau möchte dazu beitragen, die Zukunft der Datenverarbeitung zu demokratisieren, indem es Off-Chain-Berechnungen über Daten ermöglicht, ohne die Dezentralisierungswerte von IPFS, Filecoin und Web3 im Allgemeinen aufzugeben.
Bacalhau ist ein offenes Peer-to-Peer-Rechennetzwerk, das eine Plattform für öffentliche, transparente und optional überprüfbare Rechenprozesse bietet, bei der Benutzer Docker-Container oder Web Assembly-Images als Aufgaben für beliebige Daten ausführen können, einschließlich der in IPFS (und bald auch Filecoin) gespeicherten Daten. Es unterstützt sogar GPU-Jobs und das nicht für 400 US-Dollar oder mehr!
Ausführen des Skripts auf Bacalhau
Um dieses Skript auszuführen, können wir es für die Verwendung auf Bacalhau dockerisieren. Sie können dem Tutorial hier folgen, wenn Sie das lernen möchten. Wir können es dann mit der Bacalhau-CLI mit nur einer Codezeile ausführen (nachdem wir Bacalhau mit einem anderen Einzeiler installiert haben ):
bacalhau docker run --gpu 1 ghcr.io/bacalhau-project/examples/stable-diffusion-gpu:0.0.1 -- python main.py --o ./outputs --p "Rainbow Unicorn"
In diesem Beispiel verwende ich jedoch einen HTTP-Endpunkt, der mich mit diesem dockerisierten stabilen Diffusionsskript verbindet, das ich Ihnen im Abschnitt „Integrationen“ zeige!
Ich möchte hier jedoch anmerken, dass dies eine leistungsstarke und flexible Möglichkeit ist, Datenberechnungsprozesse auszuführen, die auch Web3-freundlich ist – wir sind nicht nur auf dieses eine kleine Modell beschränkt.
Kommen wir aber zum NFT-Skript! :) :)
Der Smart Contract
Der NFT Smart Contract basiert auf der Open Zeppelin-Implementierung von ERC721, verwendet aber die ERC721URIStorage-Version, die die Metadaten-Standarderweiterungen enthält (damit wir unsere IPFS-adressierten Metadaten – die wir auf NFT.Storage speichern – an den Vertrag übergeben können). .
Dieser Basisvertrag bietet uns zusätzlich die allgemeine Funktionalität eines NFT-Vertrags mit bereits für uns implementierten Funktionen wie mint() und transfer().
Sie werden feststellen, dass ich auch ein paar Getter-Funktionen hinzugefügt habe, um Daten für mein Frontend abzurufen, sowie ein Ereignis, das jedes Mal in der Kette ausgegeben wird, wenn ein neuer NFT geprägt wird. Dies gibt die Möglichkeit, On-Chain-Ereignisse von der DApp abzuhören.
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]; }
Anforderungen
Ich werde diesen Vertrag im Filecoin Virtual Machine Hyperspace Testnet bereitstellen, aber Sie können diesen Vertrag auch in jeder EVM-kompatiblen Kette bereitstellen, einschließlich Polygon, BSC, Optimism, Arbitrum, Avalanche und mehr. Sie könnten sogar Ihr Frontend optimieren, um ein Multi-Chain-NFT zu erstellen (Hinweis: dieses Repo )!
Für die Bereitstellung im Hyperspace Testnet müssen wir dies tun
Bereitstellung des Smart Contracts mit Hardhat
Ich verwende Hardhat, um diesen Vertrag im Hyperspace-Testnetz bereitzustellen.
🛸 Hyperspace RPC- und BlockExplorer-Optionen:
Öffentliche RPC-Endpunkte
BlockExplorers
https://filecoin-hyperspace.chainstacklabs.com/rpc/v0
https://hyperspace.filfox.info/rpc/v0
https://fvm.starboard.ventures/contracts/
https://rpc.ankr.com/filecoin_testnet
https://explorer.glif.io/?network=hyperspacenet
Offene API : beryx.zondax.ch
Für die Konfigurationseinrichtung können wir aus jedem der verfügbaren öffentlichen RPC-Endpunkte wählen.
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;
Und um den Smart Contract bereitzustellen, erstellen wir ein Bereitstellungsskript. Beachten Sie, dass ich hier speziell die Wallet-Adresse als Unterzeichner (Eigentümer) festlege. Zum Zeitpunkt des Schreibens werden in FEVM noch einige Zuordnungsfehler behoben, die dazu führen können ein merkwürdiges Verhalten.
deploy/deployBacalhauFRC721.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; });
Führen Sie zum Bereitstellen das obige Skript im Terminal aus, indem Sie den folgenden Code verwenden (Hinweis: Da wir in unserer Konfiguration das Standardnetzwerk auf filecoinHyperspace festgelegt haben, ist es nicht erforderlich, ein Flag für das Netzwerk zu übergeben, obwohl dies unten gezeigt wird.)
> cd ./pages/hardhat/deploy/
npx hardhat run ./deployBacalhauFRC721.ts --network filecoinHyperspace
Feiern! Wir haben gerade unseren NFT-Vertrag im Filecoin-Hyperspace-Testnetz bereitgestellt!
Wooo, auf das hübsche Teil... und auch auf den Kleber, der hier alles zusammenhält :)
Um das Frontend zu erstellen, verwende ich NextJS und Typescript. Allerdings nutze ich, um ehrlich zu sein, keine der SSR-Funktionen (serverseitiges Rendering) von NextJS und nutze nicht einmal deren Seitenweiterleitung (da es sich um ein Single-Page-Dapp handelt), Sie könnten also einfach loslegen mit einem Vanilla React-Setup (oder natürlich einem beliebigen Framework Ihrer Wahl!).
Was das Typoskript betrifft ... nun, ich habe es etwas in Eile erstellt und muss zugeben, dass dies kein sehr gutes Beispiel für Typoskript ist – die Vars scheinen jedoch zufrieden zu sein ... ;)
Wie auch immer – der Hauptpunkt dieses Abschnitts besteht nicht darin, Ihnen zu zeigen, wie man ein Frontend programmiert, sondern Ihnen zu zeigen, wie Sie mit dem Smart Contract, Bacalhau (mit unserem stabilen ML-Diffusionsmodell) und natürlich NFT.Storage interagieren – # NotOnIPFSNotYourNFT.
[Aufgabe: Flussdiagramm erstellen]
Schön – mal sehen, wie wir das im Code umsetzen!
Die Erstellung des Front-End-API-Endpunkts für Bacalhau wird in diesem Projektbericht von Ingenieur Luke Marsden dokumentiert.
Die API greift derzeit nur direkt auf die in diesem Blog dokumentierten stabilen Diffusionsskripte zu. Das Team ist jedoch dabei, sie zu einer allgemeineren API zu erweitern, sodass Sie jedes der Beispiele und auch Ihre eigenen bereitgestellten Skripte über HTTP aufrufen können REST-API. Behalten Sie dies hier oder im #bacalhau-Kanal in FilecoinProject Slack im Auge.
>run/test in terminal
curl -XPOST -d '{"prompt": "rainbow unicorn"}' 'http://dashboard.bacalhau.org:1000/api/v1/stablediffusion';
>react / typescript code
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; };
Diese Funktion gibt eine IPFS-CID (Inhaltskennung) mit einer Ordnerstruktur wie der folgenden zurück. Das Bild ist dann unter /outputs/image0.png
zu finden.
Ahhh Regenbogen-Einhörner... was gibt es daran nicht zu mögen!
NFT.Storage ist ein öffentliches Gut (auch bekannt als kostenlos), das es einfach macht, NFT-Metadaten dauerhaft auf IPFS und Filecoin mit entweder einem Javascript- oder HTTP-SDK zu speichern.
NFT-Metadaten sind ein JSON-Dokument, das in etwa wie das folgende Beispiel aussieht und direkt aus den Open Zeppelin-Dokumenten stammt:
Bei der Erstellung von NFTs ist es wichtig zu beachten, dass Sie zur Einhaltung der „Nicht-Fungibilität“ eines Tokens Speicher benötigen, es sei denn, Sie speichern die Metadaten in der Kette (was bei großen Dateien unerschwinglich teuer werden kann). beharrlich, zuverlässig und unveränderlich.
Wenn Ihr NFT eine standortbasierte Adresse hat, wie im obigen Beispiel, dann ist es ziemlich einfach, diesen Standortpfad nach einem Verkauf zu ändern, was bedeutet, dass der NFT, von dem Sie dachten, dass Sie ihn gekauft haben, etwas ganz anderes wird – oder in diesem Fall im wahrsten Sinne des Wortes zum Teppich wird unten, wo der NFT-Ersteller die Kunstbilder gegen Bilder von Teppichen ausgetauscht hat.
Davor warnt sogar Open Zeppelin!
Die Verwendung von NFT.Storage bedeutet, dass wir eine unveränderliche IPFS-Datei-CID ( Inhalt – nicht Standort – ID- Entifier) für unsere Metadaten erhalten, die nicht nur an IPFS gepinnt, sondern dann auch zur Persistenz in Filecoin gespeichert wird. Sie müssen sich lediglich anmelden NFT.Storage und erhalten Sie einen API-Schlüssel (zum Speichern in Ihrer .env-Datei) für diesen.
.env example
NEXT_PUBLIC_NFT_STORAGE_API_KEY=xxx
Wir müssen auch sicherstellen, dass wir ein ordnungsgemäß formatiertes Metadaten-JSON erstellt haben – denn obwohl FVM (noch!) keine NFT-Marktplätze hat, möchten wir dennoch sicherstellen, dass unser NFT nach seiner Einführung hoffentlich immer noch dem Standard entspricht .
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; };
Speichern wir nun diese Metadaten in 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 – wir haben unser Bild von Bacalhau, wir haben unsere Metadaten unveränderlich und dauerhaft mit NFT.Storage gespeichert, jetzt prägen wir unser NFT!
💡 Kurzer Tipp 💡NFT.Storage bietet auch eine Reihe anderer API-Aufrufe wie storeCar und storeDirectory sowie eine status()-Funktion – die das IPFS-Pinning und die Filecoin-Speicherangebote einer CID zurückgibt -> dies könnte eine ziemlich coole Ergänzung für sein eine FEVM-DApp (oder eine NFT-Implementierung auf FEVM, sobald FEVM die Mainnet-Veröffentlichung erreicht) zur Überprüfung des NFT-Status.
Hier gibt es drei Arten von Interaktionen (und ein paar FEVM-Fallstricke – Beta-Technologie wird immer einige skurrile Bug-Features haben!)
schreibgeschützte Aufrufe, um Daten aus der Kette abzurufen, ohne sie zu verändern
Schreiben Sie Anrufe, für deren Unterzeichnung und Bezahlung von Benzin eine Brieftasche erforderlich ist, z. Funktionen, die den Zustand der Kette ändern, wie das Prägen des NFT!
Ereignis-Listener – die auf Ereignisse warten, die vom Vertrag ausgegeben werden
Für alle diese Funktionen verwenden wir die Bibliothek ethers.js – einen leichten Wrapper für die Ethereum-API, um eine Verbindung zu unserem Vertrag herzustellen und ihn aufzurufen.
Verbindung zum Vertrag im Lesemodus mit einem öffentlichen RPC herstellen:
//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 );
Auf Ereignisse im Vertrag achten. Da es sich um ein schreibgeschütztes (get) Ereignis handelt, können wir den öffentlichen RPC verwenden, um auf Ereignisemissionen in der Kette zu warten.
//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 } );
Herstellen einer Verbindung zum Vertrag im Schreibmodus – dies erfordert, dass das Ethereum-Objekt von einer Wallet in den Webbrowser eingefügt wird, damit ein Benutzer eine Transaktion unterzeichnen und für Benzin bezahlen kann – weshalb wir nach einem window.ethereum suchen Objekt.
//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); }
Aufrufen der Mint-Funktion mithilfe des Write-Connected-Vertrags.
Stellen Sie zunächst sicher, dass wir eine Wallet-Adresse des Benutzers haben und dass wir uns in der FVM-Hyperspace-Kette befinden. Hier sind einige hilfreiche Wallet-Funktionen, die Sie möglicherweise benötigen, einschließlich der Überprüfung der ChainId und des programmgesteuerten Hinzufügens des Hyperspace-Netzwerks zu Metamask/Wallet. Sie können mit Wallets interagieren, indem Sie das Ethereum-Objekt direkt oder ethers.js verwenden.
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); }); } };
Rufen Sie die Contract-Mint-Funktion im Schreibmodus auf....
// 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' ), }); }); }
Wooo – NFT geprägt!! Zeit für den Einhorn-Tanzmodus!
Bacalhau eignet sich gut für die Durchführung sich wiederholender, deterministischer Verarbeitungsaufgaben für Daten.
ETL-Prozesse
Maschinelles Lernen und KI
IOT-Datenintegration
Stapelverarbeitung einschließlich für
Video- und Bildverarbeitung – ideal für Kreative
In den Bacalhau-Dokumenten gibt es auch mehrere Beispiele dafür, wie einige der oben genannten Ziele erreicht werden können.
Während Bacalhau damit beschäftigt ist, eine Integration zu entwickeln, um Bacalhau direkt aus FEVM-Smart-Verträgen aufzurufen, finden Sie hier einige Gedanken zur Zusammenarbeit von Bacalhau x FVM:
Wir entwickeln derzeit eine Möglichkeit für Sie, Bacalhau direkt über Ihre Smart Contracts zu betreiben!!!! Dieses Projekt heißt Project Frog / Project Lilypad – und wird eine Integrationsschicht sein, die den Aufruf von Bacalhau-Jobs aus FEVM-Smart Contracts ermöglicht.
Behalten Sie den Fortschritt im Auge, indem Sie sich für unseren Newsletter anmelden oder den folgenden sozialen Netzwerken beitreten.
Herzlichen Glückwunsch, wenn Sie bis zum Ende gelesen haben!!!
Ich würde mich über ein „Gefällt mir“, einen Kommentar, ein Follow oder ein Teilen freuen, wenn dies für Sie hilfreich wäre! <3
Bleiben Sie in Kontakt mit Bacalhau!
Mit ♥️ DeveloperAlly
Unterstützen Sie Alison Haire , indem Sie Sponsor werden. Jeder Betrag ist willkommen!
Erfahren Sie mehr über Hashnode-Sponsoren
Auch hier veröffentlicht.