UNPKG

solana-token-extension-boost

Version:

SDK for Solana Token Extensions with wallet adapter support

324 lines (323 loc) 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MetadataPointerToken = void 0; const web3_js_1 = require("@solana/web3.js"); const spl_token_1 = require("@solana/spl-token"); const spl_token_metadata_1 = require("@solana/spl-token-metadata"); const token_1 = require("../../core/token"); class MetadataPointerToken extends token_1.Token { constructor(connection, mint, metadata) { super(connection, mint); this.metadata = metadata; } /** * Create instructions to make a new MetadataPointerToken * * @param connection - Solana connection * @param payer - Public key of the payer * @param params - Parameters for token creation * @returns Instructions, signers, and mint address */ static async createInstructions(connection, payer, params) { const { decimals, mintAuthority, metadata } = params; if (!metadata.name || metadata.name.length > 32) { throw new Error("Metadata name is required and must be 32 characters or less"); } if (!metadata.symbol || metadata.symbol.length > 10) { throw new Error("Metadata symbol is required and must be 10 characters or less"); } if (!metadata.uri || metadata.uri.length > 200) { throw new Error("Metadata URI is required and must be 200 characters or less"); } const mintKeypair = web3_js_1.Keypair.generate(); const tokenMetadata = { mint: mintKeypair.publicKey, name: metadata.name, symbol: metadata.symbol, uri: metadata.uri, additionalMetadata: Object.entries(metadata.additionalMetadata || {}).map(([key, value]) => [key, value]), }; const mintLen = (0, spl_token_1.getMintLen)([spl_token_1.ExtensionType.MetadataPointer]); const metadataLen = spl_token_1.TYPE_SIZE + spl_token_1.LENGTH_SIZE + (0, spl_token_metadata_1.pack)(tokenMetadata).length; const lamports = await connection.getMinimumBalanceForRentExemption(mintLen + metadataLen); const instructions = [ web3_js_1.SystemProgram.createAccount({ fromPubkey: payer, newAccountPubkey: mintKeypair.publicKey, space: mintLen, lamports, programId: spl_token_1.TOKEN_2022_PROGRAM_ID, }), (0, spl_token_1.createInitializeMetadataPointerInstruction)(mintKeypair.publicKey, payer, mintKeypair.publicKey, spl_token_1.TOKEN_2022_PROGRAM_ID), (0, spl_token_1.createInitializeMintInstruction)(mintKeypair.publicKey, decimals, mintAuthority, null, spl_token_1.TOKEN_2022_PROGRAM_ID), (0, spl_token_metadata_1.createInitializeInstruction)({ programId: spl_token_1.TOKEN_2022_PROGRAM_ID, metadata: mintKeypair.publicKey, updateAuthority: payer, mint: mintKeypair.publicKey, mintAuthority: mintAuthority, name: metadata.name, symbol: metadata.symbol, uri: metadata.uri, }) ]; if (metadata.additionalMetadata) { for (const [key, value] of Object.entries(metadata.additionalMetadata)) { if (key.length === 0 || value.length === 0) { continue; } instructions.push((0, spl_token_metadata_1.createUpdateFieldInstruction)({ programId: spl_token_1.TOKEN_2022_PROGRAM_ID, metadata: mintKeypair.publicKey, updateAuthority: payer, field: key, value: value, })); } } return { instructions, signers: [mintKeypair], mint: mintKeypair.publicKey }; } static async create(connection, payer, params) { const { instructions, signers, mint } = await this.createInstructions(connection, payer.publicKey, params); const transaction = new web3_js_1.Transaction().add(...instructions); await (0, web3_js_1.sendAndConfirmTransaction)(connection, transaction, [payer, ...signers]); return new MetadataPointerToken(connection, mint, params.metadata); } static async fromMint(connection, mint) { try { const tokenMetadata = await (0, spl_token_1.getTokenMetadata)(connection, mint, "confirmed", spl_token_1.TOKEN_2022_PROGRAM_ID); if (!tokenMetadata) { return null; } const additionalMetadata = {}; if (tokenMetadata.additionalMetadata) { for (const [key, value] of tokenMetadata.additionalMetadata) { additionalMetadata[key] = value; } } const metadata = { name: tokenMetadata.name, symbol: tokenMetadata.symbol, uri: tokenMetadata.uri, additionalMetadata }; return new MetadataPointerToken(connection, mint, metadata); } catch (error) { console.error("Error loading metadata token:", error); return null; } } async getMetadataPointer() { try { const mintInfo = await (0, spl_token_1.getMint)(this.connection, this.mint, "confirmed", spl_token_1.TOKEN_2022_PROGRAM_ID); const pointerState = (0, spl_token_1.getMetadataPointerState)(mintInfo); if (!pointerState || !pointerState.authority || !pointerState.metadataAddress) { return null; } return { authority: pointerState.authority, metadataAddress: pointerState.metadataAddress }; } catch (error) { throw new Error(`Failed to get metadata pointer: ${error}`); } } async getTokenMetadata() { const metadata = await (0, spl_token_1.getTokenMetadata)(this.connection, this.mint, "confirmed", spl_token_1.TOKEN_2022_PROGRAM_ID); if (!metadata) { throw new Error("No metadata found for this token"); } return metadata; } /** * Create instruction to update a metadata field * * @param authority - Update authority public key * @param field - Field name to update * @param value - New value for the field * @returns Transaction instruction */ createUpdateMetadataFieldInstruction(authority, field, value) { return (0, spl_token_metadata_1.createUpdateFieldInstruction)({ programId: spl_token_1.TOKEN_2022_PROGRAM_ID, metadata: this.mint, updateAuthority: authority, field, value, }); } async updateMetadataField(authority, field, value) { try { const instruction = this.createUpdateMetadataFieldInstruction(authority.publicKey, field, value); const transaction = new web3_js_1.Transaction().add(instruction); const signature = await (0, web3_js_1.sendAndConfirmTransaction)(this.connection, transaction, [authority]); const metadata = await this.getTokenMetadata(); return { signature, metadata, }; } catch (error) { throw new Error(`Failed to update metadata field: ${error}`); } } async removeMetadataField(authority, key) { if (!key || key.length === 0) { throw new Error("Field key cannot be empty"); } try { const transaction = new web3_js_1.Transaction(); transaction.add((0, spl_token_metadata_1.createRemoveKeyInstruction)({ programId: spl_token_1.TOKEN_2022_PROGRAM_ID, metadata: this.mint, updateAuthority: authority.publicKey, key: key, idempotent: true, })); const signature = await (0, web3_js_1.sendAndConfirmTransaction)(this.connection, transaction, [ authority, ]); const updatedMetadata = await this.getTokenMetadata(); return { signature, metadata: updatedMetadata }; } catch (error) { throw new Error(`Failed to remove metadata field: ${error}`); } } async updateMetadataPointer(authority, newMetadataAddress) { try { const transaction = new web3_js_1.Transaction().add((0, spl_token_1.createUpdateMetadataPointerInstruction)(this.mint, authority.publicKey, newMetadataAddress, [], spl_token_1.TOKEN_2022_PROGRAM_ID)); return await (0, web3_js_1.sendAndConfirmTransaction)(this.connection, transaction, [ authority, ]); } catch (error) { throw new Error(`Failed to update metadata pointer: ${error}`); } } async updateMetadataBatch(authority, fields) { if (Object.keys(fields).length === 0) { throw new Error("No fields provided for update"); } try { let totalSize = 0; for (const [field, value] of Object.entries(fields)) { totalSize += spl_token_1.LENGTH_SIZE + field.length + value.length; } const additionalRent = await this.connection.getMinimumBalanceForRentExemption(totalSize); const transaction = new web3_js_1.Transaction(); transaction.add(web3_js_1.SystemProgram.transfer({ fromPubkey: authority.publicKey, toPubkey: this.mint, lamports: additionalRent, })); for (const [field, value] of Object.entries(fields)) { if (field.length === 0 || value.length === 0) { continue; } transaction.add((0, spl_token_metadata_1.createUpdateFieldInstruction)({ programId: spl_token_1.TOKEN_2022_PROGRAM_ID, metadata: this.mint, updateAuthority: authority.publicKey, field: field, value: value, })); } const signature = await (0, web3_js_1.sendAndConfirmTransaction)(this.connection, transaction, [ authority, ]); const updatedMetadata = await this.getTokenMetadata(); return { signature, metadata: updatedMetadata }; } catch (error) { throw new Error(`Failed to update metadata batch: ${error}`); } } async getNFTMetadata() { const tokenMetadata = await this.getTokenMetadata(); if (!tokenMetadata.uri) { throw new Error("No metadata URI found for this token"); } try { const response = await fetch(tokenMetadata.uri); if (!response.ok) { throw new Error(`Failed to fetch metadata: ${response.statusText}`); } return await response.json(); } catch (error) { throw new Error(`Error fetching off-chain metadata: ${error}`); } } getMetadataConfig() { return { ...this.metadata }; } async updateMetadataAuthority(currentAuthority, newAuthority) { try { const transaction = new web3_js_1.Transaction().add((0, spl_token_metadata_1.createUpdateAuthorityInstruction)({ programId: spl_token_1.TOKEN_2022_PROGRAM_ID, metadata: this.mint, oldAuthority: currentAuthority.publicKey, newAuthority: newAuthority, })); return await (0, web3_js_1.sendAndConfirmTransaction)(this.connection, transaction, [ currentAuthority, ]); } catch (error) { throw new Error(`Failed to update metadata authority: ${error}`); } } async getMetadataField(field) { try { const metadata = await this.getTokenMetadata(); if (!metadata.additionalMetadata) { return null; } for (const [key, value] of metadata.additionalMetadata) { if (key === field) { return value; } } return null; } catch (error) { throw new Error(`Failed to get metadata field: ${error}`); } } async updateBasicMetadata(authority, updates) { const fields = {}; if (updates.name) { if (updates.name.length > 32) { throw new Error("Name must be 32 characters or less"); } fields["name"] = updates.name; } if (updates.symbol) { if (updates.symbol.length > 10) { throw new Error("Symbol must be 10 characters or less"); } fields["symbol"] = updates.symbol; } if (updates.uri) { if (updates.uri.length > 200) { throw new Error("URI must be 200 characters or less"); } fields["uri"] = updates.uri; } return await this.updateMetadataBatch(authority, fields); } } exports.MetadataPointerToken = MetadataPointerToken;