@metamask-previews/perps-controller 7.0.0-preview-63e8c0e → 7.0.0-preview-ecd6b75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,106 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.filterMarketsByQuery = exports.calculate24hHighLow = exports.calculateFundingCountdown = exports.getPerpsDexFromSymbol = exports.getPerpsDisplaySymbol = exports.shouldIncludeMarket = exports.matchesMarketPattern = exports.compileMarketPattern = exports.validateMarketPattern = exports.escapeRegex = exports.MAX_MARKET_PATTERN_LENGTH = void 0;
3
+ exports.filterMarketsByQuery = exports.calculate24hHighLow = exports.calculateFundingCountdown = exports.getPerpsDexFromSymbol = exports.getPerpsDisplaySymbol = exports.shouldIncludeMarket = exports.matchesMarketPattern = exports.compileMarketPattern = exports.validateMarketPattern = exports.escapeRegex = exports.MAX_MARKET_PATTERN_LENGTH = exports.applyMarketFilters = exports.getMarketTypeFilter = exports.matchesCategory = exports.isHip3Market = void 0;
4
+ const sortMarkets_1 = require("./sortMarkets.cjs");
5
+ // ============================================================================
6
+ // Market category classification (pure functions)
7
+ // No service dependencies — pure data transformations that can be tested and
8
+ // reused independently. `matchesCategory` and `getMarketTypeFilter` share the
9
+ // same category model.
10
+ // ============================================================================
11
+ /**
12
+ * Whether a market is a HIP-3 (non-main-DEX) market. A `marketSource` DEX id
13
+ * marks a HIP-3 market even when the `isHip3` flag is unset (e.g. partial route
14
+ * params), so both signals are checked. Used as the single HIP-3 signal so the
15
+ * classifiers stay consistent.
16
+ *
17
+ * @param market - The market data to test.
18
+ * @returns True if the market is HIP-3.
19
+ */
20
+ const isHip3Market = (market) => Boolean(market.isHip3) || Boolean(market.marketSource);
21
+ exports.isHip3Market = isHip3Market;
22
+ /**
23
+ * Returns true when a market matches the given UI filter category.
24
+ *
25
+ * @param market - The market data to test.
26
+ * @param category - The filter category to test against.
27
+ * @returns Whether the market matches the category.
28
+ */
29
+ function matchesCategory(market, category) {
30
+ switch (category) {
31
+ case 'all':
32
+ return true;
33
+ case 'new':
34
+ // Explicitly flagged, or an uncategorized HIP-3 market (kept in sync with
35
+ // getMarketTypeFilter's 'new' bucket).
36
+ return (market.isNewMarket === true ||
37
+ ((0, exports.isHip3Market)(market) && market.marketType === undefined));
38
+ case 'crypto':
39
+ // Main-DEX markets, plus HIP-3 assets explicitly typed as CryptoCurrency.
40
+ return !(0, exports.isHip3Market)(market) || market.marketType === 'crypto';
41
+ default:
42
+ // Every other filter is a 1:1 data-model category match.
43
+ return market.marketType !== undefined && market.marketType === category;
44
+ }
45
+ }
46
+ exports.matchesCategory = matchesCategory;
47
+ /**
48
+ * Resolve the user-facing category bucket for a market — one of `crypto`,
49
+ * `stock`, `pre-ipo`, `index`, `etf`, `commodity`, `forex`, or `new`. Data-model
50
+ * categories map 1:1. A market with no data-model category is `crypto` when it
51
+ * is main-DEX, or `new` when it is an uncategorized HIP-3 market (`isHip3`, or a
52
+ * `marketSource` DEX id when `isHip3` is unset, e.g. minimal route params).
53
+ * Never returns the `all` sentinel.
54
+ *
55
+ * Centralised as the single source of truth so consumers (e.g. category
56
+ * shortcuts, related markets) share one classification instead of re-deriving
57
+ * it per client and drifting as new categories are added.
58
+ *
59
+ * @param market - The market data to classify.
60
+ * @returns The market type filter bucket.
61
+ */
62
+ function getMarketTypeFilter(market) {
63
+ const { marketType } = market;
64
+ if (marketType) {
65
+ return marketType;
66
+ }
67
+ // No data-model category: an uncategorized HIP-3 market is the 'new' bucket;
68
+ // otherwise it's a main-DEX crypto market.
69
+ return (0, exports.isHip3Market)(market) ? 'new' : 'crypto';
70
+ }
71
+ exports.getMarketTypeFilter = getMarketTypeFilter;
72
+ /**
73
+ * Applies optional category filtering, sorting, and limit to a list of markets.
74
+ *
75
+ * @param markets - Source market array.
76
+ * @param params - Optional filter/sort/limit params.
77
+ * @returns Filtered, sorted, and/or sliced market array.
78
+ */
79
+ function applyMarketFilters(markets, params) {
80
+ let result = markets;
81
+ if (params?.categories?.length) {
82
+ const { categories } = params;
83
+ result = result.filter((market) =>
84
+ // A market is included if it matches ANY of the requested categories.
85
+ categories.some((category) => matchesCategory(market, category)));
86
+ }
87
+ if (params?.excludeSymbols?.length) {
88
+ const excluded = new Set(params.excludeSymbols);
89
+ result = result.filter((market) => !excluded.has(market.symbol));
90
+ }
91
+ if (params?.sortBy) {
92
+ result = (0, sortMarkets_1.sortMarkets)({
93
+ markets: result,
94
+ sortBy: params.sortBy,
95
+ direction: params.direction,
96
+ });
97
+ }
98
+ if (params?.limit !== undefined) {
99
+ result = result.slice(0, params.limit);
100
+ }
101
+ return result;
102
+ }
103
+ exports.applyMarketFilters = applyMarketFilters;
4
104
  /**
5
105
  * Maximum length for market filter patterns (prevents DoS attacks)
6
106
  */
@@ -1 +1 @@
1
- {"version":3,"file":"marketUtils.cjs","sourceRoot":"","sources":["../../src/utils/marketUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PerpsMarketData } from '../types';\nimport type { CandleData, CandleStick } from '../types/perps-types';\n\n/**\n * Maximum length for market filter patterns (prevents DoS attacks)\n */\nexport const MAX_MARKET_PATTERN_LENGTH = 100;\n\nexport type MarketPatternMatcher = RegExp | string;\n\nexport type CompiledMarketPattern = {\n pattern: string;\n matcher: MarketPatternMatcher;\n};\n\nexport const escapeRegex = (str: string): string =>\n str.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n\nexport const validateMarketPattern = (pattern: string): boolean => {\n if (!pattern || pattern.trim().length === 0) {\n throw new Error('Market pattern cannot be empty');\n }\n\n const normalizedPattern = pattern.trim();\n\n if (normalizedPattern.length > MAX_MARKET_PATTERN_LENGTH) {\n throw new Error(\n `Market pattern exceeds maximum length (${MAX_MARKET_PATTERN_LENGTH} chars): ${normalizedPattern}`,\n );\n }\n\n const dangerousChars = /[\\\\()[\\]{}^$+?.|]/u;\n if (dangerousChars.test(normalizedPattern)) {\n throw new Error(\n `Market pattern contains invalid regex characters: ${normalizedPattern}`,\n );\n }\n\n const validPattern = /^[a-zA-Z0-9:_\\-*]+$/u;\n if (!validPattern.test(normalizedPattern)) {\n throw new Error(\n `Market pattern contains invalid characters: ${normalizedPattern}`,\n );\n }\n\n return true;\n};\n\nexport const compileMarketPattern = (pattern: string): MarketPatternMatcher => {\n const normalizedPattern = pattern.trim();\n validateMarketPattern(normalizedPattern);\n\n if (normalizedPattern.endsWith(':*')) {\n const prefix = normalizedPattern.slice(0, -2);\n return new RegExp(`^${escapeRegex(prefix)}:`, 'u');\n }\n\n if (!normalizedPattern.includes(':')) {\n return new RegExp(`^${escapeRegex(normalizedPattern)}:`, 'u');\n }\n\n return normalizedPattern;\n};\n\nexport const matchesMarketPattern = (\n symbol: string,\n matcher: MarketPatternMatcher,\n): boolean => {\n if (typeof matcher === 'string') {\n return symbol === matcher;\n }\n\n return matcher.test(symbol);\n};\n\nexport const shouldIncludeMarket = (\n symbol: string,\n dex: string | null,\n hip3Enabled: boolean,\n compiledEnabledPatterns: CompiledMarketPattern[],\n compiledBlockedPatterns: CompiledMarketPattern[],\n): boolean => {\n if (dex === null) {\n return true;\n }\n\n if (!hip3Enabled) {\n return false;\n }\n\n if (compiledEnabledPatterns.length > 0) {\n const whitelisted = compiledEnabledPatterns.some(({ matcher }) =>\n matchesMarketPattern(symbol, matcher),\n );\n if (!whitelisted) {\n return false;\n }\n }\n\n if (compiledBlockedPatterns.length === 0) {\n return true;\n }\n\n const blacklisted = compiledBlockedPatterns.some(({ matcher }) =>\n matchesMarketPattern(symbol, matcher),\n );\n\n return !blacklisted;\n};\n\nexport const getPerpsDisplaySymbol = (symbol: string): string => {\n if (!symbol || typeof symbol !== 'string') {\n return symbol;\n }\n\n const colonIndex = symbol.indexOf(':');\n if (colonIndex > 0 && colonIndex < symbol.length - 1) {\n return symbol.substring(colonIndex + 1);\n }\n\n return symbol;\n};\n\nexport const getPerpsDexFromSymbol = (symbol: string): string | null => {\n if (!symbol || typeof symbol !== 'string') {\n return null;\n }\n\n const colonIndex = symbol.indexOf(':');\n if (colonIndex > 0 && colonIndex < symbol.length - 1) {\n return symbol.substring(0, colonIndex);\n }\n\n return null;\n};\n\ntype FundingCountdownParams = {\n nextFundingTime?: number;\n fundingIntervalHours?: number;\n};\n\nexport const calculateFundingCountdown = (\n params?: FundingCountdownParams,\n): string => {\n const now = new Date();\n const nowMs = now.getTime();\n\n if (params?.nextFundingTime && params.nextFundingTime > nowMs) {\n const msUntilFunding = params.nextFundingTime - nowMs;\n const hoursUntilFunding = msUntilFunding / (1000 * 60 * 60);\n\n if (hoursUntilFunding <= 1.1) {\n const totalSeconds = Math.floor(msUntilFunding / 1000);\n\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n\n const formattedHours = String(hours).padStart(2, '0');\n const formattedMinutes = String(minutes).padStart(2, '0');\n const formattedSeconds = String(seconds).padStart(2, '0');\n\n return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;\n }\n }\n\n const utcMinutes = now.getUTCMinutes();\n const utcSeconds = now.getUTCSeconds();\n\n const minutesUntilNextHour = 59 - utcMinutes;\n const secondsUntilNextHour = 60 - utcSeconds;\n\n const finalSeconds = secondsUntilNextHour === 60 ? 0 : secondsUntilNextHour;\n const finalMinutes =\n secondsUntilNextHour === 60\n ? minutesUntilNextHour + 1\n : minutesUntilNextHour;\n\n const finalHours = finalMinutes === 60 ? 1 : 0;\n const adjustedMinutes = finalMinutes === 60 ? 0 : finalMinutes;\n\n const formattedHours = String(finalHours).padStart(2, '0');\n const formattedMinutes = String(adjustedMinutes).padStart(2, '0');\n const formattedSeconds = String(finalSeconds).padStart(2, '0');\n\n return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;\n};\n\nexport const calculate24hHighLow = (\n candleData: CandleData | null,\n): { high: number; low: number } => {\n if (!candleData?.candles || candleData.candles.length === 0) {\n return { high: 0, low: 0 };\n }\n\n const now = Date.now();\n const twentyFourHoursAgo = now - 24 * 60 * 60 * 1000;\n\n let last24hCandles = candleData.candles.filter(\n (candle: CandleStick) => candle.time >= twentyFourHoursAgo,\n );\n\n if (last24hCandles.length === 0) {\n last24hCandles = [...candleData.candles];\n }\n\n const highs = last24hCandles.map((candle: CandleStick) =>\n parseFloat(candle.high),\n );\n const lows = last24hCandles.map((candle: CandleStick) =>\n parseFloat(candle.low),\n );\n\n return {\n high: Math.max(...highs),\n low: Math.min(...lows),\n };\n};\n\nexport const filterMarketsByQuery = (\n markets: PerpsMarketData[],\n searchQuery: string,\n): PerpsMarketData[] => {\n if (!searchQuery?.trim()) {\n return markets;\n }\n\n const lowerQuery = searchQuery.toLowerCase().trim();\n\n return markets.filter(\n (market) =>\n market.symbol?.toLowerCase().includes(lowerQuery) ||\n market.name?.toLowerCase().includes(lowerQuery),\n );\n};\n"]}
1
+ {"version":3,"file":"marketUtils.cjs","sourceRoot":"","sources":["../../src/utils/marketUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n GetMarketDataWithPricesParams,\n MarketTypeFilter,\n PerpsMarketData,\n} from '../types';\nimport type { CandleData, CandleStick } from '../types/perps-types';\nimport { sortMarkets } from './sortMarkets';\n\n// ============================================================================\n// Market category classification (pure functions)\n// No service dependencies — pure data transformations that can be tested and\n// reused independently. `matchesCategory` and `getMarketTypeFilter` share the\n// same category model.\n// ============================================================================\n\n/**\n * Whether a market is a HIP-3 (non-main-DEX) market. A `marketSource` DEX id\n * marks a HIP-3 market even when the `isHip3` flag is unset (e.g. partial route\n * params), so both signals are checked. Used as the single HIP-3 signal so the\n * classifiers stay consistent.\n *\n * @param market - The market data to test.\n * @returns True if the market is HIP-3.\n */\nexport const isHip3Market = (\n market: Pick<PerpsMarketData, 'isHip3' | 'marketSource'>,\n): boolean => Boolean(market.isHip3) || Boolean(market.marketSource);\n\n/**\n * Returns true when a market matches the given UI filter category.\n *\n * @param market - The market data to test.\n * @param category - The filter category to test against.\n * @returns Whether the market matches the category.\n */\nexport function matchesCategory(\n market: PerpsMarketData,\n category: MarketTypeFilter,\n): boolean {\n switch (category) {\n case 'all':\n return true;\n case 'new':\n // Explicitly flagged, or an uncategorized HIP-3 market (kept in sync with\n // getMarketTypeFilter's 'new' bucket).\n return (\n market.isNewMarket === true ||\n (isHip3Market(market) && market.marketType === undefined)\n );\n case 'crypto':\n // Main-DEX markets, plus HIP-3 assets explicitly typed as CryptoCurrency.\n return !isHip3Market(market) || market.marketType === 'crypto';\n default:\n // Every other filter is a 1:1 data-model category match.\n return market.marketType !== undefined && market.marketType === category;\n }\n}\n\n/**\n * Resolve the user-facing category bucket for a market — one of `crypto`,\n * `stock`, `pre-ipo`, `index`, `etf`, `commodity`, `forex`, or `new`. Data-model\n * categories map 1:1. A market with no data-model category is `crypto` when it\n * is main-DEX, or `new` when it is an uncategorized HIP-3 market (`isHip3`, or a\n * `marketSource` DEX id when `isHip3` is unset, e.g. minimal route params).\n * Never returns the `all` sentinel.\n *\n * Centralised as the single source of truth so consumers (e.g. category\n * shortcuts, related markets) share one classification instead of re-deriving\n * it per client and drifting as new categories are added.\n *\n * @param market - The market data to classify.\n * @returns The market type filter bucket.\n */\nexport function getMarketTypeFilter(market: PerpsMarketData): MarketTypeFilter {\n const { marketType } = market;\n if (marketType) {\n return marketType;\n }\n // No data-model category: an uncategorized HIP-3 market is the 'new' bucket;\n // otherwise it's a main-DEX crypto market.\n return isHip3Market(market) ? 'new' : 'crypto';\n}\n\n/**\n * Applies optional category filtering, sorting, and limit to a list of markets.\n *\n * @param markets - Source market array.\n * @param params - Optional filter/sort/limit params.\n * @returns Filtered, sorted, and/or sliced market array.\n */\nexport function applyMarketFilters(\n markets: PerpsMarketData[],\n params?: GetMarketDataWithPricesParams,\n): PerpsMarketData[] {\n let result = markets;\n\n if (params?.categories?.length) {\n const { categories } = params;\n result = result.filter((market) =>\n // A market is included if it matches ANY of the requested categories.\n categories.some((category) => matchesCategory(market, category)),\n );\n }\n\n if (params?.excludeSymbols?.length) {\n const excluded = new Set(params.excludeSymbols);\n result = result.filter((market) => !excluded.has(market.symbol));\n }\n\n if (params?.sortBy) {\n result = sortMarkets({\n markets: result,\n sortBy: params.sortBy,\n direction: params.direction,\n });\n }\n\n if (params?.limit !== undefined) {\n result = result.slice(0, params.limit);\n }\n\n return result;\n}\n\n/**\n * Maximum length for market filter patterns (prevents DoS attacks)\n */\nexport const MAX_MARKET_PATTERN_LENGTH = 100;\n\nexport type MarketPatternMatcher = RegExp | string;\n\nexport type CompiledMarketPattern = {\n pattern: string;\n matcher: MarketPatternMatcher;\n};\n\nexport const escapeRegex = (str: string): string =>\n str.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n\nexport const validateMarketPattern = (pattern: string): boolean => {\n if (!pattern || pattern.trim().length === 0) {\n throw new Error('Market pattern cannot be empty');\n }\n\n const normalizedPattern = pattern.trim();\n\n if (normalizedPattern.length > MAX_MARKET_PATTERN_LENGTH) {\n throw new Error(\n `Market pattern exceeds maximum length (${MAX_MARKET_PATTERN_LENGTH} chars): ${normalizedPattern}`,\n );\n }\n\n const dangerousChars = /[\\\\()[\\]{}^$+?.|]/u;\n if (dangerousChars.test(normalizedPattern)) {\n throw new Error(\n `Market pattern contains invalid regex characters: ${normalizedPattern}`,\n );\n }\n\n const validPattern = /^[a-zA-Z0-9:_\\-*]+$/u;\n if (!validPattern.test(normalizedPattern)) {\n throw new Error(\n `Market pattern contains invalid characters: ${normalizedPattern}`,\n );\n }\n\n return true;\n};\n\nexport const compileMarketPattern = (pattern: string): MarketPatternMatcher => {\n const normalizedPattern = pattern.trim();\n validateMarketPattern(normalizedPattern);\n\n if (normalizedPattern.endsWith(':*')) {\n const prefix = normalizedPattern.slice(0, -2);\n return new RegExp(`^${escapeRegex(prefix)}:`, 'u');\n }\n\n if (!normalizedPattern.includes(':')) {\n return new RegExp(`^${escapeRegex(normalizedPattern)}:`, 'u');\n }\n\n return normalizedPattern;\n};\n\nexport const matchesMarketPattern = (\n symbol: string,\n matcher: MarketPatternMatcher,\n): boolean => {\n if (typeof matcher === 'string') {\n return symbol === matcher;\n }\n\n return matcher.test(symbol);\n};\n\nexport const shouldIncludeMarket = (\n symbol: string,\n dex: string | null,\n hip3Enabled: boolean,\n compiledEnabledPatterns: CompiledMarketPattern[],\n compiledBlockedPatterns: CompiledMarketPattern[],\n): boolean => {\n if (dex === null) {\n return true;\n }\n\n if (!hip3Enabled) {\n return false;\n }\n\n if (compiledEnabledPatterns.length > 0) {\n const whitelisted = compiledEnabledPatterns.some(({ matcher }) =>\n matchesMarketPattern(symbol, matcher),\n );\n if (!whitelisted) {\n return false;\n }\n }\n\n if (compiledBlockedPatterns.length === 0) {\n return true;\n }\n\n const blacklisted = compiledBlockedPatterns.some(({ matcher }) =>\n matchesMarketPattern(symbol, matcher),\n );\n\n return !blacklisted;\n};\n\nexport const getPerpsDisplaySymbol = (symbol: string): string => {\n if (!symbol || typeof symbol !== 'string') {\n return symbol;\n }\n\n const colonIndex = symbol.indexOf(':');\n if (colonIndex > 0 && colonIndex < symbol.length - 1) {\n return symbol.substring(colonIndex + 1);\n }\n\n return symbol;\n};\n\nexport const getPerpsDexFromSymbol = (symbol: string): string | null => {\n if (!symbol || typeof symbol !== 'string') {\n return null;\n }\n\n const colonIndex = symbol.indexOf(':');\n if (colonIndex > 0 && colonIndex < symbol.length - 1) {\n return symbol.substring(0, colonIndex);\n }\n\n return null;\n};\n\ntype FundingCountdownParams = {\n nextFundingTime?: number;\n fundingIntervalHours?: number;\n};\n\nexport const calculateFundingCountdown = (\n params?: FundingCountdownParams,\n): string => {\n const now = new Date();\n const nowMs = now.getTime();\n\n if (params?.nextFundingTime && params.nextFundingTime > nowMs) {\n const msUntilFunding = params.nextFundingTime - nowMs;\n const hoursUntilFunding = msUntilFunding / (1000 * 60 * 60);\n\n if (hoursUntilFunding <= 1.1) {\n const totalSeconds = Math.floor(msUntilFunding / 1000);\n\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n\n const formattedHours = String(hours).padStart(2, '0');\n const formattedMinutes = String(minutes).padStart(2, '0');\n const formattedSeconds = String(seconds).padStart(2, '0');\n\n return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;\n }\n }\n\n const utcMinutes = now.getUTCMinutes();\n const utcSeconds = now.getUTCSeconds();\n\n const minutesUntilNextHour = 59 - utcMinutes;\n const secondsUntilNextHour = 60 - utcSeconds;\n\n const finalSeconds = secondsUntilNextHour === 60 ? 0 : secondsUntilNextHour;\n const finalMinutes =\n secondsUntilNextHour === 60\n ? minutesUntilNextHour + 1\n : minutesUntilNextHour;\n\n const finalHours = finalMinutes === 60 ? 1 : 0;\n const adjustedMinutes = finalMinutes === 60 ? 0 : finalMinutes;\n\n const formattedHours = String(finalHours).padStart(2, '0');\n const formattedMinutes = String(adjustedMinutes).padStart(2, '0');\n const formattedSeconds = String(finalSeconds).padStart(2, '0');\n\n return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;\n};\n\nexport const calculate24hHighLow = (\n candleData: CandleData | null,\n): { high: number; low: number } => {\n if (!candleData?.candles || candleData.candles.length === 0) {\n return { high: 0, low: 0 };\n }\n\n const now = Date.now();\n const twentyFourHoursAgo = now - 24 * 60 * 60 * 1000;\n\n let last24hCandles = candleData.candles.filter(\n (candle: CandleStick) => candle.time >= twentyFourHoursAgo,\n );\n\n if (last24hCandles.length === 0) {\n last24hCandles = [...candleData.candles];\n }\n\n const highs = last24hCandles.map((candle: CandleStick) =>\n parseFloat(candle.high),\n );\n const lows = last24hCandles.map((candle: CandleStick) =>\n parseFloat(candle.low),\n );\n\n return {\n high: Math.max(...highs),\n low: Math.min(...lows),\n };\n};\n\nexport const filterMarketsByQuery = (\n markets: PerpsMarketData[],\n searchQuery: string,\n): PerpsMarketData[] => {\n if (!searchQuery?.trim()) {\n return markets;\n }\n\n const lowerQuery = searchQuery.toLowerCase().trim();\n\n return markets.filter(\n (market) =>\n market.symbol?.toLowerCase().includes(lowerQuery) ||\n market.name?.toLowerCase().includes(lowerQuery),\n );\n};\n"]}
@@ -1,5 +1,47 @@
1
- import type { PerpsMarketData } from "../types/index.cjs";
1
+ import type { GetMarketDataWithPricesParams, MarketTypeFilter, PerpsMarketData } from "../types/index.cjs";
2
2
  import type { CandleData } from "../types/perps-types.cjs";
3
+ /**
4
+ * Whether a market is a HIP-3 (non-main-DEX) market. A `marketSource` DEX id
5
+ * marks a HIP-3 market even when the `isHip3` flag is unset (e.g. partial route
6
+ * params), so both signals are checked. Used as the single HIP-3 signal so the
7
+ * classifiers stay consistent.
8
+ *
9
+ * @param market - The market data to test.
10
+ * @returns True if the market is HIP-3.
11
+ */
12
+ export declare const isHip3Market: (market: Pick<PerpsMarketData, 'isHip3' | 'marketSource'>) => boolean;
13
+ /**
14
+ * Returns true when a market matches the given UI filter category.
15
+ *
16
+ * @param market - The market data to test.
17
+ * @param category - The filter category to test against.
18
+ * @returns Whether the market matches the category.
19
+ */
20
+ export declare function matchesCategory(market: PerpsMarketData, category: MarketTypeFilter): boolean;
21
+ /**
22
+ * Resolve the user-facing category bucket for a market — one of `crypto`,
23
+ * `stock`, `pre-ipo`, `index`, `etf`, `commodity`, `forex`, or `new`. Data-model
24
+ * categories map 1:1. A market with no data-model category is `crypto` when it
25
+ * is main-DEX, or `new` when it is an uncategorized HIP-3 market (`isHip3`, or a
26
+ * `marketSource` DEX id when `isHip3` is unset, e.g. minimal route params).
27
+ * Never returns the `all` sentinel.
28
+ *
29
+ * Centralised as the single source of truth so consumers (e.g. category
30
+ * shortcuts, related markets) share one classification instead of re-deriving
31
+ * it per client and drifting as new categories are added.
32
+ *
33
+ * @param market - The market data to classify.
34
+ * @returns The market type filter bucket.
35
+ */
36
+ export declare function getMarketTypeFilter(market: PerpsMarketData): MarketTypeFilter;
37
+ /**
38
+ * Applies optional category filtering, sorting, and limit to a list of markets.
39
+ *
40
+ * @param markets - Source market array.
41
+ * @param params - Optional filter/sort/limit params.
42
+ * @returns Filtered, sorted, and/or sliced market array.
43
+ */
44
+ export declare function applyMarketFilters(markets: PerpsMarketData[], params?: GetMarketDataWithPricesParams): PerpsMarketData[];
3
45
  /**
4
46
  * Maximum length for market filter patterns (prevents DoS attacks)
5
47
  */
@@ -1 +1 @@
1
- {"version":3,"file":"marketUtils.d.cts","sourceRoot":"","sources":["../../src/utils/marketUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,2BAAiB;AAChD,OAAO,KAAK,EAAE,UAAU,EAAe,iCAA6B;AAEpE;;GAEG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnD,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,oBAAoB,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAS,MAAM,KAAG,MACG,CAAC;AAE9C,eAAO,MAAM,qBAAqB,YAAa,MAAM,KAAG,OA4BvD,CAAC;AAEF,eAAO,MAAM,oBAAoB,YAAa,MAAM,KAAG,oBActD,CAAC;AAEF,eAAO,MAAM,oBAAoB,WACvB,MAAM,WACL,oBAAoB,KAC5B,OAMF,CAAC;AAEF,eAAO,MAAM,mBAAmB,WACtB,MAAM,OACT,MAAM,GAAG,IAAI,eACL,OAAO,2BACK,qBAAqB,EAAE,2BACvB,qBAAqB,EAAE,KAC/C,OA2BF,CAAC;AAEF,eAAO,MAAM,qBAAqB,WAAY,MAAM,KAAG,MAWtD,CAAC;AAEF,eAAO,MAAM,qBAAqB,WAAY,MAAM,KAAG,MAAM,GAAG,IAW/D,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,yBAAyB,YAC3B,sBAAsB,KAC9B,MA2CF,CAAC;AAEF,eAAO,MAAM,mBAAmB,eAClB,UAAU,GAAG,IAAI,KAC5B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CA2B7B,CAAC;AAEF,eAAO,MAAM,oBAAoB,YACtB,eAAe,EAAE,eACb,MAAM,KAClB,eAAe,EAYjB,CAAC"}
1
+ {"version":3,"file":"marketUtils.d.cts","sourceRoot":"","sources":["../../src/utils/marketUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,eAAe,EAChB,2BAAiB;AAClB,OAAO,KAAK,EAAE,UAAU,EAAe,iCAA6B;AAUpE;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,WACf,KAAK,eAAe,EAAE,QAAQ,GAAG,cAAc,CAAC,KACvD,OAAiE,CAAC;AAErE;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAkBT;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CAQ7E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,eAAe,EAAE,EAC1B,MAAM,CAAC,EAAE,6BAA6B,GACrC,eAAe,EAAE,CA6BnB;AAED;;GAEG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnD,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,oBAAoB,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAS,MAAM,KAAG,MACG,CAAC;AAE9C,eAAO,MAAM,qBAAqB,YAAa,MAAM,KAAG,OA4BvD,CAAC;AAEF,eAAO,MAAM,oBAAoB,YAAa,MAAM,KAAG,oBActD,CAAC;AAEF,eAAO,MAAM,oBAAoB,WACvB,MAAM,WACL,oBAAoB,KAC5B,OAMF,CAAC;AAEF,eAAO,MAAM,mBAAmB,WACtB,MAAM,OACT,MAAM,GAAG,IAAI,eACL,OAAO,2BACK,qBAAqB,EAAE,2BACvB,qBAAqB,EAAE,KAC/C,OA2BF,CAAC;AAEF,eAAO,MAAM,qBAAqB,WAAY,MAAM,KAAG,MAWtD,CAAC;AAEF,eAAO,MAAM,qBAAqB,WAAY,MAAM,KAAG,MAAM,GAAG,IAW/D,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,yBAAyB,YAC3B,sBAAsB,KAC9B,MA2CF,CAAC;AAEF,eAAO,MAAM,mBAAmB,eAClB,UAAU,GAAG,IAAI,KAC5B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CA2B7B,CAAC;AAEF,eAAO,MAAM,oBAAoB,YACtB,eAAe,EAAE,eACb,MAAM,KAClB,eAAe,EAYjB,CAAC"}
@@ -1,5 +1,47 @@
1
- import type { PerpsMarketData } from "../types/index.mjs";
1
+ import type { GetMarketDataWithPricesParams, MarketTypeFilter, PerpsMarketData } from "../types/index.mjs";
2
2
  import type { CandleData } from "../types/perps-types.mjs";
3
+ /**
4
+ * Whether a market is a HIP-3 (non-main-DEX) market. A `marketSource` DEX id
5
+ * marks a HIP-3 market even when the `isHip3` flag is unset (e.g. partial route
6
+ * params), so both signals are checked. Used as the single HIP-3 signal so the
7
+ * classifiers stay consistent.
8
+ *
9
+ * @param market - The market data to test.
10
+ * @returns True if the market is HIP-3.
11
+ */
12
+ export declare const isHip3Market: (market: Pick<PerpsMarketData, 'isHip3' | 'marketSource'>) => boolean;
13
+ /**
14
+ * Returns true when a market matches the given UI filter category.
15
+ *
16
+ * @param market - The market data to test.
17
+ * @param category - The filter category to test against.
18
+ * @returns Whether the market matches the category.
19
+ */
20
+ export declare function matchesCategory(market: PerpsMarketData, category: MarketTypeFilter): boolean;
21
+ /**
22
+ * Resolve the user-facing category bucket for a market — one of `crypto`,
23
+ * `stock`, `pre-ipo`, `index`, `etf`, `commodity`, `forex`, or `new`. Data-model
24
+ * categories map 1:1. A market with no data-model category is `crypto` when it
25
+ * is main-DEX, or `new` when it is an uncategorized HIP-3 market (`isHip3`, or a
26
+ * `marketSource` DEX id when `isHip3` is unset, e.g. minimal route params).
27
+ * Never returns the `all` sentinel.
28
+ *
29
+ * Centralised as the single source of truth so consumers (e.g. category
30
+ * shortcuts, related markets) share one classification instead of re-deriving
31
+ * it per client and drifting as new categories are added.
32
+ *
33
+ * @param market - The market data to classify.
34
+ * @returns The market type filter bucket.
35
+ */
36
+ export declare function getMarketTypeFilter(market: PerpsMarketData): MarketTypeFilter;
37
+ /**
38
+ * Applies optional category filtering, sorting, and limit to a list of markets.
39
+ *
40
+ * @param markets - Source market array.
41
+ * @param params - Optional filter/sort/limit params.
42
+ * @returns Filtered, sorted, and/or sliced market array.
43
+ */
44
+ export declare function applyMarketFilters(markets: PerpsMarketData[], params?: GetMarketDataWithPricesParams): PerpsMarketData[];
3
45
  /**
4
46
  * Maximum length for market filter patterns (prevents DoS attacks)
5
47
  */
@@ -1 +1 @@
1
- {"version":3,"file":"marketUtils.d.mts","sourceRoot":"","sources":["../../src/utils/marketUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,2BAAiB;AAChD,OAAO,KAAK,EAAE,UAAU,EAAe,iCAA6B;AAEpE;;GAEG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnD,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,oBAAoB,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAS,MAAM,KAAG,MACG,CAAC;AAE9C,eAAO,MAAM,qBAAqB,YAAa,MAAM,KAAG,OA4BvD,CAAC;AAEF,eAAO,MAAM,oBAAoB,YAAa,MAAM,KAAG,oBActD,CAAC;AAEF,eAAO,MAAM,oBAAoB,WACvB,MAAM,WACL,oBAAoB,KAC5B,OAMF,CAAC;AAEF,eAAO,MAAM,mBAAmB,WACtB,MAAM,OACT,MAAM,GAAG,IAAI,eACL,OAAO,2BACK,qBAAqB,EAAE,2BACvB,qBAAqB,EAAE,KAC/C,OA2BF,CAAC;AAEF,eAAO,MAAM,qBAAqB,WAAY,MAAM,KAAG,MAWtD,CAAC;AAEF,eAAO,MAAM,qBAAqB,WAAY,MAAM,KAAG,MAAM,GAAG,IAW/D,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,yBAAyB,YAC3B,sBAAsB,KAC9B,MA2CF,CAAC;AAEF,eAAO,MAAM,mBAAmB,eAClB,UAAU,GAAG,IAAI,KAC5B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CA2B7B,CAAC;AAEF,eAAO,MAAM,oBAAoB,YACtB,eAAe,EAAE,eACb,MAAM,KAClB,eAAe,EAYjB,CAAC"}
1
+ {"version":3,"file":"marketUtils.d.mts","sourceRoot":"","sources":["../../src/utils/marketUtils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,eAAe,EAChB,2BAAiB;AAClB,OAAO,KAAK,EAAE,UAAU,EAAe,iCAA6B;AAUpE;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,WACf,KAAK,eAAe,EAAE,QAAQ,GAAG,cAAc,CAAC,KACvD,OAAiE,CAAC;AAErE;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,eAAe,EACvB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAkBT;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,gBAAgB,CAQ7E;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,eAAe,EAAE,EAC1B,MAAM,CAAC,EAAE,6BAA6B,GACrC,eAAe,EAAE,CA6BnB;AAED;;GAEG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAE7C,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnD,MAAM,MAAM,qBAAqB,GAAG;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,oBAAoB,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,WAAW,QAAS,MAAM,KAAG,MACG,CAAC;AAE9C,eAAO,MAAM,qBAAqB,YAAa,MAAM,KAAG,OA4BvD,CAAC;AAEF,eAAO,MAAM,oBAAoB,YAAa,MAAM,KAAG,oBActD,CAAC;AAEF,eAAO,MAAM,oBAAoB,WACvB,MAAM,WACL,oBAAoB,KAC5B,OAMF,CAAC;AAEF,eAAO,MAAM,mBAAmB,WACtB,MAAM,OACT,MAAM,GAAG,IAAI,eACL,OAAO,2BACK,qBAAqB,EAAE,2BACvB,qBAAqB,EAAE,KAC/C,OA2BF,CAAC;AAEF,eAAO,MAAM,qBAAqB,WAAY,MAAM,KAAG,MAWtD,CAAC;AAEF,eAAO,MAAM,qBAAqB,WAAY,MAAM,KAAG,MAAM,GAAG,IAW/D,CAAC;AAEF,KAAK,sBAAsB,GAAG;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAEF,eAAO,MAAM,yBAAyB,YAC3B,sBAAsB,KAC9B,MA2CF,CAAC;AAEF,eAAO,MAAM,mBAAmB,eAClB,UAAU,GAAG,IAAI,KAC5B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CA2B7B,CAAC;AAEF,eAAO,MAAM,oBAAoB,YACtB,eAAe,EAAE,eACb,MAAM,KAClB,eAAe,EAYjB,CAAC"}
@@ -1,3 +1,99 @@
1
+ import { sortMarkets } from "./sortMarkets.mjs";
2
+ // ============================================================================
3
+ // Market category classification (pure functions)
4
+ // No service dependencies — pure data transformations that can be tested and
5
+ // reused independently. `matchesCategory` and `getMarketTypeFilter` share the
6
+ // same category model.
7
+ // ============================================================================
8
+ /**
9
+ * Whether a market is a HIP-3 (non-main-DEX) market. A `marketSource` DEX id
10
+ * marks a HIP-3 market even when the `isHip3` flag is unset (e.g. partial route
11
+ * params), so both signals are checked. Used as the single HIP-3 signal so the
12
+ * classifiers stay consistent.
13
+ *
14
+ * @param market - The market data to test.
15
+ * @returns True if the market is HIP-3.
16
+ */
17
+ export const isHip3Market = (market) => Boolean(market.isHip3) || Boolean(market.marketSource);
18
+ /**
19
+ * Returns true when a market matches the given UI filter category.
20
+ *
21
+ * @param market - The market data to test.
22
+ * @param category - The filter category to test against.
23
+ * @returns Whether the market matches the category.
24
+ */
25
+ export function matchesCategory(market, category) {
26
+ switch (category) {
27
+ case 'all':
28
+ return true;
29
+ case 'new':
30
+ // Explicitly flagged, or an uncategorized HIP-3 market (kept in sync with
31
+ // getMarketTypeFilter's 'new' bucket).
32
+ return (market.isNewMarket === true ||
33
+ (isHip3Market(market) && market.marketType === undefined));
34
+ case 'crypto':
35
+ // Main-DEX markets, plus HIP-3 assets explicitly typed as CryptoCurrency.
36
+ return !isHip3Market(market) || market.marketType === 'crypto';
37
+ default:
38
+ // Every other filter is a 1:1 data-model category match.
39
+ return market.marketType !== undefined && market.marketType === category;
40
+ }
41
+ }
42
+ /**
43
+ * Resolve the user-facing category bucket for a market — one of `crypto`,
44
+ * `stock`, `pre-ipo`, `index`, `etf`, `commodity`, `forex`, or `new`. Data-model
45
+ * categories map 1:1. A market with no data-model category is `crypto` when it
46
+ * is main-DEX, or `new` when it is an uncategorized HIP-3 market (`isHip3`, or a
47
+ * `marketSource` DEX id when `isHip3` is unset, e.g. minimal route params).
48
+ * Never returns the `all` sentinel.
49
+ *
50
+ * Centralised as the single source of truth so consumers (e.g. category
51
+ * shortcuts, related markets) share one classification instead of re-deriving
52
+ * it per client and drifting as new categories are added.
53
+ *
54
+ * @param market - The market data to classify.
55
+ * @returns The market type filter bucket.
56
+ */
57
+ export function getMarketTypeFilter(market) {
58
+ const { marketType } = market;
59
+ if (marketType) {
60
+ return marketType;
61
+ }
62
+ // No data-model category: an uncategorized HIP-3 market is the 'new' bucket;
63
+ // otherwise it's a main-DEX crypto market.
64
+ return isHip3Market(market) ? 'new' : 'crypto';
65
+ }
66
+ /**
67
+ * Applies optional category filtering, sorting, and limit to a list of markets.
68
+ *
69
+ * @param markets - Source market array.
70
+ * @param params - Optional filter/sort/limit params.
71
+ * @returns Filtered, sorted, and/or sliced market array.
72
+ */
73
+ export function applyMarketFilters(markets, params) {
74
+ let result = markets;
75
+ if (params?.categories?.length) {
76
+ const { categories } = params;
77
+ result = result.filter((market) =>
78
+ // A market is included if it matches ANY of the requested categories.
79
+ categories.some((category) => matchesCategory(market, category)));
80
+ }
81
+ if (params?.excludeSymbols?.length) {
82
+ const excluded = new Set(params.excludeSymbols);
83
+ result = result.filter((market) => !excluded.has(market.symbol));
84
+ }
85
+ if (params?.sortBy) {
86
+ result = sortMarkets({
87
+ markets: result,
88
+ sortBy: params.sortBy,
89
+ direction: params.direction,
90
+ });
91
+ }
92
+ if (params?.limit !== undefined) {
93
+ result = result.slice(0, params.limit);
94
+ }
95
+ return result;
96
+ }
1
97
  /**
2
98
  * Maximum length for market filter patterns (prevents DoS attacks)
3
99
  */
@@ -1 +1 @@
1
- {"version":3,"file":"marketUtils.mjs","sourceRoot":"","sources":["../../src/utils/marketUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PerpsMarketData } from '../types';\nimport type { CandleData, CandleStick } from '../types/perps-types';\n\n/**\n * Maximum length for market filter patterns (prevents DoS attacks)\n */\nexport const MAX_MARKET_PATTERN_LENGTH = 100;\n\nexport type MarketPatternMatcher = RegExp | string;\n\nexport type CompiledMarketPattern = {\n pattern: string;\n matcher: MarketPatternMatcher;\n};\n\nexport const escapeRegex = (str: string): string =>\n str.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n\nexport const validateMarketPattern = (pattern: string): boolean => {\n if (!pattern || pattern.trim().length === 0) {\n throw new Error('Market pattern cannot be empty');\n }\n\n const normalizedPattern = pattern.trim();\n\n if (normalizedPattern.length > MAX_MARKET_PATTERN_LENGTH) {\n throw new Error(\n `Market pattern exceeds maximum length (${MAX_MARKET_PATTERN_LENGTH} chars): ${normalizedPattern}`,\n );\n }\n\n const dangerousChars = /[\\\\()[\\]{}^$+?.|]/u;\n if (dangerousChars.test(normalizedPattern)) {\n throw new Error(\n `Market pattern contains invalid regex characters: ${normalizedPattern}`,\n );\n }\n\n const validPattern = /^[a-zA-Z0-9:_\\-*]+$/u;\n if (!validPattern.test(normalizedPattern)) {\n throw new Error(\n `Market pattern contains invalid characters: ${normalizedPattern}`,\n );\n }\n\n return true;\n};\n\nexport const compileMarketPattern = (pattern: string): MarketPatternMatcher => {\n const normalizedPattern = pattern.trim();\n validateMarketPattern(normalizedPattern);\n\n if (normalizedPattern.endsWith(':*')) {\n const prefix = normalizedPattern.slice(0, -2);\n return new RegExp(`^${escapeRegex(prefix)}:`, 'u');\n }\n\n if (!normalizedPattern.includes(':')) {\n return new RegExp(`^${escapeRegex(normalizedPattern)}:`, 'u');\n }\n\n return normalizedPattern;\n};\n\nexport const matchesMarketPattern = (\n symbol: string,\n matcher: MarketPatternMatcher,\n): boolean => {\n if (typeof matcher === 'string') {\n return symbol === matcher;\n }\n\n return matcher.test(symbol);\n};\n\nexport const shouldIncludeMarket = (\n symbol: string,\n dex: string | null,\n hip3Enabled: boolean,\n compiledEnabledPatterns: CompiledMarketPattern[],\n compiledBlockedPatterns: CompiledMarketPattern[],\n): boolean => {\n if (dex === null) {\n return true;\n }\n\n if (!hip3Enabled) {\n return false;\n }\n\n if (compiledEnabledPatterns.length > 0) {\n const whitelisted = compiledEnabledPatterns.some(({ matcher }) =>\n matchesMarketPattern(symbol, matcher),\n );\n if (!whitelisted) {\n return false;\n }\n }\n\n if (compiledBlockedPatterns.length === 0) {\n return true;\n }\n\n const blacklisted = compiledBlockedPatterns.some(({ matcher }) =>\n matchesMarketPattern(symbol, matcher),\n );\n\n return !blacklisted;\n};\n\nexport const getPerpsDisplaySymbol = (symbol: string): string => {\n if (!symbol || typeof symbol !== 'string') {\n return symbol;\n }\n\n const colonIndex = symbol.indexOf(':');\n if (colonIndex > 0 && colonIndex < symbol.length - 1) {\n return symbol.substring(colonIndex + 1);\n }\n\n return symbol;\n};\n\nexport const getPerpsDexFromSymbol = (symbol: string): string | null => {\n if (!symbol || typeof symbol !== 'string') {\n return null;\n }\n\n const colonIndex = symbol.indexOf(':');\n if (colonIndex > 0 && colonIndex < symbol.length - 1) {\n return symbol.substring(0, colonIndex);\n }\n\n return null;\n};\n\ntype FundingCountdownParams = {\n nextFundingTime?: number;\n fundingIntervalHours?: number;\n};\n\nexport const calculateFundingCountdown = (\n params?: FundingCountdownParams,\n): string => {\n const now = new Date();\n const nowMs = now.getTime();\n\n if (params?.nextFundingTime && params.nextFundingTime > nowMs) {\n const msUntilFunding = params.nextFundingTime - nowMs;\n const hoursUntilFunding = msUntilFunding / (1000 * 60 * 60);\n\n if (hoursUntilFunding <= 1.1) {\n const totalSeconds = Math.floor(msUntilFunding / 1000);\n\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n\n const formattedHours = String(hours).padStart(2, '0');\n const formattedMinutes = String(minutes).padStart(2, '0');\n const formattedSeconds = String(seconds).padStart(2, '0');\n\n return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;\n }\n }\n\n const utcMinutes = now.getUTCMinutes();\n const utcSeconds = now.getUTCSeconds();\n\n const minutesUntilNextHour = 59 - utcMinutes;\n const secondsUntilNextHour = 60 - utcSeconds;\n\n const finalSeconds = secondsUntilNextHour === 60 ? 0 : secondsUntilNextHour;\n const finalMinutes =\n secondsUntilNextHour === 60\n ? minutesUntilNextHour + 1\n : minutesUntilNextHour;\n\n const finalHours = finalMinutes === 60 ? 1 : 0;\n const adjustedMinutes = finalMinutes === 60 ? 0 : finalMinutes;\n\n const formattedHours = String(finalHours).padStart(2, '0');\n const formattedMinutes = String(adjustedMinutes).padStart(2, '0');\n const formattedSeconds = String(finalSeconds).padStart(2, '0');\n\n return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;\n};\n\nexport const calculate24hHighLow = (\n candleData: CandleData | null,\n): { high: number; low: number } => {\n if (!candleData?.candles || candleData.candles.length === 0) {\n return { high: 0, low: 0 };\n }\n\n const now = Date.now();\n const twentyFourHoursAgo = now - 24 * 60 * 60 * 1000;\n\n let last24hCandles = candleData.candles.filter(\n (candle: CandleStick) => candle.time >= twentyFourHoursAgo,\n );\n\n if (last24hCandles.length === 0) {\n last24hCandles = [...candleData.candles];\n }\n\n const highs = last24hCandles.map((candle: CandleStick) =>\n parseFloat(candle.high),\n );\n const lows = last24hCandles.map((candle: CandleStick) =>\n parseFloat(candle.low),\n );\n\n return {\n high: Math.max(...highs),\n low: Math.min(...lows),\n };\n};\n\nexport const filterMarketsByQuery = (\n markets: PerpsMarketData[],\n searchQuery: string,\n): PerpsMarketData[] => {\n if (!searchQuery?.trim()) {\n return markets;\n }\n\n const lowerQuery = searchQuery.toLowerCase().trim();\n\n return markets.filter(\n (market) =>\n market.symbol?.toLowerCase().includes(lowerQuery) ||\n market.name?.toLowerCase().includes(lowerQuery),\n );\n};\n"]}
1
+ {"version":3,"file":"marketUtils.mjs","sourceRoot":"","sources":["../../src/utils/marketUtils.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n GetMarketDataWithPricesParams,\n MarketTypeFilter,\n PerpsMarketData,\n} from '../types';\nimport type { CandleData, CandleStick } from '../types/perps-types';\nimport { sortMarkets } from './sortMarkets';\n\n// ============================================================================\n// Market category classification (pure functions)\n// No service dependencies — pure data transformations that can be tested and\n// reused independently. `matchesCategory` and `getMarketTypeFilter` share the\n// same category model.\n// ============================================================================\n\n/**\n * Whether a market is a HIP-3 (non-main-DEX) market. A `marketSource` DEX id\n * marks a HIP-3 market even when the `isHip3` flag is unset (e.g. partial route\n * params), so both signals are checked. Used as the single HIP-3 signal so the\n * classifiers stay consistent.\n *\n * @param market - The market data to test.\n * @returns True if the market is HIP-3.\n */\nexport const isHip3Market = (\n market: Pick<PerpsMarketData, 'isHip3' | 'marketSource'>,\n): boolean => Boolean(market.isHip3) || Boolean(market.marketSource);\n\n/**\n * Returns true when a market matches the given UI filter category.\n *\n * @param market - The market data to test.\n * @param category - The filter category to test against.\n * @returns Whether the market matches the category.\n */\nexport function matchesCategory(\n market: PerpsMarketData,\n category: MarketTypeFilter,\n): boolean {\n switch (category) {\n case 'all':\n return true;\n case 'new':\n // Explicitly flagged, or an uncategorized HIP-3 market (kept in sync with\n // getMarketTypeFilter's 'new' bucket).\n return (\n market.isNewMarket === true ||\n (isHip3Market(market) && market.marketType === undefined)\n );\n case 'crypto':\n // Main-DEX markets, plus HIP-3 assets explicitly typed as CryptoCurrency.\n return !isHip3Market(market) || market.marketType === 'crypto';\n default:\n // Every other filter is a 1:1 data-model category match.\n return market.marketType !== undefined && market.marketType === category;\n }\n}\n\n/**\n * Resolve the user-facing category bucket for a market — one of `crypto`,\n * `stock`, `pre-ipo`, `index`, `etf`, `commodity`, `forex`, or `new`. Data-model\n * categories map 1:1. A market with no data-model category is `crypto` when it\n * is main-DEX, or `new` when it is an uncategorized HIP-3 market (`isHip3`, or a\n * `marketSource` DEX id when `isHip3` is unset, e.g. minimal route params).\n * Never returns the `all` sentinel.\n *\n * Centralised as the single source of truth so consumers (e.g. category\n * shortcuts, related markets) share one classification instead of re-deriving\n * it per client and drifting as new categories are added.\n *\n * @param market - The market data to classify.\n * @returns The market type filter bucket.\n */\nexport function getMarketTypeFilter(market: PerpsMarketData): MarketTypeFilter {\n const { marketType } = market;\n if (marketType) {\n return marketType;\n }\n // No data-model category: an uncategorized HIP-3 market is the 'new' bucket;\n // otherwise it's a main-DEX crypto market.\n return isHip3Market(market) ? 'new' : 'crypto';\n}\n\n/**\n * Applies optional category filtering, sorting, and limit to a list of markets.\n *\n * @param markets - Source market array.\n * @param params - Optional filter/sort/limit params.\n * @returns Filtered, sorted, and/or sliced market array.\n */\nexport function applyMarketFilters(\n markets: PerpsMarketData[],\n params?: GetMarketDataWithPricesParams,\n): PerpsMarketData[] {\n let result = markets;\n\n if (params?.categories?.length) {\n const { categories } = params;\n result = result.filter((market) =>\n // A market is included if it matches ANY of the requested categories.\n categories.some((category) => matchesCategory(market, category)),\n );\n }\n\n if (params?.excludeSymbols?.length) {\n const excluded = new Set(params.excludeSymbols);\n result = result.filter((market) => !excluded.has(market.symbol));\n }\n\n if (params?.sortBy) {\n result = sortMarkets({\n markets: result,\n sortBy: params.sortBy,\n direction: params.direction,\n });\n }\n\n if (params?.limit !== undefined) {\n result = result.slice(0, params.limit);\n }\n\n return result;\n}\n\n/**\n * Maximum length for market filter patterns (prevents DoS attacks)\n */\nexport const MAX_MARKET_PATTERN_LENGTH = 100;\n\nexport type MarketPatternMatcher = RegExp | string;\n\nexport type CompiledMarketPattern = {\n pattern: string;\n matcher: MarketPatternMatcher;\n};\n\nexport const escapeRegex = (str: string): string =>\n str.replace(/[.*+?^${}()|[\\]\\\\]/gu, '\\\\$&');\n\nexport const validateMarketPattern = (pattern: string): boolean => {\n if (!pattern || pattern.trim().length === 0) {\n throw new Error('Market pattern cannot be empty');\n }\n\n const normalizedPattern = pattern.trim();\n\n if (normalizedPattern.length > MAX_MARKET_PATTERN_LENGTH) {\n throw new Error(\n `Market pattern exceeds maximum length (${MAX_MARKET_PATTERN_LENGTH} chars): ${normalizedPattern}`,\n );\n }\n\n const dangerousChars = /[\\\\()[\\]{}^$+?.|]/u;\n if (dangerousChars.test(normalizedPattern)) {\n throw new Error(\n `Market pattern contains invalid regex characters: ${normalizedPattern}`,\n );\n }\n\n const validPattern = /^[a-zA-Z0-9:_\\-*]+$/u;\n if (!validPattern.test(normalizedPattern)) {\n throw new Error(\n `Market pattern contains invalid characters: ${normalizedPattern}`,\n );\n }\n\n return true;\n};\n\nexport const compileMarketPattern = (pattern: string): MarketPatternMatcher => {\n const normalizedPattern = pattern.trim();\n validateMarketPattern(normalizedPattern);\n\n if (normalizedPattern.endsWith(':*')) {\n const prefix = normalizedPattern.slice(0, -2);\n return new RegExp(`^${escapeRegex(prefix)}:`, 'u');\n }\n\n if (!normalizedPattern.includes(':')) {\n return new RegExp(`^${escapeRegex(normalizedPattern)}:`, 'u');\n }\n\n return normalizedPattern;\n};\n\nexport const matchesMarketPattern = (\n symbol: string,\n matcher: MarketPatternMatcher,\n): boolean => {\n if (typeof matcher === 'string') {\n return symbol === matcher;\n }\n\n return matcher.test(symbol);\n};\n\nexport const shouldIncludeMarket = (\n symbol: string,\n dex: string | null,\n hip3Enabled: boolean,\n compiledEnabledPatterns: CompiledMarketPattern[],\n compiledBlockedPatterns: CompiledMarketPattern[],\n): boolean => {\n if (dex === null) {\n return true;\n }\n\n if (!hip3Enabled) {\n return false;\n }\n\n if (compiledEnabledPatterns.length > 0) {\n const whitelisted = compiledEnabledPatterns.some(({ matcher }) =>\n matchesMarketPattern(symbol, matcher),\n );\n if (!whitelisted) {\n return false;\n }\n }\n\n if (compiledBlockedPatterns.length === 0) {\n return true;\n }\n\n const blacklisted = compiledBlockedPatterns.some(({ matcher }) =>\n matchesMarketPattern(symbol, matcher),\n );\n\n return !blacklisted;\n};\n\nexport const getPerpsDisplaySymbol = (symbol: string): string => {\n if (!symbol || typeof symbol !== 'string') {\n return symbol;\n }\n\n const colonIndex = symbol.indexOf(':');\n if (colonIndex > 0 && colonIndex < symbol.length - 1) {\n return symbol.substring(colonIndex + 1);\n }\n\n return symbol;\n};\n\nexport const getPerpsDexFromSymbol = (symbol: string): string | null => {\n if (!symbol || typeof symbol !== 'string') {\n return null;\n }\n\n const colonIndex = symbol.indexOf(':');\n if (colonIndex > 0 && colonIndex < symbol.length - 1) {\n return symbol.substring(0, colonIndex);\n }\n\n return null;\n};\n\ntype FundingCountdownParams = {\n nextFundingTime?: number;\n fundingIntervalHours?: number;\n};\n\nexport const calculateFundingCountdown = (\n params?: FundingCountdownParams,\n): string => {\n const now = new Date();\n const nowMs = now.getTime();\n\n if (params?.nextFundingTime && params.nextFundingTime > nowMs) {\n const msUntilFunding = params.nextFundingTime - nowMs;\n const hoursUntilFunding = msUntilFunding / (1000 * 60 * 60);\n\n if (hoursUntilFunding <= 1.1) {\n const totalSeconds = Math.floor(msUntilFunding / 1000);\n\n const hours = Math.floor(totalSeconds / 3600);\n const minutes = Math.floor((totalSeconds % 3600) / 60);\n const seconds = totalSeconds % 60;\n\n const formattedHours = String(hours).padStart(2, '0');\n const formattedMinutes = String(minutes).padStart(2, '0');\n const formattedSeconds = String(seconds).padStart(2, '0');\n\n return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;\n }\n }\n\n const utcMinutes = now.getUTCMinutes();\n const utcSeconds = now.getUTCSeconds();\n\n const minutesUntilNextHour = 59 - utcMinutes;\n const secondsUntilNextHour = 60 - utcSeconds;\n\n const finalSeconds = secondsUntilNextHour === 60 ? 0 : secondsUntilNextHour;\n const finalMinutes =\n secondsUntilNextHour === 60\n ? minutesUntilNextHour + 1\n : minutesUntilNextHour;\n\n const finalHours = finalMinutes === 60 ? 1 : 0;\n const adjustedMinutes = finalMinutes === 60 ? 0 : finalMinutes;\n\n const formattedHours = String(finalHours).padStart(2, '0');\n const formattedMinutes = String(adjustedMinutes).padStart(2, '0');\n const formattedSeconds = String(finalSeconds).padStart(2, '0');\n\n return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;\n};\n\nexport const calculate24hHighLow = (\n candleData: CandleData | null,\n): { high: number; low: number } => {\n if (!candleData?.candles || candleData.candles.length === 0) {\n return { high: 0, low: 0 };\n }\n\n const now = Date.now();\n const twentyFourHoursAgo = now - 24 * 60 * 60 * 1000;\n\n let last24hCandles = candleData.candles.filter(\n (candle: CandleStick) => candle.time >= twentyFourHoursAgo,\n );\n\n if (last24hCandles.length === 0) {\n last24hCandles = [...candleData.candles];\n }\n\n const highs = last24hCandles.map((candle: CandleStick) =>\n parseFloat(candle.high),\n );\n const lows = last24hCandles.map((candle: CandleStick) =>\n parseFloat(candle.low),\n );\n\n return {\n high: Math.max(...highs),\n low: Math.min(...lows),\n };\n};\n\nexport const filterMarketsByQuery = (\n markets: PerpsMarketData[],\n searchQuery: string,\n): PerpsMarketData[] => {\n if (!searchQuery?.trim()) {\n return markets;\n }\n\n const lowerQuery = searchQuery.toLowerCase().trim();\n\n return markets.filter(\n (market) =>\n market.symbol?.toLowerCase().includes(lowerQuery) ||\n market.name?.toLowerCase().includes(lowerQuery),\n );\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metamask-previews/perps-controller",
3
- "version": "7.0.0-preview-63e8c0e",
3
+ "version": "7.0.0-preview-ecd6b75",
4
4
  "description": "Controller for perpetual trading functionality in MetaMask",
5
5
  "keywords": [
6
6
  "Ethereum",