👋

デジタル文化財管理システム(試行版)のNFT対応

に公開

お知らせ: 2025-06-14

開発の経過は以下にまとめています。

https://zenn.dev/nakamura196/books/41693d2d017082

概要

以下の記事をはじめとして、ブロックチェーンを用いたデジタル文化財管理システムの試作をしています。

https://zenn.dev/nakamura196/articles/e0d995eca8dec0

今回、アップロードしたデータがNFTとして認識されるように改修しました。

勉強過程のため、不完全な点があるかと思いますが、参考になりましたら幸いです。

使い方ページ

ファイルのアップロード方法はこれまでと同様です。アップロード後に表示される一覧ページにおいて、詳細ページへのリンクを追加しました。

リンクをクリックすると、以下のような詳細画面に遷移します。

実装方法

※ この章は、AIが執筆しました。

1. コントラクトのNFT対応

既存のデジタル文化財管理コントラクトを、ERC721規格に準拠したNFTコントラクトに改修しました。

主な変更点:

1. OpenZeppelinライブラリの追加

import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";

2. コントラクトの継承構造を変更

contract DigitalHeritage is 
    Initializable, 
    OwnableUpgradeable, 
    UUPSUpgradeable,
    ERC721Upgradeable,
    ERC721URIStorageUpgradeable 
{
    // ...
}

3. 初期化関数の更新

function initialize() public initializer {
    __Ownable_init(msg.sender);
    __UUPSUpgradeable_init();
    __ERC721_init("Digital Heritage", "DH");
    __ERC721URIStorage_init();
}

4. 文化財登録時のNFTミント機能

function registerHeritage(
    string memory _name,
    string memory _description,
    string memory _imageUrl,
    string memory _tokenURI
) public {
    uint256 id = heritages.length;
    
    // 文化財データを保存
    heritages.push(Heritage({
        id: id,
        name: _name,
        description: _description,
        imageUrl: _imageUrl,
        owner: msg.sender,
        timestamp: block.timestamp,
        isDeleted: false
    }));

    // NFTとしてミント
    _safeMint(msg.sender, id);
    _setTokenURI(id, _tokenURI);
    
    emit HeritageRegistered(id, msg.sender, _name);
}

2. メタデータ管理システムの実装

NFTの標準的なメタデータ形式に対応するため、サーバーサイドでのメタデータ生成・アップロード機能を実装しました。

1. API Routeでのメタデータ生成

// src/app/api/upload/route.js
const uploadMetadataToPinata = async (name, description, imageUrl) => {
  const metadata = {
    name: name,
    description: description,
    image: imageUrl,
    attributes: [
      {
        trait_type: "登録日時",
        value: new Date().toISOString()
      },
      {
        trait_type: "カテゴリ",
        value: "デジタル文化財"
      }
    ]
  };
  
  // JSONをBlobに変換してPinataにアップロード
  const jsonBlob = new Blob([JSON.stringify(metadata, null, 2)], {
    type: 'application/json'
  });
  
  // Pinata APIを使用してIPFSにアップロード
  // ...
};

2. フロントエンドでの統合処理

// 画像アップロード → メタデータ生成 → コントラクト登録の流れ
const handleSubmit = async (e) => {
  // 1. 画像をIPFSにアップロード
  const imageUrl = formData.imageUrl;
  
  // 2. メタデータをサーバーサイドで生成・アップロード
  const metadataFormData = new FormData();
  metadataFormData.append('type', 'metadata');
  metadataFormData.append('name', formData.name);
  metadataFormData.append('description', formData.description);
  metadataFormData.append('imageUrl', imageUrl);

  const metadataResponse = await fetch('/api/upload', {
    method: 'POST',
    body: metadataFormData
  });
  
  const metadataResult = await metadataResponse.json();
  
  // 3. コントラクトに登録(トークンURIを含む)
  await registerHeritage(
    formData.name, 
    formData.description, 
    imageUrl, 
    metadataResult.url
  );
};

まとめ

Web3およびNFTの学習過程において、参考になりましたら幸いです。

Discussion