Sausage Erectos X Polygon NFTスマートコントラクト——ERC-721とメタデータ設計

Polygon NFTスマートコントラクト──ERC-721の設計と実装

Sausage ErectoのカスタムフィギュアをNFTとして発行する仕組みを、Polygon(旧Matic)上に構築した。設計判断、実装、デプロイ、そしてテストの全過程を記録する。

ERC-721 vs ERC-1155の設計判断

NFTの規格には主にERC-721とERC-1155がある。

ERC-721:  各トークンが一意。1つのコントラクトで1種類のNFT。
ERC-1155: 複数種類のトークンを1つのコントラクトで管理。
          同一デザインの複数発行にも対応。

Sausage Erectosでは、各フィギュアが完全にユニークなカスタムデザインであるため、ERC-721を選択した。ERC-1155の「同一トークンの複数発行」機能は不要であり、ERC-721のほうがマーケットプレイス(OpenSea等)との互換性が高い。

OpenZeppelinのコントラクト継承構造

セキュリティを最優先に、OpenZeppelinのライブラリを利用した。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract SausageErectos is ERC721, ERC721URIStorage, Ownable {
    uint256 private _nextTokenId;

    constructor() ERC721("SausageErectos", "SEREC") Ownable(msg.sender) {}

    function mint(address to, string memory uri) public onlyOwner {
        uint256 tokenId = _nextTokenId++;
        _safeMint(to, tokenId);
        _setTokenURI(tokenId, uri);
    }

    // Override required by Solidity
    function tokenURI(uint256 tokenId)
        public view override(ERC721, ERC721URIStorage) returns (string memory) {
        return super.tokenURI(tokenId);
    }

    function supportsInterface(bytes4 interfaceId)
        public view override(ERC721, ERC721URIStorage) returns (bool) {
        return super.supportsInterface(interfaceId);
    }
}

Ownableにより、mintは管理者(コントラクトオーナー)のみが実行可能。ユーザーが注文し決済が完了した後、サーバーサイドからmintを実行する設計だ。

Hardhatでのデプロイスクリプト

// scripts/deploy.js
const { ethers } = require("hardhat");

async function main() {
  const SausageErectos = await ethers.getContractFactory("SausageErectos");
  const contract = await SausageErectos.deploy();
  await contract.waitForDeployment();
  console.log("Deployed to:", await contract.getAddress());
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Amoyテストネットでのテスト

Polygon Amoyテストネットでのテスト方法:

  1. hardhat.config.jsにAmoyネットワークを追加
  2. Alchemy/InfuraでAmoy用のRPC URLを取得
  3. テスト用POLをfaucetから取得
  4. npx hardhat run scripts/deploy.js --network amoyでデプロイ
  5. Polygonscan(Amoy)でコントラクトを検証
// hardhat.config.js
module.exports = {
  networks: {
    amoy: {
      url: process.env.AMOY_RPC_URL,
      accounts: [process.env.PRIVATE_KEY],
    },
  },
};

メタデータのIPFS保存

NFTのメタデータはブロックチェーン上ではなく、IPFSに保存する。Pinataを利用してピン留め(永続化)する。

const pinataSDK = require('@pinata/sdk');
const pinata = new pinataSDK(process.env.PINATA_KEY, process.env.PINATA_SECRET);

async function uploadMetadata(figureData) {
  // 画像をIPFSにアップロード
  const imgResult = await pinata.pinFileToIPFS(figureData.image);

  // メタデータJSON
  const metadata = {
    name: `Sausage Erectos #${figureData.id}`,
    description: figureData.description,
    image: `ipfs://${imgResult.IpfsHash}`,
    attributes: [
      { trait_type: "Height", value: figureData.height },
      { trait_type: "Pose", value: figureData.poseName },
      { trait_type: "Color", value: figureData.color },
    ],
  };

  const metaResult = await pinata.pinJSONToIPFS(metadata);
  return `ipfs://${metaResult.IpfsHash}`;
}

ガス代の実測値

Polygon上でのガス代は非常に安価だ。実際の測定結果:

コントラクトデプロイ:  約0.01 POL(約¥1)
1回のmint:           約0.002 POL(約¥0.2)
メタデータ更新:       約0.001 POL(約¥0.1)

Ethereumメインネットでは同じ操作に数千円かかることを考えると、Polygonの低コストは個人プロジェクトにとって決定的な利点だ。

NFTの技術的価値は、トークンそのものではなく、「デジタルな所有権」という概念を実装可能にしたことにある。Sausage Erectosの各フィギュアが、ブロックチェーン上で唯一無二であると証明されること。それは、創作物への敬意の技術的表現だ。

← ブログ一覧に戻る