/**
* Snipe Module
* Snipe token launches across multiple wallets
*/
import {
Connection,
PublicKey,
VersionedTransaction,
} from '@solana/web3.js';
import type { SnipeLaunchParams, SnipeResult } from '../types.js';
import { TokenLaunchError, TokenLaunchErrorCode } from '../types.js';
import { logger } from '../utils/logger.js';
import { buyOnPumpFun } from '../pumpfun/client.js';
/**
* Snipe a token launch with multiple wallets
*/
export async function snipeLaunch(
connection: Connection,
params: SnipeLaunchParams,
getWalletKeypair: (walletId: string) => Promise<{
publicKey: PublicKey;
signTransaction: (tx: VersionedTransaction) => Promise<VersionedTransaction>;
}>
): Promise<SnipeResult> {
const {
tokenMint,
dex,
walletIds,
amountPerWallet,
slippageBps,
useJito: _useJito,
maxRetries = 3,
} = params;
logger.info('Starting snipe operation', {
tokenMint,
dex,
walletCount: walletIds.length,
amountPerWallet,
});
const results: SnipeResult['results'] = [];
let successfulSnipes = 0;
let failedSnipes = 0;
let totalSolSpent = BigInt(0);
let totalTokensReceived = BigInt(0);
// Process wallets in parallel (with concurrency limit)
const CONCURRENCY = 10;
const solAmountBigInt = BigInt(Math.floor(parseFloat(amountPerWallet) * 1e9));
for (let i = 0; i < walletIds.length; i += CONCURRENCY) {
const batch = walletIds.slice(i, i + CONCURRENCY);
const batchPromises = batch.map(async (walletId) => {
const wallet = await getWalletKeypair(walletId);
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
if (dex === 'pumpfun') {
// Buy on PumpFun
const result = await buyOnPumpFun(
connection,
tokenMint,
wallet.publicKey,
solAmountBigInt,
slippageBps,
wallet.signTransaction
);
return {
walletId,
success: true,
signature: result.signature,
tokensReceived: result.tokensReceived.toString(),
solSpent: amountPerWallet,
};
} else {
// For other DEXs, would use Jupiter or direct pool swap
throw new TokenLaunchError(
TokenLaunchErrorCode.TRANSACTION_FAILED,
`DEX ${dex} sniping not yet implemented. Use pumpfun or integrate Jupiter.`
);
}
} catch (error) {
if (attempt === maxRetries - 1) {
return {
walletId,
success: false,
error: error instanceof Error ? error.message : 'Unknown error',
};
}
// Wait before retry
await new Promise(resolve => setTimeout(resolve, 500));
}
}
return {
walletId,
success: false,
error: 'Max retries exceeded',
};
});
const batchResults = await Promise.all(batchPromises);
for (const result of batchResults) {
results.push(result);
if (result.success) {
successfulSnipes++;
totalSolSpent += BigInt(Math.floor(parseFloat(result.solSpent || '0') * 1e9));
totalTokensReceived += BigInt(result.tokensReceived || '0');
} else {
failedSnipes++;
}
}
}
logger.info('Snipe operation completed', {
successfulSnipes,
failedSnipes,
totalSolSpent: totalSolSpent.toString(),
totalTokensReceived: totalTokensReceived.toString(),
});
return {
success: successfulSnipes > 0,
totalWallets: walletIds.length,
successfulSnipes,
failedSnipes,
totalSolSpent: totalSolSpent.toString(),
totalTokensReceived: totalTokensReceived.toString(),
results,
};
}
/**
* Snipe with Jito bundle for atomic execution
*/
export async function snipeWithJitoBundle(
_connection: Connection,
params: SnipeLaunchParams,
getWalletKeypair: (walletId: string) => Promise<{
publicKey: PublicKey;
signTransaction: (tx: VersionedTransaction) => Promise<VersionedTransaction>;
}>,
jitoClient: {
sendBundle: (txs: VersionedTransaction[], tipLamports: string) => Promise<{ bundleId: string }>;
}
): Promise<SnipeResult> {
const {
tokenMint,
walletIds,
amountPerWallet,
slippageBps: _slippageBps,
jitoTipLamports = '10000000', // 0.01 SOL default
} = params;
logger.info('Starting Jito bundle snipe', {
tokenMint,
walletCount: walletIds.length,
tipLamports: jitoTipLamports,
});
// Build all transactions
const transactions: VersionedTransaction[] = [];
const solAmountBigInt = BigInt(Math.floor(parseFloat(amountPerWallet) * 1e9));
for (const walletId of walletIds) {
const _wallet = await getWalletKeypair(walletId);
const _mintPubkey = new PublicKey(tokenMint);
// Build buy transaction
// This would be similar to buildPumpFunBuyInstruction
// For brevity, leaving as placeholder
logger.debug('Built transaction for wallet', { walletId });
}
// Jito bundles support max 5 transactions
if (transactions.length > 5) {
throw new TokenLaunchError(
TokenLaunchErrorCode.BUNDLE_FAILED,
'Jito bundles support max 5 transactions. Split into multiple bundles.'
);
}
try {
const result = await jitoClient.sendBundle(transactions, jitoTipLamports);
return {
success: true,
totalWallets: walletIds.length,
successfulSnipes: walletIds.length,
failedSnipes: 0,
totalSolSpent: (solAmountBigInt * BigInt(walletIds.length)).toString(),
totalTokensReceived: '0', // Would need to parse from bundle result
bundleId: result.bundleId,
results: walletIds.map(walletId => ({
walletId,
success: true,
})),
};
} catch (error) {
logger.error('Jito bundle failed', { error });
return {
success: false,
totalWallets: walletIds.length,
successfulSnipes: 0,
failedSnipes: walletIds.length,
totalSolSpent: '0',
totalTokensReceived: '0',
results: walletIds.map(walletId => ({
walletId,
success: false,
error: error instanceof Error ? error.message : 'Bundle failed',
})),
};
}
}