perp-cli 0.3.3

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 (325) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -0
  3. package/dist/__tests__/alert-logic.test.d.ts +1 -0
  4. package/dist/__tests__/alert-logic.test.js +107 -0
  5. package/dist/__tests__/arb-auto-3dex.test.d.ts +1 -0
  6. package/dist/__tests__/arb-auto-3dex.test.js +397 -0
  7. package/dist/__tests__/arb-history-stats.test.d.ts +1 -0
  8. package/dist/__tests__/arb-history-stats.test.js +176 -0
  9. package/dist/__tests__/arb-logic.test.d.ts +1 -0
  10. package/dist/__tests__/arb-logic.test.js +84 -0
  11. package/dist/__tests__/arb-manage.test.d.ts +1 -0
  12. package/dist/__tests__/arb-manage.test.js +253 -0
  13. package/dist/__tests__/arb-new-features.test.d.ts +1 -0
  14. package/dist/__tests__/arb-new-features.test.js +457 -0
  15. package/dist/__tests__/arb-sizing.test.d.ts +1 -0
  16. package/dist/__tests__/arb-sizing.test.js +48 -0
  17. package/dist/__tests__/arb-state.test.d.ts +1 -0
  18. package/dist/__tests__/arb-state.test.js +284 -0
  19. package/dist/__tests__/arb-userflow.test.d.ts +1 -0
  20. package/dist/__tests__/arb-userflow.test.js +945 -0
  21. package/dist/__tests__/arb-utils.test.d.ts +1 -0
  22. package/dist/__tests__/arb-utils.test.js +264 -0
  23. package/dist/__tests__/bot-conditions.test.d.ts +1 -0
  24. package/dist/__tests__/bot-conditions.test.js +341 -0
  25. package/dist/__tests__/client-id-tracker.test.d.ts +1 -0
  26. package/dist/__tests__/client-id-tracker.test.js +137 -0
  27. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +1 -0
  28. package/dist/__tests__/commands/new-atomic-commands.test.js +502 -0
  29. package/dist/__tests__/commands/order-intent.test.d.ts +1 -0
  30. package/dist/__tests__/commands/order-intent.test.js +600 -0
  31. package/dist/__tests__/commands/trade-commands.test.d.ts +1 -0
  32. package/dist/__tests__/commands/trade-commands.test.js +821 -0
  33. package/dist/__tests__/config.test.d.ts +1 -0
  34. package/dist/__tests__/config.test.js +86 -0
  35. package/dist/__tests__/cross-chain-margin.test.d.ts +1 -0
  36. package/dist/__tests__/cross-chain-margin.test.js +287 -0
  37. package/dist/__tests__/dex-asset-map.test.d.ts +1 -0
  38. package/dist/__tests__/dex-asset-map.test.js +191 -0
  39. package/dist/__tests__/errors.test.d.ts +1 -0
  40. package/dist/__tests__/errors.test.js +110 -0
  41. package/dist/__tests__/event-stream.test.d.ts +1 -0
  42. package/dist/__tests__/event-stream.test.js +276 -0
  43. package/dist/__tests__/exchanges/interface.test.d.ts +1 -0
  44. package/dist/__tests__/exchanges/interface.test.js +132 -0
  45. package/dist/__tests__/exchanges/mock-adapter.d.ts +69 -0
  46. package/dist/__tests__/exchanges/mock-adapter.js +137 -0
  47. package/dist/__tests__/execution-log.test.d.ts +1 -0
  48. package/dist/__tests__/execution-log.test.js +106 -0
  49. package/dist/__tests__/funding-calc.test.d.ts +1 -0
  50. package/dist/__tests__/funding-calc.test.js +71 -0
  51. package/dist/__tests__/funding-history.test.d.ts +1 -0
  52. package/dist/__tests__/funding-history.test.js +343 -0
  53. package/dist/__tests__/funding-rates.test.d.ts +1 -0
  54. package/dist/__tests__/funding-rates.test.js +342 -0
  55. package/dist/__tests__/funding.test.d.ts +1 -0
  56. package/dist/__tests__/funding.test.js +173 -0
  57. package/dist/__tests__/gap-logic.test.d.ts +1 -0
  58. package/dist/__tests__/gap-logic.test.js +43 -0
  59. package/dist/__tests__/hip3-dex.test.d.ts +1 -0
  60. package/dist/__tests__/hip3-dex.test.js +234 -0
  61. package/dist/__tests__/integration/agent-features.integration.test.d.ts +1 -0
  62. package/dist/__tests__/integration/agent-features.integration.test.js +553 -0
  63. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +13 -0
  64. package/dist/__tests__/integration/atomic-commands.integration.test.js +246 -0
  65. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +1 -0
  66. package/dist/__tests__/integration/bridge-simulation.integration.test.js +453 -0
  67. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +1 -0
  68. package/dist/__tests__/integration/bridge-strict.integration.test.js +812 -0
  69. package/dist/__tests__/integration/bridge.integration.test.d.ts +1 -0
  70. package/dist/__tests__/integration/bridge.integration.test.js +309 -0
  71. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +1 -0
  72. package/dist/__tests__/integration/cli-e2e.integration.test.js +202 -0
  73. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +1 -0
  74. package/dist/__tests__/integration/dex-arb.integration.test.js +116 -0
  75. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +13 -0
  76. package/dist/__tests__/integration/envelope-consistency.integration.test.js +205 -0
  77. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +1 -0
  78. package/dist/__tests__/integration/hip3-dex.integration.test.js +147 -0
  79. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +1 -0
  80. package/dist/__tests__/integration/hyperliquid.integration.test.js +79 -0
  81. package/dist/__tests__/integration/lighter.integration.test.d.ts +1 -0
  82. package/dist/__tests__/integration/lighter.integration.test.js +53 -0
  83. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +9 -0
  84. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +236 -0
  85. package/dist/__tests__/integration/order-verification.integration.test.d.ts +1 -0
  86. package/dist/__tests__/integration/order-verification.integration.test.js +321 -0
  87. package/dist/__tests__/integration/pacifica.integration.test.d.ts +1 -0
  88. package/dist/__tests__/integration/pacifica.integration.test.js +75 -0
  89. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +1 -0
  90. package/dist/__tests__/integration/response-shapes.integration.test.js +278 -0
  91. package/dist/__tests__/liquidity.test.d.ts +1 -0
  92. package/dist/__tests__/liquidity.test.js +225 -0
  93. package/dist/__tests__/plan-executor.test.d.ts +1 -0
  94. package/dist/__tests__/plan-executor.test.js +314 -0
  95. package/dist/__tests__/position-history.test.d.ts +1 -0
  96. package/dist/__tests__/position-history.test.js +367 -0
  97. package/dist/__tests__/retry.test.d.ts +1 -0
  98. package/dist/__tests__/retry.test.js +310 -0
  99. package/dist/__tests__/risk-assessment.test.d.ts +1 -0
  100. package/dist/__tests__/risk-assessment.test.js +145 -0
  101. package/dist/__tests__/security-adversarial.test.d.ts +1 -0
  102. package/dist/__tests__/security-adversarial.test.js +574 -0
  103. package/dist/__tests__/strategies.test.d.ts +1 -0
  104. package/dist/__tests__/strategies.test.js +539 -0
  105. package/dist/__tests__/trade-execution.test.d.ts +1 -0
  106. package/dist/__tests__/trade-execution.test.js +129 -0
  107. package/dist/__tests__/trade-validator.test.d.ts +1 -0
  108. package/dist/__tests__/trade-validator.test.js +655 -0
  109. package/dist/__tests__/utils.test.d.ts +1 -0
  110. package/dist/__tests__/utils.test.js +76 -0
  111. package/dist/api/public/hyperliquid.d.ts +18 -0
  112. package/dist/api/public/hyperliquid.js +82 -0
  113. package/dist/api/public/index.d.ts +8 -0
  114. package/dist/api/public/index.js +8 -0
  115. package/dist/api/public/lighter.d.ts +24 -0
  116. package/dist/api/public/lighter.js +100 -0
  117. package/dist/api/public/pacifica.d.ts +17 -0
  118. package/dist/api/public/pacifica.js +54 -0
  119. package/dist/api/public/urls.d.ts +12 -0
  120. package/dist/api/public/urls.js +33 -0
  121. package/dist/arb/history-stats.d.ts +44 -0
  122. package/dist/arb/history-stats.js +135 -0
  123. package/dist/arb/index.d.ts +4 -0
  124. package/dist/arb/index.js +4 -0
  125. package/dist/arb/sizing.d.ts +23 -0
  126. package/dist/arb/sizing.js +96 -0
  127. package/dist/arb/state.d.ts +51 -0
  128. package/dist/arb/state.js +112 -0
  129. package/dist/arb/utils.d.ts +81 -0
  130. package/dist/arb/utils.js +267 -0
  131. package/dist/arb-history-stats.d.ts +5 -0
  132. package/dist/arb-history-stats.js +5 -0
  133. package/dist/arb-sizing.d.ts +5 -0
  134. package/dist/arb-sizing.js +5 -0
  135. package/dist/arb-state.d.ts +5 -0
  136. package/dist/arb-state.js +5 -0
  137. package/dist/arb-utils.d.ts +5 -0
  138. package/dist/arb-utils.js +5 -0
  139. package/dist/bot/conditions.d.ts +32 -0
  140. package/dist/bot/conditions.js +141 -0
  141. package/dist/bot/config.d.ts +76 -0
  142. package/dist/bot/config.js +160 -0
  143. package/dist/bot/engine.d.ts +8 -0
  144. package/dist/bot/engine.js +519 -0
  145. package/dist/bot/presets.d.ts +11 -0
  146. package/dist/bot/presets.js +296 -0
  147. package/dist/bridge-engine.d.ts +133 -0
  148. package/dist/bridge-engine.js +1487 -0
  149. package/dist/cache.d.ts +25 -0
  150. package/dist/cache.js +99 -0
  151. package/dist/cli-spec.d.ts +50 -0
  152. package/dist/cli-spec.js +75 -0
  153. package/dist/client-id-tracker.d.ts +25 -0
  154. package/dist/client-id-tracker.js +76 -0
  155. package/dist/commands/account.d.ts +3 -0
  156. package/dist/commands/account.js +425 -0
  157. package/dist/commands/agent.d.ts +3 -0
  158. package/dist/commands/agent.js +386 -0
  159. package/dist/commands/alert.d.ts +2 -0
  160. package/dist/commands/alert.js +421 -0
  161. package/dist/commands/analytics.d.ts +3 -0
  162. package/dist/commands/analytics.js +311 -0
  163. package/dist/commands/arb/index.d.ts +3 -0
  164. package/dist/commands/arb/index.js +921 -0
  165. package/dist/commands/arb-auto.d.ts +54 -0
  166. package/dist/commands/arb-auto.js +1328 -0
  167. package/dist/commands/arb-manage.d.ts +5 -0
  168. package/dist/commands/arb-manage.js +5 -0
  169. package/dist/commands/arb.d.ts +2 -0
  170. package/dist/commands/arb.js +347 -0
  171. package/dist/commands/backtest.d.ts +2 -0
  172. package/dist/commands/backtest.js +327 -0
  173. package/dist/commands/bot.d.ts +3 -0
  174. package/dist/commands/bot.js +412 -0
  175. package/dist/commands/bridge.d.ts +2 -0
  176. package/dist/commands/bridge.js +396 -0
  177. package/dist/commands/dashboard.d.ts +3 -0
  178. package/dist/commands/dashboard.js +176 -0
  179. package/dist/commands/deposit.d.ts +4 -0
  180. package/dist/commands/deposit.js +573 -0
  181. package/dist/commands/dex.d.ts +3 -0
  182. package/dist/commands/dex.js +114 -0
  183. package/dist/commands/env.d.ts +2 -0
  184. package/dist/commands/env.js +136 -0
  185. package/dist/commands/funding.d.ts +2 -0
  186. package/dist/commands/funding.js +347 -0
  187. package/dist/commands/gap.d.ts +2 -0
  188. package/dist/commands/gap.js +305 -0
  189. package/dist/commands/health.d.ts +2 -0
  190. package/dist/commands/health.js +67 -0
  191. package/dist/commands/history.d.ts +2 -0
  192. package/dist/commands/history.js +235 -0
  193. package/dist/commands/init.d.ts +15 -0
  194. package/dist/commands/init.js +266 -0
  195. package/dist/commands/jobs.d.ts +2 -0
  196. package/dist/commands/jobs.js +133 -0
  197. package/dist/commands/manage.d.ts +4 -0
  198. package/dist/commands/manage.js +309 -0
  199. package/dist/commands/market.d.ts +3 -0
  200. package/dist/commands/market.js +225 -0
  201. package/dist/commands/plan.d.ts +3 -0
  202. package/dist/commands/plan.js +95 -0
  203. package/dist/commands/portfolio.d.ts +3 -0
  204. package/dist/commands/portfolio.js +169 -0
  205. package/dist/commands/rebalance.d.ts +3 -0
  206. package/dist/commands/rebalance.js +293 -0
  207. package/dist/commands/risk.d.ts +3 -0
  208. package/dist/commands/risk.js +169 -0
  209. package/dist/commands/run.d.ts +3 -0
  210. package/dist/commands/run.js +202 -0
  211. package/dist/commands/settings.d.ts +2 -0
  212. package/dist/commands/settings.js +102 -0
  213. package/dist/commands/stream.d.ts +5 -0
  214. package/dist/commands/stream.js +123 -0
  215. package/dist/commands/trade.d.ts +3 -0
  216. package/dist/commands/trade.js +1273 -0
  217. package/dist/commands/wallet.d.ts +14 -0
  218. package/dist/commands/wallet.js +602 -0
  219. package/dist/commands/withdraw.d.ts +3 -0
  220. package/dist/commands/withdraw.js +187 -0
  221. package/dist/config.d.ts +5 -0
  222. package/dist/config.js +68 -0
  223. package/dist/cross-chain-margin.d.ts +46 -0
  224. package/dist/cross-chain-margin.js +107 -0
  225. package/dist/dashboard/server.d.ts +80 -0
  226. package/dist/dashboard/server.js +340 -0
  227. package/dist/dashboard/ui.d.ts +4 -0
  228. package/dist/dashboard/ui.js +538 -0
  229. package/dist/dashboard/ws-feeds.d.ts +29 -0
  230. package/dist/dashboard/ws-feeds.js +660 -0
  231. package/dist/dex-asset-map.d.ts +80 -0
  232. package/dist/dex-asset-map.js +201 -0
  233. package/dist/errors.d.ts +109 -0
  234. package/dist/errors.js +84 -0
  235. package/dist/event-stream.d.ts +25 -0
  236. package/dist/event-stream.js +168 -0
  237. package/dist/exchanges/hyperliquid.d.ts +212 -0
  238. package/dist/exchanges/hyperliquid.js +931 -0
  239. package/dist/exchanges/interface.d.ts +95 -0
  240. package/dist/exchanges/interface.js +5 -0
  241. package/dist/exchanges/lighter.d.ts +159 -0
  242. package/dist/exchanges/lighter.js +793 -0
  243. package/dist/exchanges/pacifica.d.ts +51 -0
  244. package/dist/exchanges/pacifica.js +248 -0
  245. package/dist/execution-log.d.ts +36 -0
  246. package/dist/execution-log.js +102 -0
  247. package/dist/funding/history.d.ts +63 -0
  248. package/dist/funding/history.js +266 -0
  249. package/dist/funding/index.d.ts +3 -0
  250. package/dist/funding/index.js +3 -0
  251. package/dist/funding/normalize.d.ts +39 -0
  252. package/dist/funding/normalize.js +66 -0
  253. package/dist/funding/rates.d.ts +45 -0
  254. package/dist/funding/rates.js +172 -0
  255. package/dist/funding-history.d.ts +5 -0
  256. package/dist/funding-history.js +5 -0
  257. package/dist/funding-rates.d.ts +5 -0
  258. package/dist/funding-rates.js +5 -0
  259. package/dist/funding.d.ts +5 -0
  260. package/dist/funding.js +5 -0
  261. package/dist/index.d.ts +2 -0
  262. package/dist/index.js +458 -0
  263. package/dist/jobs.d.ts +37 -0
  264. package/dist/jobs.js +152 -0
  265. package/dist/liquidity.d.ts +34 -0
  266. package/dist/liquidity.js +100 -0
  267. package/dist/mcp-server.d.ts +9 -0
  268. package/dist/mcp-server.js +1206 -0
  269. package/dist/pacifica/client.d.ts +111 -0
  270. package/dist/pacifica/client.js +310 -0
  271. package/dist/pacifica/constants.d.ts +27 -0
  272. package/dist/pacifica/constants.js +47 -0
  273. package/dist/pacifica/deposit.d.ts +14 -0
  274. package/dist/pacifica/deposit.js +78 -0
  275. package/dist/pacifica/index.d.ts +6 -0
  276. package/dist/pacifica/index.js +11 -0
  277. package/dist/pacifica/signing.d.ts +49 -0
  278. package/dist/pacifica/signing.js +97 -0
  279. package/dist/pacifica/types/account.d.ts +42 -0
  280. package/dist/pacifica/types/account.js +1 -0
  281. package/dist/pacifica/types/index.d.ts +6 -0
  282. package/dist/pacifica/types/index.js +6 -0
  283. package/dist/pacifica/types/lake.d.ts +18 -0
  284. package/dist/pacifica/types/lake.js +1 -0
  285. package/dist/pacifica/types/market.d.ts +64 -0
  286. package/dist/pacifica/types/market.js +1 -0
  287. package/dist/pacifica/types/order.d.ts +92 -0
  288. package/dist/pacifica/types/order.js +1 -0
  289. package/dist/pacifica/types/position.d.ts +25 -0
  290. package/dist/pacifica/types/position.js +1 -0
  291. package/dist/pacifica/types/ws.d.ts +34 -0
  292. package/dist/pacifica/types/ws.js +41 -0
  293. package/dist/pacifica/ws-client.d.ts +42 -0
  294. package/dist/pacifica/ws-client.js +180 -0
  295. package/dist/plan-executor.d.ts +48 -0
  296. package/dist/plan-executor.js +280 -0
  297. package/dist/position-history.d.ts +68 -0
  298. package/dist/position-history.js +222 -0
  299. package/dist/rebalance.d.ts +64 -0
  300. package/dist/rebalance.js +142 -0
  301. package/dist/retry.d.ts +74 -0
  302. package/dist/retry.js +129 -0
  303. package/dist/risk.d.ts +48 -0
  304. package/dist/risk.js +156 -0
  305. package/dist/settings.d.ts +19 -0
  306. package/dist/settings.js +45 -0
  307. package/dist/shared-api.d.ts +5 -0
  308. package/dist/shared-api.js +5 -0
  309. package/dist/strategies/dca.d.ts +25 -0
  310. package/dist/strategies/dca.js +114 -0
  311. package/dist/strategies/funding-arb.d.ts +15 -0
  312. package/dist/strategies/funding-arb.js +281 -0
  313. package/dist/strategies/grid.d.ts +34 -0
  314. package/dist/strategies/grid.js +185 -0
  315. package/dist/strategies/trailing-stop.d.ts +17 -0
  316. package/dist/strategies/trailing-stop.js +121 -0
  317. package/dist/strategies/twap.d.ts +20 -0
  318. package/dist/strategies/twap.js +78 -0
  319. package/dist/trade-validator.d.ts +39 -0
  320. package/dist/trade-validator.js +154 -0
  321. package/dist/utils.d.ts +38 -0
  322. package/dist/utils.js +110 -0
  323. package/package.json +63 -0
  324. package/skills/perp-cli/SKILL.md +149 -0
  325. package/skills/perp-cli/references/commands.md +143 -0
@@ -0,0 +1,655 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { validateTrade } from "../trade-validator.js";
3
+ // Mock the dynamic import of risk.js to avoid file I/O
4
+ vi.mock("../risk.js", () => ({
5
+ assessRisk: vi.fn().mockReturnValue({
6
+ level: "low",
7
+ violations: [],
8
+ metrics: {
9
+ totalEquity: 1000,
10
+ totalUnrealizedPnl: 0,
11
+ totalMarginUsed: 200,
12
+ totalExposure: 500,
13
+ positionCount: 0,
14
+ marginUtilization: 20,
15
+ largestPositionUsd: 0,
16
+ maxLeverageUsed: 0,
17
+ },
18
+ limits: {
19
+ maxDrawdownUsd: 500,
20
+ maxPositionUsd: 5000,
21
+ maxTotalExposureUsd: 20000,
22
+ dailyLossLimitUsd: 200,
23
+ maxPositions: 10,
24
+ maxLeverage: 20,
25
+ maxMarginUtilization: 80,
26
+ },
27
+ canTrade: true,
28
+ }),
29
+ preTradeCheck: vi.fn().mockReturnValue({ allowed: true }),
30
+ }));
31
+ // ── Mock adapter factory ──
32
+ function mockAdapter(overrides) {
33
+ return {
34
+ name: "test-exchange",
35
+ getMarkets: vi.fn().mockResolvedValue([
36
+ {
37
+ symbol: "BTC-PERP",
38
+ markPrice: "60000",
39
+ indexPrice: "60000",
40
+ fundingRate: "0.0001",
41
+ volume24h: "1000000",
42
+ openInterest: "500000",
43
+ maxLeverage: 20,
44
+ },
45
+ {
46
+ symbol: "ETH-PERP",
47
+ markPrice: "3000",
48
+ indexPrice: "3000",
49
+ fundingRate: "0.00005",
50
+ volume24h: "500000",
51
+ openInterest: "250000",
52
+ maxLeverage: 15,
53
+ },
54
+ ]),
55
+ getBalance: vi.fn().mockResolvedValue({
56
+ equity: "10000",
57
+ available: "8000",
58
+ marginUsed: "2000",
59
+ unrealizedPnl: "0",
60
+ }),
61
+ getPositions: vi.fn().mockResolvedValue([]),
62
+ getOrderbook: vi.fn().mockResolvedValue({
63
+ bids: [
64
+ ["59990", "1"],
65
+ ["59980", "2"],
66
+ ["59970", "3"],
67
+ ],
68
+ asks: [
69
+ ["60010", "1"],
70
+ ["60020", "2"],
71
+ ["60030", "3"],
72
+ ],
73
+ }),
74
+ getOpenOrders: vi.fn().mockResolvedValue([]),
75
+ getOrderHistory: vi.fn().mockResolvedValue([]),
76
+ getTradeHistory: vi.fn().mockResolvedValue([]),
77
+ getRecentTrades: vi.fn().mockResolvedValue([]),
78
+ getFundingHistory: vi.fn().mockResolvedValue([]),
79
+ getFundingPayments: vi.fn().mockResolvedValue([]),
80
+ getKlines: vi.fn().mockResolvedValue([]),
81
+ marketOrder: vi.fn().mockResolvedValue({ orderId: "m1" }),
82
+ limitOrder: vi.fn().mockResolvedValue({ orderId: "l1" }),
83
+ editOrder: vi.fn().mockResolvedValue({}),
84
+ cancelOrder: vi.fn().mockResolvedValue({}),
85
+ cancelAllOrders: vi.fn().mockResolvedValue({}),
86
+ setLeverage: vi.fn().mockResolvedValue({}),
87
+ stopOrder: vi.fn().mockResolvedValue({}),
88
+ ...overrides,
89
+ };
90
+ }
91
+ // ──────────────────────────────────────────────
92
+ // 1. Symbol Validity
93
+ // ──────────────────────────────────────────────
94
+ describe("validateTrade — symbol validity", () => {
95
+ it("passes when symbol is found exactly", async () => {
96
+ const adapter = mockAdapter();
97
+ const result = await validateTrade(adapter, {
98
+ symbol: "BTC-PERP",
99
+ side: "buy",
100
+ size: 0.01,
101
+ });
102
+ const symbolCheck = result.checks.find(c => c.check === "symbol_valid");
103
+ expect(symbolCheck?.passed).toBe(true);
104
+ expect(result.valid).toBe(true);
105
+ });
106
+ it("matches BTC to BTC-PERP (suffix matching)", async () => {
107
+ const adapter = mockAdapter();
108
+ const result = await validateTrade(adapter, {
109
+ symbol: "BTC",
110
+ side: "buy",
111
+ size: 0.01,
112
+ });
113
+ const symbolCheck = result.checks.find(c => c.check === "symbol_valid");
114
+ expect(symbolCheck?.passed).toBe(true);
115
+ expect(symbolCheck?.message).toContain("BTC");
116
+ });
117
+ it("matches btc case-insensitively to BTC-PERP", async () => {
118
+ const adapter = mockAdapter();
119
+ const result = await validateTrade(adapter, {
120
+ symbol: "btc",
121
+ side: "buy",
122
+ size: 0.01,
123
+ });
124
+ const symbolCheck = result.checks.find(c => c.check === "symbol_valid");
125
+ expect(symbolCheck?.passed).toBe(true);
126
+ });
127
+ it("returns invalid early when symbol is not found", async () => {
128
+ const adapter = mockAdapter();
129
+ const result = await validateTrade(adapter, {
130
+ symbol: "DOGE",
131
+ side: "buy",
132
+ size: 1,
133
+ });
134
+ expect(result.valid).toBe(false);
135
+ const symbolCheck = result.checks.find(c => c.check === "symbol_valid");
136
+ expect(symbolCheck?.passed).toBe(false);
137
+ expect(symbolCheck?.message).toContain("DOGE");
138
+ // Should return early — only one check
139
+ expect(result.checks).toHaveLength(1);
140
+ expect(result.estimatedCost).toBeUndefined();
141
+ });
142
+ it("handles -PERP suffix stripping (input=BTC-PERP, market=BTC-PERP)", async () => {
143
+ const adapter = mockAdapter({
144
+ getMarkets: vi.fn().mockResolvedValue([
145
+ { symbol: "BTC-PERP", markPrice: "60000", indexPrice: "60000", fundingRate: "0.0001", volume24h: "1000000", openInterest: "500000", maxLeverage: 20 },
146
+ ]),
147
+ });
148
+ const result = await validateTrade(adapter, {
149
+ symbol: "BTC-PERP",
150
+ side: "buy",
151
+ size: 0.01,
152
+ });
153
+ expect(result.checks.find(c => c.check === "symbol_valid")?.passed).toBe(true);
154
+ });
155
+ it("handles market without -PERP suffix matching input with -PERP suffix", async () => {
156
+ // Market returns "BTC", user queries "BTC-PERP"
157
+ const adapter = mockAdapter({
158
+ getMarkets: vi.fn().mockResolvedValue([
159
+ { symbol: "BTC", markPrice: "60000", indexPrice: "60000", fundingRate: "0.0001", volume24h: "1000000", openInterest: "500000", maxLeverage: 20 },
160
+ ]),
161
+ });
162
+ // The code logic: ms === sym → "BTC" === "BTC-PERP" (no), ms === `${sym}-PERP` → "BTC" === "BTC-PERP-PERP" (no), ms.replace(/-PERP$/, "") === sym → "BTC" === "BTC-PERP" (no)
163
+ // So BTC-PERP input won't match "BTC" market. This tests the current behavior.
164
+ const result = await validateTrade(adapter, {
165
+ symbol: "BTC-PERP",
166
+ side: "buy",
167
+ size: 0.01,
168
+ });
169
+ // "BTC" won't match "BTC-PERP" — ms=BTC, sym=BTC-PERP: ms !== sym, `${sym}-PERP`="BTC-PERP-PERP" !== "BTC", ms.replace(/-PERP$/,"")="BTC" !== "BTC-PERP"
170
+ expect(result.checks.find(c => c.check === "symbol_valid")?.passed).toBe(false);
171
+ });
172
+ });
173
+ // ──────────────────────────────────────────────
174
+ // 2. Balance Checks
175
+ // ──────────────────────────────────────────────
176
+ describe("validateTrade — balance check", () => {
177
+ it("passes when balance is sufficient", async () => {
178
+ const adapter = mockAdapter();
179
+ // BTC at 60000, size 0.01 = $600 notional, at 20x leverage = $30 margin
180
+ const result = await validateTrade(adapter, {
181
+ symbol: "BTC",
182
+ side: "buy",
183
+ size: 0.01,
184
+ });
185
+ const balCheck = result.checks.find(c => c.check === "balance_sufficient");
186
+ expect(balCheck?.passed).toBe(true);
187
+ expect(balCheck?.message).toContain(">=");
188
+ });
189
+ it("fails when balance is insufficient", async () => {
190
+ const adapter = mockAdapter({
191
+ getBalance: vi.fn().mockResolvedValue({
192
+ equity: "100",
193
+ available: "10",
194
+ marginUsed: "90",
195
+ unrealizedPnl: "0",
196
+ }),
197
+ });
198
+ // BTC at 60000, size 1 = $60000 notional, at 20x = $3000 margin. Available is $10
199
+ const result = await validateTrade(adapter, {
200
+ symbol: "BTC",
201
+ side: "buy",
202
+ size: 1,
203
+ });
204
+ const balCheck = result.checks.find(c => c.check === "balance_sufficient");
205
+ expect(balCheck?.passed).toBe(false);
206
+ expect(balCheck?.message).toContain("Insufficient");
207
+ });
208
+ it("skips balance check for reduce-only orders (always passes)", async () => {
209
+ const adapter = mockAdapter({
210
+ getBalance: vi.fn().mockResolvedValue({
211
+ equity: "1",
212
+ available: "0",
213
+ marginUsed: "1",
214
+ unrealizedPnl: "0",
215
+ }),
216
+ getPositions: vi.fn().mockResolvedValue([
217
+ { symbol: "BTC-PERP", side: "long", size: "1", entryPrice: "60000", markPrice: "60000", liquidationPrice: "50000", unrealizedPnl: "0", leverage: 10 },
218
+ ]),
219
+ });
220
+ const result = await validateTrade(adapter, {
221
+ symbol: "BTC",
222
+ side: "sell",
223
+ size: 0.5,
224
+ reduceOnly: true,
225
+ });
226
+ const balCheck = result.checks.find(c => c.check === "balance_sufficient");
227
+ expect(balCheck?.passed).toBe(true);
228
+ expect(balCheck?.message).toContain("Reduce-only");
229
+ });
230
+ it("uses custom leverage for margin calculation", async () => {
231
+ const adapter = mockAdapter({
232
+ getBalance: vi.fn().mockResolvedValue({
233
+ equity: "1000",
234
+ available: "500",
235
+ marginUsed: "500",
236
+ unrealizedPnl: "0",
237
+ }),
238
+ });
239
+ // BTC at 60000, size 0.1 = $6000 notional. At 2x leverage = $3000 margin > $500 available
240
+ const result = await validateTrade(adapter, {
241
+ symbol: "BTC",
242
+ side: "buy",
243
+ size: 0.1,
244
+ leverage: 2,
245
+ });
246
+ const balCheck = result.checks.find(c => c.check === "balance_sufficient");
247
+ expect(balCheck?.passed).toBe(false);
248
+ });
249
+ });
250
+ // ──────────────────────────────────────────────
251
+ // 3. Price Freshness
252
+ // ──────────────────────────────────────────────
253
+ describe("validateTrade — price freshness", () => {
254
+ it("passes when limit price is within 3% of mark", async () => {
255
+ const adapter = mockAdapter();
256
+ // Mark = 60000, price = 60500 → deviation = 500/60000 = 0.83%
257
+ const result = await validateTrade(adapter, {
258
+ symbol: "BTC",
259
+ side: "buy",
260
+ size: 0.01,
261
+ price: 60500,
262
+ type: "limit",
263
+ });
264
+ const priceCheck = result.checks.find(c => c.check === "price_fresh");
265
+ expect(priceCheck?.passed).toBe(true);
266
+ expect(priceCheck?.message).toContain("within normal range");
267
+ });
268
+ it("passes with warning when price deviates 3–10% from mark", async () => {
269
+ const adapter = mockAdapter();
270
+ // Mark = 60000, price = 63000 → deviation = 3000/60000 = 5%
271
+ const result = await validateTrade(adapter, {
272
+ symbol: "BTC",
273
+ side: "buy",
274
+ size: 0.01,
275
+ price: 63000,
276
+ type: "limit",
277
+ });
278
+ const priceCheck = result.checks.find(c => c.check === "price_fresh");
279
+ expect(priceCheck?.passed).toBe(true);
280
+ expect(priceCheck?.message).toContain("5.0%");
281
+ expect(result.warnings.length).toBeGreaterThan(0);
282
+ expect(result.warnings.some(w => w.includes("5.0%"))).toBe(true);
283
+ });
284
+ it("fails when price deviates more than 10% from mark", async () => {
285
+ const adapter = mockAdapter();
286
+ // Mark = 60000, price = 70000 → deviation = 10000/60000 = 16.7%
287
+ const result = await validateTrade(adapter, {
288
+ symbol: "BTC",
289
+ side: "buy",
290
+ size: 0.01,
291
+ price: 70000,
292
+ type: "limit",
293
+ });
294
+ const priceCheck = result.checks.find(c => c.check === "price_fresh");
295
+ expect(priceCheck?.passed).toBe(false);
296
+ expect(priceCheck?.message).toContain("deviates");
297
+ expect(result.valid).toBe(false);
298
+ });
299
+ it("passes with mark price info when no limit price provided (market order)", async () => {
300
+ const adapter = mockAdapter();
301
+ const result = await validateTrade(adapter, {
302
+ symbol: "BTC",
303
+ side: "buy",
304
+ size: 0.01,
305
+ });
306
+ const priceCheck = result.checks.find(c => c.check === "price_fresh");
307
+ expect(priceCheck?.passed).toBe(true);
308
+ expect(priceCheck?.message).toContain("60000");
309
+ });
310
+ });
311
+ // ──────────────────────────────────────────────
312
+ // 4. Liquidity Check
313
+ // ──────────────────────────────────────────────
314
+ describe("validateTrade — liquidity check", () => {
315
+ it("passes when sufficient liquidity exists", async () => {
316
+ const adapter = mockAdapter();
317
+ // Default orderbook: asks sum to 60010*1 + 60020*2 + 60030*3 = ~$360,140
318
+ // Buying 0.01 BTC at ~$60000 = $600 notional
319
+ const result = await validateTrade(adapter, {
320
+ symbol: "BTC",
321
+ side: "buy",
322
+ size: 0.01,
323
+ });
324
+ const liqCheck = result.checks.find(c => c.check === "liquidity_ok");
325
+ expect(liqCheck?.passed).toBe(true);
326
+ expect(liqCheck?.message).toContain("Sufficient liquidity");
327
+ });
328
+ it("fails when orderbook has insufficient liquidity", async () => {
329
+ const adapter = mockAdapter({
330
+ getOrderbook: vi.fn().mockResolvedValue({
331
+ bids: [["59990", "0.001"]],
332
+ asks: [["60010", "0.001"]], // only ~$60 of liquidity
333
+ }),
334
+ });
335
+ // Buying 1 BTC = $60000 notional, but only $60 on asks
336
+ const result = await validateTrade(adapter, {
337
+ symbol: "BTC",
338
+ side: "buy",
339
+ size: 1,
340
+ });
341
+ const liqCheck = result.checks.find(c => c.check === "liquidity_ok");
342
+ expect(liqCheck?.passed).toBe(false);
343
+ expect(liqCheck?.message).toContain("Insufficient liquidity");
344
+ });
345
+ it("fails when slippage exceeds threshold", async () => {
346
+ const adapter = mockAdapter({
347
+ getOrderbook: vi.fn().mockResolvedValue({
348
+ bids: [["59990", "10"]],
349
+ asks: [
350
+ ["60010", "0.001"], // small amount at near price
351
+ ["60500", "100"], // large amount at 0.83% away — above 0.5% threshold
352
+ ],
353
+ }),
354
+ });
355
+ // Buying 10 BTC at ~$60000 = $600,000. First level covers ~$60, rest at $60500.
356
+ // worstPrice = 60500, markPrice = 60000, slippage = 500/60000 = 0.833%
357
+ const result = await validateTrade(adapter, {
358
+ symbol: "BTC",
359
+ side: "buy",
360
+ size: 10,
361
+ });
362
+ const liqCheck = result.checks.find(c => c.check === "liquidity_ok");
363
+ expect(liqCheck?.passed).toBe(false);
364
+ expect(liqCheck?.message).toContain("Slippage");
365
+ });
366
+ it("skips liquidity check for limit orders", async () => {
367
+ const adapter = mockAdapter();
368
+ const result = await validateTrade(adapter, {
369
+ symbol: "BTC",
370
+ side: "buy",
371
+ size: 0.01,
372
+ price: 60000,
373
+ type: "limit",
374
+ });
375
+ const liqCheck = result.checks.find(c => c.check === "liquidity_ok");
376
+ expect(liqCheck?.passed).toBe(true);
377
+ expect(liqCheck?.message).toContain("skipped");
378
+ });
379
+ it("uses bids for sell orders", async () => {
380
+ const adapter = mockAdapter({
381
+ getOrderbook: vi.fn().mockResolvedValue({
382
+ bids: [
383
+ ["59990", "1"],
384
+ ["59980", "2"],
385
+ ],
386
+ asks: [["60010", "0.0001"]], // tiny asks
387
+ }),
388
+ });
389
+ // Selling 0.01 BTC — checks bids. Bids have ample liquidity.
390
+ const result = await validateTrade(adapter, {
391
+ symbol: "BTC",
392
+ side: "sell",
393
+ size: 0.01,
394
+ });
395
+ const liqCheck = result.checks.find(c => c.check === "liquidity_ok");
396
+ expect(liqCheck?.passed).toBe(true);
397
+ });
398
+ });
399
+ // ──────────────────────────────────────────────
400
+ // 5. Reduce-Only / Position Exists
401
+ // ──────────────────────────────────────────────
402
+ describe("validateTrade — reduce-only position check", () => {
403
+ it("passes when position exists with enough size", async () => {
404
+ const adapter = mockAdapter({
405
+ getPositions: vi.fn().mockResolvedValue([
406
+ { symbol: "BTC-PERP", side: "long", size: "1.0", entryPrice: "60000", markPrice: "60000", liquidationPrice: "50000", unrealizedPnl: "0", leverage: 10 },
407
+ ]),
408
+ });
409
+ const result = await validateTrade(adapter, {
410
+ symbol: "BTC",
411
+ side: "sell",
412
+ size: 0.5,
413
+ reduceOnly: true,
414
+ });
415
+ const posCheck = result.checks.find(c => c.check === "position_exists");
416
+ expect(posCheck?.passed).toBe(true);
417
+ expect(posCheck?.message).toContain("Position exists");
418
+ });
419
+ it("fails when reduce size exceeds position size", async () => {
420
+ const adapter = mockAdapter({
421
+ getPositions: vi.fn().mockResolvedValue([
422
+ { symbol: "BTC-PERP", side: "long", size: "0.5", entryPrice: "60000", markPrice: "60000", liquidationPrice: "50000", unrealizedPnl: "0", leverage: 10 },
423
+ ]),
424
+ });
425
+ const result = await validateTrade(adapter, {
426
+ symbol: "BTC",
427
+ side: "sell",
428
+ size: 1.0,
429
+ reduceOnly: true,
430
+ });
431
+ const posCheck = result.checks.find(c => c.check === "position_exists");
432
+ expect(posCheck?.passed).toBe(false);
433
+ expect(posCheck?.message).toContain("exceeds");
434
+ });
435
+ it("fails when no position exists for reduce-only", async () => {
436
+ const adapter = mockAdapter();
437
+ const result = await validateTrade(adapter, {
438
+ symbol: "BTC",
439
+ side: "sell",
440
+ size: 0.1,
441
+ reduceOnly: true,
442
+ });
443
+ const posCheck = result.checks.find(c => c.check === "position_exists");
444
+ expect(posCheck?.passed).toBe(false);
445
+ expect(posCheck?.message).toContain("No position found");
446
+ });
447
+ it("matches position with -PERP suffix to symbol without suffix", async () => {
448
+ const adapter = mockAdapter({
449
+ getPositions: vi.fn().mockResolvedValue([
450
+ { symbol: "ETH-PERP", side: "short", size: "10", entryPrice: "3000", markPrice: "3000", liquidationPrice: "4000", unrealizedPnl: "0", leverage: 5 },
451
+ ]),
452
+ });
453
+ const result = await validateTrade(adapter, {
454
+ symbol: "ETH",
455
+ side: "buy",
456
+ size: 5,
457
+ reduceOnly: true,
458
+ });
459
+ const posCheck = result.checks.find(c => c.check === "position_exists");
460
+ expect(posCheck?.passed).toBe(true);
461
+ });
462
+ it("does not add position_exists check for non-reduce-only orders", async () => {
463
+ const adapter = mockAdapter();
464
+ const result = await validateTrade(adapter, {
465
+ symbol: "BTC",
466
+ side: "buy",
467
+ size: 0.01,
468
+ });
469
+ const posCheck = result.checks.find(c => c.check === "position_exists");
470
+ expect(posCheck).toBeUndefined();
471
+ });
472
+ });
473
+ // ──────────────────────────────────────────────
474
+ // 6. Estimated Cost
475
+ // ──────────────────────────────────────────────
476
+ describe("validateTrade — estimated cost", () => {
477
+ it("calculates margin, fee, slippage for market order", async () => {
478
+ const adapter = mockAdapter();
479
+ // BTC at 60000, size 0.1 = $6000 notional, 20x leverage → margin = $300
480
+ const result = await validateTrade(adapter, {
481
+ symbol: "BTC",
482
+ side: "buy",
483
+ size: 0.1,
484
+ });
485
+ expect(result.estimatedCost).toBeDefined();
486
+ const cost = result.estimatedCost;
487
+ // margin = 6000 / 20 = 300
488
+ expect(cost.margin).toBeCloseTo(300, 0);
489
+ // fee = 6000 * 0.0005 = 3
490
+ expect(cost.fee).toBeCloseTo(3, 1);
491
+ // slippage = 6000 * 0.001 = 6 (for market orders)
492
+ expect(cost.slippage).toBeCloseTo(6, 1);
493
+ // total = margin + fee + slippage
494
+ expect(cost.total).toBeCloseTo(cost.margin + cost.fee + cost.slippage, 1);
495
+ });
496
+ it("sets slippage to 0 for limit orders", async () => {
497
+ const adapter = mockAdapter();
498
+ const result = await validateTrade(adapter, {
499
+ symbol: "BTC",
500
+ side: "buy",
501
+ size: 0.1,
502
+ price: 60000,
503
+ type: "limit",
504
+ });
505
+ expect(result.estimatedCost?.slippage).toBe(0);
506
+ });
507
+ it("uses specified price for limit order cost calculation", async () => {
508
+ const adapter = mockAdapter();
509
+ // Limit price 59000, size 0.1 = $5900 notional
510
+ const result = await validateTrade(adapter, {
511
+ symbol: "BTC",
512
+ side: "buy",
513
+ size: 0.1,
514
+ price: 59000,
515
+ type: "limit",
516
+ });
517
+ const cost = result.estimatedCost;
518
+ // margin = 5900 / 20 = 295
519
+ expect(cost.margin).toBeCloseTo(295, 0);
520
+ // fee = 5900 * 0.0005 = 2.95
521
+ expect(cost.fee).toBeCloseTo(2.95, 1);
522
+ });
523
+ });
524
+ // ──────────────────────────────────────────────
525
+ // 7. Market Info
526
+ // ──────────────────────────────────────────────
527
+ describe("validateTrade — market info output", () => {
528
+ it("returns marketInfo for valid symbols", async () => {
529
+ const adapter = mockAdapter();
530
+ const result = await validateTrade(adapter, {
531
+ symbol: "ETH",
532
+ side: "buy",
533
+ size: 1,
534
+ });
535
+ expect(result.marketInfo).toBeDefined();
536
+ expect(result.marketInfo?.symbol).toBe("ETH");
537
+ expect(result.marketInfo?.markPrice).toBe(3000);
538
+ expect(result.marketInfo?.fundingRate).toBeCloseTo(0.00005);
539
+ expect(result.marketInfo?.maxLeverage).toBe(15);
540
+ });
541
+ it("does not include marketInfo when symbol not found", async () => {
542
+ const adapter = mockAdapter();
543
+ const result = await validateTrade(adapter, {
544
+ symbol: "NONEXISTENT",
545
+ side: "buy",
546
+ size: 1,
547
+ });
548
+ expect(result.marketInfo).toBeUndefined();
549
+ });
550
+ });
551
+ // ──────────────────────────────────────────────
552
+ // 8. Leverage Warning
553
+ // ──────────────────────────────────────────────
554
+ describe("validateTrade — leverage warning", () => {
555
+ it("warns when requested leverage exceeds max", async () => {
556
+ const adapter = mockAdapter();
557
+ // BTC-PERP maxLeverage is 20
558
+ const result = await validateTrade(adapter, {
559
+ symbol: "BTC",
560
+ side: "buy",
561
+ size: 0.01,
562
+ leverage: 50,
563
+ });
564
+ expect(result.warnings.some(w => w.includes("50x") && w.includes("20x"))).toBe(true);
565
+ });
566
+ });
567
+ // ──────────────────────────────────────────────
568
+ // 9. Error Resilience
569
+ // ──────────────────────────────────────────────
570
+ describe("validateTrade — error resilience", () => {
571
+ it("handles getMarkets failure gracefully", async () => {
572
+ const adapter = mockAdapter({
573
+ getMarkets: vi.fn().mockRejectedValue(new Error("network error")),
574
+ });
575
+ const result = await validateTrade(adapter, {
576
+ symbol: "BTC",
577
+ side: "buy",
578
+ size: 0.01,
579
+ });
580
+ // Markets failed → returns empty array → symbol not found
581
+ expect(result.valid).toBe(false);
582
+ expect(result.checks[0].check).toBe("symbol_valid");
583
+ expect(result.checks[0].passed).toBe(false);
584
+ });
585
+ it("handles getBalance failure gracefully", async () => {
586
+ const adapter = mockAdapter({
587
+ getBalance: vi.fn().mockRejectedValue(new Error("auth error")),
588
+ });
589
+ const result = await validateTrade(adapter, {
590
+ symbol: "BTC",
591
+ side: "buy",
592
+ size: 0.01,
593
+ });
594
+ // Balance defaults to 0 available → balance check fails for non-zero orders
595
+ const balCheck = result.checks.find(c => c.check === "balance_sufficient");
596
+ expect(balCheck).toBeDefined();
597
+ // available = 0, marginRequired = 600/20=30, so should fail
598
+ expect(balCheck?.passed).toBe(false);
599
+ });
600
+ it("handles getOrderbook failure gracefully", async () => {
601
+ const adapter = mockAdapter({
602
+ getOrderbook: vi.fn().mockRejectedValue(new Error("timeout")),
603
+ });
604
+ const result = await validateTrade(adapter, {
605
+ symbol: "BTC",
606
+ side: "buy",
607
+ size: 0.01,
608
+ });
609
+ // Orderbook defaults to empty → liquidity check skipped
610
+ const liqCheck = result.checks.find(c => c.check === "liquidity_ok");
611
+ expect(liqCheck?.passed).toBe(true);
612
+ expect(liqCheck?.message).toContain("skipped");
613
+ });
614
+ });
615
+ // ──────────────────────────────────────────────
616
+ // 10. Overall Validity
617
+ // ──────────────────────────────────────────────
618
+ describe("validateTrade — overall validity", () => {
619
+ it("returns valid=true when all checks pass", async () => {
620
+ const adapter = mockAdapter();
621
+ const result = await validateTrade(adapter, {
622
+ symbol: "BTC",
623
+ side: "buy",
624
+ size: 0.01,
625
+ });
626
+ expect(result.valid).toBe(true);
627
+ expect(result.checks.every(c => c.passed)).toBe(true);
628
+ });
629
+ it("returns valid=false if any check fails", async () => {
630
+ const adapter = mockAdapter({
631
+ getBalance: vi.fn().mockResolvedValue({
632
+ equity: "1",
633
+ available: "0",
634
+ marginUsed: "1",
635
+ unrealizedPnl: "0",
636
+ }),
637
+ });
638
+ const result = await validateTrade(adapter, {
639
+ symbol: "BTC",
640
+ side: "buy",
641
+ size: 1,
642
+ });
643
+ expect(result.valid).toBe(false);
644
+ });
645
+ it("includes a timestamp in ISO format", async () => {
646
+ const adapter = mockAdapter();
647
+ const result = await validateTrade(adapter, {
648
+ symbol: "BTC",
649
+ side: "buy",
650
+ size: 0.01,
651
+ });
652
+ expect(result.timestamp).toBeDefined();
653
+ expect(new Date(result.timestamp).toISOString()).toBe(result.timestamp);
654
+ });
655
+ });
@@ -0,0 +1 @@
1
+ export {};