Some checks failed
deploy / deploy (push) Failing after 40s
Proof: Dashboard portfolio metrics now include DB-tracked USDC balances valued from live BTC/EUR and BTC/USDC reference prices, with regression coverage for the observed USDC inventory case. Assumptions: USDC is cash-equivalent for valuation when a fresh BTC/USDC reference event is available; live trading safety remains governed by pair config and price route checks. Still fake: Portfolio valuation still does not provide fee-complete realized PnL or generalized valuation for every imported non-stable asset.
2455 lines
88 KiB
JavaScript
2455 lines
88 KiB
JavaScript
import { unitsToNumber } from './assets.mjs';
|
|
import { bridgeDepositObservedAt } from './bridge-assets.mjs';
|
|
import { summarizeFundingObservations } from './funding-observations.mjs';
|
|
import { resolveDashboardRequestAuth } from './operator-dashboard-auth.mjs';
|
|
import { TERMINAL_SETTLEMENT_ATTRIBUTION_STATUSES } from './quote-outcomes.mjs';
|
|
import { inferServiceFreshnessTimestamp as inferRuntimeFreshnessTimestamp } from './runtime-health.mjs';
|
|
|
|
export const DASHBOARD_LIVE_QUOTE_LIMIT = 10;
|
|
export const DASHBOARD_LIVE_LIFECYCLE_LIMIT = 20;
|
|
|
|
const DECIMAL_SCALE = 18;
|
|
const DECIMAL_FACTOR = 10n ** BigInt(DECIMAL_SCALE);
|
|
const ALERT_SEVERITY_ORDER = {
|
|
critical: 3,
|
|
warning: 2,
|
|
info: 1,
|
|
};
|
|
const CREDITED_FUNDING_STATUSES = new Set(['CREDITED', 'COMPLETED', 'FINALIZED', 'SETTLED']);
|
|
|
|
const CONTROL_DEFINITIONS = [
|
|
{
|
|
service: 'near-intents-ingest',
|
|
action: 'reconnect',
|
|
method: 'POST',
|
|
path: '/reconnect',
|
|
label: 'Reconnect Ingest',
|
|
description: 'Reconnect the ingest websocket without restarting the deployment.',
|
|
page: 'system',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'market-reference-ingest',
|
|
action: 'refresh',
|
|
method: 'POST',
|
|
path: '/refresh',
|
|
label: 'Refresh Price',
|
|
description: 'Fetch the latest BTC/EUR reference price.',
|
|
page: 'funds',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'inventory-sync',
|
|
action: 'refresh',
|
|
method: 'POST',
|
|
path: '/refresh',
|
|
label: 'Refresh Inventory',
|
|
description: 'Sync verifier balances into the latest inventory snapshot.',
|
|
page: 'funds',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'operator-dashboard',
|
|
action: 'import-supported-assets',
|
|
method: 'POST',
|
|
path: '/internal/import-supported-assets',
|
|
label: 'Import Assets',
|
|
description: 'Fetch and store the current 1Click supported token catalog.',
|
|
page: 'strategy',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'operator-dashboard',
|
|
action: 'update-pair-edge',
|
|
method: 'POST',
|
|
path: '/internal/update-pair-edge',
|
|
label: 'Update Edge',
|
|
description: 'Create a new active strategy config version for a pair.',
|
|
page: 'strategy',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'operator-dashboard',
|
|
action: 'enable-observe-only-pair',
|
|
method: 'POST',
|
|
path: '/internal/enable-observe-only-pair',
|
|
label: 'Enable Observe-Only',
|
|
description: 'Approve a directed pair for observation without enabling trading.',
|
|
page: 'strategy',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'operator-dashboard',
|
|
action: 'set-pair-mode',
|
|
method: 'POST',
|
|
path: '/internal/set-pair-mode',
|
|
label: 'Set Pair Mode',
|
|
description: 'Activate a directed pair mode from durable DB config. Trading still requires valid strategy and price route state.',
|
|
page: 'strategy',
|
|
risk_class: 'live_funds',
|
|
},
|
|
{
|
|
service: 'operator-dashboard',
|
|
action: 'pause-pair',
|
|
method: 'POST',
|
|
path: '/internal/pause-pair',
|
|
label: 'Pause Pair',
|
|
description: 'Disable a directed pair in durable DB config without deleting historical strategy versions.',
|
|
page: 'strategy',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'trade-executor',
|
|
action: 'intent-request-preflight',
|
|
method: 'POST',
|
|
path: '/intent-request/preflight',
|
|
label: 'Preflight BTC Request',
|
|
description: 'Ask solvers for an EURe-to-BTC request quote without submitting live funds.',
|
|
page: 'funds',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'trade-executor',
|
|
action: 'intent-request-submit',
|
|
method: 'POST',
|
|
path: '/intent-request/submit',
|
|
label: 'Submit BTC Request',
|
|
description: 'Submit a previously drafted EURe-to-BTC request. Relay acceptance is not settlement.',
|
|
page: 'funds',
|
|
risk_class: 'live_funds',
|
|
},
|
|
{
|
|
service: 'trade-executor',
|
|
action: 'intent-request-refresh-outcomes',
|
|
method: 'POST',
|
|
path: '/intent-request/refresh-outcomes',
|
|
label: 'Refresh Request Outcomes',
|
|
description: 'Recompute own-request settlement attribution from durable inventory snapshots.',
|
|
page: 'funds',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'liquidity-manager',
|
|
action: 'refresh',
|
|
method: 'POST',
|
|
path: '/refresh',
|
|
label: 'Refresh Liquidity',
|
|
description: 'Refresh bridge deposit handles, deposits, and tracked withdrawals.',
|
|
page: 'funds',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'liquidity-manager',
|
|
action: 'pause',
|
|
method: 'POST',
|
|
path: '/pause',
|
|
label: 'Pause Liquidity',
|
|
description: 'Pause non-fund-moving liquidity state refreshes.',
|
|
page: 'system',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'liquidity-manager',
|
|
action: 'resume',
|
|
method: 'POST',
|
|
path: '/resume',
|
|
label: 'Resume Liquidity',
|
|
description: 'Resume liquidity state refreshes.',
|
|
page: 'system',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'liquidity-manager',
|
|
action: 'pause-funding-observer',
|
|
method: 'POST',
|
|
path: '/pause-funding-observer',
|
|
label: 'Pause Funding Observer',
|
|
description: 'Pause pre-credit funding observations without touching spendable truth.',
|
|
page: 'funds',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'liquidity-manager',
|
|
action: 'resume-funding-observer',
|
|
method: 'POST',
|
|
path: '/resume-funding-observer',
|
|
label: 'Resume Funding Observer',
|
|
description: 'Resume pre-credit funding observations.',
|
|
page: 'funds',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'liquidity-manager',
|
|
action: 'freeze-withdrawals',
|
|
method: 'POST',
|
|
path: '/freeze-withdrawals',
|
|
label: 'Update Withdrawal Freeze',
|
|
description: 'Toggle withdrawal freeze without submitting live withdrawals.',
|
|
page: 'funds',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'liquidity-manager',
|
|
action: 'withdrawal-estimate',
|
|
method: 'POST',
|
|
path: '/withdrawal-estimate',
|
|
label: 'Estimate Withdrawal',
|
|
description: 'Estimate a bridge withdrawal without moving funds.',
|
|
page: 'funds',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'trade-executor',
|
|
action: 'reconnect',
|
|
method: 'POST',
|
|
path: '/reconnect',
|
|
label: 'Reconnect Relay',
|
|
description: 'Reconnect the trade-executor solver relay websocket.',
|
|
page: 'system',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'trade-executor',
|
|
action: 'arm',
|
|
method: 'POST',
|
|
path: '/arm',
|
|
label: 'Arm Executor',
|
|
description: 'Persistently arm trade-executor so strategy-approved decisions can submit.',
|
|
page: 'system',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'trade-executor',
|
|
action: 'pause',
|
|
method: 'POST',
|
|
path: '/pause',
|
|
label: 'Pause Executor Intake',
|
|
description: 'Pause trade-executor command consumption without changing armed state.',
|
|
page: 'system',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'trade-executor',
|
|
action: 'resume',
|
|
method: 'POST',
|
|
path: '/resume',
|
|
label: 'Resume Executor Intake',
|
|
description: 'Resume trade-executor command consumption without changing armed state.',
|
|
page: 'system',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'trade-executor',
|
|
action: 'disarm',
|
|
method: 'POST',
|
|
path: '/disarm',
|
|
label: 'Disarm Executor',
|
|
description: 'Force the executor into a non-fund-moving safe state.',
|
|
page: 'system',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'ops-sentinel',
|
|
action: 'pause',
|
|
method: 'POST',
|
|
path: '/pause',
|
|
label: 'Pause Sentinel',
|
|
description: 'Pause background observation without changing trade arming state.',
|
|
page: 'system',
|
|
risk_class: 'safe',
|
|
},
|
|
{
|
|
service: 'ops-sentinel',
|
|
action: 'resume',
|
|
method: 'POST',
|
|
path: '/resume',
|
|
label: 'Resume Sentinel',
|
|
description: 'Resume background observation.',
|
|
page: 'system',
|
|
risk_class: 'safe',
|
|
},
|
|
];
|
|
|
|
const SERVICE_DEFINITIONS = [
|
|
['near-intents-ingest', 'Intents Ingest', 'nearIntentsControlBaseUrl'],
|
|
['market-reference-ingest', 'Reference Price', 'marketReferenceControlBaseUrl'],
|
|
['inventory-sync', 'Inventory Sync', 'inventorySyncControlBaseUrl'],
|
|
['liquidity-manager', 'Liquidity Manager', 'liquidityManagerControlBaseUrl'],
|
|
['history-writer', 'History Writer', 'historyWriterControlBaseUrl'],
|
|
['ops-sentinel', 'Ops Sentinel', 'opsSentinelControlBaseUrl'],
|
|
['strategy-engine', 'Strategy Engine', 'strategyEngineControlBaseUrl'],
|
|
['trade-executor', 'Trade Executor', 'tradeExecutorControlBaseUrl'],
|
|
['operator-dashboard', 'Operator Dashboard', 'operatorDashboardControlBaseUrl'],
|
|
];
|
|
|
|
export function resolveDashboardAuth({ mode = 'stub' } = {}) {
|
|
return resolveDashboardRequestAuth({ mode });
|
|
}
|
|
|
|
export function listDashboardControls({ page = null } = {}) {
|
|
const controls = CONTROL_DEFINITIONS.map((definition) => ({ ...definition }));
|
|
if (!page) return controls;
|
|
return controls.filter((definition) => definition.page === page);
|
|
}
|
|
|
|
export function resolveDashboardControl({ service, action }) {
|
|
return CONTROL_DEFINITIONS.find((definition) => (
|
|
definition.service === service && definition.action === action
|
|
)) || null;
|
|
}
|
|
|
|
export function buildDashboardControlErrorResponse(error, { control = null } = {}) {
|
|
const name = String(error?.name || 'Error');
|
|
const message = String(error?.message || 'Control request failed.');
|
|
const timedOut = name === 'TimeoutError' || /timeout|aborted/i.test(message);
|
|
return {
|
|
statusCode: timedOut ? 504 : 502,
|
|
payload: {
|
|
ok: false,
|
|
error: timedOut ? 'control_timeout' : 'control_failed',
|
|
reason: message,
|
|
control,
|
|
},
|
|
};
|
|
}
|
|
|
|
export function resolveDashboardControlTimeoutMs({ control, config } = {}) {
|
|
const baseTimeoutMs = Number(config?.operatorDashboardUpstreamTimeoutMs || 3_000);
|
|
if (control?.service !== 'trade-executor') return baseTimeoutMs;
|
|
|
|
if (control.action === 'intent-request-preflight') {
|
|
return Math.max(baseTimeoutMs, Number(config?.intentRequestQuoteTimeoutMs || 10_000) + 2_000);
|
|
}
|
|
if (control.action === 'intent-request-submit') {
|
|
return Math.max(
|
|
baseTimeoutMs,
|
|
Number(config?.intentRequestPublishTimeoutMs || 10_000)
|
|
+ Number(config?.intentRequestStatusTimeoutMs || 10_000)
|
|
+ 5_000,
|
|
);
|
|
}
|
|
if (control.action === 'intent-request-refresh-outcomes') {
|
|
return Math.max(baseTimeoutMs, Number(config?.intentRequestStatusTimeoutMs || 10_000) + 5_000);
|
|
}
|
|
return baseTimeoutMs;
|
|
}
|
|
|
|
export function listDashboardServices(config) {
|
|
return SERVICE_DEFINITIONS.map(([service, label, configKey]) => ({
|
|
service,
|
|
label,
|
|
base_url: config[configKey],
|
|
}));
|
|
}
|
|
|
|
export function createDashboardLiveState({
|
|
config,
|
|
recentQuotes = [],
|
|
recentTradeDecisions = [],
|
|
recentExecuteTradeCommands = [],
|
|
recentExecutionResults = [],
|
|
recentQuoteOutcomes = [],
|
|
latestMarketPrice = null,
|
|
latestInventory = null,
|
|
recentSubmissionCount = 0,
|
|
lastSubmissionAt = null,
|
|
activeAlerts = [],
|
|
nearIntentsStatus = null,
|
|
} = {}) {
|
|
const state = {
|
|
config,
|
|
active_pair: config.activePair,
|
|
btc_asset: config.tradingBtc,
|
|
btc_assets: config.tradingBtcAssets || [config.tradingBtc],
|
|
eure_asset: config.tradingEure,
|
|
quote_limit: config.operatorDashboardQuoteLimit || DASHBOARD_LIVE_QUOTE_LIMIT,
|
|
lifecycle_limit: config.operatorDashboardLifecycleLimit || DASHBOARD_LIVE_LIFECYCLE_LIMIT,
|
|
recent_quotes: recentQuotes.slice(0, config.operatorDashboardQuoteLimit || DASHBOARD_LIVE_QUOTE_LIMIT),
|
|
recent_trade_decisions: recentTradeDecisions.slice(0, config.operatorDashboardLifecycleLimit || DASHBOARD_LIVE_LIFECYCLE_LIMIT),
|
|
recent_execute_trade_commands: recentExecuteTradeCommands.slice(0, config.operatorDashboardLifecycleLimit || DASHBOARD_LIVE_LIFECYCLE_LIMIT),
|
|
recent_execution_results: recentExecutionResults.slice(0, config.operatorDashboardLifecycleLimit || DASHBOARD_LIVE_LIFECYCLE_LIMIT),
|
|
recent_quote_outcomes: recentQuoteOutcomes.slice(0, config.operatorDashboardLifecycleLimit || DASHBOARD_LIVE_LIFECYCLE_LIMIT),
|
|
latest_market_price: latestMarketPrice?.payload || latestMarketPrice || null,
|
|
latest_inventory: latestInventory?.payload || latestInventory || null,
|
|
recent_submission_count: Number(recentSubmissionCount || 0),
|
|
last_submission_at: lastSubmissionAt || null,
|
|
near_intents_status: nearIntentsStatus,
|
|
active_alerts: new Map(),
|
|
};
|
|
|
|
for (const alert of activeAlerts) {
|
|
if (!alert) continue;
|
|
state.active_alerts.set(buildAlertKey(alert), alert);
|
|
}
|
|
|
|
return state;
|
|
}
|
|
|
|
export function buildLiveQuoteLifecycleRows(state, { flashQuoteId = null, flashAt = null } = {}) {
|
|
const rows = deriveQuoteLifecycleRows({
|
|
recentQuotes: state.recent_quotes,
|
|
recentTradeDecisions: state.recent_trade_decisions,
|
|
recentExecuteTradeCommands: state.recent_execute_trade_commands,
|
|
recentExecutionResults: state.recent_execution_results,
|
|
recentQuoteOutcomes: state.recent_quote_outcomes,
|
|
limit: state.lifecycle_limit,
|
|
}).map((row) => enrichLifecycleRowForUi({ config: state.config, row }));
|
|
|
|
if (!flashQuoteId) return rows;
|
|
const highlightedAt = flashAt || new Date().toISOString();
|
|
return rows.map((row) => (
|
|
row.quote_id === flashQuoteId
|
|
? { ...row, live_flash_at: highlightedAt }
|
|
: row
|
|
));
|
|
}
|
|
|
|
export function applyDashboardLiveEvent(state, { topic, event }) {
|
|
if (!event?.payload) return [];
|
|
|
|
switch (normalizeDashboardLiveTopic(state, topic)) {
|
|
case 'norm.swap_demand': {
|
|
const quote = normalizeLiveQuote(event.payload, event);
|
|
if (!quote) return [];
|
|
state.recent_quotes = appendUniqueRecentQuote(state.recent_quotes, quote, state.quote_limit);
|
|
return [
|
|
{
|
|
type: 'quotes.recent',
|
|
recent_quotes: state.recent_quotes,
|
|
},
|
|
buildQuoteLifecycleUpdate(state, { flashQuoteId: quote.quote_id }),
|
|
];
|
|
}
|
|
case 'decision.trade_decision': {
|
|
const decision = normalizeLiveDecision(event.payload, event);
|
|
if (!decision) return [];
|
|
state.recent_trade_decisions = appendUniqueRecentEvent(
|
|
state.recent_trade_decisions,
|
|
decision,
|
|
state.lifecycle_limit,
|
|
(entry) => livePayloadKey(entry, ['decision_id', 'quote_id']),
|
|
);
|
|
return [buildQuoteLifecycleUpdate(state, { flashQuoteId: decision.payload?.quote_id })];
|
|
}
|
|
case 'cmd.execute_trade': {
|
|
const command = normalizeLiveCommand(event.payload, event);
|
|
if (!command) return [];
|
|
state.recent_execute_trade_commands = appendUniqueRecentEvent(
|
|
state.recent_execute_trade_commands,
|
|
command,
|
|
state.lifecycle_limit,
|
|
(entry) => livePayloadKey(entry, ['command_id', 'decision_id', 'quote_id']),
|
|
);
|
|
return [buildQuoteLifecycleUpdate(state, { flashQuoteId: command.payload?.quote_id })];
|
|
}
|
|
case 'ref.market_price':
|
|
state.latest_market_price = {
|
|
...event.payload,
|
|
observed_at: event.observed_at || event.payload.observed_at || null,
|
|
ingested_at: event.ingested_at || null,
|
|
};
|
|
return [{
|
|
type: 'status_bar.updated',
|
|
status_bar: buildLiveStatusBar(state),
|
|
}];
|
|
case 'state.intent_inventory':
|
|
state.latest_inventory = {
|
|
...event.payload,
|
|
observed_at: event.observed_at || event.payload.synced_at || null,
|
|
ingested_at: event.ingested_at || null,
|
|
};
|
|
return [{
|
|
type: 'status_bar.updated',
|
|
status_bar: buildLiveStatusBar(state),
|
|
}];
|
|
case 'ops.alert': {
|
|
return [];
|
|
}
|
|
case 'ops.environment_status': {
|
|
state.near_intents_status = event.payload;
|
|
return [
|
|
{
|
|
type: 'status_bar.updated',
|
|
status_bar: buildLiveStatusBar(state),
|
|
},
|
|
{
|
|
type: 'environment_status.updated',
|
|
environment_status: {
|
|
observed_at: event.observed_at || event.payload.observed_at || null,
|
|
ingested_at: event.ingested_at || null,
|
|
payload: event.payload,
|
|
},
|
|
},
|
|
];
|
|
}
|
|
case 'exec.trade_result': {
|
|
const execution = normalizeLiveExecutionResult(event.payload, event);
|
|
if (!execution) return [];
|
|
state.recent_execution_results = appendUniqueRecentEvent(
|
|
state.recent_execution_results,
|
|
execution,
|
|
state.lifecycle_limit,
|
|
(entry) => livePayloadKey(entry, ['command_id', 'decision_id', 'quote_id', 'result_at']),
|
|
);
|
|
const updates = [];
|
|
if (event.payload.status === 'submitted') {
|
|
state.recent_submission_count += 1;
|
|
state.last_submission_at = event.observed_at || event.ingested_at || new Date().toISOString();
|
|
updates.push({
|
|
type: 'status_bar.updated',
|
|
status_bar: buildLiveStatusBar(state),
|
|
});
|
|
}
|
|
updates.push(buildQuoteLifecycleUpdate(state, { flashQuoteId: execution.quote_id }));
|
|
return updates;
|
|
}
|
|
default:
|
|
return [];
|
|
}
|
|
}
|
|
|
|
export function buildDashboardBootstrap({
|
|
config,
|
|
auth,
|
|
portfolioMetric,
|
|
inventorySnapshot,
|
|
marketPrice,
|
|
recentQuotes,
|
|
submissionPage,
|
|
submissionSummary,
|
|
fundingObservations,
|
|
recentDepositStatuses,
|
|
recentTradeDecisions,
|
|
recentExecuteTradeCommands,
|
|
recentExecutionResults,
|
|
recentQuoteOutcomes = [],
|
|
recentIntentRequests = [],
|
|
recentAlertTransitions,
|
|
recentEnvironmentStatuses = [],
|
|
assetCatalog = null,
|
|
pairConfig = null,
|
|
serviceSnapshots,
|
|
nearIntentsStatus = null,
|
|
sourceErrors = [],
|
|
} = {}) {
|
|
const servicesByName = Object.fromEntries(
|
|
(serviceSnapshots || []).map((snapshot) => [snapshot.service, snapshot]),
|
|
);
|
|
const activeAlerts = normalizeAlertList(
|
|
servicesByName['ops-sentinel']?.state?.active_alerts || [],
|
|
);
|
|
const recentAlerts = summarizeRecentAlertTransitions(normalizeAlertList(
|
|
servicesByName['ops-sentinel']?.state?.recent_transitions
|
|
|| recentAlertTransitions?.map((entry) => entry.payload)
|
|
|| [],
|
|
));
|
|
const profitability = buildProfitabilitySummary({
|
|
metric: portfolioMetric,
|
|
submissionSummary,
|
|
});
|
|
const balances = buildBalanceSummary({
|
|
inventorySnapshot,
|
|
marketPrice,
|
|
config,
|
|
portfolioMetric,
|
|
});
|
|
const funding = buildFundingSummary({
|
|
config,
|
|
fundingObservations,
|
|
recentDepositStatuses,
|
|
liquidityState: servicesByName['liquidity-manager']?.state || {},
|
|
});
|
|
const normalizedSubmissionPage = normalizeSubmissionPage({
|
|
config,
|
|
submissionPage,
|
|
});
|
|
const environmentStatus = buildEnvironmentStatusHistory({
|
|
recentEnvironmentStatuses,
|
|
currentStatus: nearIntentsStatus,
|
|
});
|
|
const effectiveNearIntentsStatus = nearIntentsStatus || environmentStatus.current;
|
|
|
|
return {
|
|
session: auth,
|
|
source_errors: sourceErrors,
|
|
default_page: 'funds',
|
|
status_bar: buildStatusBar({
|
|
config,
|
|
profitability,
|
|
inventorySnapshot,
|
|
marketPrice,
|
|
activeAlerts,
|
|
servicesByName,
|
|
nearIntentsStatus: effectiveNearIntentsStatus,
|
|
}),
|
|
funds: {
|
|
profitability,
|
|
balances,
|
|
funding,
|
|
recent_deposits: funding.credited_deposits,
|
|
recent_withdrawals: buildRecentWithdrawals({
|
|
config,
|
|
liquidityState: servicesByName['liquidity-manager']?.state || {},
|
|
}),
|
|
recent_submission_terms: buildRecentSubmissionTerms({
|
|
config,
|
|
submissions: normalizedSubmissionPage.items,
|
|
}),
|
|
recent_quotes: (recentQuotes || []).slice(0, config.operatorDashboardQuoteLimit || DASHBOARD_LIVE_QUOTE_LIMIT),
|
|
intent_requests: buildIntentRequestSummary({
|
|
config,
|
|
intentRequests: recentIntentRequests,
|
|
executorState: servicesByName['trade-executor']?.state || {},
|
|
}),
|
|
submission_ledger: normalizedSubmissionPage,
|
|
controls: listDashboardControls({ page: 'funds' }),
|
|
caveats: profitability.caveats,
|
|
},
|
|
strategy: buildStrategySummary({
|
|
config,
|
|
servicesByName,
|
|
activeAlerts,
|
|
assetCatalog,
|
|
pairConfig,
|
|
fundingHandles: funding.handles,
|
|
recentQuotes,
|
|
recentTradeDecisions,
|
|
recentExecuteTradeCommands,
|
|
recentExecutionResults,
|
|
recentQuoteOutcomes,
|
|
}),
|
|
system: buildSystemSummary({
|
|
servicesByName,
|
|
activeAlerts,
|
|
recentAlerts,
|
|
nearIntentsStatus: effectiveNearIntentsStatus,
|
|
environmentStatus,
|
|
}),
|
|
};
|
|
}
|
|
|
|
export function buildProfitabilitySummary({ metric, submissionSummary } = {}) {
|
|
const externalCashFlows = metric?.payload?.external_cash_flows || {};
|
|
const externalFlowCount = Number(externalCashFlows.flow_count || 0);
|
|
const externalFlowAdjusted = externalFlowCount > 0;
|
|
const summary = {
|
|
computed_at: metric?.computed_at || null,
|
|
current_total_portfolio_value_eure: metric?.payload?.current_portfolio_value_eure || null,
|
|
deposit_baseline_value_eure: metric?.payload?.baseline_portfolio_value_eure_at_baseline_price || null,
|
|
simple_hold_value_eure: metric?.payload?.baseline_portfolio_value_eure_at_current_price || null,
|
|
pnl_vs_deposit_baseline_eure: null,
|
|
pnl_vs_simple_hold_eure: null,
|
|
market_move_contribution_eure: null,
|
|
portfolio_vs_simple_hold_eure: null,
|
|
trading_contribution_eure: null,
|
|
baseline_anchor_at: metric?.baseline_anchor_at || null,
|
|
baseline_status: metric?.baseline_status || metric?.payload?.baseline_status || 'unavailable',
|
|
external_flow_adjusted: externalFlowAdjusted,
|
|
external_flow_count: externalFlowCount,
|
|
external_deposit_count: Number(externalCashFlows.deposit_count || 0),
|
|
external_withdrawal_count: Number(externalCashFlows.withdrawal_count || 0),
|
|
latest_external_flow_at: externalCashFlows.latest_effective_at || null,
|
|
net_external_flow_value_eure: externalCashFlows.net_value_eure_at_flow_time || '0',
|
|
recent_submission_count: submissionSummary?.total ?? 0,
|
|
last_submission_at: submissionSummary?.last_submission_at || null,
|
|
caveats: [
|
|
'Portfolio value and simple-hold comparison use durable inventory and reference prices.',
|
|
'This is not realized per-trade PnL; completed trades require linked outcome and settlement records.',
|
|
],
|
|
};
|
|
|
|
if (summary.current_total_portfolio_value_eure == null) {
|
|
summary.caveats.unshift('Profitability is unavailable until durable portfolio metrics exist.');
|
|
return summary;
|
|
}
|
|
|
|
if (summary.deposit_baseline_value_eure) {
|
|
summary.pnl_vs_deposit_baseline_eure = formatDecimalDifference(
|
|
summary.current_total_portfolio_value_eure,
|
|
summary.deposit_baseline_value_eure,
|
|
);
|
|
}
|
|
|
|
if (summary.simple_hold_value_eure) {
|
|
summary.pnl_vs_simple_hold_eure = formatDecimalDifference(
|
|
summary.current_total_portfolio_value_eure,
|
|
summary.simple_hold_value_eure,
|
|
);
|
|
}
|
|
|
|
if (summary.deposit_baseline_value_eure && summary.simple_hold_value_eure) {
|
|
summary.market_move_contribution_eure = formatDecimalDifference(
|
|
summary.simple_hold_value_eure,
|
|
summary.deposit_baseline_value_eure,
|
|
);
|
|
}
|
|
|
|
if (summary.simple_hold_value_eure) {
|
|
summary.portfolio_vs_simple_hold_eure = formatDecimalDifference(
|
|
summary.current_total_portfolio_value_eure,
|
|
summary.simple_hold_value_eure,
|
|
);
|
|
summary.trading_contribution_eure = null;
|
|
}
|
|
|
|
if (summary.external_flow_adjusted) {
|
|
summary.caveats.unshift(
|
|
`Later credited deposits and completed withdrawals (${summary.external_flow_count}) are treated as external cash flows, not trading PnL.`,
|
|
);
|
|
}
|
|
|
|
return summary;
|
|
}
|
|
|
|
export function buildLiveStatusBar(state) {
|
|
return {
|
|
near_intents_upstream_status: state.near_intents_status?.status || null,
|
|
near_intents_upstream_label: state.near_intents_status?.label || null,
|
|
near_intents_upstream_reason: state.near_intents_status?.decisive_reason || null,
|
|
near_intents_upstream_observed_at: state.near_intents_status?.observed_at || null,
|
|
latest_reference_price_eure_per_btc: state.latest_market_price?.eure_per_btc || null,
|
|
market_observed_at:
|
|
state.latest_market_price?.observed_at
|
|
|| state.latest_market_price?.ingested_at
|
|
|| null,
|
|
market_freshness_ms: ageMs(
|
|
state.latest_market_price?.observed_at || state.latest_market_price?.ingested_at,
|
|
),
|
|
inventory_observed_at:
|
|
state.latest_inventory?.synced_at
|
|
|| state.latest_inventory?.observed_at
|
|
|| state.latest_inventory?.ingested_at
|
|
|| null,
|
|
inventory_freshness_ms: ageMs(
|
|
state.latest_inventory?.synced_at
|
|
|| state.latest_inventory?.observed_at
|
|
|| state.latest_inventory?.ingested_at,
|
|
),
|
|
current_total_portfolio_value_eure: computeCurrentPortfolioValue({
|
|
inventory: state.latest_inventory,
|
|
marketPrice: state.latest_market_price,
|
|
btcAsset: state.btc_asset,
|
|
btcAssets: state.btc_assets,
|
|
eureAsset: state.eure_asset,
|
|
}),
|
|
active_alert_count: 0,
|
|
highest_alert_severity: null,
|
|
recent_submission_count: state.recent_submission_count,
|
|
last_submission_at: state.last_submission_at,
|
|
};
|
|
}
|
|
|
|
function buildStatusBar({
|
|
config,
|
|
profitability,
|
|
inventorySnapshot,
|
|
marketPrice,
|
|
activeAlerts,
|
|
servicesByName,
|
|
nearIntentsStatus = null,
|
|
}) {
|
|
return {
|
|
active_pair: config.activePair,
|
|
near_intents_upstream_status: nearIntentsStatus?.status || null,
|
|
near_intents_upstream_label: nearIntentsStatus?.label || null,
|
|
near_intents_upstream_reason: nearIntentsStatus?.decisive_reason || null,
|
|
near_intents_upstream_observed_at: nearIntentsStatus?.observed_at || null,
|
|
latest_reference_price_eure_per_btc: marketPrice?.payload?.eure_per_btc || null,
|
|
market_observed_at: marketPrice?.payload?.observed_at || marketPrice?.ingested_at || null,
|
|
market_freshness_ms: ageMs(marketPrice?.payload?.observed_at || marketPrice?.ingested_at),
|
|
inventory_observed_at:
|
|
inventorySnapshot?.payload?.synced_at || inventorySnapshot?.ingested_at || null,
|
|
inventory_freshness_ms: ageMs(
|
|
inventorySnapshot?.payload?.synced_at || inventorySnapshot?.ingested_at,
|
|
),
|
|
active_alert_count: 0,
|
|
highest_alert_severity: null,
|
|
strategy_armed: servicesByName['strategy-engine']?.state?.armed ?? null,
|
|
executor_armed: servicesByName['trade-executor']?.state?.armed ?? null,
|
|
current_total_portfolio_value_eure: profitability.current_total_portfolio_value_eure,
|
|
recent_submission_count: profitability.recent_submission_count,
|
|
last_submission_at: profitability.last_submission_at,
|
|
};
|
|
}
|
|
|
|
function buildBalanceSummary({ inventorySnapshot, marketPrice, config, portfolioMetric = null }) {
|
|
const inventory = inventorySnapshot?.payload || {};
|
|
const spendable = inventory.spendable || {};
|
|
const pendingInbound = inventory.pending_inbound || {};
|
|
const pendingOutbound = inventory.pending_outbound || {};
|
|
const balanceAssets = config.trackedAssets?.length
|
|
? config.trackedAssets
|
|
: [...config.assetRegistry.values()];
|
|
const metricValuationsByAssetId = new Map(
|
|
(portfolioMetric?.payload?.current_inventory?.valued_assets || [])
|
|
.filter((asset) => asset?.asset_id)
|
|
.map((asset) => [asset.asset_id, asset]),
|
|
);
|
|
|
|
return {
|
|
synced_at: inventory.synced_at || inventorySnapshot?.ingested_at || null,
|
|
reconciliation_status: inventory.reconciliation_status || null,
|
|
items: balanceAssets.map((asset) => {
|
|
const spendableUnits = String(spendable[asset.assetId] || '0');
|
|
const pendingInboundUnits = String(pendingInbound[asset.assetId] || '0');
|
|
const pendingOutboundUnits = String(pendingOutbound[asset.assetId] || '0');
|
|
const metricValuation = metricValuationsByAssetId.get(asset.assetId);
|
|
return {
|
|
asset_id: asset.assetId,
|
|
symbol: asset.symbol,
|
|
label: asset.label || asset.symbol,
|
|
role: asset.role || null,
|
|
chain: asset.chain,
|
|
spendable_units: spendableUnits,
|
|
spendable: formatUnits(spendableUnits, asset.decimals),
|
|
pending_inbound_units: pendingInboundUnits,
|
|
pending_inbound: formatUnits(pendingInboundUnits, asset.decimals),
|
|
pending_outbound_units: pendingOutboundUnits,
|
|
pending_outbound: formatUnits(pendingOutboundUnits, asset.decimals),
|
|
eur_value_eure: metricValuation?.value_eure
|
|
|| valueAssetInEur({
|
|
asset,
|
|
units: spendableUnits,
|
|
marketPrice: marketPrice?.payload || marketPrice || null,
|
|
}),
|
|
eur_value_source: metricValuation?.valuation_source || null,
|
|
};
|
|
}),
|
|
};
|
|
}
|
|
|
|
function buildFundingSummary({ config, fundingObservations, recentDepositStatuses, liquidityState }) {
|
|
const observations = (fundingObservations || []).map((entry) => entry.payload || entry);
|
|
const summary = summarizeFundingObservations(observations, {
|
|
now: new Date().toISOString(),
|
|
});
|
|
const observerItems = observations.map((observation) => normalizeFundingObservationForUi({
|
|
config,
|
|
observation,
|
|
}));
|
|
const depositItems = buildRecentDepositItems({
|
|
config,
|
|
recentDepositStatuses,
|
|
liquidityState,
|
|
});
|
|
const recentFundingActivity = mergeFundingActivityItems({
|
|
observerItems,
|
|
depositItems,
|
|
});
|
|
|
|
return {
|
|
latest_observed_at: latestFundingActivityAt(recentFundingActivity, summary.latest_funding_observation_at),
|
|
control_state: {
|
|
paused: liquidityState?.paused ?? null,
|
|
funding_observer_paused: liquidityState?.funding_observer_paused ?? null,
|
|
withdrawals_frozen: liquidityState?.withdrawals_frozen ?? null,
|
|
withdrawal_defaults: liquidityState?.withdrawal_defaults || {},
|
|
},
|
|
handles: Object.entries(liquidityState?.deposit_addresses || {}).map(([chain, details]) => {
|
|
const chainAssets = (config.trackedAssets || [...config.assetRegistry.values()])
|
|
.filter((asset) => asset.chain === chain);
|
|
return {
|
|
chain,
|
|
asset_id: chainAssets.length === 1 ? chainAssets[0].assetId : null,
|
|
asset_ids: chainAssets.map((asset) => asset.assetId),
|
|
symbol: chainAssets.length === 1
|
|
? chainAssets[0].symbol
|
|
: chainAssets.map((asset) => asset.label || asset.symbol).join(', '),
|
|
label: chainAssets.length === 1
|
|
? (chainAssets[0].label || chainAssets[0].symbol)
|
|
: `${chain} funding handle`,
|
|
address: details?.address || null,
|
|
memo: details?.memo || null,
|
|
refreshed_at: details?.refreshed_at || null,
|
|
};
|
|
}),
|
|
credited_deposits: recentFundingActivity
|
|
.filter((observation) => CREDITED_FUNDING_STATUSES.has(String(observation?.status || '').toUpperCase()))
|
|
.sort((left, right) => sortTimestamps(
|
|
fundingActivityTimestamp(right),
|
|
fundingActivityTimestamp(left),
|
|
))
|
|
.slice(0, 10)
|
|
.map((observation) => ({ ...observation })),
|
|
pre_credit_by_asset: Object.values(summary.funding_visibility_by_asset || {}).map((entry) => {
|
|
const asset = config.assetRegistry.get(entry.asset_id);
|
|
return {
|
|
asset_id: entry.asset_id,
|
|
symbol: asset?.symbol || entry.asset_id,
|
|
asset_symbol: asset?.label || asset?.symbol || entry.asset_id,
|
|
pre_credit_total_units: entry.pre_credit_total || '0',
|
|
pre_credit_total: formatUnits(entry.pre_credit_total || '0', asset?.decimals || 0),
|
|
latest_status: entry.latest_status,
|
|
latest_observation_at: entry.latest_observation_at,
|
|
};
|
|
}),
|
|
pre_credit_by_handle: Object.values(summary.funding_observations_by_handle || {}).map((entry) => {
|
|
const asset = config.assetRegistry.get(entry.asset_id);
|
|
return {
|
|
funding_handle: entry.funding_handle,
|
|
chain: entry.chain,
|
|
asset_id: entry.asset_id,
|
|
symbol: asset?.symbol || entry.asset_id,
|
|
asset_symbol: asset?.label || asset?.symbol || entry.asset_id,
|
|
pre_credit_total_units: entry.pre_credit_total || '0',
|
|
pre_credit_total: formatUnits(entry.pre_credit_total || '0', asset?.decimals || 0),
|
|
latest_status: entry.latest_status,
|
|
latest_observation_at: entry.latest_observation_at,
|
|
observation_count: entry.observations?.length || 0,
|
|
};
|
|
}),
|
|
recent_observations: recentFundingActivity
|
|
.sort((left, right) => sortTimestamps(
|
|
fundingActivityTimestamp(right),
|
|
fundingActivityTimestamp(left),
|
|
))
|
|
.slice(0, 10)
|
|
.map((observation) => ({ ...observation })),
|
|
};
|
|
}
|
|
|
|
function buildIntentRequestSummary({ config, intentRequests = [], executorState = {} } = {}) {
|
|
const defaultPair = config.defaultTakerPair || null;
|
|
const strategyConfig = defaultPair?.strategyConfig || null;
|
|
const usingDbPairConfig = Boolean(config.tradingConfigLoaded && strategyConfig);
|
|
const sourceAsset = defaultPair?.assetIn || config.tradingEure;
|
|
const destinationAsset = defaultPair?.assetOut || config.tradingBtc;
|
|
return {
|
|
defaults: {
|
|
source_symbol: sourceAsset?.symbol || 'Source',
|
|
destination_symbol: destinationAsset?.symbol || 'Destination',
|
|
amount_eure: String(strategyConfig?.requestDefaultNotional ?? config.intentRequestDefaultAmountEure ?? 5),
|
|
max_amount_eure: usingDbPairConfig
|
|
? strategyConfig.requestMaxNotional ?? null
|
|
: String(config.intentRequestMaxAmountEure || 5),
|
|
slippage_bps: Number(strategyConfig?.slippageBps ?? config.intentRequestDefaultSlippageBps ?? 200),
|
|
max_slippage_bps: usingDbPairConfig
|
|
? strategyConfig.requestMaxSlippageBps ?? null
|
|
: Number(config.intentRequestMaxSlippageBps ?? 200),
|
|
},
|
|
executor_armed: executorState.armed ?? null,
|
|
executor_paused: executorState.paused ?? null,
|
|
request_creation_state: executorState.request_creation || null,
|
|
items: (intentRequests || []).map((request) => normalizeIntentRequestForUi({ config, request })),
|
|
caveat:
|
|
'Own request relay acceptance is not a completed trade. Completed requires durable EURe decrease and BTC increase evidence.',
|
|
};
|
|
}
|
|
|
|
function normalizeIntentRequestForUi({ config, request }) {
|
|
const sourceAsset = config.assetRegistry.get(request.source_asset_id) || config.tradingEure;
|
|
const destinationAsset = config.assetRegistry.get(request.destination_asset_id) || config.tradingBtc;
|
|
return {
|
|
...request,
|
|
source_amount: formatUnits(request.source_amount_units || '0', sourceAsset?.decimals || 0),
|
|
expected_destination_amount: formatUnits(
|
|
request.expected_destination_amount_units || '0',
|
|
destinationAsset?.decimals || 0,
|
|
),
|
|
min_destination_amount: formatUnits(
|
|
request.min_destination_amount_units || '0',
|
|
destinationAsset?.decimals || 0,
|
|
),
|
|
quoted_destination_amount: request.quoted_destination_amount_units == null
|
|
? null
|
|
: formatUnits(request.quoted_destination_amount_units || '0', destinationAsset?.decimals || 0),
|
|
settlement_summary: buildSettlementSummary({
|
|
config,
|
|
delta: request.attributed_inventory_delta,
|
|
attributionStatus: request.attribution_status,
|
|
attributionMethod: request.attribution_method,
|
|
subject: 'request',
|
|
}),
|
|
};
|
|
}
|
|
|
|
function buildRecentWithdrawals({ config, liquidityState }) {
|
|
return Object.values(liquidityState?.tracked_withdrawals || {})
|
|
.sort((left, right) => sortTimestamps(
|
|
right.last_checked_at || right.submitted_at || right.noted_at,
|
|
left.last_checked_at || left.submitted_at || left.noted_at,
|
|
))
|
|
.slice(0, 10)
|
|
.map((withdrawal) => {
|
|
const asset = config.assetRegistry.get(withdrawal.asset_id);
|
|
return {
|
|
withdrawal_hash: withdrawal.withdrawal_hash,
|
|
asset_id: withdrawal.asset_id,
|
|
symbol: asset?.symbol || withdrawal.asset_id,
|
|
asset_symbol: asset?.label || asset?.symbol || withdrawal.asset_id,
|
|
chain: withdrawal.chain || null,
|
|
amount_units: String(withdrawal.amount || '0'),
|
|
amount_display: formatUnits(withdrawal.amount || '0', asset?.decimals || 0),
|
|
amount: formatUnits(withdrawal.amount || '0', asset?.decimals || 0),
|
|
status: withdrawal.status || null,
|
|
address: withdrawal.address || null,
|
|
destination_address: withdrawal.address || null,
|
|
requested_at: withdrawal.submitted_at || withdrawal.noted_at || null,
|
|
completed_at: withdrawal.status === 'COMPLETED' ? withdrawal.last_checked_at : null,
|
|
submitted_at: withdrawal.submitted_at || null,
|
|
last_checked_at: withdrawal.last_checked_at || null,
|
|
noted_at: withdrawal.noted_at || null,
|
|
};
|
|
});
|
|
}
|
|
|
|
function buildRecentSubmissionTerms({ config, submissions }) {
|
|
return (submissions || []).slice(0, 10).map((submission) => {
|
|
const assetIn = config.assetRegistry.get(submission.asset_in);
|
|
const assetOut = config.assetRegistry.get(submission.asset_out);
|
|
return {
|
|
observed_at: submission.observed_at,
|
|
quote_id: submission.quote_id,
|
|
request_kind: submission.request_kind,
|
|
asset_in: submission.asset_in,
|
|
asset_in_symbol: assetIn?.symbol || submission.asset_in,
|
|
amount_in_units: submission.amount_in,
|
|
amount_in: formatUnits(submission.amount_in || '0', assetIn?.decimals || 0),
|
|
asset_out: submission.asset_out,
|
|
asset_out_symbol: assetOut?.symbol || submission.asset_out,
|
|
amount_out_units: submission.amount_out,
|
|
amount_out: formatUnits(submission.amount_out || '0', assetOut?.decimals || 0),
|
|
};
|
|
});
|
|
}
|
|
|
|
function normalizeSubmissionPage({ config, submissionPage }) {
|
|
return {
|
|
...submissionPage,
|
|
items: (submissionPage?.items || []).map((trade) => normalizeTradeForUi({
|
|
config,
|
|
trade,
|
|
})),
|
|
};
|
|
}
|
|
|
|
const LIFECYCLE_TONE_BY_STATE = {
|
|
observed: 'info',
|
|
evaluated: 'healthy',
|
|
command_emitted: 'info',
|
|
rejected: 'warning',
|
|
blocked: 'warning',
|
|
failed: 'critical',
|
|
submitted: 'healthy',
|
|
awaiting_outcome: 'info',
|
|
not_filled: 'warning',
|
|
completed: 'healthy',
|
|
};
|
|
|
|
const HUMAN_REASON_TEXT = {
|
|
actionable: 'Strategy approved the quote.',
|
|
below_edge_threshold: 'Below edge threshold.',
|
|
duplicate_quote_id: 'Duplicate quote id.',
|
|
executor_disarmed: 'Executor is disarmed.',
|
|
executor_paused: 'Executor intake is paused.',
|
|
inventory_unavailable: 'Inventory unavailable.',
|
|
pending_deposit_not_credited: 'Funding is not credited yet.',
|
|
quote_expired: 'Quote expired.',
|
|
quote_response_ack: 'Quote response acknowledged by the relay.',
|
|
quote_response_ok: 'Quote response accepted by the relay.',
|
|
awaiting_outcome: 'No durable venue outcome is recorded yet.',
|
|
deadline_elapsed_without_settlement:
|
|
'No matching inventory delta was observed after the quote-response deadline.',
|
|
matched_inventory_delta:
|
|
'Matched to an observed inventory delta. Attribution is heuristic unless the source says linked settlement.',
|
|
ambiguous_inventory_delta_match: 'Inventory movement matched more than one quote candidate.',
|
|
reason_unknown: 'Reason not recorded.',
|
|
stale_reference_price: 'Reference price is stale.',
|
|
strategy_approved: 'Strategy approved the quote.',
|
|
strategy_disarmed: 'Strategy is disarmed.',
|
|
submission_failed: 'Submission failed.',
|
|
unsupported_pair: 'Unsupported pair.',
|
|
};
|
|
|
|
const COMPLETED_OUTCOME_STATUSES = new Set(['completed', 'filled', 'settled', 'finalized']);
|
|
const NOT_FILLED_OUTCOME_STATUSES = new Set(['expired', 'not_filled', 'cancelled']);
|
|
|
|
export function deriveQuoteLifecycleRows({
|
|
recentQuotes = [],
|
|
recentTradeDecisions = [],
|
|
recentExecuteTradeCommands = [],
|
|
recentExecutionResults = [],
|
|
recentQuoteOutcomes = [],
|
|
limit = 20,
|
|
} = {}) {
|
|
const rowsByKey = new Map();
|
|
|
|
for (const quote of recentQuotes || []) {
|
|
const normalizedQuote = normalizeLifecycleQuote(quote);
|
|
const row = ensureLifecycleRow(rowsByKey, normalizedQuote?.quote_id || `quote:${normalizedQuote?.observed_at || normalizedQuote?.ingested_at || rowsByKey.size}`);
|
|
mergeLifecycleEvidence(row, {
|
|
quote_id: normalizedQuote?.quote_id || null,
|
|
pair: normalizedQuote?.pair || null,
|
|
request_kind: normalizedQuote?.request_kind || null,
|
|
asset_in: normalizedQuote?.asset_in || null,
|
|
asset_out: normalizedQuote?.asset_out || null,
|
|
amount_in: normalizedQuote?.amount_in || null,
|
|
amount_out: normalizedQuote?.amount_out || null,
|
|
quote: normalizedQuote,
|
|
quote_observed_at: normalizedQuote?.observed_at || normalizedQuote?.ingested_at || null,
|
|
});
|
|
}
|
|
|
|
for (const decisionEntry of recentTradeDecisions || []) {
|
|
const decision = normalizeDecision({
|
|
...(decisionEntry?.payload || decisionEntry || {}),
|
|
decision_at:
|
|
decisionEntry?.payload?.decision_at
|
|
|| decisionEntry?.decision_at
|
|
|| decisionEntry?.observed_at
|
|
|| decisionEntry?.ingested_at
|
|
|| null,
|
|
});
|
|
if (!decision) continue;
|
|
const row = ensureLifecycleRow(rowsByKey, decision.quote_id || decision.decision_id || `decision:${decision.decision_at || rowsByKey.size}`);
|
|
mergeLifecycleEvidence(row, {
|
|
quote_id: decision.quote_id,
|
|
decision_id: decision.decision_id,
|
|
pair: decision.pair,
|
|
pair_id: decision.pair_id,
|
|
pair_config_id: decision.pair_config_id,
|
|
pair_config_version: decision.pair_config_version,
|
|
edge_bps: decision.edge_bps,
|
|
direction: decision.direction,
|
|
request_kind: decision.request_kind,
|
|
gross_edge_pct: decision.gross_edge_pct,
|
|
eure_notional: decision.eure_notional,
|
|
decision,
|
|
decision_at: decision.decision_at || null,
|
|
});
|
|
}
|
|
|
|
for (const commandEntry of recentExecuteTradeCommands || []) {
|
|
const command = normalizeCommand({
|
|
...(commandEntry?.payload || commandEntry || {}),
|
|
command_at:
|
|
commandEntry?.command_at
|
|
|| commandEntry?.observed_at
|
|
|| commandEntry?.ingested_at
|
|
|| null,
|
|
});
|
|
if (!command) continue;
|
|
const row = ensureLifecycleRow(rowsByKey, command.quote_id || command.decision_id || command.command_id || `command:${command.command_at || rowsByKey.size}`);
|
|
mergeLifecycleEvidence(row, {
|
|
quote_id: command.quote_id,
|
|
decision_id: command.decision_id,
|
|
command_id: command.command_id,
|
|
pair: command.pair,
|
|
pair_id: command.pair_id,
|
|
pair_config_id: command.pair_config_id,
|
|
pair_config_version: command.pair_config_version,
|
|
edge_bps: command.edge_bps,
|
|
direction: command.direction,
|
|
request_kind: command.request_kind,
|
|
asset_in: command.asset_in || null,
|
|
asset_out: command.asset_out || null,
|
|
amount_in: command.amount_in || null,
|
|
amount_out: command.amount_out || null,
|
|
command,
|
|
command_at: command.command_at || null,
|
|
});
|
|
}
|
|
|
|
for (const execution of recentExecutionResults || []) {
|
|
const row = ensureLifecycleRow(rowsByKey, execution?.quote_id || execution?.decision_id || execution?.command_id || `execution:${execution?.result_at || rowsByKey.size}`);
|
|
mergeLifecycleEvidence(row, {
|
|
quote_id: execution?.quote_id || null,
|
|
decision_id: execution?.decision_id || null,
|
|
command_id: execution?.command_id || null,
|
|
pair: execution?.pair || null,
|
|
execution,
|
|
execution_result_at: execution?.result_at || null,
|
|
});
|
|
}
|
|
|
|
for (const outcome of recentQuoteOutcomes || []) {
|
|
const row = ensureLifecycleRow(rowsByKey, outcome?.quote_id || outcome?.decision_id || outcome?.command_id || `outcome:${outcome?.outcome_observed_at || rowsByKey.size}`);
|
|
mergeLifecycleEvidence(row, {
|
|
quote_id: outcome?.quote_id || null,
|
|
decision_id: outcome?.decision_id || null,
|
|
command_id: outcome?.command_id || null,
|
|
pair: outcome?.pair || null,
|
|
direction: outcome?.direction || null,
|
|
request_kind: outcome?.request_kind || null,
|
|
gross_edge_pct: outcome?.gross_edge_pct || null,
|
|
eure_notional: outcome?.eure_notional || null,
|
|
outcome,
|
|
command_at: outcome?.command_at || null,
|
|
execution_result_at: outcome?.submitted_at || null,
|
|
outcome_observed_at: outcome?.outcome_observed_at || outcome?.submitted_at || null,
|
|
});
|
|
}
|
|
|
|
const finalized = [...rowsByKey.values()]
|
|
.map((row) => finalizeLifecycleRow(row))
|
|
.sort((left, right) => sortTimestamps(right.quote_activity_at, left.quote_activity_at));
|
|
|
|
return limit == null ? finalized : finalized.slice(0, limit);
|
|
}
|
|
|
|
function ensureLifecycleRow(rowsByKey, key) {
|
|
if (!rowsByKey.has(key)) {
|
|
rowsByKey.set(key, {
|
|
quote_id: null,
|
|
decision_id: null,
|
|
command_id: null,
|
|
pair: null,
|
|
pair_id: null,
|
|
pair_config_id: null,
|
|
pair_config_version: null,
|
|
edge_bps: null,
|
|
direction: null,
|
|
request_kind: null,
|
|
gross_edge_pct: null,
|
|
eure_notional: null,
|
|
quote_observed_at: null,
|
|
decision_at: null,
|
|
asset_in: null,
|
|
asset_out: null,
|
|
amount_in: null,
|
|
amount_out: null,
|
|
command_at: null,
|
|
execution_result_at: null,
|
|
outcome_observed_at: null,
|
|
quote: null,
|
|
decision: null,
|
|
command: null,
|
|
execution: null,
|
|
outcome: null,
|
|
});
|
|
}
|
|
return rowsByKey.get(key);
|
|
}
|
|
|
|
function mergeLifecycleEvidence(row, next) {
|
|
for (const [key, value] of Object.entries(next || {})) {
|
|
if (value != null && row[key] == null) {
|
|
row[key] = value;
|
|
}
|
|
}
|
|
if (next?.quote) row.quote = next.quote;
|
|
if (next?.decision) row.decision = next.decision;
|
|
if (next?.command) row.command = next.command;
|
|
if (next?.execution) row.execution = next.execution;
|
|
if (next?.outcome) row.outcome = next.outcome;
|
|
}
|
|
|
|
function finalizeLifecycleRow(row) {
|
|
const decision = row.decision || null;
|
|
const execution = row.execution || null;
|
|
const outcome = row.outcome || null;
|
|
const outcomeStatus = normalizeLifecycleToken(
|
|
outcome?.outcome_status
|
|
|| execution?.outcome_status
|
|
|| execution?.outcome_reason
|
|
|| null,
|
|
);
|
|
let lifecycle_state = 'observed';
|
|
let lifecycle_label = 'Observed';
|
|
let reason_code = 'reason_unknown';
|
|
let reason_text = 'Strategy evaluation not recorded yet.';
|
|
|
|
if (outcomeStatus && COMPLETED_OUTCOME_STATUSES.has(outcomeStatus)) {
|
|
lifecycle_state = 'completed';
|
|
lifecycle_label = 'Completed';
|
|
reason_code = normalizeLifecycleToken(
|
|
outcome?.outcome_reason
|
|
|| execution?.outcome_reason
|
|
|| execution?.result_code
|
|
|| 'completed',
|
|
);
|
|
reason_text = buildCompletedOutcomeText({ outcome, reasonCode: reason_code });
|
|
} else if (outcomeStatus && NOT_FILLED_OUTCOME_STATUSES.has(outcomeStatus)) {
|
|
lifecycle_state = 'not_filled';
|
|
lifecycle_label = 'Not filled';
|
|
reason_code = normalizeLifecycleToken(
|
|
outcome?.outcome_reason
|
|
|| execution?.outcome_reason
|
|
|| execution?.result_code
|
|
|| outcomeStatus,
|
|
);
|
|
reason_text = buildNotFilledText({ outcome, reasonCode: reason_code });
|
|
} else if (outcomeStatus === 'awaiting_outcome') {
|
|
lifecycle_state = 'awaiting_outcome';
|
|
lifecycle_label = 'Awaiting outcome';
|
|
reason_code = normalizeLifecycleToken(outcome?.outcome_reason || 'awaiting_outcome');
|
|
reason_text = humanizeReasonCode(reason_code, 'No durable venue outcome is recorded yet.');
|
|
} else if (execution?.status === 'submitted') {
|
|
lifecycle_state = 'submitted';
|
|
lifecycle_label = 'Submitted';
|
|
reason_code = normalizeLifecycleToken(execution?.result_code || 'awaiting_outcome');
|
|
reason_text = `${humanizeReasonCode(reason_code, 'Submitted to the relay.')} No durable venue outcome is recorded yet.`;
|
|
} else if (execution?.status === 'failed') {
|
|
lifecycle_state = 'failed';
|
|
lifecycle_label = 'Submission failed';
|
|
reason_code = normalizeLifecycleToken(execution?.result_code || 'submission_failed');
|
|
reason_text = buildExecutionFailureText(execution, reason_code);
|
|
} else if (execution?.status === 'rejected') {
|
|
lifecycle_state = 'blocked';
|
|
lifecycle_label = 'Blocked before submit';
|
|
reason_code = normalizeLifecycleToken(execution?.result_code || 'reason_unknown');
|
|
reason_text = buildExecutorBlockText(execution, reason_code);
|
|
} else if (row.command_id || row.command_at) {
|
|
lifecycle_state = 'command_emitted';
|
|
lifecycle_label = 'Awaiting executor';
|
|
reason_code = 'awaiting_executor';
|
|
reason_text = 'Execute command recorded, but no executor result is stored yet.';
|
|
} else if (decision?.decision === 'rejected') {
|
|
lifecycle_state = 'rejected';
|
|
lifecycle_label = 'Rejected by strategy';
|
|
reason_code = normalizeLifecycleToken(decision?.decision_reason || 'reason_unknown');
|
|
reason_text = humanizeReasonCode(reason_code, 'Strategy rejected the quote.');
|
|
} else if (decision?.decision) {
|
|
lifecycle_state = 'evaluated';
|
|
lifecycle_label = 'Approved by strategy';
|
|
reason_code = normalizeLifecycleToken(decision?.decision_reason || 'strategy_approved');
|
|
reason_text = 'Strategy approved the quote, but no durable execute command is recorded yet.';
|
|
}
|
|
|
|
return {
|
|
...row,
|
|
lifecycle_state,
|
|
lifecycle_label,
|
|
lifecycle_tone: LIFECYCLE_TONE_BY_STATE[lifecycle_state] || 'unknown',
|
|
reason_code,
|
|
reason_text,
|
|
latest_stage_at:
|
|
row.outcome_observed_at
|
|
|| row.execution_result_at
|
|
|| row.command_at
|
|
|| row.decision_at
|
|
|| row.quote_observed_at
|
|
|| null,
|
|
quote_activity_at:
|
|
row.quote_observed_at
|
|
|| row.decision_at
|
|
|| row.command_at
|
|
|| row.execution_result_at
|
|
|| row.outcome_observed_at
|
|
|| null,
|
|
stage_details: {
|
|
quote_observed_at: row.quote_observed_at,
|
|
decision_at: row.decision_at,
|
|
command_at: row.command_at,
|
|
execution_result_at: row.execution_result_at,
|
|
strategy_decision: decision?.decision || null,
|
|
strategy_reason_code: decision?.decision_reason || null,
|
|
execution_status: execution?.status || null,
|
|
execution_result_code: execution?.result_code || null,
|
|
execution_outcome_status: execution?.outcome_status || null,
|
|
durable_outcome_status: outcome?.outcome_status || null,
|
|
durable_outcome_source: outcome?.outcome_source || null,
|
|
attribution_status: outcome?.attribution_status || null,
|
|
attribution_method: outcome?.attribution_method || null,
|
|
},
|
|
outcome_source: outcome?.outcome_source || null,
|
|
outcome_status: outcome?.outcome_status || execution?.outcome_status || null,
|
|
attribution_status: outcome?.attribution_status || execution?.attribution_status || null,
|
|
attribution_method: outcome?.attribution_method || execution?.attribution_method || null,
|
|
attributed_inventory_delta:
|
|
outcome?.attributed_inventory_delta
|
|
|| execution?.attributed_inventory_delta
|
|
|| null,
|
|
has_settlement_evidence: hasSettlementEvidence(outcome || execution),
|
|
};
|
|
}
|
|
|
|
function buildCompletedOutcomeText({ outcome, reasonCode }) {
|
|
const base = humanizeReasonCode(reasonCode, 'Completed.');
|
|
if (!outcome?.attribution_status) return `${base} Settlement attribution is not stored.`;
|
|
if (outcome.attribution_status === 'heuristic_match') {
|
|
return `${base} Matched to inventory movement by exact asset-unit delta; venue terminal status is not stored.`;
|
|
}
|
|
return base;
|
|
}
|
|
|
|
function buildNotFilledText({ outcome, reasonCode }) {
|
|
const base = humanizeReasonCode(reasonCode, 'Not filled.');
|
|
const uncertainty = outcome?.evidence?.uncertainty;
|
|
return uncertainty ? `${base} ${uncertainty}` : base;
|
|
}
|
|
|
|
function hasSettlementEvidence(outcome) {
|
|
return Boolean(
|
|
outcome?.attributed_inventory_delta
|
|
&& TERMINAL_SETTLEMENT_ATTRIBUTION_STATUSES.has(outcome.attribution_status),
|
|
);
|
|
}
|
|
|
|
function buildExecutionFailureText(execution, reasonCode) {
|
|
const base = humanizeReasonCode(reasonCode, 'Submission failed.');
|
|
if (execution?.error_message) return `${base} ${execution.error_message}`;
|
|
if (execution?.note) return `${base} ${execution.note}`;
|
|
return base;
|
|
}
|
|
|
|
function buildExecutorBlockText(execution, reasonCode) {
|
|
if (execution?.note) return execution.note;
|
|
return humanizeReasonCode(reasonCode, 'Executor blocked the submission.');
|
|
}
|
|
|
|
function humanizeReasonCode(code, fallback = 'Reason not recorded.') {
|
|
const normalized = normalizeLifecycleToken(code || '');
|
|
if (!normalized) return fallback;
|
|
return HUMAN_REASON_TEXT[normalized] || normalized.replaceAll('_', ' ');
|
|
}
|
|
|
|
function normalizeLifecycleToken(value) {
|
|
return String(value || '')
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/[\s-]+/g, '_');
|
|
}
|
|
|
|
function normalizeLifecycleQuote(quote) {
|
|
if (!quote) return null;
|
|
return {
|
|
quote_id: quote.quote_id || null,
|
|
pair: quote.pair || null,
|
|
asset_in: quote.asset_in || null,
|
|
asset_out: quote.asset_out || null,
|
|
request_kind: quote.request_kind || null,
|
|
amount_in: quote.amount_in ?? null,
|
|
amount_out: quote.amount_out ?? null,
|
|
min_deadline_ms: quote.min_deadline_ms ?? null,
|
|
observed_at: quote.observed_at || null,
|
|
ingested_at: quote.ingested_at || null,
|
|
};
|
|
}
|
|
|
|
function normalizeCommand(command) {
|
|
if (!command) return null;
|
|
return {
|
|
command_id: command.command_id || null,
|
|
decision_id: command.decision_id || null,
|
|
execution_key: command.execution_key || null,
|
|
quote_id: command.quote_id || null,
|
|
pair: command.pair || null,
|
|
pair_id: command.pair_id || null,
|
|
pair_config_id: command.pair_config_id || null,
|
|
pair_config_version: command.pair_config_version || null,
|
|
edge_bps: command.edge_bps || null,
|
|
direction: command.direction || null,
|
|
request_kind: command.request_kind || null,
|
|
asset_in: command.asset_in || null,
|
|
asset_out: command.asset_out || null,
|
|
amount_in: command.amount_in ?? null,
|
|
amount_out: command.amount_out ?? null,
|
|
command_at: command.command_at || command.observed_at || command.ingested_at || null,
|
|
};
|
|
}
|
|
|
|
function buildStrategySummary({
|
|
config,
|
|
servicesByName,
|
|
activeAlerts,
|
|
assetCatalog = null,
|
|
pairConfig = null,
|
|
fundingHandles = [],
|
|
recentQuotes = [],
|
|
recentTradeDecisions = [],
|
|
recentExecuteTradeCommands = [],
|
|
recentExecutionResults = [],
|
|
recentQuoteOutcomes = [],
|
|
}) {
|
|
const strategyState = servicesByName['strategy-engine']?.state || {};
|
|
const executorState = servicesByName['trade-executor']?.state || {};
|
|
const durableDecisionsById = new Map(
|
|
(recentTradeDecisions || [])
|
|
.map((entry) => normalizeDecision({
|
|
...(entry.payload || {}),
|
|
decision_at:
|
|
entry?.payload?.decision_at
|
|
|| entry?.observed_at
|
|
|| entry?.ingested_at
|
|
|| null,
|
|
}))
|
|
.filter((entry) => entry?.decision_id)
|
|
.map((entry) => [entry.decision_id, entry]),
|
|
);
|
|
const recentDecisions = (strategyState.recent_decisions || []).slice(0, 20).map((decision) => {
|
|
const durableDecision = durableDecisionsById.get(decision?.decision_id) || null;
|
|
return normalizeDecision({
|
|
...(durableDecision || {}),
|
|
...(decision || {}),
|
|
decision_at:
|
|
decision?.decision_at
|
|
|| durableDecision?.decision_at
|
|
|| null,
|
|
});
|
|
});
|
|
const latestDecision = normalizeDecision({
|
|
...(durableDecisionsById.get(strategyState.latest_decision?.decision_id) || {}),
|
|
...(strategyState.latest_decision || {}),
|
|
decision_at:
|
|
strategyState.latest_decision?.decision_at
|
|
|| durableDecisionsById.get(strategyState.latest_decision?.decision_id)?.decision_at
|
|
|| null,
|
|
});
|
|
const allLifecycleRows = deriveQuoteLifecycleRows({
|
|
recentQuotes,
|
|
recentTradeDecisions,
|
|
recentExecuteTradeCommands,
|
|
recentExecutionResults,
|
|
recentQuoteOutcomes,
|
|
limit: null,
|
|
}).map((row) => enrichLifecycleRowForUi({ config, row }));
|
|
const lifecycleRows = allLifecycleRows.slice(0, 20);
|
|
const tradeFunnel = buildTradeFunnelSummary(allLifecycleRows);
|
|
|
|
return {
|
|
strategy_state: {
|
|
armed: strategyState.armed ?? null,
|
|
paused: strategyState.paused ?? null,
|
|
threshold_pct:
|
|
strategyState.threshold_pct
|
|
?? firstActivePairConfig(pairConfig)?.edge_pct
|
|
?? null,
|
|
max_notional_eure:
|
|
strategyState.max_notional_eure
|
|
?? firstActivePairConfig(pairConfig)?.max_notional
|
|
?? null,
|
|
latest_decision: latestDecision?.decision_id ? latestDecision : null,
|
|
recent_decisions: recentDecisions.length
|
|
? recentDecisions
|
|
: [...durableDecisionsById.values()].slice(0, 20),
|
|
recent_lifecycle_rows: lifecycleRows,
|
|
trade_funnel: tradeFunnel,
|
|
skipped_counts: strategyState.skipped_counts || {},
|
|
durable_control_state: strategyState.durable_control_state || null,
|
|
trading_config: strategyState.trading_config || null,
|
|
},
|
|
asset_catalog: attachDepositHandlesToAssetCatalog(
|
|
assetCatalog || buildFallbackAssetCatalog(config),
|
|
fundingHandles,
|
|
),
|
|
pair_config: pairConfig || buildFallbackPairConfig(config),
|
|
executor_state: {
|
|
armed: executorState.armed ?? null,
|
|
paused: executorState.paused ?? null,
|
|
draining: executorState.draining ?? null,
|
|
in_flight_count: executorState.in_flight_count ?? 0,
|
|
submitted_count: executorState.submitted_count ?? executorState.completed_count ?? 0,
|
|
last_command: executorState.last_command || null,
|
|
last_venue_response: executorState.last_venue_response || null,
|
|
last_error: executorState.last_error || null,
|
|
signer_registered: executorState.signer_registered ?? null,
|
|
account_id: executorState.account_id || null,
|
|
signer_public_key: executorState.signer_public_key || null,
|
|
},
|
|
relevant_alerts: [],
|
|
omitted_controls: [
|
|
'Strategy arm and disarm are intentionally absent in this turn.',
|
|
'Executor drain remains intentionally absent in this turn.',
|
|
'Live withdrawal submission is intentionally absent in this turn.',
|
|
],
|
|
};
|
|
}
|
|
|
|
function attachDepositHandlesToAssetCatalog(assetCatalog, fundingHandles = []) {
|
|
const handlesByAssetId = new Map();
|
|
const handlesByChain = new Map();
|
|
for (const handle of fundingHandles || []) {
|
|
if (!handle) continue;
|
|
if (handle.chain) handlesByChain.set(handle.chain, handle);
|
|
if (handle.asset_id) handlesByAssetId.set(handle.asset_id, handle);
|
|
for (const assetId of handle.asset_ids || []) {
|
|
handlesByAssetId.set(assetId, handle);
|
|
}
|
|
}
|
|
|
|
return {
|
|
...(assetCatalog || {}),
|
|
items: (assetCatalog?.items || []).map((asset) => {
|
|
const assetId = asset.asset_id || asset.assetId;
|
|
const chain = asset.chain || asset.blockchain || null;
|
|
const handle = handlesByAssetId.get(assetId) || handlesByChain.get(chain) || null;
|
|
return {
|
|
...asset,
|
|
depositAddress: handle?.address || null,
|
|
deposit_address: handle?.address || null,
|
|
depositChain: handle?.chain || chain,
|
|
deposit_chain: handle?.chain || chain,
|
|
depositMemo: handle?.memo || null,
|
|
deposit_memo: handle?.memo || null,
|
|
depositRefreshedAt: handle?.refreshed_at || null,
|
|
deposit_refreshed_at: handle?.refreshed_at || null,
|
|
};
|
|
}),
|
|
};
|
|
}
|
|
|
|
function buildTradeFunnelSummary(lifecycleRows = []) {
|
|
const counts = {
|
|
observed: 0,
|
|
rejected: 0,
|
|
blocked: 0,
|
|
submitted: 0,
|
|
awaiting_outcome: 0,
|
|
failed: 0,
|
|
not_filled: 0,
|
|
completed: 0,
|
|
};
|
|
|
|
const successfulTrades = [];
|
|
const unresolvedSubmissions = [];
|
|
const noTradeRows = [];
|
|
|
|
for (const row of lifecycleRows || []) {
|
|
if (Object.hasOwn(counts, row.lifecycle_state)) {
|
|
counts[row.lifecycle_state] += 1;
|
|
}
|
|
|
|
if (row.lifecycle_state === 'completed' && row.has_settlement_evidence) {
|
|
successfulTrades.push(row);
|
|
} else if (['submitted', 'awaiting_outcome'].includes(row.lifecycle_state)) {
|
|
unresolvedSubmissions.push(row);
|
|
noTradeRows.push(row);
|
|
} else if (['observed', 'evaluated', 'command_emitted', 'rejected', 'blocked', 'failed', 'not_filled'].includes(row.lifecycle_state)) {
|
|
noTradeRows.push(row);
|
|
}
|
|
}
|
|
|
|
const grossEdgeEstimate = summarizeGrossEdgeEstimate(successfulTrades);
|
|
|
|
return {
|
|
successful_trade_count: successfulTrades.length,
|
|
successful_trade_gross_edge_estimate_eure: grossEdgeEstimate.total_eure,
|
|
successful_trade_gross_edge_estimate_count: grossEdgeEstimate.count,
|
|
unresolved_submission_count: unresolvedSubmissions.length,
|
|
no_trade_count: noTradeRows.length,
|
|
successful_trades: successfulTrades,
|
|
unresolved_submissions: unresolvedSubmissions.slice(0, 20),
|
|
no_trade_rows: [],
|
|
counts,
|
|
caveat:
|
|
successfulTrades.length > 0
|
|
? 'Successful trades require durable terminal outcome evidence.'
|
|
: 'No quote currently has linked terminal outcome and settled inventory evidence, so there are no successful trades to show yet.',
|
|
};
|
|
}
|
|
|
|
function firstActivePairConfig(pairConfig) {
|
|
const pair = pairConfig?.pairs?.find((entry) => entry.strategyConfig || entry.strategy_config);
|
|
const strategyConfig = pair?.strategyConfig || pair?.strategy_config || null;
|
|
if (!strategyConfig) return null;
|
|
return {
|
|
edge_pct: strategyConfig.edge_bps == null ? null : String(Number(strategyConfig.edge_bps) / 100),
|
|
max_notional: strategyConfig.max_notional == null ? null : String(strategyConfig.max_notional),
|
|
};
|
|
}
|
|
|
|
function buildFallbackAssetCatalog(config) {
|
|
const items = [...(config.assetRegistry?.values?.() || [])].map((asset) => ({
|
|
asset_id: asset.assetId,
|
|
symbol: asset.symbol,
|
|
label: asset.label || asset.symbol,
|
|
decimals: asset.decimals,
|
|
blockchain: asset.blockchain || asset.chain || null,
|
|
chain: asset.chain || asset.blockchain || null,
|
|
supported: asset.supported ?? null,
|
|
retired_at: asset.retiredAt || null,
|
|
enabled_for_inventory: asset.enabledForInventory ?? true,
|
|
}));
|
|
return {
|
|
latest_import: null,
|
|
counts: {
|
|
known: items.length,
|
|
supported: items.filter((asset) => asset.supported === true).length,
|
|
retired: items.filter((asset) => asset.retired_at).length,
|
|
inventory_enabled: items.filter((asset) => asset.enabled_for_inventory !== false).length,
|
|
},
|
|
items,
|
|
};
|
|
}
|
|
|
|
function buildFallbackPairConfig(config) {
|
|
return {
|
|
ok: false,
|
|
block_reason: 'pair_config_unavailable',
|
|
loaded_at: null,
|
|
pairs: config.activePair ? [{
|
|
pair_id: config.activePair,
|
|
key: config.activePair,
|
|
mode: 'legacy',
|
|
status: 'legacy',
|
|
enabled: false,
|
|
canTrade: false,
|
|
blockReason: 'pair_config_unavailable',
|
|
}] : [],
|
|
};
|
|
}
|
|
|
|
function summarizeGrossEdgeEstimate(rows = []) {
|
|
let total = 0n;
|
|
let count = 0;
|
|
|
|
for (const row of rows || []) {
|
|
const value = row?.gross_edge_value_eure || estimateGrossEdgeValueEure(row);
|
|
if (value == null) continue;
|
|
total += parseScaledDecimal(value);
|
|
count += 1;
|
|
}
|
|
|
|
return {
|
|
count,
|
|
total_eure: count > 0 ? formatScaledDecimal(total) : null,
|
|
};
|
|
}
|
|
|
|
function buildEnvironmentStatusHistory({ recentEnvironmentStatuses = [], currentStatus = null } = {}) {
|
|
const recentChanges = (recentEnvironmentStatuses || []).map((entry) => ({
|
|
observed_at: entry.observed_at || entry.payload?.observed_at || null,
|
|
ingested_at: entry.ingested_at || null,
|
|
payload: entry.payload || entry,
|
|
}));
|
|
const latestDurable = recentChanges[0]?.payload || null;
|
|
const current = currentStatus || latestDurable;
|
|
|
|
return {
|
|
current,
|
|
recent_changes: recentChanges,
|
|
latest_durable_change_at:
|
|
recentChanges[0]?.payload?.changed_at
|
|
|| recentChanges[0]?.observed_at
|
|
|| recentChanges[0]?.ingested_at
|
|
|| null,
|
|
change_count: recentChanges.length,
|
|
};
|
|
}
|
|
|
|
function buildSystemSummary({
|
|
servicesByName,
|
|
activeAlerts,
|
|
recentAlerts,
|
|
nearIntentsStatus = null,
|
|
environmentStatus = { current: null, recent_changes: [] },
|
|
}) {
|
|
const historyWriterState = servicesByName['history-writer']?.state || {};
|
|
void activeAlerts;
|
|
void recentAlerts;
|
|
|
|
return {
|
|
service_health: Object.values(servicesByName).map((snapshot) => (
|
|
summarizeServiceSnapshot(snapshot, {
|
|
authoritativeHealth: null,
|
|
activeAlerts: [],
|
|
nearIntentsStatus,
|
|
})
|
|
)),
|
|
alerts: {
|
|
active: [],
|
|
recent: [],
|
|
},
|
|
environment_status: environmentStatus,
|
|
persistence: {
|
|
database_connectivity: historyWriterState.database_connectivity ?? null,
|
|
last_write_at: historyWriterState.last_write_at || null,
|
|
last_alert_write_at: historyWriterState.last_alert_write_at || null,
|
|
last_funding_observation_write_at: historyWriterState.last_funding_observation_write_at || null,
|
|
last_environment_status_write_at: historyWriterState.last_environment_status_write_at || null,
|
|
last_environment_status_seen_at: historyWriterState.last_environment_status_seen_at || null,
|
|
last_environment_status_duplicate_at: historyWriterState.last_environment_status_duplicate_at || null,
|
|
last_metrics_at: historyWriterState.last_metrics_at || null,
|
|
last_quote_outcomes_at: historyWriterState.last_quote_outcomes_at || null,
|
|
latest_portfolio_metrics: historyWriterState.latest_portfolio_metrics || null,
|
|
latest_quote_outcomes: historyWriterState.latest_quote_outcomes || null,
|
|
offsets: historyWriterState.offsets || {},
|
|
metrics_error: historyWriterState.metrics_error || null,
|
|
quote_outcomes_error: historyWriterState.quote_outcomes_error || null,
|
|
},
|
|
controls: listDashboardControls({ page: 'system' }),
|
|
};
|
|
}
|
|
|
|
function summarizeServiceSnapshot(snapshot, { authoritativeHealth = null, activeAlerts = [], nearIntentsStatus = null } = {}) {
|
|
const state = snapshot.state || {};
|
|
const health = snapshot.health || {};
|
|
void authoritativeHealth;
|
|
void activeAlerts;
|
|
const freshnessAt = inferServiceFreshnessTimestamp(snapshot.service, state, health);
|
|
const reachable = snapshot.reachable !== false;
|
|
const online = reachable && health.ok !== false;
|
|
const upstreamStatus = resolveServiceUpstreamStatus(snapshot.service, nearIntentsStatus);
|
|
const upstreamDisrupted = upstreamStatus?.status === 'disrupted';
|
|
const healthStatus = upstreamDisrupted
|
|
? 'upstream_paused'
|
|
: online
|
|
? 'online'
|
|
: reachable
|
|
? 'reachable'
|
|
: 'offline';
|
|
const healthLabel = upstreamDisrupted ? 'upstream paused' : healthStatus;
|
|
const healthReasons = upstreamDisrupted && upstreamStatus.decisive_reason
|
|
? [upstreamStatus.decisive_reason]
|
|
: [];
|
|
|
|
return {
|
|
service: snapshot.service,
|
|
label: snapshot.label,
|
|
base_url: snapshot.base_url,
|
|
reachable,
|
|
health_ok: healthStatus === 'online',
|
|
health_status: healthStatus,
|
|
health_label: healthLabel,
|
|
health_reasons: healthReasons,
|
|
upstream_status: upstreamStatus,
|
|
highest_alert_severity: null,
|
|
paused: state.paused ?? health.paused ?? null,
|
|
armed: state.armed ?? null,
|
|
draining: state.draining ?? null,
|
|
freshness_at: freshnessAt,
|
|
freshness_age_ms: ageMs(freshnessAt),
|
|
last_error: state.last_error || health.last_error || null,
|
|
summary: buildServiceSummary(snapshot.service, state),
|
|
};
|
|
}
|
|
|
|
const NEAR_INTENTS_RELAY_SERVICES = new Set(['near-intents-ingest', 'trade-executor']);
|
|
|
|
function resolveServiceUpstreamStatus(service, nearIntentsStatus) {
|
|
if (!NEAR_INTENTS_RELAY_SERVICES.has(service) || !nearIntentsStatus) return null;
|
|
return {
|
|
source: nearIntentsStatus.source || 'near_intents_status_page',
|
|
status: nearIntentsStatus.status || 'unknown',
|
|
label: nearIntentsStatus.label || nearIntentsStatus.status || 'unknown',
|
|
observed_at: nearIntentsStatus.observed_at || null,
|
|
decisive_reason: nearIntentsStatus.decisive_reason || null,
|
|
current_incident_count: nearIntentsStatus.current_incident_count || 0,
|
|
affected_services: nearIntentsStatus.affected_services || [],
|
|
observed_incident_count: nearIntentsStatus.observed_incident_count ?? null,
|
|
observed_affected_services: nearIntentsStatus.observed_affected_services || [],
|
|
unrelated_incident_count: nearIntentsStatus.unrelated_incident_count || 0,
|
|
quoting_stopped: nearIntentsStatus.quoting_stopped ?? null,
|
|
};
|
|
}
|
|
|
|
function buildServiceSummary(service, state) {
|
|
switch (service) {
|
|
case 'near-intents-ingest':
|
|
return {
|
|
connected: state.ingest?.connected ?? null,
|
|
reconnect_count: state.ingest?.reconnect_count ?? null,
|
|
active_pair: state.trading_config?.active_pair || null,
|
|
enabled_pair_count: state.trading_config?.enabled_pair_count ?? null,
|
|
last_message_at: state.ingest?.last_message_at || null,
|
|
last_matching_quote_at: state.ingest?.last_matching_quote_at || null,
|
|
last_published_at: state.ingest?.last_published_at || null,
|
|
};
|
|
case 'market-reference-ingest':
|
|
return {
|
|
last_published_at: state.last_published_at || null,
|
|
source_used: state.kraken?.healthy ? 'kraken' : state.coingecko?.healthy ? 'coingecko' : null,
|
|
};
|
|
case 'inventory-sync':
|
|
return {
|
|
last_sync_at: state.last_sync_at || null,
|
|
reconciliation_status: state.last_snapshot?.reconciliation_status || null,
|
|
};
|
|
case 'liquidity-manager':
|
|
return {
|
|
last_refresh_at: state.last_refresh_at || null,
|
|
funding_observer_paused: state.funding_observer_paused ?? null,
|
|
withdrawals_frozen: state.withdrawals_frozen ?? null,
|
|
};
|
|
case 'history-writer':
|
|
return {
|
|
last_write_at: state.last_write_at || null,
|
|
last_alert_write_at: state.last_alert_write_at || null,
|
|
last_quote_outcomes_at: state.last_quote_outcomes_at || null,
|
|
database_connectivity: state.database_connectivity ?? null,
|
|
};
|
|
case 'ops-sentinel':
|
|
return {
|
|
last_event_at: state.last_event_at || null,
|
|
last_runtime_eval_at: state.last_runtime_eval_at || null,
|
|
active_alert_count: state.active_alerts?.length || 0,
|
|
stale: state.stale ?? null,
|
|
};
|
|
case 'strategy-engine':
|
|
return {
|
|
latest_decision_id: state.latest_decision?.decision_id || null,
|
|
threshold_pct: state.threshold_pct ?? null,
|
|
max_notional_eure: state.max_notional_eure ?? null,
|
|
};
|
|
case 'trade-executor':
|
|
return {
|
|
in_flight_count: state.in_flight_count ?? 0,
|
|
submitted_count: state.submitted_count ?? state.completed_count ?? 0,
|
|
signer_registered: state.signer_registered ?? null,
|
|
relay_connected: state.relay?.connected ?? null,
|
|
relay_last_message_at: state.relay?.last_message_at || null,
|
|
};
|
|
case 'operator-dashboard':
|
|
return {
|
|
source_error_count: state.source_error_count ?? 0,
|
|
last_source_error_at: state.last_source_error_at || null,
|
|
last_bootstrap_at: state.last_bootstrap_at || null,
|
|
};
|
|
default:
|
|
return {};
|
|
}
|
|
}
|
|
|
|
function inferServiceFreshnessTimestamp(service, state, health) {
|
|
return inferRuntimeFreshnessTimestamp(service, state, health);
|
|
}
|
|
|
|
function normalizeTradeForUi({ config, trade }) {
|
|
const assetIn = config.assetRegistry.get(trade.asset_in);
|
|
const assetOut = config.assetRegistry.get(trade.asset_out);
|
|
|
|
return {
|
|
...trade,
|
|
decision_reason: normalizeDecisionReason(trade.decision_reason),
|
|
asset_in_symbol: assetIn?.symbol || trade.asset_in,
|
|
asset_out_symbol: assetOut?.symbol || trade.asset_out,
|
|
amount_in_display: formatUnits(trade.amount_in || '0', assetIn?.decimals || 0),
|
|
amount_out_display: formatUnits(trade.amount_out || '0', assetOut?.decimals || 0),
|
|
};
|
|
}
|
|
|
|
function enrichLifecycleRowForUi({ config, row }) {
|
|
return {
|
|
...row,
|
|
request_terms: buildLifecycleTerms({
|
|
config,
|
|
terms: row.quote || row,
|
|
}),
|
|
submitted_terms: buildLifecycleTerms({
|
|
config,
|
|
terms: row.command || row.execution || null,
|
|
}),
|
|
gross_edge_value_eure: estimateGrossEdgeValueEure(row),
|
|
settlement_summary: buildSettlementSummary({
|
|
config,
|
|
delta: row.attributed_inventory_delta,
|
|
attributionStatus: row.attribution_status,
|
|
attributionMethod: row.attribution_method,
|
|
}),
|
|
};
|
|
}
|
|
|
|
function buildLifecycleTerms({ config, terms }) {
|
|
if (!terms?.asset_in && !terms?.asset_out) return null;
|
|
|
|
const assetIn = config.assetRegistry.get(terms.asset_in);
|
|
const assetOut = config.assetRegistry.get(terms.asset_out);
|
|
const amountIn = terms.amount_in ?? null;
|
|
const amountOut = terms.amount_out ?? null;
|
|
|
|
return {
|
|
asset_in: terms.asset_in || null,
|
|
asset_in_symbol: assetIn?.symbol || terms.asset_in || null,
|
|
amount_in_units: amountIn,
|
|
amount_in: amountIn == null ? null : formatUnits(amountIn, assetIn?.decimals || 0),
|
|
asset_out: terms.asset_out || null,
|
|
asset_out_symbol: assetOut?.symbol || terms.asset_out || null,
|
|
amount_out_units: amountOut,
|
|
amount_out: amountOut == null ? null : formatUnits(amountOut, assetOut?.decimals || 0),
|
|
};
|
|
}
|
|
|
|
function estimateGrossEdgeValueEure(row) {
|
|
const edge = Number(row?.gross_edge_pct);
|
|
const notional = Number(row?.eure_notional);
|
|
if (!Number.isFinite(edge) || !Number.isFinite(notional)) return null;
|
|
const value = (notional * edge) / 100;
|
|
return value.toFixed(8).replace(/\.?0+$/, '');
|
|
}
|
|
|
|
function buildSettlementSummary({
|
|
config,
|
|
delta,
|
|
attributionStatus,
|
|
attributionMethod,
|
|
subject = 'quote',
|
|
}) {
|
|
if (!delta?.delta_units) {
|
|
return {
|
|
status: attributionStatus || 'unattributed',
|
|
method: attributionMethod || null,
|
|
lines: [],
|
|
text: attributionStatus === 'ambiguous'
|
|
? `Inventory movement is ambiguous and is not assigned to this ${subject}.`
|
|
: `No settled inventory delta is linked to this ${subject}.`,
|
|
};
|
|
}
|
|
|
|
const lines = Object.entries(delta.delta_units).map(([assetId, units]) => {
|
|
const asset = config.assetRegistry.get(assetId);
|
|
const symbol = asset?.symbol || assetId;
|
|
const formatted = formatUnits(units, asset?.decimals || 0);
|
|
const signed = BigInt(String(units || '0')) > 0n ? `+${formatted}` : formatted;
|
|
return {
|
|
asset_id: assetId,
|
|
symbol,
|
|
units,
|
|
amount: signed,
|
|
};
|
|
});
|
|
|
|
return {
|
|
status: attributionStatus || 'unattributed',
|
|
method: attributionMethod || null,
|
|
observed_at: delta.observed_at || null,
|
|
previous_observed_at: delta.previous_observed_at || null,
|
|
lines,
|
|
text: lines.map((line) => `${line.amount} ${line.symbol}`).join(', '),
|
|
caveat: delta.uncertainty || null,
|
|
};
|
|
}
|
|
|
|
function buildRecentDepositItems({ config, recentDepositStatuses, liquidityState }) {
|
|
const recentItems = (recentDepositStatuses || []).map((entry) => normalizeDepositStatusForUi({
|
|
config,
|
|
depositStatus: entry,
|
|
}));
|
|
const itemsByKey = new Map(
|
|
recentItems.map((item) => [buildFundingActivityKey(item), item]),
|
|
);
|
|
|
|
for (const deposit of Object.values(liquidityState?.deposits || {})) {
|
|
const fallbackItem = normalizeLiquidityDepositForUi({
|
|
config,
|
|
deposit,
|
|
observedAt: null,
|
|
});
|
|
const key = buildFundingActivityKey(fallbackItem);
|
|
const existing = itemsByKey.get(key);
|
|
if (!existing || shouldReplaceFundingActivityItem(existing, fallbackItem)) {
|
|
itemsByKey.set(key, fallbackItem);
|
|
}
|
|
}
|
|
|
|
return [...itemsByKey.values()];
|
|
}
|
|
|
|
function mergeFundingActivityItems({ observerItems, depositItems }) {
|
|
const merged = new Map();
|
|
|
|
for (const item of depositItems || []) {
|
|
merged.set(buildFundingActivityKey(item), item);
|
|
}
|
|
for (const item of observerItems || []) {
|
|
merged.set(buildFundingActivityKey(item), item);
|
|
}
|
|
|
|
return [...merged.values()];
|
|
}
|
|
|
|
function latestFundingActivityAt(items, fallback = null) {
|
|
let latest = fallback;
|
|
for (const item of items || []) {
|
|
if (sortTimestamps(fundingActivityTimestamp(item), latest) > 0) {
|
|
latest = fundingActivityTimestamp(item);
|
|
}
|
|
}
|
|
return latest;
|
|
}
|
|
|
|
function fundingActivityTimestamp(item) {
|
|
return item?.last_seen_at || item?.credited_at || item?.first_seen_at || null;
|
|
}
|
|
|
|
function shouldReplaceFundingActivityItem(existing, candidate) {
|
|
if (!candidate) return false;
|
|
if (!existing) return true;
|
|
if (candidate.bridge_created_at && !existing.bridge_created_at) return true;
|
|
|
|
const existingTs = timestampValue(fundingActivityTimestamp(existing));
|
|
const candidateTs = timestampValue(fundingActivityTimestamp(candidate));
|
|
return Number.isFinite(candidateTs)
|
|
&& (!Number.isFinite(existingTs) || candidateTs < existingTs);
|
|
}
|
|
|
|
function buildFundingActivityKey(item) {
|
|
return [
|
|
item?.tx_hash || 'no-tx',
|
|
item?.chain || 'no-chain',
|
|
item?.asset_id || 'no-asset',
|
|
item?.funding_handle || 'no-handle',
|
|
item?.amount_units || 'no-amount',
|
|
].join('|');
|
|
}
|
|
|
|
function normalizeFundingObservationForUi({ config, observation }) {
|
|
const asset = config.assetRegistry.get(observation.asset_id);
|
|
return {
|
|
funding_observation_id: observation.funding_observation_id,
|
|
asset_id: observation.asset_id,
|
|
symbol: asset?.symbol || observation.asset_id,
|
|
asset_symbol: asset?.label || asset?.symbol || observation.asset_id,
|
|
chain: observation.chain,
|
|
funding_handle: observation.funding_handle,
|
|
tx_hash: observation.tx_hash,
|
|
status: observation.status,
|
|
amount_units: observation.amount,
|
|
amount_display: formatUnits(observation.amount || '0', asset?.decimals || 0),
|
|
amount: formatUnits(observation.amount || '0', asset?.decimals || 0),
|
|
confirmations: observation.confirmations,
|
|
first_seen_at: observation.first_seen_at,
|
|
last_seen_at: observation.last_seen_at,
|
|
credited_at: observation.credited_at || null,
|
|
};
|
|
}
|
|
|
|
function normalizeDepositStatusForUi({ config, depositStatus }) {
|
|
const payload = depositStatus?.payload || {};
|
|
const details = payload.details || {};
|
|
return normalizeLiquidityDepositForUi({
|
|
config,
|
|
deposit: {
|
|
tx_hash: details.tx_hash || null,
|
|
chain: payload.chain || details.chain || null,
|
|
asset_id: payload.asset_id || details.asset_id || null,
|
|
amount: details.amount || '0',
|
|
address: details.address || details.deposit_address || null,
|
|
status: payload.status || details.status || null,
|
|
created_at: details.created_at || payload.created_at || null,
|
|
},
|
|
observedAt: depositStatus?.observed_at || depositStatus?.ingested_at || null,
|
|
});
|
|
}
|
|
|
|
function normalizeLiquidityDepositForUi({ config, deposit, observedAt }) {
|
|
const asset = config.assetRegistry.get(deposit?.asset_id);
|
|
const status = deposit?.status || null;
|
|
const bridgeCreatedAt = bridgeDepositObservedAt(deposit);
|
|
const timestamp = bridgeCreatedAt || observedAt || null;
|
|
|
|
return {
|
|
funding_observation_id: null,
|
|
asset_id: deposit?.asset_id || null,
|
|
symbol: asset?.symbol || deposit?.asset_id || null,
|
|
asset_symbol: asset?.label || asset?.symbol || deposit?.asset_id || null,
|
|
chain: deposit?.chain || null,
|
|
funding_handle: deposit?.address || null,
|
|
tx_hash: deposit?.tx_hash || null,
|
|
status,
|
|
amount_units: String(deposit?.amount || '0'),
|
|
amount_display: formatUnits(deposit?.amount || '0', asset?.decimals || 0),
|
|
amount: formatUnits(deposit?.amount || '0', asset?.decimals || 0),
|
|
confirmations: null,
|
|
first_seen_at: timestamp,
|
|
last_seen_at: timestamp,
|
|
credited_at: CREDITED_FUNDING_STATUSES.has(String(status || '').toUpperCase()) ? timestamp : null,
|
|
bridge_created_at: bridgeCreatedAt,
|
|
};
|
|
}
|
|
|
|
function normalizeDecision(decision) {
|
|
if (!decision) return null;
|
|
return {
|
|
decision_id: decision.decision_id || null,
|
|
decision_at: decision.decision_at || null,
|
|
quote_id: decision.quote_id || null,
|
|
pair: decision.pair || null,
|
|
pair_id: decision.pair_id || null,
|
|
pair_config_id: decision.pair_config_id || null,
|
|
pair_config_version: decision.pair_config_version || null,
|
|
edge_bps: decision.edge_bps || null,
|
|
direction: decision.direction || null,
|
|
request_kind: decision.request_kind || null,
|
|
decision: normalizeDecisionVerdict(decision.decision),
|
|
decision_reason: normalizeDecisionReason(decision.decision_reason),
|
|
gross_edge_pct: decision.gross_edge_pct || null,
|
|
threshold_pct: decision.threshold_pct || null,
|
|
max_notional_eure: decision.max_notional_eure || null,
|
|
strategy_armed: decision.strategy_armed ?? null,
|
|
inventory_asset: decision.inventory_asset || null,
|
|
eure_notional: decision.eure_notional || null,
|
|
};
|
|
}
|
|
|
|
function normalizeDecisionVerdict(value) {
|
|
if (value === 'actionable') return 'approved';
|
|
return value || null;
|
|
}
|
|
|
|
function normalizeDecisionReason(value) {
|
|
if (value === 'actionable') return 'strategy_approved';
|
|
return value || null;
|
|
}
|
|
|
|
function normalizeAlertList(alerts) {
|
|
return (alerts || []).map(normalizeAlert).sort((left, right) => sortTimestamps(
|
|
right.raised_at || right.first_raised_at || right.cleared_at,
|
|
left.raised_at || left.first_raised_at || left.cleared_at,
|
|
));
|
|
}
|
|
|
|
function normalizeAlert(alert) {
|
|
return {
|
|
alert_code: alert.alert_code,
|
|
status: alert.status,
|
|
severity: alert.severity,
|
|
reason: alert.reason,
|
|
service_scope: alert.service_scope,
|
|
pair: alert.pair || null,
|
|
asset_id: alert.asset_id || null,
|
|
tx_hash: alert.tx_hash || null,
|
|
raised_at: alert.raised_at || null,
|
|
first_raised_at: alert.first_raised_at || null,
|
|
cleared_at: alert.cleared_at || null,
|
|
last_evaluated_at: alert.last_evaluated_at || null,
|
|
details: alert.details || {},
|
|
transition_count: Number(alert.transition_count || 1),
|
|
raised_count: Number(alert.raised_count || (alert.status === 'raised' ? 1 : 0)),
|
|
cleared_count: Number(alert.cleared_count || (alert.status === 'cleared' ? 1 : 0)),
|
|
};
|
|
}
|
|
|
|
function summarizeRecentAlertTransitions(alerts) {
|
|
const summaries = new Map();
|
|
|
|
for (const alert of alerts || []) {
|
|
const key = buildAlertKey(alert);
|
|
const existing = summaries.get(key);
|
|
if (!existing) {
|
|
summaries.set(key, {
|
|
...alert,
|
|
transition_count: alert.transition_count || 1,
|
|
raised_count: alert.status === 'raised' ? 1 : 0,
|
|
cleared_count: alert.status === 'cleared' ? 1 : 0,
|
|
});
|
|
continue;
|
|
}
|
|
|
|
existing.transition_count += 1;
|
|
existing.raised_count += alert.status === 'raised' ? 1 : 0;
|
|
existing.cleared_count += alert.status === 'cleared' ? 1 : 0;
|
|
}
|
|
|
|
return [...summaries.values()].sort((left, right) => sortTimestamps(
|
|
right.cleared_at || right.raised_at || right.last_evaluated_at,
|
|
left.cleared_at || left.raised_at || left.last_evaluated_at,
|
|
));
|
|
}
|
|
|
|
function normalizeDashboardLiveTopic(state, topic) {
|
|
const config = state?.config || {};
|
|
const aliases = new Map([
|
|
[config.kafkaTopicNormSwapDemand, 'norm.swap_demand'],
|
|
[config.kafkaTopicDecisionTradeDecision, 'decision.trade_decision'],
|
|
[config.kafkaTopicCmdExecuteTrade, 'cmd.execute_trade'],
|
|
[config.kafkaTopicRefMarketPrice, 'ref.market_price'],
|
|
[config.kafkaTopicStateIntentInventory, 'state.intent_inventory'],
|
|
[config.kafkaTopicOpsAlert, 'ops.alert'],
|
|
[config.kafkaTopicOpsEnvironmentStatus, 'ops.environment_status'],
|
|
[config.kafkaTopicExecTradeResult, 'exec.trade_result'],
|
|
]);
|
|
return aliases.get(topic) || topic;
|
|
}
|
|
|
|
function buildQuoteLifecycleUpdate(state, { flashQuoteId = null } = {}) {
|
|
const receivedAt = new Date().toISOString();
|
|
return {
|
|
type: 'quote_lifecycle.updated',
|
|
recent_lifecycle_rows: buildLiveQuoteLifecycleRows(state, {
|
|
flashQuoteId,
|
|
flashAt: receivedAt,
|
|
}),
|
|
flash_quote_id: flashQuoteId || null,
|
|
received_at: receivedAt,
|
|
};
|
|
}
|
|
|
|
function appendUniqueRecentEvent(items, nextItem, limit, keyFn) {
|
|
const nextKey = keyFn(nextItem);
|
|
const deduped = [
|
|
nextItem,
|
|
...(items || []).filter((item) => keyFn(item) !== nextKey),
|
|
];
|
|
return deduped.slice(0, limit);
|
|
}
|
|
|
|
function livePayloadKey(entry, fields) {
|
|
const payload = entry?.payload || entry || {};
|
|
for (const field of fields) {
|
|
if (payload[field] != null && payload[field] !== '') return `${field}:${payload[field]}`;
|
|
}
|
|
return `event:${entry?.observed_at || entry?.ingested_at || JSON.stringify(payload)}`;
|
|
}
|
|
|
|
function normalizeLiveDecision(payload, event) {
|
|
if (!payload?.decision_id && !payload?.quote_id) return null;
|
|
const decisionAt = payload.decision_at || event.observed_at || event.ingested_at || null;
|
|
return {
|
|
observed_at: event.observed_at || decisionAt,
|
|
ingested_at: event.ingested_at || null,
|
|
payload: {
|
|
...payload,
|
|
decision_at: decisionAt,
|
|
},
|
|
};
|
|
}
|
|
|
|
function normalizeLiveCommand(payload, event) {
|
|
if (!payload?.command_id && !payload?.decision_id && !payload?.quote_id) return null;
|
|
return {
|
|
observed_at: event.observed_at || event.ingested_at || null,
|
|
ingested_at: event.ingested_at || null,
|
|
payload: {
|
|
...payload,
|
|
amount_in: payload.quote_output?.amount_in ?? payload.proposed_amount_in ?? payload.amount_in ?? null,
|
|
amount_out: payload.quote_output?.amount_out ?? payload.proposed_amount_out ?? payload.amount_out ?? null,
|
|
},
|
|
};
|
|
}
|
|
|
|
function normalizeLiveExecutionResult(payload, event) {
|
|
if (!payload?.command_id && !payload?.decision_id && !payload?.quote_id) return null;
|
|
return {
|
|
command_id: payload.command_id || null,
|
|
decision_id: payload.decision_id || null,
|
|
execution_key: payload.execution_key || null,
|
|
quote_id: payload.quote_id || null,
|
|
pair: payload.pair || null,
|
|
result_at: event.observed_at || event.ingested_at || new Date().toISOString(),
|
|
status: payload.status || null,
|
|
result_code: payload.result_code || null,
|
|
outcome_status: payload.outcome_status || payload.venue_outcome_status || payload.trade_outcome_status || null,
|
|
outcome_reason: payload.outcome_reason || payload.venue_outcome_reason || payload.trade_outcome_reason || null,
|
|
attribution_status: payload.attribution_status || null,
|
|
attribution_method: payload.attribution_method || null,
|
|
attributed_inventory_delta: payload.attributed_inventory_delta || null,
|
|
venue_response: payload.venue_response || null,
|
|
error_message: payload.error?.message || null,
|
|
note: payload.note || null,
|
|
};
|
|
}
|
|
|
|
function appendUniqueRecentQuote(quotes, nextQuote, limit) {
|
|
const deduped = [nextQuote, ...quotes.filter((quote) => quote.quote_id !== nextQuote.quote_id)];
|
|
return deduped.slice(0, limit);
|
|
}
|
|
|
|
function normalizeLiveQuote(payload, event) {
|
|
if (!payload?.quote_id) return null;
|
|
return {
|
|
quote_id: payload.quote_id,
|
|
pair: payload.pair || `${payload.asset_in}->${payload.asset_out}`,
|
|
asset_in: payload.asset_in || null,
|
|
asset_out: payload.asset_out || null,
|
|
request_kind: payload.request_kind || null,
|
|
amount_in: payload.amount_in ?? null,
|
|
amount_out: payload.amount_out ?? null,
|
|
observed_at: event.observed_at || null,
|
|
ingested_at: event.ingested_at || null,
|
|
};
|
|
}
|
|
|
|
function buildAlertKey(alert) {
|
|
return [
|
|
alert.alert_code,
|
|
alert.service_scope,
|
|
alert.pair || '',
|
|
alert.asset_id || '',
|
|
alert.tx_hash || '',
|
|
].join('|');
|
|
}
|
|
|
|
function highestAlertSeverity(alerts) {
|
|
return (alerts || []).reduce((highest, alert) => {
|
|
const currentRank = ALERT_SEVERITY_ORDER[highest] || 0;
|
|
const nextRank = ALERT_SEVERITY_ORDER[alert.severity] || 0;
|
|
return nextRank > currentRank ? alert.severity : highest;
|
|
}, null);
|
|
}
|
|
|
|
function computeCurrentPortfolioValue({
|
|
inventory,
|
|
marketPrice,
|
|
btcAsset,
|
|
btcAssets = null,
|
|
eureAsset,
|
|
}) {
|
|
if (!inventory || !marketPrice || !btcAsset || !eureAsset) return null;
|
|
|
|
const effectiveBtcAssets = btcAssets?.length ? btcAssets : [btcAsset];
|
|
const btcScaled = effectiveBtcAssets.reduce((total, asset) => {
|
|
const units = String(inventory.spendable?.[asset.assetId] || '0');
|
|
return total + unitsToScaledDecimal(units, asset.decimals);
|
|
}, 0n);
|
|
const eureUnits = String(inventory.spendable?.[eureAsset.assetId] || '0');
|
|
const eureScaled = unitsToScaledDecimal(eureUnits, eureAsset.decimals);
|
|
const priceScaled = parseScaledDecimal(marketPrice.eure_per_btc || marketPrice.eur_per_btc || '0');
|
|
const total = eureScaled + multiplyScaled(btcScaled, priceScaled);
|
|
|
|
return formatScaledDecimal(total);
|
|
}
|
|
|
|
function valueAssetInEur({ asset, units, marketPrice }) {
|
|
if (!asset) return null;
|
|
if (asset.symbol === 'EURe') {
|
|
return formatUnits(units || '0', asset.decimals);
|
|
}
|
|
if (!marketPrice || asset.symbol !== 'BTC') return null;
|
|
|
|
const scaledUnits = unitsToScaledDecimal(units || '0', asset.decimals);
|
|
const priceScaled = parseScaledDecimal(marketPrice.eure_per_btc || marketPrice.eur_per_btc || '0');
|
|
return formatScaledDecimal(multiplyScaled(scaledUnits, priceScaled));
|
|
}
|
|
|
|
function formatDecimalDifference(left, right) {
|
|
return formatScaledDecimal(parseScaledDecimal(left) - parseScaledDecimal(right));
|
|
}
|
|
|
|
function unitsToScaledDecimal(units, decimals) {
|
|
return BigInt(String(units || '0')) * 10n ** BigInt(DECIMAL_SCALE - decimals);
|
|
}
|
|
|
|
function parseScaledDecimal(value) {
|
|
const normalized = String(value ?? '0').trim();
|
|
const negative = normalized.startsWith('-');
|
|
const unsigned = normalized.replace(/^[+-]/, '');
|
|
const [wholePart, fractionalPart = ''] = unsigned.split('.');
|
|
const whole = BigInt(wholePart || '0');
|
|
const fractional = BigInt(
|
|
(fractionalPart.padEnd(DECIMAL_SCALE, '0')).slice(0, DECIMAL_SCALE) || '0',
|
|
);
|
|
const scaled = (whole * DECIMAL_FACTOR) + fractional;
|
|
return negative ? -scaled : scaled;
|
|
}
|
|
|
|
function multiplyScaled(left, right) {
|
|
return (left * right) / DECIMAL_FACTOR;
|
|
}
|
|
|
|
function formatScaledDecimal(value) {
|
|
const negative = value < 0n;
|
|
const absolute = negative ? -value : value;
|
|
const whole = absolute / DECIMAL_FACTOR;
|
|
const fractional = absolute % DECIMAL_FACTOR;
|
|
if (fractional === 0n) {
|
|
return `${negative ? '-' : ''}${whole}`;
|
|
}
|
|
const fractionalText = fractional
|
|
.toString()
|
|
.padStart(DECIMAL_SCALE, '0')
|
|
.replace(/0+$/, '');
|
|
return `${negative ? '-' : ''}${whole}.${fractionalText}`;
|
|
}
|
|
|
|
function formatUnits(units, decimals) {
|
|
const numeric = unitsToNumber(units, decimals);
|
|
if (!Number.isFinite(numeric)) return '0';
|
|
if (Math.abs(numeric) >= 1000) return numeric.toLocaleString('en-US', { maximumFractionDigits: 8 });
|
|
return numeric.toLocaleString('en-US', { maximumFractionDigits: 8 });
|
|
}
|
|
|
|
function ageMs(value) {
|
|
const parsed = Date.parse(value || '');
|
|
if (!Number.isFinite(parsed)) return null;
|
|
return Math.max(0, Date.now() - parsed);
|
|
}
|
|
|
|
function sortTimestamps(left, right) {
|
|
return timestampValue(left) - timestampValue(right);
|
|
}
|
|
|
|
function timestampValue(value) {
|
|
const parsed = Date.parse(value || '');
|
|
return Number.isFinite(parsed) ? parsed : -Infinity;
|
|
}
|