Last active
December 2, 2025 17:36
-
-
Save rogaldh/59be2c4cc6ab90679ee2142e24d87a25 to your computer and use it in GitHub Desktop.
create-token22-with-metadata.mjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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