UNPKG

delegate-framework

Version:

A TypeScript framework for building robust, production-ready blockchain workflows with comprehensive error handling, logging, and testing. Maintained by delegate.fun

239 lines 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Distributor = void 0; const web3_js_1 = require("@solana/web3.js"); const spl_token_1 = require("@solana/spl-token"); const base_delegate_1 = require("./base-delegate"); class Distributor extends base_delegate_1.BaseDelegate { constructor(connection, signerKeypair, heliusClient, feeTakerKeypair) { super(connection, signerKeypair, feeTakerKeypair); this.heliusClient = heliusClient; } async executeDelegate(delegateOptions) { const requestId = this.generateRequestId(); try { this.logOperation('distributor_execution_started', { requestId }); this.validateOptions(delegateOptions); // Get recipients based on distribution type const recipients = await this.getRecipients(delegateOptions); if (recipients.length === 0) { throw new Error("No recipients found for distribution"); } const signatures = []; const recipientResults = []; // Setup token account if needed let senderTokenAccount = null; let mint = null; if (delegateOptions.tokenAddress) { mint = new web3_js_1.PublicKey(delegateOptions.tokenAddress); senderTokenAccount = await this.retryOperation(async () => { return await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(this.connection, this.signerKeypair, mint, this.signerKeypair.publicKey, true); }, 3); } // Process each recipient for (let i = 0; i < recipients.length; i++) { const recipientAddress = recipients[i]; if (!recipientAddress) { this.logOperation('recipient_skipped', { requestId, reason: 'undefined_address' }); continue; } try { const result = await this.processRecipient(recipientAddress, delegateOptions, recipients.length, mint, senderTokenAccount); signatures.push(result.signature); recipientResults.push(result); this.logOperation('recipient_processed', { requestId, recipientAddress, signature: result.signature }); } catch (error) { await this.handleError(error instanceof Error ? error : new Error(String(error)), { requestId, recipientAddress }); // For multi distribution, continue with next recipient if (delegateOptions.distributionType === 'multi') { this.logOperation('recipient_skipped', { requestId, recipientAddress }); continue; } else { // For single and holders distribution, throw error throw error; } } } this.logOperation('distributor_execution_completed', { requestId, signatures }); return { success: true, signatures, recipients: recipientResults }; } catch (error) { await this.handleError(error instanceof Error ? error : new Error(String(error)), { requestId }); throw error; } } validateOptions(delegateOptions) { this.validateNumberField(delegateOptions.numTokens, 'numTokens', 0); if (delegateOptions.tokenAddress) { this.validatePublicKey(delegateOptions.tokenAddress, 'tokenAddress'); } switch (delegateOptions.distributionType) { case 'single': if (!delegateOptions.singleAddress) { throw new Error("singleAddress is required for single distribution"); } this.validatePublicKey(delegateOptions.singleAddress, 'singleAddress'); break; case 'multi': if (!delegateOptions.multipleAddresses || delegateOptions.multipleAddresses.length === 0) { throw new Error("multipleAddresses array is required for multi distribution"); } for (const address of delegateOptions.multipleAddresses) { this.validatePublicKey(address, 'multipleAddresses'); } break; case 'holders': if (!delegateOptions.distributionMethod) { throw new Error("distributionMethod is required for holders distribution"); } if (delegateOptions.distributionMethod === 'topx') { if (!delegateOptions.topX || delegateOptions.topX <= 0) { throw new Error("topX must be greater than 0 for topx distribution method"); } if (!delegateOptions.holderOfWhichToken) { throw new Error("holderOfWhichToken is required for holders distribution"); } this.validatePublicKey(delegateOptions.holderOfWhichToken, 'holderOfWhichToken'); } break; default: throw new Error(`Invalid distribution type: ${delegateOptions.distributionType}`); } } async getRecipients(delegateOptions) { switch (delegateOptions.distributionType) { case 'single': if (!delegateOptions.singleAddress) { throw new Error("singleAddress is required for single distribution"); } return [delegateOptions.singleAddress]; case 'multi': if (!delegateOptions.multipleAddresses) { throw new Error("multipleAddresses is required for multi distribution"); } return delegateOptions.multipleAddresses; case 'holders': if (delegateOptions.distributionMethod === 'topx') { if (!delegateOptions.holderOfWhichToken) { throw new Error("holderOfWhichToken is required for holders distribution"); } if (!delegateOptions.topX) { throw new Error("topX is required for topx distribution method"); } return await this.getTopHolders(delegateOptions.holderOfWhichToken, delegateOptions.topX); } else { throw new Error("Distribution method 'all' not yet implemented"); } default: throw new Error(`Invalid distribution type: ${delegateOptions.distributionType}`); } } async getTopHolders(tokenAddress, topX) { try { const holdersData = await this.retryOperation(async () => { return await this.heliusClient.getTopHolders(tokenAddress); }, 3); if (!holdersData || !Array.isArray(holdersData)) { throw new Error("Invalid response from getTopHolders"); } // Extract owner addresses from the largest accounts const topHolders = holdersData.slice(0, topX); const ownerAddresses = []; for (const holder of topHolders) { if (holder && holder.address) { // Get the owner of this token account using Helius API const owner = await this.retryOperation(async () => { return await this.heliusClient.getTokenAccountOwner(holder.address); }, 3); if (owner) { ownerAddresses.push(owner); } } } return ownerAddresses; } catch (error) { this.logOperation('get_top_holders_failed', { tokenAddress, error: error instanceof Error ? error.message : String(error) }); throw new Error(`Failed to get top holders for token ${tokenAddress}: ${error}`); } } async processRecipient(recipientAddress, delegateOptions, numRecipients, mint, senderTokenAccount) { const recipientPubkey = new web3_js_1.PublicKey(recipientAddress); const transaction = new web3_js_1.Transaction(); if (delegateOptions.tokenAddress && mint && senderTokenAccount) { // Token transfer const receivingTokenAccount = await this.retryOperation(async () => { return await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(this.connection, this.signerKeypair, mint, recipientPubkey, true); }, 3); const decimals = await this.getTokenDecimals(delegateOptions.tokenAddress); const amountToTransfer = Math.floor(this.calculateAmountToTransfer(delegateOptions.numTokens, numRecipients, decimals)); transaction.add((0, spl_token_1.createTransferCheckedInstruction)(senderTokenAccount.address, mint, receivingTokenAccount.address, this.signerKeypair.publicKey, amountToTransfer, decimals)); } else { // SOL transfer const amountPerRecipient = Math.floor(delegateOptions.numTokens / numRecipients * web3_js_1.LAMPORTS_PER_SOL); transaction.add(web3_js_1.SystemProgram.transfer({ fromPubkey: this.signerKeypair.publicKey, toPubkey: recipientPubkey, lamports: amountPerRecipient, })); } transaction.feePayer = this.signerKeypair.publicKey; const blockhash = await this.retryOperation(async () => { return (await this.connection.getLatestBlockhash()).blockhash; }, 3); transaction.recentBlockhash = blockhash; transaction.sign(this.signerKeypair); const signature = await this.retryOperation(async () => { return await this.connection.sendTransaction(transaction, [this.signerKeypair], { skipPreflight: false, maxRetries: 3, }); }, 3); // Wait for confirmation await this.retryOperation(async () => { const latestBlockhash = await this.connection.getLatestBlockhash(); await this.connection.confirmTransaction({ blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, signature }); return true; }, 3); const amount = delegateOptions.tokenAddress ? delegateOptions.numTokens / numRecipients : delegateOptions.numTokens / numRecipients; return { address: recipientAddress, amount, signature }; } async getTokenDecimals(_) { // This is a mock implementation - in a real scenario, you would fetch from the token mint return 6; // Default to 6 decimals } calculateAmountToTransfer(totalAmount, numRecipients, decimals) { const amountPerRecipient = totalAmount / numRecipients; return amountPerRecipient * Math.pow(10, decimals); } } exports.Distributor = Distributor; //# sourceMappingURL=distributor.js.map