Metaplex CoreでCollection紐付けのNFTを作ると「SendTransactionError」エラー

Metaplex Create an Asset into a Collection を試していたときに発生したエラー。

現象

サンプル通りに作成して実行すると以下のエラーが発生。

ts-node src/creatingAssetIntoCollection.ts
/Users/metaplex/core/node_modules/@solana/web3.js/src/connection.ts:5923
      throw new SendTransactionError(
            ^
SendTransactionError: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: Program failed to complete
    at Connection.sendEncodedTransaction (/Users/metaplex/core/node_modules/@solana/web3.js/src/connection.ts:5923:13)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Connection.sendRawTransaction (/Users/metaplex/core/node_modules/@solana/web3.js/src/connection.ts:5880:20)
    at async Object.sendTransaction (/Users/metaplex/core/node_modules/@metaplex-foundation/umi-rpc-web3js/src/createWeb3JsRpc.ts:327:25)
    at async TransactionBuilder.sendAndConfirm (/Users/metaplex/core/node_modules/@metaplex-foundation/umi/src/TransactionBuilder.ts:359:23) {
  logs: [
    'Program CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d invoke [1]',
    'Program log: Instruction: Create',
    "Program log: panicked at 'index out of bounds: the len is 0 but the index is 0', programs/mpl-core/src/utils.rs:28:22",
    'Program CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d consumed 5985 of 200000 compute units',
    'Program CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d failed: SBF program panicked'
  ]
}

ソース

// Docs: https://developers.metaplex.com/core/create-asset
// Lib
import * as dotenv from 'dotenv';
import * as bs58 from 'bs58';
import { sleep } from './lib/sleep';

// Metaplex
import { keypairIdentity, generateSigner } from '@metaplex-foundation/umi';
import { createUmi } from '@metaplex-foundation/umi-bundle-defaults';
import {
  mplCore,
  createV1,
  createCollectionV1,
} from '@metaplex-foundation/mpl-core';

// Solana
import { Connection } from '@solana/web3.js';

const creatingAssetIntoCollection = async () => {
  // -------------------------------------
  //  Setup
  // -------------------------------------
  dotenv.config();

  // Set Endpoint
  const endpoint = process.env.ENDPOINT;
  if (!endpoint) throw new Error('endpoint not found.');
  const umi = createUmi(endpoint);

  // Set Payer
  const payerSecretKey = process.env.PAYER_SECRET_KEY;
  if (!payerSecretKey) throw new Error('payerSecretKey not found.');
  const secretKeyUInt8Array = new Uint8Array(JSON.parse(payerSecretKey));
  const payerKeypair =
    umi.eddsa.createKeypairFromSecretKey(secretKeyUInt8Array);
  umi.use(keypairIdentity(payerKeypair));

  // Register Library
  umi.use(mplCore());

  // -------------------------------------
  //  Create a Collection
  // -------------------------------------
  const collection = generateSigner(umi);

  const creatingCollectionResult = await createCollectionV1(umi, {
    collection: collection,
    name: 'Core Collection',
    uri: 'https://example.com/my-nft.json',
  }).sendAndConfirm(umi);

  // -------------------------------------
  //  Create an Asset Into a Collection
  // -------------------------------------
  const asset = generateSigner(umi);

  const creatingAssetResult = await createV1(umi, {
    asset,
    name: 'My Core NFT',
    uri: 'https://arweave.net/IjF_Sd0zcvGwTbkfFjPFoiHlmVPn7duJ1diU92OZHKo',
    collection: collection.publicKey,
  }).sendAndConfirm(umi);

  console.log('payer =>', payerKeypair.publicKey.toString());
  console.log('collection =>', collection.publicKey.toString());
  console.log('asset =>', asset.publicKey.toString());
  console.log(
    'creatingCollectionResult signature =>',
    bs58.encode(creatingCollectionResult.signature)
  );
  console.log(
    'creatingAssetResult signature =>',
    bs58.encode(creatingAssetResult.signature)
  );
};

creatingAssetIntoCollection();

何回か実行すると、たまに成功する。

原因

以下2STEPで処理を進めているが、Collectionが作成されていないのに紐付けてCore NFTを作成してしまっていることが原因。

  1. Core NFT Collectionを作成 ← これがまだ作成完了していない
  2. 上記Collectionに紐付けてCore NFTを作成

対応

Collectionの作成有無をチェックしてから次の処理に進める。

ソース

  // -------------------------------------
  //  Create a Collection
  // -------------------------------------
  const collection = generateSigner(umi);

  const creatingCollectionResult = await createCollectionV1(umi, {
    collection: collection,
    name: 'Core Collection',
    uri: 'https://example.com/my-nft.json',
  }).sendAndConfirm(umi);

  // ーーー以下を追加ーーーーーーーーーーーーーーーー

  // -------------------------------------
  //  Check a Created Collection
  // -------------------------------------
    const sleep = (ms: number): Promise<void> => {
  return new Promise((resolve) => setTimeout(resolve, ms));
};

  const connection = new Connection(endpoint, 'confirmed');

  let status = await connection.getSignatureStatus(
    bs58.encode(creatingCollectionResult.signature),
    {
      searchTransactionHistory: true,
    }
  );

  while (
    status.value?.err === null &&
    status.value?.confirmationStatus === 'confirmed'
  ) {
    console.log('Collection not found. Sleep then check again...');
    await sleep(3000); // 1000 = 3sec

    status = await connection.getSignatureStatus(
      bs58.encode(creatingCollectionResult.signature),
      {
        searchTransactionHistory: true,
      }
    );
  }

  // ーーー追加ここまでーーーーーーーーーーーーーーーー

  // -------------------------------------
  //  Create an Asset Into a Collection
  // -------------------------------------

備考

本件は、NFT作成でよく発生する現象で、NFTを作る場合は、その前段階でいくつか処理が入っていて、順番に1つずつ処理が終わってから次に進める必要がある。間に完了チェックを入れずに、一気に処理を進めてしまうと、ブロックチェーン上の処理が終わっていないのにも関わらず、処理を進めてしまい失敗する、といった現象になる。
基礎的なハマりポイントなのに忘れがちなため、要注意。