UNPKG

solana-token-extension-boost

Version:

SDK for Solana Token Extensions with wallet adapter support

274 lines (273 loc) 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TransferFeeToken = void 0; const web3_js_1 = require("@solana/web3.js"); const spl_token_1 = require("@solana/spl-token"); const token_1 = require("../../core/token"); /** * TransferFeeToken - Extension for Token with transfer fee functionality * * This extension allows automatic fee collection when transferring tokens, * with configurable fee rate and maximum fee amount. */ class TransferFeeToken extends token_1.Token { constructor(connection, mint, config) { super(connection, mint); this.config = config; } /** * Generate instructions to create a new TransferFeeToken * * @param connection - Connection to Solana cluster * @param payer - Public key of the transaction fee payer * @param params - Initialization parameters including: * - decimals: Number of decimal places * - mintAuthority: Authority allowed to mint tokens * - transferFeeConfig: Transfer fee configuration * @returns Instructions, signers and mint address */ static async createInstructions(connection, payer, params) { if (params.transferFeeConfig.feeBasisPoints < 0 || params.transferFeeConfig.feeBasisPoints > 10000) { throw new Error("Fee rate must be between 0 and 10000 basis points (0-100%)"); } if (params.transferFeeConfig.maxFee < 0n) { throw new Error("Maximum fee cannot be negative"); } try { const mintKeypair = web3_js_1.Keypair.generate(); const mintLen = (0, spl_token_1.getMintLen)([spl_token_1.ExtensionType.TransferFeeConfig]); const lamports = await connection.getMinimumBalanceForRentExemption(mintLen); 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.createInitializeTransferFeeConfigInstruction)(mintKeypair.publicKey, params.transferFeeConfig.transferFeeConfigAuthority, params.transferFeeConfig.withdrawWithheldAuthority, params.transferFeeConfig.feeBasisPoints, params.transferFeeConfig.maxFee, spl_token_1.TOKEN_2022_PROGRAM_ID), (0, spl_token_1.createInitializeMintInstruction)(mintKeypair.publicKey, params.decimals, params.mintAuthority, null, spl_token_1.TOKEN_2022_PROGRAM_ID) ]; return { instructions, signers: [mintKeypair], mint: mintKeypair.publicKey }; } catch (error) { throw new Error(`Could not create TransferFeeToken instructions: ${error.message}`); } } /** * Calculate transfer fee based on token amount and fee configuration * * @param amount - Token amount to transfer * @returns Calculated fee amount */ calculateFee(amount) { const fee = (amount * BigInt(this.config.feeBasisPoints)) / BigInt(10000); return fee > this.config.maxFee ? this.config.maxFee : fee; } /** * Create transfer instruction with automatically calculated fee * * @param source - Source account address * @param destination - Destination account address * @param owner - Source account owner * @param amount - Token amount to transfer * @param decimals - Token decimal places * @returns TransactionInstruction */ createTransferInstruction(source, destination, owner, amount, decimals) { const fee = this.calculateFee(amount); return this.createTransferWithFeeInstruction(source, destination, owner, amount, decimals, Number(fee)); } /** * Create transfer instruction with specified fee * * @param source - Source account address * @param destination - Destination account address * @param owner - Source account owner * @param amount - Token amount to transfer * @param decimals - Token decimal places * @param fee - Specified fee amount * @returns TransactionInstruction */ createTransferWithFeeInstruction(source, destination, owner, amount, decimals, fee) { return (0, spl_token_1.createTransferCheckedWithFeeInstruction)(source, this.mint, destination, owner, amount, decimals, BigInt(fee), [], spl_token_1.TOKEN_2022_PROGRAM_ID); } /** * Create instruction to harvest withheld tokens from accounts to the mint * * @param accounts - List of accounts with withheld fees to harvest * @returns Transaction instruction */ createHarvestWithheldTokensToMintInstruction(accounts) { if (accounts.length === 0) { throw new Error("Account list cannot be empty"); } return (0, spl_token_1.createHarvestWithheldTokensToMintInstruction)(this.mint, accounts, spl_token_1.TOKEN_2022_PROGRAM_ID); } /** * Create instruction to withdraw withheld tokens from accounts to a destination account * * @param accounts - List of accounts with withheld fees to withdraw * @param destination - Destination account to receive fees * @param authority - Withdraw authority public key * @returns Transaction instruction */ createWithdrawFeesFromAccountsInstruction(accounts, destination, authority) { if (accounts.length === 0) { throw new Error("Account list cannot be empty"); } return (0, spl_token_1.createWithdrawWithheldTokensFromAccountsInstruction)(this.mint, destination, authority, [], accounts, spl_token_1.TOKEN_2022_PROGRAM_ID); } /** * Create instruction to withdraw withheld tokens from mint to a destination account * * @param destination - Destination account to receive fees * @param authority - Withdraw authority public key * @returns Transaction instruction */ createWithdrawFeesFromMintInstruction(destination, authority) { return (0, spl_token_1.createWithdrawWithheldTokensFromMintInstruction)(this.mint, destination, authority, [], spl_token_1.TOKEN_2022_PROGRAM_ID); } /** * Create instructions to create token account and mint tokens to it * * @param owner - Token account owner address * @param payer - Transaction fee payer public key * @param amount - Token amount to mint * @param mintAuthority - Mint authority * @returns Instructions and token account address */ async createAccountAndMintToInstructions(owner, payer, amount, mintAuthority) { try { const tokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.mint, owner, false, spl_token_1.TOKEN_2022_PROGRAM_ID); const instructions = []; try { await (0, spl_token_1.getAccount)(this.connection, tokenAccount, "recent", spl_token_1.TOKEN_2022_PROGRAM_ID); } catch (error) { instructions.push((0, spl_token_1.createAssociatedTokenAccountInstruction)(payer, tokenAccount, owner, this.mint, spl_token_1.TOKEN_2022_PROGRAM_ID)); } const mintInstruction = (0, spl_token_1.createMintToInstruction)(this.mint, tokenAccount, mintAuthority, amount, [], spl_token_1.TOKEN_2022_PROGRAM_ID); instructions.push(mintInstruction); return { instructions, address: tokenAccount }; } catch (error) { throw new Error(`Could not create account and mint instructions: ${error.message}`); } } /** * Find all accounts with withheld fees * * @returns List of public keys for accounts with withheld fees */ async findAccountsWithWithheldFees() { try { const accounts = await this.connection.getProgramAccounts(spl_token_1.TOKEN_2022_PROGRAM_ID, { commitment: "confirmed", filters: [ { memcmp: { offset: 0, bytes: this.mint.toString(), }, }, ], }); const accountsWithFees = []; for (const { pubkey } of accounts) { try { const tokenAccount = await (0, spl_token_1.getAccount)(this.connection, pubkey, "confirmed", spl_token_1.TOKEN_2022_PROGRAM_ID); const feeAmount = (0, spl_token_1.getTransferFeeAmount)(tokenAccount); if (feeAmount !== null && feeAmount.withheldAmount > 0) { accountsWithFees.push(pubkey); } } catch (error) { } } return accountsWithFees; } catch (error) { throw new Error(`Could not find accounts with fees: ${error.message}`); } } /** * Get transfer fee configuration * * @returns TransferFeeConfig object */ getTransferFeeConfig() { return { ...this.config }; } /** * Lấy thông tin chi tiết về phí chuyển khoản hiện tại và phí đã giữ lại * * @param tokenAccount - Địa chỉ tài khoản token cần kiểm tra * @returns Đối tượng chứa thông tin về phí * - withheldAmount: Số lượng token đã giữ lại làm phí * - hasOlderTransferFee: Cho biết tài khoản có phí từ giao dịch trước đó chưa được rút */ async getAccountTransferFeeInfo(tokenAccount) { try { const account = await (0, spl_token_1.getAccount)(this.connection, tokenAccount, "confirmed", spl_token_1.TOKEN_2022_PROGRAM_ID); const feeAmount = (0, spl_token_1.getTransferFeeAmount)(account); if (!feeAmount) { return { withheldAmount: BigInt(0), hasOlderTransferFee: false }; } return { withheldAmount: feeAmount.withheldAmount, hasOlderTransferFee: feeAmount.withheldAmount > BigInt(0) }; } catch (error) { throw new Error(`Could not get transfer fee info: ${error.message}`); } } /** * Tính tổng số token đã giữ lại làm phí từ nhiều tài khoản * * @param accounts - Danh sách các địa chỉ tài khoản token * @returns Tổng số token đã giữ lại */ async getTotalWithheldAmount(accounts) { let totalWithheldAmount = BigInt(0); for (const account of accounts) { try { const feeInfo = await this.getAccountTransferFeeInfo(account); totalWithheldAmount += feeInfo.withheldAmount; } catch (error) { } } return totalWithheldAmount; } /** * Kiểm tra xem một địa chỉ có phải là withdraw withheld authority của token không * * @param address - Địa chỉ cần kiểm tra * @returns true nếu là withdraw withheld authority, false nếu không phải */ async isWithdrawWithheldAuthority(address) { if (!this.config.withdrawWithheldAuthority) { return false; } if (this.config.withdrawWithheldAuthority instanceof web3_js_1.Keypair) { return this.config.withdrawWithheldAuthority.publicKey.equals(address); } else if (this.config.withdrawWithheldAuthority instanceof web3_js_1.PublicKey) { return this.config.withdrawWithheldAuthority.equals(address); } return false; } } exports.TransferFeeToken = TransferFeeToken;