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

209 lines 10.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Hopper = 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"); const constants_1 = require("./constants"); const bs58_1 = __importDefault(require("bs58")); class Hopper 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('hopper_execution_started', { requestId }); this.validateOptions(delegateOptions); const { hopDestination, numOfHops, numTokens, tokenType, tokenAddress } = delegateOptions; const hopDestinationPubkey = new web3_js_1.PublicKey(hopDestination); const hopMap = []; const signatures = []; this.logOperation('hopper_setup', { requestId, numOfHops, tokenType, destination: hopDestination }); if (tokenType === 'sol') { // Native SOL hopper logic const result = await this.executeSolHopping(numOfHops, numTokens, hopDestinationPubkey, hopMap, requestId); signatures.push(...result.signatures); } else { // Token hopper logic if (!tokenAddress) { throw new Error("tokenAddress is required when tokenType is 'token'"); } const result = await this.executeTokenHopping(numOfHops, numTokens, tokenAddress, hopDestinationPubkey, hopMap, requestId); signatures.push(...result.signatures); } this.logOperation('hopper_execution_completed', { requestId, signatures, totalHops: numOfHops }); return { success: true, signatures, hopMap, finalDestination: hopDestination, totalHops: numOfHops }; } catch (error) { await this.handleError(error instanceof Error ? error : new Error(String(error)), { requestId }); throw error; } } validateOptions(delegateOptions) { this.validateRequiredField(delegateOptions.hopDestination, 'hopDestination'); this.validatePublicKey(delegateOptions.hopDestination, 'hopDestination'); this.validateNumberField(delegateOptions.numOfHops, 'numOfHops', 1, 100); this.validateNumberField(delegateOptions.numTokens, 'numTokens', 0); if (delegateOptions.tokenType !== 'sol' && delegateOptions.tokenType !== 'token') { throw new Error("tokenType must be either 'sol' or 'token'"); } if (delegateOptions.tokenType === 'token') { this.validateRequiredField(delegateOptions.tokenAddress, 'tokenAddress'); this.validatePublicKey(delegateOptions.tokenAddress, 'tokenAddress'); } } async executeSolHopping(numOfHops, numTokens, hopDestination, hopMap, requestId) { const signatures = []; const rentAndMinBalance = constants_1.TOKEN_ACCOUNT_RENT + 50000; // Rent + minimum balance const costBuffer = Math.max(rentAndMinBalance, 50000); // Ensure we always leave at least rent const initialBalance = numTokens * web3_js_1.LAMPORTS_PER_SOL; let lastHopKeypair = this.signerKeypair; for (let i = 0; i < numOfHops; i++) { this.logOperation('sol_hop_progress', { requestId, currentHop: i + 1, totalHops: numOfHops }); const senderBalance = i === 0 ? initialBalance : await this.heliusClient.getBalance(lastHopKeypair.publicKey); const newHopKeypair = web3_js_1.Keypair.generate(); hopMap.push({ publicKey: newHopKeypair.publicKey.toBase58(), privateKey: bs58_1.default.encode(newHopKeypair.secretKey) }); const transaction = new web3_js_1.Transaction(); transaction.add(web3_js_1.SystemProgram.transfer({ fromPubkey: lastHopKeypair.publicKey, toPubkey: (i === numOfHops - 1) ? hopDestination : newHopKeypair.publicKey, lamports: senderBalance - costBuffer })); const signature = await this.executeTransaction(transaction, lastHopKeypair, requestId); signatures.push(signature); lastHopKeypair = newHopKeypair; } return { signatures }; } async executeTokenHopping(numOfHops, numTokens, tokenAddress, hopDestination, hopMap, requestId) { const signatures = []; const mint = new web3_js_1.PublicKey(tokenAddress); const decimals = await this.getTokenDecimals(tokenAddress); const amountToTransfer = Math.floor(this.calculateAmountToTransfer(numTokens, 1, decimals)); if (decimals === -1) { throw new Error("Failed to get token decimals"); } let lastHopKeypair = this.signerKeypair; let lastHopTokenAccount = null; // Get or create initial token account lastHopTokenAccount = await this.retryOperation(async () => { return await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(this.connection, lastHopKeypair, mint, lastHopKeypair.publicKey, true); }, 5); if (!lastHopTokenAccount) { throw new Error("Failed to get last hop token account"); } for (let i = 0; i < numOfHops; i++) { this.logOperation('token_hop_progress', { requestId, currentHop: i + 1, totalHops: numOfHops }); const initialBalance = await this.getTokenBalance(lastHopTokenAccount.address.toBase58()); if (initialBalance < amountToTransfer) { throw new Error(`Insufficient token balance. Required: ${amountToTransfer}, Available: ${initialBalance}`); } const newHopKeypair = web3_js_1.Keypair.generate(); hopMap.push({ publicKey: newHopKeypair.publicKey.toBase58(), privateKey: bs58_1.default.encode(newHopKeypair.secretKey) }); const newHopAddress = (i === numOfHops - 1) ? hopDestination : newHopKeypair.publicKey; const newHopTokenAccount = await this.retryOperation(async () => { return await (0, spl_token_1.getOrCreateAssociatedTokenAccount)(this.connection, lastHopKeypair, mint, newHopAddress, true); }, 5); if (!newHopTokenAccount) { throw new Error("Failed to get new hop token account"); } // Transfer tokens const tokenTransaction = new web3_js_1.Transaction(); tokenTransaction.add((0, spl_token_1.createTransferCheckedInstruction)(lastHopTokenAccount.address, mint, newHopTokenAccount.address, lastHopKeypair.publicKey, amountToTransfer, decimals)); const tokenSignature = await this.executeTransaction(tokenTransaction, lastHopKeypair, requestId); signatures.push(tokenSignature); // Verify transfer const newBalance = await this.getTokenBalance(newHopTokenAccount.address.toBase58()); if (newBalance < amountToTransfer) { throw new Error("Token transfer may have failed - balance not updated"); } // Transfer SOL for fees const solTransaction = new web3_js_1.Transaction(); const senderBalance = await this.heliusClient.getBalance(lastHopKeypair.publicKey); const costBuffer = constants_1.TOKEN_ACCOUNT_RENT * 1.5; const solToSend = (i === 0 && senderBalance > 0.01 * web3_js_1.LAMPORTS_PER_SOL) ? (numOfHops * 0.01 * web3_js_1.LAMPORTS_PER_SOL) - costBuffer : senderBalance - costBuffer; solTransaction.add(web3_js_1.SystemProgram.transfer({ fromPubkey: lastHopKeypair.publicKey, toPubkey: (i === numOfHops - 1) ? new web3_js_1.PublicKey(constants_1.FEE_WALLET_ADDRESS) : newHopAddress, lamports: solToSend })); const solSignature = await this.executeTransaction(solTransaction, lastHopKeypair, requestId); signatures.push(solSignature); lastHopKeypair = newHopKeypair; lastHopTokenAccount = newHopTokenAccount; } return { signatures }; } async executeTransaction(transaction, signer, requestId) { transaction.feePayer = signer.publicKey; const blockhash = await this.retryOperation(async () => { return (await this.connection.getLatestBlockhash()).blockhash; }, 3); transaction.recentBlockhash = blockhash; transaction.sign(signer); const signature = await this.retryOperation(async () => { return await this.connection.sendTransaction(transaction, [signer], { skipPreflight: false, maxRetries: 3, }); }, 3); await this.retryOperation(async () => { await this.connection.confirmTransaction(signature, 'confirmed'); }, 3); this.logOperation('transaction_confirmed', { requestId, signature }); return signature; } async getTokenBalance(tokenAccountAddress) { const balanceInfo = await this.heliusClient.getTokenAccountBalance(new web3_js_1.PublicKey(tokenAccountAddress)); return parseFloat(balanceInfo.value.uiAmountString || '0'); } async getTokenDecimals(tokenAddress) { const mint = new web3_js_1.PublicKey(tokenAddress); const mintInfo = await this.retryOperation(async () => { return await (0, spl_token_1.getMint)(this.connection, mint); }, 3); return mintInfo.decimals; } calculateAmountToTransfer(numTokens, numRecipients, decimals) { return Math.floor((numTokens / numRecipients) * Math.pow(10, decimals)); } } exports.Hopper = Hopper; //# sourceMappingURL=hopper.js.map