Skip to content

Instantly share code, notes, and snippets.

@rogaldh
Last active December 2, 2025 17:36
Show Gist options
  • Select an option

  • Save rogaldh/59be2c4cc6ab90679ee2142e24d87a25 to your computer and use it in GitHub Desktop.

Select an option

Save rogaldh/59be2c4cc6ab90679ee2142e24d87a25 to your computer and use it in GitHub Desktop.
create-token22-with-metadata.mjs
// RUN:
// solana-test-validator --deactivate-feature CxeBn9PVeeXbmjbNwLv6U4C6svNxnC4JX6mfkvgeMocM --reset
// pnpx ts-node ./ctm.mjs
import { getCreateAccountInstruction } from '@solana-program/system';
import {
extension,
getCreateAssociatedTokenInstructionAsync,
getInitializeMint2Instruction,
getInitializeMetadataPointerInstruction,
getMintSize,
TOKEN_2022_PROGRAM_ADDRESS,
getInitializeTokenMetadataInstruction,
} from '@solana-program/token-2022';
import {
airdropFactory,
appendTransactionMessageInstructions,
createSolanaRpc,
createSolanaRpcSubscriptions,
createTransactionMessage,
generateKeyPairSigner,
getSignatureFromTransaction,
lamports,
pipe,
sendAndConfirmTransactionFactory,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
signTransactionMessageWithSigners,
some,
} from '@solana/kit';
// Create Connection, local validator in this example
const rpc = createSolanaRpc('http://localhost:8899');
const rpcSubscriptions = createSolanaRpcSubscriptions('ws://localhost:8900');
// Generate the authority for the mint (also acts as fee payer)
const authority = await generateKeyPairSigner();
// Fund authority/fee payer
await airdropFactory({ rpc, rpcSubscriptions })({
recipientAddress: authority.address,
lamports: lamports(5_000_000_000n), // 5 SOL
commitment: 'confirmed',
});
// Generate keypair to use as address of mint
const mint = await generateKeyPairSigner();
// Enable Metadata and Metadata Pointer extensions
const metadataExtension = extension('TokenMetadata', {
updateAuthority: some(authority.address),
mint: mint.address,
name: 'OPOS1',
symbol: 'OPS1',
uri: '', //'https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/DeveloperPortal/metadata.json',
additionalMetadata: new Map().set('description', 'Only possible on Solana'),
});
const metadataPointerExtension = extension('MetadataPointer', {
authority: some(authority.address),
metadataAddress: some(mint.address), // can also point to another account if desired
});
// Get mint account size with the metadata pointer extension alone
const spaceWithoutTokenMetadataExtension = BigInt(getMintSize([metadataPointerExtension]));
// Get mint account size with all extensions(metadata && metadataPointer)
const spaceWithTokenMetadataExtension = BigInt(getMintSize([metadataPointerExtension, metadataExtension]));
// Get minimum balance for rent exemption
const rent = await rpc.getMinimumBalanceForRentExemption(spaceWithTokenMetadataExtension).send();
// Get latest blockhash to include in transaction
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
// Instruction to create new account for mint
// Space: only metadata pointer (metadata will be added dynamically via realloc)
// Lamports: enough for full metadata size (to cover rent after realloc)
const createMintAccountInstruction = getCreateAccountInstruction({
payer: authority,
newAccount: mint,
lamports: rent, // rent for spaceWithTokenMetadataExtension
space: spaceWithoutTokenMetadataExtension,
programAddress: TOKEN_2022_PROGRAM_ADDRESS,
});
// Initialize metadata extension
const initializeMetadataInstruction = getInitializeTokenMetadataInstruction({
metadata: mint.address, // Account address that holds the metadata
updateAuthority: authority.address, // Authority that can update the metadata
mint: mint.address, // Mint Account address
mintAuthority: authority, // Designated Mint Authority
name: 'OPOS1',
symbol: 'OPS1',
uri: '', //'https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/DeveloperPortal/metadata.json',
});
// Initialize metadata pointer extension
const initializeMetadataPointerInstruction = getInitializeMetadataPointerInstruction({
mint: mint.address,
authority: some(authority.address),
metadataAddress: some(mint.address),
});
// Initialize mint account data (using Mint2 which doesn't require rent sysvar)
const initializeMintInstruction = getInitializeMint2Instruction({
mint: mint.address,
decimals: 9,
mintAuthority: authority.address,
freezeAuthority: some(authority.address),
});
// Create associated token account instruction
const createAtaInstruction = await getCreateAssociatedTokenInstructionAsync({
payer: authority,
owner: authority.address,
mint: mint.address,
});
// Build the instruction list
// NOTE: initializeMetadataInstruction requires reallocation which fails on local validator
// unless started with: solana-test-validator --deactivate-feature CxeBn9PVeeXbmjbNwLv6U4C6svNxnC4JX6mfkvgeMocM
const instructions = [
createMintAccountInstruction,
initializeMetadataPointerInstruction,
initializeMintInstruction,
initializeMetadataInstruction, // Uncomment after restarting validator with deactivated feature
createAtaInstruction,
];
// Create transaction message
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayerSigner(authority, tx),
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
tx => appendTransactionMessageInstructions(instructions, tx)
);
// Sign transaction message with all required signers
const signedTransaction = await signTransactionMessageWithSigners(transactionMessage);
// Send and confirm transaction
await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedTransaction, {
commitment: 'confirmed',
skipPreflight: true,
});
// Get transaction signature
const transactionSignature = getSignatureFromTransaction(signedTransaction);
console.log('Mint Address:', mint.address.toString());
console.log('Transaction Signature:', transactionSignature);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment