|
|
@@ -6,6 +6,8 @@ export class SniperEngine {
|
|
|
constructor() {
|
|
|
this.copiedCache = new PositionCache('.copied-positions.json', CONFIG.DATA_DIR);
|
|
|
this.closedCache = new PositionCache('.closed-positions.json', CONFIG.DATA_DIR);
|
|
|
+ this.initialPositions = new Set(); // Store positions at startup
|
|
|
+ this.hasInitialized = false; // Track if initialization is complete
|
|
|
this.swapper = new JupiterSwapper();
|
|
|
this.isRunning = false;
|
|
|
this.myWallet = CONFIG.MY_WALLET || this.swapper.getWalletAddress();
|
|
|
@@ -123,6 +125,33 @@ export class SniperEngine {
|
|
|
return success;
|
|
|
}
|
|
|
|
|
|
+ async initialize() {
|
|
|
+ // Fetch all existing positions at startup and mark them as initial positions
|
|
|
+ logger.info('Initializing sniper - checking existing positions...');
|
|
|
+
|
|
|
+ const { positions, poolMap } = await ByrealAPI.fetchTargetPositions(CONFIG.TARGET_WALLET);
|
|
|
+
|
|
|
+ if (positions.length === 0) {
|
|
|
+ logger.info('No existing positions found at startup');
|
|
|
+ } else {
|
|
|
+ logger.info(`Found ${positions.length} existing positions at startup - will monitor for new ones only`);
|
|
|
+
|
|
|
+ for (const position of positions) {
|
|
|
+ const positionAddress = position.positionAddress;
|
|
|
+ this.initialPositions.add(positionAddress);
|
|
|
+
|
|
|
+ // Log existing positions
|
|
|
+ const poolInfo = poolMap[position.poolAddress];
|
|
|
+ const symbolA = poolInfo?.mintA?.symbol || '?';
|
|
|
+ const symbolB = poolInfo?.mintB?.symbol || '?';
|
|
|
+ logger.info(` [INITIAL] ${positionAddress} - ${symbolA}/${symbolB} (will not copy)`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ this.hasInitialized = true;
|
|
|
+ logger.success(`Initialization complete - monitoring for ${this.initialPositions.size} existing positions and new additions only\n`);
|
|
|
+ }
|
|
|
+
|
|
|
async scanForNewPositions() {
|
|
|
logger.info(`Scanning for target wallet positions: ${CONFIG.TARGET_WALLET}`);
|
|
|
|
|
|
@@ -133,22 +162,36 @@ export class SniperEngine {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
- logger.info(`Found ${positions.length} positions, analyzing...`);
|
|
|
+ // Count new positions (not in initial set)
|
|
|
+ const newPositions = positions.filter(p => !this.initialPositions.has(p.positionAddress));
|
|
|
+
|
|
|
+ if (newPositions.length === 0) {
|
|
|
+ logger.info(`Found ${positions.length} positions, 0 new - all are from initial scan`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ logger.info(`Found ${positions.length} total positions, ${newPositions.length} new to analyze...`);
|
|
|
|
|
|
- for (const position of positions) {
|
|
|
+ for (const position of newPositions) {
|
|
|
const positionAddress = position.positionAddress;
|
|
|
|
|
|
+ // Skip if already processed (copied or closed)
|
|
|
if (this.copiedCache.has(positionAddress) || this.closedCache.has(positionAddress)) {
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
+ // Skip if this was an initial position
|
|
|
+ if (this.initialPositions.has(positionAddress)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
const poolInfo = poolMap[position.poolAddress];
|
|
|
if (!poolInfo) {
|
|
|
logger.warn(`Pool info not found for ${position.poolAddress}`);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
- logger.info(`\nAnalyzing position: ${positionAddress}`);
|
|
|
+ logger.info(`\n🆕 New position detected: ${positionAddress}`);
|
|
|
logger.info(`Pool: ${poolInfo.mintA?.symbol || '?'}/${poolInfo.mintB?.symbol || '?'}`);
|
|
|
|
|
|
const analysis = await this.analyzePosition(position, poolInfo);
|
|
|
@@ -219,6 +262,9 @@ export class SniperEngine {
|
|
|
logger.info(`⏱️ Poll Interval: ${CONFIG.POLL_INTERVAL_MS / 1000}s`);
|
|
|
logger.info('═══════════════════════════════════════════\n');
|
|
|
|
|
|
+ // Initialize: record existing positions but don't copy them
|
|
|
+ await this.initialize();
|
|
|
+
|
|
|
while (this.isRunning) {
|
|
|
try {
|
|
|
await this.scanForNewPositions();
|