Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 85 additions & 8 deletions src/handlers/market.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type OrderbookIncrements,
type OrderbookLevel,
} from '../output/orderbook.js';
import { ToolExecutionError } from '../utils/errors.js';
import { ToolExecutionError, formatError } from '../utils/errors.js';
import { fmtProductIds } from '../utils/formatting.js';
import { resolveMarketData } from '../utils/orderBuilder.js';
import { getMarkets } from '../utils/resolveMarket.js';
Expand All @@ -17,13 +17,83 @@ import type { CommandResult } from './types.js';
// get_all_markets
// ---------------------------------------------------------------------------

type MarketListKind = 'perp' | 'spot';

function resolveMarketListLabels(
productId: number,
kind: MarketListKind,
meta: { marketName: string; symbol: string } | undefined,
engineSymbol: string | undefined,
): { name: string; symbol: string } {
const metaSym = meta?.symbol?.trim();
const metaName = meta?.marketName?.trim();
const engSym = engineSymbol?.trim();

const symbol = metaSym || engSym || '-';
const name = metaName || metaSym || engSym || `${kind} ${productId}`;
return { name, symbol };
}

/**
* Normalizes engine `getSymbols` payloads: some responses use a Record map,
* others may expose an array — avoids `symbols.map is not a function`.
*/
function engineSymbolMapFromResponse(symbolsRaw: unknown): Map<number, string> {
const map = new Map<number, string>();
try {
const entries: unknown[] = Array.isArray(symbolsRaw)
? symbolsRaw
: symbolsRaw && typeof symbolsRaw === 'object'
? Object.values(symbolsRaw as Record<string, unknown>)
: [];
for (const entry of entries) {
if (!entry || typeof entry !== 'object') continue;
const e = entry as { productId?: unknown; symbol?: unknown };
const productId = e.productId;
const sym = typeof e.symbol === 'string' ? e.symbol.trim() : '';
if (typeof productId === 'number' && sym) {
map.set(productId, sym);
}
}
} catch {
// ignore malformed payloads
}
return map;
}

export async function getAllMarkets(ctx: NadoContext): Promise<CommandResult> {
try {
const [data, metadata] = await Promise.all([
const [marketsRes, metaRes, symsRes] = await Promise.allSettled([
ctx.client.market.getAllMarkets(),
getMarkets(ctx.dataEnv, ctx.chainEnv).catch(() => []),
getMarkets(ctx.dataEnv, ctx.chainEnv),
ctx.client.context.engineClient.getSymbols({}),
]);
const nameById = new Map(metadata.map((m) => [m.productId, m]));

if (marketsRes.status === 'rejected') {
throw marketsRes.reason;
}
const data = [...marketsRes.value].sort(
(a, b) => a.productId - b.productId,
);

const metadata = metaRes.status === 'fulfilled' ? metaRes.value : [];
if (metaRes.status === 'rejected') {
console.warn(
`[nado] Product metadata unavailable: ${formatError(metaRes.reason)}. Name/Symbol may be incomplete.`,
);
}

const engineById =
symsRes.status === 'fulfilled'
? engineSymbolMapFromResponse(symsRes.value.symbols)
: new Map<number, string>();
if (symsRes.status === 'rejected') {
console.warn(
`[nado] Engine symbols unavailable: ${formatError(symsRes.reason)}. Symbol column may rely on metadata only.`,
);
}

const metaById = new Map(metadata.map((m) => [m.productId, m]));

return {
data,
Expand All @@ -36,12 +106,19 @@ export async function getAllMarkets(ctx: NadoContext): Promise<CommandResult> {
'Size Increment',
],
rows: data.map((m) => {
const meta = nameById.get(m.productId);
const kind: MarketListKind =
m.type === ProductEngineType.PERP ? 'perp' : 'spot';
const { name, symbol } = resolveMarketListLabels(
m.productId,
kind,
metaById.get(m.productId),
engineById.get(m.productId),
);
return [
String(m.productId),
meta?.marketName ?? '-',
meta?.symbol ?? '-',
m.type === ProductEngineType.PERP ? 'perp' : 'spot',
name,
symbol,
kind,
m.priceIncrement.toFixed(),
m.sizeIncrement.toFixed(),
];
Expand Down