目次

トレーサビリティを提供する DApps

課題

  1. トレースはどう実現すべきか?
    • ブロックチェーンのトランザクションログからトレース?
      • 追加で履歴を書き込まないためガス代が安く済むと思われる
    • スマートコントラクトに transferFrom の履歴を保持する?
      • 追加で履歴を表すデータを書き込むためガス代がかかる
  2. あるアカウントが所有している 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<Props> = () => {
  const ItemNFT = useContext(ItemNFTContext);
  const Signer = useContext(SignerContext);
  const [address, setAddress] = useState<string>("");
  Signer[0]?.getAddress().then((address) => {
    setAddress(address);
  });
  const [tokenId, setTokenId] = useState<BigNumber>();
  const [tokenURI, setTokenURI] = useState<string>("");
 
  const handleGetAccounts = async (
    e: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    e.preventDefault();
    console.log(Signer);
  };
 
  const handleSafeMint = async (
    e: React.MouseEvent<HTMLButtonElement, 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<HTMLButtonElement, 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 (
    <div>
      <button onClick={(e) => handleGetAccounts(e)}>GetAccounts</button>
      <p>Signer: { address }</p>
      <button onClick={(e) => handleSafeMint(e)}>mint</button>
      <p>tokenId: { tokenId?.toNumber() }</p>
      <button onClick={(e) => handleTokenURI(e)}>get tokenURI</button>
      <p>tokenURI: { tokenURI }</p>
    </div>
  );
};