Repository Public
1// Controlled live trading — wiring for the rest of the API service: the process-wide execution engine on the real signer2// client and the real venue, and its reconciliation worker. Disabled by construction: an account trades only after the owner's3// passkey approved a venue authorization on the signer, and mainnet additionally needs SPW_DERIV_LIVE_MAINNET=on.4import { listCreds } from '../auth.ts';5import { config } from '../config.ts';6import { signer } from '../signer-client.ts';7import { loop } from '../worker.ts';8import { LiveService, LIVE_STEP_MS, type LiveDeps } from './exec.ts';9import { LiveVenues } from './live-venue.ts';1011/** server setting, read at every use: anything but the exact value `on` keeps the mainnet exchange endpoint closed */12export const mainnetLiveEnabled = (): boolean => process.env.SPW_DERIV_LIVE_MAINNET === 'on';1314/** second, separate server setting for AUTONOMOUS (mandate) trading on mainnet: it needs this AND SPW_DERIV_LIVE_MAINNET, both exactly `on` */15export const mandateMainnetEnabled = (): boolean => process.env.SPW_DERIV_MANDATE_MAINNET === 'on';1617/** tests pass a fake venue (`fetch`), an in-process signer and a clock */18export function createLive(o: Partial<LiveDeps> & { fetch?: typeof fetch } = {}): LiveService {19 const now = o.now ?? Date.now, mainnetEnabled = o.mainnetEnabled ?? mainnetLiveEnabled;20 return new LiveService({21 venues: o.venues ?? new LiveVenues({ now, mainnetEnabled, ...(o.fetch ? { fetch: o.fetch } : {}) }),22 signer: o.signer ?? signer, now, rpId: o.rpId ?? config.rpId, mainnetEnabled,23 // derivative mandates: the real signer client offers them; a test that injects its own signer port says so explicitly24 ...(o.mandateSigner ? { mandateSigner: o.mandateSigner } : o.signer ? {} : { mandateSigner: signer }), mandateMainnetEnabled: o.mandateMainnetEnabled ?? mandateMainnetEnabled,25 credentials: o.credentials ?? (async () => (await listCreds()).map((c) => ({ id: c.id, transports: c.transports }))),26 ...(o.confirmDelayMs !== undefined ? { confirmDelayMs: o.confirmDelayMs } : {}),27 });28}2930let shared: LiveService | null = null;31export function live(): LiveService { return (shared ??= createLive()); }3233/** ONE reconciliation worker: orders in flight, fills, funding, positions, divergence. It never signs and never places anything. */34export function startLiveWorker(l: LiveService = live()): void {35 let lastError = '';36 loop('deriv-live', async () => {37 const s = await l.step();38 if (s.fills || s.funding || s.divergences) console.log(`[derivatives] live: ${s.accounts} account(s), ${s.reconciled} order(s) reconciled, ${s.fills} fill(s), ${s.funding} funding payment(s), ${s.divergences} divergence(s)`);39 const err = s.errors[0] ?? '';40 if (err && err !== lastError) console.log(`[derivatives] live: ${s.errors.length} error(s) (${err})`);41 lastError = err;42 }, LIVE_STEP_MS);43}44