Durable Nonceを使ってSOL送信すると「insufficient funds for rent」エラー

@solana/web3.js、Durable Nonceを使ってSOLを送信したときに発生したエラー。

現象

ソースコードを実行すると以下のエラーが発生。

/Users/transaction-test/node_modules/@solana/web3.js/src/connection.ts:5920
      throw new SendTransactionError(
            ^
SendTransactionError: failed to send transaction: Transaction simulation failed: Transaction results in an account (2) with insufficient funds for rent
    at Connection.sendEncodedTransaction (/Users/transaction-test/node_modules/@solana/web3.js/src/connection.ts:5920:13)
    at processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async Connection.sendRawTransaction (/Users/transaction-test/node_modules/@solana/web3.js/src/connection.ts:5879:20)
    at async main (/Users/transaction-test/src/transferSolUsingMadMints.ts:105:39) {
  logs: [
    'Program 11111111111111111111111111111111 invoke [1]',
    'Program 11111111111111111111111111111111 success',
    'Program 11111111111111111111111111111111 invoke [1]',
    'Program 11111111111111111111111111111111 success'
  ]
}

ソースコード:

// Lib
import * as dotenv from 'dotenv';
import * as bs58 from 'bs58';

// Solana
import {
  Connection,
  Keypair,
  PublicKey,
  SystemProgram,
  Transaction,
  LAMPORTS_PER_SOL,
} from '@solana/web3.js';

import { closeAccount } from '@solana/spl-token';

// Mad Mints
import { createNonceAccount } from 'mad-mints-packages';
import { getNonceAccount } from 'mad-mints-packages';

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

  const endpoint = process.env.ENDPOINT;
  if (!endpoint) throw new Error('endpoint not found.');
  const connection = new Connection(endpoint, 'confirmed');

  const adminSecretKeyBase58 = process.env.PAYER_SECRET_KEY;
  if (!adminSecretKeyBase58) throw new Error('adminSecretKeyBase58 not found.');
  const payer = Keypair.fromSecretKey(bs58.decode(adminSecretKeyBase58));

  // Taker
  const takerPublicKey = new PublicKey(
    'CJsPSQtV28CJiRt8XThuG5Ei1cX2fH5GcPoZYyM26gzm'
  );

  // ---------------------------------------------------
  //  Airdrop
  // ---------------------------------------------------
  let latestBlockhash = await connection.getLatestBlockhash();
  const airdropSignature = await connection.requestAirdrop(
    payer.publicKey,
        LAMPORTS_PER_SOL
  );
  await connection.confirmTransaction({
    blockhash: latestBlockhash.blockhash,
    lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
    signature: airdropSignature,
  });

  // ------------------------------------
  //  Create Nonce Account
  // ------------------------------------
  // Read None Account Auth Secret Key
  const nonceAccountAuth = payer;
  const nonceAccount = await createNonceAccount(
    connection,
    payer,
    nonceAccountAuth.publicKey
  );

  if (!nonceAccount) throw Error('Nonce Account not found.');
  console.log();

  // ------------------------------------
  //  Get Nonce
  // ------------------------------------
  const nonceAccountInfo = await getNonceAccount(connection, nonceAccount);

  if (!nonceAccountInfo) throw Error('Nonce Account not found.');
  const nonce = nonceAccountInfo.nonce;

  // ------------------------------------
  //  Create Instruction
  // ------------------------------------
  let tx = new Transaction();

  // nonce advance must be the first insturction.
  const nonceInstruction = SystemProgram.nonceAdvance({
    noncePubkey: nonceAccount,
    authorizedPubkey: nonceAccountAuth.publicKey,
  });
  tx.add(nonceInstruction);

  const instructions = SystemProgram.transfer({
    fromPubkey: payer.publicKey,
    toPubkey: takerPublicKey,
    lamports: LAMPORTS_PER_SOL * 0.00001,
  });
  tx.add(instructions);

  // assign `nonce` as recentBlockhash.
  tx.recentBlockhash = nonce;
  tx.feePayer = payer.publicKey;

  // ------------------------------------
  //  Sign and Send Transaction
  // ------------------------------------
  tx.sign(payer, nonceAccountAuth);
  const signatureSendRawTransaction = await connection.sendRawTransaction(
    tx.serialize()
  );

  console.log('signature =>', signatureSendRawTransaction);
};

main();

原因

SOLの受取先がSOL残高ゼロのため、Rent Feeがなくアカウント保持ができていないため。

対応

受取先にもSOL(Rent Fee分だけでよい)を残しておく。
(今回は、PayerしかAirdropしていなかったため、TakerのみAirdropすることで解決)

SolanaのRent Feeを計算することもできるが、面倒なのでだいたい 0.002 SOL をテストとして送信している。もしダメだったらもっと増やせばよい。