¡Una guía completa para crear, ejecutar e implementar una DApp con su propio script de texto a imagen para crear NFT artísticos generados por IA en FVM Hyperspace Testnet!
Este blog lo guiará a través de cómo
Cree un script de texto a imagen basado en python de código abierto basado en Tensorflow (también puede usar el punto final HTTP de Bacalhau si no le interesa)
Ejecute este script en Bacalhau (una plataforma de cómputo fuera de cadena p2p abierta)
Cree un contrato NFT en Solidity (basado en un contrato Open Zeppelin ERC721)
Implemente el contrato NFT en la red de prueba hiperespacial de Filecoin Virtual Machine (FVM) con Hardhat
Interacciones front-end: cómo interactuar con el script de texto a imagen de Bacalhau y su contrato NFT en React
Cómo guardar sus metadatos NFT en NFT.Storage
Cómo implementar su DApp front-end en Fleek
Elegí deliberadamente usar tanta tecnología de código abierto y descentralizada como esté disponible en esta pila.
Este blog va a ser bastante largo (oye, ¡quiero dar TODA LA INFORMACIÓN y asegurarme de que seamos amigables para los principiantes e inclusivos!), así que siéntete libre de pasar a las partes que te sean útiles en la tabla. de contenidos <3
(consíguelo, es una pila de panqueques #lo siento, no lo siento)
Código abierto y valor Web3 desde cero :)
💡 Sugerencia TLDR 💡
Este script ya está disponible para su uso a través de Bacalhau a través de la CLI y un punto final HTTP, así que no dude en omitir esta parte.
Introducción rápida a la difusión estable
Stable Diffusion es actualmente el modelo de aprendizaje automático líder para realizar el procesamiento de texto a imagen (y es el mismo modelo que usa Dall-E). Es un tipo de aprendizaje profundo, un subconjunto de aprendizaje automático que se enseña a sí mismo a realizar una tarea específica, en este caso, convertir una entrada de texto en una salida de imagen.
En este ejemplo, estamos usando un modelo probabilístico de difusión que usa un transformador para generar imágenes a partir de texto.
Sin embargo, no se preocupe, no necesitamos ir y entrenar un modelo de aprendizaje automático para esto (aunque, si eso es lo que le gusta, ¡podría hacerlo!)
En su lugar, vamos a utilizar un modelo previamente entrenado de la biblioteca de aprendizaje automático de código abierto TensorFlow de Google en nuestra secuencia de comandos de python porque los pesos de ML se han calculado previamente para nosotros.
Más correctamente, estamos usando una bifurcación de implementación Keras/TensorFlow optimizada del modelo ML original.
El guión de Python
🦄 Puede encontrar un tutorial completo sobre cómo crear y dockerizar este script de texto a imagen y ejecutarlo en Bacalhau tanto en los documentos de Bacalhau como en este video de YouTube de @BacalhauProject. 🦄 También puede ejecutarlo en este Google Collabs Notebook
¡Aquí está el script completo de Python!
import argparse from stable_diffusion_tf.stable_diffusion import Text2Image from PIL import Image import os parser = argparse.ArgumentParser(description="Stable Diffusion") parser.add_argument("--h",dest="height", type=int,help="height of the image",default=512) parser.add_argument("--w",dest="width", type=int,help="width of the image",default=512) parser.add_argument("--p",dest="prompt", type=str,help="Description of the image you want to generate",default="cat") parser.add_argument("--n",dest="numSteps", type=int,help="Number of Steps",default=50) parser.add_argument("--u",dest="unconditionalGuidanceScale", type=float,help="Number of Steps",default=7.5) parser.add_argument("--t",dest="temperature", type=int,help="Number of Steps",default=1) parser.add_argument("--b",dest="batchSize", type=int,help="Number of Images",default=1) parser.add_argument("--o",dest="output", type=str,help="Output Folder where to store the Image",default="./") args=parser.parse_args() height=args.height width=args.width prompt=args.prompt numSteps=args.numSteps unconditionalGuidanceScale=args.unconditionalGuidanceScale temperature=args.temperature batchSize=args.batchSize output=args.output generator = Text2Image( img_height=height, img_width=width, jit_compile=False, # You can try True as well (different performance profile) ) img = generator.generate( prompt, num_steps=numSteps, unconditional_guidance_scale=unconditionalGuidanceScale, temperature=temperature, batch_size=batchSize, ) for i in range(0,batchSize): pil_img = Image.fromarray(img[i]) image = pil_img.save(f"{output}/image{i}.png")
La secuencia de comandos anterior simplemente toma un argumento de entrada de solicitud de texto y algunos otros parámetros opcionales y luego llama a la biblioteca TensorFlow bifurcada para generar las imágenes y guardarlas en un archivo de salida.
Todo el trabajo pesado que se hace aquí ocurre en la siguiente sección: aquí es donde el modelo de aprendizaje automático hace su magia. 🪄
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, )
Genial, podemos generar imágenes a partir de un indicador de texto, pero um... dónde ejecutar este script requerido por GPU... 🤔🤔
Si hay algo que la tecnología blockchain no hace inherentemente bien, es el procesamiento de grandes cantidades de datos. Esto se debe al costo de la informática en un sistema distribuido para proporcionar otras propiedades poderosas como la falta de confianza y la resistencia a la censura.
Es posible usar su máquina local para ejemplos pequeños; de hecho, logré que este ejemplo en particular funcionara en mi (muy descontento) Mac M1, sin embargo, fue una espera muy larga en los resultados (¿juego de tenis de mesa alguien?) entonces, una vez que comience a hacer un procesamiento de datos más grande, necesitará más gasolina (juego de palabras intencionado) y si no tiene un servidor dedicado en la casa, necesitará usar una máquina virtual en un plataforma de computación en la nube.
No solo está centralizado, también es ineficiente, debido a que los datos están a una distancia desconocida de la máquina de cómputo, y puede volverse costoso rápidamente. No pude encontrar ningún servicio de computación en la nube de nivel gratuito que ofreciera procesamiento de GPU para esto (¿alguien dijo prohibiciones de criptominería?) y los planes llegaron a > US $ 400 por mes (no gracias).
Bacalhau!
Sin embargo, afortunadamente, estos problemas son algunos de los problemas que Bacalhau está tratando de resolver. Hacer que el procesamiento y la computación de datos estén abiertos y disponibles para todos y acelerar los tiempos de procesamiento es posible en Bacalhau, en primer lugar, mediante el uso del procesamiento por lotes en múltiples nodos y, en segundo lugar, al colocar los nodos de procesamiento donde residen los datos.
Bacalhau tiene como objetivo ayudar a democratizar el futuro del procesamiento de datos al permitir el cálculo fuera de la cadena sobre los datos sin renunciar a los valores de descentralización inherentes a IPFS, Filecoin y Web3 en general.
Bacalhau es una red de computación abierta punto a punto que proporciona una plataforma para procesos de computación públicos, transparentes y opcionalmente verificables donde los usuarios pueden ejecutar contenedores Docker o imágenes de ensamblaje web como tareas contra cualquier dato, incluidos los datos almacenados en IPFS (y pronto Filecoin). ¡Incluso tiene soporte para trabajos de GPU y no a US $ 400 o más!
Ejecutando el script en Bacalhau
Para ejecutar este script, podemos dockerizarlo para usarlo en Bacalhau. Puede seguir el tutorial aquí si quiere aprender a hacer eso. Luego podemos ejecutarlo con Bacalhau CLI con solo una línea de código (después de instalar Bacalhau con otra línea):
bacalhau docker run --gpu 1 ghcr.io/bacalhau-project/examples/stable-diffusion-gpu:0.0.1 -- python main.py --o ./outputs --p "Rainbow Unicorn"
Sin embargo, en este ejemplo, voy a usar un punto final HTTP que me conecta a este script de difusión estable dockerizado, que le mostraré en la sección Integraciones.
Sin embargo, señalaré aquí que esta es una forma poderosa y flexible de ejecutar procesos de cálculo de datos que también es compatible con web3: no estamos limitados a este modelo pequeño.
¡Sin embargo, pasemos al script NFT! :)
El contrato inteligente
El contrato inteligente de NFT se basa en la implementación de ERC721 de Open Zeppelin, pero utiliza la versión ERC721URIStorage, que incluye las extensiones estándar de metadatos (para que podamos pasar nuestros metadatos con direcciones IPFS, que guardaremos en NFT.Storage, al contrato) .
Este contrato base además nos brinda la funcionalidad general de un contrato NFT con funciones como mint() y transfer() ya implementadas para nosotros.
Notará que también agregué un par de funciones de captación para obtener datos para mi interfaz, así como un evento que se emitirá en la cadena cada vez que se acuñe un nuevo NFT. Esto brinda la capacidad de escuchar eventos en cadena desde la DApp.
💡 ¡ Pruébelo en remix y vea todas las funciones disponibles haciendo clic en este enlace! 💡
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]; }
Requisitos
Implementaré este contrato en Filecoin Virtual Machine Hyperspace Testnet , pero podría implementar este contrato en cualquier cadena compatible con EVM, incluidos Polygon, BSC, Optimism, Arbitrum, Avalanche y más. ¡Incluso podría modificar su interfaz para hacer un NFT de cadena múltiple (pista: este repositorio )!
Para implementar en Hyperspace Testnet, necesitaremos
Implementación del contrato inteligente con Hardhat
Estoy usando un casco para implementar este contrato en la red de pruebas de Hyperspace.
🛸 Opciones de Hyperspace RPC y BlockExplorer:
Puntos finales RPC públicos
Explorador de bloques
https://filecoin-hiperespacio.chainstacklabs.com/rpc/v0
https://hiperespacio.filfox.info/rpc/v0
https://fvm.starboard.ventures/contratos/
https://rpc.ankr.com/filecoin_testnet
https://explorer.glif.io/?network=hiperespacionet
API abierta : beryx.zondax.ch
Para la configuración, podemos elegir cualquiera de los puntos finales RPC públicos disponibles.
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;
Y para implementar el contrato inteligente, creamos un script de implementación. Tenga en cuenta que estoy configurando específicamente la dirección de Wallet aquí como el firmante (propietario). Hay algunos errores de mapeo que aún se están trabajando en FEVM en el momento de la escritura que pueden causar algún comportamiento extraño.
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; });
Para implementar, ejecute la secuencia de comandos anterior en la terminal usando el siguiente código (NB: dado que hemos configurado la red predeterminada en filecoinHyperspace en nuestra configuración, no es necesario pasar una bandera para la red, aunque esto se muestra a continuación)
> cd ./pages/hardhat/deploy/
npx hardhat run ./deployBacalhauFRC721.ts --network filecoinHyperspace
¡Celebrar! ¡Acabamos de implementar nuestro contrato NFT en la red de prueba hiperespacial de Filecoin!
Wooo en la parte bonita... y también el pegamento que mantiene todo junto aquí :)
Para construir el front-end, estoy usando NextJS y Typescript. Sin embargo, para ser honesto, no estoy aprovechando ninguna de las funciones SSR (representación del lado del servidor) de NextJS y ni siquiera uso su enrutamiento de página (ya que es un Dapp de una sola página), por lo que realmente podría ir con una configuración Vanilla React (¡o cualquier marco de su elección, por supuesto!).
En cuanto al mecanografiado... bueno, construí esto con un poco de prisa y tengo que admitir que este no es un muy buen ejemplo de mecanografiado; sin embargo, los vars parecen felices... ;)
De todos modos, el punto principal de esta sección no es mostrarle cómo codificar una interfaz, sino mostrarle cómo interactuar con el contrato inteligente, Bacalhau (con nuestro modelo ML de difusión estable) y, por supuesto, NFT.Storage - # No en IPFS No en su NFT.
[tarea: construir un diagrama de flujo]
Genial, ¡veamos cómo implementamos esto en el código!
La creación del extremo de la API de front-end para Bacalhau está documentada en este informe de proyecto por el ingeniero Luke Marsden .
Actualmente, la API solo afecta directamente a los scripts de difusión estables documentados en este blog; sin embargo, el equipo está en proceso de ampliarla a una API más genérica para que pueda llamar a cualquiera de los ejemplos, y también a sus propios scripts implementados desde un HTTP. API REST. Esté atento a esto aquí o en el canal #bacalhau en FilecoinProject slack.
>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; };
Esta función devolverá un CID (identificador de contenido) de IPFS con una estructura de carpetas como la siguiente. La imagen se puede encontrar en /outputs/image0.png
.
💡 ¡Véalo usted mismo haciendo clic aquí ! 💡
Ahhh unicornios arcoíris... ¡Qué no puede gustar!
NFT.Storage es un bien público (también conocido como gratuito) que facilita el almacenamiento de metadatos NFT de forma perpetua en IPFS y Filecoin con JavaScript o HTTP SDK.
NFT Metadata es un documento JSON que se parece al siguiente ejemplo, que se toma directamente de los documentos de Open Zeppelin:
Al crear NFT, es importante tener en cuenta que, a menos que almacene los metadatos en la cadena (lo que puede volverse prohibitivamente costoso para archivos grandes), para cumplir con la 'no fungibilidad' de un token, necesita un almacenamiento que sea persistente, confiable e inmutable.
Si su NFT tiene una dirección basada en la ubicación como el ejemplo anterior, entonces es bastante simple que esta ruta de ubicación se cambie después de una venta, lo que significa que el NFT que pensó que compró se convierte en algo completamente diferente, o literalmente en un tirón de alfombra en el caso a continuación, donde el creador de NFT cambió las imágenes artísticas por imágenes de alfombras.
¡Algo de lo que incluso Open Zeppelin advierte!
El uso de NFT.Storage significa que obtenemos un CID de archivo IPFS inmutable ( contenido , no ubicación, identificador de identificación) para nuestros metadatos que no solo se anclan a IPFS, sino que también se almacenan en Filecoin para persistencia. NFT.Storage y obtenga una clave API (para guardar en su archivo .env) para este.
.env example
NEXT_PUBLIC_NFT_STORAGE_API_KEY=xxx
También debemos asegurarnos de haber creado un JSON de metadatos correctamente formado, porque si bien FVM no tiene (¡todavía!) NFT Marketplaces... queremos asegurarnos de que, cuando se adopte, nuestro NFT todavía se ajusta al estándar. .
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; };
¡Ahora almacenemos estos metadatos en 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: tenemos nuestra imagen de Bacalhau, hemos guardado nuestros metadatos de manera inmutable y persistente con NFT. Strorage, ¡ahora acuñemos nuestro NFT!
💡 Sugerencia rápida 💡NFT.Storage también ofrece una gama de otras llamadas API como storeCar y storeDirectory, así como una función de estado () , que devuelve las ofertas de almacenamiento de pines IPFS y Filecoin de un CID -> esta podría ser una adición genial para una DApp de FEVM (o implementación de NFT en FEVM una vez que FEVM llega al lanzamiento de la red principal) para comprobar el estado de los NFT.
Hay 3 tipos de interacciones aquí (y algunos errores de FEVM: ¡la tecnología beta siempre tendrá algunas características de errores extravagantes!)
llamadas de solo lectura para recuperar datos de la cadena sin mutarlos
escribe llamadas que requieren una billetera para firmar y pagar gasolina, es decir. ¡funciones que cambian el estado de la cadena, como acuñar el NFT!
detectores de eventos: que escuchan los eventos emitidos por el contrato
Para todas estas funciones, usaremos la biblioteca ethers.js , un contenedor liviano para la API de Ethereum, para conectarnos a nuestro contrato y realizar llamadas.
Conexión al contrato en modo lectura con una RPC pública:
//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 );
Escucha de eventos en el contrato. Dado que este es un evento de solo lectura (obtener), podemos usar el RPC público para escuchar las emisiones de eventos en la cadena.
//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 } );
Conectarse al contrato en modo de escritura : esto requiere que una billetera inyecte el objeto Ethereum en el navegador web para que un usuario pueda firmar una transacción y pagar la gasolina, razón por la cual estamos buscando una ventana.ethereum objeto.
//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); }
Llamar a la función mint usando el contrato conectado de escritura.
Primero, asegúrese de que tenemos una dirección de billetera del usuario y que estamos en la cadena FVM Hyperspace. Aquí hay algunas funciones útiles de billetera que puede desear, incluida la forma de verificar el chainId y cómo agregar mediante programación la red Hyperspace a Metamask/billetera. Puede interactuar con las billeteras usando el objeto Ethereum directamente o usando ethers.js.
declare let window: any; const fetchWalletAccounts = async () => { console.log('Fetching wallet accounts...'); await window.ethereum //use ethers? .request({ method: 'eth_requestAccounts' }) .then((accounts: string[]) => { return accounts; }) .catch((error: any) => { if (error.code === 4001) { // EIP-1193 userRejectedRequest error console.log('Please connect to MetaMask.'); } else { console.error(error); } }); }; const fetchChainId = async () => { console.log('Fetching chainId...'); await window.ethereum .request({ method: 'eth_chainId' }) .then((chainId: string[]) => { return chainId; }) .catch((error: any) => { if (error.code === 4001) { // EIP-1193 userRejectedRequest error console.log('Please connect to MetaMask.'); } else { console.error(error); } }); }; //!! This function checks for a wallet connection WITHOUT being intrusive to to the user or opening their wallet export const checkForWalletConnection = async () => { if (window.ethereum) { console.log('Checking for Wallet Connection...'); await window.ethereum .request({ method: 'eth_accounts' }) .then(async (accounts: String[]) => { console.log('Connected to wallet...'); // Found a user wallet return true; }) .catch((err: Error) => { console.log('Error fetching wallet', err); return false; }); } else { //Handle no wallet connection return false; } }; //Subscribe to changes on a user's wallet export const setWalletListeners = () => { console.log('Setting up wallet event listeners...'); if (window.ethereum) { // subscribe to provider events compatible with EIP-1193 standard. window.ethereum.on('accountsChanged', (accounts: any) => { //logic to check if disconnected accounts[] is empty if (accounts.length < 1) { //handle the locked wallet case } if (userWallet.accounts[0] !== accounts[0]) { //user has changed address } }); // Subscribe to chainId change window.ethereum.on('chainChanged', () => { // handle changed chain case }); } else { //handle the no wallet case } }; export const changeWalletChain = async (newChainId: string) => { console.log('Changing wallet chain...'); const provider = window.ethereum; try { await provider.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: newChainId }], //newChainId }); } catch (error: any) { alert(error.message); } }; //AddHyperspaceChain export const addHyperspaceNetwork = async () => { console.log('Adding the Hyperspace Network to Wallet...'); if (window.ethereum) { window.ethereum .request({ method: 'wallet_addEthereumChain', params: [ { chainId: '0xc45', rpcUrls: [ 'https://hyperspace.filfox.info/rpc/v0', 'https://filecoin-hyperspace.chainstacklabs.com/rpc/v0', ], chainName: 'Filecoin Hyperspace', nativeCurrency: { name: 'tFIL', symbol: 'tFIL', decimals: 18, }, blockExplorerUrls: [ 'https://fvm.starboard.ventures/contracts/', 'https://hyperspace.filscan.io/', 'https://beryx.zondax.chfor', ], }, ], }) .then((res: XMLHttpRequestResponseType) => { console.log('added hyperspace successfully', res); }) .catch((err: ErrorEvent) => { console.log('Error adding hyperspace network', err); }); } };
Llame a la función de menta del contrato en modo de escritura....
// 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 acuñado! Hora del modo de baile de unicornio!
Bacalhau se presta bien para realizar trabajos de procesamiento repetitivos y deterministas sobre datos.
Procesos ETL
Aprendizaje automático e IA
Integración de datos IOT
Procesamiento por lotes, incluido para
Procesamiento de video e imagen: ideal para creativos
Hay múltiples ejemplos en los documentos de Bacalhau de cómo lograr algo de lo anterior también.
Si bien Bacalhau está ocupado construyendo una integración para llamar directamente a Bacalhau desde los contratos inteligentes de FEVM, aquí hay algunas ideas sobre las colaboraciones de Bacalhau x FVM:
¡Actualmente estamos construyendo una forma para que usted pueda ejecutar Bacalhau directamente desde sus contratos inteligentes! Este proyecto se llama Project Frog / Project Lilypad, y será una capa de integración que permitirá llamar a trabajos de Bacalhau desde contratos inteligentes FEVM.
Esté atento al progreso de esto suscribiéndose a nuestro boletín o uniéndose a las redes sociales a continuación.
Felicidades si lo leíste completo!!!
¡Agradecería un me gusta, comentario, seguir o compartir si esto te fue útil! <3
¡Mantente en contacto con Bacalhau!
Con ♥️ DeveloperAlly
Apoye a Alison Haire convirtiéndose en patrocinador. ¡Cualquier cantidad es apreciada!
Obtenga más información sobre los patrocinadores de Hashnode
También publicado aquí .