Help us understand the problem. What are the problem?

This post is Private. Only a writer or those who know its URL can access this post.

posted at

updated at

solidity-jp #1

今日のテーマ

  • ERC-721(NFT)を発行する独自コントラクトを作ってみようハンズオン
  • Remixを使ってNFTを発行する独自コントラクトを作りながらSolidityを学びます。
  • https://solidity-jp.connpass.com/event/237306/

環境

  • macOS 12.0
  • GoogleChrome 97.0.4692.99
  • Remix 0.20.3

準備

  • GoogleChromeのインストール
  • Metamaskのインストール

コントラクト開発

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.4.2/contracts/token/ERC721/ERC721.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.4.2/contracts/utils/Counters.sol";

contract SushiNeko is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenCounter;

    constructor () ERC721 ("SushiNeko", "SNEKO") {}

    function mint() public returns (uint256) {
        // 0 -> 1, 1 -> 2 .....
        _tokenCounter.increment();
        uint256 newItemId = _tokenCounter.current();

        // NFTを発行する処理
        _safeMint(msg.sender, newItemId);
        return newItemId;
    }
}

コンパイル

  • ブロックチェーン上にデプロイするためにコンパイルを行います。
  • Control(Command)+S で保存&コンパイルが走ります。もしくは手動でコンパイルボタンからコンパイル

スクリーンショット 2022-01-27 16.54.42.png

ブラウザ上のローカルブロックチェーンにデプロイ

  • ブラウザ上のローカルブロックチェーンにデプロイする

スクリーンショット 2022-01-27 16.56.07.png

  • mintボタンからNFTの発行ができる。
  • ownerOfの横にtokenIDを入力してownerOfボタンを押すと tokenIDが1のNFTをどのアドレスが所有しているか表示される

スクリーンショット 2022-01-27 16.57.33.png

  • テストネット上でデプロイしてみよう

rinkeby テストネット上にデプロイ

スクリーンショット 2022-01-27 17.00.06.png

スクリーンショット 2022-01-27 17.00.25.png

  • デプロイする
  • デプロイしたコントラクトのアドレスを元にetherscanを見てみる
    • https://rinkeby.etherscan.io/address/コントラクトのアドレス
    • 例: https://rinkeby.etherscan.io/address/0x19a887b4984dca2b1ca293e03ec11a7787189215

スクリーンショット 2022-01-27 17.01.38.png

  • remixからmintしてみる

スクリーンショット 2022-01-27 17.02.07.png

  • rinkebyテストネットのetherscanを見てみる
  • openseaテストネットを見てみる
    • https://testnets.opensea.io/assets/コントラクトのアドレス/トークンID
    • 例: https://testnets.opensea.io/assets/0x2949c658ED864a79B6DdbbDA3BeEB6Bb4a80ACDA/1
  • 出品されているのを確認。ただしデータがなにもないのを確認
    • これはtokenURIが正しく設定されていないため。

メタデータを入れてみよう

  • mint時にtokenURIを書き込む方法を試す
  • コントラクトを書き換えよう。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.4.2/contracts/token/ERC721/ERC721.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.4.2/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.4.2/contracts/utils/Counters.sol";


contract SushiNeko is ERC721URIStorage {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenCounter;

    constructor () ERC721 ("SushiNeko", "SNEKO") {}

    function mint(string memory tokenURI) public returns (uint256) {
        _tokenCounter.increment();
        uint256 newItemId = _tokenCounter.current();

        _safeMint(msg.sender, newItemId);
        _setTokenURI(newItemId, tokenURI);

        return newItemId;
    }
}
  • rinkebyテストネットにデプロイしておく

メタデータの用意

  • メタデータを用意する。今回はpinataで用意する
  • 画像を用意し、pinata経由でipfsにアップロード

animal_dance_cat.png

  • アップロードした画像のURLを取得し、以下のjsonファイルを作成する
{
  "name": "すしねこ #1",
  "description": "まぐろ",
  "image": "https://gateway.pinata.cloud/ipfs/QmWyX6q28LTkGBkjidn1bKKv1gFc9NjqoG3MjvU3FwYnAg"
}
  • このjsonファイルをpinata経由でipfsにアップロードし、URLをコピーする
// 今回のサンプルメタデータjson URL
https://gateway.pinata.cloud/ipfs/QmbsrWq6FBpcbn4zoZKFZ6qPwqud2QtJ21gBkQ1fiibaFT
  • mintボタンのところにあるテキストボックスにメタデータのURLをコピペし、mintボタンを押す

スクリーンショット 2022-01-27 17.07.55.png

  • デプロイしたコントラクトのアドレスを元にetherscanを見てみる
    • https://rinkeby.etherscan.io/address/コントラクトのアドレス
    • 例: https://rinkeby.etherscan.io/address/0x19a887b4984dca2b1ca293e03ec11a7787189215
  • openseaテストネットを見てみる
    • https://testnets.opensea.io/assets/コントラクトのアドレス/トークンID
    • 例: https://testnets.opensea.io/assets/0x2949c658ED864a79B6DdbbDA3BeEB6Bb4a80ACDA/1
  • 確認できたら成功〜〜!!

まとめ

  • Remixを使ってスマートコントラクトを開発した
  • ERC721を継承させ、デプロイし、NFTをmintした
  • etherscanとopenseaテストネットからmintしたNFTを確認した

おすすめの記事

FAQ

てっきり、openseaからNFTを発行すると他のマケプレとの互換性がないって印象を持っていたのですが、正確にはメタデータとフロントエンドの関係が合わない場合は正しい情報が表示されないってことでしょうか?

  • はい、そのとおりです!
  • OpenSeaの場合は https://docs.opensea.io/docs/metadata-standards#metadata-structure に表示できるメタデータが記載されています。
  • OpenSeaの場合には正しく表示できていても、他のマケプレではその形式は対応していないから表示に失敗する、みたいなのはあります。
  • 例えば、OpenSeaでは animation_url にHTMLを表示するURLを記載しても正しく表示してますが、他のマケプレだと正しく表示される保証はないです!

tokenURIを提供するサーバが落ちたらNFTアートにアクセスできなくなっちゃうんでしょうか.

  • 直でアクセスした場合はNFTアートにアクセスできなくなります。
  • OpenSeaからアクセスした場合にOpenSea側のサーバーのキャッシュが存在していれば表示できますが、キャッシュが存在しない場合は表示に失敗します。
  • キャッシュの保持期間はマケプレによって実装が異なりますが、大体のマケプレはキャッシュがあると思うので、一時的なサーバーダウンの場合なら特に問題ないと思います。
  • 永久的なサーバーダウンであれば、アクセスできなくなります。

NFTの本質はトークンそのものであって、画像などは外にあるものを読み込んでる、、っていう理解で、正しいんでしょうか。
世間のイメージと実態はだいぶ違うのかもしれません、勉強になりました。

  • はい、ERC-721の場合はそうなります!

PolygonNetでNFTを出品するときのデメリット

  • PolygonチェーンはEthereumのL2ソリューションなのでEthereumがしぬとしにます。
  • ERC-721の基本的な実装の場合は、Polygonチェーンで発行したNFTはEthereumメインネットにブリッジできないです。
  • ただ、実装すればブリッジできるようになります(試してないので違ったらすみません)

ERCからはじまる数字はどのように決まっているんですか?

ERC20や721はトークンに関する規格だと思うのですが、他の番号のものはトークン以外のものもあるのでしょうか?

空き時間で作ったフルオンチェーンNFT

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.4.2/contracts/token/ERC721/ERC721.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.4.2/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.4.2/contracts/utils/Counters.sol";
import "https://github.com/Brechtpd/base64/blob/main/base64.sol";


contract Kisekitaiken is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenCounter;

    struct KisekitaikenToken {
        string text;
    }

    KisekitaikenToken[] public tokens;


    constructor() ERC721("Kisekitaiken", "KSK") {}

    function mint(string memory text) public {
        _tokenCounter.increment();
        uint256 newItemId = _tokenCounter.current();
        _safeMint(msg.sender, newItemId);


        tokens.push(KisekitaikenToken(text));
    }

    function tokenURI(uint256 tokenId)
        public
        view
        override
        returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );
        KisekitaikenToken memory token = tokens[tokenId - 1];
        string memory svg = getSVG(token);
        bytes memory json = abi.encodePacked(
            '{"name": "',
            token.text,
            '", "description": "aaaa", "image": "data:image/svg+xml;base64,',
            Base64.encode(bytes(svg)),
            '"}'
        );
        string memory _tokenURI = string(
            abi.encodePacked(
                "data:application/json;base64,",
                Base64.encode(json)
            )
        );
        return _tokenURI;
    }

    // OpenSea等で表示される実体
    function getSVG(KisekitaikenToken memory token)
        private
        pure
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    '<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet" viewBox="0 0 350 350">',
                    "<style>text{fill:black;font-family:serif;}</style>",
                    '<rect width="100%" height="100%" fill="#a9ceec" />',
                    '<text x="10%" y="45%" font-size="50px">',
                    token.text,
                    "</text>",
                    "</svg>"
                )
            );
    }
}

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
Help us understand the problem. What are the problem?