Agenda
現象
Token ExtensionsでMetadata Pointer、TOKEN_2022_PROGRAM_IDを使って、getOrCreateAssociatedTokenAccountを実行したところ以下のエラー。
/Users/256hax/Documents/buidl/256hax/solana-anchor-react-minimal-example/scripts/solana/token-extensions/metadata-pointer-extension-without-metaplex/node_modules/@solana/spl-token/src/state/account.ts:170
if (!info) throw new TokenAccountNotFoundError();
^
TokenAccountNotFoundError
at unpackAccount (/Users/256hax/Documents/buidl/256hax/solana-anchor-react-minimal-example/scripts/solana/token-extensions/metadata-pointer-extension-without-metaplex/node_modules/@solana/spl-token/src/state/account.ts:170:22)
at getAccount (/Users/256hax/Documents/buidl/256hax/solana-anchor-react-minimal-example/scripts/solana/token-extensions/metadata-pointer-extension-without-metaplex/node_modules/@solana/spl-token/src/state/account.ts:103:12)
at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
at async getOrCreateAssociatedTokenAccount (/Users/256hax/Documents/buidl/256hax/solana-anchor-react-minimal-example/scripts/solana/token-extensions/metadata-pointer-extension-without-metaplex/node_modules/@solana/spl-token/src/actions/getOrCreateAssociatedTokenAccount.ts:79:23)
at async <anonymous> (/Users/256hax/Documents/buidl/256hax/solana-anchor-react-minimal-example/scripts/solana/token-extensions/metadata-pointer-extension-without-metaplexsrc/index.ts:264:3)
Node.js v23.0.0
ソース
import {
Connection,
Keypair,
LAMPORTS_PER_SOL,
PublicKey,
SystemProgram,
Transaction,
clusterApiUrl,
sendAndConfirmTransaction,
} from "@solana/web3.js";
import {
ExtensionType,
TOKEN_2022_PROGRAM_ID,
createInitializeMintInstruction,
getMintLen,
createInitializeMetadataPointerInstruction,
getMint,
getMetadataPointerState,
getTokenMetadata,
TYPE_SIZE,
LENGTH_SIZE,
getOrCreateAssociatedTokenAccount,
mintTo,
getAssociatedTokenAddress,
createAssociatedTokenAccountInstruction,
createMintToInstruction,
} from "@solana/spl-token";
import {
createInitializeInstruction,
createUpdateFieldInstruction,
createRemoveKeyInstruction,
pack,
TokenMetadata,
} from "@solana/spl-token-metadata";
import dotenv from "dotenv";
dotenv.config();
// Payer
const payerSecretKey = process.env.PAYER_SECRET_KEY;
if (!payerSecretKey) throw new Error('payerSecretKey not found.');
const secretKey = Uint8Array.from(JSON.parse(payerSecretKey));
const payer = Keypair.fromSecretKey(secretKey);
// Connection to devnet cluster
const connection = new Connection('http://127.0.0.1:8899', 'confirmed');
// const connection = new Connection(clusterApiUrl("devnet"), "confirmed");
let transaction: Transaction;
let transactionSignature: string;
const mintKeypair = Keypair.generate();
const mint = mintKeypair.publicKey;
const decimals = 2;
const mintAuthority = payer.publicKey;
const updateAuthority = payer.publicKey;
// Metadata to store in Mint Account
const metaData: TokenMetadata = {
updateAuthority: updateAuthority,
mint: mint,
name: "OPOS",
symbol: "OPOS",
uri: "https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/DeveloperPortal/metadata.json",
additionalMetadata: [["description", "Only Possible On Solana"]],
};
// Size of MetadataExtension 2 bytes for type, 2 bytes for length
const metadataExtension = TYPE_SIZE + LENGTH_SIZE;
// Size of metadata
const metadataLen = pack(metaData).length;
// Size of Mint Account with extension
const mintLen = getMintLen([ExtensionType.MetadataPointer]);
// Minimum lamports required for Mint Account
const lamports = await connection.getMinimumBalanceForRentExemption(
mintLen + metadataExtension + metadataLen
);
// Instruction to invoke System Program to create new account
const createAccountInstruction = SystemProgram.createAccount({
fromPubkey: payer.publicKey, // Account that will transfer lamports to created account
newAccountPubkey: mint, // Address of the account to create
space: mintLen, // Amount of bytes to allocate to the created account
lamports, // Amount of lamports transferred to created account
programId: TOKEN_2022_PROGRAM_ID, // Program assigned as owner of created account
});
// Instruction to initialize the MetadataPointer Extension
const initializeMetadataPointerInstruction =
createInitializeMetadataPointerInstruction(
mint, // Mint Account address
updateAuthority, // Authority that can set the metadata address
mint, // Account address that holds the metadata
TOKEN_2022_PROGRAM_ID
);
// Instruction to initialize Mint Account data
const initializeMintInstruction = createInitializeMintInstruction(
mint, // Mint Account Address
decimals, // Decimals of Mint
mintAuthority, // Designated Mint Authority
null, // Optional Freeze Authority
TOKEN_2022_PROGRAM_ID // Token Extension Program ID
);
// Instruction to initialize Metadata Account data
const initializeMetadataInstruction = createInitializeInstruction({
programId: TOKEN_2022_PROGRAM_ID, // Token Extension Program as Metadata Program
metadata: mint, // Account address that holds the metadata
updateAuthority: updateAuthority, // Authority that can update the metadata
mint: mint, // Mint Account address
mintAuthority: mintAuthority, // Designated Mint Authority
name: metaData.name,
symbol: metaData.symbol,
uri: metaData.uri,
});
// Instruction to update metadata, adding custom field
const updateFieldInstruction = createUpdateFieldInstruction({
programId: TOKEN_2022_PROGRAM_ID, // Token Extension Program as Metadata Program
metadata: mint, // Account address that holds the metadata
updateAuthority: updateAuthority, // Authority that can update the metadata
field: metaData.additionalMetadata[0][0], // key
value: metaData.additionalMetadata[0][1], // value
});
// Add instructions to new transaction
transaction = new Transaction().add(
createAccountInstruction,
initializeMetadataPointerInstruction,
initializeMintInstruction,
initializeMetadataInstruction,
updateFieldInstruction
);
// Send transaction
transactionSignature = await sendAndConfirmTransaction(
connection,
transaction,
[payer, mintKeypair] // Signers
);
console.log(
"\nCreate Mint Account:",
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`
);
// Retrieve mint information
const mintInfo = await getMint(
connection,
mint,
"confirmed",
TOKEN_2022_PROGRAM_ID
);
// Retrieve and log the metadata pointer state
const metadataPointer = getMetadataPointerState(mintInfo);
console.log("\nMetadata Pointer:", JSON.stringify(metadataPointer, null, 2));
// Retrieve and log the metadata state
const metadata = await getTokenMetadata(
connection,
mint // Mint Account address
);
console.log("\nMetadata:", JSON.stringify(metadata, null, 2));
// Instruction to remove a key from the metadata
const removeKeyInstruction = createRemoveKeyInstruction({
programId: TOKEN_2022_PROGRAM_ID, // Token Extension Program as Metadata Program
metadata: mint, // Address of the metadata
updateAuthority: updateAuthority, // Authority that can update the metadata
key: metaData.additionalMetadata[0][0], // Key to remove from the metadata
idempotent: true, // If the idempotent flag is set to true, then the instruction will not error if the key does not exist
});
// Add instruction to new transaction
transaction = new Transaction().add(removeKeyInstruction);
// Send transaction
transactionSignature = await sendAndConfirmTransaction(
connection,
transaction,
[payer]
);
console.log(
"\nRemove Additional Metadata Field:",
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`
);
// Retrieve and log the metadata state
const updatedMetadata = await getTokenMetadata(
connection,
mint // Mint Account address
);
console.log("\nUpdated Metadata:", JSON.stringify(updatedMetadata, null, 2));
console.log(
"\nMint Account:",
`https://solana.fm/address/${mint}?cluster=devnet-solana`
);
const tokenProgramId = TOKEN_2022_PROGRAM_ID;
const tokenAccount = await getOrCreateAssociatedTokenAccount(
connection,
payer,
mint,
payer.publicKey,
true,
"finalized",
{ commitment: "finalized" },
tokenProgramId, // TOKEN_PROGRAM_ID for Token Program tokens and TOKEN_2022_PROGRAM_ID for Token Extensions Program tokens
);
原因
いろいろ調べたものの原因わからず。
よくあるのが、MintやPayerなどのアドレスの記載ミスだが、今回はどうしてもわからず。
公式サイトどおりに実装しているのにエラーが発生してしまう。
対策
getOrCreateAssociatedTokenAccount は、うまくいかないケースがあるため、 getAssociatedTokenAddress と createMintToInstruction を別々に実行する。
let transactionMint: Transaction;
let transactionMintSignature: string;
const associatedTokenAccount = await getAssociatedTokenAddress(
mint,
payer.publicKey,
false,
TOKEN_2022_PROGRAM_ID,
);
console.log("\nassociatedTokenAccount:", associatedTokenAccount);
const destinationTokenAccount = await createAssociatedTokenAccountInstruction(
payer.publicKey, // Payer to create Token Account
associatedTokenAccount, // Token Account owner
payer.publicKey,
mint, // Mint Account address
TOKEN_2022_PROGRAM_ID, // Token Extension Program ID
);
const mintToInstruction = await createMintToInstruction(
new PublicKey(mint), // Mint Account address
associatedTokenAccount, // Destination address
mintAuthority, // Mint token authority
100, // Amount (100 for 2 decimal place mint = 1.00 tokens)
undefined, // Signers if multisig
TOKEN_2022_PROGRAM_ID, // Token Extension Program ID
);
transactionMint = new Transaction().add(
destinationTokenAccount, mintToInstruction);
transactionMintSignature = await sendAndConfirmTransaction(
connection,
transactionMint,
[payer]
);
console.log(
"\nMint Signature:",
`https://solana.fm/tx/${transactionMintSignature}?cluster=devnet-solana`
);