🛠

Metaplexを使ってSolana上にNFTをミントするAPIを作ろう!

2024/10/08に公開

はじめに

皆さん、こんにちは!

9月中旬〜10月初旬まで開催されていた Solana の大型ハッカソン Radar Hackathon に参加しました。

その時にNFT特化のライブラリなどを提供している Metaplex を使ってみたところ使い勝手が良かったので記事にしてみました!

https://www.colosseum.org/radar

僕は、 Yukiさんにお声がけいただいて AW系のゲームである Q-drop Adventure というプロダクトの開発をご一緒させていただきました!!

Live demoは以下で公開されています!!
ぜひ触ってみてください!!

https://qdropadventure.vercel.app/

Metaplexとは

Metaplex はSolana上でNFT開発ツールやライブラリを提供しています!

今年の3月に 新しいNFT標準となる「Core」というものを発表しています。

https://coinpost.jp/?p=519178

以下のブログではその Core NFTを発行する手順が丁寧に解説されているのですが今回はこの内容を参考にNext.jsに組み込んでみました!!

https://note.com/standenglish/n/n8f942d09ec8d

自分でも試してみたリポジトリは以下の通りです。

https://github.com/mashharuki/metaplex_core

こっちはミニマムのコードで Metaplex Core NFTの実装を試せるのでおすすめです!!

今回の実装

今回実装したソースコードは以下のGitHubリポジトリに含まれています。

https://github.com/ytakahashi2020/airdrop_quest/tree/main

ゲームをクリアした人には NFTを2種類ミントすることを考えているのですがそのための方法として Metaplex を使いました!!

例えば、 WinnerNFTを発行する時のコードは以下のように実装しています!

import { create, mplCore } from "@metaplex-foundation/mpl-core";
import {
  createGenericFile,
  generateSigner,
  keypairIdentity
} from "@metaplex-foundation/umi";
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
import { irysUploader } from "@metaplex-foundation/umi-uploader-irys";
import { base58 } from "@metaplex-foundation/umi/serializers";
import fs from "fs";
import { NextResponse } from "next/server";
import path from "path";

/**
 * NFT作成用のAPI
 * @returns
 */
export async function POST() {
  try {
    // UMI インスタンスを作成する。
    const umi = createUmi("https://api.devnet.solana.com")
    .use(mplCore())
    .use(
      irysUploader({
        address: "https://devnet.irys.xyz",
      })
    );

    // バックエンド用の鍵ペアを読み込む
    const keyData = fs.readFileSync(path.join(process.cwd(), "src", "data", "id.json"));
    const walletFile = JSON.parse(keyData.toString());
    // キーペアを作成
    let keypair = umi.eddsa.createKeypairFromSecretKey(
      new Uint8Array(walletFile)
    );
    // バックエンド用の鍵ペアとUMIを紐付ける。
    umi.use(keypairIdentity(keypair));

    // Upload image data
    const imageFile = fs.readFileSync(path.join(process.cwd(), 'public/images/nft/winnerNft.png'));
    const umiImageFile = createGenericFile(imageFile, "image.png", {
      tags: [{ name: "Content-Type", value: "image/png" }],
    });
    console.log("Uploading Image...");
    // upload
    const imageUri = await umi.uploader.upload([umiImageFile]).catch((err) => {
      throw new Error(err);
    });
    const irysImageUri = imageUri[0].replace("arweave.net", "gateway.irys.xyz");
    console.log("imageUri: " + irysImageUri);

    const metadata = {
      name: "Q Drop Adventure Winner NFT",
      description: "This is a Q Drop Adventure Winner NFT",
      image: irysImageUri,
      external_url: "https://x.com/a_kingdom_radar",
      attributes: [
        {
          trait_type: "rarity",
          value: "max",
        },
      ],
      properties: {
        files: [
          {
            uri: imageUri[0],
            type: "image/jpeg",
          },
        ],
        category: "image",
      },
    };
  
    // Call upon umi's `uploadJson` function to upload our metadata to Arweave via Irys.
  
    console.log("Uploading Metadata...");
    const metadataUri = await umi.uploader.uploadJson(metadata).catch((err) => {
      throw new Error(err);
    });

    const irysMetadataUri = metadataUri.replace(
      "arweave.net",
      "gateway.irys.xyz"
    );
  
    console.log("metadataUri: " + irysMetadataUri);

    const asset = generateSigner(umi);

  console.log("Creating NFT...");
  // NFTを作成するトランザクションを署名して送信
  const tx = await create(umi, {
    asset,
    name: "My Super NFT",
    uri: irysMetadataUri,
  }).sendAndConfirm(umi);

  // Finally we can deserialize the signature that we can check on chain.
  const signature = base58.deserialize(tx.signature)[0];

  // Log out the signature and the links to the transaction and the NFT.
  console.log("\nNFT Created");
  console.log("View Transaction on Solana Explorer");
  console.log(`https://explorer.solana.com/tx/${signature}?cluster=devnet`);
  console.log("\n");
  console.log("View NFT on Metaplex Explorer");
  console.log(
    `https://core.metaplex.com/explorer/${asset.publicKey}?env=devnet`
  );

  return NextResponse.json({
    txHash: `https://explorer.solana.com/tx/${signature}?cluster=devnet`,
    metaplexUrl: `https://core.metaplex.com/explorer/${asset.publicKey}?env=devnet`,
  })

  } catch (error) {
    console.error("error:", error);
    throw error;
  }
}

発行の際ウォレットが必要になるのですが、今回はウォレットを持っていない人でも発行できるようにバックエンドウォレットを用意してそこから発行するようにしています!

上から順に

  • UMIインスタンスの設定
  • 画像データのホスティング
  • NFTメタデータの設定
  • メタデータのホスティング
  • NFTの発行

という実装になっています!!

Areweave 上に画像データをホスティングしているらしいのですが、その時に必要な資産も SOL でOKです!!

ここがMetaplexの使いやすい点だなと思いました!!

動かしてみた!

ではこのコードを動かしてみます!!

まずフロントエンドを起動します!!

yarn dev

その後、以下のコマンドを打って APIを呼び出してみます!!

curl -XPOST "http://localhost:3000/api/nft/mint/winnerNft"

しばらく待つと・・・

ターミナル上にはログが出力されます!!

bigint: Failed to load bindings, pure JS will be used (try npm run rebuild?)
Uploading Image...
imageUri: https://gateway.irys.xyz/5TLfaC5dSK6JYHtEcrPXVXVjqYq91YvE68UqzqVWYQ1C
Uploading Metadata...
metadataUri: https://gateway.irys.xyz/CidsQDowbZKjUarbqyXi8PV1GNMaRWAesVy9bPM5Kdmn
Creating NFT...

NFT Created
View Transaction on Solana Explorer
https://explorer.solana.com/tx/4PLrhxdVdaD1xkUzcUbUGgW4vvE3FAjFZ1MdjRDFGkXS2DLZ5K7vFm1Cqk1Vcua4fvdM12C7u324yWofDcd7D87o?cluster=devnet


View NFT on Metaplex Explorer
https://core.metaplex.com/explorer/Bor7wL2cKrJzQkPXSMzpZ1pTVfrew2LH2XF9bEb8YPdX?env=devnet
 POST /api/nft/mint/winnerNft 200 in 45673ms

結果としてブロックエクスプローラーとNFTまでのURLが出力されます!!

{
  "txHash":"https://explorer.solana.com/tx/4PLrhxdVdaD1xkUzcUbUGgW4vvE3FAjFZ1MdjRDFGkXS2DLZ5K7vFm1Cqk1Vcua4fvdM12C7u324yWofDcd7D87o?cluster=devnet",
  "metaplexUrl":"https://core.metaplex.com/explorer/Bor7wL2cKrJzQkPXSMzpZ1pTVfrew2LH2XF9bEb8YPdX?env=devnet"
}

無事にNFTが発行されたようです!!

念の為両方にアクセスして確認してみます!!

https://explorer.solana.com/tx/4PLrhxdVdaD1xkUzcUbUGgW4vvE3FAjFZ1MdjRDFGkXS2DLZ5K7vFm1Cqk1Vcua4fvdM12C7u324yWofDcd7D87o?cluster=devnet

https://core.metaplex.com/explorer/Bor7wL2cKrJzQkPXSMzpZ1pTVfrew2LH2XF9bEb8YPdX?env=devnet

ちゃんと発行できていました!!

まとめ

いかがでしたでしょうか??

普段は Solana 系のプロトコルやSDKには触れないのですが、今回はハッカソンということでちょっと触れてみました!

画像のホスティングからNFT発行まで一気通貫でできるので Metaplex は非常に使い勝手が良いと思いました!!!

皆さんも触ってみてください!!

また、最後になりますが僕たちのチームのプロダクトである Q Drop Adventure を応援していただけると大変嬉しいです!!

プロダクトページは以下です!

https://arena.colosseum.org/refresh-session?redirectBack=/projects/hackathon/q-drop-adventure

プロダクトのピッチ資料やビデオは以下で確認ができます!!
良かったら見てみてください!!

https://www.loom.com/share/b1b8d8710510400cacf7ecfeca59c4f1

https://www.canva.com/design/DAGSeD3VV-8/7NY0PWR8QbAc5Cje4XG94g/watch?embed

今回は以上となります!!

ここまで読んでいただきありがとうございました!!

参考文献

  1. GitHub - Q-drop Adventure
  2. Metaplexの公式サイト
  3. Radar Hackathon 公式サイト
  4. 【完全保存版】Irysを使ってMetaplexのCore NFTを作成しよう!
GitHubで編集を提案

Discussion