@metamask-previews/perps-controller 8.2.0-preview-b2742a2fc → 8.3.0-preview-a8fd340

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.
Files changed (86) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/dist/PerpsController.cjs +46 -10
  3. package/dist/PerpsController.cjs.map +1 -1
  4. package/dist/PerpsController.d.cts.map +1 -1
  5. package/dist/PerpsController.d.mts.map +1 -1
  6. package/dist/PerpsController.mjs +46 -10
  7. package/dist/PerpsController.mjs.map +1 -1
  8. package/dist/constants/hyperLiquidConfig.cjs +7 -0
  9. package/dist/constants/hyperLiquidConfig.cjs.map +1 -1
  10. package/dist/constants/hyperLiquidConfig.d.cts +1 -0
  11. package/dist/constants/hyperLiquidConfig.d.cts.map +1 -1
  12. package/dist/constants/hyperLiquidConfig.d.mts +1 -0
  13. package/dist/constants/hyperLiquidConfig.d.mts.map +1 -1
  14. package/dist/constants/hyperLiquidConfig.mjs +7 -0
  15. package/dist/constants/hyperLiquidConfig.mjs.map +1 -1
  16. package/dist/constants/perpsConfig.cjs +11 -1
  17. package/dist/constants/perpsConfig.cjs.map +1 -1
  18. package/dist/constants/perpsConfig.d.cts +10 -0
  19. package/dist/constants/perpsConfig.d.cts.map +1 -1
  20. package/dist/constants/perpsConfig.d.mts +10 -0
  21. package/dist/constants/perpsConfig.d.mts.map +1 -1
  22. package/dist/constants/perpsConfig.mjs +10 -0
  23. package/dist/constants/perpsConfig.mjs.map +1 -1
  24. package/dist/index.cjs +4 -3
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +2 -2
  27. package/dist/index.d.cts.map +1 -1
  28. package/dist/index.d.mts +2 -2
  29. package/dist/index.d.mts.map +1 -1
  30. package/dist/index.mjs +1 -1
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/providers/HyperLiquidProvider.cjs +6 -3
  33. package/dist/providers/HyperLiquidProvider.cjs.map +1 -1
  34. package/dist/providers/HyperLiquidProvider.d.cts +1 -0
  35. package/dist/providers/HyperLiquidProvider.d.cts.map +1 -1
  36. package/dist/providers/HyperLiquidProvider.d.mts +1 -0
  37. package/dist/providers/HyperLiquidProvider.d.mts.map +1 -1
  38. package/dist/providers/HyperLiquidProvider.mjs +7 -4
  39. package/dist/providers/HyperLiquidProvider.mjs.map +1 -1
  40. package/dist/services/HyperLiquidSubscriptionService.cjs +16 -3
  41. package/dist/services/HyperLiquidSubscriptionService.cjs.map +1 -1
  42. package/dist/services/HyperLiquidSubscriptionService.d.cts +1 -1
  43. package/dist/services/HyperLiquidSubscriptionService.d.cts.map +1 -1
  44. package/dist/services/HyperLiquidSubscriptionService.d.mts +1 -1
  45. package/dist/services/HyperLiquidSubscriptionService.d.mts.map +1 -1
  46. package/dist/services/HyperLiquidSubscriptionService.mjs +17 -4
  47. package/dist/services/HyperLiquidSubscriptionService.mjs.map +1 -1
  48. package/dist/services/MarketDataService.cjs +100 -7
  49. package/dist/services/MarketDataService.cjs.map +1 -1
  50. package/dist/services/MarketDataService.d.cts +10 -1
  51. package/dist/services/MarketDataService.d.cts.map +1 -1
  52. package/dist/services/MarketDataService.d.mts +10 -1
  53. package/dist/services/MarketDataService.d.mts.map +1 -1
  54. package/dist/services/MarketDataService.mjs +100 -7
  55. package/dist/services/MarketDataService.mjs.map +1 -1
  56. package/dist/services/TerminalMarketService.cjs +195 -0
  57. package/dist/services/TerminalMarketService.cjs.map +1 -0
  58. package/dist/services/TerminalMarketService.d.cts +36 -0
  59. package/dist/services/TerminalMarketService.d.cts.map +1 -0
  60. package/dist/services/TerminalMarketService.d.mts +36 -0
  61. package/dist/services/TerminalMarketService.d.mts.map +1 -0
  62. package/dist/services/TerminalMarketService.mjs +191 -0
  63. package/dist/services/TerminalMarketService.mjs.map +1 -0
  64. package/dist/types/index.cjs.map +1 -1
  65. package/dist/types/index.d.cts +82 -0
  66. package/dist/types/index.d.cts.map +1 -1
  67. package/dist/types/index.d.mts +82 -0
  68. package/dist/types/index.d.mts.map +1 -1
  69. package/dist/types/index.mjs.map +1 -1
  70. package/dist/utils/marketDataTransform.cjs +44 -9
  71. package/dist/utils/marketDataTransform.cjs.map +1 -1
  72. package/dist/utils/marketDataTransform.d.cts +28 -0
  73. package/dist/utils/marketDataTransform.d.cts.map +1 -1
  74. package/dist/utils/marketDataTransform.d.mts +28 -0
  75. package/dist/utils/marketDataTransform.d.mts.map +1 -1
  76. package/dist/utils/marketDataTransform.mjs +42 -8
  77. package/dist/utils/marketDataTransform.mjs.map +1 -1
  78. package/dist/utils/marketSearch.cjs +28 -2
  79. package/dist/utils/marketSearch.cjs.map +1 -1
  80. package/dist/utils/marketSearch.d.cts +4 -3
  81. package/dist/utils/marketSearch.d.cts.map +1 -1
  82. package/dist/utils/marketSearch.d.mts +4 -3
  83. package/dist/utils/marketSearch.d.mts.map +1 -1
  84. package/dist/utils/marketSearch.mjs +28 -2
  85. package/dist/utils/marketSearch.mjs.map +1 -1
  86. package/package.json +3 -2
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
3
+ if (kind === "m") throw new TypeError("Private method is not writable");
4
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
5
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
6
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
7
+ };
8
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
9
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
10
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
+ };
13
+ var _TerminalMarketService_instances, _TerminalMarketService_deps, _TerminalMarketService_cache, _TerminalMarketService_validateItems, _TerminalMarketService_mapToMarketInfo, _TerminalMarketService_extractMetadata;
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.TerminalMarketService = void 0;
16
+ const superstruct_1 = require("@metamask/superstruct");
17
+ const perpsConfig_1 = require("../constants/perpsConfig.cjs");
18
+ const types_1 = require("../types/index.cjs");
19
+ const errorUtils_1 = require("../utils/errorUtils.cjs");
20
+ const VALID_MARKET_TYPES = new Set(Object.values(types_1.MarketCategory));
21
+ /**
22
+ * Runtime validation schema for a single market item returned by
23
+ * `GET {terminalApiUrl}`.
24
+ *
25
+ * Uses `type()` (loose object matching) so that extra fields the API sends
26
+ * (e.g. `price`, `iconUrl`, `trend`) are silently accepted.
27
+ * Each item is individually validated; items that fail validation are
28
+ * filtered out and logged rather than rejecting the entire response.
29
+ */
30
+ const TerminalPerpetualItemStruct = (0, superstruct_1.type)({
31
+ symbol: (0, superstruct_1.string)(),
32
+ name: (0, superstruct_1.optional)((0, superstruct_1.nullable)((0, superstruct_1.string)())),
33
+ szDecimals: (0, superstruct_1.optional)((0, superstruct_1.number)()),
34
+ maxLeverage: (0, superstruct_1.optional)((0, superstruct_1.number)()),
35
+ marginTableId: (0, superstruct_1.optional)((0, superstruct_1.number)()),
36
+ onlyIsolated: (0, superstruct_1.optional)((0, superstruct_1.boolean)()),
37
+ isDelisted: (0, superstruct_1.optional)((0, superstruct_1.boolean)()),
38
+ minimumOrderSize: (0, superstruct_1.optional)((0, superstruct_1.number)()),
39
+ keywords: (0, superstruct_1.optional)((0, superstruct_1.nullable)((0, superstruct_1.array)((0, superstruct_1.string)()))),
40
+ tags: (0, superstruct_1.optional)((0, superstruct_1.nullable)((0, superstruct_1.array)((0, superstruct_1.string)()))),
41
+ categories: (0, superstruct_1.optional)((0, superstruct_1.nullable)((0, superstruct_1.array)((0, superstruct_1.string)()))),
42
+ marketType: (0, superstruct_1.optional)((0, superstruct_1.nullable)((0, superstruct_1.string)())),
43
+ });
44
+ /**
45
+ * TerminalMarketService
46
+ *
47
+ * Fetches structured market metadata from the MetaMask Terminal API.
48
+ * Caches responses for {@link TERMINAL_API_CONFIG.CacheTtlMs} to avoid
49
+ * redundant network calls across polling cycles.
50
+ *
51
+ * Instance-based service with constructor injection of platform dependencies.
52
+ */
53
+ class TerminalMarketService {
54
+ constructor(deps) {
55
+ _TerminalMarketService_instances.add(this);
56
+ _TerminalMarketService_deps.set(this, void 0);
57
+ _TerminalMarketService_cache.set(this, null);
58
+ __classPrivateFieldSet(this, _TerminalMarketService_deps, deps, "f");
59
+ }
60
+ /**
61
+ * Fetch markets from the Terminal API.
62
+ * Returns cached data when available and within TTL.
63
+ *
64
+ * @returns Object with mapped MarketInfo array and per-symbol metadata.
65
+ */
66
+ async fetchMarkets() {
67
+ if (__classPrivateFieldGet(this, _TerminalMarketService_cache, "f") &&
68
+ Date.now() - __classPrivateFieldGet(this, _TerminalMarketService_cache, "f").timestamp < perpsConfig_1.TERMINAL_API_CONFIG.CacheTtlMs) {
69
+ return {
70
+ markets: __classPrivateFieldGet(this, _TerminalMarketService_cache, "f").markets,
71
+ metadata: __classPrivateFieldGet(this, _TerminalMarketService_cache, "f").metadata,
72
+ };
73
+ }
74
+ if (!__classPrivateFieldGet(this, _TerminalMarketService_deps, "f").terminalApiUrl) {
75
+ throw new Error('Terminal API URL not configured (terminalApiUrl is required)');
76
+ }
77
+ const url = __classPrivateFieldGet(this, _TerminalMarketService_deps, "f").terminalApiUrl;
78
+ const controller = new AbortController();
79
+ const timeoutId = setTimeout(() => controller.abort(new Error('Terminal API fetch timed out')), perpsConfig_1.TERMINAL_API_CONFIG.FetchTimeoutMs);
80
+ let response;
81
+ try {
82
+ response = await fetch(url, {
83
+ method: 'GET',
84
+ headers: { 'Content-Type': 'application/json' },
85
+ signal: controller.signal,
86
+ });
87
+ }
88
+ finally {
89
+ clearTimeout(timeoutId);
90
+ }
91
+ if (!response.ok) {
92
+ throw new Error(`Terminal API returned ${String(response.status)}: ${response.statusText}`);
93
+ }
94
+ const body = await response.json();
95
+ if (!Array.isArray(body)) {
96
+ throw new Error(`Terminal API returned non-array body: ${typeof body}`);
97
+ }
98
+ const items = __classPrivateFieldGet(this, _TerminalMarketService_instances, "m", _TerminalMarketService_validateItems).call(this, body);
99
+ const markets = __classPrivateFieldGet(this, _TerminalMarketService_instances, "m", _TerminalMarketService_mapToMarketInfo).call(this, items);
100
+ const metadata = __classPrivateFieldGet(this, _TerminalMarketService_instances, "m", _TerminalMarketService_extractMetadata).call(this, items);
101
+ __classPrivateFieldSet(this, _TerminalMarketService_cache, { markets, metadata, timestamp: Date.now() }, "f");
102
+ return { markets, metadata };
103
+ }
104
+ /**
105
+ * Invalidate the internal cache so the next fetch hits the network.
106
+ */
107
+ clearCache() {
108
+ __classPrivateFieldSet(this, _TerminalMarketService_cache, null, "f");
109
+ }
110
+ /**
111
+ * Log a Terminal API error to Sentry without surfacing it to the user.
112
+ *
113
+ * @param error - The caught error.
114
+ * @param method - The calling method name for context.
115
+ */
116
+ logError(error, method) {
117
+ __classPrivateFieldGet(this, _TerminalMarketService_deps, "f").logger.error((0, errorUtils_1.ensureError)(error, `TerminalMarketService.${method}`), {
118
+ tags: { feature: perpsConfig_1.PERPS_CONSTANTS.FeatureName, source: 'terminal-api' },
119
+ context: {
120
+ name: `TerminalMarketService.${method}`,
121
+ data: { url: __classPrivateFieldGet(this, _TerminalMarketService_deps, "f").terminalApiUrl },
122
+ },
123
+ });
124
+ }
125
+ }
126
+ exports.TerminalMarketService = TerminalMarketService;
127
+ _TerminalMarketService_deps = new WeakMap(), _TerminalMarketService_cache = new WeakMap(), _TerminalMarketService_instances = new WeakSet(), _TerminalMarketService_validateItems = function _TerminalMarketService_validateItems(raw) {
128
+ const valid = [];
129
+ for (const item of raw) {
130
+ if ((0, superstruct_1.is)(item, TerminalPerpetualItemStruct)) {
131
+ valid.push(item);
132
+ }
133
+ else {
134
+ __classPrivateFieldGet(this, _TerminalMarketService_deps, "f").logger.error((0, errorUtils_1.ensureError)(new Error('Terminal API item failed schema validation'), 'TerminalMarketService.validateItems'), {
135
+ tags: {
136
+ feature: perpsConfig_1.PERPS_CONSTANTS.FeatureName,
137
+ source: 'terminal-api',
138
+ },
139
+ context: {
140
+ name: 'TerminalMarketService.validateItems',
141
+ data: {
142
+ symbol: typeof item === 'object' &&
143
+ item !== null &&
144
+ Object.prototype.hasOwnProperty.call(item, 'symbol')
145
+ ? item.symbol
146
+ : undefined,
147
+ },
148
+ },
149
+ });
150
+ }
151
+ }
152
+ return valid;
153
+ }, _TerminalMarketService_mapToMarketInfo = function _TerminalMarketService_mapToMarketInfo(items) {
154
+ return items
155
+ .filter((item) => typeof item.symbol === 'string' && item.symbol.length > 0)
156
+ .map((item) => ({
157
+ name: item.symbol,
158
+ szDecimals: item.szDecimals ?? 0,
159
+ maxLeverage: item.maxLeverage ?? 1,
160
+ marginTableId: item.marginTableId ?? 0,
161
+ ...(item.onlyIsolated === true && { onlyIsolated: true }),
162
+ ...(item.isDelisted === true && { isDelisted: true }),
163
+ ...(item.minimumOrderSize !== undefined && {
164
+ minimumOrderSize: item.minimumOrderSize,
165
+ }),
166
+ }));
167
+ }, _TerminalMarketService_extractMetadata = function _TerminalMarketService_extractMetadata(items) {
168
+ const map = new Map();
169
+ for (const item of items) {
170
+ if (typeof item.symbol !== 'string' || item.symbol.length === 0) {
171
+ continue;
172
+ }
173
+ const entry = {};
174
+ if (typeof item.name === 'string' && item.name.length > 0) {
175
+ entry.name = item.name;
176
+ }
177
+ if (Array.isArray(item.keywords) && item.keywords.length > 0) {
178
+ entry.keywords = item.keywords;
179
+ }
180
+ if (Array.isArray(item.tags) && item.tags.length > 0) {
181
+ entry.tags = item.tags;
182
+ }
183
+ if (Array.isArray(item.categories) && item.categories.length > 0) {
184
+ entry.categories = item.categories;
185
+ }
186
+ if (typeof item.marketType === 'string' &&
187
+ VALID_MARKET_TYPES.has(item.marketType)) {
188
+ entry.marketType =
189
+ item.marketType;
190
+ }
191
+ map.set(item.symbol, entry);
192
+ }
193
+ return map;
194
+ };
195
+ //# sourceMappingURL=TerminalMarketService.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TerminalMarketService.cjs","sourceRoot":"","sources":["../../src/services/TerminalMarketService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Infer } from '@metamask/superstruct';\nimport {\n array,\n boolean,\n is,\n nullable,\n number,\n optional,\n string,\n type,\n} from '@metamask/superstruct';\n\nimport { PERPS_CONSTANTS, TERMINAL_API_CONFIG } from '../constants/perpsConfig';\nimport type {\n MarketInfo,\n PerpsPlatformDependencies,\n TerminalAssetMetadata,\n} from '../types';\nimport { MarketCategory } from '../types';\nimport { ensureError } from '../utils/errorUtils';\n\nconst VALID_MARKET_TYPES = new Set<string>(Object.values(MarketCategory));\n\n/**\n * Runtime validation schema for a single market item returned by\n * `GET {terminalApiUrl}`.\n *\n * Uses `type()` (loose object matching) so that extra fields the API sends\n * (e.g. `price`, `iconUrl`, `trend`) are silently accepted.\n * Each item is individually validated; items that fail validation are\n * filtered out and logged rather than rejecting the entire response.\n */\nconst TerminalPerpetualItemStruct = type({\n symbol: string(),\n name: optional(nullable(string())),\n szDecimals: optional(number()),\n maxLeverage: optional(number()),\n marginTableId: optional(number()),\n onlyIsolated: optional(boolean()),\n isDelisted: optional(boolean()),\n minimumOrderSize: optional(number()),\n keywords: optional(nullable(array(string()))),\n tags: optional(nullable(array(string()))),\n categories: optional(nullable(array(string()))),\n marketType: optional(nullable(string())),\n});\n\ntype TerminalPerpetualItem = Infer<typeof TerminalPerpetualItemStruct>;\n\ntype CacheEntry = {\n markets: MarketInfo[];\n metadata: Map<string, TerminalAssetMetadata>;\n timestamp: number;\n};\n\n/**\n * TerminalMarketService\n *\n * Fetches structured market metadata from the MetaMask Terminal API.\n * Caches responses for {@link TERMINAL_API_CONFIG.CacheTtlMs} to avoid\n * redundant network calls across polling cycles.\n *\n * Instance-based service with constructor injection of platform dependencies.\n */\nexport class TerminalMarketService {\n readonly #deps: PerpsPlatformDependencies;\n\n #cache: CacheEntry | null = null;\n\n constructor(deps: PerpsPlatformDependencies) {\n this.#deps = deps;\n }\n\n /**\n * Fetch markets from the Terminal API.\n * Returns cached data when available and within TTL.\n *\n * @returns Object with mapped MarketInfo array and per-symbol metadata.\n */\n async fetchMarkets(): Promise<{\n markets: MarketInfo[];\n metadata: Map<string, TerminalAssetMetadata>;\n }> {\n if (\n this.#cache &&\n Date.now() - this.#cache.timestamp < TERMINAL_API_CONFIG.CacheTtlMs\n ) {\n return {\n markets: this.#cache.markets,\n metadata: this.#cache.metadata,\n };\n }\n\n if (!this.#deps.terminalApiUrl) {\n throw new Error(\n 'Terminal API URL not configured (terminalApiUrl is required)',\n );\n }\n\n const url = this.#deps.terminalApiUrl;\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(new Error('Terminal API fetch timed out')),\n TERMINAL_API_CONFIG.FetchTimeoutMs,\n );\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: { 'Content-Type': 'application/json' },\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (!response.ok) {\n throw new Error(\n `Terminal API returned ${String(response.status)}: ${response.statusText}`,\n );\n }\n\n const body: unknown = await response.json();\n\n if (!Array.isArray(body)) {\n throw new Error(`Terminal API returned non-array body: ${typeof body}`);\n }\n\n const items = this.#validateItems(body);\n const markets = this.#mapToMarketInfo(items);\n const metadata = this.#extractMetadata(items);\n\n this.#cache = { markets, metadata, timestamp: Date.now() };\n return { markets, metadata };\n }\n\n /**\n * Invalidate the internal cache so the next fetch hits the network.\n */\n clearCache(): void {\n this.#cache = null;\n }\n\n /**\n * Validate raw API response items against the expected schema.\n * Items that fail validation are filtered out and logged rather than\n * rejecting the entire response.\n *\n * @param raw - The raw array from the API response body.\n * @returns Array of validated items.\n */\n #validateItems(raw: unknown[]): TerminalPerpetualItem[] {\n const valid: TerminalPerpetualItem[] = [];\n for (const item of raw) {\n if (is(item, TerminalPerpetualItemStruct)) {\n valid.push(item);\n } else {\n this.#deps.logger.error(\n ensureError(\n new Error('Terminal API item failed schema validation'),\n 'TerminalMarketService.validateItems',\n ),\n {\n tags: {\n feature: PERPS_CONSTANTS.FeatureName,\n source: 'terminal-api',\n },\n context: {\n name: 'TerminalMarketService.validateItems',\n data: {\n symbol:\n typeof item === 'object' &&\n item !== null &&\n Object.prototype.hasOwnProperty.call(item, 'symbol')\n ? (item as Record<string, unknown>).symbol\n : undefined,\n },\n },\n },\n );\n }\n }\n return valid;\n }\n\n /**\n * Map Terminal API items to the protocol-agnostic MarketInfo shape.\n *\n * @param items - Raw items from the API response.\n * @returns Array of MarketInfo objects.\n */\n #mapToMarketInfo(items: TerminalPerpetualItem[]): MarketInfo[] {\n return items\n .filter(\n (item) => typeof item.symbol === 'string' && item.symbol.length > 0,\n )\n .map((item) => ({\n name: item.symbol,\n szDecimals: item.szDecimals ?? 0,\n maxLeverage: item.maxLeverage ?? 1,\n marginTableId: item.marginTableId ?? 0,\n ...(item.onlyIsolated === true && { onlyIsolated: true as const }),\n ...(item.isDelisted === true && { isDelisted: true as const }),\n ...(item.minimumOrderSize !== undefined && {\n minimumOrderSize: item.minimumOrderSize,\n }),\n }));\n }\n\n /**\n * Extract per-symbol metadata for downstream merge into PerpsMarketData.\n *\n * @param items - Raw items from the API response.\n * @returns Map keyed by symbol with enrichment metadata.\n */\n #extractMetadata(\n items: TerminalPerpetualItem[],\n ): Map<string, TerminalAssetMetadata> {\n const map = new Map<string, TerminalAssetMetadata>();\n\n for (const item of items) {\n if (typeof item.symbol !== 'string' || item.symbol.length === 0) {\n continue;\n }\n\n const entry: TerminalAssetMetadata = {};\n\n if (typeof item.name === 'string' && item.name.length > 0) {\n entry.name = item.name;\n }\n\n if (Array.isArray(item.keywords) && item.keywords.length > 0) {\n entry.keywords = item.keywords;\n }\n if (Array.isArray(item.tags) && item.tags.length > 0) {\n entry.tags = item.tags;\n }\n if (Array.isArray(item.categories) && item.categories.length > 0) {\n entry.categories = item.categories;\n }\n if (\n typeof item.marketType === 'string' &&\n VALID_MARKET_TYPES.has(item.marketType)\n ) {\n entry.marketType =\n item.marketType as TerminalAssetMetadata['marketType'];\n }\n\n map.set(item.symbol, entry);\n }\n\n return map;\n }\n\n /**\n * Log a Terminal API error to Sentry without surfacing it to the user.\n *\n * @param error - The caught error.\n * @param method - The calling method name for context.\n */\n logError(error: unknown, method: string): void {\n this.#deps.logger.error(\n ensureError(error, `TerminalMarketService.${method}`),\n {\n tags: { feature: PERPS_CONSTANTS.FeatureName, source: 'terminal-api' },\n context: {\n name: `TerminalMarketService.${method}`,\n data: { url: this.#deps.terminalApiUrl },\n },\n },\n );\n }\n}\n"]}
@@ -0,0 +1,36 @@
1
+ import type { MarketInfo, PerpsPlatformDependencies, TerminalAssetMetadata } from "../types/index.cjs";
2
+ /**
3
+ * TerminalMarketService
4
+ *
5
+ * Fetches structured market metadata from the MetaMask Terminal API.
6
+ * Caches responses for {@link TERMINAL_API_CONFIG.CacheTtlMs} to avoid
7
+ * redundant network calls across polling cycles.
8
+ *
9
+ * Instance-based service with constructor injection of platform dependencies.
10
+ */
11
+ export declare class TerminalMarketService {
12
+ #private;
13
+ constructor(deps: PerpsPlatformDependencies);
14
+ /**
15
+ * Fetch markets from the Terminal API.
16
+ * Returns cached data when available and within TTL.
17
+ *
18
+ * @returns Object with mapped MarketInfo array and per-symbol metadata.
19
+ */
20
+ fetchMarkets(): Promise<{
21
+ markets: MarketInfo[];
22
+ metadata: Map<string, TerminalAssetMetadata>;
23
+ }>;
24
+ /**
25
+ * Invalidate the internal cache so the next fetch hits the network.
26
+ */
27
+ clearCache(): void;
28
+ /**
29
+ * Log a Terminal API error to Sentry without surfacing it to the user.
30
+ *
31
+ * @param error - The caught error.
32
+ * @param method - The calling method name for context.
33
+ */
34
+ logError(error: unknown, method: string): void;
35
+ }
36
+ //# sourceMappingURL=TerminalMarketService.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TerminalMarketService.d.cts","sourceRoot":"","sources":["../../src/services/TerminalMarketService.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,UAAU,EACV,yBAAyB,EACzB,qBAAqB,EACtB,2BAAiB;AAsClB;;;;;;;;GAQG;AACH,qBAAa,qBAAqB;;gBAKpB,IAAI,EAAE,yBAAyB;IAI3C;;;;;OAKG;IACG,YAAY,IAAI,OAAO,CAAC;QAC5B,OAAO,EAAE,UAAU,EAAE,CAAC;QACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;KAC9C,CAAC;IAuDF;;OAEG;IACH,UAAU,IAAI,IAAI;IAmHlB;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAY/C"}
@@ -0,0 +1,36 @@
1
+ import type { MarketInfo, PerpsPlatformDependencies, TerminalAssetMetadata } from "../types/index.mjs";
2
+ /**
3
+ * TerminalMarketService
4
+ *
5
+ * Fetches structured market metadata from the MetaMask Terminal API.
6
+ * Caches responses for {@link TERMINAL_API_CONFIG.CacheTtlMs} to avoid
7
+ * redundant network calls across polling cycles.
8
+ *
9
+ * Instance-based service with constructor injection of platform dependencies.
10
+ */
11
+ export declare class TerminalMarketService {
12
+ #private;
13
+ constructor(deps: PerpsPlatformDependencies);
14
+ /**
15
+ * Fetch markets from the Terminal API.
16
+ * Returns cached data when available and within TTL.
17
+ *
18
+ * @returns Object with mapped MarketInfo array and per-symbol metadata.
19
+ */
20
+ fetchMarkets(): Promise<{
21
+ markets: MarketInfo[];
22
+ metadata: Map<string, TerminalAssetMetadata>;
23
+ }>;
24
+ /**
25
+ * Invalidate the internal cache so the next fetch hits the network.
26
+ */
27
+ clearCache(): void;
28
+ /**
29
+ * Log a Terminal API error to Sentry without surfacing it to the user.
30
+ *
31
+ * @param error - The caught error.
32
+ * @param method - The calling method name for context.
33
+ */
34
+ logError(error: unknown, method: string): void;
35
+ }
36
+ //# sourceMappingURL=TerminalMarketService.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TerminalMarketService.d.mts","sourceRoot":"","sources":["../../src/services/TerminalMarketService.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,UAAU,EACV,yBAAyB,EACzB,qBAAqB,EACtB,2BAAiB;AAsClB;;;;;;;;GAQG;AACH,qBAAa,qBAAqB;;gBAKpB,IAAI,EAAE,yBAAyB;IAI3C;;;;;OAKG;IACG,YAAY,IAAI,OAAO,CAAC;QAC5B,OAAO,EAAE,UAAU,EAAE,CAAC;QACtB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;KAC9C,CAAC;IAuDF;;OAEG;IACH,UAAU,IAAI,IAAI;IAmHlB;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAY/C"}
@@ -0,0 +1,191 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _TerminalMarketService_instances, _TerminalMarketService_deps, _TerminalMarketService_cache, _TerminalMarketService_validateItems, _TerminalMarketService_mapToMarketInfo, _TerminalMarketService_extractMetadata;
13
+ import { array, boolean, is, nullable, number, optional, string, type } from "@metamask/superstruct";
14
+ import { PERPS_CONSTANTS, TERMINAL_API_CONFIG } from "../constants/perpsConfig.mjs";
15
+ import { MarketCategory } from "../types/index.mjs";
16
+ import { ensureError } from "../utils/errorUtils.mjs";
17
+ const VALID_MARKET_TYPES = new Set(Object.values(MarketCategory));
18
+ /**
19
+ * Runtime validation schema for a single market item returned by
20
+ * `GET {terminalApiUrl}`.
21
+ *
22
+ * Uses `type()` (loose object matching) so that extra fields the API sends
23
+ * (e.g. `price`, `iconUrl`, `trend`) are silently accepted.
24
+ * Each item is individually validated; items that fail validation are
25
+ * filtered out and logged rather than rejecting the entire response.
26
+ */
27
+ const TerminalPerpetualItemStruct = type({
28
+ symbol: string(),
29
+ name: optional(nullable(string())),
30
+ szDecimals: optional(number()),
31
+ maxLeverage: optional(number()),
32
+ marginTableId: optional(number()),
33
+ onlyIsolated: optional(boolean()),
34
+ isDelisted: optional(boolean()),
35
+ minimumOrderSize: optional(number()),
36
+ keywords: optional(nullable(array(string()))),
37
+ tags: optional(nullable(array(string()))),
38
+ categories: optional(nullable(array(string()))),
39
+ marketType: optional(nullable(string())),
40
+ });
41
+ /**
42
+ * TerminalMarketService
43
+ *
44
+ * Fetches structured market metadata from the MetaMask Terminal API.
45
+ * Caches responses for {@link TERMINAL_API_CONFIG.CacheTtlMs} to avoid
46
+ * redundant network calls across polling cycles.
47
+ *
48
+ * Instance-based service with constructor injection of platform dependencies.
49
+ */
50
+ export class TerminalMarketService {
51
+ constructor(deps) {
52
+ _TerminalMarketService_instances.add(this);
53
+ _TerminalMarketService_deps.set(this, void 0);
54
+ _TerminalMarketService_cache.set(this, null);
55
+ __classPrivateFieldSet(this, _TerminalMarketService_deps, deps, "f");
56
+ }
57
+ /**
58
+ * Fetch markets from the Terminal API.
59
+ * Returns cached data when available and within TTL.
60
+ *
61
+ * @returns Object with mapped MarketInfo array and per-symbol metadata.
62
+ */
63
+ async fetchMarkets() {
64
+ if (__classPrivateFieldGet(this, _TerminalMarketService_cache, "f") &&
65
+ Date.now() - __classPrivateFieldGet(this, _TerminalMarketService_cache, "f").timestamp < TERMINAL_API_CONFIG.CacheTtlMs) {
66
+ return {
67
+ markets: __classPrivateFieldGet(this, _TerminalMarketService_cache, "f").markets,
68
+ metadata: __classPrivateFieldGet(this, _TerminalMarketService_cache, "f").metadata,
69
+ };
70
+ }
71
+ if (!__classPrivateFieldGet(this, _TerminalMarketService_deps, "f").terminalApiUrl) {
72
+ throw new Error('Terminal API URL not configured (terminalApiUrl is required)');
73
+ }
74
+ const url = __classPrivateFieldGet(this, _TerminalMarketService_deps, "f").terminalApiUrl;
75
+ const controller = new AbortController();
76
+ const timeoutId = setTimeout(() => controller.abort(new Error('Terminal API fetch timed out')), TERMINAL_API_CONFIG.FetchTimeoutMs);
77
+ let response;
78
+ try {
79
+ response = await fetch(url, {
80
+ method: 'GET',
81
+ headers: { 'Content-Type': 'application/json' },
82
+ signal: controller.signal,
83
+ });
84
+ }
85
+ finally {
86
+ clearTimeout(timeoutId);
87
+ }
88
+ if (!response.ok) {
89
+ throw new Error(`Terminal API returned ${String(response.status)}: ${response.statusText}`);
90
+ }
91
+ const body = await response.json();
92
+ if (!Array.isArray(body)) {
93
+ throw new Error(`Terminal API returned non-array body: ${typeof body}`);
94
+ }
95
+ const items = __classPrivateFieldGet(this, _TerminalMarketService_instances, "m", _TerminalMarketService_validateItems).call(this, body);
96
+ const markets = __classPrivateFieldGet(this, _TerminalMarketService_instances, "m", _TerminalMarketService_mapToMarketInfo).call(this, items);
97
+ const metadata = __classPrivateFieldGet(this, _TerminalMarketService_instances, "m", _TerminalMarketService_extractMetadata).call(this, items);
98
+ __classPrivateFieldSet(this, _TerminalMarketService_cache, { markets, metadata, timestamp: Date.now() }, "f");
99
+ return { markets, metadata };
100
+ }
101
+ /**
102
+ * Invalidate the internal cache so the next fetch hits the network.
103
+ */
104
+ clearCache() {
105
+ __classPrivateFieldSet(this, _TerminalMarketService_cache, null, "f");
106
+ }
107
+ /**
108
+ * Log a Terminal API error to Sentry without surfacing it to the user.
109
+ *
110
+ * @param error - The caught error.
111
+ * @param method - The calling method name for context.
112
+ */
113
+ logError(error, method) {
114
+ __classPrivateFieldGet(this, _TerminalMarketService_deps, "f").logger.error(ensureError(error, `TerminalMarketService.${method}`), {
115
+ tags: { feature: PERPS_CONSTANTS.FeatureName, source: 'terminal-api' },
116
+ context: {
117
+ name: `TerminalMarketService.${method}`,
118
+ data: { url: __classPrivateFieldGet(this, _TerminalMarketService_deps, "f").terminalApiUrl },
119
+ },
120
+ });
121
+ }
122
+ }
123
+ _TerminalMarketService_deps = new WeakMap(), _TerminalMarketService_cache = new WeakMap(), _TerminalMarketService_instances = new WeakSet(), _TerminalMarketService_validateItems = function _TerminalMarketService_validateItems(raw) {
124
+ const valid = [];
125
+ for (const item of raw) {
126
+ if (is(item, TerminalPerpetualItemStruct)) {
127
+ valid.push(item);
128
+ }
129
+ else {
130
+ __classPrivateFieldGet(this, _TerminalMarketService_deps, "f").logger.error(ensureError(new Error('Terminal API item failed schema validation'), 'TerminalMarketService.validateItems'), {
131
+ tags: {
132
+ feature: PERPS_CONSTANTS.FeatureName,
133
+ source: 'terminal-api',
134
+ },
135
+ context: {
136
+ name: 'TerminalMarketService.validateItems',
137
+ data: {
138
+ symbol: typeof item === 'object' &&
139
+ item !== null &&
140
+ Object.prototype.hasOwnProperty.call(item, 'symbol')
141
+ ? item.symbol
142
+ : undefined,
143
+ },
144
+ },
145
+ });
146
+ }
147
+ }
148
+ return valid;
149
+ }, _TerminalMarketService_mapToMarketInfo = function _TerminalMarketService_mapToMarketInfo(items) {
150
+ return items
151
+ .filter((item) => typeof item.symbol === 'string' && item.symbol.length > 0)
152
+ .map((item) => ({
153
+ name: item.symbol,
154
+ szDecimals: item.szDecimals ?? 0,
155
+ maxLeverage: item.maxLeverage ?? 1,
156
+ marginTableId: item.marginTableId ?? 0,
157
+ ...(item.onlyIsolated === true && { onlyIsolated: true }),
158
+ ...(item.isDelisted === true && { isDelisted: true }),
159
+ ...(item.minimumOrderSize !== undefined && {
160
+ minimumOrderSize: item.minimumOrderSize,
161
+ }),
162
+ }));
163
+ }, _TerminalMarketService_extractMetadata = function _TerminalMarketService_extractMetadata(items) {
164
+ const map = new Map();
165
+ for (const item of items) {
166
+ if (typeof item.symbol !== 'string' || item.symbol.length === 0) {
167
+ continue;
168
+ }
169
+ const entry = {};
170
+ if (typeof item.name === 'string' && item.name.length > 0) {
171
+ entry.name = item.name;
172
+ }
173
+ if (Array.isArray(item.keywords) && item.keywords.length > 0) {
174
+ entry.keywords = item.keywords;
175
+ }
176
+ if (Array.isArray(item.tags) && item.tags.length > 0) {
177
+ entry.tags = item.tags;
178
+ }
179
+ if (Array.isArray(item.categories) && item.categories.length > 0) {
180
+ entry.categories = item.categories;
181
+ }
182
+ if (typeof item.marketType === 'string' &&
183
+ VALID_MARKET_TYPES.has(item.marketType)) {
184
+ entry.marketType =
185
+ item.marketType;
186
+ }
187
+ map.set(item.symbol, entry);
188
+ }
189
+ return map;
190
+ };
191
+ //# sourceMappingURL=TerminalMarketService.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TerminalMarketService.mjs","sourceRoot":"","sources":["../../src/services/TerminalMarketService.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Infer } from '@metamask/superstruct';\nimport {\n array,\n boolean,\n is,\n nullable,\n number,\n optional,\n string,\n type,\n} from '@metamask/superstruct';\n\nimport { PERPS_CONSTANTS, TERMINAL_API_CONFIG } from '../constants/perpsConfig';\nimport type {\n MarketInfo,\n PerpsPlatformDependencies,\n TerminalAssetMetadata,\n} from '../types';\nimport { MarketCategory } from '../types';\nimport { ensureError } from '../utils/errorUtils';\n\nconst VALID_MARKET_TYPES = new Set<string>(Object.values(MarketCategory));\n\n/**\n * Runtime validation schema for a single market item returned by\n * `GET {terminalApiUrl}`.\n *\n * Uses `type()` (loose object matching) so that extra fields the API sends\n * (e.g. `price`, `iconUrl`, `trend`) are silently accepted.\n * Each item is individually validated; items that fail validation are\n * filtered out and logged rather than rejecting the entire response.\n */\nconst TerminalPerpetualItemStruct = type({\n symbol: string(),\n name: optional(nullable(string())),\n szDecimals: optional(number()),\n maxLeverage: optional(number()),\n marginTableId: optional(number()),\n onlyIsolated: optional(boolean()),\n isDelisted: optional(boolean()),\n minimumOrderSize: optional(number()),\n keywords: optional(nullable(array(string()))),\n tags: optional(nullable(array(string()))),\n categories: optional(nullable(array(string()))),\n marketType: optional(nullable(string())),\n});\n\ntype TerminalPerpetualItem = Infer<typeof TerminalPerpetualItemStruct>;\n\ntype CacheEntry = {\n markets: MarketInfo[];\n metadata: Map<string, TerminalAssetMetadata>;\n timestamp: number;\n};\n\n/**\n * TerminalMarketService\n *\n * Fetches structured market metadata from the MetaMask Terminal API.\n * Caches responses for {@link TERMINAL_API_CONFIG.CacheTtlMs} to avoid\n * redundant network calls across polling cycles.\n *\n * Instance-based service with constructor injection of platform dependencies.\n */\nexport class TerminalMarketService {\n readonly #deps: PerpsPlatformDependencies;\n\n #cache: CacheEntry | null = null;\n\n constructor(deps: PerpsPlatformDependencies) {\n this.#deps = deps;\n }\n\n /**\n * Fetch markets from the Terminal API.\n * Returns cached data when available and within TTL.\n *\n * @returns Object with mapped MarketInfo array and per-symbol metadata.\n */\n async fetchMarkets(): Promise<{\n markets: MarketInfo[];\n metadata: Map<string, TerminalAssetMetadata>;\n }> {\n if (\n this.#cache &&\n Date.now() - this.#cache.timestamp < TERMINAL_API_CONFIG.CacheTtlMs\n ) {\n return {\n markets: this.#cache.markets,\n metadata: this.#cache.metadata,\n };\n }\n\n if (!this.#deps.terminalApiUrl) {\n throw new Error(\n 'Terminal API URL not configured (terminalApiUrl is required)',\n );\n }\n\n const url = this.#deps.terminalApiUrl;\n const controller = new AbortController();\n const timeoutId = setTimeout(\n () => controller.abort(new Error('Terminal API fetch timed out')),\n TERMINAL_API_CONFIG.FetchTimeoutMs,\n );\n\n let response: Response;\n try {\n response = await fetch(url, {\n method: 'GET',\n headers: { 'Content-Type': 'application/json' },\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timeoutId);\n }\n\n if (!response.ok) {\n throw new Error(\n `Terminal API returned ${String(response.status)}: ${response.statusText}`,\n );\n }\n\n const body: unknown = await response.json();\n\n if (!Array.isArray(body)) {\n throw new Error(`Terminal API returned non-array body: ${typeof body}`);\n }\n\n const items = this.#validateItems(body);\n const markets = this.#mapToMarketInfo(items);\n const metadata = this.#extractMetadata(items);\n\n this.#cache = { markets, metadata, timestamp: Date.now() };\n return { markets, metadata };\n }\n\n /**\n * Invalidate the internal cache so the next fetch hits the network.\n */\n clearCache(): void {\n this.#cache = null;\n }\n\n /**\n * Validate raw API response items against the expected schema.\n * Items that fail validation are filtered out and logged rather than\n * rejecting the entire response.\n *\n * @param raw - The raw array from the API response body.\n * @returns Array of validated items.\n */\n #validateItems(raw: unknown[]): TerminalPerpetualItem[] {\n const valid: TerminalPerpetualItem[] = [];\n for (const item of raw) {\n if (is(item, TerminalPerpetualItemStruct)) {\n valid.push(item);\n } else {\n this.#deps.logger.error(\n ensureError(\n new Error('Terminal API item failed schema validation'),\n 'TerminalMarketService.validateItems',\n ),\n {\n tags: {\n feature: PERPS_CONSTANTS.FeatureName,\n source: 'terminal-api',\n },\n context: {\n name: 'TerminalMarketService.validateItems',\n data: {\n symbol:\n typeof item === 'object' &&\n item !== null &&\n Object.prototype.hasOwnProperty.call(item, 'symbol')\n ? (item as Record<string, unknown>).symbol\n : undefined,\n },\n },\n },\n );\n }\n }\n return valid;\n }\n\n /**\n * Map Terminal API items to the protocol-agnostic MarketInfo shape.\n *\n * @param items - Raw items from the API response.\n * @returns Array of MarketInfo objects.\n */\n #mapToMarketInfo(items: TerminalPerpetualItem[]): MarketInfo[] {\n return items\n .filter(\n (item) => typeof item.symbol === 'string' && item.symbol.length > 0,\n )\n .map((item) => ({\n name: item.symbol,\n szDecimals: item.szDecimals ?? 0,\n maxLeverage: item.maxLeverage ?? 1,\n marginTableId: item.marginTableId ?? 0,\n ...(item.onlyIsolated === true && { onlyIsolated: true as const }),\n ...(item.isDelisted === true && { isDelisted: true as const }),\n ...(item.minimumOrderSize !== undefined && {\n minimumOrderSize: item.minimumOrderSize,\n }),\n }));\n }\n\n /**\n * Extract per-symbol metadata for downstream merge into PerpsMarketData.\n *\n * @param items - Raw items from the API response.\n * @returns Map keyed by symbol with enrichment metadata.\n */\n #extractMetadata(\n items: TerminalPerpetualItem[],\n ): Map<string, TerminalAssetMetadata> {\n const map = new Map<string, TerminalAssetMetadata>();\n\n for (const item of items) {\n if (typeof item.symbol !== 'string' || item.symbol.length === 0) {\n continue;\n }\n\n const entry: TerminalAssetMetadata = {};\n\n if (typeof item.name === 'string' && item.name.length > 0) {\n entry.name = item.name;\n }\n\n if (Array.isArray(item.keywords) && item.keywords.length > 0) {\n entry.keywords = item.keywords;\n }\n if (Array.isArray(item.tags) && item.tags.length > 0) {\n entry.tags = item.tags;\n }\n if (Array.isArray(item.categories) && item.categories.length > 0) {\n entry.categories = item.categories;\n }\n if (\n typeof item.marketType === 'string' &&\n VALID_MARKET_TYPES.has(item.marketType)\n ) {\n entry.marketType =\n item.marketType as TerminalAssetMetadata['marketType'];\n }\n\n map.set(item.symbol, entry);\n }\n\n return map;\n }\n\n /**\n * Log a Terminal API error to Sentry without surfacing it to the user.\n *\n * @param error - The caught error.\n * @param method - The calling method name for context.\n */\n logError(error: unknown, method: string): void {\n this.#deps.logger.error(\n ensureError(error, `TerminalMarketService.${method}`),\n {\n tags: { feature: PERPS_CONSTANTS.FeatureName, source: 'terminal-api' },\n context: {\n name: `TerminalMarketService.${method}`,\n data: { url: this.#deps.terminalApiUrl },\n },\n },\n );\n }\n}\n"]}