lesson-2_NFTをミントするボタンを作成しよう
🎩 mintToken
関数を実装する
[Mint NFT]ボタンが押されたときに実行するミント機能を実装していきましょう。
Solanaではプログラムで状態を保持しません。コントラクトで状態を保持するEthereumとは大きく異なります。詳細は こちら をご覧ください。
Solanaでは、トランザクションの中に命令をひとまとめにしています。Metaplexは、トランザクションを作成するためのメソッドをモジュールとして提供しており、既に複数のメソッドがCandyMachine/index.tsx
にインポートされています。それらの機能を確認しながら、ミントを行うためのトランザクションを作成していきましょう。
前回のレッスンで登場したUmi
ライブラリは、トランザクションの構築を容易にするためのクラスTransactionBuilder
を提供しています。ここでは、このクラスを用いてトランザクションを構築していきたいと思います。
TransactionBuilderクラスにはadd
というメソッドがあり、これにより命令をトランザクションに追加することができます。
const transaction = transactionBuilder()
.add(/* トランザクションに追加したい命令 */)
.add(/* ... */)
.add(/* ... */);
ここで追加する命令は2つあります。1つはミントの実行、もう1つは計算ユニット(Compute unit)の上限設定です。
Solanaは現在、計算ユニットのデフォルト値を200k(200,000)と設定しています(参照)。しかし、NFTのミントのような複雑なトランザクションでは許容されたリソースを超えてしまう可能性があります。試しに計算ユニットの上限設定を行わずにミントを実行すると、下記のようなエラーが発生すると思います。後ほど試してみてください。
[Phantom Wallet の表示]
[Approve 後のコンソール出力]
Error: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Computational budget exceeded
それでは、計算ユニットの上限設定を行いましょう。Metaplexの公式ドキュメントでミント機能の実装を確認すると、setComputeUnitLimit
というメソッドが使用されています。こちらはmpl-essentials
ライブラリが提供するメソッドです。
今回は600_000
と設定したいと思います。
const transaction = transactionBuilder().add(
setComputeUnitLimit(umi, { units: 600_000 })
);
続いて、ミントの実行を追加しましょう。再度Metaplexの公式ドキュメントを確認してみると、mintV2
というメソッドが使用されています。mpl-candy-machine
ライブラリが提供するメソッドで、Candy Machineのデータを用いてミントを行います。
mintV2メソッドの引数を確認すると、MintV2InstructionAccounts
というオブジェクトを受け取ることがわかります。オブジェクトの中にはたくさんのプロパティが定義されていますが、ここでは必須のものとCandy Guardの設定を指定したいと思います。
const transaction = transactionBuilder()
.add(setComputeUnitLimit(umi, { units: 600_000 }))
.add(
mintV2(umi, {
candyGuard: candyGuard.publicKey,
candyMachine: candyMachine.publicKey,
collectionMint: candyMachine.collectionMint,
collectionUpdateAuthority: candyMachine.authority,
mintArgs: {
solPayment: some({ destination: destination }),
},
nftMint: nftSigner,
})
);
solPayment
は、Candy GuardにNFTの価格とその受 け取るアドレスを設定した場合に必須の設定となります。
これで、トランザクションをどのように構築するか確認ができました。次に、トランザクションの送信方法を確認してみましょう。
再度TransactionBuilder
クラスのメソッドを見てみます。すると、sendAndConfirm
というメソッドが提供されていることが確認できます。このメソッドを使ってトランザクションの送信と結果の確認を行うのが良さそうです。
await transaction.sendAndConfirm(umi).then((response) => {
const transactionResult = response.result.value;
if (transactionResult.err) {
throw new Error(`Failed mint: ${transactionResult.err}`);
}
});
戻り値の定義を見てみると、Promiseで2つの値が返ってくることがわかります。そのうちresult
の方を確認してみると、トランザクションで何かしらエラーが発生した場合はstring
型のデータが返ってくるようです。そのため、sendAndConfirmメソッドの戻り 値を取得してエラーチェックを行うようにします。
それでは、ここまでの情報をもとに実際にmintToken
関数を実装していきましょう。下記のコードをgetCandyMachineState
関数の下に追加すると良いでしょう。
// CandyMachine/index.tsx
const mintToken = async (
candyMachine: CandyMachineType,
candyGuard: CandyGuardType
) => {
try {
if (umi === undefined) {
throw new Error("Umi context was not initialized.");
}
if (candyGuard.guards.solPayment.__option === "None") {
throw new Error("Destination of solPayment is not set.");
}
const nftSigner = generateSigner(umi);
const destination = candyGuard.guards.solPayment.value.destination;
// トランザクションの構築を行います。
const transaction = transactionBuilder()
.add(setComputeUnitLimit(umi, { units: 600_000 }))
.add(
mintV2(umi, {
candyGuard: candyGuard.publicKey,
candyMachine: candyMachine.publicKey,
collectionMint: candyMachine.collectionMint,
collectionUpdateAuthority: candyMachine.authority,
mintArgs: {
solPayment: some({ destination: destination }),
},
nftMint: nftSigner,
})
);
// トランザクションを送信して、ネットワークによる確認を待ちます。
await transaction.sendAndConfirm(umi).then((response) => {
const transactionResult = response.result.value;
if (transactionResult.err) {
console.error(`Failed mint: ${transactionResult.err}`);
}
});
} catch (error) {
console.error(error);
}
};
try
文の最初に、ステート変数に値が保存されていることを確認します。値が保存されていない場合は、ミントが実行できないのでエラーとして処理をします。
ここで、mintToken関数を少し更新して関数の実行が終わるまで[Mint NFT]ボタンを押せないようにしたいと思います。button
タグにdisable
属性を指定することで解決しましょう。関数の実行中はtrue
、そうでない場合はfalse
として実行中かどうかをステートで管理し、その値をdisable属性に渡しましょう。
まずは、Booleanを保持するステートを定義します。これまでに定義してきたステートの下に追加しましょう。
// CandyMachine/index.tsx
const CandyMachine = (props: CandyMachineProps) => {
// mintToken関数が実行中かどうかを管理するステートを追加する。
const [isMinting, setIsMinting] = useState(false);
次に、mintToken関数の最初と最後にsetIsMinting
を呼んで実行中かどうかを設定します。try-catch文の最後にfinally
を追加しましょう。正常 に処理が進んだ場合とエラーが発生してcatch文に進んでしまった場合、どちらの場合でもsetIsMinting(false)
が実行されるようにします。
const mintToken = async (
candyMachine: CandyMachineType,
candyGuard: CandyGuardType
) => {
// 関数実行中なので`true`を設定します。
setIsMinting(true);
try {
} catch (error) {
console.error(error);
} finally {
// 関数が終了するので`false`を設定します。
setIsMinting(false);
}
};
✨ NFT をミントしよう!
CandyMachine
コンポーネントで、[Mint NFT]ボタンをクリックしたときにmintToken
関数を呼び出すよう設定します。return文を下記の通り修正してください。
// CandyMachine/index.tsx
return candyMachine && candyGuard ? (
<div className={candyMachineStyles.machineContainer}>
<p>{`Drop Date: ${startDateString}`}</p>
<p>
{`Items Minted: ${candyMachine.itemsRedeemed} / ${candyMachine.data.itemsAvailable}`}
</p>
<button
className={`${styles.ctaButton} ${styles.mintButton}`}
onClick={() => mintToken(candyMachine, candyGuard)}
disabled={isMinting}
>
Mint NFT
</button>
</div>
) : null;
Mint NFT
をクリックする前に、PhantomWalletにDevnetSOLがあることを確認する必要があります。これはとても簡単です。
まず、Phantom Walletのパブリックアドレスを取得します。
次に、Devnetでトークンを得るためにターミナルで次のコマンドを実行します。
solana airdrop 2 INSERT_YOUR_PHANTOM_WALLET_ADDRESS
トークンが取得できたら、ブラウザを操作してみましょう。Mint NFT
をクリックすると、次のようなポップアップが表示されます。
[承認]をクリックして取引手数料を支払うと、Candy MachineにNFTを作成するように指示されます。
NFTが正常にミントされると、アプリケーションに表示されているItems Minted
の数が更新されます。また、この値はコンソールのログ出力からも確認できるでしょう。
Phantom Walletを開き、所有NFT一覧に表示されるかどうかを確認します。
Phantom Walletの左から2つ目のタブに切り替えてみましょう 👀
🙋♂️ 質問する
ここまでの作業で何かわからないことがある場合は、Discordの#solana
で質問をしてください。
ヘルプをするときのフローが円滑になるので、エラーレポートには下記の3点を記載してください ✨
1. 質問が関連しているセクション番号とレッスン番号
2. 何をしようとしていたか
3. エラー文をコピー&ペースト
4. エラー画面のスクリーンショット
おめでとうございます! セクション3は終了です!
ぜひ、あなたのNFT画像をコミュニティに投稿してください!
あなたのNFTが気になります ✨
次のレッスンに進んで、ほかの機能をWebアプリケーションに実装していきましょう 🎉