SolanaでToken Accountを作成すると「SendTransactionError: incorrect program id」エラー

現象

Token Accountを作成すると以下のエラーが発生。

ターミナル

/Users/user/Documents/Programming/Blockchain/solana-anchor-react-minimal-example/scripts/solana/spl-token-v0.3.x/node_modules/@solana/web3.js/src/connection.ts:5791
      throw new SendTransactionError(
            ^
SendTransactionError: failed to send transaction: Transaction simulation failed: Error processing Instruction 0: incorrect program id for instruction
    at Connection.sendEncodedTransaction (/Users/user/Documents/Programming/Blockchain/solana-anchor-react-minimal-example/scripts/solana/spl-token-v0.3.x/node_modules/@solana/web3.js/src/connection.ts:5791:13)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at async Connection.sendRawTransaction (/Users/user/Documents/Programming/Blockchain/solana-anchor-react-minimal-example/scripts/solana/spl-token-v0.3.x/node_modules/@solana/web3.js/src/connection.ts:5750:20)
    at async Connection.sendTransaction (/Users/user/Documents/Programming/Blockchain/solana-anchor-react-minimal-example/scripts/solana/spl-token-v0.3.x/node_modules/@solana/web3.js/src/connection.ts:5738:12)
    at async sendAndConfirmTransaction (/Users/user/Documents/Programming/Blockchain/solana-anchor-react-minimal-example/scripts/solana/spl-token-v0.3.x/node_modules/@solana/web3.js/src/utils/send-and-confirm-transaction.ts:35:21) {
  logs: [
    'Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [1]',
    'Program log: Create',
    'Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 3749 of 200000 compute units',
    'Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL failed: incorrect program id for instruction'
  ]
}

ts

import {
  createMint,
  createAccount,
  TOKEN_PROGRAM_ID,
} from '@solana/spl-token';

  const mint = await createMint(
    connection, // connection
    payer, // payer
    payer.publicKey, // mintAuthority
    null, // freezeAuthority
    9 // decimals
  );

  const tokenAccount = await createAccount(
    connection, // connection,
    payer, // payer
    Keypair.generate().publicKey, // mint
    payer.publicKey, // owner
  );

原因

Mintアドレスが間違っている

mintアドレスが正しくないため。
今回、わかりやすく失敗させるため、keypairで適当にアドレスを発行した。

Keypair.generate().publicKey, // mint

Mint Accountが存在していない

これもよくあるケースとして、createMint(Mint Accountの作成)してから、すぐにそのMint Accountを使おうとすると、トランザクションが完了していない場合が多々ある。その場合、「そのMintアドレスが間違っている(=まだ存在していない)」というエラーになる。

以下のようにデバッグするとMint Accountがnullになっていることがわかる。

console.log(await connection.getAccountInfo(mint));
// => null

対応

ケース1:mintアドレスを正しいものにする

正しいmintアドレスを指定する。

  const mint = await createMint(
    connection, // connection
    payer, // payer
    payer.publicKey, // mintAuthority
    null, // freezeAuthority
    9 // decimals
  );

  const tokenAccount = await createAccount(
    connection, // connection,
    payer, // payer
    mint, // mint ← 正しいmintアドレスを指定
    payer.publicKey, // owner
  );

ケース2:カスタムRPCにする

もしDevnet(https://api.devnet.solana.com/)で試している場合は、カスタムRPCを使う。

QuickNode

Solana公式のDevnetだと、バリデーター側の処理が遅いだけのため、カスタムRPCを使うだけでも解決することがある。

ケース3:commitmentOrConfigをセットする

Connectionで「confirmed」をセットする。

型:

new Connection(endpoint: string, commitmentOrConfig?: Commitment | ConnectionConfig): Connection

実装例:

const connection = new web3.Connection('http://127.0.0.1:8899', 'confirmed');

API:constructor

ケース4:sleepさせる

トランザクションが終わっていない場合は、終わるまでsleep&retryして待つ。
mainnet-betaで厳密に運用するならこの方法が確実。

Mint Accountのトランザクションチェック

import {
  Connection,
  SendTransactionError,
  AccountInfo,
} from '@solana/web3.js';
import {
  createAccount,
  createMint,
  getAccount,
  TokenAccountNotFoundError,
} from '@solana/spl-token';

...

  // Create mint
  const mint = await createMint(
    connection,
    payer,
    payer.publicKey,
    payer.publicKey,
    0,
  );

  // Check transaction
  {
    const retry = 60; // Max retry
    let accountInfo: null | AccountInfo<Buffer>;

    for (let i = 0; i < retry; i++) {
      console.count('Checking for mint account transaction...');
      accountInfo = await connection.getAccountInfo(mint);

      if(accountInfo) {
        console.log("Mint address:", mint.toBase58());
        break;
      } else {
        sleep(1000); // Wait for 1s(=1000ms)
      }
    }
  }

ATA(Associated Token Account)のトランザクションチェック

import {
  Connection,
  SendTransactionError,
  AccountInfo,
} from '@solana/web3.js';
import {
  createAccount,
  createMint,
  getAccount,
  TokenAccountNotFoundError,
} from '@solana/spl-token';

...

  // Create associated token account
  const tokenAccount = await createAccount(
    connection,
    payer,
    mint,
    payer.publicKey,
  );

    // Check transaction
  {
    const retry = 60; // Max retry

    for (let i = 0; i < retry; i++) {
      console.count('Checking for token account transaction...');

      try {
        const tokenAccountInfo = await getAccount(connection, tokenAccount);
        console.log("Token account:", tokenAccountInfo.address.toBase58());
        break;
      } catch (error: any) {
        if (!(error instanceof SendTransactionError || TokenAccountNotFoundError)) {
          console.error(error);
        }
        sleep(1000); // Wait for 1s(=1000ms)
      }
    }
  }