FVM ãã€ããŒã¹ããŒã¹ ãã¹ãããã㧠AI çæã¢ãŒã NFT ãäœæããããã®ç¬èªã® Text-to-Image ã¹ã¯ãªããã䜿çšã㊠DApp ãæ§ç¯ãå®è¡ããããã€ããããã®å®å šãªã¬ã€ãã§ãã
(ããããŸã - ãã³ã±ãŒã ã¹ã¿ã㯠#sorrynotsorry ã§ã)
ð¡TLDRã®ãã³ãð¡
ãã®ã¹ã¯ãªããã¯ãCLI ããã³ HTTP ãšã³ããã€ã³ããä»ã㊠Bacalhau ã§éçšã§ããããã«ãªã£ãŠããããããã®å°æ¹ã¯çç¥ããŠããŸããŸããã
å®å®æ¡æ£ã®ç°¡åãªçŽ¹ä»
ãã ããå¿é ããªãã§ãã ããããã®ããã«æ©æ¢°åŠç¿ã¢ãã«ããã¬ãŒãã³ã°ããå¿ èŠæ§ã¯ãããŸãã (ãã ãããããããªãã®å¥œã¿ã§ããã°ããŸã£ããèœå€ã§ã!)ã
Python ã¹ã¯ãªãã
ðŠ ãã®ããã¹ãããç»åãžã®ã¹ã¯ãªããããã«ãã㊠Docker åããBacalhau ã§å®è¡ããæ¹æ³ã®å®å šãªãŠã©ãŒã¯ã¹ã«ãŒã¯ã ãšãã®ã®äž¡æ¹ã§èŠã€ããããšãã§ããŸãðŠ ãŸãããã®ã§ãå®è¡ã§ããŸãã
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")
generator = Text2Image( img_height=height, img_width=width, jit_compile=False, ) img = generator.generate( prompt, num_steps=numSteps, unconditional_guidance_scale=unconditionalGuidanceScale, temperature=temperature, batch_size=batchSize, )
ããã¯éäžåãããŠããã ãã§ãªããéå¹ççã§ããããŸããããŒã¿ãèšç®ãã·ã³ããæªç¥ã®è·é¢ã«ãããããã³ã¹ããããããé«éã«ãªãå¯èœæ§ããããŸãããã®ããã® GPU åŠçãæäŸããç¡æå±€ã®ã¯ã©ãŠã ã³ã³ãã¥ãŒãã£ã³ã° ãµãŒãã¹ãèŠã€ããããšãã§ãã (誰ããä»®æ³é貚ã®ãã€ãã³ã°ãçŠæ¢ãããšèšããŸããã?.
ãã«ã«ããŠïŒ
ã¯ããããªãã¯ã§éææ§ãããããªãã·ã§ã³ã§æ€èšŒå¯èœãªèšç®ããã»ã¹ã®ãã©ãããã©ãŒã ãæäŸãããã¢ããŒãã¢ã®ãªãŒãã³ãªèšç®ãããã¯ãŒã¯ã§ãããŠãŒã¶ãŒã¯ãDocker ã³ã³ãããŒãŸã㯠Web Assembly ã€ã¡ãŒãžããIPFS (ããã³ãŸããªã Filecoin) ã«æ ŒçŽãããããŒã¿ãå«ãä»»æã®ããŒã¿ã«å¯ŸããŠã¿ã¹ã¯ãšããŠå®è¡ã§ããŸãã GPU ãžã§ãããµããŒãããŠããã400 ç±³ãã«ä»¥äžã§ã¯ãããŸãã!
ãã«ãªã£ãŠã§ã¹ã¯ãªãããå®è¡ãã
bacalhau docker run --gpu 1 ghcr.io/bacalhau-project/examples/stable-diffusion-gpu:0.0.1 -- python main.py --o ./outputs --p "Rainbow Unicorn"
ã¹ããŒãã³ã³ãã©ã¯ã
NFT ã¹ããŒã ã³ã³ãã©ã¯ãã¯ã ã«åºã¥ããŠããŸãããã¡ã¿ããŒã¿æšæºæ¡åŒµæ©èœãå«ã ERC721URIStorage ããŒãžã§ã³ãæäœããŸã (ãã®ãããIPFS ã¢ãã¬ã¹ã®ã¡ã¿ããŒã¿ãæž¡ãããšãã§ããŸããããã NFT.Storage ã«äžäŒ ããŸã)ã .
ð¡ ð¡
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]; }
èŠä»¶
ãã®ã³ã³ãã©ã¯ããã«å±éããŸããããã®ã³ã³ãã©ã¯ãã¯ãPolygonãBSCãOptimismãArbitrumãAvalanche ãªã©ãå«ã EVM äºæãã§ãŒã³ã«å±éã§ããŸããããã³ããšã³ãã埮調æŽããŠããã«ããã§ãŒã³ NFT ãåæããããšãã§ããŸã (ãã³ã:)!
Hardhat ã䜿çšããã¹ããŒã ã³ã³ãã©ã¯ãã®ãããã€
ãã®ã³ã³ãã©ã¯ãã Hyperspace ãã¹ããããã«ãããã€ããããã« hardhat ãå®å šäœ¿çšããŠããŸãã
ðž Hyperspace RPC & BlockExplorer ãªãã·ã§ã³:
ãããªã㯠RPC ãšã³ããã€ã³ããããã¯ãšã¯ã¹ãããŒã©ãŒã®ãªãŒãã³ API :
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: '//api.hyperspace.node.glif.io/rpc/v1', chainId: 3141, accounts: [process.env.WALLET_PRIVATE_KEY ?? 'undefined'], }, // bleeding edge often-reset FVM testnet filecoinWallaby: { url: '//wallaby.node.glif.io/rpc/v0', chainId: 31415, accounts: [process.env.WALLET_PRIVATE_KEY ?? 'undefined'], //explorer: //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;
ãããŠãã¹ããŒã ã³ã³ãã©ã¯ãããããã€ããããã«ãããã〠ã¹ã¯ãªãããåŒæããŸããããã§ã¯çŸåè
(å
¶ä»è
) ãšããŠãŠã©ã¬ãã ã¢ãã¬ã¹ãäž»èŠçã«èšå®ããŠããããšã«ç¹å«æ³šæããŠãã ãããããã€ãã®å¥åŒãªåäœã
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; });
å±éããã«ã¯ã次ã®ã³ãŒãã䜿çšçããŠã¿ãŒããã«ã§äžèšã®ã¹ã¯ãªãããå®è¡ããŸã (泚æäºé¡¹: æ§æã§ããã©ã«ãã®ãããã¯ãŒã¯ã filecoinHyperspace ã«èšå®ããããããããã¯ãŒã¯ã®ãã©ã°ãæž¡ãçšäžçã¯ãããŸããããããã¯æ以äžã«ç€ºãããŠããŸã)ã > cd ./pages/hardhat/deploy/
npx hardhat run ./deployBacalhauFRC721.ts --network filecoinHyperspace
API ã¯çŸåšããã®ããã°ã«èšèŒãããŠããå®å®ããæ¡æ£ã¹ã¯ãªããã«çŽæ¥ãããããã ãã§ãããããŒã ã¯ãããããäžè¬ç㪠API ã«æ¡åŒµããŠãä»»æã®äŸãåŒã³åºãããšãã§ããããã«ããŠããŸããæ®ãã® APIããŸãã¯
>run/test in terminal
curl -XPOST -d '{"prompt": "rainbow unicorn"}' '//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 = '//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; };
ãã®é¢æ°ã¯ã以äžã®ãããªãã©ã«ããŒæ§é ãæ〠IPFS CID (ã³ã³ãã³ãèå¥å) ãè¿ããŸããç»åã¯/outputs/image0.png
ã®äžã«ãããŸãã
ð¡ ãããã¯ãªãã¯ç®ã§ç¢ºãããŠãã ãã ! ð¡
NFT ãäœæãããšãã¯ãã¡ã¿ããŒã¿ããªã³ãã§ãŒã³ã«ä¿åããªãéã (倧ããªãã¡ã€ã«ã§ã¯éåžžã«é«äŸ¡ã«ãªãå¯èœæ§ããããŸã)ãããŒã¯ã³ã®ãé代æ¿æ§ãã«æºæ ããããã«ã次ã®ãããªã¹ãã¬ãŒãžãå¿ èŠã§ããããšã«æ³šæããããšãéèŠã§ããæ°žç¶çã§ãä¿¡é Œæ§ãé«ããäžå€ã§ãã
NFT.Storage ã䜿çšãããšãIPFS ã«ãã³çããããã ãã§ãªããæ°žç¶åã®ããã« Filecoin ã«ãä¿åãããã¡ã¿ããŒã¿ã®äžå€ã® IPFS ãã¡ã€ã« CID (ã³ã³ãã³ã- å Žæã§ã¯ãªã - IDãšã³ãã£ãã£) ãååŸããããšãæå³ããŸãããµã€ã³ã¢ããããã ãã§æžã¿ãŸãã NFT.Storage ãéãããã®ã (.env ãã¡ã€ã«ã«ä¿åãããã) ååŸããŸãã
.env example
NEXT_PUBLIC_NFT_STORAGE_API_KEY=xxx
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 //[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; };
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'); });
ð¡ã¯ã€ã㯠ãã³ãð¡NFT.Storage ã¯ãstoreCar ã storeDirectory ãªã©ã®ä»ã®ããCID ã® IPFS ãã³ãã³ã°ãš Filecoin ã¹ãã¬ãŒãžååŒãè¿ãstatus() é¢æ°ãæäŸããŸã -> ããã¯ã NFT ã®ã¹ããŒã¿ã¹ããã§ãã¯ããããã® FEVM DApp (ãŸã㯠FEVM ãã¡ã€ã³ããã ãªãªãŒã¹ã«ãªã£ãåŸã® FEVM ã§ã® NFT å®è£ )ã
ãããªã㯠RPC ã䜿çšããŠèªã¿åãã¢ãŒãã§ã³ã³ãã©ã¯ãã«æ¥ç¶ãã:
//The compiled contract found in pages/api/hardhat/artifacts/contracts import BacalhauCompiledContract from '@Contracts/BacalhauFRC721.sol/BacalhauFRC721.json'; //On-chain address of the contract const contractAddressHyperspace = '0x773d8856dd7F78857490e5Eea65111D8d466A646'; //A public RPC Endpoint (see table from contract section) const rpc = '//api.hyperspace.node.glif.io/rpc/v1'; const provider = new ethers.providers.JsonRpcProvider(rpc); const connectedReadBacalhauContract = new ethers.Contract( contractAddressHyperspace, BacalhauCompiledContract.abi, provider );
//use the read-only connected Bacalhau Contract connectedReadBacalhauContract.on( // Listen for the specific event we made in our contract 'NewBacalhauFRC721NFTMinted', (sender: string, tokenId: number, tokenURI: string) => { //DO STUFF WHEN AN EVENT COMES IN // eg. re-fetch NFT's, store in state and change page status } );
æžã蟌ã¿ã¢ãŒãã§ã®ã³ã³ãã©ã¯ããžã®æ¥ç¶ - ããã«ã¯ããŠãŒã¶ãŒããã©ã³ã¶ã¯ã·ã§ã³ã«çœ²åããŠã¬ã¹ä»£ãæ¯æãããšãã§ããããã«ãEthereum ãªããžã§ã¯ãããŠã©ã¬ããã«ãã£ãŠ Web ãã©ãŠã¶ã«æ¿å ¥ãããŠããå¿ èŠããããŸã - ããããwindow.ethereum ããã§ãã¯ããŠããçç±ã§ããç©äœã
//Typescript needs to know window is an object with potentially and ethereum value. There might be a better way to do this? Open to tips! declare let window: any; //The compiled contract found in pages/api/hardhat/artifacts/contracts import BacalhauCompiledContract from '@Contracts/BacalhauFRC721.sol/BacalhauFRC721.json'; //On-chain address of the contract const contractAddressHyperspace = '0x773d8856dd7F78857490e5Eea65111D8d466A646'; //check for the ethereum object if (!window.ethereum) { //ask user to install a wallet or connect //abort this } // else there's a wallet provider else { // same function - different provider - this one has a signer - the user's connected wallet address const provider = new ethers.providers.Web3Provider(window.ethereum); const contract = new ethers.Contract( contractAddressHyperspace, BacalhauCompiledContract.abi, provider ); const signer = provider.getSigner(); const connectedWriteBacalhauContract = contract.connect(signer); }
write connected contract ãæœçšã㊠mint Function ãåŒã³åºããŸãã
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: [ '//hyperspace.filfox.info/rpc/v0', '//filecoin-hyperspace.chainstacklabs.com/rpc/v0', ], chainName: 'Filecoin Hyperspace', nativeCurrency: { name: 'tFIL', symbol: 'tFIL', decimals: 18, }, blockExplorerUrls: [ '//fvm.starboard.ventures/contracts/', '//hyperspace.filscan.io/', '//beryx.zondax.chfor', ], }, ], }) .then((res: XMLHttpRequestResponseType) => { console.log('added hyperspace successfully', res); }) .catch((err: ErrorEvent) => { console.log('Error adding hyperspace network', err); }); } };
// Pass in the metadata return from saving to NFT.Storage const mintNFT = async (metadata: any) => { await connectedWriteBacalhauContract // The name of our function in our smart contract .mintBacalhauNFT( userWallet.accounts[0], //users account to use metadata.url //test ipfs address ) .then(async (data: any) => { console.log('CALLED CONTRACT MINT FUNCTION', data); await data .wait() .then(async (tx: any) => { console.log('tx', tx); //CURRENTLY NOT RETURNING TX - (I use event triggering to know when this function is complete) let tokenId = tx.events[1].args.tokenId.toString(); console.log('tokenId args', tokenId); setStatus({ ...INITIAL_TRANSACTION_STATE, success: successMintingNFTmsg(data), }); }) .catch((err: any) => { console.log('ERROR', err); setStatus({ ...status, loading: '', error: errorMsg(err.message, 'Error minting NFT'), }); }); }) .catch((err: any) => { console.log('ERROR1', err); setStatus({ ...status, loading: '', error: errorMsg( err && err.message ? err.message : null, 'Error minting NFT' ), }); }); }
Bacalhau ã¯ãããŒã¿ã«å¯ŸããŠå埩çã§æ±ºå®è«çãªåŠçãžã§ããå®è¡ããã®ã«é©ããŠããŸãã
⥠ãš
ã¹ãã³ãµãŒã«ãªã£ãŠã¢ãªãœã³ã»ãã¢ãŒããµããŒãããŸãããããããã§ã倧æè¿ã§ãïŒ