diff --git a/src/apps/strategy-engine.mjs b/src/apps/strategy-engine.mjs index 102a21f..6e27161 100644 --- a/src/apps/strategy-engine.mjs +++ b/src/apps/strategy-engine.mjs @@ -9,7 +9,7 @@ import { createLogger, serializeError } from '../core/log.mjs'; import { extendMakerTiming } from '../core/maker-timing.mjs'; import { createRecentIdCache } from '../core/recent-id-cache.mjs'; import { assertInventorySnapshotEvent, assertMarketPriceEvent, assertNormalizedSwapDemand } from '../core/schemas.mjs'; -import { evaluateTradeOpportunity } from '../core/strategy.mjs'; +import { evaluateTradeOpportunity, makerTradePairKeyForDemand } from '../core/strategy.mjs'; import { loadConfig } from '../lib/config.mjs'; import { createPairStrategyConfigVersion, @@ -113,7 +113,9 @@ async function handleDemand(event) { const tradingConfig = await tradingConfigStore.getConfig(); if (seenQuotes.has(event.payload.quote_id)) { - const pair = tradingConfig.pairByKey?.get(event.payload.pair || `${event.payload.asset_in}->${event.payload.asset_out}`); + const requestPairKey = event.payload.pair || `${event.payload.asset_in}->${event.payload.asset_out}`; + const makerPairKey = makerTradePairKeyForDemand(event.payload); + const pair = tradingConfig.pairByKey?.get(makerPairKey) || tradingConfig.pairByKey?.get(requestPairKey); const strategyConfig = pair?.strategyConfig || null; const legacyEureNotional = !pair?.priceRoute || pair.priceRoute.quoteAssetId === tradingConfig.tradingEure?.assetId; @@ -121,7 +123,11 @@ async function handleDemand(event) { decision_id: `duplicate-${event.payload.quote_id}`, decision_at: strategyReceivedAt, quote_id: event.payload.quote_id, - pair: event.payload.pair || `${event.payload.asset_in}->${event.payload.asset_out}`, + pair: requestPairKey, + request_pair: requestPairKey, + request_pair_id: tradingConfig.pairByKey?.get(requestPairKey)?.pairId || requestPairKey, + maker_trade_pair: makerPairKey, + maker_trade_pair_id: pair?.pairId || makerPairKey, pair_id: pair?.pairId || null, pair_config_id: strategyConfig?.configId || null, pair_config_version: strategyConfig?.version == null ? null : String(strategyConfig.version), @@ -135,6 +141,10 @@ async function handleDemand(event) { max_notional_eure: legacyEureNotional && strategyConfig?.maxNotional != null ? String(strategyConfig.maxNotional) : null, + maker_send_asset_id: event.payload.asset_out || null, + maker_receive_asset_id: event.payload.asset_in || null, + maker_send_symbol: tradingConfig.assetRegistry?.get(event.payload.asset_out)?.symbol || null, + maker_receive_symbol: tradingConfig.assetRegistry?.get(event.payload.asset_in)?.symbol || null, maker_timing: extendMakerTiming(event.payload.maker_timing, { strategy_received_at: strategyReceivedAt, strategy_decided_at: strategyReceivedAt, @@ -185,7 +195,8 @@ async function handleDemand(event) { function resolvePriceEventForDemand(event, tradingConfig) { const key = event.payload.pair || `${event.payload.asset_in}->${event.payload.asset_out}`; - const pair = tradingConfig.pairByKey?.get(key); + const makerKey = makerTradePairKeyForDemand(event.payload); + const pair = tradingConfig.pairByKey?.get(makerKey) || tradingConfig.pairByKey?.get(key); const routeId = pair?.priceRoute?.routeId || null; if (routeId) return state.latest_price_events_by_route[routeId] || null; return state.latest_price_events_by_pair[key] || state.latest_price_event; diff --git a/src/core/operator-dashboard.mjs b/src/core/operator-dashboard.mjs index 6d6d0d8..fe7ab03 100644 --- a/src/core/operator-dashboard.mjs +++ b/src/core/operator-dashboard.mjs @@ -1192,6 +1192,10 @@ export function deriveQuoteLifecycleRows({ quote_id: decision.quote_id, decision_id: decision.decision_id, pair: decision.pair, + request_pair: decision.request_pair, + request_pair_id: decision.request_pair_id, + maker_trade_pair: decision.maker_trade_pair, + maker_trade_pair_id: decision.maker_trade_pair_id, pair_id: decision.pair_id, pair_config_id: decision.pair_config_id, pair_config_version: decision.pair_config_version, @@ -1204,6 +1208,10 @@ export function deriveQuoteLifecycleRows({ inventory_available: decision.inventory_available, inventory_spendable: decision.inventory_spendable, inventory_pending_outbound: decision.inventory_pending_outbound, + maker_send_asset_id: decision.maker_send_asset_id, + maker_receive_asset_id: decision.maker_receive_asset_id, + maker_send_amount_units: decision.maker_send_amount_units, + maker_receive_amount_units: decision.maker_receive_amount_units, pending_inbound: decision.pending_inbound, pending_outbound: decision.pending_outbound, notional: decision.notional, @@ -1232,6 +1240,10 @@ export function deriveQuoteLifecycleRows({ decision_id: command.decision_id, command_id: command.command_id, pair: command.pair, + request_pair: command.request_pair, + request_pair_id: command.request_pair_id, + maker_trade_pair: command.maker_trade_pair, + maker_trade_pair_id: command.maker_trade_pair_id, pair_id: command.pair_id, pair_config_id: command.pair_config_id, pair_config_version: command.pair_config_version, @@ -1242,6 +1254,10 @@ export function deriveQuoteLifecycleRows({ asset_out: command.asset_out || null, amount_in: command.amount_in || null, amount_out: command.amount_out || null, + maker_send_asset_id: command.maker_send_asset_id || null, + maker_receive_asset_id: command.maker_receive_asset_id || null, + maker_send_amount_units: command.maker_send_amount_units || null, + maker_receive_amount_units: command.maker_receive_amount_units || null, notional: command.notional || null, notional_asset_id: command.notional_asset_id || null, notional_symbol: command.notional_symbol || null, @@ -1302,6 +1318,10 @@ function ensureLifecycleRow(rowsByKey, key) { decision_id: null, command_id: null, pair: null, + request_pair: null, + request_pair_id: null, + maker_trade_pair: null, + maker_trade_pair_id: null, pair_id: null, pair_config_id: null, pair_config_version: null, @@ -1326,6 +1346,10 @@ function ensureLifecycleRow(rowsByKey, key) { asset_out: null, amount_in: null, amount_out: null, + maker_send_asset_id: null, + maker_receive_asset_id: null, + maker_send_amount_units: null, + maker_receive_amount_units: null, command_at: null, execution_result_at: null, outcome_observed_at: null, @@ -1562,6 +1586,10 @@ function normalizeCommand(command) { execution_key: command.execution_key || null, quote_id: command.quote_id || null, pair: command.pair || null, + request_pair: command.request_pair || null, + request_pair_id: command.request_pair_id || null, + maker_trade_pair: command.maker_trade_pair || null, + maker_trade_pair_id: command.maker_trade_pair_id || null, pair_id: command.pair_id || null, pair_config_id: command.pair_config_id || null, pair_config_version: command.pair_config_version || null, @@ -1576,6 +1604,12 @@ function normalizeCommand(command) { asset_out: command.asset_out || null, amount_in: command.amount_in ?? null, amount_out: command.amount_out ?? null, + maker_send_asset_id: command.maker_send_asset_id || null, + maker_receive_asset_id: command.maker_receive_asset_id || null, + maker_send_symbol: command.maker_send_symbol || null, + maker_receive_symbol: command.maker_receive_symbol || null, + maker_send_amount_units: command.maker_send_amount_units || null, + maker_receive_amount_units: command.maker_receive_amount_units || null, maker_timing: command.maker_timing || null, quote_age_at_command_ms: command.quote_age_at_command_ms ?? null, command_at: command.command_at || command.observed_at || command.ingested_at || null, @@ -2107,7 +2141,7 @@ function enrichLifecycleRowForUi({ config, row }) { }), maker_terms: buildMakerLifecycleTerms({ config, - terms: row.command || row.execution || null, + terms: row.command || row.execution || row.decision || row, }), inventory_check: buildInventoryCheck({ config, @@ -2145,6 +2179,31 @@ function buildLifecycleTerms({ config, terms }) { } function buildMakerLifecycleTerms({ config, terms }) { + if (terms?.maker_send_asset_id || terms?.maker_receive_asset_id) { + const sendAsset = config.assetRegistry.get(terms.maker_send_asset_id); + const receiveAsset = config.assetRegistry.get(terms.maker_receive_asset_id); + const sendAmount = terms.maker_send_amount_units ?? null; + const receiveAmount = terms.maker_receive_amount_units ?? null; + const sendDisplay = sendAmount == null ? null : formatUnits(sendAmount, sendAsset?.decimals || 0); + const receiveDisplay = receiveAmount == null ? null : formatUnits(receiveAmount, receiveAsset?.decimals || 0); + const sendSymbol = sendAsset?.symbol || terms.maker_send_symbol || terms.maker_send_asset_id || null; + const receiveSymbol = receiveAsset?.symbol || terms.maker_receive_symbol || terms.maker_receive_asset_id || null; + return { + receive_asset: terms.maker_receive_asset_id || null, + receive_asset_symbol: receiveSymbol, + receive_amount_units: receiveAmount, + receive_amount: receiveDisplay, + send_asset: terms.maker_send_asset_id || null, + send_asset_symbol: sendSymbol, + send_amount_units: sendAmount, + send_amount: sendDisplay, + delta_text: [ + sendSymbol ? `-${sendDisplay || ''} ${sendSymbol}`.trim() : null, + receiveSymbol ? `+${receiveDisplay || ''} ${receiveSymbol}`.trim() : null, + ].filter(Boolean).join(' / '), + }; + } + const relayTerms = buildLifecycleTerms({ config, terms }); if (!relayTerms?.asset_in && !relayTerms?.asset_out) return null; return { @@ -2156,6 +2215,10 @@ function buildMakerLifecycleTerms({ config, terms }) { send_asset_symbol: relayTerms.asset_out_symbol, send_amount_units: relayTerms.amount_out_units, send_amount: relayTerms.amount_out, + delta_text: [ + relayTerms.asset_out ? `-${relayTerms.amount_out || ''} ${relayTerms.asset_out_symbol || relayTerms.asset_out}`.trim() : null, + relayTerms.asset_in ? `+${relayTerms.amount_in || ''} ${relayTerms.asset_in_symbol || relayTerms.asset_in}`.trim() : null, + ].filter(Boolean).join(' / '), }; } @@ -2391,6 +2454,10 @@ function normalizeDecision(decision) { decision_at: decision.decision_at || null, quote_id: decision.quote_id || null, pair: decision.pair || null, + request_pair: decision.request_pair || null, + request_pair_id: decision.request_pair_id || null, + maker_trade_pair: decision.maker_trade_pair || null, + maker_trade_pair_id: decision.maker_trade_pair_id || null, pair_id: decision.pair_id || null, pair_config_id: decision.pair_config_id || null, pair_config_version: decision.pair_config_version || null, @@ -2409,6 +2476,12 @@ function normalizeDecision(decision) { inventory_available: decision.inventory_available || null, inventory_spendable: decision.inventory_spendable || null, inventory_pending_outbound: decision.inventory_pending_outbound || null, + maker_send_asset_id: decision.maker_send_asset_id || null, + maker_receive_asset_id: decision.maker_receive_asset_id || null, + maker_send_symbol: decision.maker_send_symbol || null, + maker_receive_symbol: decision.maker_receive_symbol || null, + maker_send_amount_units: decision.maker_send_amount_units || null, + maker_receive_amount_units: decision.maker_receive_amount_units || null, pending_inbound: decision.pending_inbound || null, pending_outbound: decision.pending_outbound || null, notional: decision.notional || null, @@ -2569,6 +2642,10 @@ function normalizeLiveExecutionResult(payload, event) { execution_key: payload.execution_key || null, quote_id: payload.quote_id || null, pair: payload.pair || null, + request_pair: payload.request_pair || null, + request_pair_id: payload.request_pair_id || null, + maker_trade_pair: payload.maker_trade_pair || null, + maker_trade_pair_id: payload.maker_trade_pair_id || null, result_at: event.observed_at || event.ingested_at || new Date().toISOString(), status: payload.status || null, result_code: payload.result_code || null, @@ -2582,6 +2659,10 @@ function normalizeLiveExecutionResult(payload, event) { error_message: payload.relay_error_message || payload.error?.message || null, note: payload.note || null, timing: payload.executor_timing || null, + maker_send_asset_id: payload.maker_send_asset_id || null, + maker_receive_asset_id: payload.maker_receive_asset_id || null, + maker_send_amount_units: payload.maker_send_amount_units || null, + maker_receive_amount_units: payload.maker_receive_amount_units || null, maker_timing: payload.maker_timing || null, }; } diff --git a/src/core/strategy.mjs b/src/core/strategy.mjs index 93f84f7..dd3852a 100644 --- a/src/core/strategy.mjs +++ b/src/core/strategy.mjs @@ -19,6 +19,11 @@ import { const EDGE_EPSILON_PCT = 1e-9; +export function makerTradePairKeyForDemand(payload = {}) { + if (!payload?.asset_in || !payload?.asset_out) return null; + return pairKey(payload.asset_out, payload.asset_in); +} + export function evaluateTradeOpportunity({ demandEvent, priceEvent, @@ -31,6 +36,8 @@ export function evaluateTradeOpportunity({ maxNotional = config.strategyMaxNotional ?? config.strategyMaxNotionalEure, }) { const payload = demandEvent.payload; + const requestPairKey = pairKey(payload.asset_in, payload.asset_out); + const makerTradePairKey = makerTradePairKeyForDemand(payload); const pairRuntime = resolvePairRuntime({ payload, config, thresholdPct, maxNotional }); const effectiveThresholdPct = pairRuntime.thresholdPct ?? thresholdPct; const effectiveMaxNotional = pairRuntime.maxNotional ?? maxNotional; @@ -46,7 +53,11 @@ export function evaluateTradeOpportunity({ decision_id: decisionId, decision_at: decisionAt, quote_id: payload.quote_id, - pair: pairKey(payload.asset_in, payload.asset_out), + pair: requestPairKey, + request_pair: requestPairKey, + request_pair_id: pairRuntime.requestPair?.pairId || requestPairKey, + maker_trade_pair: pairRuntime.makerTradePairKey || makerTradePairKey, + maker_trade_pair_id: pairRuntime.makerTradePair?.pairId || pairRuntime.pair?.pairId || makerTradePairKey, pair_id: pairRuntime.pair?.pairId || null, pair_config_id: pairRuntime.strategyConfig?.configId || null, pair_config_version: pairRuntime.strategyConfig?.version == null @@ -69,6 +80,10 @@ export function evaluateTradeOpportunity({ maker_timing: makerTiming, quote_age_at_decision_ms: makerTiming.quote_age_at_decision_ms, strategy_armed: armed, + maker_send_asset_id: payload.asset_out, + maker_receive_asset_id: payload.asset_in, + maker_send_symbol: pairRuntime.assetOut?.symbol || null, + maker_receive_symbol: pairRuntime.assetIn?.symbol || null, assumptions: compact({ eure_per_eur: pairRuntime.priceRoute?.source === 'btc_eur_reference' ? '1' : null, usdc_per_usd: pairRuntime.priceRoute?.source === 'btc_usdc_reference' ? '1' : null, @@ -140,10 +155,16 @@ export function evaluateTradeOpportunity({ }); if (!buildResult.ok) { + const makerTerms = buildMakerTradeTerms({ + demand: payload, + proposedAmountIn: buildResult.details?.proposed_amount_in ?? null, + proposedAmountOut: buildResult.details?.proposed_amount_out ?? null, + }); return { decision: { ...baseDecision, ...buildResult.details, + ...makerTerms, price_freshness_ms: String(priceAgeMs), inventory_freshness_ms: String(inventoryAgeMs), decision_reason: buildResult.reason, @@ -151,9 +172,16 @@ export function evaluateTradeOpportunity({ }; } + const makerTerms = buildMakerTradeTerms({ + demand: payload, + quoteOutput: buildResult.quoteOutput, + proposedAmountIn: buildResult.details.proposed_amount_in ?? null, + proposedAmountOut: buildResult.details.proposed_amount_out ?? null, + }); const decision = { ...baseDecision, ...buildResult.details, + ...makerTerms, price_freshness_ms: String(priceAgeMs), inventory_freshness_ms: String(inventoryAgeMs), decision: armed ? 'actionable' : 'rejected', @@ -206,6 +234,7 @@ export function evaluateTradeOpportunity({ quote_output: buildResult.quoteOutput, proposed_amount_in: buildResult.details.proposed_amount_in ?? null, proposed_amount_out: buildResult.details.proposed_amount_out ?? null, + ...makerTerms, expected_inventory_delta_units: buildExpectedMakerInventoryDelta({ demand: payload, quoteOutput: buildResult.quoteOutput, @@ -563,13 +592,35 @@ function buildExpectedMakerInventoryDelta({ }; } +function buildMakerTradeTerms({ + demand, + quoteOutput = {}, + proposedAmountIn = null, + proposedAmountOut = null, +} = {}) { + if (!demand?.asset_in || !demand?.asset_out || !demand?.request_kind) return {}; + const receiveAmount = demand.request_kind === 'exact_in' + ? demand.amount_in + : quoteOutput?.amount_in || proposedAmountIn; + const sendAmount = demand.request_kind === 'exact_in' + ? quoteOutput?.amount_out || proposedAmountOut + : demand.amount_out; + return compact({ + maker_send_asset_id: demand.asset_out, + maker_receive_asset_id: demand.asset_in, + maker_send_amount_units: sendAmount == null ? null : String(sendAmount), + maker_receive_amount_units: receiveAmount == null ? null : String(receiveAmount), + }); +} + function resolvePairRuntime({ payload, config, thresholdPct, maxNotional, }) { - const key = pairKey(payload.asset_in, payload.asset_out); + const requestKey = pairKey(payload.asset_in, payload.asset_out); + const makerTradeKey = makerTradePairKeyForDemand(payload); const requiresDb = config.requireDbTradingConfig === true || config.tradingConfigLoaded === true; if (!requiresDb) { @@ -619,18 +670,21 @@ function resolvePairRuntime({ }; } - const pair = config.pairByKey?.get(key) || null; - if (!pair || !pair.enabled || !pair.observeEnabled) { + const requestPair = config.pairByKey?.get(requestKey) || null; + if (!requestPair || !requestPair.enabled || !requestPair.observeEnabled) { return { ok: false, - reason: pair ? 'pair_disabled' : 'unsupported_pair', + reason: requestPair ? 'pair_disabled' : 'unsupported_pair', direction: 'unsupported', - pair, - strategyConfig: pair?.strategyConfig || null, - priceRoute: pair?.priceRoute || null, + pair: null, + requestPair, + makerTradePair: null, + makerTradePairKey: makerTradeKey, + strategyConfig: null, + priceRoute: requestPair?.priceRoute || null, assetRegistry: config.assetRegistry, - assetIn: pair?.assetIn || config.assetRegistry?.get(payload.asset_in) || null, - assetOut: pair?.assetOut || config.assetRegistry?.get(payload.asset_out) || null, + assetIn: config.assetRegistry?.get(payload.asset_in) || null, + assetOut: config.assetRegistry?.get(payload.asset_out) || null, thresholdPct, maxNotional, priceMaxAgeMs: config.strategyPriceMaxAgeMs, @@ -638,24 +692,34 @@ function resolvePairRuntime({ }; } - if (!pair.makerEnabled) return blockedPairRuntime(pair, config, 'pair_not_maker_enabled'); - if (pair.blockReason) return blockedPairRuntime(pair, config, pair.blockReason); - if (!pair.strategyConfig) return blockedPairRuntime(pair, config, 'pair_strategy_config_missing'); - if (!pair.priceRoute) return blockedPairRuntime(pair, config, 'price_route_missing'); + const pair = config.pairByKey?.get(makerTradeKey) || null; + if (!pair) { + return blockedPairRuntime(null, config, 'maker_trade_pair_unavailable', { payload, requestPair, makerTradePairKey: makerTradeKey }); + } + if (!pair.makerEnabled) return blockedPairRuntime(pair, config, 'pair_not_maker_enabled', { payload, requestPair, makerTradePairKey: makerTradeKey }); + if (pair.blockReason) return blockedPairRuntime(pair, config, pair.blockReason, { payload, requestPair, makerTradePairKey: makerTradeKey }); + if (!pair.strategyConfig) return blockedPairRuntime(pair, config, 'pair_strategy_config_missing', { payload, requestPair, makerTradePairKey: makerTradeKey }); + const priceRoute = pair.priceRoute || requestPair.priceRoute || null; + if (!priceRoute) return blockedPairRuntime(pair, config, 'price_route_missing', { payload, requestPair, makerTradePairKey: makerTradeKey }); - const direction = classifyPriceRouteDirection({ payload, priceRoute: pair.priceRoute }); - if (direction === 'unsupported') return blockedPairRuntime(pair, config, 'price_route_missing'); + const direction = classifyPriceRouteDirection({ payload, priceRoute }); + if (direction === 'unsupported') { + return blockedPairRuntime(pair, config, 'price_route_missing', { payload, requestPair, makerTradePairKey: makerTradeKey, priceRoute }); + } return { ok: true, reason: null, direction, pair, + requestPair, + makerTradePair: pair, + makerTradePairKey: makerTradeKey, strategyConfig: pair.strategyConfig, - priceRoute: pair.priceRoute, + priceRoute, assetRegistry: config.assetRegistry, - assetIn: pair.assetIn, - assetOut: pair.assetOut, + assetIn: config.assetRegistry?.get(payload.asset_in) || null, + assetOut: config.assetRegistry?.get(payload.asset_out) || null, thresholdPct: Number(pair.strategyConfig.edgeBps) / 100, maxNotional: Number(pair.strategyConfig.maxNotional), minNotional: Number(pair.strategyConfig.minNotional ?? 0), @@ -664,20 +728,35 @@ function resolvePairRuntime({ }; } -function blockedPairRuntime(pair, config, reason) { +function blockedPairRuntime(pair, config, reason, { + payload = null, + requestPair = null, + makerTradePairKey = pair?.key || pair?.pairId || null, + priceRoute = null, +} = {}) { + const assetIn = payload?.asset_in + ? config.assetRegistry?.get(payload.asset_in) || null + : pair?.assetIn || null; + const assetOut = payload?.asset_out + ? config.assetRegistry?.get(payload.asset_out) || null + : pair?.assetOut || null; + const effectivePriceRoute = priceRoute || pair?.priceRoute || requestPair?.priceRoute || null; return { ok: false, reason, direction: classifyPriceRouteDirection({ - payload: { asset_in: pair?.asset_in, asset_out: pair?.asset_out }, - priceRoute: pair?.priceRoute, + payload: payload || { asset_in: pair?.asset_in, asset_out: pair?.asset_out }, + priceRoute: effectivePriceRoute, }), pair, + requestPair, + makerTradePair: pair || null, + makerTradePairKey, strategyConfig: pair?.strategyConfig || null, - priceRoute: pair?.priceRoute || null, + priceRoute: effectivePriceRoute, assetRegistry: config.assetRegistry, - assetIn: pair?.assetIn || null, - assetOut: pair?.assetOut || null, + assetIn, + assetOut, thresholdPct: pair?.strategyConfig ? Number(pair.strategyConfig.edgeBps) / 100 : config.strategyGrossThresholdPct, maxNotional: pair?.strategyConfig ? Number(pair.strategyConfig.maxNotional) diff --git a/src/operator-dashboard/static/pages/StrategyPage.jsx b/src/operator-dashboard/static/pages/StrategyPage.jsx index c2f2e8f..ae46044 100644 --- a/src/operator-dashboard/static/pages/StrategyPage.jsx +++ b/src/operator-dashboard/static/pages/StrategyPage.jsx @@ -104,13 +104,14 @@ function formatTerms(terms) { function formatMakerTerms(terms) { if (!terms) return null; + if (terms.delta_text) return `For us: ${terms.delta_text}`; const send = terms.send_amount ? `${terms.send_amount} ${terms.send_asset_symbol || ''}`.trim() : terms.send_asset_symbol || terms.send_asset || 'send unavailable'; const receive = terms.receive_amount ? `${terms.receive_amount} ${terms.receive_asset_symbol || ''}`.trim() : terms.receive_asset_symbol || terms.receive_asset || 'receive unavailable'; - return `Maker sends ${send}; receives ${receive}`; + return `For us: -${send}; +${receive}`; } function formatInventoryCheck(check) { @@ -301,6 +302,22 @@ function pairDisplayLabel(pairId, pairConfig) { return `${pair.asset_in_symbol || pair.asset_in || pair.assetIn} -> ${pair.asset_out_symbol || pair.asset_out || pair.assetOut}`; } +function pairAssetInSymbol(pair) { + return pair.asset_in_symbol || pair.assetInSymbol || pair.asset_in || pair.assetIn || 'send asset'; +} + +function pairAssetOutSymbol(pair) { + return pair.asset_out_symbol || pair.assetOutSymbol || pair.asset_out || pair.assetOut || 'receive asset'; +} + +function makerPairMovementLabel(pair) { + return `For us: -${pairAssetInSymbol(pair)} / +${pairAssetOutSymbol(pair)}`; +} + +function makerPairRequestLabel(pair) { + return `Requests: ${pairAssetOutSymbol(pair)} -> ${pairAssetInSymbol(pair)}`; +} + function fixedRows(items, count) { const rows = (items || []).slice(0, count); while (rows.length < count) rows.push(null); @@ -760,8 +777,11 @@ function QuoteLifecycleTable({ items }) {