perp-cli 0.9.8 → 0.12.0

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 (254) hide show
  1. package/README.md +30 -25
  2. package/dist/_stderr-filter.d.ts +0 -0
  3. package/dist/_stderr-filter.js +30 -0
  4. package/dist/agent-wallet/expiry.d.ts +15 -0
  5. package/dist/agent-wallet/expiry.js +26 -0
  6. package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
  7. package/dist/agent-wallet/lighter-keystore.js +232 -0
  8. package/dist/agent-wallet/passphrase.d.ts +13 -0
  9. package/dist/agent-wallet/passphrase.js +54 -0
  10. package/dist/agent-wallet/signing-strategy.d.ts +67 -0
  11. package/dist/agent-wallet/signing-strategy.js +51 -0
  12. package/dist/agent-wallet/store.d.ts +37 -0
  13. package/dist/agent-wallet/store.js +172 -0
  14. package/dist/api/public/_http.d.ts +15 -0
  15. package/dist/api/public/_http.js +24 -0
  16. package/dist/api/public/hyperliquid.js +36 -24
  17. package/dist/api/public/lighter.d.ts +7 -1
  18. package/dist/api/public/lighter.js +85 -37
  19. package/dist/api/public/pacifica.js +47 -18
  20. package/dist/bot/engine.js +23 -4
  21. package/dist/bot/presets.js +3 -3
  22. package/dist/bot/strategies/funding-arb-strategy.d.ts +0 -23
  23. package/dist/bot/strategies/funding-arb-strategy.js +41 -176
  24. package/dist/bot/strategies/funding-auto.js +2 -2
  25. package/dist/bridge-engine.js +102 -49
  26. package/dist/cli-spec.js +1 -2
  27. package/dist/commands/account.js +2 -500
  28. package/dist/commands/agent.d.ts +8 -2
  29. package/dist/commands/agent.js +1903 -366
  30. package/dist/commands/alerts.js +3 -1
  31. package/dist/commands/arb/index.js +37 -7
  32. package/dist/commands/arb-auto.js +44 -11
  33. package/dist/commands/arb.js +83 -34
  34. package/dist/commands/bot.d.ts +1 -1
  35. package/dist/commands/bot.js +20 -16
  36. package/dist/commands/bridge.d.ts +1 -1
  37. package/dist/commands/bridge.js +4 -4
  38. package/dist/commands/funds.d.ts +1 -1
  39. package/dist/commands/funds.js +18 -68
  40. package/dist/commands/jobs.d.ts +1 -1
  41. package/dist/commands/jobs.js +2 -2
  42. package/dist/commands/manage.d.ts +1 -1
  43. package/dist/commands/manage.js +12 -84
  44. package/dist/commands/plan.d.ts +1 -1
  45. package/dist/commands/plan.js +2 -2
  46. package/dist/commands/portfolio.d.ts +19 -3
  47. package/dist/commands/portfolio.js +747 -163
  48. package/dist/commands/rebalance.d.ts +1 -1
  49. package/dist/commands/rebalance.js +18 -12
  50. package/dist/commands/risk.js +2 -6
  51. package/dist/commands/settings.js +30 -53
  52. package/dist/commands/trade.js +16 -15
  53. package/dist/commands/wallet.d.ts +2 -1
  54. package/dist/commands/wallet.js +1122 -176
  55. package/dist/config.d.ts +9 -0
  56. package/dist/config.js +55 -9
  57. package/dist/errors.d.ts +64 -0
  58. package/dist/errors.js +82 -2
  59. package/dist/event-stream.js +12 -1
  60. package/dist/exchanges/aster-typed-data.d.ts +156 -0
  61. package/dist/exchanges/aster-typed-data.js +189 -0
  62. package/dist/exchanges/aster.d.ts +60 -11
  63. package/dist/exchanges/aster.js +278 -136
  64. package/dist/exchanges/hyperliquid.d.ts +14 -0
  65. package/dist/exchanges/hyperliquid.js +88 -34
  66. package/dist/exchanges/lighter-spot.js +19 -5
  67. package/dist/exchanges/lighter.d.ts +66 -0
  68. package/dist/exchanges/lighter.js +213 -31
  69. package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
  70. package/dist/exchanges/pacifica-typed-data.js +74 -0
  71. package/dist/exchanges/pacifica.d.ts +29 -1
  72. package/dist/exchanges/pacifica.js +94 -6
  73. package/dist/funding/rates.js +59 -21
  74. package/dist/guardrail/contracts.d.ts +14 -0
  75. package/dist/guardrail/contracts.js +72 -0
  76. package/dist/guardrail/perp-guardrail.d.ts +21 -0
  77. package/dist/guardrail/perp-guardrail.js +145 -0
  78. package/dist/index.d.ts +1 -1
  79. package/dist/index.js +307 -362
  80. package/dist/jobs.js +3 -1
  81. package/dist/mcp-server.js +92 -88
  82. package/dist/position-history.js +47 -4
  83. package/dist/settings.d.ts +69 -3
  84. package/dist/settings.js +3 -2
  85. package/dist/signer/index.d.ts +2 -0
  86. package/dist/signer/index.js +2 -0
  87. package/dist/signer/ows-evm.d.ts +32 -0
  88. package/dist/signer/ows-evm.js +86 -0
  89. package/dist/signer/ows-loader.d.ts +2 -0
  90. package/dist/signer/ows-loader.js +6 -0
  91. package/dist/signer/ows-solana.d.ts +25 -0
  92. package/dist/signer/ows-solana.js +56 -0
  93. package/dist/smart-order.d.ts +8 -2
  94. package/dist/smart-order.js +46 -11
  95. package/dist/strategies/funding-arb.js +29 -8
  96. package/dist/trade-validator.js +30 -5
  97. package/dist/utils.d.ts +10 -0
  98. package/dist/utils.js +19 -0
  99. package/package.json +4 -2
  100. package/skills/perp-cli/SKILL.md +23 -14
  101. package/skills/perp-cli/references/agent-operations.md +6 -4
  102. package/skills/perp-cli/references/commands.md +55 -34
  103. package/skills/perp-cli/scripts/preflight.sh +4 -4
  104. package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
  105. package/dist/__tests__/alert-logic.test.d.ts +0 -1
  106. package/dist/__tests__/alert-logic.test.js +0 -107
  107. package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
  108. package/dist/__tests__/arb-auto-3dex.test.js +0 -397
  109. package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
  110. package/dist/__tests__/arb-history-stats.test.js +0 -176
  111. package/dist/__tests__/arb-logic.test.d.ts +0 -1
  112. package/dist/__tests__/arb-logic.test.js +0 -84
  113. package/dist/__tests__/arb-manage.test.d.ts +0 -1
  114. package/dist/__tests__/arb-manage.test.js +0 -253
  115. package/dist/__tests__/arb-new-features.test.d.ts +0 -1
  116. package/dist/__tests__/arb-new-features.test.js +0 -457
  117. package/dist/__tests__/arb-sizing.test.d.ts +0 -1
  118. package/dist/__tests__/arb-sizing.test.js +0 -48
  119. package/dist/__tests__/arb-state.test.d.ts +0 -1
  120. package/dist/__tests__/arb-state.test.js +0 -284
  121. package/dist/__tests__/arb-userflow.test.d.ts +0 -1
  122. package/dist/__tests__/arb-userflow.test.js +0 -945
  123. package/dist/__tests__/arb-utils.test.d.ts +0 -1
  124. package/dist/__tests__/arb-utils.test.js +0 -264
  125. package/dist/__tests__/bot-conditions.test.d.ts +0 -1
  126. package/dist/__tests__/bot-conditions.test.js +0 -341
  127. package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
  128. package/dist/__tests__/client-id-tracker.test.js +0 -137
  129. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
  130. package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
  131. package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
  132. package/dist/__tests__/commands/order-intent.test.js +0 -600
  133. package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
  134. package/dist/__tests__/commands/trade-commands.test.js +0 -821
  135. package/dist/__tests__/config.test.d.ts +0 -1
  136. package/dist/__tests__/config.test.js +0 -86
  137. package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
  138. package/dist/__tests__/cross-chain-margin.test.js +0 -287
  139. package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
  140. package/dist/__tests__/dex-asset-map.test.js +0 -191
  141. package/dist/__tests__/errors.test.d.ts +0 -1
  142. package/dist/__tests__/errors.test.js +0 -110
  143. package/dist/__tests__/event-stream.test.d.ts +0 -1
  144. package/dist/__tests__/event-stream.test.js +0 -276
  145. package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
  146. package/dist/__tests__/exchanges/interface.test.js +0 -132
  147. package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
  148. package/dist/__tests__/exchanges/mock-adapter.js +0 -137
  149. package/dist/__tests__/execution-log.test.d.ts +0 -1
  150. package/dist/__tests__/execution-log.test.js +0 -106
  151. package/dist/__tests__/funding-calc.test.d.ts +0 -1
  152. package/dist/__tests__/funding-calc.test.js +0 -71
  153. package/dist/__tests__/funding-history.test.d.ts +0 -1
  154. package/dist/__tests__/funding-history.test.js +0 -343
  155. package/dist/__tests__/funding-rates.test.d.ts +0 -1
  156. package/dist/__tests__/funding-rates.test.js +0 -342
  157. package/dist/__tests__/funding.test.d.ts +0 -1
  158. package/dist/__tests__/funding.test.js +0 -173
  159. package/dist/__tests__/gap-logic.test.d.ts +0 -1
  160. package/dist/__tests__/gap-logic.test.js +0 -43
  161. package/dist/__tests__/hip3-dex.test.d.ts +0 -1
  162. package/dist/__tests__/hip3-dex.test.js +0 -234
  163. package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
  164. package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
  165. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
  166. package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
  167. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
  168. package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
  169. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
  170. package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
  171. package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
  172. package/dist/__tests__/integration/bridge.integration.test.js +0 -309
  173. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
  174. package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
  175. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
  176. package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
  177. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
  178. package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
  179. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
  180. package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
  181. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
  182. package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
  183. package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
  184. package/dist/__tests__/integration/lighter.integration.test.js +0 -53
  185. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
  186. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
  187. package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
  188. package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
  189. package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
  190. package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
  191. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
  192. package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
  193. package/dist/__tests__/liquidity.test.d.ts +0 -1
  194. package/dist/__tests__/liquidity.test.js +0 -225
  195. package/dist/__tests__/plan-executor.test.d.ts +0 -1
  196. package/dist/__tests__/plan-executor.test.js +0 -314
  197. package/dist/__tests__/position-history.test.d.ts +0 -1
  198. package/dist/__tests__/position-history.test.js +0 -367
  199. package/dist/__tests__/retry.test.d.ts +0 -1
  200. package/dist/__tests__/retry.test.js +0 -310
  201. package/dist/__tests__/risk-assessment.test.d.ts +0 -1
  202. package/dist/__tests__/risk-assessment.test.js +0 -288
  203. package/dist/__tests__/security-adversarial.test.d.ts +0 -1
  204. package/dist/__tests__/security-adversarial.test.js +0 -574
  205. package/dist/__tests__/strategies.test.d.ts +0 -1
  206. package/dist/__tests__/strategies.test.js +0 -539
  207. package/dist/__tests__/trade-execution.test.d.ts +0 -1
  208. package/dist/__tests__/trade-execution.test.js +0 -129
  209. package/dist/__tests__/trade-validator.test.d.ts +0 -1
  210. package/dist/__tests__/trade-validator.test.js +0 -655
  211. package/dist/__tests__/utils.test.d.ts +0 -1
  212. package/dist/__tests__/utils.test.js +0 -76
  213. package/dist/arb/reconcile.d.ts +0 -28
  214. package/dist/arb/reconcile.js +0 -162
  215. package/dist/commands/alert.d.ts +0 -2
  216. package/dist/commands/alert.js +0 -421
  217. package/dist/commands/analytics.d.ts +0 -3
  218. package/dist/commands/analytics.js +0 -311
  219. package/dist/commands/arb-manage.d.ts +0 -5
  220. package/dist/commands/arb-manage.js +0 -5
  221. package/dist/commands/dashboard.d.ts +0 -3
  222. package/dist/commands/dashboard.js +0 -176
  223. package/dist/commands/deposit.d.ts +0 -4
  224. package/dist/commands/deposit.js +0 -573
  225. package/dist/commands/dex.d.ts +0 -3
  226. package/dist/commands/dex.js +0 -120
  227. package/dist/commands/env.d.ts +0 -2
  228. package/dist/commands/env.js +0 -136
  229. package/dist/commands/funding.d.ts +0 -3
  230. package/dist/commands/funding.js +0 -554
  231. package/dist/commands/gap.d.ts +0 -2
  232. package/dist/commands/gap.js +0 -305
  233. package/dist/commands/health.d.ts +0 -2
  234. package/dist/commands/health.js +0 -67
  235. package/dist/commands/multileg.d.ts +0 -16
  236. package/dist/commands/multileg.js +0 -174
  237. package/dist/commands/pnl.d.ts +0 -12
  238. package/dist/commands/pnl.js +0 -243
  239. package/dist/commands/run.d.ts +0 -3
  240. package/dist/commands/run.js +0 -202
  241. package/dist/commands/stream.d.ts +0 -5
  242. package/dist/commands/stream.js +0 -200
  243. package/dist/commands/withdraw.d.ts +0 -3
  244. package/dist/commands/withdraw.js +0 -187
  245. package/dist/exchanges/edgex.d.ts +0 -62
  246. package/dist/exchanges/edgex.js +0 -552
  247. package/dist/exchanges/nado.d.ts +0 -52
  248. package/dist/exchanges/nado.js +0 -223
  249. package/dist/funding/index.d.ts +0 -3
  250. package/dist/funding/index.js +0 -3
  251. package/dist/retry.d.ts +0 -74
  252. package/dist/retry.js +0 -129
  253. package/dist/ws/market-feed.d.ts +0 -74
  254. package/dist/ws/market-feed.js +0 -227
@@ -69,7 +69,11 @@ perp --json trade market <SYM> buy <SIZE> --split # split via market command f
69
69
  perp --json trade market <SYM> buy <SIZE> --split --max-slippage 0.5
70
70
  ```
71
71
 
72
- ## Funds (Deposit, Withdraw, Transfer, Bridge)
72
+ ## Funds (Deposit, Withdraw, Transfer, Bridge, Rebalance)
73
+
74
+ All fund movement lives under `perp funds`.
75
+
76
+ ### Exchange in/out
73
77
  ```bash
74
78
  perp --json funds deposit pacifica <AMOUNT>
75
79
  perp --json funds deposit hyperliquid <AMOUNT>
@@ -79,19 +83,26 @@ perp --json funds deposit lighter info # all Lighter deposi
79
83
  perp --json funds withdraw pacifica <AMOUNT>
80
84
  perp --json funds withdraw hyperliquid <AMOUNT>
81
85
  perp --json funds withdraw lighter <AMOUNT>
82
- perp --json funds transfer <AMOUNT> <ADDRESS> # HL internal transfer (instant)
83
- perp --json funds bridge --from <CHAIN> --to <CHAIN> --amount <AMT> --recipient <ADDR> # CCTP bridge
84
- perp --json funds bridge-status --hash <HASH> # check CCTP bridge status
85
- perp --json funds info # combined deposit & withdrawal info
86
+ perp --json funds transfer <AMOUNT> <ADDRESS> # HL internal transfer (instant)
87
+ perp --json funds info # combined routes & limits
88
+ ```
89
+
90
+ ### Cross-chain bridge (multi-provider router: cctp / relay / debridge — auto-cheapest)
91
+ ```bash
92
+ perp --json funds bridge chains # supported chains
93
+ perp --json funds bridge quote --from <CHAIN> --to <CHAIN> --amount <AMT>
94
+ perp --json funds bridge send --from <CHAIN> --to <CHAIN> --amount <AMT> # auto-cheapest
95
+ perp --json funds bridge send --from <CHAIN> --to <CHAIN> --amount <AMT> --provider cctp
96
+ perp --json funds bridge exchange --from <EX> --to <EX> --amount <AMT> # between exchanges
97
+ perp --json funds bridge status <ORDER_ID>
86
98
  ```
87
99
 
88
- ## Bridge (Cross-chain USDC)
100
+ ### Inter-exchange rebalance (orchestrated withdraw → bridge → deposit)
89
101
  ```bash
90
- perp --json bridge chains # supported chains
91
- perp --json bridge quote --from <CHAIN> --to <CHAIN> --amount <AMT>
92
- perp --json bridge send --from <CHAIN> --to <CHAIN> --amount <AMT>
93
- perp --json bridge exchange --from <EX> --to <EX> --amount <AMT>
94
- perp --json bridge status <ORDER_ID>
102
+ perp --json funds rebalance check # balances across exchanges
103
+ perp --json funds rebalance plan # compute optimal moves
104
+ perp --json funds rebalance execute --auto-bridge # withdraw → bridge → deposit
105
+ perp --json funds rebalance execute --withdraw-only # only withdraw, manual rest
95
106
  ```
96
107
 
97
108
  ## Arbitrage
@@ -137,7 +148,6 @@ perp --json risk liquidation-distance # % distance from liquidation for AL
137
148
  perp --json risk limits # view current risk limits
138
149
  perp --json risk limits --min-liq-distance 30 --max-leverage 5 # set risk limits
139
150
  perp --json risk check --notional 1000 --leverage 3 # pre-trade risk check
140
- perp --json agent ping # exchange connectivity & latency
141
151
  perp --json history summary # trading performance
142
152
  perp --json history pnl # P&L breakdown by exchange
143
153
  perp --json history funding # funding payment aggregation
@@ -148,24 +158,34 @@ perp --json history perf --period summary # performance summary stats
148
158
  perp --json history list # execution audit trail
149
159
  ```
150
160
 
151
- ## Automated Strategies (bot — 19 strategies)
161
+ ## Automated Strategies (strategy — 19 algorithms + nested scripted plans)
152
162
  ```bash
153
- perp --json bot list-strategies # list all 19 strategies
154
- perp --json bot run <STRATEGY> [SYMBOL] # run any registered strategy
155
- perp --json bot run funding-auto # multi-exchange funding arb (no symbol needed)
156
- perp --json bot apex [SYMBOL] # APEX autonomous orchestrator (Radar+Pulse+Guard)
157
- perp --json bot reflect # trading performance analysis (win rate, fees, PnL)
158
- perp --json bot preset-list # list strategy presets
159
- perp --json bot preset <NAME> [SYMBOL] # run from preset
160
- perp --json bot twap <SYMBOL> <SIDE> <SIZE> <DURATION> # TWAP execution
161
- perp --json bot grid <SYMBOL> --range <PCT> --grids <N> --size <USD>
162
- perp --json bot dca <SYMBOL> <SIDE> <AMOUNT> <INTERVAL>
163
- perp --json bot trailing-stop <SYMBOL> # trailing stop with callback %
164
- perp --json bot funding-arb # auto funding arb
165
- perp --json bot quick-grid <SYMBOL> # quick grid bot
166
- perp --json bot quick-arb # quick arb bot
167
- perp --json jobs list # list running jobs
168
- perp --json jobs stop <ID> # stop a job
163
+ perp --json strategy list-strategies # list all 19 strategies
164
+ perp --json strategy run <STRATEGY> [SYMBOL] # run any registered strategy
165
+ perp --json strategy run funding-auto # multi-exchange funding arb (no symbol needed)
166
+ perp --json strategy apex [SYMBOL] # APEX autonomous orchestrator (Radar+Pulse+Guard)
167
+ perp --json strategy reflect # trading performance analysis (win rate, fees, PnL)
168
+ perp --json strategy preset-list # list strategy presets
169
+ perp --json strategy preset <NAME> [SYMBOL] # run from preset
170
+ perp --json strategy twap <SYMBOL> <SIDE> <SIZE> <DURATION> # TWAP execution
171
+ perp --json strategy grid <SYMBOL> --range <PCT> --grids <N> --size <USD>
172
+ perp --json strategy dca <SYMBOL> <SIDE> <AMOUNT> <INTERVAL>
173
+ perp --json strategy trailing-stop <SYMBOL> # trailing stop with callback %
174
+ perp --json strategy funding-arb # auto funding arb
175
+ perp --json strategy quick-grid <SYMBOL> # quick grid bot
176
+ perp --json strategy quick-arb # quick arb bot
177
+
178
+ # Scripted execution plans (one-shot multi-step)
179
+ perp --json strategy plan example # show plan format
180
+ perp --json strategy plan validate <FILE> # validate plan
181
+ perp --json strategy plan execute <FILE> --dry-run # dry-run plan
182
+
183
+ # Background process supervisor (tmux)
184
+ perp --json background list # list running jobs
185
+ perp --json background stop <ID> # stop a job
186
+ perp --json background logs <ID> # view job logs
187
+ perp --json background remove <ID> # remove a job entry
188
+ perp --json background clean # remove all stopped/done jobs
169
189
  ```
170
190
 
171
191
  ### Available Strategies
@@ -175,10 +195,11 @@ Arbitrage: `funding-arb`, `funding-auto`, `basis-arb`
175
195
  Infrastructure: `hedge-agent`, `rfq-agent`, `claude-agent`
176
196
  Classic: `grid`, `dca`, `twap`, `apex`
177
197
 
178
- ## Command Discovery
198
+ ## Agent Wallet Management
179
199
  ```bash
180
- perp schema # Full CLI schema as JSON
181
- perp agent capabilities # High-level capability list
182
- perp agent plan "<goal>" # Suggest command sequence for a goal
183
- perp agent ping # Health check all exchanges
200
+ perp wallet agent approve <exchange> [--master <name>] # Register an agent wallet
201
+ perp wallet agent list [exchange] # List registered agents
202
+ perp wallet agent revoke <exchange> <agentName> # Revoke an agent
203
+ perp wallet agent rotate <exchange> [agentName] # Revoke + re-approve same name
204
+ perp wallet agent verify [exchange] [agentName] # Verify via DEX query API
184
205
  ```
@@ -41,12 +41,12 @@ else
41
41
  add_check "wallet" "fail" "No wallet configured — run: perp wallet set <exchange> <key>"
42
42
  fi
43
43
 
44
- # 3. Exchange connectivity
45
- HEALTH_OUT=$($PERP --json agent ping 2>/dev/null || echo '{"ok":false}')
44
+ # 3. Exchange connectivity (proxy via unauthenticated market prices fetch)
45
+ HEALTH_OUT=$($PERP --json market prices 2>/dev/null || echo '{"ok":false}')
46
46
  if echo "$HEALTH_OUT" | grep -q '"ok":true'; then
47
- add_check "connectivity" "pass" "Exchanges reachable"
47
+ add_check "connectivity" "pass" "Exchange reachable"
48
48
  else
49
- add_check "connectivity" "warn" "Some exchanges unreachable"
49
+ add_check "connectivity" "warn" "Exchange unreachable"
50
50
  fi
51
51
 
52
52
  # 4. Portfolio & balances
@@ -38,8 +38,8 @@ fi
38
38
  # 2. Run appropriate scan
39
39
  SCAN_RESULT=$($PERP --json arb scan --mode "$MODE" --min "$MIN_SPREAD" 2>/dev/null || echo '{"ok":false}')
40
40
 
41
- # 3. Get exchange health for context
42
- HEALTH=$($PERP --json agent ping 2>/dev/null || echo '{"ok":false}')
41
+ # 3. Get exchange health for context (proxy via market prices fetch)
42
+ HEALTH=$($PERP --json market prices 2>/dev/null || echo '{"ok":false}')
43
43
 
44
44
  if $JSON_MODE; then
45
45
  cat <<EOF
@@ -1 +0,0 @@
1
- export {};
@@ -1,107 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- /**
3
- * Tests for the alert price merging logic (extracted from alert.ts).
4
- * Validates the fix for the price map key inconsistency bug.
5
- */
6
- function mergePrices(pacData, hlData) {
7
- const map = new Map();
8
- if (pacData) {
9
- for (const p of pacData) {
10
- map.set(`pac:${p.symbol}`, Number(p.mark));
11
- }
12
- }
13
- if (hlData) {
14
- for (const [symbol, price] of Object.entries(hlData)) {
15
- map.set(`hl:${symbol}`, Number(price));
16
- }
17
- }
18
- // Merge into bare symbol keys: prefer pac price, fallback to hl
19
- const allSymbols = new Set();
20
- for (const k of map.keys()) {
21
- if (k.includes(":"))
22
- allSymbols.add(k.split(":")[1]);
23
- }
24
- for (const sym of allSymbols) {
25
- const pacPrice = map.get(`pac:${sym}`);
26
- const hlPrice = map.get(`hl:${sym}`);
27
- map.set(sym, pacPrice ?? hlPrice ?? 0);
28
- }
29
- return map;
30
- }
31
- describe("Alert price merging", () => {
32
- it("prefers Pacifica price when both available", () => {
33
- const map = mergePrices([{ symbol: "BTC", mark: "100000" }], { BTC: "99900" });
34
- expect(map.get("BTC")).toBe(100000);
35
- expect(map.get("pac:BTC")).toBe(100000);
36
- expect(map.get("hl:BTC")).toBe(99900);
37
- });
38
- it("falls back to HL price when pac unavailable", () => {
39
- const map = mergePrices(null, { ETH: "3500" });
40
- expect(map.get("ETH")).toBe(3500);
41
- expect(map.has("pac:ETH")).toBe(false);
42
- expect(map.get("hl:ETH")).toBe(3500);
43
- });
44
- it("handles Pacifica-only symbols", () => {
45
- const map = mergePrices([{ symbol: "SOL", mark: "150" }], {});
46
- expect(map.get("SOL")).toBe(150);
47
- });
48
- it("handles empty data", () => {
49
- const map = mergePrices(null, null);
50
- expect(map.size).toBe(0);
51
- });
52
- it("handles multiple symbols from both sources", () => {
53
- const map = mergePrices([
54
- { symbol: "BTC", mark: "100000" },
55
- { symbol: "ETH", mark: "3500" },
56
- ], {
57
- BTC: "99900",
58
- ETH: "3490",
59
- SOL: "150",
60
- });
61
- // Pac preferred for BTC and ETH
62
- expect(map.get("BTC")).toBe(100000);
63
- expect(map.get("ETH")).toBe(3500);
64
- // HL-only for SOL
65
- expect(map.get("SOL")).toBe(150);
66
- });
67
- it("bare symbol key is always set (no orphaned prefixed keys)", () => {
68
- const map = mergePrices([{ symbol: "DOGE", mark: "0.15" }], { DOGE: "0.14", ARB: "1.20" });
69
- // Every symbol with a prefixed key should have a bare key
70
- for (const k of map.keys()) {
71
- if (k.includes(":")) {
72
- const bare = k.split(":")[1];
73
- expect(map.has(bare)).toBe(true);
74
- }
75
- }
76
- });
77
- });
78
- describe("Alert trigger conditions", () => {
79
- it("fires price above alert", () => {
80
- const currentPrice = 105000;
81
- const threshold = 100000;
82
- const condition = "above";
83
- expect(condition === "above" && currentPrice >= threshold).toBe(true);
84
- });
85
- it("does NOT fire price above when below", () => {
86
- const currentPrice = 95000;
87
- const threshold = 100000;
88
- const condition = "above";
89
- expect(condition === "above" && currentPrice >= threshold).toBe(false);
90
- });
91
- it("fires price below alert", () => {
92
- const currentPrice = 95000;
93
- const threshold = 100000;
94
- const condition = "below";
95
- expect(condition === "below" && currentPrice <= threshold).toBe(true);
96
- });
97
- it("fires funding spread alert", () => {
98
- const spread = 35; // annual %
99
- const alertThreshold = 30;
100
- expect(spread >= alertThreshold).toBe(true);
101
- });
102
- it("does NOT fire funding spread below threshold", () => {
103
- const spread = 25;
104
- const alertThreshold = 30;
105
- expect(spread >= alertThreshold).toBe(false);
106
- });
107
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,397 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { computeNetSpread, computeRoundTripCostPct, getNextSettlement, isNearSettlement, isSpreadReversed, } from "../commands/arb-auto.js";
3
- /**
4
- * Mirrors the direction logic in arb-auto.ts:
5
- * Sort all available exchange rates by hourly-normalized rate.
6
- * Long on the lowest-rate exchange, short on the highest.
7
- */
8
- function determine3DexDirection(snap) {
9
- return {
10
- longExchange: snap.longExch,
11
- shortExchange: snap.shortExch,
12
- };
13
- }
14
- /**
15
- * Mirrors the funding accumulation logic in arb-auto:
16
- * Estimate funding collected based on rate differential and elapsed time.
17
- */
18
- function accumulateFunding(pos, current, nowMs) {
19
- const elapsedHours = (nowMs - pos.lastCheckTime) / (1000 * 60 * 60);
20
- const notional = parseFloat(pos.size) * current.markPrice;
21
- const rateFor = (e) => e === "pacifica" ? current.pacRate : e === "hyperliquid" ? current.hlRate : current.ltRate;
22
- const longHourly = rateFor(pos.longExchange) / 1; // all exchanges are hourly
23
- const shortHourly = rateFor(pos.shortExchange) / 1; // all exchanges are hourly
24
- const hourlyIncome = (shortHourly - longHourly) * notional;
25
- return hourlyIncome * elapsedHours;
26
- }
27
- // ──────────────────────────────────────────────
28
- // 3-DEX direction
29
- // ──────────────────────────────────────────────
30
- describe("3-DEX direction determination", () => {
31
- it("uses longExch/shortExch from snapshot (all 3 available)", () => {
32
- const snap = {
33
- symbol: "BTC",
34
- pacRate: 0.0006, // pac = 0.0006/hr
35
- hlRate: 0.0003, // hl = 0.0003/hr
36
- ltRate: 0.0002, // lt = 0.0002/hr (lowest!)
37
- spread: 240.9,
38
- longExch: "lighter",
39
- shortExch: "pacifica",
40
- markPrice: 60000,
41
- pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
42
- };
43
- const { longExchange, shortExchange } = determine3DexDirection(snap);
44
- expect(longExchange).toBe("lighter");
45
- expect(shortExchange).toBe("pacifica");
46
- });
47
- it("handles pac vs hl only (lighter missing)", () => {
48
- const snap = {
49
- symbol: "ETH",
50
- pacRate: 0.002, // pac = 0.002/hr (highest)
51
- hlRate: 0.00005, // hl = 0.00005/hr (lowest)
52
- ltRate: 0, // no lighter
53
- spread: 175.2,
54
- longExch: "hyperliquid",
55
- shortExch: "pacifica",
56
- markPrice: 3000,
57
- pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
58
- };
59
- const { longExchange, shortExchange } = determine3DexDirection(snap);
60
- expect(longExchange).toBe("hyperliquid");
61
- expect(shortExchange).toBe("pacifica");
62
- });
63
- it("handles lighter vs pac when HL missing", () => {
64
- const snap = {
65
- symbol: "SOL",
66
- pacRate: 0.0001,
67
- hlRate: 0,
68
- ltRate: 0.001,
69
- spread: 98.55,
70
- longExch: "pacifica",
71
- shortExch: "lighter",
72
- markPrice: 150,
73
- pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
74
- };
75
- const { longExchange, shortExchange } = determine3DexDirection(snap);
76
- expect(longExchange).toBe("pacifica");
77
- expect(shortExchange).toBe("lighter");
78
- });
79
- });
80
- // ──────────────────────────────────────────────
81
- // Funding accumulation
82
- // ──────────────────────────────────────────────
83
- describe("Funding accumulation tracking", () => {
84
- const baseTime = Date.now();
85
- it("accumulates positive income when spread is favorable", () => {
86
- const pos = {
87
- symbol: "BTC",
88
- longExchange: "hyperliquid", // low funding
89
- shortExchange: "pacifica", // high funding
90
- size: "0.1",
91
- entrySpread: 50,
92
- entryTime: new Date().toISOString(),
93
- entryMarkPrice: 60000,
94
- accumulatedFundingUsd: 0,
95
- lastCheckTime: baseTime,
96
- };
97
- const snap = {
98
- symbol: "BTC",
99
- pacRate: 0.000125, // pac = 0.000125/hr (all hourly now)
100
- hlRate: 0.00005, // hl = 0.00005/hr
101
- ltRate: 0,
102
- spread: 65.7,
103
- longExch: "hyperliquid",
104
- shortExch: "pacifica",
105
- markPrice: 60000,
106
- pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
107
- };
108
- // 1 hour elapsed
109
- const income = accumulateFunding(pos, snap, baseTime + 3600_000);
110
- // shortHourly = 0.000125
111
- // longHourly = 0.00005
112
- // diff = 0.000125 - 0.00005 = 0.000075
113
- // notional = 0.1 * 60000 = 6000
114
- // income = 0.000075 * 6000 * 1 = $0.45/hr
115
- expect(income).toBeCloseTo(0.45, 2);
116
- });
117
- it("returns zero income when no time elapsed", () => {
118
- const pos = {
119
- symbol: "ETH",
120
- longExchange: "lighter",
121
- shortExchange: "pacifica",
122
- size: "1",
123
- entrySpread: 30,
124
- entryTime: new Date().toISOString(),
125
- entryMarkPrice: 3000,
126
- accumulatedFundingUsd: 0,
127
- lastCheckTime: baseTime,
128
- };
129
- const snap = {
130
- symbol: "ETH",
131
- pacRate: 0.0005, // all hourly now
132
- hlRate: 0.0001,
133
- ltRate: 0.0001,
134
- spread: 43.8,
135
- longExch: "lighter",
136
- shortExch: "pacifica",
137
- markPrice: 3000,
138
- pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
139
- };
140
- const income = accumulateFunding(pos, snap, baseTime); // same time
141
- expect(income).toBe(0);
142
- });
143
- it("scales with elapsed time", () => {
144
- const pos = {
145
- symbol: "BTC",
146
- longExchange: "hyperliquid",
147
- shortExchange: "pacifica",
148
- size: "0.1",
149
- entrySpread: 50,
150
- entryTime: new Date().toISOString(),
151
- entryMarkPrice: 60000,
152
- accumulatedFundingUsd: 0,
153
- lastCheckTime: baseTime,
154
- };
155
- const snap = {
156
- symbol: "BTC",
157
- pacRate: 0.000125,
158
- hlRate: 0.00005,
159
- ltRate: 0,
160
- spread: 65.7,
161
- longExch: "hyperliquid",
162
- shortExch: "pacifica",
163
- markPrice: 60000,
164
- pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
165
- };
166
- const income1h = accumulateFunding(pos, snap, baseTime + 3600_000);
167
- const income24h = accumulateFunding(pos, snap, baseTime + 24 * 3600_000);
168
- expect(income24h).toBeCloseTo(income1h * 24, 2);
169
- });
170
- it("can produce negative income if spread reverses", () => {
171
- const pos = {
172
- symbol: "BTC",
173
- longExchange: "pacifica", // originally was low
174
- shortExchange: "hyperliquid", // originally was high
175
- size: "0.1",
176
- entrySpread: 20,
177
- entryTime: new Date().toISOString(),
178
- entryMarkPrice: 60000,
179
- accumulatedFundingUsd: 0,
180
- lastCheckTime: baseTime,
181
- };
182
- // Now PAC rate is HIGHER than HL — bad for our direction
183
- const snap = {
184
- symbol: "BTC",
185
- pacRate: 0.00025, // pac now high: 0.00025/hr
186
- hlRate: 0.00001, // hl now low: 0.00001/hr
187
- ltRate: 0,
188
- spread: 218.3,
189
- longExch: "hyperliquid",
190
- shortExch: "pacifica",
191
- markPrice: 60000,
192
- pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
193
- };
194
- const income = accumulateFunding(pos, snap, baseTime + 3600_000);
195
- // shortHourly (HL) = 0.00001
196
- // longHourly (PAC) = 0.00025
197
- // diff = 0.00001 - 0.00025 = -0.00024
198
- // notional = 6000
199
- // income = -0.00024 * 6000 * 1 = -$1.44
200
- expect(income).toBeLessThan(0);
201
- });
202
- });
203
- // ──────────────────────────────────────────────
204
- // Entry/exit conditions with 3 DEXs
205
- // ──────────────────────────────────────────────
206
- describe("3-DEX entry/exit conditions", () => {
207
- const minSpread = 30;
208
- const closeSpread = 5;
209
- it("enters when any 2-exchange spread exceeds threshold", () => {
210
- // Only PAC and LT available, but spread is large
211
- const absSpread = 45;
212
- expect(absSpread >= minSpread).toBe(true);
213
- });
214
- it("closes when best available spread drops below close threshold", () => {
215
- const currentSpread = 3;
216
- expect(currentSpread <= closeSpread).toBe(true);
217
- });
218
- it("does not enter if max positions reached", () => {
219
- const maxPositions = 3;
220
- const openPositions = 3;
221
- expect(openPositions >= maxPositions).toBe(true);
222
- });
223
- it("skips symbol already in open positions", () => {
224
- const openPositions = [{
225
- symbol: "BTC",
226
- longExchange: "hyperliquid",
227
- shortExchange: "pacifica",
228
- size: "0.1",
229
- entrySpread: 40,
230
- entryTime: new Date().toISOString(),
231
- entryMarkPrice: 60000,
232
- accumulatedFundingUsd: 0.5,
233
- lastCheckTime: Date.now(),
234
- }];
235
- const alreadyOpen = openPositions.some(p => p.symbol === "BTC");
236
- expect(alreadyOpen).toBe(true);
237
- });
238
- });
239
- // ──────────────────────────────────────────────
240
- // Net spread calculation
241
- // ──────────────────────────────────────────────
242
- describe("computeNetSpread", () => {
243
- it("correctly deducts annualized round-trip cost from gross spread", () => {
244
- // gross=30%, hold=7d, roundTrip=0.14% → net = 30 - (0.14/7*365) = 30 - 7.3 = 22.7
245
- const net = computeNetSpread(30, 7, 0.14);
246
- expect(net).toBeCloseTo(22.7, 1);
247
- });
248
- it("returns gross spread when costs are zero", () => {
249
- const net = computeNetSpread(50, 7, 0);
250
- expect(net).toBe(50);
251
- });
252
- it("can produce negative net spread when costs exceed gross", () => {
253
- // gross=5%, hold=1d, roundTrip=0.14% → net = 5 - (0.14*365) = 5 - 51.1 = -46.1
254
- const net = computeNetSpread(5, 1, 0.14);
255
- expect(net).toBeLessThan(0);
256
- });
257
- it("longer hold periods reduce annualized cost impact", () => {
258
- const net7d = computeNetSpread(30, 7, 0.14);
259
- const net30d = computeNetSpread(30, 30, 0.14);
260
- expect(net30d).toBeGreaterThan(net7d);
261
- });
262
- it("includes bridge cost in net spread calculation", () => {
263
- // Without bridge cost
264
- const netNoBridge = computeNetSpread(30, 7, 0.14, 0, 100);
265
- // With $0.50 bridge cost, $100 position
266
- // bridgeRoundTripPct = (0.5 * 2 / 100) * 100 = 1%
267
- // bridgeAnnualized = (1/7) * 365 = 52.14%
268
- const netWithBridge = computeNetSpread(30, 7, 0.14, 0.5, 100);
269
- expect(netWithBridge).toBeLessThan(netNoBridge);
270
- // Difference should be the annualized bridge cost
271
- const bridgeDiff = netNoBridge - netWithBridge;
272
- expect(bridgeDiff).toBeCloseTo(52.14, 0);
273
- });
274
- it("bridge cost impact scales inversely with position size", () => {
275
- const netSmall = computeNetSpread(30, 7, 0.14, 0.5, 50); // $50 position
276
- const netLarge = computeNetSpread(30, 7, 0.14, 0.5, 500); // $500 position
277
- // Larger positions dilute bridge cost
278
- expect(netLarge).toBeGreaterThan(netSmall);
279
- });
280
- });
281
- describe("computeRoundTripCostPct", () => {
282
- it("computes round-trip cost for same-fee exchanges", () => {
283
- // 2 × (0.035% + 0.035%) + 2 × 0.05% = 0.24%
284
- const cost = computeRoundTripCostPct("hyperliquid", "pacifica", 0.05);
285
- expect(cost).toBeCloseTo(0.24, 4);
286
- });
287
- it("uses default slippage of 0.05%", () => {
288
- const cost = computeRoundTripCostPct("hyperliquid", "lighter");
289
- // 2 × (0.035% + 0.035%) + 2 × 0.05% = 0.24%
290
- expect(cost).toBeCloseTo(0.24, 4);
291
- });
292
- it("handles custom slippage", () => {
293
- const cost = computeRoundTripCostPct("hyperliquid", "pacifica", 0.1);
294
- // 2 × (0.035% + 0.035%) + 2 × 0.1% = 0.34%
295
- expect(cost).toBeCloseTo(0.34, 4);
296
- });
297
- });
298
- // ──────────────────────────────────────────────
299
- // Spread reversal detection
300
- // ──────────────────────────────────────────────
301
- describe("Spread reversal detection", () => {
302
- it("detects reversal when long exchange rate exceeds short", () => {
303
- const snap = {
304
- symbol: "BTC",
305
- pacRate: 0.0001, // PAC low (was short, now low)
306
- hlRate: 0.0005, // HL high (was long, now high)
307
- ltRate: 0,
308
- spread: 30,
309
- longExch: "pacifica",
310
- shortExch: "hyperliquid",
311
- markPrice: 60000,
312
- pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
313
- };
314
- // Position: long HL, short PAC — but now HL hourly (0.0005) > PAC hourly (0.0001)
315
- const reversed = isSpreadReversed("hyperliquid", "pacifica", snap);
316
- expect(reversed).toBe(true);
317
- });
318
- it("does not flag reversal when spread is still favorable", () => {
319
- const snap = {
320
- symbol: "BTC",
321
- pacRate: 0.001, // PAC high (0.001/hr)
322
- hlRate: 0.00005, // HL low (0.00005/hr)
323
- ltRate: 0,
324
- spread: 65.7,
325
- longExch: "hyperliquid",
326
- shortExch: "pacifica",
327
- markPrice: 60000,
328
- pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
329
- };
330
- // Position: long HL, short PAC — HL hourly (0.00005) < PAC hourly (0.001) → no reversal
331
- const reversed = isSpreadReversed("hyperliquid", "pacifica", snap);
332
- expect(reversed).toBe(false);
333
- });
334
- it("handles lighter vs pacifica reversal", () => {
335
- const snap = {
336
- symbol: "SOL",
337
- pacRate: 0.0001, // PAC hourly = 0.0001
338
- hlRate: 0,
339
- ltRate: 0.0016, // LT 8h rate, hourly = 0.0016/8 = 0.0002
340
- spread: 10,
341
- longExch: "pacifica",
342
- shortExch: "lighter",
343
- markPrice: 150,
344
- pacMarkPrice: 0, hlMarkPrice: 0, ltMarkPrice: 0,
345
- };
346
- // Position: long LT, short PAC — LT hourly (0.0002) > PAC hourly (0.0001) → reversed
347
- const reversed = isSpreadReversed("lighter", "pacifica", snap);
348
- expect(reversed).toBe(true);
349
- });
350
- });
351
- // ──────────────────────────────────────────────
352
- // Settlement timing awareness
353
- // ──────────────────────────────────────────────
354
- describe("Settlement timing awareness", () => {
355
- it("getNextSettlement returns next hour for HL", () => {
356
- // At 14:30 UTC, next HL settlement is at 15:00 UTC
357
- const now = new Date("2025-01-15T14:30:00Z");
358
- const next = getNextSettlement("hyperliquid", now);
359
- expect(next.getUTCHours()).toBe(15);
360
- expect(next.getUTCMinutes()).toBe(0);
361
- });
362
- it("getNextSettlement returns next hour for PAC (hourly like HL)", () => {
363
- // At 06:00 UTC, next PAC settlement is at 07:00 UTC
364
- const now = new Date("2025-01-15T06:30:00Z");
365
- const next = getNextSettlement("pacifica", now);
366
- expect(next.getUTCHours()).toBe(7);
367
- });
368
- it("getNextSettlement wraps to next day for PAC when at 23:xx", () => {
369
- // At 23:30 UTC, next PAC settlement is at 00:00 next day
370
- const now = new Date("2025-01-15T23:30:00Z");
371
- const next = getNextSettlement("pacifica", now);
372
- expect(next.getUTCHours()).toBe(0);
373
- expect(next.getUTCDate()).toBe(16);
374
- });
375
- it("isNearSettlement blocks entry within 5 minutes of settlement", () => {
376
- // 3 minutes before 08:00 UTC settlement — all exchanges settle hourly now
377
- const now = new Date("2025-01-15T07:57:00Z");
378
- const result = isNearSettlement("lighter", "pacifica", 5, now);
379
- expect(result.blocked).toBe(true);
380
- // Both lighter and pacifica settle at 08:00, lighter checked first
381
- expect(result.exchange).toBe("lighter");
382
- expect(result.minutesUntil).toBeLessThanOrEqual(5);
383
- });
384
- it("isNearSettlement allows entry far from settlement", () => {
385
- // 30 minutes past the hour — next settlement is 30 min away
386
- const now = new Date("2025-01-15T15:30:00Z");
387
- const result = isNearSettlement("lighter", "pacifica", 5, now);
388
- expect(result.blocked).toBe(false);
389
- });
390
- it("isNearSettlement checks both exchanges", () => {
391
- // 2 minutes before hourly HL settlement
392
- const now = new Date("2025-01-15T14:58:00Z");
393
- const result = isNearSettlement("hyperliquid", "pacifica", 5, now);
394
- expect(result.blocked).toBe(true);
395
- expect(result.exchange).toBe("hyperliquid");
396
- });
397
- });