UNPKG

@solana-developers/helpers

Version:
145 lines 7.38 kB
import { Keypair, sendAndConfirmTransaction, SystemProgram, Transaction, TransactionMessage, VersionedTransaction } from "@solana/web3.js"; import { makeKeypairs } from "./keypair.js"; import { createAssociatedTokenAccountIdempotentInstruction, createInitializeInstruction, createInitializeMetadataPointerInstruction, createInitializeMint2Instruction, createInitializeMintInstruction, createMintToInstruction, ExtensionType, getAssociatedTokenAddressSync, getMinimumBalanceForRentExemptMint, getMintLen, LENGTH_SIZE, MINT_SIZE, TOKEN_2022_PROGRAM_ID, TYPE_SIZE } from "@solana/spl-token"; import { confirmTransaction } from "./transaction.js"; import { createUpdateFieldInstruction, pack } from "@solana/spl-token-metadata"; const TOKEN_PROGRAM = TOKEN_2022_PROGRAM_ID; // Create users, mints, create ATAs and mint tokens. // TODO: we may actually want to split this into multiple transactions // to avoid the transaction size limit (or use lookup tables) // in the future. However it works for two transactions of the size // used in our unit tests. export const createAccountsMintsAndTokenAccounts = async (usersAndTokenBalances, lamports, connection, payer) => { const userCount = usersAndTokenBalances.length; // Set the variable mintCount to the largest array in the usersAndTokenBalances array const mintCount = Math.max(...usersAndTokenBalances.map((mintBalances) => mintBalances.length)); const users = makeKeypairs(userCount); const mints = makeKeypairs(mintCount); // This will be returned // [user index][mint index]address of token account let tokenAccounts; tokenAccounts = users.map((user) => { return mints.map((mint) => getAssociatedTokenAddressSync(mint.publicKey, user.publicKey, false, TOKEN_PROGRAM)); }); const sendSolInstructions = users.map((user) => SystemProgram.transfer({ fromPubkey: payer.publicKey, toPubkey: user.publicKey, lamports, })); // Airdrops to user const minimumLamports = await getMinimumBalanceForRentExemptMint(connection); const createMintInstructions = mints.map((mint) => SystemProgram.createAccount({ fromPubkey: payer.publicKey, newAccountPubkey: mint.publicKey, lamports: minimumLamports, space: MINT_SIZE, programId: TOKEN_PROGRAM, })); // Make tokenA and tokenB mints, mint tokens and create ATAs const mintTokensInstructions = usersAndTokenBalances.flatMap((userTokenBalances, userIndex) => { return userTokenBalances.flatMap((tokenBalance, mintIndex) => { if (tokenBalance === 0) { return []; } return makeMintInstructions(mints[mintIndex].publicKey, tokenAccounts[userIndex][mintIndex], tokenBalance, users[userIndex].publicKey, payer.publicKey); }); }); const instructions = [ ...sendSolInstructions, ...createMintInstructions, ...mintTokensInstructions, ]; const signers = [...users, ...mints, payer]; // Finally, make the transaction and send it. await makeAndSendAndConfirmTransaction(connection, instructions, signers, payer); return { users, mints, tokenAccounts, }; }; export const makeTokenMint = async (connection, mintAuthority, name, symbol, decimals, uri, additionalMetadata = [], updateAuthority = mintAuthority.publicKey, freezeAuthority = null) => { const mint = Keypair.generate(); if (!Array.isArray(additionalMetadata)) { additionalMetadata = Object.entries(additionalMetadata); } const addMetadataInstructions = additionalMetadata.map((additionalMetadataItem) => { return createUpdateFieldInstruction({ metadata: mint.publicKey, updateAuthority: updateAuthority, programId: TOKEN_2022_PROGRAM_ID, field: additionalMetadataItem[0], value: additionalMetadataItem[1], }); }); const metadata = { mint: mint.publicKey, name, symbol, uri, additionalMetadata, }; // Work out how much SOL we need to store our Token const mintLength = getMintLen([ExtensionType.MetadataPointer]); const metadataLength = TYPE_SIZE + LENGTH_SIZE + pack(metadata).length; const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLength + metadataLength); const mintTransaction = new Transaction().add( // Create Account SystemProgram.createAccount({ fromPubkey: mintAuthority.publicKey, newAccountPubkey: mint.publicKey, space: mintLength, lamports: mintLamports, programId: TOKEN_2022_PROGRAM_ID, }), // Initialize metadata pointer (so the mint points to itself for metadata) createInitializeMetadataPointerInstruction(mint.publicKey, mintAuthority.publicKey, mint.publicKey, TOKEN_2022_PROGRAM_ID), // Initialize mint createInitializeMintInstruction(mint.publicKey, decimals, mintAuthority.publicKey, freezeAuthority, TOKEN_2022_PROGRAM_ID), // Initialize createInitializeInstruction({ programId: TOKEN_2022_PROGRAM_ID, mint: mint.publicKey, metadata: mint.publicKey, name: metadata.name, symbol: metadata.symbol, uri: metadata.uri, mintAuthority: mintAuthority.publicKey, updateAuthority: updateAuthority, }), // Update field (actually used to add a custom field) // See https://github.com/solana-labs/solana-program-library/blob/master/token/js/examples/metadata.ts#L81C6-L81C6 // Must come last! ...addMetadataInstructions); const signature = await sendAndConfirmTransaction(connection, mintTransaction, [mintAuthority, mint]); return mint.publicKey; }; // Just a non-exposed helper function to create all the instructions instructions // needed for creating a mint, creating an ATA, and minting tokens to the ATA // TODO: maybe we should expose this? To discuss. const makeMintInstructions = (mintAddress, ataAddress, amount, authority, payer = authority) => { return [ // Initializes a new mint and optionally deposits all the newly minted tokens in an account. createInitializeMint2Instruction(mintAddress, 6, authority, null, TOKEN_PROGRAM), // Create the ATA createAssociatedTokenAccountIdempotentInstruction(payer, ataAddress, authority, mintAddress, TOKEN_PROGRAM), // Mint some tokens to the ATA createMintToInstruction(mintAddress, ataAddress, authority, amount, [], TOKEN_PROGRAM), ]; }; // Send a versioned transaction with less boilerplate // https://www.quicknode.com/guides/solana-development/transactions/how-to-use-versioned-transactions-on-solana // TODO: maybe we should expose this? To discuss. const makeAndSendAndConfirmTransaction = async (connection, instructions, signers, payer) => { const latestBlockhash = await connection.getLatestBlockhash("confirmed"); const messageV0 = new TransactionMessage({ payerKey: payer.publicKey, recentBlockhash: latestBlockhash.blockhash, instructions, }).compileToV0Message(); const transaction = new VersionedTransaction(messageV0); transaction.sign(signers); const signature = await connection.sendTransaction(transaction); await confirmTransaction(connection, signature); }; //# sourceMappingURL=token.js.map