UNPKG

solana-kite

Version:

The modern Solana framework for TypeScript.

1,416 lines (1,402 loc) 70.4 kB
import { ASSOCIATED_TOKEN_PROGRAM, DEFAULT_AIRDROP_AMOUNT, DEFAULT_ENV_KEYPAIR_VARIABLE_NAME, DEFAULT_TRANSACTION_RETRIES, DEFAULT_TRANSACTION_TIMEOUT, DISCRIMINATOR_SIZE, LENGTH_FIELD_SIZE, NOT_FOUND, PUBLIC_KEY_SIZE, SECONDS, SOL, TOKEN_EXTENSIONS_PROGRAM, TOKEN_PROGRAM, addKeyPairSignerToEnvFile, checkAddressMatchesPrivateKey, checkIfAddressIsPublicKey, createJSONFromKeyPairSigner, grindKeyPair, loadWalletFromEnvironment, loadWalletFromFile } from "./chunk-D5BJ3YJK.js"; // src/lib/connect.ts import { createDefaultRpcTransport, createSolanaRpcFromTransport as createSolanaRpcFromTransport5, createSolanaRpcSubscriptions as createSolanaRpcSubscriptions3, sendAndConfirmTransactionFactory as sendAndConfirmTransactionFactory3 } from "@solana/kit"; import { createRecentSignatureConfirmationPromiseFactory } from "@solana/transaction-confirmation"; // src/lib/url.ts var encodeURL = (baseUrl, searchParams) => { const url = new URL(baseUrl); url.search = new URLSearchParams(searchParams).toString(); return url.toString(); }; var checkIsValidURL = (string) => { try { new URL(string); return true; } catch (error) { return false; } }; // src/lib/clusters.ts var CLUSTERS = { // Solana Labs RPCs // Don't add a seperate entry for 'mainnet'. Instead, we'll correct the cluster name to 'mainnet-beta' // in the connect function, and avoid making a duplicate entry. // Note these are rate-limited public RPCs, you should use a commercial RPC provider for production apps. "mainnet-beta": { httpURL: "https://api.mainnet-beta.solana.com", webSocketURL: "wss://api.mainnet-beta.solana.com", requiredParam: null, requiredParamEnvironmentVariable: null, requiredRpcEnvironmentVariable: null, features: { isExplorerDefault: true, isNameKnownToSolanaExplorer: true, supportsGetPriorityFeeEstimate: false, enableClientSideRetries: true, needsPriorityFees: true } }, testnet: { httpURL: "https://api.testnet.solana.com", webSocketURL: "wss://api.testnet.solana.com", requiredParam: null, requiredParamEnvironmentVariable: null, requiredRpcEnvironmentVariable: null, features: { isExplorerDefault: false, isNameKnownToSolanaExplorer: true, supportsGetPriorityFeeEstimate: false, enableClientSideRetries: true, needsPriorityFees: true } }, devnet: { httpURL: "https://api.devnet.solana.com", webSocketURL: "wss://api.devnet.solana.com", requiredParam: null, requiredParamEnvironmentVariable: null, requiredRpcEnvironmentVariable: null, features: { isExplorerDefault: false, isNameKnownToSolanaExplorer: true, supportsGetPriorityFeeEstimate: false, enableClientSideRetries: true, needsPriorityFees: true } }, // Quicknode RPCs "quicknode-mainnet": { // These endpoints are configured at https://dashboard.quicknode.com/endpoints httpURL: null, webSocketURL: null, requiredParam: null, requiredParamEnvironmentVariable: null, requiredRpcEnvironmentVariable: "QUICKNODE_SOLANA_MAINNET_ENDPOINT", features: { isExplorerDefault: false, isNameKnownToSolanaExplorer: false, // TODO: add support for QuickNode priority fee API supportsGetPriorityFeeEstimate: false, enableClientSideRetries: true, needsPriorityFees: true } }, "quicknode-devnet": { // These endpoints are configured at https://dashboard.quicknode.com/endpoints httpURL: null, webSocketURL: null, requiredParam: null, requiredParamEnvironmentVariable: null, requiredRpcEnvironmentVariable: "QUICKNODE_SOLANA_DEVNET_ENDPOINT", features: { isExplorerDefault: false, isNameKnownToSolanaExplorer: false, // TODO: add support for QuickNode priority fee API supportsGetPriorityFeeEstimate: false, enableClientSideRetries: true, needsPriorityFees: true } }, "quicknode-testnet": { // These endpoints are configured at https://dashboard.quicknode.com/endpoints httpURL: null, webSocketURL: null, requiredParam: null, requiredParamEnvironmentVariable: null, requiredRpcEnvironmentVariable: "QUICKNODE_SOLANA_TESTNET_ENDPOINT", features: { isExplorerDefault: false, isNameKnownToSolanaExplorer: false, // TODO: add support for QuickNode priority fee API supportsGetPriorityFeeEstimate: false, enableClientSideRetries: true, needsPriorityFees: true } }, // Helius RPCs "helius-mainnet": { httpURL: "https://mainnet.helius-rpc.com/", webSocketURL: "wss://mainnet.helius-rpc.com/", requiredParam: "api-key", requiredParamEnvironmentVariable: "HELIUS_API_KEY", requiredRpcEnvironmentVariable: null, features: { isExplorerDefault: false, isNameKnownToSolanaExplorer: false, supportsGetPriorityFeeEstimate: true, enableClientSideRetries: true, needsPriorityFees: true } }, "helius-devnet": { httpURL: "https://devnet.helius-rpc.com/", webSocketURL: "wss://devnet.helius-rpc.com/", requiredParam: "api-key", requiredParamEnvironmentVariable: "HELIUS_API_KEY", requiredRpcEnvironmentVariable: null, features: { isExplorerDefault: false, isNameKnownToSolanaExplorer: false, supportsGetPriorityFeeEstimate: false, enableClientSideRetries: true, needsPriorityFees: true } }, // Triton RPCs "triton-mainnet": { // These endpoints are configured at https://triton.one/ // Format: https://your-app.mainnet.rpcpool.com/your-token httpURL: null, webSocketURL: null, requiredParam: null, requiredParamEnvironmentVariable: null, requiredRpcEnvironmentVariable: "TRITON_SOLANA_MAINNET_ENDPOINT", features: { isExplorerDefault: false, isNameKnownToSolanaExplorer: false, supportsGetPriorityFeeEstimate: false, enableClientSideRetries: true, needsPriorityFees: true } }, "triton-devnet": { // These endpoints are configured at https://triton.one/ // Format: https://your-app.devnet.rpcpool.com/your-token httpURL: null, webSocketURL: null, requiredParam: null, requiredParamEnvironmentVariable: null, requiredRpcEnvironmentVariable: "TRITON_SOLANA_DEVNET_ENDPOINT", features: { isExplorerDefault: false, isNameKnownToSolanaExplorer: false, supportsGetPriorityFeeEstimate: false, enableClientSideRetries: true, needsPriorityFees: true } }, "triton-testnet": { // These endpoints are configured at https://triton.one/ // Format: https://your-app.testnet.rpcpool.com/your-token httpURL: null, webSocketURL: null, requiredParam: null, requiredParamEnvironmentVariable: null, requiredRpcEnvironmentVariable: "TRITON_SOLANA_TESTNET_ENDPOINT", features: { isExplorerDefault: false, isNameKnownToSolanaExplorer: false, supportsGetPriorityFeeEstimate: false, enableClientSideRetries: true, needsPriorityFees: true } }, // Localnet localnet: { httpURL: "http://localhost:8899", webSocketURL: "ws://localhost:8900", requiredParam: null, requiredParamEnvironmentVariable: null, requiredRpcEnvironmentVariable: null, features: { isExplorerDefault: false, isNameKnownToSolanaExplorer: false, supportsGetPriorityFeeEstimate: false, enableClientSideRetries: false, needsPriorityFees: false } } }; var KNOWN_CLUSTER_NAMES = Object.keys(CLUSTERS); var KNOWN_CLUSTER_NAMES_STRING = KNOWN_CLUSTER_NAMES.join(", "); // src/lib/transactions.ts import { appendTransactionMessageInstructions, assertIsTransactionWithinSizeLimit as assertIsTransactionWithinSizeLimit2, createTransactionMessage, getBase58Decoder, getBase58Encoder, getSignatureFromTransaction, pipe, setTransactionMessageFeePayerSigner, setTransactionMessageLifetimeUsingBlockhash, signTransactionMessageWithSigners, assertIsTransactionMessageWithBlockhashLifetime } from "@solana/kit"; // src/lib/smart-transactions.ts import { appendTransactionMessageInstruction, isWritableRole, isInstructionWithData, SOLANA_ERROR__TRANSACTION_ERROR__ALREADY_PROCESSED, isSolanaError, SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE, assertIsTransactionWithinSizeLimit } from "@solana/kit"; import { getSetComputeUnitPriceInstruction, identifyComputeBudgetInstruction, COMPUTE_BUDGET_PROGRAM_ADDRESS, ComputeBudgetInstruction, estimateComputeUnitLimitFactory } from "@solana-program/compute-budget"; import { getAbortablePromise } from "@solana/promises"; var getPriorityFeeEstimate = async (rpc, supportsGetPriorityFeeEstimate, transactionMessage, abortSignal = null) => { const accountKeys = [ .../* @__PURE__ */ new Set([ ...transactionMessage.instructions.flatMap( (instruction) => (instruction.accounts ?? []).filter((account) => isWritableRole(account.role)).map((account) => account.address) ) ]) ]; if (!supportsGetPriorityFeeEstimate) { const recentFeesResponse = await rpc.getRecentPrioritizationFees([...accountKeys]).send({ abortSignal }); const recentFeesValues = recentFeesResponse.reduce((accumulator, current) => { if (current.prioritizationFee > 0n) { return [...accumulator, current.prioritizationFee]; } else { return accumulator; } }, []); recentFeesValues.sort((a, b) => Number(a - b)); return Number(recentFeesValues[Math.floor(recentFeesValues.length / 2)]); } const { priorityFeeEstimate } = await rpc.getPriorityFeeEstimate({ accountKeys, options: { // See https://docs.helius.dev/solana-apis/priority-fee-api // Per Evan at Helius 20250213: recommended: true is not longer preferred, // instead use priorityLevel: "High" priorityLevel: "High" } }).send({ abortSignal }); return priorityFeeEstimate; }; var getComputeUnitEstimate = async (rpc, transactionMessage, abortSignal = null) => { const hasExistingComputeBudgetPriceInstruction = transactionMessage.instructions.some( (instruction) => instruction.programAddress === COMPUTE_BUDGET_PROGRAM_ADDRESS && isInstructionWithData(instruction) && identifyComputeBudgetInstruction(instruction) === ComputeBudgetInstruction.SetComputeUnitPrice ); const transactionMessageToSimulate = hasExistingComputeBudgetPriceInstruction ? transactionMessage : appendTransactionMessageInstruction(getSetComputeUnitPriceInstruction({ microLamports: 0n }), transactionMessage); const estimateComputeUnitLimit = estimateComputeUnitLimitFactory({ rpc }); return estimateComputeUnitLimit(transactionMessageToSimulate, { abortSignal: abortSignal ?? void 0 }); }; var sendTransactionWithRetries = async (sendAndConfirmTransaction, transaction, options = { maximumClientSideRetries: DEFAULT_TRANSACTION_RETRIES, abortSignal: null, commitment: "confirmed", timeout: null }) => { if (options.commitment === "finalized") { console.warn( "Using finalized commitment for transaction with retries is not recommended. This will likely result in blockhash expiration." ); } let retriesLeft = options.maximumClientSideRetries; const transactionOptions = { // TODO: web3.js wants explicit undefineds. Fix upstream. abortSignal: options.abortSignal || void 0, commitment: options.commitment, // This is the server-side retries and should always be 0. // We will do retries here on the client. // See https://docs.helius.dev/solana-rpc-nodes/sending-transactions-on-solana#sending-transactions-without-the-sdk maxRetries: 0n }; let timeout; if (options.timeout) { timeout = options.timeout; } else { switch (options.commitment) { case "processed": timeout = 5 * SECONDS; break; case "confirmed": timeout = 15 * SECONDS; break; case "finalized": timeout = 30 * SECONDS; break; default: timeout = DEFAULT_TRANSACTION_TIMEOUT; break; } } while (retriesLeft) { try { assertIsTransactionWithinSizeLimit(transaction); const txPromise = sendAndConfirmTransaction(transaction, transactionOptions); await getAbortablePromise(txPromise, AbortSignal.timeout(timeout)); break; } catch (error) { if (error instanceof DOMException && error.name === "TimeoutError") { console.debug("Transaction not confirmed, retrying..."); } else if (isSolanaError(error, SOLANA_ERROR__TRANSACTION_ERROR__ALREADY_PROCESSED)) { break; } else if (isSolanaError(error, SOLANA_ERROR__JSON_RPC__SERVER_ERROR_SEND_TRANSACTION_PREFLIGHT_FAILURE)) { if (error.cause && isSolanaError(error.cause, SOLANA_ERROR__TRANSACTION_ERROR__ALREADY_PROCESSED)) { break; } } else { throw error; } } finally { retriesLeft--; } } }; // src/lib/transactions.ts import { getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction as getSetComputeUnitPriceInstruction2 } from "@solana-program/compute-budget"; // src/lib/logs.ts var getLogsFactory = (rpc) => { const getLogs = async (signature) => { const transaction = await rpc.getTransaction(signature, { commitment: "confirmed", maxSupportedTransactionVersion: 0 }).send(); if (!transaction?.meta) { throw new Error(`Transaction not found: ${signature}`); } return transaction.meta.logMessages ?? []; }; return getLogs; }; var getErrorMessageFromLogs = (logMessages) => { const anchorErrorIndex = logMessages.findIndex( (logMessage) => logMessage.includes("Program log: AnchorError caused by account:") ); if (anchorErrorIndex !== NOT_FOUND) { const programInvokeLog = logMessages[anchorErrorIndex - 2]; const programName = programInvokeLog?.split("Program ")[1]?.split(" invoke")[0]; const instructionHandlerLog = logMessages[anchorErrorIndex - 1]; const instructionHandlerName = instructionHandlerLog?.split("Instruction: ")[1]; const errorMessage = logMessages[anchorErrorIndex].split("Error Message: ")[1]?.split(".")[0]?.trim(); if (!errorMessage || !programName || !instructionHandlerName) { return null; } return `${programName}.${instructionHandlerName}: ${errorMessage}`; } const errorIndex = logMessages.findIndex((logMessage) => logMessage.includes("Program log: Error: ")); if (errorIndex !== NOT_FOUND) { const programInvokeLog = logMessages[errorIndex - 2]; const programName = programInvokeLog?.split("Program ")[1]?.split(" invoke")[0]; const instructionHandlerLog = logMessages[errorIndex - 1]; const instructionHandlerName = instructionHandlerLog?.split("Instruction: ")[1]; const errorMessage = logMessages[errorIndex].split("Program log: Error: ")[1]?.trim(); if (!errorMessage || !programName || !instructionHandlerName) { return null; } return `${programName}.${instructionHandlerName}: ${errorMessage}`; } const systemErrorIndex = logMessages.findIndex( (logMessage) => logMessage.includes(": ") && !logMessage.includes("Program log:") && !logMessage.includes("Program ") && !logMessage.includes(" consumed ") && !logMessage.includes(" success") && !logMessage.includes(" failed:") ); if (systemErrorIndex !== NOT_FOUND) { const programInvokeLog = logMessages[systemErrorIndex - 1]; const programName = programInvokeLog?.split("Program ")[1]?.split(" invoke")[0]; const [instructionName, ...errorParts] = logMessages[systemErrorIndex].split(": "); const errorMessage = errorParts.join(": "); if (!errorMessage || !programName || !instructionName) { return null; } const cleanErrorMessage = errorMessage.replace(/Address {[^}]*}/, "").replace(/\s+/g, " ").trim(); return `${programName}.${instructionName}: ${cleanErrorMessage}`; } return null; }; // src/lib/transactions.ts var signatureBytesToBase58String = (signatureBytes) => { return getBase58Decoder().decode(signatureBytes); }; var signatureBase58StringToBytes = (base58String) => { return new Uint8Array(getBase58Encoder().encode(base58String)); }; var sendTransactionFromInstructionsWithWalletAppFactory = (rpc) => { const sendTransactionFromInstructionsWithWalletApp = async ({ feePayer, instructions, abortSignal = null }) => { const { value: latestBlockhash } = await rpc.getLatestBlockhash().send({ abortSignal }); const transactionMessage = pipe( createTransactionMessage({ version: 0 }), (message) => setTransactionMessageFeePayerSigner(feePayer, message), (message) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, message), (message) => appendTransactionMessageInstructions(instructions, message) ); const signedTransaction = await signTransactionMessageWithSigners(transactionMessage); const signatureBytes = await rpc.sendTransaction(signedTransaction, { encoding: "base64", skipPreflight: false }).send({ abortSignal }); const signature = signatureBytesToBase58String(signatureBytes); return signature; }; return sendTransactionFromInstructionsWithWalletApp; }; var sendTransactionFromInstructionsFactory = (rpc, needsPriorityFees, supportsGetPriorityFeeEstimate, enableClientSideRetries, sendAndConfirmTransaction) => { const sendTransactionFromInstructions = async ({ feePayer, instructions, commitment = "confirmed", skipPreflight = true, maximumClientSideRetries = enableClientSideRetries ? DEFAULT_TRANSACTION_RETRIES : 0, abortSignal = null, timeout }) => { let placeholderBlockhash = { blockhash: "11111111111111111111111111111111", lastValidBlockHeight: 0n }; let transactionMessage = pipe( createTransactionMessage({ version: 0 }), (message) => setTransactionMessageLifetimeUsingBlockhash(placeholderBlockhash, message), (message) => setTransactionMessageFeePayerSigner(feePayer, message), (message) => appendTransactionMessageInstructions(instructions, message) ); if (needsPriorityFees) { const [priorityFeeEstimate, computeUnitEstimate] = await Promise.all([ getPriorityFeeEstimate(rpc, supportsGetPriorityFeeEstimate, transactionMessage, abortSignal), getComputeUnitEstimate(rpc, transactionMessage, abortSignal) ]); const setComputeUnitPriceInstruction = getSetComputeUnitPriceInstruction2({ microLamports: BigInt(priorityFeeEstimate) }); const setComputeUnitLimitInstruction = getSetComputeUnitLimitInstruction({ units: Math.ceil(computeUnitEstimate * 1.1) }); transactionMessage = appendTransactionMessageInstructions( [setComputeUnitPriceInstruction, setComputeUnitLimitInstruction], transactionMessage ); } const { value: latestBlockhash } = await rpc.getLatestBlockhash().send({ abortSignal }); transactionMessage = setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, transactionMessage); assertIsTransactionMessageWithBlockhashLifetime(transactionMessage); const signedTransaction = await signTransactionMessageWithSigners(transactionMessage); assertIsTransactionWithinSizeLimit2(signedTransaction); const blockhashSignedTransaction = signedTransaction; const signature = getSignatureFromTransaction(blockhashSignedTransaction); try { if (maximumClientSideRetries) { await sendTransactionWithRetries(sendAndConfirmTransaction, blockhashSignedTransaction, { maximumClientSideRetries, abortSignal, commitment, timeout }); } else { await sendAndConfirmTransaction(blockhashSignedTransaction, { commitment, skipPreflight }); } } catch (thrownObject) { const error = thrownObject; const commitmentToUse = commitment === "processed" ? "confirmed" : commitment; const transaction = await rpc.getTransaction(signature, { commitment: commitmentToUse, maxSupportedTransactionVersion: 0 }).send(); error.transaction = transaction; if (error.message.includes("custom program error") && transaction.meta?.logMessages) { const betterMessage = getErrorMessageFromLogs(transaction.meta.logMessages); if (betterMessage) { error.message = betterMessage; } } throw error; } return signature; }; return sendTransactionFromInstructions; }; // src/lib/wallets.ts import { createSignerFromKeyPair } from "@solana/kit"; import dotenv from "dotenv"; var expandHomeDirectoryPath = async (filePath) => { if (filePath[0] === "~") { const path = await import("path"); const home = process.env.HOME || null; if (home) { return path.join(home, filePath.slice(1)); } } return filePath; }; var ensureFileDoesNotExist = async (filePath) => { const { access } = await import("fs/promises"); try { await access(filePath); throw new Error(`File '${filePath}' already exists.`); } catch (error) { const fileError = error; if (fileError.message.includes("already exists")) { throw fileError; } if (fileError.code !== "ENOENT") { throw fileError; } } }; var createWalletFactory = (airdropIfRequired) => { const createWallet = async (options = {}) => { if (options.envVariableName && !options.envFileName) { options.envFileName = ".env"; } const { prefix = null, suffix = null, envFileName = null, envVariableName = DEFAULT_ENV_KEYPAIR_VARIABLE_NAME, fileName = null, airdropAmount = DEFAULT_AIRDROP_AMOUNT, commitment = null } = options; if (envFileName && fileName) { throw new Error("Cannot save to both envFileName and fileName. Please specify only one."); } if (fileName) { const filePath = await expandHomeDirectoryPath(fileName); await ensureFileDoesNotExist(filePath); } let keyPairSigner; if (envFileName || fileName) { const temporaryExtractableKeyPair = await grindKeyPair({ prefix, suffix, silenceGrindProgress: false, isPrivateKeyExtractable: "yes I understand the risk of extractable private keys and will delete this keypair shortly after saving it to a file" }); const temporaryExtractableKeyPairSigner = await createSignerFromKeyPair(temporaryExtractableKeyPair); if (envFileName) { await addKeyPairSignerToEnvFile(temporaryExtractableKeyPairSigner, envVariableName, envFileName); dotenv.config({ path: envFileName }); keyPairSigner = await loadWalletFromEnvironment(envVariableName); } if (fileName) { const { writeFile } = await import("fs/promises"); const filePath = await expandHomeDirectoryPath(fileName); const privateKeyJSON = await createJSONFromKeyPairSigner(temporaryExtractableKeyPairSigner); await writeFile(filePath, privateKeyJSON); const { loadWalletFromFile: loadWalletFromFile2 } = await import("./keypair-63XDBLMZ.js"); keyPairSigner = await loadWalletFromFile2(filePath); } } else { const keyPair = await grindKeyPair({ prefix, suffix }); keyPairSigner = await createSignerFromKeyPair(keyPair); } const finalKeyPairSigner = keyPairSigner; if (airdropAmount) { await airdropIfRequired(finalKeyPairSigner.address, airdropAmount, airdropAmount, commitment); } return finalKeyPairSigner; }; return createWallet; }; var createWalletsFactory = (createWallet) => { const createWallets = (amount, options = {}) => { const walletPromises = Array.from({ length: amount }, () => createWallet(options)); return Promise.all(walletPromises); }; return createWallets; }; // src/lib/tokens.ts import { generateKeyPairSigner, isSolanaError as isSolanaError2, some } from "@solana/kit"; import { extension as getExtensionData, findAssociatedTokenPda, getCreateAssociatedTokenInstructionAsync, getInitializeMetadataPointerInstruction, getInitializeMintInstruction, getInitializeTokenMetadataInstruction, getMintSize, getMintToInstruction, getUpdateTokenMetadataFieldInstruction, tokenMetadataField, getTransferCheckedInstruction, fetchMint, getCreateAssociatedTokenInstruction, getBurnCheckedInstruction as getBurnCheckedInstructionToken2022, getCloseAccountInstruction as getCloseAccountInstructionToken2022 } from "@solana-program/token-2022"; import { getCreateAccountInstruction, getTransferSolInstruction } from "@solana-program/system"; // src/lib/errors.ts var ensureError = (thrownObject) => { if (thrownObject instanceof Error) { return thrownObject; } return new Error(`Non-Error thrown: ${String(thrownObject)}`); }; // src/lib/tokens.ts var transferLamportsFactory = (sendTransactionFromInstructions) => { const transferLamports = async ({ source, destination, amount, commitment = "confirmed", skipPreflight = true, maximumClientSideRetries = 0, abortSignal = null }) => { const instruction = getTransferSolInstruction({ amount, destination, source }); const signature = await sendTransactionFromInstructions({ feePayer: source, instructions: [instruction], commitment, skipPreflight, maximumClientSideRetries, abortSignal }); return signature; }; return transferLamports; }; var transferTokensFactory = (getMint, sendTransactionFromInstructions) => { const transferTokens = async ({ sender, destination, mintAddress, amount, maximumClientSideRetries = 0, abortSignal = null, useTokenExtensions = true }) => { const tokenProgram = useTokenExtensions ? TOKEN_EXTENSIONS_PROGRAM : TOKEN_PROGRAM; const mint = await getMint(mintAddress); if (!mint) { throw new Error(`Mint not found: ${mintAddress}`); } const decimals = mint.data.decimals; const sourceAssociatedTokenAddress = await getTokenAccountAddress(sender.address, mintAddress, useTokenExtensions); const destinationAssociatedTokenAddress = await getTokenAccountAddress( destination, mintAddress, useTokenExtensions ); const createAssociatedTokenInstruction = getCreateAssociatedTokenInstruction({ ata: destinationAssociatedTokenAddress, mint: mintAddress, owner: destination, payer: sender, tokenProgram }); const transferInstruction = getTransferCheckedInstruction( { source: sourceAssociatedTokenAddress, mint: mintAddress, destination: destinationAssociatedTokenAddress, authority: sender.address, amount, decimals }, { programAddress: tokenProgram } ); const signature = await sendTransactionFromInstructions({ feePayer: sender, instructions: [createAssociatedTokenInstruction, transferInstruction], commitment: "confirmed", skipPreflight: true, maximumClientSideRetries, abortSignal }); return signature; }; return transferTokens; }; var getTokenAccountAddress = async (wallet, mint, useTokenExtensions = false) => { const tokenProgram = useTokenExtensions ? TOKEN_EXTENSIONS_PROGRAM : TOKEN_PROGRAM; const [address] = await findAssociatedTokenPda({ mint, owner: wallet, tokenProgram }); return address; }; var createClassicTokenMint = async ({ rpc, sendTransactionFromInstructions, mintAuthority, decimals }) => { const { getInitializeMintInstruction: getClassicInitializeMintInstruction, getMintSize: getClassicMintSize } = await import("@solana-program/token"); const mint = await generateKeyPairSigner(); const mintSpace = BigInt(getClassicMintSize()); const rent = await rpc.getMinimumBalanceForRentExemption(mintSpace).send(); const createAccountInstruction = getCreateAccountInstruction({ payer: mintAuthority, newAccount: mint, lamports: rent, space: mintSpace, programAddress: TOKEN_PROGRAM }); const initializeMintInstruction = getClassicInitializeMintInstruction( { mint: mint.address, decimals, mintAuthority: mintAuthority.address }, { programAddress: TOKEN_PROGRAM } ); const instructions = [createAccountInstruction, initializeMintInstruction]; await sendTransactionFromInstructions({ feePayer: mintAuthority, instructions }); return mint.address; }; var createTokenExtensionsMint = async ({ rpc, sendTransactionFromInstructions, mintAuthority, decimals, name, symbol, uri, additionalMetadata = {} }) => { const mint = await generateKeyPairSigner(); const additionalMetadataMap = additionalMetadata instanceof Map ? additionalMetadata : new Map(Object.entries(additionalMetadata)); const metadataPointerExtensionData = getExtensionData("MetadataPointer", { authority: some(mintAuthority.address), metadataAddress: some(mint.address) }); const tokenMetadataExtensionData = getExtensionData("TokenMetadata", { updateAuthority: some(mintAuthority.address), mint: mint.address, name, symbol, uri, additionalMetadata: additionalMetadataMap }); const spaceWithoutMetadata = BigInt(getMintSize([metadataPointerExtensionData])); const spaceWithMetadata = BigInt(getMintSize([metadataPointerExtensionData, tokenMetadataExtensionData])); const rent = await rpc.getMinimumBalanceForRentExemption(spaceWithMetadata).send(); const createAccountInstruction = getCreateAccountInstruction({ payer: mintAuthority, newAccount: mint, lamports: rent, space: spaceWithoutMetadata, programAddress: TOKEN_EXTENSIONS_PROGRAM }); const initializeMetadataPointerInstruction = getInitializeMetadataPointerInstruction({ mint: mint.address, authority: mintAuthority.address, metadataAddress: mint.address }); const initializeMintInstruction = getInitializeMintInstruction({ mint: mint.address, decimals, mintAuthority: mintAuthority.address }); const initializeTokenMetadataInstruction = getInitializeTokenMetadataInstruction({ metadata: mint.address, updateAuthority: mintAuthority.address, mint: mint.address, mintAuthority, name: tokenMetadataExtensionData.name, symbol: tokenMetadataExtensionData.symbol, uri: tokenMetadataExtensionData.uri }); const updateInstructions = Array.from(additionalMetadataMap.entries()).map(([key, value]) => { return getUpdateTokenMetadataFieldInstruction({ metadata: mint.address, updateAuthority: mintAuthority, field: tokenMetadataField("Key", [key]), value }); }); const instructions = [ createAccountInstruction, initializeMetadataPointerInstruction, initializeMintInstruction, initializeTokenMetadataInstruction, ...updateInstructions ]; await sendTransactionFromInstructions({ feePayer: mintAuthority, instructions }); return mint.address; }; var createTokenMintFactory = (rpc, sendTransactionFromInstructions) => { const createTokenMint = async ({ mintAuthority, decimals, name, symbol, uri, additionalMetadata = {}, useTokenExtensions = true }) => { if (useTokenExtensions) { if (!name || !symbol || !uri) { throw new Error("name, symbol, and uri are required when useTokenExtensions is true"); } return createTokenExtensionsMint({ rpc, sendTransactionFromInstructions, mintAuthority, decimals, name, symbol, uri, additionalMetadata }); } return createClassicTokenMint({ rpc, sendTransactionFromInstructions, mintAuthority, decimals }); }; return createTokenMint; }; var mintTokensFactory = (sendTransactionFromInstructions) => { const mintTokens = async (mintAddress, mintAuthority, amount, destination, useTokenExtensions = true) => { const tokenProgram = useTokenExtensions ? TOKEN_EXTENSIONS_PROGRAM : TOKEN_PROGRAM; const createAtaInstruction = await getCreateAssociatedTokenInstructionAsync({ payer: mintAuthority, mint: mintAddress, owner: destination, tokenProgram }); const associatedTokenAddress = await getTokenAccountAddress(destination, mintAddress, useTokenExtensions); const mintToInstruction = getMintToInstruction( { mint: mintAddress, token: associatedTokenAddress, mintAuthority: mintAuthority.address, amount }, { programAddress: tokenProgram } ); const transactionSignature = await sendTransactionFromInstructions({ feePayer: mintAuthority, instructions: [createAtaInstruction, mintToInstruction] }); return transactionSignature; }; return mintTokens; }; var getMintFactory = (rpc) => { const getMint = async (mintAddress, commitment = "confirmed") => { const mint = await fetchMint(rpc, mintAddress, { commitment }); return mint; }; return getMint; }; var getTokenAccountBalanceFactory = (rpc) => { const getTokenAccountBalance = async (options) => { const { wallet, mint, tokenAccount, useTokenExtensions } = options; if (!tokenAccount) { if (!wallet || !mint) { throw new Error("wallet and mint are required when tokenAccount is not provided"); } options.tokenAccount = await getTokenAccountAddress(wallet, mint, useTokenExtensions); } const result = await rpc.getTokenAccountBalance(options.tokenAccount).send(); const { amount, decimals, uiAmount, uiAmountString } = result.value; return { amount: BigInt(amount), decimals, uiAmount, uiAmountString }; }; return getTokenAccountBalance; }; var checkTokenAccountIsClosedFactory = (getTokenAccountBalance) => { const checkTokenAccountIsClosed = async (options) => { try { await getTokenAccountBalance(options); return false; } catch (thrownObject) { const error = thrownObject; if (error.message.includes("Invalid param: could not find account")) { return true; } throw error; } }; return checkTokenAccountIsClosed; }; var getTokenMetadataFactory = (rpc) => { const getTokenMetadata = async (mintAddress, commitment = "confirmed") => { let mint; try { mint = await fetchMint(rpc, mintAddress, { commitment }); } catch (error) { if (isSolanaError2(error)) { throw error; } throw new Error( `Mint not found: ${mintAddress}. Original error: ${error instanceof Error ? error.message : "Unknown error"}` ); } if (!mint) { throw new Error(`Mint not found: ${mintAddress}`); } const extensions = mint.data?.extensions?.__option === "Some" ? mint.data.extensions.value : []; const metadataPointerExtension = extensions.find((extension) => extension.__kind === "MetadataPointer"); if (!metadataPointerExtension || metadataPointerExtension.metadataAddress.__option === "None") { throw new Error(`No metadata pointer extension found for mint: ${mintAddress}`); } const metadataAddress = metadataPointerExtension.metadataAddress?.__option === "Some" ? metadataPointerExtension.metadataAddress.value : null; if (!metadataAddress) { throw new Error(`No metadata address found in metadata pointer extension for mint: ${mintAddress}`); } if (metadataAddress.toString() === mintAddress.toString()) { const tokenMetadata = extensions.find((extension) => extension.__kind === "TokenMetadata"); if (!tokenMetadata) { throw new Error(`TokenMetadata extension not found in mint account: ${mintAddress}`); } const additionalMetadata = {}; if (tokenMetadata.additionalMetadata instanceof Map) { for (const [key, value] of tokenMetadata.additionalMetadata) { additionalMetadata[key] = value; } } const updateAuthority = tokenMetadata.updateAuthority?.__option === "Some" ? tokenMetadata.updateAuthority.value : null; return { updateAuthority, mint: tokenMetadata.mint, name: tokenMetadata.name, symbol: tokenMetadata.symbol, uri: tokenMetadata.uri, additionalMetadata }; } else { const metadataAccount = await rpc.getAccountInfo(metadataAddress, { commitment }).send(); if (!metadataAccount.value) { throw new Error(`Metadata account not found: ${metadataAddress}`); } const data = metadataAccount.value.data; return parseTokenMetadataAccount(data); } }; const parseTokenMetadataAccount = (data) => { let offset = DISCRIMINATOR_SIZE; const updateAuthority = data.slice(offset, offset + PUBLIC_KEY_SIZE); offset += PUBLIC_KEY_SIZE; const mintAddressFromMetadata = data.slice(offset, offset + PUBLIC_KEY_SIZE); offset += PUBLIC_KEY_SIZE; const nameLength = new DataView(data.buffer, data.byteOffset).getUint32(offset, true); offset += LENGTH_FIELD_SIZE; const name = new TextDecoder("utf8").decode(data.slice(offset, offset + nameLength)); offset += nameLength; const symbolLength = new DataView(data.buffer, data.byteOffset).getUint32(offset, true); offset += LENGTH_FIELD_SIZE; const symbol = new TextDecoder("utf8").decode(data.slice(offset, offset + symbolLength)); offset += symbolLength; const uriLength = new DataView(data.buffer, data.byteOffset).getUint32(offset, true); offset += LENGTH_FIELD_SIZE; const uri = new TextDecoder("utf8").decode(data.slice(offset, offset + uriLength)); offset += uriLength; const additionalMetadataCount = new DataView(data.buffer, data.byteOffset).getUint32(offset, true); offset += LENGTH_FIELD_SIZE; const additionalMetadata = {}; for (let i = 0; i < additionalMetadataCount; i++) { const keyLength = new DataView(data.buffer, data.byteOffset).getUint32(offset, true); offset += LENGTH_FIELD_SIZE; const key = new TextDecoder("utf8").decode(data.slice(offset, offset + keyLength)); offset += keyLength; const valueLength = new DataView(data.buffer, data.byteOffset).getUint32(offset, true); offset += LENGTH_FIELD_SIZE; const value = new TextDecoder("utf8").decode(data.slice(offset, offset + valueLength)); offset += valueLength; additionalMetadata[key] = value; } return { updateAuthority, mint: mintAddressFromMetadata, name, symbol, uri, additionalMetadata }; }; return getTokenMetadata; }; var updateTokenMetadataFactory = (rpc, sendTransactionFromInstructions) => { const updateTokenMetadata = async ({ mintAddress, updateAuthority, name, symbol, uri, additionalMetadata, commitment = "confirmed" }) => { const mint = await fetchMint(rpc, mintAddress, { commitment }); if (!mint) { throw new Error(`Mint not found: ${mintAddress}`); } const extensions = mint.data?.extensions?.__option === "Some" ? mint.data.extensions.value : []; const metadataPointerExtension = extensions.find((extension) => extension.__kind === "MetadataPointer"); if (!metadataPointerExtension || metadataPointerExtension.metadataAddress.__option === "None") { throw new Error(`No metadata pointer extension found for mint: ${mintAddress}`); } const metadataAddress = metadataPointerExtension.metadataAddress?.__option === "Some" ? metadataPointerExtension.metadataAddress.value : null; if (!metadataAddress) { throw new Error(`No metadata address found in metadata pointer extension for mint: ${mintAddress}`); } const instructions = []; if (name !== void 0) { instructions.push( getUpdateTokenMetadataFieldInstruction({ metadata: metadataAddress, updateAuthority, field: tokenMetadataField("Name"), value: name }) ); } if (symbol !== void 0) { instructions.push( getUpdateTokenMetadataFieldInstruction({ metadata: metadataAddress, updateAuthority, field: tokenMetadataField("Symbol"), value: symbol }) ); } if (uri !== void 0) { instructions.push( getUpdateTokenMetadataFieldInstruction({ metadata: metadataAddress, updateAuthority, field: tokenMetadataField("Uri"), value: uri }) ); } if (additionalMetadata) { for (const [key, value] of Object.entries(additionalMetadata)) { instructions.push( getUpdateTokenMetadataFieldInstruction({ metadata: metadataAddress, updateAuthority, field: tokenMetadataField("Key", [key]), value }) ); } } if (instructions.length === 0) { throw new Error("No metadata fields provided to update"); } const signature = await sendTransactionFromInstructions({ feePayer: updateAuthority, instructions, commitment }); return signature; }; return updateTokenMetadata; }; var watchTokenBalanceFactory = (rpc, rpcSubscriptions) => { const getTokenAccountBalance = getTokenAccountBalanceFactory(rpc); const watchTokenBalance = (ownerAddress, mintAddress, callback, useTokenExtensions = true) => { const abortController = new AbortController(); let lastUpdateSlot = -1n; const fetchInitialBalance = async () => { try { const balance = await getTokenAccountBalance({ wallet: ownerAddress, mint: mintAddress, useTokenExtensions }); callback(null, balance); } catch (error) { if (error?.name !== "AbortError") { callback(null, { amount: 0n, decimals: 9, uiAmount: 0, uiAmountString: "0" }); } } }; const subscribeToUpdates = async () => { try { const tokenAccountAddress = await getTokenAccountAddress(ownerAddress, mintAddress, useTokenExtensions); const accountInfoNotifications = await rpcSubscriptions.accountNotifications(tokenAccountAddress).subscribe({ abortSignal: abortController.signal }); try { for await (const { context: { slot } } of accountInfoNotifications) { if (slot < lastUpdateSlot) { continue; } lastUpdateSlot = slot; try { const balance = await getTokenAccountBalance({ wallet: ownerAddress, mint: mintAddress, useTokenExtensions }); callback(null, balance); } catch (balanceError) { if (balanceError?.name !== "AbortError") { callback(null, { amount: 0n, decimals: 9, uiAmount: 0, uiAmountString: "0" }); } } } } catch (thrownObject) { const error = ensureError(thrownObject); if (error.name !== "AbortError") { callback(error, null); } } } catch (thrownObject) { const error = ensureError(thrownObject); if (error.name !== "AbortError") { callback(error, null); } } }; fetchInitialBalance(); subscribeToUpdates(); return () => { abortController.abort(); }; }; return watchTokenBalance; }; var burnTokensFactory = (getMint, sendTransactionFromInstructions) => { const burnTokens = async ({ mintAddress, owner, amount, useTokenExtensions = true, skipPreflight = true, maximumClientSideRetries = 0, abortSignal = null }) => { const tokenProgram = useTokenExtensions ? TOKEN_EXTENSIONS_PROGRAM : TOKEN_PROGRAM; const mint = await getMint(mintAddress); if (!mint) { throw new Error(`Mint not found: ${mintAddress}`); } const decimals = mint.data.decimals; const tokenAccount = await getTokenAccountAddress(owner.address, mintAddress, useTokenExtensions); let burnInstruction; if (useTokenExtensions) { burnInstruction = getBurnCheckedInstructionToken2022({ account: tokenAccount, mint: mintAddress, authority: owner, amount, decimals }); } else { const { getBurnCheckedInstruction: getBurnCheckedInstructionClassic } = await import("@solana-program/token"); burnInstruction = getBurnCheckedInstructionClassic( { account: tokenAccount, mint: mintAddress, authority: owner, amount, decimals }, { programAddress: tokenProgram } ); } const signature = await sendTransactionFromInstructions({ feePayer: owner, instructions: [burnInstruction], commitment: "confirmed", skipPreflight, maximumClientSideRetries, abortSignal }); return signature; }; return burnTokens; }; var closeTokenAccountFactory = (sendTransactionFromInstructions) => { const closeTokenAccount = async ({ owner, tokenAccount, wallet, mint, destination, useTokenExtensions = true, skipPreflight = true, maximumClientSideRetries = 0, abortSignal = null }) => { let accountToClose; if (tokenAccount) { accountToClose = tokenAccount; } else if (wallet && mint) { accountToClose = await getTokenAccountAddress(wallet, mint, useTokenExtensions); } else { throw new Error("Either tokenAccount or both wallet and mint must be provided"); } const rentDestination = destination || owner.address; let closeInstruction; if (useTokenExtensions) { closeInstruction = getCloseAccountInstructionToken2022({ account: accountToClose, destination: rentDestination, owner }); } else { const { getCloseAccountInstruction: getCloseAccountInstructionClassic } = await import("@solana-program/token"); closeInstruction = getCloseAccountInstructionClassic({ account: accountToClose, destination: rentDestination, owner }); } const signature = await sendTransactionFromInstructions({ feePayer: owner, instructions: [closeInstruction], commitment: "confirmed", skipPreflight, maximumClientSideRetries, abortSignal }); return signature; }; return closeTokenAccount; }; var getTokenAccountsFactory = (rpc) => { const getTokenAccounts = async (walletAddress, excludeZeroBalance = false) => { const [classicTokenProgramResponse, tokenExtensionsProgramResponse] = await Promise.all([ rpc.getTokenAccountsByOwner( walletAddress, { programId: TOKEN_PROGRAM }, { encoding: "jsonParsed" } ).send(), rpc.getTokenAccountsByOwner( walletAddress, { programId: TOKEN_EXTENSIONS_PROGRAM }, { encoding: "jsonParsed" } ).send() ]); const allAccounts = [...classicTokenProgramResponse.value, ...tokenExtensionsProgramResponse.value]; if (excludeZeroBalance) { return allAccounts.filter((account) => { const amount = account.account.data.parsed?.info?.tokenAmount?.amount; return amount && BigInt(amount) > 0n; }); } return allAccounts; }; return getTokenAccounts; }; // src/lib/explorer.ts var getExplorerLinkFactory = (clusterNameOrURL) => { const getExplorerLink = (linkType, id) => { const searchParams = {}; if (KNOWN_CLUSTER_NAMES.includes(clusterNameOrURL)) { const clusterConfig = CLUSTERS[clusterNameOrURL]; const clusterDetails = getClusterDetailsFromClusterConfig(clusterNameOrURL, clusterConfig); if (!clusterDetails.features.isExplorerDefault) { if (clusterDetails.features.isNameKnownToSolanaExplorer) { searchParams["cluster"] = clusterNameOrURL; } else { searchParams["cluster"] = "custom"; } if (!clusterDetails.features.isNameKnownToSolanaExplorer && clusterNameOrURL !== "localnet") { searchParams["customUrl"] = clusterDetails.httpURL; } } } else { if (checkIsValidURL(clusterNameOrURL)) { searchParams["cluster"] = "custom"; searchParams["customUrl"] = clusterNameOrURL; } else { throw new Error(`Unsupported cluster name: ${clusterNameOrURL}`); } } let baseUrl = ""; if (linkType === "address") { baseUrl = `https://explorer.solana.com/address/${id}`; } if (linkType === "transaction" || linkType === "tx") { baseUrl = `https://explorer.solana.com/tx/${id}`; } if (linkType === "block") { baseUrl = `https://explorer.solana.com/block/${id}`; } return encodeURL(baseUrl, searchParams); }; return getExplorerLink; }; // src/lib/sol.ts import { airdropFactory } from "@solana/kit"; var ensureError2 = (thrownObject) => { if (thrownObject instanceof Error) { return thrownObject; } return new Error(`Non-Error thrown: ${String(thrownObject)}`); }; var getLamportBalanceFactory = (rpc) => { const getLamportBalance = async (address, commitment = "finalized") => { const getLamportBalanceResponse = await rpc.getBalance(address, { commitment }).send(); return getLamportBalanceResponse.value; }; return getLamportBalance; }; var airdropIfRequiredFactory = (rpc, rpcSubscriptions) => { const getLamportBalance = getLamportBalanceFactory(rpc); const airdrop = airdropFactory({ rp