FVM ãã€ããŒã¹ããŒã¹ ãã¹ãããã㧠AI çæã¢ãŒã NFT ãäœæããããã®ç¬èªã® Text-to-Image ã¹ã¯ãªããã䜿çšã㊠DApp ãæ§ç¯ãå®è¡ããããã€ããããã®å®å šãªã¬ã€ãã§ãã
ãã®ããã°ã§ã¯ããã®æ¹æ³ã«ã€ããŠèª¬æããŸã
Tensorflow ã«åºã¥ããŠããªãŒãã³ãœãŒã¹ã® Python ããŒã¹ã®ããã¹ãããç»åãžã®ã¹ã¯ãªãããäœæããŸã (èå³ããªãå Žåã¯ãBacalhau HTTP ãšã³ããã€ã³ãã䜿çšããããšãã§ããŸã)ã
Bacalhau (ãªãŒãã³ãª p2p ãªããã§ãŒã³ ã³ã³ãã¥ãŒãã£ã³ã° ãã©ãããã©ãŒã ) ã§ãã®ã¹ã¯ãªãããå®è¡ããŸãã
Solidity 㧠NFT ã³ã³ãã©ã¯ããäœæãã (Open Zeppelin ERC721 ã³ã³ãã©ã¯ãã«åºã¥ã)
NFT ã³ã³ãã©ã¯ãã Filecoin Virtual Machine (FVM) Hyperspace Testnet ã« Hardhat ã§ãããã€ãã
ããã³ããšã³ã ã€ã³ã¿ã©ã¯ã·ã§ã³ - React 㧠Bacalhau ã®ããã¹ãããç»åãžã®ã¹ã¯ãªãããš NFT ã³ã³ãã©ã¯ããæäœããæ¹æ³
NFT ã¡ã¿ããŒã¿ã NFT.Storage ã«ä¿åããæ¹æ³
ããã³ããšã³ã DApp ã Fleek ã«ãããã€ããæ¹æ³
ç§ã¯æå³çã«ããã®ã¹ã¿ãã¯ã§å©çšã§ãããªãŒãã³ãœãŒã¹ãšåæ£åãããæè¡ãã§ããã ãå€ã䜿çšããããšãéžæããŸããã
ãã®ããã°ã¯ããªãé·ããªãäºå®ã§ã (ãã¹ãŠã®æ å ±ãæäŸããåå¿è ã«ããããå æ¬çã§ããããšã確èªããããšæããŸã!) ãã®ãããè¡šã®äžã§åœ¹ç«ã€éšåã«é²ãã§ãã ãããå 容㮠<3
(ããããŸã - ãã³ã±ãŒã ã¹ã¿ã㯠#sorrynotsorry ã§ã)
ãªãŒãã³ãœãŒã¹ãš Web3 ããŒãããè©äŸ¡:)
ð¡TLDRã®ãã³ãð¡
ãã®ã¹ã¯ãªããã¯ãCLI ããã³ HTTP ãšã³ããã€ã³ããä»ã㊠Bacalhau ã§äœ¿çšã§ããããã«ãªã£ãŠããããããã®éšåã¯çç¥ããŠããŸããŸããã
å®å®æ¡æ£ã®ç°¡åãªçŽ¹ä»
Stable Diffusion ã¯çŸåšãããã¹ãããç»åãžã®åŠçãè¡ãäž»èŠãªæ©æ¢°åŠç¿ã¢ãã«ã§ã (& 㯠Dall-E ã䜿çšããã¢ãã«ãšåãã§ã)ãããã¯ãã£ãŒã ã©ãŒãã³ã°ã®äžçš®ã§ãç¹å®ã®ã¿ã¹ã¯ãå®è¡ããããšãåŠç¿ããæ©æ¢°åŠç¿ã®ãµãã»ããã§ãããã®å Žåãããã¹ãå ¥åãç»ååºåã«å€æããŸãã
ãã®äŸã§ã¯ããã©ã³ã¹ãã©ãŒããŒã䜿çšããŠããã¹ãããç»åãçæããæ¡æ£ç¢ºçã¢ãã«ã䜿çšããŠããŸãã
ãã ããå¿é ããªãã§ãã ããããã®ããã«æ©æ¢°åŠç¿ã¢ãã«ããã¬ãŒãã³ã°ããå¿ èŠã¯ãããŸãã (ãã ãããããããªãã®å¥œã¿ã§ããã°ããŸã£ããå¯èœã§ã!)ã
代ããã«ãML ã®éã¿ãäºåã«èšç®ãããŠãããããGoogle ã® TensorFlow ãªãŒãã³ãœãŒã¹ Machine Learning ã©ã€ãã©ãªã®äºåãã¬ãŒãã³ã°æžã¿ã¢ãã«ã Python ã¹ã¯ãªããã§äœ¿çšããŸãã
ããæ£ç¢ºã«ã¯ãå ã® ML ã¢ãã«ã®æé©åãããKeras/TensorFlow å®è£ ãã©ãŒã¯ã䜿çšããŠããŸãã
Python ã¹ã¯ãªãã
ðŠ ãã®ããã¹ãããç»åãžã®ã¹ã¯ãªããããã«ãã㊠Docker åããBacalhau ã§å®è¡ããæ¹æ³ã®å®å šãªãŠã©ãŒã¯ã¹ã«ãŒã¯ã Bacalhau ããã¥ã¡ã³ããšãã®@BacalhauProject YouTube ãããªã®äž¡æ¹ã§èŠã€ããããšãã§ããŸãðŠ ãŸãããã®Google Collabs Notebookã§ãå®è¡ã§ããŸãã
ãããå®å šãª 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")
äžèšã®ã¹ã¯ãªããã¯ãåçŽã«ããã¹ã ããã³ããã®å ¥ååŒæ°ãšãã®ä»ã®ãªãã·ã§ã³ ãã©ã¡ãŒã¿ãåãåãããã©ãŒã¯ããã TensorFlow ã©ã€ãã©ãªãåŒã³åºããŠç»åãçæããããããåºåãã¡ã€ã«ã«ä¿åããŸãã
ããã§è¡ãããé¢åãªäœæ¥ã¯ãã¹ãŠã以äžã®ã»ã¯ã·ã§ã³ã§è¡ãããŸããããã§ãæ©æ¢°åŠç¿ã¢ãã«ãéæ³ã®ããã«æ©èœããŸãã ðª
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 ãå¿ èŠãªã¹ã¯ãªãããã©ãã§å®è¡ããã..... ð€ð€
ãããã¯ãã§ãŒã³æè¡ãæ¬è³ªçã«ããŸããããªãããšã 1 ã€ãããšããã°ãããã¯å€§èŠæš¡ãªããŒã¿åŠçã§ããããã¯ããã©ã¹ãã¬ã¹æ§ãæ€é²èæ§ãªã©ã®ä»ã®åŒ·åãªç¹æ§ãæäŸããããã«ãåæ£ã·ã¹ãã ãä»ããŠèšç®ããã³ã¹ããåå ã§ãã
ããŒã«ã« ãã·ã³ãå°ããªäŸã«äœ¿çšããããšã¯å¯èœã§ããå®éããã®ç¹å®ã®äŸãç§ã® (éåžžã«äžæºãª) Mac M1 ã§åäœãããããšãã§ããŸããããçµæãåŸ ã€ã®ã«éåžžã«é·ãæéãããããŸãã (åçã®ã²ãŒã ã¯ãããŸãã?)ãã®ããããã倧ããªããŒã¿åŠçãéå§ãããšãããå€ãã®ã¬ã¹ãå¿ èŠã«ãªããŸã (ããããæå³ãããŠããŸã)ã家ã®åšãã«å°çšãµãŒããŒããªãå Žåã¯ãä»®æ³ãã·ã³ã䜿çšããå¿ èŠããããŸããã¯ã©ãŠã ã³ã³ãã¥ãŒãã£ã³ã° ãã©ãããã©ãŒã ã
ããã¯éäžåãããŠããã ãã§ãªããéå¹ççã§ããããŸããããŒã¿ãèšç®ãã·ã³ããæªç¥ã®è·é¢ã«ãããããã³ã¹ããããããé«éã«ãªãå¯èœæ§ããããŸãããã®ããã® GPU åŠçãæäŸããç¡æå±€ã®ã¯ã©ãŠã ã³ã³ãã¥ãŒãã£ã³ã° ãµãŒãã¹ãèŠã€ããããšãã§ãã (誰ããä»®æ³é貚ã®ãã€ãã³ã°ãçŠæ¢ãããšèšããŸããã?.
ãã«ã«ããŠïŒ
幞ããªããšã«ããããã®åé¡ã¯ Bacalhau ã解決ããããšããŠããåé¡ã®äžéšã§ãã Bacalhau ã§ã¯ãããŒã¿åŠçãšèšç®ããªãŒãã³ã«ããŠèª°ã§ãå©çšã§ããããã«ããåŠçæéãé«éåããããšãã§ããŸãããŸããè€æ°ã®ããŒãéã§ãããåŠçã䜿çšãã次ã«ããŒã¿ãååšããå Žæã«åŠçããŒããé 眮ããããšã«ãã£ãŠ!
Bacalhau ã¯ãIPFSãFilecoinãWeb3 ã«åºæã®åæ£åã®äŸ¡å€ãããåºãæŸæ£ããããšãªããããŒã¿ã®ãªããã§ãŒã³èšç®ãå¯èœã«ããããšã§ãããŒã¿åŠçã®æªæ¥ãæ°äž»åããããšãç®æããŠããŸãã
Bacalhauã¯ããããªãã¯ã§éææ§ãããããªãã·ã§ã³ã§æ€èšŒå¯èœãªèšç®ããã»ã¹ã®ãã©ãããã©ãŒã ãæäŸãããã¢ããŒãã¢ã®ãªãŒãã³ãªèšç®ãããã¯ãŒã¯ã§ãããŠãŒã¶ãŒã¯ãDocker ã³ã³ãããŒãŸã㯠Web Assembly ã€ã¡ãŒãžããIPFS (ããã³ãŸããªã Filecoin) ã«æ ŒçŽãããããŒã¿ãå«ãä»»æã®ããŒã¿ã«å¯ŸããŠã¿ã¹ã¯ãšããŠå®è¡ã§ããŸãã GPU ãžã§ãããµããŒãããŠããã400 ç±³ãã«ä»¥äžã§ã¯ãããŸãã!
ãã«ãªã£ãŠã§ã¹ã¯ãªãããå®è¡ãã
ãã®ã¹ã¯ãªãããå®è¡ããã«ã¯ãBacalhau ã§äœ¿çšã§ããããã« Docker åããŸãããã®æ¹æ³ãåŠã³ããå Žåã¯ããã¡ãã®ãã¥ãŒããªã¢ã«ã«åŸã£ãŠãã ããããã®åŸãBacalhau CLI 㧠1 è¡ã®ã³ãŒãã ãã§å®è¡ã§ããŸã ( Bacalhau ãå¥ã®ã¯ã³ã©ã€ããŒã§ã€ã³ã¹ããŒã«ããåŸ)ã
bacalhau docker run --gpu 1 ghcr.io/bacalhau-project/examples/stable-diffusion-gpu:0.0.1 -- python main.py --o ./outputs --p "Rainbow Unicorn"
ãã ãããã®äŸã§ã¯ããã® Docker åãããå®å®ããæ¡æ£ã¹ã¯ãªããã«æ¥ç¶ãã HTTP ãšã³ããã€ã³ãã䜿çšããŸããããã«ã€ããŠã¯ãçµ±åã»ã¯ã·ã§ã³ã§èª¬æããŸãã
ãã ããããã¯ããŒã¿èšç®ããã»ã¹ãå®è¡ããããã®åŒ·åã§æè»ãªæ¹æ³ã§ãããweb3 ã«ãé©ããŠããããšã«æ³šæããŠãã ããããã® 1 ã€ã®å°ããªã¢ãã«ã«éå®ãããããã§ã¯ãããŸããã
ããã§ã¯ãNFT ã¹ã¯ãªããã«ç§»ããŸãããã :)
ã¹ããŒãã³ã³ãã©ã¯ã
NFT ã¹ããŒã ã³ã³ãã©ã¯ãã¯ã Open Zeppelin ã® ERC721 ã®å®è£ ã«åºã¥ããŠããŸãããã¡ã¿ããŒã¿æšæºæ¡åŒµæ©èœãå«ã ERC721URIStorage ããŒãžã§ã³ã䜿çšããŸã (ãã®ãããIPFS ã¢ãã¬ã¹ã®ã¡ã¿ããŒã¿ãæž¡ãããšãã§ããŸããããã NFT.Storage ã«ä¿åããŸã)ã .
ãã®ããŒã¹ ã³ã³ãã©ã¯ãã¯ãmint() ã transfer() ãªã©ã®é¢æ°ãæ¢ã«å®è£ ãããŠãã NFT ã³ã³ãã©ã¯ãã®äžè¬çãªæ©èœãæäŸããŸãã
ããã³ããšã³ãã®ããŒã¿ããã§ããããããã®ã²ãã¿ãŒé¢æ°ãšãæ°ãã NFT ãäœæããããã³ã«ãªã³ãã§ãŒã³ã§çºè¡ãããã€ãã³ããè¿œå ããããšã«æ°ä»ãã§ããããããã«ãããDApp ãããªã³ãã§ãŒã³ ã€ãã³ãããªãã¹ã³ã§ããããã«ãªããŸãã
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]; }
èŠä»¶
ãã®ã³ã³ãã©ã¯ããFilecoin Virtual Machine Hyperspace Testnetã«å±éããŸããããã®ã³ã³ãã©ã¯ãã¯ãPolygonãBSCãOptimismãArbitrumãAvalanche ãªã©ãå«ã EVM äºæãã§ãŒã³ã«å±éã§ããŸããããã³ããšã³ãã埮調æŽããŠããã«ããã§ãŒã³ NFT ãäœæããããšãã§ããŸã (ãã³ã:ãã®ãªããžããª)!
Hyperspace Testnet ã«ãããã€ããã«ã¯ã以äžãå¿ èŠã§ã
Hardhat ã䜿çšããã¹ããŒã ã³ã³ãã©ã¯ãã®ãããã€
ãã®ã³ã³ãã©ã¯ãã Hyperspace ãã¹ããããã«ãããã€ããããã« hardhat ã䜿çšããŠããŸãã
ðž Hyperspace RPC & BlockExplorer ãªãã·ã§ã³:
ãããªã㯠RPC ãšã³ããã€ã³ã
ãããã¯ãšã¯ã¹ãããŒã©ãŒã®
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 : beryx.zondax.ch
æ§æã®ã»ããã¢ããã§ã¯ãå©çšå¯èœãªãããªã㯠RPC ãšã³ããã€ã³ãã®ããããããéžæã§ããŸãã
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;
ãããŠãã¹ããŒã ã³ã³ãã©ã¯ãããããã€ããããã«ãããã〠ã¹ã¯ãªãããäœæããŸããããã§ã¯çœ²åè (ææè ) ãšããŠãŠã©ã¬ãã ã¢ãã¬ã¹ãå ·äœçã«èšå®ããŠããããšã«æ³šæããŠãã ãããããã€ãã®å¥åŠãªåäœã
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
ç¥ãïŒ Filecoin ãã€ããŒã¹ããŒã¹ ãã¹ããããã« NFT ã³ã³ãã©ã¯ãããããã€ããŸããã
ããããéšåã«ãã£ãšã...ãããŠãããã§ãã¹ãŠããŸãšããæ¥çå€ã:)
ããã³ããšã³ããæ§ç¯ããããã«ãNextJS ãš Typescript ã䜿çšããŠããŸããæ£çŽãªãšãããç§ã¯ NextJS ã® SSR (ãµãŒããŒåŽã¬ã³ããªã³ã°) æ©èœãå©çšããŠããããããŒãž ã«ãŒãã£ã³ã°ã䜿çšããŠããŸãã (åäžããŒãžã® Dapp ã§ãããã)ãããã©ã® React ã»ããã¢ãã (ãŸãã¯ãã¡ãããä»»æã®ãã¬ãŒã ã¯ãŒã¯!) ã䜿çšããŸãã
ã¿ã€ãã¹ã¯ãªããã«é¢ããŠã¯...ãŸããç§ã¯ãããå°ãæ¥ãã§æ§ç¯ããã®ã§ããããã¿ã€ãã¹ã¯ãªããã®ããŸãè¯ãäŸã§ã¯ãªãããšãèªããªããã°ãªããŸãã-å€æ°ã¯æºè¶³ããŠããããã§ãã... ;)
Anyhoo - ãã®ã»ã¯ã·ã§ã³ã®äž»ãªãã€ã³ãã¯ãããã³ã ãšã³ãã®ã³ãŒãã£ã³ã°æ¹æ³ã瀺ãããšã§ã¯ãªããã¹ããŒã ã³ã³ãã©ã¯ããBacalhau (å®å®ããæ¡æ£ ML ã¢ãã«ã䜿çš)ããããŠãã¡ãã NFT.Storage ãšããåãããæ¹æ³ã瀺ãããšã§ãã NotOnIPFSNotYourNFTã
[todo: ãããŒãã£ãŒãå³ãäœæãã]
ãããã³ãŒãã«å®è£ ããæ¹æ³ãèŠãŠã¿ãŸãããã
Bacalhau ã®ããã³ããšã³ã API ãšã³ããã€ã³ãã®äœæã«ã€ããŠã¯ããšã³ãžãã¢ã®Luke Marsdenã«ãããã®ãããžã§ã¯ã ã¬ããŒãã«èšèŒãããŠããŸãã
API ã¯çŸåšããã®ããã°ã«èšèŒãããŠããå®å®ããæ¡æ£ã¹ã¯ãªããã«çŽæ¥ãããããã ãã§ãããããŒã ã¯ãããããäžè¬ç㪠API ã«æ¡åŒµããŠãä»»æã®äŸãåŒã³åºãããšãã§ããããã«ããŠããŸããæ®ãã® APIããããŸãã¯FilecoinProject slack ã® #bacalhau ãã£ã³ãã«ã§ããã«æ³šç®ããŠãã ããã
>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; };
ãã®é¢æ°ã¯ã以äžã®ãããªãã©ã«ããŒæ§é ãæ〠IPFS CID (ã³ã³ãã³ãèå¥å) ãè¿ããŸããç»åã¯/outputs/image0.png
ã®äžã«ãããŸãã
ð¡ ãããã¯ãªãã¯ããŠèªåã®ç®ã§ç¢ºãããŠãã ãã ! ð¡
ãããè¹è²ã®ãŠãã³ãŒã³âŠå¥œãã§ã¯ãªããã®ã¯äœã§ãã!
NFT.Storage ã¯ãjavascript ãŸã㯠HTTP SDK ã䜿çšã㊠IPFS ããã³ Filecoin ã« NFT ã¡ã¿ããŒã¿ãæ°žç¶çã«ä¿åããããšã容æã«ããå ¬å ±è²¡ (å¥åç¡æ) ã§ãã
NFT ã¡ã¿ããŒã¿ã¯ã以äžã®äŸã®ãã㪠JSON ããã¥ã¡ã³ãã§ããããã¯ãOpen Zeppelin ããã¥ã¡ã³ãããçŽæ¥ååŸãããã®ã§ãã
NFT ãäœæãããšãã¯ãã¡ã¿ããŒã¿ããªã³ãã§ãŒã³ã«ä¿åããªãéã (倧ããªãã¡ã€ã«ã§ã¯éåžžã«é«äŸ¡ã«ãªãå¯èœæ§ããããŸã)ãããŒã¯ã³ã®ãé代æ¿æ§ãã«æºæ ããããã«ã次ã®ãããªã¹ãã¬ãŒãžãå¿ èŠã§ããããšã«æ³šæããããšãéèŠã§ããæ°žç¶çã§ãä¿¡é Œæ§ãé«ããäžå€ã§ãã
ããªãã® NFT ãäžèšã®äŸã®ããã«ãã±ãŒã·ã§ã³ããŒã¹ã®ã¢ãã¬ã¹ãæã£ãŠããå Žåã販売åŸã«ãã®ãã±ãŒã·ã§ã³ ãã¹ãåãæ¿ããã®ã¯ããªãç°¡åã§ãã以äžã¯ãNFTã®äœæè ãã¢ãŒãç»åãã©ã°ã®åçã«åãæ¿ããå Žæã§ã.
Open Zeppelin ã§ããèŠåããŠããããšããããŸãã
NFT.Storage ã䜿çšãããšãIPFS ã«ãã³çããããã ãã§ãªããæ°žç¶åã®ããã« Filecoin ã«ãä¿åãããã¡ã¿ããŒã¿ã®äžå€ã® IPFS ãã¡ã€ã« CID (ã³ã³ãã³ã- å Žæã§ã¯ãªã - IDãšã³ãã£ãã£) ãååŸããããšãæå³ããŸãããµã€ã³ã¢ããããã ãã§æžã¿ãŸãã NFT.Storage ãéãããã®API ããŒã (.env ãã¡ã€ã«ã«ä¿åãããã) ååŸããŸãã
.env example
NEXT_PUBLIC_NFT_STORAGE_API_KEY=xxx
ãŸããé©åãªåœ¢åŒã®ã¡ã¿ããŒã¿ JSON ãäœæããããšã確èªããå¿ èŠããããŸããFVM ã«ã¯ (ãŸã !) NFT ããŒã±ãããã¬ã€ã¹ããããŸããããæ¡çšããããšãã« NFT ãæšæºã«æºæ ããŠããããšã確èªãããããã§ãã .
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; };
ããã§ã¯ããã®ã¡ã¿ããŒã¿ã 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 - Bacalhau ããã®ã€ã¡ãŒãžããããNFT.Strorage ã䜿çšããŠã¡ã¿ããŒã¿ãäžå€ãã€æ°žç¶çã«ä¿åããŸãããNFT ãäœæããŸããã!
ð¡ã¯ã€ã㯠ãã³ãð¡NFT.Storage ã¯ãstoreCar ã storeDirectory ãªã©ã®ä»ã®API åŒã³åºãããCID ã® IPFS ãã³ãã³ã°ãš Filecoin ã¹ãã¬ãŒãžååŒãè¿ãstatus() é¢æ°ãæäŸããŸã -> ããã¯ã NFT ã®ã¹ããŒã¿ã¹ããã§ãã¯ããããã® FEVM DApp (ãŸã㯠FEVM ãã¡ã€ã³ããã ãªãªãŒã¹ã«ãªã£ãåŸã® FEVM ã§ã® NFT å®è£ )ã
ããã«ã¯ 3 çš®é¡ã®ã€ã³ã¿ã©ã¯ã·ã§ã³ããããŸã (ãããŠãããã€ãã® FEVM ã®èœãšãç©ŽããããŸã - ããŒã¿æè¡ã«ã¯åžžã«ããã€ãã®é¢šå€ãããªãã°æ©èœããããŸã!)
å€æŽããã«ãã§ãŒã³ããããŒã¿ãååŸããããã®èªã¿åãå°çšåŒã³åºã
眲åããŠã¬ã¹ãæ¯æãããã«ãŠã©ã¬ãããå¿ èŠãšããåŒã³åºããæžã蟌ã¿ãŸãã NFT ã®é³é ã®ããã«ããã§ãŒã³ã®ç¶æ ãå€æŽããé¢æ°!
ã€ãã³ã ãªã¹ã㌠- ã³ã³ãã©ã¯ãããçºè¡ãããã€ãã³ãããªãã¹ã³ãã
ããããã¹ãŠã®é¢æ°ã«ã€ããŠãEthereum API ã®è»œéã©ãããŒã§ããethers.js ã©ã€ãã©ãªã䜿çšããŠãã³ã³ãã©ã¯ãã«æ¥ç¶ãããã®åŒã³åºããå®è¡ããŸãã
ãããªã㯠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 = 'https://api.hyperspace.node.glif.io/rpc/v1'; const provider = new ethers.providers.JsonRpcProvider(rpc); const connectedReadBacalhauContract = new ethers.Contract( contractAddressHyperspace, BacalhauCompiledContract.abi, provider );
ã³ã³ãã©ã¯ãã®ã€ãã³ãããªãã¹ã³ããŸããããã¯èªã¿åãå°çš (get) ã€ãã³ãã§ããããããããªã㯠RPC ã䜿çšããŠãã§ãŒã³äžã®ã€ãã³ãã®çºè¡ããªãã¹ã³ã§ããŸãã
//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 ãåŒã³åºããŸãã
ãŸãããŠãŒã¶ãŒããã®ãŠã©ã¬ãã ã¢ãã¬ã¹ãååŸããFVM ãã€ããŒã¹ããŒã¹ ãã§ãŒã³äžã«ããããšã確èªããŸãã chainId ã確èªããæ¹æ³ããHyperspace ãããã¯ãŒã¯ãããã°ã©ã 㧠Metamask / wallet ã«è¿œå ããæ¹æ³ãªã©ã䟿å©ãªãŠã©ã¬ããé¢æ°ãããã€ã玹ä»ããŸããEthereum ãªããžã§ã¯ããçŽæ¥äœ¿çšãããã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); }); } };
ã³ã³ãã©ã¯ã mint é¢æ°ãæžã蟌ã¿ã¢ãŒãã§åŒã³åºã....
// 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 Minted!!ãŠãã³ãŒã³ãã³ã¹ã¢ãŒãã¿ã€ã ïŒ
Bacalhau ã¯ãããŒã¿ã«å¯ŸããŠå埩çã§æ±ºå®è«çãªåŠçãžã§ããå®è¡ããã®ã«é©ããŠããŸãã
ETL ããã»ã¹
æ©æ¢°åŠç¿ãš AI
IOT ããŒã¿ã®çµ±å
ãå«ããããåŠç
ãããªãšç»åã®åŠç - ã¯ãªãšã€ãã£ãã«æé©
äžèšã®ããã€ããéæããæ¹æ³ã«ã€ããŠãã Bacalhau ã®ããã¥ã¡ã³ãã«è€æ°ã®äŸããããŸãã
Bacalhau 㯠FEVM ã¹ããŒã ã³ã³ãã©ã¯ããã Bacalhau ãçŽæ¥åŒã³åºãããã®çµ±åã®æ§ç¯ã«å¿æ®ºãããŠããŸãããBacalhau x FVM ã³ã©ãã¬ãŒã·ã§ã³ã«é¢ããããã€ãã®èãã以äžã«ç€ºããŸãã
çŸåšãã¹ããŒã ã³ã³ãã©ã¯ãããçŽæ¥ Bacalhau ãå®è¡ããæ¹æ³ãæ§ç¯äžã§ã!!!!ãã®ãããžã§ã¯ã㯠Project Frog / Project Lilypad ãšåŒã°ããFEVM ã¹ããŒã ã³ã³ãã©ã¯ããã Bacalhau ãžã§ããåŒã³åºãããšãå¯èœã«ããçµ±åã¬ã€ã€ãŒã«ãªããŸãã
ãã¥ãŒã¹ã¬ã¿ãŒã«ãµã€ã³ã¢ãããããã以äžã®ãœãŒã·ã£ã«ã«åå ããŠããã®é²æç¶æ³ã«æ³šç®ããŠãã ããã
æåŸãŸã§èªãã§ããããããã§ãšãïŒïŒïŒ
ããã圹ã«ç«ã£ãå Žåã¯ãããããã³ã¡ã³ãããã©ããŒããŸãã¯å ±æããŠããã ããã°å¹žãã§ãã <3
ãã«ãªã£ãŠãšé£çµ¡ãåãåããŸãããïŒ
⥠ïžDeveloperAllyãš
ã¹ãã³ãµãŒã«ãªã£ãŠã¢ãªãœã³ã»ãã¢ãŒããµããŒãããŸãããããããã§ã倧æè¿ã§ãïŒ
Hashnode ã¹ãã³ãµãŒã®è©³çŽ°
ããã«ãæ²èŒãããŠããŸãã