> ## Documentation Index
> Fetch the complete documentation index at: https://docs.0xmonaco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Markets

> View the supported spot and perps markets available on Monaco, including leverage and product support.

export const MONACO_MARKETS_API_BASE = "https://staging.apimonaco.xyz/api/v1";
export const MONACO_MARKETS_PAIRS_CACHE_KEY = "pairs-active-100";
export const MONACO_MARKETS_SCREENER_CACHE_KEY = "screener-active-100";
export const MONACO_MARKETS_PAIRS_URL = `${MONACO_MARKETS_API_BASE}/market/pairs?page=1&pageSize=100&isActive=true`;
export const MONACO_MARKETS_SCREENER_URL = `${MONACO_MARKETS_API_BASE}/market/screener?page=1&pageSize=100&isActive=true`;
export const MONACO_MARKET_DATA_CACHE_TTL_MS = 60000;
export const MONACO_MARKET_DATA_STALE_MS = 5 * 60000;
export const MONACO_MARKET_DATA_CACHE_PREFIX = "monaco-docs-market-data:";

export function monacoMarketCacheStore() {
  const root = globalThis;
  if (!root.__monacoMarketDataCache) {
    root.__monacoMarketDataCache = {
      values: {},
      inflight: {}
    };
  }
  return root.__monacoMarketDataCache;
}

export function monacoMarketCacheStorageKey(key) {
  return `${MONACO_MARKET_DATA_CACHE_PREFIX}${key}`;
}

export function readMonacoMarketLocalStorageCache(key) {
  try {
    const storage = globalThis.localStorage;
    const value = storage ? storage.getItem(monacoMarketCacheStorageKey(key)) : null;
    if (!value) return null;
    return JSON.parse(value);
  } catch (error) {
    return null;
  }
}

export function writeMonacoMarketLocalStorageCache(key, entry) {
  try {
    const storage = globalThis.localStorage;
    if (storage) storage.setItem(monacoMarketCacheStorageKey(key), JSON.stringify(entry));
  } catch (error) {}
}

export function readMonacoMarketCache(key, options = {}) {
  const ttlMs = options.ttlMs == null ? MONACO_MARKET_DATA_CACHE_TTL_MS : options.ttlMs;
  const staleMs = options.staleMs == null ? MONACO_MARKET_DATA_STALE_MS : options.staleMs;
  const now = Date.now();
  const store = monacoMarketCacheStore();
  const memoryEntry = store.values[key];
  const localEntry = memoryEntry || readMonacoMarketLocalStorageCache(key);
  const age = localEntry ? now - Number(localEntry.timestamp || 0) : Infinity;
  if (!localEntry || age > staleMs) return null;
  store.values[key] = localEntry;
  return {
    data: localEntry.data,
    timestamp: localEntry.timestamp,
    isFresh: age <= ttlMs
  };
}

export async function fetchMonacoMarketData(key, url) {
  const store = monacoMarketCacheStore();
  if (store.inflight[key]) return store.inflight[key];
  store.inflight[key] = fetch(url).then(async response => {
    if (!response.ok) throw new Error("Unable to load Monaco market data.");
    const entry = {
      data: await response.json(),
      timestamp: Date.now()
    };
    store.values[key] = entry;
    writeMonacoMarketLocalStorageCache(key, entry);
    return entry;
  }).finally(() => {
    delete store.inflight[key];
  });
  return store.inflight[key];
}

export function formatMonacoMarketCacheTime(timestamp) {
  if (!timestamp) return "";
  return new Date(timestamp).toLocaleTimeString(undefined, {
    hour: "numeric",
    minute: "2-digit"
  });
}

export const monacoMarketsStyles = `
.monaco-markets {
	--markets-surface: #ffffff;
	--markets-muted-surface: rgba(35, 51, 41, 0.045);
	--markets-border: rgba(35, 51, 41, 0.12);
	--markets-row-border: rgba(35, 51, 41, 0.035);
	--markets-border-strong: rgba(35, 51, 41, 0.18);
	--markets-text: #18231d;
	--markets-muted: #526258;
	--markets-positive: #1f7a4d;
	--markets-negative: #b42318;
	--markets-primary: #233329;
	--markets-row-h: 3.8rem;
	--markets-columns: minmax(9rem, 1.45fr) minmax(6rem, 0.7fr) minmax(6.25rem, 0.75fr) minmax(7rem, 0.6fr);
	width: 100%;
	min-width: 0;
	max-width: 100%;
	margin: 1.25rem 0 2rem;
	color: var(--markets-text);
	overflow: hidden;
}

:where(.dark, [data-theme='dark']) .monaco-markets {
	--markets-surface: rgba(10, 15, 12, 0.88);
	--markets-muted-surface: rgba(26, 38, 32, 0.42);
	--markets-border: rgba(166, 190, 174, 0.16);
	--markets-row-border: rgba(166, 190, 174, 0.035);
	--markets-border-strong: rgba(166, 190, 174, 0.24);
	--markets-text: #f4faf5;
	--markets-muted: #b6c4ba;
	--markets-positive: #74d99f;
	--markets-negative: #ff8d80;
	--markets-primary: #d8e8dc;
}

.monaco-markets-controls {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: flex-start;
	gap: 0.65rem;
	margin-bottom: 0.85rem;
}

.monaco-markets-note {
	margin: -0.2rem 0 0.85rem;
	color: var(--markets-muted);
	font-size: 0.75rem;
	line-height: 1.1rem;
}

.monaco-markets-search {
	display: flex;
	align-items: center;
	gap: 0.4rem;
	width: min(22rem, 100%);
	min-height: 2.1rem;
	padding: 0 0.7rem;
	border: 1px solid var(--markets-border);
	border-radius: 999px;
	background: var(--markets-muted-surface);
	transition:
		background-color 150ms ease,
		border-color 150ms ease,
		box-shadow 150ms ease;
}

.monaco-markets-search:hover {
	border-color: #18231d;
}

:where(.dark, [data-theme='dark']) .monaco-markets-search:hover {
	border-color: #f4faf5;
}

.monaco-markets-search:focus-within {
	border-color: #18231d;
	box-shadow: 0 0 0 3px rgba(24, 35, 29, 0.18);
}

:where(.dark, [data-theme='dark']) .monaco-markets-search:focus-within {
	border-color: #f4faf5;
	box-shadow: 0 0 0 3px rgba(244, 250, 245, 0.22);
}

.monaco-markets-search-icon {
	flex: 0 0 auto;
	color: var(--markets-muted);
}

.monaco-markets-search-input {
	flex: 1 1 auto;
	width: 100%;
	min-width: 0;
	min-height: 2rem;
	border: 0;
	background: transparent;
	color: var(--markets-text);
	padding: 0.32rem 0;
	font-size: 0.78rem;
	font-weight: 700;
	outline: none;
}

.monaco-markets-search-input::placeholder {
	color: var(--markets-muted);
}

.monaco-markets-search-input::-webkit-search-cancel-button,
.monaco-markets-search-input::-webkit-search-decoration,
.monaco-markets-search-input::-webkit-search-results-button,
.monaco-markets-search-input::-webkit-search-results-decoration {
	-webkit-appearance: none;
	appearance: none;
}

.monaco-markets-search-clear {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 1.4rem;
	height: 1.4rem;
	padding: 0;
	border: 0;
	border-radius: 999px;
	background: transparent;
	color: var(--markets-muted);
	cursor: pointer;
	outline: none;
	transition:
		background-color 150ms ease,
		color 150ms ease;
}

.monaco-markets-search-clear[hidden] {
	display: none;
}

.monaco-markets-search-clear:hover {
	background: rgba(24, 35, 29, 0.08);
	color: var(--markets-text);
}

:where(.dark, [data-theme='dark']) .monaco-markets-search-clear:hover {
	background: rgba(244, 250, 245, 0.12);
	color: var(--markets-text);
}

.monaco-markets-search-clear:focus-visible {
	color: var(--markets-text);
	box-shadow: 0 0 0 3px rgba(24, 35, 29, 0.18);
}

:where(.dark, [data-theme='dark']) .monaco-markets-search-clear:focus-visible {
	box-shadow: 0 0 0 3px rgba(244, 250, 245, 0.22);
}

.monaco-markets-filters {
	display: inline-flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 0.4rem;
	min-width: 0;
}

.monaco-markets-segment {
	display: inline-flex;
	align-items: center;
	gap: 0.15rem;
	padding: 0.2rem;
	border: 1px solid var(--markets-border);
	border-radius: 999px;
	background: rgba(35, 51, 41, 0.06);
	box-shadow: inset 0 1px 2px rgba(24, 35, 29, 0.06);
	-webkit-backdrop-filter: blur(12px) saturate(140%);
	backdrop-filter: blur(12px) saturate(140%);
}

:where(.dark, [data-theme='dark']) .monaco-markets-segment {
	background: rgba(8, 12, 10, 0.5);
	box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.35);
}

.monaco-markets-segment-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 1.7rem;
	padding: 0.28rem 0.95rem;
	border: 1px solid transparent;
	border-radius: 999px;
	background: transparent;
	color: var(--markets-muted);
	font-size: 0.74rem;
	font-weight: 750;
	cursor: pointer;
	outline: none;
	transition:
		background-color 180ms ease,
		box-shadow 180ms ease,
		color 180ms ease;
}

.monaco-markets-segment-btn:hover {
	color: var(--markets-text);
}

.monaco-markets-segment-btn-active {
	color: var(--markets-text);
	background: linear-gradient(180deg, rgba(255, 255, 255, 0.92), rgba(255, 255, 255, 0.74));
	border-color: rgba(255, 255, 255, 0.7);
	box-shadow:
		0 1px 2px rgba(24, 35, 29, 0.18),
		0 0 0 0.5px rgba(24, 35, 29, 0.06),
		inset 0 1px 0 rgba(255, 255, 255, 0.9);
}

:where(.dark, [data-theme='dark']) .monaco-markets-segment-btn-active {
	color: var(--markets-text);
	background: linear-gradient(180deg, rgba(255, 255, 255, 0.16), rgba(255, 255, 255, 0.08));
	border-color: rgba(255, 255, 255, 0.16);
	box-shadow:
		0 1px 3px rgba(0, 0, 0, 0.45),
		inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

.monaco-markets-segment-btn:focus-visible {
	box-shadow: 0 0 0 3px rgba(24, 35, 29, 0.18);
}

:where(.dark, [data-theme='dark']) .monaco-markets-segment-btn:focus-visible {
	box-shadow: 0 0 0 3px rgba(244, 250, 245, 0.22);
}

.monaco-markets-meta {
	margin-top: 0.5rem;
	color: var(--markets-muted);
	font-size: 0.75rem;
	line-height: 1.1rem;
}

.monaco-markets-table-wrap {
	position: relative;
	width: 100%;
	min-width: 0;
	max-width: 100%;
	overflow-x: auto;
	overflow-y: hidden;
	-webkit-overflow-scrolling: touch;
	scrollbar-color: var(--markets-border-strong) transparent;
	scrollbar-gutter: stable;
	scrollbar-width: thin;
	border: 1px solid var(--markets-border);
	border-radius: 12px;
	background: transparent;
}

.monaco-markets-table-wrap::-webkit-scrollbar {
	height: 0.65rem;
}

.monaco-markets-table-wrap::-webkit-scrollbar-track {
	background: transparent;
}

.monaco-markets-table-wrap::-webkit-scrollbar-thumb {
	border: 0.2rem solid transparent;
	border-radius: 999px;
	background: var(--markets-border-strong);
	background-clip: padding-box;
}

.monaco-markets-grid {
	width: 100%;
	min-width: 0;
	font-size: 0.78rem;
}

.monaco-markets-row {
	display: grid;
	align-items: center;
	grid-template-columns: var(--markets-columns);
	gap: 0.5rem;
	width: 100%;
	height: var(--markets-row-h);
	box-sizing: border-box;
	border-bottom: 1px solid var(--markets-row-border);
}

.monaco-markets-pad-row {
	pointer-events: none;
}

.monaco-markets-row-link {
	color: inherit;
	text-decoration: none;
	transition:
		background-color 150ms ease,
		border-color 150ms ease;
}

.monaco-markets-row-link:hover {
	background: rgba(35, 51, 41, 0.045);
}

:where(.dark, [data-theme='dark']) .monaco-markets-row-link:hover {
	background: rgba(244, 250, 245, 0.045);
}

.monaco-markets-row-link:focus-visible {
	border-radius: 8px;
	outline: 2px solid var(--markets-primary);
	outline-offset: -2px;
}

.monaco-markets-row:last-child {
	border-bottom: 0;
}

.monaco-markets-cell {
	display: flex;
	align-items: center;
	min-width: 0;
	overflow: hidden;
	padding: 0.72rem 0.55rem;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.monaco-markets-products-cell {
	align-items: center;
	overflow: visible;
	white-space: normal;
}

.monaco-markets-market {
	display: flex;
	align-items: center;
	gap: 0.55rem;
	min-width: 0;
	max-width: 100%;
}

.monaco-markets-icon {
	height: 1.8rem;
	width: 1.8rem;
	flex: 0 0 auto;
	border-radius: 999px;
	background: var(--markets-muted-surface);
	object-fit: contain;
}

.monaco-markets-icon-fallback {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	color: var(--markets-muted);
	font-size: 0.7rem;
	font-weight: 800;
}

.monaco-markets-market-copy {
	min-width: 0;
	overflow: hidden;
}

.monaco-markets-symbol {
	overflow: hidden;
	text-overflow: ellipsis;
	font-weight: 850;
	line-height: 1.05rem;
	white-space: nowrap;
}

.monaco-markets-category {
	overflow: hidden;
	text-overflow: ellipsis;
	color: var(--markets-muted);
	font-size: 0.72rem;
	line-height: 0.95rem;
	text-transform: capitalize;
	white-space: nowrap;
}

.monaco-markets-products {
	display: flex;
	flex-wrap: nowrap;
	max-width: 100%;
	gap: 0.2rem;
	overflow: visible;
}

.monaco-markets-chip {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	border: 1px solid var(--markets-border);
	border-radius: 6px;
	background: var(--markets-muted-surface);
	color: var(--markets-muted);
	padding: 0.16rem 0.36rem;
	font-size: 0.64rem;
	font-weight: 800;
	line-height: 0.9rem;
	white-space: nowrap;
}

.monaco-markets-chip-active {
	color: var(--markets-primary);
}

.monaco-markets-chip-unavailable {
	border-color: rgba(180, 35, 24, 0.22);
	background: rgba(180, 35, 24, 0.055);
	color: var(--markets-negative);
	opacity: 0.72;
	text-decoration: line-through;
}

.monaco-markets-number {
	font-variant-numeric: tabular-nums;
	font-weight: 750;
}

.monaco-markets-quote {
	display: flex;
	min-width: 0;
	align-items: center;
	justify-content: flex-end;
	width: 100%;
	gap: 1rem;
	white-space: normal;
}

.monaco-markets-price {
	flex: 0 0 auto;
	font-size: 0.84rem;
}

.monaco-markets-price-stack {
	display: grid;
	gap: 0.12rem;
	text-align: right;
}

.monaco-markets-change {
	display: flex;
	min-width: 0;
	align-items: center;
	justify-content: flex-end;
	font-size: 0.72rem;
	line-height: 1rem;
}

.monaco-markets-activity {
	display: grid;
	min-width: 0;
	align-items: end;
	justify-content: flex-end;
	gap: 0.1rem;
	text-align: right;
}

.monaco-markets-activity-volume {
	overflow: hidden;
	color: var(--markets-muted);
	font-size: 0.68rem;
	font-weight: 700;
	line-height: 0.95rem;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.monaco-markets-muted {
	color: var(--markets-muted);
}

.monaco-markets-positive {
	color: var(--markets-positive);
}

.monaco-markets-negative {
	color: var(--markets-negative);
}

.monaco-markets-scroll-hint {
	display: none;
	margin-top: 0.35rem;
	color: var(--markets-muted);
	font-size: 0.68rem;
	line-height: 1rem;
}

.monaco-markets-empty {
	display: flex;
	grid-column: 1 / -1;
	align-items: center;
	justify-content: center;
	min-height: calc(var(--markets-row-h) * 1.5);
	padding: 1.15rem;
	color: var(--markets-muted);
	font-size: 0.85rem;
	line-height: 1.45;
	text-align: center;
}

.monaco-markets-skeleton-row {
	height: var(--markets-row-h);
}

.monaco-markets-skeleton {
	display: block;
	height: 0.8rem;
	width: 100%;
	max-width: 8rem;
	border-radius: 999px;
	background: linear-gradient(90deg, var(--markets-muted-surface), var(--markets-border), var(--markets-muted-surface));
	background-size: 220% 100%;
	animation: monacoMarketsPulse 1.4s ease-in-out infinite;
}

.monaco-markets-skeleton-icon {
	height: 1.8rem;
	width: 1.8rem;
	flex: 0 0 auto;
	border-radius: 999px;
}

.monaco-markets-skeleton-short {
	max-width: 4.5rem;
}

.monaco-markets-skeleton-medium {
	max-width: 6.5rem;
}

.monaco-markets-skeleton-wide {
	max-width: 10rem;
}

@keyframes monacoMarketsPulse {
	0% {
		background-position: 180% 0;
	}
	100% {
		background-position: -80% 0;
	}
}

@media (max-width: 760px) {
	.monaco-markets-scroll-hint {
		display: block;
	}

	.monaco-markets-search {
		width: 100%;
	}
}

@media (max-width: 520px) {
	.monaco-markets-products {
		flex-wrap: wrap;
	}
}

.monaco-markets-head {
	display: grid;
	align-items: center;
	grid-template-columns: var(--markets-columns);
	gap: 0.5rem;
	width: 100%;
	border-bottom: 1px solid var(--markets-border);
	color: var(--markets-muted);
	font-size: 0.62rem;
	font-weight: 850;
	letter-spacing: 0.06em;
	text-transform: uppercase;
}

.monaco-markets-head > span {
	display: flex;
	align-items: center;
	justify-self: stretch;
	min-width: 0;
	padding: 0.55rem;
	white-space: nowrap;
}

.monaco-markets-head-right {
	justify-content: flex-end;
	text-align: right;
}

.monaco-markets-col-right {
	justify-content: flex-end;
	text-align: right;
}

.monaco-markets-vol-cell {
	justify-content: flex-end;
	color: var(--markets-muted);
	font-size: 0.68rem;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
}

.monaco-markets-lev {
	font-variant-numeric: tabular-nums;
	font-size: 0.76rem;
	font-weight: 600;
	color: var(--markets-text);
}

.monaco-markets-lev-muted {
	color: var(--markets-muted);
	font-weight: 500;
}

.monaco-markets-tag {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	padding: 0.16rem 0.42rem;
	border-radius: 999px;
	border: 1px solid var(--markets-border);
	background: var(--markets-muted-surface);
	color: var(--markets-muted);
	font-size: 0.56rem;
	font-weight: 850;
	letter-spacing: 0.05em;
	text-transform: uppercase;
	white-space: nowrap;
}

.monaco-markets-tag-crypto { color: #1f7a4d; background: rgba(31, 122, 77, 0.1); border-color: rgba(31, 122, 77, 0.22); }
.monaco-markets-tag-equities { color: #2456b8; background: rgba(36, 86, 184, 0.1); border-color: rgba(36, 86, 184, 0.22); }
.monaco-markets-tag-commodities { color: #8a5a10; background: rgba(154, 106, 20, 0.12); border-color: rgba(154, 106, 20, 0.24); }
.monaco-markets-tag-fx { color: #6b3fb0; background: rgba(107, 63, 176, 0.1); border-color: rgba(107, 63, 176, 0.22); }

:where(.dark, [data-theme='dark']) .monaco-markets-tag-crypto { color: #74d99f; background: rgba(116, 217, 159, 0.13); border-color: rgba(116, 217, 159, 0.3); }
:where(.dark, [data-theme='dark']) .monaco-markets-tag-equities { color: #8fb6ff; background: rgba(143, 182, 255, 0.13); border-color: rgba(143, 182, 255, 0.3); }
:where(.dark, [data-theme='dark']) .monaco-markets-tag-commodities { color: #e6c178; background: rgba(230, 193, 120, 0.13); border-color: rgba(230, 193, 120, 0.3); }
:where(.dark, [data-theme='dark']) .monaco-markets-tag-fx { color: #c6a8ff; background: rgba(198, 168, 255, 0.13); border-color: rgba(198, 168, 255, 0.3); }

.monaco-markets-pagination {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: 0.6rem;
	margin-top: 0.6rem;
	min-height: 2rem;
}

.monaco-markets-pagination-info {
	color: var(--markets-muted);
	font-size: 0.72rem;
	font-variant-numeric: tabular-nums;
}

.monaco-markets-pagination-controls {
	display: inline-flex;
	align-items: center;
	gap: 0.4rem;
}

.monaco-markets-page-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	min-height: 2rem;
	padding: 0.32rem 0.78rem;
	border: 1px solid var(--markets-border);
	border-radius: 999px;
	background: var(--markets-muted-surface);
	color: var(--markets-text);
	font-size: 0.74rem;
	font-weight: 750;
	cursor: pointer;
	outline: none;
	transition:
		background-color 150ms ease,
		border-color 150ms ease,
		box-shadow 150ms ease,
		color 150ms ease,
		opacity 150ms ease;
}

.monaco-markets-page-btn:hover:not(:disabled) {
	border-color: #18231d;
	background: rgba(24, 35, 29, 0.08);
	color: #18231d;
}

:where(.dark, [data-theme='dark']) .monaco-markets-page-btn:hover:not(:disabled) {
	border-color: #f4faf5;
	background: rgba(244, 250, 245, 0.12);
	color: #f4faf5;
}

.monaco-markets-page-btn:focus-visible {
	border-color: #18231d;
	box-shadow: 0 0 0 3px rgba(24, 35, 29, 0.18);
}

:where(.dark, [data-theme='dark']) .monaco-markets-page-btn:focus-visible {
	border-color: #f4faf5;
	box-shadow: 0 0 0 3px rgba(244, 250, 245, 0.22);
}

.monaco-markets-page-btn:disabled {
	opacity: 0.4;
	cursor: default;
}

.monaco-markets-page-status {
	padding: 0 0.35rem;
	color: var(--markets-muted);
	font-size: 0.72rem;
	font-weight: 750;
	font-variant-numeric: tabular-nums;
}
`;

export function formatMarketPrice(value) {
  const number = Number(value);
  if (!Number.isFinite(number)) return "n/a";
  if (number >= 1000) return `$${number.toLocaleString(undefined, {
    maximumFractionDigits: 0
  })}`;
  if (number >= 1) return `$${number.toLocaleString(undefined, {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2
  })}`;
  return `$${number.toLocaleString(undefined, {
    maximumSignificantDigits: 4
  })}`;
}

export function formatMarketPercent(value) {
  const number = Number(value);
  if (!Number.isFinite(number)) return "n/a";
  const sign = number > 0 ? "+" : "";
  return `${sign}${number.toFixed(2)}%`;
}

export function formatMarketVolume(value) {
  const number = Number(value);
  if (!Number.isFinite(number)) return "n/a";
  if (number >= 1_000_000_000) return `$${(number / 1_000_000_000).toFixed(2)}B`;
  if (number >= 1_000_000) return `$${(number / 1_000_000).toFixed(2)}M`;
  if (number >= 1_000) return `$${(number / 1_000).toFixed(1)}K`;
  return `$${number.toLocaleString(undefined, {
    maximumFractionDigits: 0
  })}`;
}

export function formatMarketLeverage(value) {
  const number = Number(value);
  if (!Number.isFinite(number)) return "n/a";
  return `${Number.isInteger(number) ? number.toFixed(0) : number.toFixed(1)}x`;
}

export function formatMarketCategory(value) {
  const category = String(value || "");
  return category.toLowerCase() === "fx" ? "FX" : category.charAt(0).toUpperCase() + category.slice(1);
}

export function formatMarketDisplaySymbol(symbol) {
  return String(symbol || "").replace(new RegExp("[/-]USDC$", "i"), "");
}

export function marketSymbolToSlug(symbol) {
  return String(symbol || "").trim().toLowerCase().replace(new RegExp("[^a-z0-9]+", "g"), "-").replace(new RegExp("^-+|-+$", "g"), "");
}

export function marketDetailHref(symbol) {
  return `/markets/market?symbol=${encodeURIComponent(marketSymbolToSlug(symbol))}`;
}

export function normalizeMarketRows(pairs, screeners) {
  const screenerById = new Map((screeners || []).map(item => [item.tradingPairId, item]));
  const groups = new Map();
  for (const pair of pairs || []) {
    const key = pair.symbol || pair.id;
    const existing = groups.get(key) || ({
      symbol: pair.symbol,
      category: pair.category || "market",
      baseIconUrl: pair.baseIconUrl || "",
      spot: null,
      perp: null,
      screeners: []
    });
    if (pair.marketType === "SPOT") existing.spot = pair;
    if (pair.marketType === "MARGIN") existing.perp = pair;
    const screener = screenerById.get(pair.id);
    if (screener) existing.screeners.push(screener);
    groups.set(key, existing);
  }
  return [...groups.values()].map(row => {
    const bestScreener = row.screeners.slice().sort((a, b) => Number(b.quoteVolume24h || -1) - Number(a.quoteVolume24h || -1))[0] || null;
    return {
      ...row,
      id: row.spot?.id || row.perp?.id || row.symbol,
      price: bestScreener?.lastPrice,
      change24h: bestScreener?.priceChangePercent24h,
      volume24h: bestScreener?.quoteVolume24h,
      maxLeverage: row.perp?.maxLeverage,
      minOrderSize: row.spot?.minOrderSize || row.perp?.minOrderSize,
      tickSize: row.spot?.tickSize || row.perp?.tickSize
    };
  }).sort((a, b) => Number(b.volume24h || -1) - Number(a.volume24h || -1));
}

export const MARKETS_PAGE_SIZE = 10;
export const MONACO_MARKET_CATEGORY_ORDER = ["crypto", "equities", "commodities", "fx"];

export function compareMarketRowsByCategory(a, b) {
  const aCategoryIndex = MONACO_MARKET_CATEGORY_ORDER.indexOf(String(a.category || "").toLowerCase());
  const bCategoryIndex = MONACO_MARKET_CATEGORY_ORDER.indexOf(String(b.category || "").toLowerCase());
  const aRank = aCategoryIndex === -1 ? MONACO_MARKET_CATEGORY_ORDER.length : aCategoryIndex;
  const bRank = bCategoryIndex === -1 ? MONACO_MARKET_CATEGORY_ORDER.length : bCategoryIndex;
  if (aRank !== bRank) return aRank - bRank;
  const volumeDelta = Number(b.volume24h || 0) - Number(a.volume24h || 0);
  if (volumeDelta !== 0) return volumeDelta;
  return String(a.symbol || "").localeCompare(String(b.symbol || ""));
}

export const MonacoMarketsTable = () => {
  const [state, setState] = useState({
    status: "loading",
    rows: [],
    updatedAt: "",
    error: ""
  });
  const [productFilter, setProductFilter] = useState("all");
  const [categoryFilter, setCategoryFilter] = useState("all");
  const [searchQuery, setSearchQuery] = useState("");
  const [page, setPage] = useState(1);
  useEffect(() => {
    const tableWrap = document.querySelector(".monaco-markets-table-wrap");
    if (tableWrap) tableWrap.scrollLeft = 0;
  }, []);
  useEffect(() => {
    let active = true;
    const applyMarketPayload = (pairsPayload, screenerPayload, timestamp) => {
      const rows = normalizeMarketRows(pairsPayload.tradingPairs || [], screenerPayload.items || []);
      if (active) {
        setState({
          status: "ready",
          rows,
          updatedAt: formatMonacoMarketCacheTime(timestamp),
          error: ""
        });
      }
    };
    const loadMarkets = async () => {
      const cachedPairs = readMonacoMarketCache(MONACO_MARKETS_PAIRS_CACHE_KEY);
      const cachedScreener = readMonacoMarketCache(MONACO_MARKETS_SCREENER_CACHE_KEY);
      if (cachedPairs && cachedScreener) {
        applyMarketPayload(cachedPairs.data, cachedScreener.data, Math.min(cachedPairs.timestamp, cachedScreener.timestamp));
        if (cachedPairs.isFresh && cachedScreener.isFresh) return;
      }
      try {
        const [pairsEntry, screenerEntry] = await Promise.all([fetchMonacoMarketData(MONACO_MARKETS_PAIRS_CACHE_KEY, MONACO_MARKETS_PAIRS_URL), fetchMonacoMarketData(MONACO_MARKETS_SCREENER_CACHE_KEY, MONACO_MARKETS_SCREENER_URL)]);
        applyMarketPayload(pairsEntry.data, screenerEntry.data, Math.min(pairsEntry.timestamp, screenerEntry.timestamp));
      } catch (error) {
        if (active) {
          setState(current => ({
            ...current,
            status: current.rows.length ? "ready" : "error",
            error: error?.message || "Unable to load Monaco market data."
          }));
        }
      }
    };
    loadMarkets();
    const timer = window.setInterval(loadMarkets, 60000);
    return () => {
      active = false;
      window.clearInterval(timer);
    };
  }, []);
  const filteredRows = state.rows.filter(row => {
    const normalizedSearch = searchQuery.trim().toLowerCase();
    const matchesProduct = productFilter === "all" || productFilter === "spot" && row.spot || productFilter === "perps" && row.perp;
    const matchesCategory = categoryFilter === "all" || String(row.category || "").toLowerCase() === categoryFilter;
    const matchesSearch = !normalizedSearch || row.symbol.toLowerCase().includes(normalizedSearch) || row.category.toLowerCase().includes(normalizedSearch) || String(row.spot?.baseToken || row.perp?.baseToken || "").toLowerCase().includes(normalizedSearch) || String(row.spot?.quoteToken || row.perp?.quoteToken || "").toLowerCase().includes(normalizedSearch);
    return matchesProduct && matchesCategory && matchesSearch;
  }).sort(compareMarketRowsByCategory);
  const productFilters = ["all", "spot", "perps"];
  const categoryFilters = ["all", "crypto", "equities", "commodities", "fx"];
  useEffect(() => {
    setPage(1);
  }, [productFilter, categoryFilter, searchQuery]);
  const totalPages = Math.max(1, Math.ceil(filteredRows.length / MARKETS_PAGE_SIZE));
  const currentPage = Math.min(page, totalPages);
  const pageStart = (currentPage - 1) * MARKETS_PAGE_SIZE;
  const pagedRows = filteredRows.slice(pageStart, pageStart + MARKETS_PAGE_SIZE);
  const padCount = state.status === "loading" ? 0 : Math.max(0, MARKETS_PAGE_SIZE - pagedRows.length);
  const isEmpty = state.status !== "loading" && filteredRows.length === 0;
  const emptyMessage = state.status === "error" ? state.error || "Unable to load markets." : "No matches yet. Want this market on Monaco? See how to propose one below.";
  return <div className="not-prose monaco-markets">
			<style>{monacoMarketsStyles}</style>
			<div className="monaco-markets-controls">
				<div className="monaco-markets-search">
					<svg aria-hidden="true" className="monaco-markets-search-icon" height="14" viewBox="0 0 24 24" width="14">
						<circle cx="11" cy="11" fill="none" r="7" stroke="currentColor" strokeWidth="2.4" />
						<path d="m16.5 16.5 4 4" fill="none" stroke="currentColor" strokeLinecap="round" strokeWidth="2.4" />
					</svg>
					<input aria-label="Search markets" className="monaco-markets-search-input" onChange={event => setSearchQuery(event.target.value)} placeholder="Search BTC, HYPE, SPX..." type="search" value={searchQuery} />
					<button aria-label="Clear search" className="monaco-markets-search-clear" hidden={!searchQuery} onClick={() => setSearchQuery("")} type="button">
						<svg aria-hidden="true" height="14" viewBox="0 0 24 24" width="14">
							<path d="M18 6 6 18M6 6l12 12" fill="none" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2.4" />
						</svg>
					</button>
				</div>
				<div className="monaco-markets-filters" aria-label="Filter markets">
					<div className="monaco-markets-segment" role="group" aria-label="Filter by product">
						{productFilters.map(product => <button aria-pressed={productFilter === product} className={`monaco-markets-segment-btn${productFilter === product ? " monaco-markets-segment-btn-active" : ""}`} key={product} onClick={() => setProductFilter(product)} type="button">
								{product === "all" ? "All" : product === "perps" ? "Perps" : "Spot"}
							</button>)}
					</div>
					<div className="monaco-markets-segment" role="group" aria-label="Filter by asset category">
						{categoryFilters.map(category => <button aria-pressed={categoryFilter === category} className={`monaco-markets-segment-btn${categoryFilter === category ? " monaco-markets-segment-btn-active" : ""}`} key={category} onClick={() => setCategoryFilter(category)} type="button">
								{category === "all" ? "All" : formatMarketCategory(category)}
							</button>)}
					</div>
				</div>
			</div>
			<div className="monaco-markets-note">All supported markets are quoted in USDC.</div>

			<div className="monaco-markets-table-wrap">
				<div className="monaco-markets-grid" role="table" aria-label="Monaco markets">
					<div className="monaco-markets-head" role="row">
						<span role="columnheader">Market</span>
						<span role="columnheader">Category</span>
						<span role="columnheader">Products</span>
						<span role="columnheader">Max Leverage</span>
					</div>
					{state.status === "loading" ? <>
							{Array.from({
    length: MARKETS_PAGE_SIZE
  }).map((_, index) => <div className="monaco-markets-row monaco-markets-skeleton-row" key={index} role="row" aria-hidden="true">
									<div className="monaco-markets-cell" role="cell">
										<div className="monaco-markets-market">
											<span className="monaco-markets-skeleton monaco-markets-skeleton-icon" />
											<span className="monaco-markets-skeleton monaco-markets-skeleton-medium" />
										</div>
									</div>
									<div className="monaco-markets-cell" role="cell"><span className="monaco-markets-skeleton monaco-markets-skeleton-short" /></div>
									<div className="monaco-markets-cell" role="cell"><span className="monaco-markets-skeleton monaco-markets-skeleton-short" /></div>
									<div className="monaco-markets-cell" role="cell"><span className="monaco-markets-skeleton monaco-markets-skeleton-short" /></div>
								</div>)}
						</> : null}
					{isEmpty ? <div className="monaco-markets-empty">{emptyMessage}</div> : null}
					{pagedRows.map(row => {
    const baseToken = row.symbol.split("/")[0] || row.symbol;
    const displaySymbol = formatMarketDisplaySymbol(row.symbol);
    const marketHref = marketDetailHref(row.symbol);
    const category = String(row.category || "market").toLowerCase();
    const categoryClass = ["crypto", "equities", "commodities", "fx"].includes(category) ? `monaco-markets-tag-${category}` : "";
    return <a className="monaco-markets-row monaco-markets-row-link" href={marketHref} key={row.id} role="row">
								<div className="monaco-markets-cell" role="cell">
									<div className="monaco-markets-market">
										{row.baseIconUrl ? <img alt="" className="monaco-markets-icon" src={row.baseIconUrl} /> : <span className="monaco-markets-icon monaco-markets-icon-fallback">{baseToken.slice(0, 2)}</span>}
										<div className="monaco-markets-market-copy">
											<div className="monaco-markets-symbol">{displaySymbol}</div>
										</div>
									</div>
								</div>
								<div className="monaco-markets-cell" role="cell">
									<span className={`monaco-markets-tag ${categoryClass}`}>{formatMarketCategory(row.category)}</span>
								</div>
								<div className="monaco-markets-cell monaco-markets-products-cell" role="cell">
									<div className="monaco-markets-products">
										<span className={`monaco-markets-chip${row.spot ? " monaco-markets-chip-active" : " monaco-markets-chip-unavailable"}`}>Spot</span>
										<span className={`monaco-markets-chip${row.perp ? " monaco-markets-chip-active" : " monaco-markets-chip-unavailable"}`}>Perps</span>
									</div>
								</div>
								<div className="monaco-markets-cell" role="cell">
									{row.perp ? <span className="monaco-markets-lev">{formatMarketLeverage(row.maxLeverage)}</span> : <span className="monaco-markets-lev monaco-markets-lev-muted">—</span>}
								</div>
							</a>;
  })}
					{Array.from({
    length: padCount
  }).map((_, index) => <div className="monaco-markets-row monaco-markets-pad-row" key={`pad-${index}`} aria-hidden="true" />)}
				</div>
			</div>
			<div className="monaco-markets-pagination">
				<span className="monaco-markets-pagination-info">
					{state.status === "loading" ? "Loading markets…" : isEmpty ? "No results" : `${pageStart + 1}–${pageStart + pagedRows.length} of ${filteredRows.length} markets`}
				</span>
				<div className="monaco-markets-pagination-controls">
					<button aria-label="Previous page" className="monaco-markets-page-btn" disabled={state.status === "loading" || currentPage <= 1} onClick={() => setPage(Math.max(1, currentPage - 1))} type="button">
						Prev
					</button>
					<span className="monaco-markets-page-status">
						{state.status === "loading" ? "Page — of —" : `Page ${currentPage} of ${totalPages}`}
					</span>
					<button aria-label="Next page" className="monaco-markets-page-btn" disabled={state.status === "loading" || currentPage >= totalPages} onClick={() => setPage(Math.min(totalPages, currentPage + 1))} type="button">
						Next
					</button>
				</div>
			</div>
			<div className="monaco-markets-scroll-hint">Scroll sideways to see all fields.</div>
			<div className="monaco-markets-meta">
				{state.status === "loading" ? "Loading live Monaco testnet markets..." : state.status === "error" ? state.error : `Live testnet data. Refreshed ${state.updatedAt}.`}
			</div>
		</div>;
};

## Available Markets

<MonacoMarketsTable />

## Propose New Markets and Earn

Monaco is designed to let proposers bring new markets to shared liquidity without paying a listing fee. A proposer supplies the required minimum liquidity for a market and can receive a 50% revenue share from activity generated in that market while it remains listed and active.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.