UNPKG

solana-token-extension-boost

Version:

SDK for Solana Token Extensions with wallet adapter support

440 lines (439 loc) 20.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TokenGroupExtension = exports.TokenGroupMemberStatus = exports.TokenGroupMemberExtensionType = exports.TokenGroupExtensionType = exports.GroupPointerExtensionType = void 0; exports.createInitializeGroupPointerInstruction = createInitializeGroupPointerInstruction; exports.createInitializeTokenGroupInstruction = createInitializeTokenGroupInstruction; exports.createUpdateTokenGroupInstruction = createUpdateTokenGroupInstruction; exports.createInitializeTokenGroupMemberInstruction = createInitializeTokenGroupMemberInstruction; exports.createUpdateTokenGroupMemberInstruction = createUpdateTokenGroupMemberInstruction; const web3_js_1 = require("@solana/web3.js"); const spl_token_1 = require("@solana/spl-token"); // Define GroupPointer extension type (not yet available in standard ExtensionType) exports.GroupPointerExtensionType = 20; // Assumed ID for GroupPointer // Define TokenGroup and TokenGroupMember extension types // These are custom extension types for Token-2022 that may not be in the standard ExtensionType enum yet exports.TokenGroupExtensionType = 23; // ID for TokenGroup exports.TokenGroupMemberExtensionType = 24; // ID for TokenGroupMember /** * Token Group Member Status Flags */ var TokenGroupMemberStatus; (function (TokenGroupMemberStatus) { TokenGroupMemberStatus[TokenGroupMemberStatus["NONE"] = 0] = "NONE"; TokenGroupMemberStatus[TokenGroupMemberStatus["ACTIVE"] = 1] = "ACTIVE"; TokenGroupMemberStatus[TokenGroupMemberStatus["FROZEN"] = 2] = "FROZEN"; })(TokenGroupMemberStatus || (exports.TokenGroupMemberStatus = TokenGroupMemberStatus = {})); /** * Create instruction to initialize group pointer for a token * @param mint - Mint address * @param groupMint - Mint address of the token group * @param programId - Token Extension Program ID * @returns Instruction to initialize group pointer */ function createInitializeGroupPointerInstruction(mint, groupMint, programId = spl_token_1.TOKEN_2022_PROGRAM_ID) { return { programId, keys: [ { pubkey: mint, isSigner: false, isWritable: true }, { pubkey: groupMint, isSigner: false, isWritable: false }, ], data: Buffer.from([exports.GroupPointerExtensionType]), // Mock data }; } /** * Create instruction to initialize a mint as a token group * @param mint - Public key of the mint account * @param payer - Public key of the payer * @param groupAuthority - Public key of the authority for managing group * @param updateAuthority - (Optional) Public key of the authority for updating metadata * @param name - Group name (max 32 bytes) * @param symbol - Group symbol (max 10 bytes) * @param programId - SPL Token program ID * @returns TransactionInstruction */ function createInitializeTokenGroupInstruction(mint, payer, groupAuthority, updateAuthority = null, name, symbol, programId = spl_token_1.TOKEN_2022_PROGRAM_ID) { const keys = [ { pubkey: mint, isSigner: false, isWritable: true }, { pubkey: payer, isSigner: true, isWritable: true }, { pubkey: groupAuthority, isSigner: false, isWritable: false }, ]; if (updateAuthority) { keys.push({ pubkey: updateAuthority, isSigner: false, isWritable: false }); } // Validate inputs if (name.length > 32) { throw new Error('Group name must be 32 characters or less'); } if (symbol.length > 10) { throw new Error('Group symbol must be 10 characters or less'); } // Create data buffer const nameBuffer = Buffer.alloc(32); const symbolBuffer = Buffer.alloc(10); Buffer.from(name).copy(nameBuffer); Buffer.from(symbol).copy(symbolBuffer); const dataLayout = Buffer.concat([ Buffer.from([ exports.TokenGroupExtensionType & 0xff, (exports.TokenGroupExtensionType >> 8) & 0xff, (exports.TokenGroupExtensionType >> 16) & 0xff, (exports.TokenGroupExtensionType >> 24) & 0xff, ]), nameBuffer, symbolBuffer, Buffer.from([updateAuthority ? 1 : 0]), ]); return new web3_js_1.TransactionInstruction({ programId, keys, data: dataLayout, }); } /** * Create instruction to update a token group * @param mint - Public key of the mint account * @param updateAuthority - Public key of the update authority * @param name - New group name (max 32 bytes) * @param symbol - New group symbol (max 10 bytes) * @param programId - SPL Token program ID * @returns TransactionInstruction */ function createUpdateTokenGroupInstruction(mint, updateAuthority, name, symbol, programId = spl_token_1.TOKEN_2022_PROGRAM_ID) { // Validate inputs if (name.length > 32) { throw new Error('Group name must be 32 characters or less'); } if (symbol.length > 10) { throw new Error('Group symbol must be 10 characters or less'); } // Create data buffer const nameBuffer = Buffer.alloc(32); const symbolBuffer = Buffer.alloc(10); Buffer.from(name).copy(nameBuffer); Buffer.from(symbol).copy(symbolBuffer); const dataLayout = Buffer.concat([ Buffer.from([ exports.TokenGroupExtensionType & 0xff, (exports.TokenGroupExtensionType >> 8) & 0xff, (exports.TokenGroupExtensionType >> 16) & 0xff, (exports.TokenGroupExtensionType >> 24) & 0xff, 1, // Update operation ]), nameBuffer, symbolBuffer, ]); return new web3_js_1.TransactionInstruction({ programId, keys: [ { pubkey: mint, isSigner: false, isWritable: true }, { pubkey: updateAuthority, isSigner: true, isWritable: false }, ], data: dataLayout, }); } /** * Create instruction to initialize a mint as a token group member * @param memberMint - Public key of the member mint account * @param groupMint - Public key of the group mint account * @param payer - Public key of the payer * @param memberAuthority - Public key of the authority for managing member status * @param programId - SPL Token program ID * @returns TransactionInstruction */ function createInitializeTokenGroupMemberInstruction(memberMint, groupMint, payer, memberAuthority, programId = spl_token_1.TOKEN_2022_PROGRAM_ID) { return new web3_js_1.TransactionInstruction({ programId, keys: [ { pubkey: memberMint, isSigner: false, isWritable: true }, { pubkey: groupMint, isSigner: false, isWritable: false }, { pubkey: payer, isSigner: true, isWritable: true }, { pubkey: memberAuthority, isSigner: false, isWritable: false }, ], data: Buffer.from([ exports.TokenGroupMemberExtensionType & 0xff, (exports.TokenGroupMemberExtensionType >> 8) & 0xff, (exports.TokenGroupMemberExtensionType >> 16) & 0xff, (exports.TokenGroupMemberExtensionType >> 24) & 0xff, ]), }); } /** * Create instruction to update a token group member's status * @param memberMint - Public key of the member mint account * @param memberAuthority - Public key of the member authority * @param status - New status for the member * @param programId - SPL Token program ID * @returns TransactionInstruction */ function createUpdateTokenGroupMemberInstruction(memberMint, memberAuthority, status, programId = spl_token_1.TOKEN_2022_PROGRAM_ID) { return new web3_js_1.TransactionInstruction({ programId, keys: [ { pubkey: memberMint, isSigner: false, isWritable: true }, { pubkey: memberAuthority, isSigner: true, isWritable: false }, ], data: Buffer.from([ exports.TokenGroupMemberExtensionType & 0xff, (exports.TokenGroupMemberExtensionType >> 8) & 0xff, (exports.TokenGroupMemberExtensionType >> 16) & 0xff, (exports.TokenGroupMemberExtensionType >> 24) & 0xff, 1, // Update operation status & 0xff, (status >> 8) & 0xff, (status >> 16) & 0xff, (status >> 24) & 0xff, ]), }); } /** * Class for managing token groups */ class TokenGroupExtension { /** * Create a new TokenGroupExtension instance * @param connection - Connection to Solana cluster * @param mint - Public key of the token group mint */ constructor(connection, mint) { this.connection = connection; this.mint = mint; } /** * Get token group information * @returns Promise resolving to token group information */ async getTokenGroupInfo() { try { const mintInfo = await (0, spl_token_1.getMint)(this.connection, this.mint, 'confirmed', spl_token_1.TOKEN_2022_PROGRAM_ID); // Extract token group data from TLV if (mintInfo.tlvData) { let offset = 0; while (offset < mintInfo.tlvData.length) { if (offset + 8 > mintInfo.tlvData.length) break; const type = mintInfo.tlvData.readUInt32LE(offset); const length = mintInfo.tlvData.readUInt32LE(offset + 4); if (type === exports.TokenGroupExtensionType) { if (offset + 8 + length > mintInfo.tlvData.length) break; // Parse name (32 bytes) const nameBytes = mintInfo.tlvData.slice(offset + 8, offset + 8 + 32); const name = nameBytes.toString('utf8').replace(/\0/g, ''); // Parse symbol (10 bytes) const symbolBytes = mintInfo.tlvData.slice(offset + 8 + 32, offset + 8 + 42); const symbol = symbolBytes.toString('utf8').replace(/\0/g, ''); // Parse authorities if they exist let groupAuthority = null; let updateAuthority = null; if (length >= 42 + 32) { const groupAuthorityBytes = mintInfo.tlvData.slice(offset + 8 + 42, offset + 8 + 42 + 32); groupAuthority = new web3_js_1.PublicKey(groupAuthorityBytes); } if (length >= 42 + 64) { const updateAuthorityFlag = mintInfo.tlvData.readUInt8(offset + 8 + 42); if (updateAuthorityFlag === 1 && length >= 42 + 65) { const updateAuthorityBytes = mintInfo.tlvData.slice(offset + 8 + 43, offset + 8 + 43 + 32); updateAuthority = new web3_js_1.PublicKey(updateAuthorityBytes); } } return { name, symbol, groupAuthority, updateAuthority, }; } offset += 8 + length; } } throw new Error('Token group data not found'); } catch (error) { console.error('Error getting token group info:', error); throw error; } } /** * Check if a mint is a member of this token group * @param memberMint - Public key of the potential member mint * @returns Promise resolving to boolean indicating membership */ async isMember(memberMint) { try { const mintInfo = await (0, spl_token_1.getMint)(this.connection, memberMint, 'confirmed', spl_token_1.TOKEN_2022_PROGRAM_ID); // Check the TLV data for TokenGroupMember extension if (mintInfo.tlvData) { let offset = 0; while (offset < mintInfo.tlvData.length) { if (offset + 8 > mintInfo.tlvData.length) break; const type = mintInfo.tlvData.readUInt32LE(offset); const length = mintInfo.tlvData.readUInt32LE(offset + 4); if (type === exports.TokenGroupMemberExtensionType) { // Member found, check if it belongs to this group if (offset + 8 + 32 > mintInfo.tlvData.length) break; const groupMintBytes = mintInfo.tlvData.slice(offset + 8, offset + 8 + 32); const groupMint = new web3_js_1.PublicKey(groupMintBytes); return groupMint.equals(this.mint); } offset += 8 + length; } } return false; // Not a member } catch (error) { console.error('Error checking group membership:', error); return false; } } /** * Get member status * @param memberMint - Public key of the member mint * @returns Promise resolving to member status */ async getMemberStatus(memberMint) { try { const mintInfo = await (0, spl_token_1.getMint)(this.connection, memberMint, 'confirmed', spl_token_1.TOKEN_2022_PROGRAM_ID); // Check the TLV data for TokenGroupMember extension if (mintInfo.tlvData) { let offset = 0; while (offset < mintInfo.tlvData.length) { if (offset + 8 > mintInfo.tlvData.length) break; const type = mintInfo.tlvData.readUInt32LE(offset); const length = mintInfo.tlvData.readUInt32LE(offset + 4); if (type === exports.TokenGroupMemberExtensionType) { // Member found, check if it belongs to this group if (offset + 8 + 32 > mintInfo.tlvData.length) break; const groupMintBytes = mintInfo.tlvData.slice(offset + 8, offset + 8 + 32); const groupMint = new web3_js_1.PublicKey(groupMintBytes); if (groupMint.equals(this.mint)) { // This is a member of our group, get status if (offset + 8 + 32 + 4 > mintInfo.tlvData.length) break; const status = mintInfo.tlvData.readUInt32LE(offset + 8 + 32); return status; } } offset += 8 + length; } } return TokenGroupMemberStatus.NONE; // Not a member or no status } catch (error) { console.error('Error getting member status:', error); return TokenGroupMemberStatus.NONE; } } /** * Create instructions to create a new token group * @param connection - Connection to Solana cluster * @param payer - Public key of the fee payer * @param mintAuthority - Mint authority * @param groupAuthority - Group authority * @param updateAuthority - (Optional) Update authority * @param name - Group name * @param symbol - Group symbol * @param decimals - Number of decimals * @returns Instructions, signers, and mint address */ static async createTokenGroupInstructions(connection, payer, mintAuthority, groupAuthority, updateAuthority, name, symbol, decimals = 6) { // Generate a new keypair for the mint const mintKeypair = web3_js_1.Keypair.generate(); const mint = mintKeypair.publicKey; // Calculate the space needed for the mint with TokenGroup extension const mintLen = (0, spl_token_1.getMintLen)([exports.TokenGroupExtensionType]); const lamports = await connection.getMinimumBalanceForRentExemption(mintLen); // Create instructions array const instructions = []; // Add instruction to create account instructions.push(web3_js_1.SystemProgram.createAccount({ fromPubkey: payer, newAccountPubkey: mint, space: mintLen, lamports, programId: spl_token_1.TOKEN_2022_PROGRAM_ID, })); // Add instruction to initialize token group instructions.push(createInitializeTokenGroupInstruction(mint, payer, groupAuthority, updateAuthority, name, symbol, spl_token_1.TOKEN_2022_PROGRAM_ID)); // Add instruction to initialize mint instructions.push((0, spl_token_1.createInitializeMintInstruction)(mint, decimals, mintAuthority, null, // freeze authority spl_token_1.TOKEN_2022_PROGRAM_ID)); return { instructions, signers: [mintKeypair], mint, }; } /** * Create instructions to create a new token group member * @param connection - Connection to Solana cluster * @param payer - Public key of the fee payer * @param mintAuthority - Mint authority * @param memberAuthority - Member authority * @param decimals - Number of decimals * @returns Instructions, signers, and mint address */ async createTokenGroupMemberInstructions(payer, mintAuthority, memberAuthority, decimals = 6) { // Generate a new keypair for the member mint const mintKeypair = web3_js_1.Keypair.generate(); const memberMint = mintKeypair.publicKey; // Calculate the space needed for the mint with TokenGroupMember extension const mintLen = (0, spl_token_1.getMintLen)([exports.TokenGroupMemberExtensionType]); const lamports = await this.connection.getMinimumBalanceForRentExemption(mintLen); // Create instructions array const instructions = []; // Add instruction to create account instructions.push(web3_js_1.SystemProgram.createAccount({ fromPubkey: payer, newAccountPubkey: memberMint, space: mintLen, lamports, programId: spl_token_1.TOKEN_2022_PROGRAM_ID, })); // Add instruction to initialize token group member instructions.push(createInitializeTokenGroupMemberInstruction(memberMint, this.mint, payer, memberAuthority, spl_token_1.TOKEN_2022_PROGRAM_ID)); // Add instruction to initialize mint instructions.push((0, spl_token_1.createInitializeMintInstruction)(memberMint, decimals, mintAuthority, null, // freeze authority spl_token_1.TOKEN_2022_PROGRAM_ID)); return { instructions, signers: [mintKeypair], mint: memberMint, }; } /** * Create instruction to update token group information * @param updateAuthority - Public key of the update authority * @param name - New group name * @param symbol - New group symbol * @returns Instruction to update token group */ createUpdateTokenGroupInstruction(updateAuthority, name, symbol) { return createUpdateTokenGroupInstruction(this.mint, updateAuthority, name, symbol, spl_token_1.TOKEN_2022_PROGRAM_ID); } /** * Create instruction to update a member's status * @param memberMint - Public key of the member mint * @param memberAuthority - Public key of the member authority * @param status - New status for the member * @returns Instruction to update member status */ createUpdateMemberStatusInstruction(memberMint, memberAuthority, status) { return createUpdateTokenGroupMemberInstruction(memberMint, memberAuthority, status, spl_token_1.TOKEN_2022_PROGRAM_ID); } /** * List all members of this token group * @returns Promise resolving to array of member mints */ async listMembers() { // This is a simplified implementation that would need to be enhanced // with a proper query mechanism to find all members efficiently // In a real implementation, you would use an indexer or RPC method to find all members // For now, return an empty array as this requires specific chain indexing return []; } } exports.TokenGroupExtension = TokenGroupExtension;