まいにちDappsも目標としていた20回まで残りわずかとなりました。
今回はこのブログ記事をNFTにしてmintできるようにしたいと思います。
先に開発物のアウトプットをお見せします。
https://pontech-blog-git-development-pontech.vercel.app/post/dapps-everyday-5
記事下部にConnectボタンがあり、mintできるようになっています。
このHP・ブログの技術構成
少し脱線しますが、株式会社PontechのHPであるこのサイトは、Sanity.io + Next.jsで作られています。
特にStablo Minimal Blog Templateというテンプレートを使っています。
フロントエンドは自分でカスタマイズすることができ、ブログ部分のCMSはSanity.ioである程度柔軟にできているので気に入っています。
今回はこのNext.jsのコードをいじっていきます。
ERC1155のSignature mint
Thirdwebを使って各記事ごとにERC1155トークンをmintできるようにします。
以下の記事がベースとなります(テーマとはズレますがERC1155のSignature mintingの点だけ参考にします)。
Sell Your NFT in Multiple Currencies at the Same Time
まずEditionコントラクトを用意しておきます。
次にAPI層を作り、フロントエンドから触れるように開通します。
app/api/generate-mint-signature/route.ts
import { NextResponse } from "next/server";
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
type APIRequest = {
address: string;
slug: string;
};
export async function POST(request: Request) {
const data: APIRequest = await request.json();
const { address, slug } = data;
const json = {
signedPayload: "mintSignature"
};
return NextResponse.json(json);
}
mint.jsというコンポーネントを作ります。ボタンを押してPOSTできていることを確認します。
"use client";
export default function Mint(props) {
const mint = async () => {
const signedPayloadReq = await fetch(
`/api/generate-mint-signature`,
{
method: "POST",
body: JSON.stringify({ address: "address", slug: props.slug.current })
}
).catch(error => {
console.log(error);
setIsMinting(false);
alert(error);
});
const signedPayload = (await signedPayloadReq.json())
.signedPayload;
};
return (
<div className="mx-auto max-w-screen-md ">
<div className="flex justify-center">
<button
onClick={mint}
type="button"
className="mb-2 mr-2 rounded-lg bg-gradient-to-br from-purple-600 to-blue-500 px-5 py-2.5 text-center text-sm font-medium text-white hover:bg-gradient-to-bl focus:outline-none focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800">
Mint This NFT
</button>
</div>
</div>
);
}
Thirdwebのreact SDKをインストールしてConnect Walletを実装し、Mint.jsをPostのページで読み込みます。読み込む際にThirdwebProviderで囲ってあげる必要があります。
"use client";
import {
ConnectWallet,
useAddress,
useContract
} from "@thirdweb-dev/react";
import { useState } from "react";
export default function Mint(props) {
const address = useAddress();
const mint = async () => {
if (!address) return;
setIsMinting(true);
const signedPayloadReq = await fetch(
`/api/generate-mint-signature`,
{
method: "POST",
body: JSON.stringify({ address, slug: props.slug.current })
}
).catch(error => {
console.log(error);
setIsMinting(false);
alert(error);
});
const signedPayload = (await signedPayloadReq.json())
.signedPayload;
console.log(signedPayload);
};
return (
<div className="mx-auto max-w-screen-md ">
<div className="flex justify-center">
{address ? (
<button
onClick={mint}
type="button"
className="mb-2 mr-2 rounded-lg bg-gradient-to-br from-purple-600 to-blue-500 px-5 py-2.5 text-center text-sm font-medium text-white hover:bg-gradient-to-bl focus:outline-none focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800">
Mint This NFT
</button>
) : (
<ConnectWallet></ConnectWallet>
)}
</div>
</div>
);
}
post/[slug]/default.js
<Container>
<ThirdwebProvider>
<Mint slug={post.slug}></Mint>
</ThirdwebProvider>
</Container>
apiでsignatureを生成します。
import { NextResponse } from "next/server";
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
type APIRequest = {
address: string;
slug: string;
};
export async function POST(request: Request) {
const data: APIRequest = await request.json();
const { address, slug } = data;
const sdk = ThirdwebSDK.fromPrivateKey(
process.env.ADMIN_PRIVATE_KEY!,
"mumbai",
{
secretKey: process.env.TW_SECRET_KEY
}
);
const edition = sdk.getContract(
"0xD2BA08159c22818EcFdbD701036b6de2dD55cFF4",
"edition"
);
const metadata = {
name: `Dapps Everyday NFT #${slug}`,
description:
"このNFTをmintしたことで、まいにちDappsのファンであることを証明します。",
image:
"https://15065ae3c21e0bff07eaf80b713a6ef0.ipfscdn.io/ipfs/bafybeihhcykfpr2nt6kt5jshcn3kskfsf2a3cl2qkbqzxsv543qpn7bi4e/IMG_8953.jpg",
attributes: {
slug
},
animation_url: `https://pontech.dev/post/${slug}`
};
const numberPart = slug.match(/\d+$/);
if (!numberPart) {
throw Error("この記事はmint対象ではありません。");
}
const number = parseInt(numberPart[0], 10);
const nextTokenIdToMint = await (
await edition
).erc1155.nextTokenIdToMint();
let mintSignature;
if (number == nextTokenIdToMint.toNumber()) {
console.log("generate");
mintSignature = await (
await edition
).erc1155.signature.generate({
quantity: 1,
to: address,
price: "0",
mintStartTime: new Date(0),
metadata
});
} else if (number < nextTokenIdToMint.toNumber()) {
console.log("generateFromTokenId");
mintSignature = await (
await edition
).erc1155.signature.generateFromTokenId({
tokenId: number,
quantity: 1,
to: address,
price: "0",
mintStartTime: new Date(0),
metadata
});
} else {
throw Error("この記事はmint対象ではありません。");
}
const json = {
signedPayload: mintSignature
};
return NextResponse.json(json);
}
ERC1155のsignatureの生成には2種類あり、新しくトークンを生成する場合は通常のerc1155.signature.generate()を、既存のトークンをmintしたい場合にはerc1155.signature.generateFromTokenId()を実行する必要があります。上記のコードはそこを使い分けています。
またThirdwebのSDKのinit時にAPIのsecretKeyを渡しているのは、generateするときにmetadataをThirdweb経由でIPFSにアップロードしてもらっているので、APIKeyが必要になります。
フロントエンドでmintします。
"use client";
import {
ConnectWallet,
useAddress,
useContract
} from "@thirdweb-dev/react";
import { useState } from "react";
export default function Mint(props) {
const address = useAddress();
const [isMinting, setIsMinting] = useState(false);
const { contract: edition } = useContract(
"0xD2BA08159c22818EcFdbD701036b6de2dD55cFF4",
"edition"
);
const mint = async () => {
if (!address) return;
setIsMinting(true);
const signedPayloadReq = await fetch(
`/api/generate-mint-signature`,
{
method: "POST",
body: JSON.stringify({ address, slug: props.slug.current })
}
).catch(error => {
console.log(error);
setIsMinting(false);
alert(error);
});
const signedPayload = (await signedPayloadReq.json())
.signedPayload;
console.log(signedPayload);
try {
const nft = await edition?.erc1155.signature.mint(
signedPayload
);
setIsMinting(false);
alert(
`mint成功 https://testnets.opensea.io/ja/assets/mumbai/0xd2ba08159c22818ecfdbd701036b6de2dd55cff4/${nft.id}`
);
return nft;
} catch (e) {
console.error(e);
setIsMinting(false);
return null;
}
};
return (
<div className="mx-auto max-w-screen-md ">
<div className="flex justify-center">
{address ? (
<>
{isMinting ? (
<button
disabled
type="button"
className="mr-2 inline-flex items-center rounded-lg bg-gradient-to-br from-purple-600 to-blue-500 px-5 py-2.5 text-center text-sm font-medium text-white hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
<svg
aria-hidden="true"
role="status"
class="mr-3 inline h-4 w-4 animate-spin text-white"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg">
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="#E5E7EB"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentColor"
/>
</svg>
Loading...
</button>
) : (
<button
onClick={mint}
type="button"
className="mb-2 mr-2 rounded-lg bg-gradient-to-br from-purple-600 to-blue-500 px-5 py-2.5 text-center text-sm font-medium text-white hover:bg-gradient-to-bl focus:outline-none focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800">
Mint This NFT
</button>
)}
</>
) : (
<ConnectWallet></ConnectWallet>
)}
</div>
</div>
);
}
これで完成です!
まとめ
今回はまだ本番のサイトには導入していませんが、機能面、デザイン面、そしてトークンの意義を調整して、各記事ごとにNFTをmintしてもらえる機能をつけようと考えています。トークン×企業HPの新しい導入例を作れたらと思います!
弊社Pontechはweb3に関わる開発を得意とするテック企業です。サービス開発に関するご相談はこちらのフォームからお願いいたします。
また、受託開発案件に共に取り組むメンバーを募集しています!ご興味のある方はぜひお話させてください!