Added the guarded, one-shot monero:pilot-address command
This commit is contained in:
Executable
+64
@@ -0,0 +1,64 @@
|
||||
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(); }
|
||||
Reference in New Issue
Block a user