@metaplex-foundation/jsでMetadataをアップロードすると「bundler: 400 Invalid tx」エラー

@metaplex-foundation/js - uploadMetadata を実装していたときのメモ。

現象

チュートリアルを参考に以下を実装。

uploadMetadata.ts

// Ref: https://github.com/metaplex-foundation/js#uploadmetadata
import { Metaplex, keypairIdentity, bundlrStorage } from "@metaplex-foundation/js";
import { Connection, clusterApiUrl, Keypair, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";

const main = async() => {
  const connection = new Connection(clusterApiUrl("devnet"));
  const wallet = Keypair.generate();

  // airdrop
  let airdropSignature = await connection.requestAirdrop(
      wallet.publicKey,
      LAMPORTS_PER_SOL,
  );

  const latestBlockHash = await connection.getLatestBlockhash();

  await connection.confirmTransaction({
    blockhash: latestBlockHash.blockhash,
    lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
    signature: airdropSignature,
  });

  const walletBalance = await connection.getBalance(wallet.publicKey);
  console.log('walletBalance =>', walletBalance);
  // End airdrop


  const metaplex = Metaplex.make(connection)
      .use(keypairIdentity(wallet))
      .use(bundlrStorage());

  const { uri } = await metaplex.nfts().uploadMetadata({
      name: "My NFT",
      description: "My description",
      image: "https://arweave.net/ovSe7hNSNMYGQU55V8pOfIsIlZ4KIQNip6g25AnMu9s",
  });

    console.log(uri);
};

main();

実行するとbundler(Bundlr Network)のtxが400とのこと。

% ts-node uploadMetadata.ts
Error: HTTP Error: Posting transaction 3EgQcVLCfeBrm2qoA7DfA14iEy8PmqP8T8YReTxWPnjofJsbxgXm8YNGrHT3fU2UiqPVUL1CDiTeypdLJvXVEzSB information to the bundler: 400 Invalid tx
    at Function.checkAndThrow (/Users/user/Documents/Programming/Blockchain/solana-anchor-react-minimal-example/scripts/metaplex/latest/node_modules/@bundlr-network/client/src/common/utils.ts:27:19)
    at Fund.fund (/Users/user/Documents/Programming/Blockchain/solana-anchor-react-minimal-example/scripts/metaplex/latest/node_modules/@bundlr-network/client/src/common/fund.ts:48:15)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async BundlrStorageDriver.fund (/Users/user/Documents/Programming/Blockchain/solana-anchor-react-minimal-example/scripts/metaplex/latest/node_modules/@metaplex-foundation/js/src/plugins/bundlrStorage/BundlrStorageDriver.ts:152:5)
    at async BundlrStorageDriver.uploadAll (/Users/user/Documents/Programming/Blockchain/solana-anchor-react-minimal-example/scripts/metaplex/latest/node_modules/@metaplex-foundation/js/src/plugins/bundlrStorage/BundlrStorageDriver.ts:110:5)
    at async Object.handle (/Users/user/Documents/Programming/Blockchain/solana-anchor-react-minimal-example/scripts/metaplex/latest/node_modules/@metaplex-foundation/js/src/plugins/nftModule/uploadMetadata.ts:33:25)
    at async /Users/user/Documents/Programming/Blockchain/solana-anchor-react-minimal-example/scripts/metaplex/latest/node_modules/@metaplex-foundation/js/src/utils/Task.ts:82:23
    at async Disposable.run (/Users/user/Documents/Programming/Blockchain/solana-anchor-react-minimal-example/scripts/metaplex/latest/node_modules/@metaplex-foundation/js/src/utils/Disposable.ts:34:14)

原因

@metaplex-foundation/js - bundlrStorage に記載されているが、デフォルトはBundlr NetworkのMainnetが設定されている。
Devnetの設定のまま、Bundler NetworkはMainnet指定だったため、整合性があわなくなっていた。

By default, it will use the same RPC endpoint used by the Metaplex instance as a providerUrl and the mainnet address "https://node1.bundlr.network" as the Bundlr address.

Devnetで実装する場合は以下の設定が必要。

import { bundlrStorage } from "@metaplex-foundation/js";

metaplex.use(bundlrStorage({
    address: 'https://devnet.bundlr.network',
    providerUrl: 'https://api.devnet.solana.com',
    timeout: 60000,
}));

対応

上記の設定を組み込むと、最終版は以下のようになった。

// Ref: https://github.com/metaplex-foundation/js#uploadmetadata
import { Metaplex, keypairIdentity, bundlrStorage } from "@metaplex-foundation/js";
import { Connection, clusterApiUrl, Keypair, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";

const main = async() => {
  const connection = new Connection(clusterApiUrl("devnet"));
  const wallet = Keypair.generate();

  // airdrop
  let airdropSignature = await connection.requestAirdrop(
      wallet.publicKey,
      LAMPORTS_PER_SOL,
  );

  const latestBlockHash = await connection.getLatestBlockhash();

  await connection.confirmTransaction({
    blockhash: latestBlockHash.blockhash,
    lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
    signature: airdropSignature,
  });

  const walletBalance = await connection.getBalance(wallet.publicKey);
  console.log('walletBalance =>', walletBalance);
  // End airdrop


  const metaplex = Metaplex.make(connection)
      .use(keypairIdentity(wallet))
      .use(bundlrStorage({
        address: 'https://devnet.bundlr.network',
        providerUrl: 'https://api.devnet.solana.com',
        timeout: 60000,
      }));

  const { uri } = await metaplex.nfts().uploadMetadata({
      name: "My NFT",
      description: "My description",
      image: "https://arweave.net/ovSe7hNSNMYGQU55V8pOfIsIlZ4KIQNip6g25AnMu9s",
  });

    console.log(uri);
};

main();