====== トレーサビリティを提供する DApps ====== {{:blockchain:traceabilitynft.drawio.png?600|}} * create はERC721.sol の _mint(to), _safeMint(to, tokenId) で実現できる * _safeMint は誤って NFT の所有者がコントラクトアドレスになることを防ぐ * deliver は ERC721.sol の transferFrom(from, to, tokenId), safeTransferFrom(from, to, tokenId) で実現できる * burn は ERC721.sol の burn(tokenId) で実現できる ===== 課題 ===== - トレースはどう実現すべきか? * ブロックチェーンのトランザクションログからトレース? * 追加で履歴を書き込まないためガス代が安く済むと思われる * スマートコントラクトに transferFrom の履歴を保持する? * 追加で履歴を表すデータを書き込むためガス代がかかる - あるアカウントが所有している NFT を全件取得するには? * balanceOf(address) で所有する NFT の総数は分かる * ItemNFT に所有する NFT を全件返すメソッドを実装すれば実現することはできる * 他に良い方法がないのか? ===== コード ===== 実装中のコード。 ItemNFT.sol // SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; import "@openzeppelin/contracts/access/AccessControl.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; contract ItemNFT is ERC721, ERC721Enumerable, ERC721Burnable, AccessControl { using Counters for Counters.Counter; bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); Counters.Counter private _tokenIdCounter; constructor() ERC721("Item NFT", "INFT") { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); _grantRole(MINTER_ROLE, msg.sender); } function _baseURI() internal pure override returns (string memory) { return "https://dummy.com/api/v1/tokens/"; } function safeMint(address to) public onlyRole(MINTER_ROLE) { uint256 tokenId = _tokenIdCounter.current(); _tokenIdCounter.increment(); _safeMint(to, tokenId); } // The following functions are overrides required by Solidity. function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable) { super._beforeTokenTransfer(from, to, tokenId); } function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable, AccessControl) returns (bool) { return super.supportsInterface(interfaceId); } } ItemNFT.tsx import React, { useContext, useState } from "react"; import { ItemNFTContext } from "./../hardhat/SymfoniContext"; import { SignerContext } from "./../hardhat/SymfoniContext"; import { BigNumber } from "ethers"; interface Props {} export const ItemNFT: React.FC = () => { const ItemNFT = useContext(ItemNFTContext); const Signer = useContext(SignerContext); const [address, setAddress] = useState(""); Signer[0]?.getAddress().then((address) => { setAddress(address); }); const [tokenId, setTokenId] = useState(); const [tokenURI, setTokenURI] = useState(""); const handleGetAccounts = async ( e: React.MouseEvent ) => { e.preventDefault(); console.log(Signer); }; const handleSafeMint = async ( e: React.MouseEvent ) => { e.preventDefault(); if (!ItemNFT.instance) throw Error("ItemNFT instance not ready"); if (ItemNFT.instance) { const tx = await ItemNFT.instance.safeMint(address); const waitLog = await tx.wait(); const event = waitLog.events ? waitLog.events[0] : undefined; if(event !== undefined){ const _tokenId = event.args ? event.args.tokenId : undefined; setTokenId(_tokenId); } } }; const handleTokenURI = async ( e: React.MouseEvent ) => { e.preventDefault(); if (!ItemNFT.instance) throw Error("ItemNFT instance not ready"); if (ItemNFT.instance) { if (tokenId !== undefined && tokenId.toNumber() >= 0){ const tokenURI = await ItemNFT.instance.tokenURI(tokenId); setTokenURI(tokenURI); } } }; return (

Signer: { address }

tokenId: { tokenId?.toNumber() }

tokenURI: { tokenURI }

); };