import { Pool } from 'pg'; import { z } from 'zod'; import { IS_PILOT } from '../shared/network.ts'; import { createMoneroDepositAddressRpc, loadMoneroObserverConfig } 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 { createOrRecoverPilotMoneroDepositAddress } from '../server/market/pilot-monero-deposit-allocation.ts'; import { parsePilotCustodianId } from '../server/market/pilot-custody-identity.ts'; import { loadPilotMoneroDepositRequestFile } from '../server/market/pilot-monero-deposit-request-file.ts'; if (!IS_PILOT || process.env.WIKIDECK_PILOT_MODE !== 'private-mock-only') { throw new Error('Pilot address command requires the private mock-only pilot profile'); } if (process.env.WIKIDECK_PILOT_CUSTODY_OPERATIONS !== 'operator-triggered-addresses-only') { throw new Error('Set WIKIDECK_PILOT_CUSTODY_OPERATIONS=operator-triggered-addresses-only for this explicit operator action'); } if (process.argv.length !== 3) throw new Error('Usage: pilot-monero-deposit-address.ts SIGNED_REQUEST_JSON_FILE'); if (process.platform === 'win32') throw new Error('Pilot address command requires POSIX protected-file permission checks'); const request = await loadPilotMoneroDepositRequestFile(process.argv[2]); const custodianId = parsePilotCustodianId(process.env.WIKIDECK_PILOT_CUSTODIAN_ID ?? ''); const requestCustodian = z.object({ custodianId: z.string().uuid() }).passthrough().parse(request).custodianId.toLowerCase(); if (requestCustodian !== custodianId) throw new Error('Signed request does not match the configured pilot custodian'); const walletGenerationId = process.env.WIKIDECK_MONERO_WALLET_GENERATION_ID ?? ''; const walletFingerprint = process.env.WIKIDECK_MONERO_WALLET_FINGERPRINT ?? ''; const accountText = process.env.WIKIDECK_MONERO_DEPOSIT_ACCOUNT_INDEX; if (!/^[a-f0-9-]{36}$/i.test(walletGenerationId) || !/^[a-f0-9]{64}$/.test(walletFingerprint) || !accountText || !/^(0|[1-9]\d{0,9})$/.test(accountText) || Number(accountText) > 0xffff_ffff) { throw new Error('Invalid protected pilot watch-only wallet generation, fingerprint, or account configuration'); } const observerConfig = await loadMoneroObserverConfig({ ...process.env, WIKIDECK_MONERO_OBSERVER: '1' }); const mtlsConfig = await loadMoneroMtlsTransportConfig(); if (observerConfig.network !== 'mainnet' || new URL(observerConfig.url).href !== new URL(mtlsConfig.url).href) { throw new Error('Pilot Wallet-RPC config must match the protected Mainnet mTLS gateway endpoint'); } const transport = await loadMoneroMtlsTransport(mtlsConfig); const rpc = createMoneroDepositAddressRpc(observerConfig, transport); const actualFingerprint = await rpc.verifyWallet(); if (actualFingerprint !== walletFingerprint) throw new Error('Configured pilot wallet fingerprint does not match the protected Wallet-RPC wallet'); const pool = new Pool(await postgresConfig({ ...process.env, WIKIDECK_CUSTODY_PG_URL_FILE: process.env.WIKIDECK_PILOT_ADDRESS_PG_URL_FILE, WIKIDECK_CUSTODY_PG_CA_FILE: process.env.WIKIDECK_PILOT_ADDRESS_PG_CA_FILE, WIKIDECK_CUSTODY_PG_TRANSPORT: 'tls', })); pool.on('error', () => console.error(JSON.stringify({ component: 'pilot-address', event: 'database-unavailable' }))); try { const result = await createOrRecoverPilotMoneroDepositAddress(pool, rpc, { walletGenerationId, walletFingerprint: actualFingerprint, accountIndex: Number(accountText), }, request); // This is an operator-invoked address response only; no balance credit or deposit acceptance occurs here. console.log(JSON.stringify({ network: 'mainnet', marketNetwork: result.reservation.marketNetwork, marketChainId: result.reservation.marketChainId, custodianId: result.reservation.custodianId, allocationId: result.reservation.allocationId, beneficiary: result.reservation.beneficiary, addressIndex: result.reservation.addressIndex, address: result.address, replayed: result.replayed, crediting: 'disabled' })); } catch { console.error('Pilot address allocation failed or requires manual reconciliation; no funding/credit action was performed.'); process.exitCode = 1; } finally { await pool.end(); }