import { describe, it, expect, vi, beforeEach } from 'vitest'; import { Connection } from '@solana/web3.js'; import { RealWalletWatcher } from '../src/solana/watcher.js'; import { fetchTransactionWithRetry } from '../src/solana/fetcher.js'; import { InMemoryRawWalletTransactionRepository, InMemoryEventJournalRepository, } from './mocks/InMemoryRepositories.js'; import { EventJournalService } from '../src/persistence/eventJournal.js'; import { WalletTransactionObservation } from '../src/domain/interfaces.js'; import { RawWalletTransaction } from '../src/domain/models.js'; import { randomUUID } from 'crypto'; // Mock @solana/web3.js references starting with "mock" to be accessible in hoisted vi.mock const mockOnLogs = vi.fn().mockReturnValue(12345); const mockRemoveOnLogsListener = vi.fn().mockResolvedValue(true); const mockGetTransaction = vi.fn(); vi.mock('@solana/web3.js', () => { class MockConnection { onLogs = mockOnLogs; removeOnLogsListener = mockRemoveOnLogsListener; getTransaction = mockGetTransaction; } class MockPublicKey { constructor(public address: string) {} toBase58() { return this.address; } } return { Connection: MockConnection, PublicKey: MockPublicKey, }; }); // Mock database interactions to avoid live connections vi.mock('../src/persistence/db.js', () => { return { initDb: vi.fn(), verifyDbHealth: vi.fn().mockResolvedValue(undefined), closeDb: vi.fn().mockResolvedValue(undefined), getDbPool: vi.fn().mockImplementation(() => { return { query: vi.fn().mockResolvedValue({ rows: [{ id: 'mock-id', address: '3yF9asA9B7G3Y1G7as78gHJKa7A', name: 'Mock Wallet', is_active: true, created_at: new Date(), updated_at: new Date() }], rowCount: 1, }), }; }), }; }); describe('Real Solana Observation Mode Unit Tests', () => { let connection: any; beforeEach(() => { vi.clearAllMocks(); connection = new Connection('http://dummy', 'confirmed'); }); // 1. Transaction fetch retry with bounded exponential backoff describe('Transaction Fetch Retry Mechanism', () => { it('should successfully return transaction if it is found on the first attempt', async () => { const mockTx = { slot: 12345, blockTime: 1710000000 }; connection.getTransaction.mockResolvedValueOnce(mockTx); const result = await fetchTransactionWithRetry(connection as any, 'dummy-sig', 3, 10, 20); expect(result).toEqual(mockTx); expect(connection.getTransaction).toHaveBeenCalledTimes(1); }); it('should retry fetching with backoff and succeed if transaction becomes available eventually', async () => { const mockTx = { slot: 12345, blockTime: 1710000000 }; connection.getTransaction .mockResolvedValueOnce(null) // first attempt: temporarily unavailable .mockRejectedValueOnce(new Error('RPC limit reached')) // second attempt: error .mockResolvedValueOnce(mockTx); // third attempt: success const result = await fetchTransactionWithRetry(connection as any, 'dummy-sig', 4, 10, 50); expect(result).toEqual(mockTx); expect(connection.getTransaction).toHaveBeenCalledTimes(3); }); it('should stop retrying and throw error if maxRetries limit is reached', async () => { connection.getTransaction.mockResolvedValue(null); // always returns null await expect( fetchTransactionWithRetry(connection as any, 'dummy-sig', 3, 5, 10) ).rejects.toThrow('Transaction dummy-sig could not be fetched after 3 attempts.'); expect(connection.getTransaction).toHaveBeenCalledTimes(3); }); }); // 2. RealWalletWatcher Subscription and Shutdown Behavior describe('RealWalletWatcher Lifecycle & Reconnect Behavior', () => { it('should subscribe to logs for all watched wallets on startup', async () => { const wallets = ['3yF9asA9B7G3Y1G7as78gHJKa7A', '4yF9asA9B7G3Y1G7as78gHJKa7B']; const watcher = new RealWalletWatcher(wallets, 'http://dummy-rpc', 'wss://dummy-wss'); await watcher.startWatching(); expect(connection.onLogs).toHaveBeenCalledTimes(2); expect(connection.onLogs).toHaveBeenNthCalledWith(1, expect.any(Object), expect.any(Function), 'confirmed'); expect(connection.onLogs).toHaveBeenNthCalledWith(2, expect.any(Object), expect.any(Function), 'confirmed'); }); it('should unsubscribe from all logs listeners on shutdown', async () => { const wallets = ['3yF9asA9B7G3Y1G7as78gHJKa7A']; const watcher = new RealWalletWatcher(wallets, 'http://dummy-rpc', 'wss://dummy-wss'); await watcher.startWatching(); await watcher.stopWatching(); expect(connection.removeOnLogsListener).toHaveBeenCalledTimes(1); expect(connection.removeOnLogsListener).toHaveBeenCalledWith(12345); }); }); // 3. Real Mode Pipeline orchestration, Raw Persistence, Duplicates, and Stopping describe('Real Mode Pipeline Logic', () => { let rawTxRepo: InMemoryRawWalletTransactionRepository; let journalRepo: InMemoryEventJournalRepository; let journalService: EventJournalService; beforeEach(() => { rawTxRepo = new InMemoryRawWalletTransactionRepository(); journalRepo = new InMemoryEventJournalRepository(); journalService = new EventJournalService(journalRepo, '1.0.0', 'commit', 'hash'); }); // Simulated callback registration helper replicating Application real-mode callback const runPipelineCallback = async ( observation: WalletTransactionObservation, watcherInstance: any, ) => { const correlationId = observation.correlationId || randomUUID(); const detectedAt = observation.timestamp || new Date(); // Check duplicate first const existing = await rawTxRepo.getTransactionBySignature(observation.signature); if (existing) { await journalService.log(correlationId, 'duplicate_transaction_ignored', { signature: observation.signature, walletAddress: observation.walletAddress, }); return { stopped: true, reason: 'duplicate' }; } await journalService.log(correlationId, 'wallet_activity_detected', { walletAddress: observation.walletAddress, signature: observation.signature, slot: observation.slot, detectedAt, }); // Fetch transaction await journalService.log(correlationId, 'raw_transaction_fetch_started', { signature: observation.signature, }); let txPayload: any; try { txPayload = await fetchTransactionWithRetry(watcherInstance.connection, observation.signature, 3, 5, 10); } catch (error) { await journalService.log(correlationId, 'raw_transaction_fetch_failed', { signature: observation.signature, error: error instanceof Error ? error.message : String(error), }); return { stopped: true, reason: 'fetch_failed' }; } await journalService.log(correlationId, 'raw_transaction_fetched', { signature: observation.signature, slot: txPayload.slot, blockTime: txPayload.blockTime, }); // Persist raw transaction try { const blockTime = txPayload.blockTime ? new Date(txPayload.blockTime * 1000) : null; const rawTx: RawWalletTransaction = { transactionSignature: observation.signature, sourceWallet: observation.walletAddress, slot: observation.slot, blockTime, detectedAt, rawPayload: txPayload, parserVersion: 'ingestion_v1', }; await rawTxRepo.saveTransaction(rawTx); await journalService.log(correlationId, 'raw_transaction_persisted', { signature: observation.signature, slot: observation.slot, }); } catch { // failed to persist } // STOP: The pipeline must return here and NOT trigger strategy or simulation return { stopped: true, reason: 'pipeline_stopped_normally' }; }; it('should fetch, persist raw transaction, log events to journal, and STOP', async () => { const observation: WalletTransactionObservation = { signature: 'tx-sig-123', slot: 100, walletAddress: '3yF9asA9B7G3Y1G7as78gHJKa7A', timestamp: new Date(), correlationId: 'corr-real-mode', }; const mockTxPayload = { slot: 100, blockTime: Math.floor(Date.now() / 1000), meta: { logMessages: ['program instruction...'] }, }; connection.getTransaction.mockResolvedValueOnce(mockTxPayload); const watcher = new RealWalletWatcher(['3yF9asA9B7G3Y1G7as78gHJKa7A'], 'http://dummy', 'wss://dummy'); (watcher as any).connection = connection; // inject connection mock const res = await runPipelineCallback(observation, watcher); // Verify the pipeline stopped normally expect(res.stopped).toBe(true); expect(res.reason).toBe('pipeline_stopped_normally'); // Verify the raw transaction was saved const savedTx = await rawTxRepo.getTransactionBySignature('tx-sig-123'); expect(savedTx).not.toBeNull(); expect(savedTx?.transactionSignature).toBe('tx-sig-123'); expect(savedTx?.sourceWallet).toBe('3yF9asA9B7G3Y1G7as78gHJKa7A'); expect(savedTx?.rawPayload).toEqual(mockTxPayload); // Verify Event Journal logs const events = await journalRepo.getEventsByCorrelationId('corr-real-mode'); const eventTypes = events.map((e) => e.eventType); expect(eventTypes).toContain('wallet_activity_detected'); expect(eventTypes).toContain('raw_transaction_fetch_started'); expect(eventTypes).toContain('raw_transaction_fetched'); expect(eventTypes).toContain('raw_transaction_persisted'); }); it('should ignore and log duplicate websocket notifications without calling RPC fetch again', async () => { const observation: WalletTransactionObservation = { signature: 'tx-sig-dup', slot: 100, walletAddress: '3yF9asA9B7G3Y1G7as78gHJKa7A', timestamp: new Date(), correlationId: 'corr-real-mode-dup', }; // Pre-populate raw persistence to simulate duplicate await rawTxRepo.saveTransaction({ transactionSignature: 'tx-sig-dup', sourceWallet: '3yF9asA9B7G3Y1G7as78gHJKa7A', slot: 100, blockTime: null, detectedAt: new Date(), rawPayload: {}, parserVersion: 'ingestion_v1', }); const watcher = new RealWalletWatcher(['3yF9asA9B7G3Y1G7as78gHJKa7A'], 'http://dummy', 'wss://dummy'); (watcher as any).connection = connection; const res = await runPipelineCallback(observation, watcher); // Verify pipeline stopped expect(res.stopped).toBe(true); expect(res.reason).toBe('duplicate'); // Verify connection.getTransaction was NEVER called (saving RPC rate limit!) expect(connection.getTransaction).not.toHaveBeenCalled(); // Verify duplicate event is written to journal const events = await journalRepo.getEventsByCorrelationId('corr-real-mode-dup'); expect(events.map((e) => e.eventType)).toContain('duplicate_transaction_ignored'); }); it('should log raw_transaction_fetch_failed if RPC continues to fail and return gracefully', async () => { const observation: WalletTransactionObservation = { signature: 'tx-sig-fail', slot: 100, walletAddress: '3yF9asA9B7G3Y1G7as78gHJKa7A', timestamp: new Date(), correlationId: 'corr-real-mode-fail', }; connection.getTransaction.mockResolvedValue(null); // simulates always failing to fetch const watcher = new RealWalletWatcher(['3yF9asA9B7G3Y1G7as78gHJKa7A'], 'http://dummy', 'wss://dummy'); (watcher as any).connection = connection; const res = await runPipelineCallback(observation, watcher); // Verify pipeline stopped on failure expect(res.stopped).toBe(true); expect(res.reason).toBe('fetch_failed'); // Verify fetch failed event is in journal const events = await journalRepo.getEventsByCorrelationId('corr-real-mode-fail'); expect(events.map((e) => e.eventType)).toContain('raw_transaction_fetch_failed'); }); }); describe('BoundedTransactionQueue Unit Tests', () => { let mockMetrics: any; beforeEach(() => { mockMetrics = { recordQueueDepth: vi.fn(), recordQueueRetries: vi.fn(), recordQueue429s: vi.fn(), recordQuoteLatency: vi.fn(), recordSignalProcessingLatency: vi.fn(), recordWalletEventReceived: vi.fn(), recordWalletQueueDepth: vi.fn(), recordOldestQueuedAge: vi.fn(), recordDroppedOldestCount: vi.fn(), recordTransactionRelevance: vi.fn(), recordRpcRequestRate: vi.fn(), recordQueueProcessed: vi.fn(), recordRpcFetchError: vi.fn(), }; }); it('should respect max concurrency limit and rate limiting delay', async () => { const { BoundedTransactionQueue } = await import('../src/solana/fetcher.js'); const mockTx = { slot: 100, blockTime: 123456 }; connection.getTransaction.mockResolvedValue(mockTx); const queue = new BoundedTransactionQueue( connection as any, 2, // maxConcurrency = 2 100, // minDelayMs = 100ms 10, // maxQueueSize = 10 mockMetrics, ); queue.start(); const successSigs: string[] = []; const pushItem = (sig: string) => { return new Promise((resolve) => { queue.push({ signature: sig, walletAddress: 'wallet1', slot: 100, detectedAt: new Date(), correlationId: 'corr', onSuccess: async () => { successSigs.push(sig); resolve(); }, onFailure: async () => { resolve(); }, }); }); }; // Push 3 items concurrently const p1 = pushItem('sig1'); const p2 = pushItem('sig2'); const p3 = pushItem('sig3'); // Check that at most 2 are active initially expect(queue.getActiveCount()).toBeLessThanOrEqual(2); await Promise.all([p1, p2, p3]); expect(successSigs).toContain('sig1'); expect(successSigs).toContain('sig2'); expect(successSigs).toContain('sig3'); expect(queue.getDepth()).toBe(0); expect(queue.getActiveCount()).toBe(0); queue.stop(); }); it('should drop incoming items and apply backpressure when queue size is exceeded', async () => { const { BoundedTransactionQueue } = await import('../src/solana/fetcher.js'); const mockTx = { slot: 100, blockTime: 123456 }; connection.getTransaction.mockResolvedValue(mockTx); const queue = new BoundedTransactionQueue( connection as any, 1, // maxConcurrency = 1 100, // minDelay = 100 2, // maxQueueSize = 2 (allows 2 items to wait in queue) ); queue.start(); const results: string[] = []; const errors: string[] = []; const pushItem = (sig: string) => { queue.push({ signature: sig, walletAddress: 'wallet1', slot: 100, detectedAt: new Date(), correlationId: 'corr', onSuccess: async () => { results.push(sig); }, onFailure: async (err) => { errors.push(`${sig}:${err.message}`); }, }); }; // Push 4 items (concurrency=1, max queue=2 -> 1 active, 2 in queue, 4th item triggers drop-oldest of item2) pushItem('item1'); // goes active immediately (active=1, queue=[]) pushItem('item2'); // queued (active=1, queue=[item2]) pushItem('item3'); // queued (active=1, queue=[item2, item3]) pushItem('item4'); // queue full! item2 is dropped, queue becomes [item3, item4] expect(errors).toContain('item2:Dropped from queue due to backpressure'); queue.stop(); }); it('should implement HTTP 429 exponential backoff with jitter and successfully retry', async () => { const mockTx = { slot: 100, blockTime: 123456 }; // First attempt triggers HTTP 429 rate limit error const rateLimitErr = { status: 429, message: 'Too Many Requests' }; connection.getTransaction .mockRejectedValueOnce(rateLimitErr) .mockResolvedValueOnce(mockTx); const result = await fetchTransactionWithRetry(connection as any, 'sig-429', 3, 10, 50, mockMetrics); expect(result).toEqual(mockTx); expect(connection.getTransaction).toHaveBeenCalledTimes(2); expect(mockMetrics.recordQueue429s).toHaveBeenCalledWith('sig-429'); }); it('should gracefully drain or terminate the queue when stopped during graceful shutdown', async () => { const { BoundedTransactionQueue } = await import('../src/solana/fetcher.js'); const queue = new BoundedTransactionQueue( connection as any, 1, 100, 10, ); queue.start(); let failed = false; queue.push({ signature: 'sig-drain', walletAddress: 'wallet', slot: 100, detectedAt: new Date(), correlationId: 'corr', onSuccess: async () => {}, onFailure: async () => { failed = true; }, }); expect(queue.getDepth()).toBe(0); // goes to active immediately queue.stop(); // Pushing after stop must fail immediately const pushedAfter = queue.push({ signature: 'sig-after', walletAddress: 'wallet', slot: 100, detectedAt: new Date(), correlationId: 'corr', onSuccess: async () => {}, onFailure: async () => { failed = true; }, }); expect(pushedAfter).toBe(false); expect(failed).toBe(true); }); it('should correctly schedule high-volume wallet without starving another wallet (fair round robin)', async () => { const { BoundedTransactionQueue } = await import('../src/solana/fetcher.js'); const mockTx = { slot: 100, blockTime: 123456 }; connection.getTransaction.mockResolvedValue(mockTx); const queue = new BoundedTransactionQueue( connection as any, 1, // maxConcurrency = 1 10, // minDelay = 10ms 10, // max size per-wallet ); queue.start(); const successOrder: string[] = []; const pushItem = (sig: string, wallet: string) => { queue.push({ signature: sig, walletAddress: wallet, slot: 100, detectedAt: new Date(), correlationId: 'corr', onSuccess: async () => { successOrder.push(sig); }, onFailure: async () => {}, }); }; // Push A1. It executes immediately. pushItem('A1', 'walletA'); // activeCount=1, walletOrder=[] // Now enqueue additional tasks. // A2 is pushed. It goes to 'walletA' queue. walletOrder becomes ['walletA'] pushItem('A2', 'walletA'); // A3 is pushed. It goes to 'walletA' queue. Since 'walletA' is already in walletOrder, order remains ['walletA'] pushItem('A3', 'walletA'); // B1 is pushed. It goes to 'walletB' queue. walletOrder becomes ['walletA', 'walletB'] pushItem('B1', 'walletB'); // Now A1 finishes. processNext() is called. // It retrieves from scheduling order: 'walletOrder.shift()' -> yields 'walletA'! // So 'A2' is dequeued and executed next. // Since 'walletA' still has 'A3', it is re-queued at the back of the order. // walletOrder becomes: ['walletB', 'walletA'] // // Then A2 finishes. processNext() is called. // It shifts 'walletOrder' -> yields 'walletB'! // So 'B1' is dequeued and executed third. // // Finally B1 finishes. processNext() shifts -> yields 'walletA' -> executes 'A3'. // // Hence successOrder will be: ['A1', 'A2', 'B1', 'A3'] // This proves B1 is scheduled fairly and is NOT starved by A3 even though A was queued first! // Process and wait for all tasks to settle await new Promise((resolve) => setTimeout(resolve, 150)); expect(successOrder[0]).toBe('A1'); expect(successOrder[1]).toBe('A2'); expect(successOrder[2]).toBe('B1'); // Fair scheduling ensures B1 executes third, before A3! expect(successOrder[3]).toBe('A3'); queue.stop(); }); it('should drop oldest items and preserve newest events under backpressure', async () => { const { BoundedTransactionQueue } = await import('../src/solana/fetcher.js'); const mockTx = { slot: 100, blockTime: 123456 }; connection.getTransaction.mockResolvedValue(mockTx); const queue = new BoundedTransactionQueue( connection as any, 1, // maxConcurrency = 1 50, // minDelay = 50ms 2, // max queue size per-wallet = 2 ); queue.start(); const results: string[] = []; const errors: string[] = []; const pushItem = (sig: string) => { queue.push({ signature: sig, walletAddress: 'wallet1', slot: 100, detectedAt: new Date(), correlationId: 'corr', onSuccess: async () => { results.push(sig); }, onFailure: async (err) => { errors.push(`${sig}:${err.message}`); }, }); }; pushItem('T1'); // Runs immediately pushItem('T2'); // Queued (pos 0) pushItem('T3'); // Queued (pos 1) pushItem('T4'); // Queued full! Drops oldest ('T2'), appends T4. Queue is now: [T3, T4] expect(errors).toContain('T2:Dropped from queue due to backpressure'); await new Promise((resolve) => setTimeout(resolve, 200)); expect(results).toContain('T1'); expect(results).not.toContain('T2'); // dropped expect(results).toContain('T3'); expect(results).toContain('T4'); // newest preserved! queue.stop(); }); it('should prove backpressure drop is not reported as fetch failure, generates no stack trace, and metrics distinguish it', async () => { const { BoundedTransactionQueue, BackpressureDropError } = await import('../src/solana/fetcher.js'); const { logger } = await import('../src/logging/index.js'); const loggerWarnSpy = vi.spyOn(logger, 'warn'); const loggerErrorSpy = vi.spyOn(logger, 'error'); const queue = new BoundedTransactionQueue( connection as any, 1, // maxConcurrency = 1 10, // minDelay = 10ms 1, // max queue size per-wallet = 1 mockMetrics, ); queue.start(); let capturedError: any = null; queue.push({ signature: 'T1', walletAddress: 'wallet1', slot: 100, detectedAt: new Date(), correlationId: 'corr1', onSuccess: async () => {}, onFailure: async () => {}, }); queue.push({ signature: 'T2', walletAddress: 'wallet1', slot: 100, detectedAt: new Date(), correlationId: 'corr2', onSuccess: async () => {}, onFailure: async (err) => { capturedError = err; }, }); // T3 will trigger drop of T2 queue.push({ signature: 'T3', walletAddress: 'wallet1', slot: 100, detectedAt: new Date(), correlationId: 'corr3', onSuccess: async () => {}, onFailure: async () => {}, }); expect(capturedError).toBeInstanceOf(BackpressureDropError); expect(capturedError.stack).toBeUndefined(); // Verify that backpressure drop logs WARN, not ERROR const backpressureWarns = loggerWarnSpy.mock.calls.filter(call => call[0] && call[0].includes('BACKPRESSURE') ); expect(backpressureWarns.length).toBeGreaterThan(0); const backpressureErrors = loggerErrorSpy.mock.calls.filter(call => call[0] && call[0].includes('BACKPRESSURE') ); expect(backpressureErrors.length).toBe(0); // Verify operational metrics: recordDroppedOldestCount called, recordRpcFetchError NOT called expect(mockMetrics.recordDroppedOldestCount).toHaveBeenCalledWith('wallet1'); expect(mockMetrics.recordRpcFetchError).not.toHaveBeenCalled(); queue.stop(); loggerWarnSpy.mockRestore(); loggerErrorSpy.mockRestore(); }); it('should prove one rate-limit wait produces only one scheduling/log event and does not spam logs', async () => { const { BoundedTransactionQueue } = await import('../src/solana/fetcher.js'); const { logger } = await import('../src/logging/index.js'); const loggerDebugSpy = vi.spyOn(logger, 'debug'); const queue = new BoundedTransactionQueue( connection as any, 1, // maxConcurrency = 1 1000, // minDelay = 1000ms (so we trigger rate limit) 10, // max queue size per-wallet = 10 mockMetrics, ); queue.start(); // Push item 1 (executed immediately, updates lastRequestTime) queue.push({ signature: 'R1', walletAddress: 'wallet1', slot: 100, detectedAt: new Date(), correlationId: 'corr1', onSuccess: async () => {}, onFailure: async () => {}, }); // Push 5 more items concurrently (within rate-limit interval) for (let i = 2; i <= 6; i++) { queue.push({ signature: `R${i}`, walletAddress: 'wallet1', slot: 100, detectedAt: new Date(), correlationId: `corr${i}`, onSuccess: async () => {}, onFailure: async () => {}, }); } // Wait for R1 to finish and trigger the rate limit wait await new Promise((resolve) => setTimeout(resolve, 50)); // Check the debug logs for "Global rate limit: waiting" const rateLimitDebugs = loggerDebugSpy.mock.calls.filter(call => call[0] && call[0].includes('Global rate limit: waiting') ); // Even with 5 pushes under rate limit, we should only have ONE rate limit waiting log event expect(rateLimitDebugs.length).toBe(1); queue.stop(); loggerDebugSpy.mockRestore(); }); it('should classify fetched transactions correctly based on SOL/SPL token balance changes', async () => { const { isTransactionRelevant } = await import('../src/solana/parser.js'); // 1. Irrelevant payload (no balance changes or only extremely tiny SOL shift simulating fee) const irrelevantTx: any = { transaction: { message: { getAccountKeys: () => ({ staticAccountKeys: [{ toBase58: () => 'my-wallet' }], }), }, }, meta: { preBalances: [1000000000n], postBalances: [1000000000n], // exactly 0 change preTokenBalances: [], postTokenBalances: [], }, }; // 2. Meaningful SOL transfer payload const relevantSolTx: any = { transaction: { message: { getAccountKeys: () => ({ staticAccountKeys: [{ toBase58: () => 'my-wallet' }], }), }, }, meta: { preBalances: [1000000000], postBalances: [900000000], // -100,000,000 lamports (-0.1 SOL) change preTokenBalances: [], postTokenBalances: [], }, }; // 3. Meaningful SPL Token swap payload const relevantTokenTx: any = { transaction: { message: { getAccountKeys: () => ({ staticAccountKeys: [{ toBase58: () => 'my-wallet' }], }), }, }, meta: { preBalances: [1000000000], postBalances: [1000000000], // 0 SOL change preTokenBalances: [ { owner: 'my-wallet', mint: 'token-mint-1', uiTokenAmount: { amount: '1000' } }, ], postTokenBalances: [ { owner: 'my-wallet', mint: 'token-mint-1', uiTokenAmount: { amount: '5000' } }, // +4000 change ], }, }; expect(isTransactionRelevant(irrelevantTx, 'my-wallet')).toBe(false); expect(isTransactionRelevant(relevantSolTx, 'my-wallet')).toBe(true); expect(isTransactionRelevant(relevantTokenTx, 'my-wallet')).toBe(true); }); it('should correctly resolve accounts and relevance for legacy transactions', async () => { const { isTransactionRelevant } = await import('../src/solana/parser.js'); const legacyTx: any = { transaction: { message: { accountKeys: [{ toBase58: () => 'my-wallet' }, { toBase58: () => 'other-acct' }], }, }, meta: { preBalances: [2000000000, 50000000], postBalances: [1000000000, 105000000], // SOL shift: -1 SOL for my-wallet (relevant) preTokenBalances: [], postTokenBalances: [], }, }; expect(isTransactionRelevant(legacyTx, 'my-wallet')).toBe(true); expect(isTransactionRelevant(legacyTx, 'other-acct')).toBe(true); }); it('should correctly resolve accounts and relevance for v0 transactions without address lookup tables', async () => { const { isTransactionRelevant } = await import('../src/solana/parser.js'); const v0TxWithoutAlts: any = { transaction: { message: { staticAccountKeys: [{ toBase58: () => 'my-wallet' }, { toBase58: () => 'other-acct' }], }, }, meta: { preBalances: [1000000000, 50000000], postBalances: [1000000000, 50000000], // no SOL shift preTokenBalances: [ { owner: 'my-wallet', mint: 'token-mint-v0', uiTokenAmount: { amount: '10' } } ], postTokenBalances: [ { owner: 'my-wallet', mint: 'token-mint-v0', uiTokenAmount: { amount: '110' } } // relevant SPL shift ], }, }; expect(isTransactionRelevant(v0TxWithoutAlts, 'my-wallet')).toBe(true); }); it('should correctly resolve accounts and relevance for v0 transactions with loaded addresses (ALTs)', async () => { const { isTransactionRelevant, getAccountKeysArray } = await import('../src/solana/parser.js'); const v0TxWithAlts: any = { transaction: { message: { staticAccountKeys: [{ toBase58: () => 'static-1' }], }, }, meta: { loadedAddresses: { writable: [{ toBase58: () => 'loaded-writable-1' }], readonly: [{ toBase58: () => 'loaded-readonly-1' }, { toBase58: () => 'loaded-readonly-2' }] }, preBalances: [5000000000, 1000000000, 50000000, 50000000], postBalances: [4900000000, 1100000000, 50000000, 50000000], // transfer 0.1 SOL from static-1 to loaded-writable-1 preTokenBalances: [], postTokenBalances: [], }, }; // Verify full index mapping: // 0 -> static-1, 1 -> loaded-writable-1, 2 -> loaded-readonly-1, 3 -> loaded-readonly-2 const keys = getAccountKeysArray(v0TxWithAlts).map(k => k.toBase58()); expect(keys).toEqual(['static-1', 'loaded-writable-1', 'loaded-readonly-1', 'loaded-readonly-2']); // static-1 had post-pre = 4.9B - 5B = -100M lamports (relevant SOL change) expect(isTransactionRelevant(v0TxWithAlts, 'static-1')).toBe(true); // loaded-writable-1 had post-pre = 1.1B - 1B = 100M lamports (relevant SOL change) expect(isTransactionRelevant(v0TxWithAlts, 'loaded-writable-1')).toBe(true); // loaded-readonly-1 had post-pre = 50M - 50M = 0 lamports (irrelevant) expect(isTransactionRelevant(v0TxWithAlts, 'loaded-readonly-1')).toBe(false); }); it('should throw an error on account resolution or parsing failures as a distinct error state', async () => { const { isTransactionRelevant } = await import('../src/solana/parser.js'); const malformedTx: any = { transaction: { // message is missing }, }; await expect(async () => { isTransactionRelevant(malformedTx, 'any-wallet'); }).rejects.toThrow('Transaction message is missing or unresolvable'); }); }); describe('Application Real Mode vs Mock Mode Loop Behavior', () => { it('should NOT start SimulationScheduler processing loop in real mode and keep pending simulated executions untouched', async () => { const oldMode = process.env.WALLET_WATCHER_MODE; process.env.WALLET_WATCHER_MODE = 'real'; process.env.WATCHED_WALLETS = '3yF9asA9B7G3Y1G7as78gHJKa7A'; try { const { Application } = await import('../src/application/index.js'); const app = new Application(); await app.bootstrap(); // Proves that no interval id was created/started for the simulated loop expect(app.simulationIntervalId).toBeNull(); await app.shutdown('SIGINT'); } finally { if (oldMode) { process.env.WALLET_WATCHER_MODE = oldMode; } else { delete process.env.WALLET_WATCHER_MODE; } } }); it('should start SimulationScheduler processing loop in mock mode', async () => { const oldMode = process.env.WALLET_WATCHER_MODE; process.env.WALLET_WATCHER_MODE = 'mock'; process.env.WATCHED_WALLETS = '3yF9asA9B7G3Y1G7as78gHJKa7A'; try { const { Application } = await import('../src/application/index.js'); const app = new Application(); await app.bootstrap(); // Proves that interval id WAS started for the simulated loop expect(app.simulationIntervalId).not.toBeNull(); await app.shutdown('SIGINT'); } finally { if (oldMode) { process.env.WALLET_WATCHER_MODE = oldMode; } else { delete process.env.WALLET_WATCHER_MODE; } } }); }); });