Um guia completo para criar, executar e implantar um DApp com seu próprio script Text-to-Image para criar NFTs de arte gerados por IA no FVM Hyperspace Testnet!
Este blog irá guiá-lo através de como
Crie um script de texto para imagem baseado em python de código aberto com base no Tensorflow (você também pode usar apenas o endpoint HTTP do Bacalhau se isso não for do seu interesse)
Execute este script no Bacalhau (uma plataforma de computação p2p off-chain aberta)
Crie um Contrato NFT em Solidity (baseado em um contrato Open Zeppelin ERC721)
Implante o contrato NFT na rede de teste de hiperespaço Filecoin Virtual Machine (FVM) com Hardhat
Interações de front-end - Como interagir com o script de texto para imagem do Bacalhau e seu contrato NFT no React
Como salvar seus metadados NFT em NFT.Storage
Como implantar seu DApp Front-End no Fleek
Escolhi deliberadamente usar o máximo de tecnologia de código aberto e descentralizada disponível nesta pilha.
Este blog vai ser bem longo (ei - eu quero dar TODAS AS INFORMAÇÕES e ter certeza de que estamos sendo amigáveis para iniciantes e inclusivos!) - então sinta-se à vontade para pular para as partes que são úteis para você na tabela de conteúdo <3
(entenda - é uma pilha de panquecas #sorrynotsorry)
Código aberto e valor Web3 desde o início :)
💡 Dica TLDR 💡
Este script já está disponível para uso através do Bacalhau via CLI e um endpoint HTTP, então sinta-se à vontade para pular esta parte.
Introdução rápida à difusão estável
A difusão estável é atualmente o principal modelo de aprendizado de máquina para fazer processamento de texto para imagem (e é o mesmo modelo que Dall-E usa). É um tipo de Deep Learning - um subconjunto de Machine Learning que se ensina a realizar uma tarefa específica - neste caso, convertendo uma entrada de texto em uma saída de imagem.
Neste exemplo, estamos usando um modelo probabilístico de difusão que usa um transformador para gerar imagens de texto.
Não se preocupe - não precisamos ir e treinar um modelo de aprendizado de máquina para isso (embora, ei - se é isso que você gosta - você poderia totalmente!)
Em vez disso, usaremos um modelo pré-treinado da biblioteca de aprendizado de máquina de código aberto TensorFlow do Google em nosso script python porque os pesos de ML foram pré-calculados para nós.
Mais corretamente, estamos usando uma bifurcação de implementação Keras/TensorFlow otimizada do modelo de ML original.
O script Python
🦄 Você pode encontrar um passo a passo completo de como construir e Dockerizar este script de texto para imagem e executá-lo no Bacalhau tanto na documentação do Bacalhau quanto neste vídeo do YouTube do @BacalhauProject .🦄 Você também pode executá-lo neste Google Collabs Notebook
Aqui está o script python completo!
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")
O script acima simplesmente recebe um argumento de entrada de prompt de texto e alguns outros parâmetros opcionais e, em seguida, chama a biblioteca bifurcada do TensorFlow para gerar as imagens e salvá-las em um arquivo de saída.
Todo o trabalho pesado feito aqui acontece na seção abaixo - é aqui que o Modelo de Aprendizado de Máquina faz sua mágica. 🪄
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, )
Ótimo, podemos gerar imagens a partir de um prompt de texto, mas hum... onde executar este script necessário para GPU..... 🤔🤔
Se há uma coisa que a tecnologia blockchain não faz inerentemente bem, é o grande processamento de dados. Isso se deve ao custo de computação em um sistema distribuído para fornecer outras propriedades poderosas, como falta de confiança e resistência à censura.
É possível usar sua máquina local para pequenos exemplos - na verdade, consegui fazer este exemplo específico funcionar no meu (muito infeliz com isso) Mac M1, no entanto, foi uma espera muito longa pelos resultados (jogo de tênis de mesa, alguém?) então, quando você começar a fazer um processamento de dados maior, precisará de mais gás (trocadilho intencional) e, se não tiver um servidor dedicado em casa, precisará usar uma máquina virtual em um plataforma de computação em nuvem.
Isso não é apenas centralizado, mas também ineficiente - devido aos dados estarem a uma distância desconhecida da máquina de computação e pode ficar caro rapidamente. Não consegui encontrar nenhum serviço de computação em nuvem de nível gratuito que oferecesse processamento de GPU para isso (alguém disse proibições de mineração de criptografia ...?) E os planos chegavam a > US $ 400 por mês (não, obrigado).
Bacalhau!
Felizmente, porém, esses problemas são alguns dos problemas que Bacalhau está tentando resolver. Tornar o processamento de dados e a computação abertos e disponíveis para todos e acelerar os tempos de processamento é possível no Bacalhau, em primeiro lugar - usando o processamento em lote em vários nós e, em segundo lugar, colocando os nós de processamento onde os dados residem!
Bacalhau tem como objetivo ajudar a democratizar o futuro do processamento de dados, permitindo a computação off-chain sobre dados sem abrir mão dos valores de descentralização inerentes ao IPFS, Filecoin e Web3 de forma mais ampla.
Bacalhau é uma rede de computação aberta ponto a ponto que fornece uma plataforma para processos de computação públicos, transparentes e opcionalmente verificáveis, onde os usuários podem executar contêineres Docker ou imagens Web Assembly como tarefas em relação a quaisquer dados, incluindo dados armazenados em IPFS (e em breve Filecoin). Ele ainda tem suporte para trabalhos de GPU e não custa US$ 400 ou mais!
Executando o script no Bacalhau
Para executar este script, podemos dockerizá-lo para uso no Bacalhau. Você pode seguir o tutorial aqui se quiser aprender a fazer isso. Podemos então executá-lo com o Bacalhau CLI com apenas uma linha de código (depois de instalar o Bacalhau com outro one-liner):
bacalhau docker run --gpu 1 ghcr.io/bacalhau-project/examples/stable-diffusion-gpu:0.0.1 -- python main.py --o ./outputs --p "Rainbow Unicorn"
Neste exemplo, porém, vou usar um endpoint HTTP que me conecta a este script de difusão estável dockerizado, que mostrarei na seção Integrações!
Vou observar aqui, porém, que esta é uma maneira poderosa e flexível de executar processos de computação de dados que também é compatível com web3 - não estamos limitados apenas a este pequeno modelo.
Vamos passar para o script NFT! :)
O Contrato Inteligente
O NFT Smart Contract é baseado na implementação do Open Zeppelin do ERC721, mas usa a versão ERC721URIStorage, que inclui as extensões padrão de metadados (para que possamos passar nossos metadados endereçados IPFS - que salvaremos no NFT.Storage, para o contrato) .
Este contrato base também nos dá a funcionalidade geral de um contrato NFT com funções como mint() e transfer() já implementadas para nós.
Você notará que também adicionei algumas funções getter para buscar dados para meu front-end, bem como um evento que será emitido na cadeia sempre que um novo NFT for criado. Isso permite ouvir eventos on-chain do DApp.
💡 Experimente no remix e veja todas as funções disponíveis clicando neste link! 💡
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
Estarei implantando este contrato no Filecoin Virtual Machine Hyperspace Testnet , mas você pode implantar este contrato em qualquer cadeia compatível com EVM, incluindo Polygon, BSC, Optimism, Arbitrum, Avalanche e muito mais. Você pode até ajustar seu front-end para criar um NFT multi-chain (dica: este repositório )!
Para implantar no Hyperspace Testnet, precisaremos
Implantando o Contrato Inteligente com Hardhat
Estou usando um capacete de segurança para implantar este contrato na rede de testes do Hyperspace.
🛸 Opções de Hyperspace RPC e BlockExplorer:
Pontos finais RPC públicos
do BlockExplorer
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
API aberta : beryx.zondax.ch
Para a configuração, podemos escolher qualquer um dos terminais RPC públicos disponíveis.
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;
E para implantar o contrato inteligente, criamos um script de implantação - observe que estou definindo especificamente o endereço da carteira aqui como o signatário (proprietário) - há alguns erros de mapeamento ainda sendo trabalhados no FEVM no momento da redação que podem causar algum comportamento estranho.
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 implantar, execute o script acima no terminal usando o seguinte código (NB: como definimos a rede padrão como filecoinHyperspace em nossa configuração, não é necessário passar um sinalizador para a rede, embora isso seja mostrado abaixo)
> cd ./pages/hardhat/deploy/
npx hardhat run ./deployBacalhauFRC721.ts --network filecoinHyperspace
Comemoro! Acabamos de implantar nosso contrato NFT na rede de teste do hiperespaço Filecoin!
Wooo para a parte bonita... e também a cola que mantém tudo junto aqui :)
Para construir o front-end, estou usando NextJS e Typescript. No entanto, para ser honesto - não estou aproveitando nenhum dos recursos SSR (renderização do lado do servidor) do NextJS e nem mesmo uso o roteamento de página (já que é um Dapp de página única), então você poderia simplesmente ir com uma configuração de baunilha do React (ou qualquer estrutura de sua escolha, é claro!).
Quanto ao typescript... bem, eu construí isso com um pouco de pressa e tenho que admitir que este não é um bom exemplo de Typescript - os vars parecem felizes embora... ;)
Anyhoo - o ponto principal desta seção não é mostrar como codificar um front end, mas mostrar como interagir com o contrato inteligente, Bacalhau (com nosso modelo ML de difusão estável) e, claro, NFT.Storage - # NotOnIPFSNotYourNFT.
[fazer: construir um diagrama de fluxograma]
Legal - vamos ver como implementamos isso no código!
A criação do endpoint da API front-end para o Bacalhau está documentada neste relatório de projeto pelo engenheiro Luke Marsden .
Atualmente, a API atinge diretamente apenas os scripts de difusão estáveis documentados neste blog, no entanto, a equipe está no processo de estendê-la para uma API mais genérica para que você possa chamar qualquer um dos exemplos e também seus próprios scripts implantados de um HTTP API REST. Fique de olho aqui ou no canal #bacalhau no 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 função retornará um IPFS CID (identificador de conteúdo) com uma estrutura de pastas como a abaixo. A imagem pode ser encontrada em /outputs/image0.png
.
Ahhh unicórnios do arco-íris... como não gostar!
NFT.Storage é um bem público (também conhecido como gratuito) que facilita o armazenamento permanente de metadados NFT em IPFS e Filecoin com javascript ou SDK HTTP.
NFT Metadata é um documento JSON que se parece com o exemplo abaixo - que é obtido diretamente dos documentos do Open Zeppelin:
Ao criar NFTs, é importante observar que, a menos que você esteja armazenando os metadados na cadeia (o que pode se tornar proibitivamente caro para arquivos grandes), para estar em conformidade com a 'não fungibilidade' de um token, você precisa de armazenamento que seja persistente, confiável e imutável.
Se o seu NFT tiver um endereço baseado em localização como o exemplo acima, é bastante simples que esse caminho de localização seja trocado após uma venda, o que significa que o NFT que você pensou ter comprado se torna algo totalmente diferente - ou um puxão de tapete literal no caso abaixo, onde o criador do NFT trocou as imagens de arte por imagens de tapetes.
Algo que até o Open Zeppelin alerta!
Usar NFT.Storage significa que obtemos um CID de arquivo IPFS imutável ( conteúdo - não localização - identificador de id ) para nossos metadados, que não são apenas fixados no IPFS, mas também armazenados no Filecoin para persistência. Você só precisa se inscrever para NFT.Storage e obtenha uma chave de API (para salvar em seu arquivo .env) para este.
.env example
NEXT_PUBLIC_NFT_STORAGE_API_KEY=xxx
Também precisamos ter certeza de que criamos um JSON de metadados devidamente formado - porque, embora o FVM não (ainda!) tenha NFT Marketplaces ... queremos ter certeza de que, quando for adotado, nosso NFT ainda esteja em conformidade com o padrão .
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; };
Agora vamos armazenar esses metadados no 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 - temos a nossa imagem de Bacalhau, guardamos os nossos metadados de forma imutável e persistente com NFT.Strorage, agora vamos cunhar o nosso NFT!
💡 Dica rápida 💡NFT.Storage também oferece uma variedade de outras chamadas de API como storeCar e storeDirectory, bem como uma função status() - que retorna a pinagem IPFS e transações de armazenamento Filecoin de um CID -> isso pode ser uma adição muito legal para um FEVM DApp (ou implementação NFT no FEVM assim que o FEVM atingir o lançamento da rede principal) para verificar o status dos NFTs.
Existem 3 tipos de interações aqui (e algumas pegadinhas do FEVM - a tecnologia beta sempre terá alguns recursos peculiares de bugs!)
chamadas somente leitura para recuperar dados da cadeia sem modificá-los
escrever chamadas que exigem uma carteira para assinar e pagar gás, ou seja. funções que alteram o estado da cadeia, como cunhar o NFT!
event listeners - que escutam os eventos emitidos pelo contrato
Para todas essas funções, usaremos a biblioteca ethers.js - um wrapper leve para a API Ethereum, para conectar-se ao nosso contrato e realizar chamadas para ele.
Conectando-se ao contrato no modo de leitura com um RPC público:
//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 );
Ouvir eventos no contrato. Como este é um evento somente leitura (obter), podemos usar o RPC público para ouvir as emissões de eventos na cadeia.
//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 } );
Conectar-se ao contrato no modo de gravação - isso requer que o objeto Ethereum seja injetado no navegador da web por uma carteira para que um usuário possa assinar uma transação e pagar pelo gás - e é por isso que estamos verificando uma janela.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); }
Chamando a Mint Function usando o contrato de gravação conectado.
Primeiro, certifique-se de que temos um endereço de carteira do usuário e que estamos na cadeia FVM Hyperspace. Aqui estão algumas funções úteis de carteira que você pode querer, incluindo como verificar o chainId e como adicionar programaticamente a rede Hyperspace a Metamask / wallet. Você pode interagir com carteiras usando o objeto Ethereum diretamente ou 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); }); } };
Chame a função mint do contrato no modo de gravação....
// 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 Cunhado!! Hora do modo de dança do unicórnio!
O Bacalhau presta-se bem à execução de trabalhos de processamento repetitivos e determinísticos sobre dados.
Processos ETL
Aprendizado de máquina e IA
Integração de dados IOT
Processamento em Lote, incluindo para
Processamento de vídeo e imagem - ótimo para criativos
Existem vários exemplos nos documentos do Bacalhau de como alcançar alguns dos itens acima também.
Enquanto o Bacalhau está ocupado desenvolvendo uma integração para chamar diretamente o Bacalhau dos contratos inteligentes da FEVM, aqui estão alguns pensamentos sobre as colaborações Bacalhau x FVM:
No momento, estamos criando uma maneira de você administrar o Bacalhau diretamente de seus contratos inteligentes!!!! Este projeto chama-se Project Frog / Project Lilypad - e será uma camada de integração que permitirá chamar jobs de Bacalhau a partir de contratos inteligentes do FEVM.
Fique de olho no progresso disso, inscrevendo-se em nosso boletim informativo ou participando das redes sociais abaixo.
Parabéns se você leu até o fim!!!
Gostaria de curtir, comentar, seguir ou compartilhar se isso foi útil para você! <3
Mantenha contato com a Bacalhau!
Com ♥️ DeveloperAlly
Apoie Alison Haire tornando-se um patrocinador. Qualquer valor é apreciado!
Saiba mais sobre os Patrocinadores Hashnode
Publicado também aqui .