import { randomUUID } from 'node:crypto'; import { Pool } from 'pg'; import { IS_PILOT } from '../shared/network.ts'; import { createMoneroWalletObserver, loadMoneroObserverConfig, moneroWalletFingerprint } from '../server/market/monero-observer.ts'; import { loadMoneroMtlsTransport, loadMoneroMtlsTransportConfig } from '../server/market/monero-mtls-transport.ts'; import { postgresConfig } from '../server/market/postgres-config.ts'; import { parsePilotCustodianId, verifyPilotCustodyIdentity } from '../server/market/pilot-custody-identity.ts'; import { readPilotMoneroObservationCursor, persistPilotMoneroObservationScan } from '../server/market/pilot-monero-observation-store.ts'; if (process.platform === 'win32') throw new Error('Pilot observer requires POSIX deployment with protected-file permission checks'); if (!IS_PILOT || process.env.WIKIDECK_PILOT_MODE !== 'private-mock-only') { throw new Error('Pilot observer requires the private mock-only pilot profile'); } if (process.env.WIKIDECK_PILOT_CUSTODY_OPERATIONS !== 'pilot-observation-only-no-credit') { throw new Error('Set WIKIDECK_PILOT_CUSTODY_OPERATIONS=pilot-observation-only-no-credit for this explicit observer action'); } const custodianId = parsePilotCustodianId(process.env.WIKIDECK_PILOT_CUSTODIAN_ID ?? ''); const walletGenerationId = process.env.WIKIDECK_MONERO_WALLET_GENERATION_ID ?? ''; const expectedFingerprint = process.env.WIKIDECK_MONERO_WALLET_FINGERPRINT ?? ''; if (!/^[a-f0-9-]{36}$/i.test(walletGenerationId) || !/^[a-f0-9]{64}$/.test(expectedFingerprint)) { throw new Error('Invalid protected pilot watch-only wallet generation or fingerprint'); } function positiveBounded(name: string, fallback: number, maximum: number): number { const value = process.env[name]; if (value === undefined) return fallback; if (!/^[1-9]\d{0,5}$/.test(value)) throw new Error(`Invalid ${name}`); const parsed = Number(value); if (!Number.isSafeInteger(parsed) || parsed > maximum) throw new Error(`Invalid ${name}`); return parsed; } const maxBlocks = positiveBounded('WIKIDECK_PILOT_OBSERVER_MAX_BLOCKS', 5000, 100_000); const overlap = positiveBounded('WIKIDECK_PILOT_OBSERVER_OVERLAP', 100, 10_000); const observerConfig = await loadMoneroObserverConfig({ ...process.env, WIKIDECK_MONERO_OBSERVER: '1' }); const transportConfig = await loadMoneroMtlsTransportConfig(); if (observerConfig.network !== 'mainnet' || new URL(observerConfig.url).href !== new URL(transportConfig.url).href) { throw new Error('Pilot observer requires matching Mainnet Wallet-RPC and mTLS gateway endpoints'); } const scanner = createMoneroWalletObserver(observerConfig, await loadMoneroMtlsTransport(transportConfig)); const primaryAddress = await scanner.verifyMainnetWallet(); const walletFingerprint = await moneroWalletFingerprint(primaryAddress); if (walletFingerprint !== expectedFingerprint) throw new Error('Configured pilot wallet fingerprint does not match protected Wallet-RPC'); const dbUrlFile = process.env.WIKIDECK_PILOT_OBSERVER_PG_URL_FILE; const dbCaFile = process.env.WIKIDECK_PILOT_OBSERVER_PG_CA_FILE; if (!dbUrlFile || !dbCaFile) throw new Error('Pilot observer PostgreSQL secret URL and CA files are required'); const pool = new Pool(await postgresConfig({ ...process.env, WIKIDECK_CUSTODY_PG_URL_FILE: dbUrlFile, WIKIDECK_CUSTODY_PG_CA_FILE: dbCaFile, WIKIDECK_CUSTODY_PG_TRANSPORT: 'tls' })); pool.on('error', () => console.error(JSON.stringify({ component: 'pilot-monero-observer', event: 'database-unavailable' }))); try { const identity = await verifyPilotCustodyIdentity(pool, custodianId); const scanHeight = await readPilotMoneroObservationCursor(pool, walletGenerationId, walletFingerprint); const fromHeight = Math.max(0, scanHeight - overlap); const page = await scanner.scanIncoming(fromHeight, maxBlocks); const result = await persistPilotMoneroObservationScan(pool, { scanId: randomUUID(), walletGenerationId, walletFingerprint, expectedScanHeight: scanHeight, scannedAt: Date.now(), page }); console.log(JSON.stringify({ component: 'pilot-monero-observer', network: identity.moneroNetwork, marketNetwork: identity.marketNetwork, walletHeight: page.walletHeight, scanHeight: result.scanHeight, caughtUp: result.scanHeight >= page.walletHeight, observed: result.observed, assigned: result.assigned, unallocated: result.unallocated, changed: result.changed, reappeared: result.reappeared, notSeen: result.notSeen, replayed: result.replayed, credits: 'disabled' })); } catch { console.error('Pilot observation failed or requires review; no balance credit was performed.'); process.exitCode = 1; } finally { await pool.end(); }