perp-cli 0.12.15 → 0.14.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 (361) hide show
  1. package/README.md +278 -31
  2. package/dist/_stderr-filter.js +0 -1
  3. package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
  4. package/dist/agent-wallet/lighter-keystore.js +92 -10
  5. package/dist/agent-wallet/passphrase.d.ts +1 -1
  6. package/dist/agent-wallet/passphrase.js +16 -3
  7. package/dist/agent-wallet/store.d.ts +37 -4
  8. package/dist/agent-wallet/store.js +132 -0
  9. package/dist/agent-wallet/venue-keystore.d.ts +14 -0
  10. package/dist/agent-wallet/venue-keystore.js +121 -0
  11. package/dist/api/public/hyperliquid.js +4 -4
  12. package/dist/api/public/lighter.js +5 -5
  13. package/dist/api/public/pacifica.d.ts +0 -1
  14. package/dist/api/public/pacifica.js +4 -13
  15. package/dist/api/public/urls.d.ts +9 -1
  16. package/dist/api/public/urls.js +35 -13
  17. package/dist/arb/file-lock.d.ts +11 -0
  18. package/dist/arb/file-lock.js +74 -0
  19. package/dist/arb/index.d.ts +2 -2
  20. package/dist/arb/index.js +2 -2
  21. package/dist/arb/pending-entry.d.ts +32 -0
  22. package/dist/arb/pending-entry.js +77 -0
  23. package/dist/arb/sizing.d.ts +6 -13
  24. package/dist/arb/sizing.js +12 -51
  25. package/dist/arb/state.d.ts +45 -12
  26. package/dist/arb/state.js +214 -42
  27. package/dist/arb/utils.d.ts +20 -6
  28. package/dist/arb/utils.js +43 -39
  29. package/dist/bot/conditions.d.ts +4 -4
  30. package/dist/bot/conditions.js +11 -6
  31. package/dist/bot/config.d.ts +17 -1
  32. package/dist/bot/config.js +48 -12
  33. package/dist/bot/engine.d.ts +61 -2
  34. package/dist/bot/engine.js +610 -144
  35. package/dist/bot/fill-size.d.ts +7 -0
  36. package/dist/bot/fill-size.js +18 -0
  37. package/dist/bot/index.d.ts +1 -1
  38. package/dist/bot/index.js +1 -1
  39. package/dist/bot/perp-order.d.ts +99 -0
  40. package/dist/bot/perp-order.js +299 -0
  41. package/dist/bot/presets.d.ts +7 -0
  42. package/dist/bot/presets.js +17 -0
  43. package/dist/bot/quarantine.d.ts +75 -0
  44. package/dist/bot/quarantine.js +190 -0
  45. package/dist/bot/quote-rules.d.ts +23 -0
  46. package/dist/bot/quote-rules.js +69 -0
  47. package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
  48. package/dist/bot/strategies/avellaneda-mm.js +72 -70
  49. package/dist/bot/strategies/basis-arb.js +2 -0
  50. package/dist/bot/strategies/dca-strategy.d.ts +2 -0
  51. package/dist/bot/strategies/dca-strategy.js +5 -0
  52. package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
  53. package/dist/bot/strategies/funding-arb-strategy.js +571 -380
  54. package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
  55. package/dist/bot/strategies/funding-arb-utils.js +159 -195
  56. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  57. package/dist/bot/strategies/funding-arb-v2.js +258 -47
  58. package/dist/bot/strategies/funding-auto.js +215 -194
  59. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
  60. package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
  61. package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
  62. package/dist/bot/strategies/spot-perp-executor.js +319 -120
  63. package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
  64. package/dist/bot/strategies/spot-perp-state.js +70 -0
  65. package/dist/bot/strategy-types.d.ts +6 -0
  66. package/dist/bot/tui/BotDashboard.d.ts +7 -2
  67. package/dist/bot/tui/BotDashboard.js +6 -3
  68. package/dist/bridge-engine.d.ts +120 -19
  69. package/dist/bridge-engine.js +710 -422
  70. package/dist/bridge-signers.d.ts +54 -0
  71. package/dist/bridge-signers.js +161 -0
  72. package/dist/cache.d.ts +4 -1
  73. package/dist/cache.js +11 -0
  74. package/dist/cli-flags.d.ts +50 -0
  75. package/dist/cli-flags.js +104 -0
  76. package/dist/cli-program.d.ts +35 -0
  77. package/dist/cli-program.js +286 -0
  78. package/dist/cli-spec.js +1 -1
  79. package/dist/client-id-tracker.js +11 -2
  80. package/dist/commands/account.d.ts +7 -1
  81. package/dist/commands/account.js +74 -84
  82. package/dist/commands/agent-import.d.ts +114 -0
  83. package/dist/commands/agent-import.js +794 -0
  84. package/dist/commands/agent.d.ts +232 -2
  85. package/dist/commands/agent.js +1858 -955
  86. package/dist/commands/alerts.d.ts +1 -0
  87. package/dist/commands/alerts.js +31 -28
  88. package/dist/commands/arb/index.d.ts +60 -1
  89. package/dist/commands/arb/index.js +381 -384
  90. package/dist/commands/arb-auto.d.ts +132 -4
  91. package/dist/commands/arb-auto.js +1474 -967
  92. package/dist/commands/arb.d.ts +3 -1
  93. package/dist/commands/arb.js +62 -30
  94. package/dist/commands/bot.js +127 -47
  95. package/dist/commands/bridge.d.ts +2 -1
  96. package/dist/commands/bridge.js +339 -192
  97. package/dist/commands/builder.d.ts +55 -0
  98. package/dist/commands/builder.js +387 -0
  99. package/dist/commands/funds-onchain.d.ts +32 -0
  100. package/dist/commands/funds-onchain.js +286 -0
  101. package/dist/commands/funds.d.ts +25 -0
  102. package/dist/commands/funds.js +939 -245
  103. package/dist/commands/history.js +72 -45
  104. package/dist/commands/init.d.ts +107 -15
  105. package/dist/commands/init.js +1116 -249
  106. package/dist/commands/jobs.d.ts +2 -1
  107. package/dist/commands/jobs.js +77 -15
  108. package/dist/commands/manage.js +136 -91
  109. package/dist/commands/market.d.ts +4 -1
  110. package/dist/commands/market.js +35 -32
  111. package/dist/commands/outcome.d.ts +10 -0
  112. package/dist/commands/outcome.js +396 -0
  113. package/dist/commands/plan.js +52 -12
  114. package/dist/commands/portfolio.d.ts +1 -0
  115. package/dist/commands/portfolio.js +60 -72
  116. package/dist/commands/rebalance.d.ts +2 -1
  117. package/dist/commands/rebalance.js +33 -43
  118. package/dist/commands/risk.js +46 -19
  119. package/dist/commands/settings.d.ts +1 -0
  120. package/dist/commands/settings.js +246 -51
  121. package/dist/commands/trade.d.ts +50 -0
  122. package/dist/commands/trade.js +1943 -695
  123. package/dist/commands/wallet.d.ts +30 -1
  124. package/dist/commands/wallet.js +316 -337
  125. package/dist/config.d.ts +60 -9
  126. package/dist/config.js +252 -82
  127. package/dist/constants.d.ts +29 -4
  128. package/dist/constants.js +62 -20
  129. package/dist/dashboard/server.d.ts +2 -0
  130. package/dist/dashboard/server.js +9 -6
  131. package/dist/dashboard/ui.js +6 -4
  132. package/dist/dashboard/ws-feeds.js +20 -8
  133. package/dist/dex-asset-map.js +5 -3
  134. package/dist/errors.d.ts +122 -0
  135. package/dist/errors.js +194 -0
  136. package/dist/event-stream.js +75 -19
  137. package/dist/evm-master-tx.d.ts +59 -0
  138. package/dist/evm-master-tx.js +154 -0
  139. package/dist/exchanges/adapter-network.d.ts +10 -0
  140. package/dist/exchanges/adapter-network.js +10 -0
  141. package/dist/exchanges/arcus/adapter.d.ts +196 -0
  142. package/dist/exchanges/arcus/adapter.js +1092 -0
  143. package/dist/exchanges/arcus/agent.d.ts +55 -0
  144. package/dist/exchanges/arcus/agent.js +188 -0
  145. package/dist/exchanges/arcus/client.d.ts +63 -0
  146. package/dist/exchanges/arcus/client.js +285 -0
  147. package/dist/exchanges/arcus/public.d.ts +65 -0
  148. package/dist/exchanges/arcus/public.js +138 -0
  149. package/dist/exchanges/arcus/signing.d.ts +176 -0
  150. package/dist/exchanges/arcus/signing.js +288 -0
  151. package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
  152. package/dist/exchanges/arcus/spot-chain.js +241 -0
  153. package/dist/exchanges/arcus/spot-router.d.ts +177 -0
  154. package/dist/exchanges/arcus/spot-router.js +183 -0
  155. package/dist/exchanges/arcus/spot.d.ts +109 -0
  156. package/dist/exchanges/arcus/spot.js +645 -0
  157. package/dist/exchanges/aster-funds.d.ts +127 -0
  158. package/dist/exchanges/aster-funds.js +252 -0
  159. package/dist/exchanges/aster-nonce.d.ts +2 -0
  160. package/dist/exchanges/aster-nonce.js +107 -0
  161. package/dist/exchanges/aster-spot.d.ts +117 -0
  162. package/dist/exchanges/aster-spot.js +686 -0
  163. package/dist/exchanges/aster-typed-data.d.ts +133 -7
  164. package/dist/exchanges/aster-typed-data.js +121 -7
  165. package/dist/exchanges/aster.d.ts +219 -6
  166. package/dist/exchanges/aster.js +1023 -153
  167. package/dist/exchanges/builder-fee.d.ts +254 -0
  168. package/dist/exchanges/builder-fee.js +421 -0
  169. package/dist/exchanges/canonical-symbols.d.ts +32 -0
  170. package/dist/exchanges/canonical-symbols.js +96 -0
  171. package/dist/exchanges/capabilities.d.ts +7 -1
  172. package/dist/exchanges/capabilities.js +7 -0
  173. package/dist/exchanges/hl-signing.d.ts +165 -0
  174. package/dist/exchanges/hl-signing.js +158 -0
  175. package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
  176. package/dist/exchanges/hyperliquid-outcome.js +646 -0
  177. package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
  178. package/dist/exchanges/hyperliquid-spot.js +308 -117
  179. package/dist/exchanges/hyperliquid.d.ts +462 -57
  180. package/dist/exchanges/hyperliquid.js +1575 -449
  181. package/dist/exchanges/index.d.ts +6 -0
  182. package/dist/exchanges/index.js +4 -0
  183. package/dist/exchanges/interface.d.ts +63 -10
  184. package/dist/exchanges/interface.js +18 -3
  185. package/dist/exchanges/ioc-outcome.d.ts +44 -0
  186. package/dist/exchanges/ioc-outcome.js +57 -0
  187. package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
  188. package/dist/exchanges/legacy-trading-v2.js +940 -0
  189. package/dist/exchanges/lighter-public.d.ts +43 -0
  190. package/dist/exchanges/lighter-public.js +276 -0
  191. package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
  192. package/dist/exchanges/lighter-rh-deposit.js +185 -0
  193. package/dist/exchanges/lighter-signer.d.ts +123 -0
  194. package/dist/exchanges/lighter-signer.js +215 -0
  195. package/dist/exchanges/lighter-spot.d.ts +91 -13
  196. package/dist/exchanges/lighter-spot.js +595 -233
  197. package/dist/exchanges/lighter.d.ts +611 -39
  198. package/dist/exchanges/lighter.js +1890 -649
  199. package/dist/exchanges/ondo/adapter.d.ts +166 -0
  200. package/dist/exchanges/ondo/adapter.js +835 -0
  201. package/dist/exchanges/ondo/agent.d.ts +31 -0
  202. package/dist/exchanges/ondo/agent.js +101 -0
  203. package/dist/exchanges/ondo/client.d.ts +60 -0
  204. package/dist/exchanges/ondo/client.js +297 -0
  205. package/dist/exchanges/ondo/funds.d.ts +122 -0
  206. package/dist/exchanges/ondo/funds.js +290 -0
  207. package/dist/exchanges/ondo/public.d.ts +95 -0
  208. package/dist/exchanges/ondo/public.js +214 -0
  209. package/dist/exchanges/ondo/spot.d.ts +127 -0
  210. package/dist/exchanges/ondo/spot.js +392 -0
  211. package/dist/exchanges/outcome-interface.d.ts +140 -0
  212. package/dist/exchanges/outcome-interface.js +16 -0
  213. package/dist/exchanges/pacifica-spot.d.ts +83 -0
  214. package/dist/exchanges/pacifica-spot.js +324 -0
  215. package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
  216. package/dist/exchanges/pacifica-typed-data.js +57 -9
  217. package/dist/exchanges/pacifica.d.ts +288 -4
  218. package/dist/exchanges/pacifica.js +1013 -76
  219. package/dist/exchanges/precision.d.ts +47 -0
  220. package/dist/exchanges/precision.js +209 -0
  221. package/dist/exchanges/qfex/adapter.d.ts +341 -0
  222. package/dist/exchanges/qfex/adapter.js +1383 -0
  223. package/dist/exchanges/qfex/agent.d.ts +58 -0
  224. package/dist/exchanges/qfex/agent.js +188 -0
  225. package/dist/exchanges/qfex/client.d.ts +54 -0
  226. package/dist/exchanges/qfex/client.js +138 -0
  227. package/dist/exchanges/qfex/errors.d.ts +70 -0
  228. package/dist/exchanges/qfex/errors.js +236 -0
  229. package/dist/exchanges/qfex/public.d.ts +122 -0
  230. package/dist/exchanges/qfex/public.js +268 -0
  231. package/dist/exchanges/qfex/signing.d.ts +160 -0
  232. package/dist/exchanges/qfex/signing.js +283 -0
  233. package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
  234. package/dist/exchanges/qfex/ws-transport.js +798 -0
  235. package/dist/exchanges/rate-limiter.d.ts +12 -0
  236. package/dist/exchanges/rate-limiter.js +64 -0
  237. package/dist/exchanges/registry.d.ts +14 -2
  238. package/dist/exchanges/registry.js +20 -32
  239. package/dist/exchanges/slippage.d.ts +37 -0
  240. package/dist/exchanges/slippage.js +70 -0
  241. package/dist/exchanges/spot-factory.d.ts +166 -0
  242. package/dist/exchanges/spot-factory.js +431 -0
  243. package/dist/exchanges/spot-interface.d.ts +98 -4
  244. package/dist/exchanges/spot-interface.js +27 -1
  245. package/dist/exchanges/spot-result.d.ts +79 -0
  246. package/dist/exchanges/spot-result.js +273 -0
  247. package/dist/exchanges/trading-v2.d.ts +117 -0
  248. package/dist/exchanges/trading-v2.js +301 -0
  249. package/dist/exchanges/venue-types.d.ts +231 -0
  250. package/dist/exchanges/venue-types.js +87 -0
  251. package/dist/exchanges/venue.d.ts +211 -0
  252. package/dist/exchanges/venue.js +132 -0
  253. package/dist/exchanges/venues/arcus.d.ts +8 -0
  254. package/dist/exchanges/venues/arcus.js +109 -0
  255. package/dist/exchanges/venues/aster.d.ts +49 -0
  256. package/dist/exchanges/venues/aster.js +176 -0
  257. package/dist/exchanges/venues/create-helpers.d.ts +18 -0
  258. package/dist/exchanges/venues/create-helpers.js +199 -0
  259. package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
  260. package/dist/exchanges/venues/hyperliquid.js +126 -0
  261. package/dist/exchanges/venues/index.d.ts +16 -0
  262. package/dist/exchanges/venues/index.js +26 -0
  263. package/dist/exchanges/venues/lighter-family.d.ts +92 -0
  264. package/dist/exchanges/venues/lighter-family.js +166 -0
  265. package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
  266. package/dist/exchanges/venues/lighter-rh.js +58 -0
  267. package/dist/exchanges/venues/lighter.d.ts +6 -0
  268. package/dist/exchanges/venues/lighter.js +55 -0
  269. package/dist/exchanges/venues/ondo.d.ts +34 -0
  270. package/dist/exchanges/venues/ondo.js +140 -0
  271. package/dist/exchanges/venues/pacifica.d.ts +8 -0
  272. package/dist/exchanges/venues/pacifica.js +95 -0
  273. package/dist/exchanges/venues/qfex.d.ts +28 -0
  274. package/dist/exchanges/venues/qfex.js +132 -0
  275. package/dist/execution-log.js +10 -2
  276. package/dist/funding/legacy-public.d.ts +31 -0
  277. package/dist/funding/legacy-public.js +369 -0
  278. package/dist/funding/normalize.d.ts +28 -16
  279. package/dist/funding/normalize.js +61 -29
  280. package/dist/funding/rates.d.ts +46 -11
  281. package/dist/funding/rates.js +228 -277
  282. package/dist/guardrail/perp-guardrail.js +0 -0
  283. package/dist/index.js +157 -682
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +14 -0
  287. package/dist/landing.js +36 -0
  288. package/dist/mcp-server.d.ts +14 -4
  289. package/dist/mcp-server.js +820 -402
  290. package/dist/mcp-trade-guard.d.ts +16 -0
  291. package/dist/mcp-trade-guard.js +59 -0
  292. package/dist/pacifica/client.d.ts +41 -10
  293. package/dist/pacifica/client.js +167 -63
  294. package/dist/pacifica/constants.d.ts +0 -1
  295. package/dist/pacifica/constants.js +0 -5
  296. package/dist/pacifica/deposit.d.ts +30 -3
  297. package/dist/pacifica/deposit.js +119 -2
  298. package/dist/pacifica/faucet.d.ts +29 -0
  299. package/dist/pacifica/faucet.js +100 -0
  300. package/dist/pacifica/sign-tx.d.ts +15 -0
  301. package/dist/pacifica/sign-tx.js +41 -0
  302. package/dist/pacifica/types/account.d.ts +28 -4
  303. package/dist/pacifica/types/lake.d.ts +20 -5
  304. package/dist/pacifica/types/lake.js +4 -0
  305. package/dist/pacifica/types/market.d.ts +5 -0
  306. package/dist/pacifica/types/order.d.ts +4 -1
  307. package/dist/pacifica/types/position.d.ts +9 -1
  308. package/dist/plan-executor.d.ts +10 -5
  309. package/dist/plan-executor.js +165 -54
  310. package/dist/position-history.js +13 -0
  311. package/dist/rebalance.d.ts +26 -2
  312. package/dist/rebalance.js +86 -7
  313. package/dist/relay-swap.d.ts +232 -0
  314. package/dist/relay-swap.js +630 -0
  315. package/dist/risk.js +5 -1
  316. package/dist/secret-env.d.ts +15 -0
  317. package/dist/secret-env.js +36 -0
  318. package/dist/settings.d.ts +169 -6
  319. package/dist/settings.js +219 -38
  320. package/dist/signer/ows-evm.d.ts +13 -0
  321. package/dist/signer/ows-evm.js +22 -0
  322. package/dist/smart-order.d.ts +9 -0
  323. package/dist/smart-order.js +53 -8
  324. package/dist/strategies/dca.d.ts +6 -2
  325. package/dist/strategies/dca.js +66 -26
  326. package/dist/strategies/funding-arb.d.ts +11 -1
  327. package/dist/strategies/funding-arb.js +202 -89
  328. package/dist/strategies/grid.d.ts +31 -0
  329. package/dist/strategies/grid.js +89 -11
  330. package/dist/strategies/pending-stop.d.ts +8 -0
  331. package/dist/strategies/pending-stop.js +12 -0
  332. package/dist/strategies/split-order.d.ts +45 -17
  333. package/dist/strategies/split-order.js +157 -91
  334. package/dist/strategies/trailing-stop.d.ts +2 -1
  335. package/dist/strategies/trailing-stop.js +52 -6
  336. package/dist/strategies/twap.d.ts +6 -1
  337. package/dist/strategies/twap.js +39 -11
  338. package/dist/trade-validator.d.ts +22 -0
  339. package/dist/trade-validator.js +119 -15
  340. package/dist/utils/numeric.d.ts +46 -0
  341. package/dist/utils/numeric.js +79 -0
  342. package/dist/utils.d.ts +19 -0
  343. package/dist/utils.js +96 -19
  344. package/dist/withdraw-gate.d.ts +49 -0
  345. package/dist/withdraw-gate.js +131 -0
  346. package/package.json +15 -6
  347. package/skills/perp-cli/SKILL.md +338 -134
  348. package/skills/perp-cli/references/agent-operations.md +181 -92
  349. package/skills/perp-cli/references/commands.md +475 -132
  350. package/skills/perp-cli/references/strategies.md +44 -38
  351. package/skills/perp-cli/scripts/arb-monitor.sh +22 -17
  352. package/skills/perp-cli/scripts/funding-analysis.sh +20 -10
  353. package/skills/perp-cli/scripts/preflight.sh +21 -14
  354. package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
  355. package/skills/perp-cli/scripts/validate-arb.sh +33 -16
  356. package/vendor/lighter-signer/SHA256SUMS +2 -0
  357. package/vendor/lighter-signer/VERSION +8 -0
  358. package/vendor/lighter-signer/lighter-signer.wasm +0 -0
  359. package/vendor/lighter-signer/wasm_exec.js +575 -0
  360. package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
  361. package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Ondo Perps API-key ("agent") registration (design §10.4).
3
+ *
4
+ * The HMAC key id + secret are created by the user in the Ondo Perps UI and
5
+ * entered via stdin by the CLI (never flags). The secret is persisted only in
6
+ * ~/.perp/ondo-agents/<keyId>.json (AES-256-GCM, 0600; SSOT Rule #3); settings
7
+ * keep the OndoAgentMeta pointer only.
8
+ */
9
+ import type { OndoAgentMeta } from "../../settings.js";
10
+ import type { Network } from "../venue-types.js";
11
+ import { type OndoCredentials } from "./client.js";
12
+ export declare const ONDO_AGENTS_DIR = "ondo-agents";
13
+ export declare const ONDO_APPROVE_REMEDIATION = "perp wallet agent approve ondo";
14
+ /** Read + decrypt the stored key for `keyId`. Throws KEY_NOT_FOUND / SIGNATURE_FAILED. */
15
+ export declare function loadOndoCredentials(keyId: string): OndoCredentials;
16
+ /**
17
+ * Verify an API key against the venue (authenticated GET /v1/account), reject
18
+ * accounts whose perps / API-key functionality is disabled, then store it (scopes unknown —
19
+ * see below). Nothing is written if any step fails.
20
+ */
21
+ export declare function approveOndoAgent(opts: {
22
+ agentName: string;
23
+ keyId: string;
24
+ secret: string;
25
+ network: Network;
26
+ }): Promise<OndoAgentMeta>;
27
+ /**
28
+ * Delete the local keystore for `keyId`. Returns true if a file was removed.
29
+ * The key itself stays valid on the venue — revoke it in the Ondo Perps UI.
30
+ */
31
+ export declare function revokeOndoAgentLocal(keyId: string): boolean;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Ondo Perps API-key ("agent") registration (design §10.4).
3
+ *
4
+ * The HMAC key id + secret are created by the user in the Ondo Perps UI and
5
+ * entered via stdin by the CLI (never flags). The secret is persisted only in
6
+ * ~/.perp/ondo-agents/<keyId>.json (AES-256-GCM, 0600; SSOT Rule #3); settings
7
+ * keep the OndoAgentMeta pointer only.
8
+ */
9
+ import { deleteVenueSecret, loadVenueSecret, saveVenueSecret } from "../../agent-wallet/venue-keystore.js";
10
+ import { PerpError } from "../../errors.js";
11
+ import { networkConfig } from "../venue.js";
12
+ import { ondoVenue } from "../venues/ondo.js";
13
+ import { OndoClient } from "./client.js";
14
+ export const ONDO_AGENTS_DIR = "ondo-agents";
15
+ export const ONDO_APPROVE_REMEDIATION = "perp wallet agent approve ondo";
16
+ const KEY_ID_RE = /^ondoKeyId_[A-Za-z0-9_-]+$/;
17
+ const SECRET_RE = /^ondoApiSecret_\S+$/;
18
+ function assertCredentialFormat(keyId, secret) {
19
+ if (!KEY_ID_RE.test(keyId)) {
20
+ throw new PerpError("INVALID_PARAMS", `Ondo key id must look like "ondoKeyId_…" (got "${keyId.slice(0, 16)}…")`, {
21
+ remediation: "Copy the key id exactly as shown in Ondo Perps → API Keys, including the ondoKeyId_ prefix",
22
+ });
23
+ }
24
+ if (!SECRET_RE.test(secret)) {
25
+ throw new PerpError("INVALID_PARAMS", `Ondo API secret must start with "ondoApiSecret_"`, {
26
+ remediation: "Copy the secret exactly as shown when the key was created, including the ondoApiSecret_ prefix",
27
+ });
28
+ }
29
+ }
30
+ /** Read + decrypt the stored key for `keyId`. Throws KEY_NOT_FOUND / SIGNATURE_FAILED. */
31
+ export function loadOndoCredentials(keyId) {
32
+ const plaintext = loadVenueSecret(ONDO_AGENTS_DIR, keyId, "", ONDO_APPROVE_REMEDIATION);
33
+ let parsed;
34
+ try {
35
+ parsed = JSON.parse(plaintext);
36
+ }
37
+ catch (e) {
38
+ throw new PerpError("SIGNATURE_FAILED", `Ondo keystore for ${keyId} is not valid JSON: ${e instanceof Error ? e.message : String(e)}`, {
39
+ remediation: ONDO_APPROVE_REMEDIATION,
40
+ });
41
+ }
42
+ const r = parsed;
43
+ if (!r || typeof r.keyId !== "string" || typeof r.secret !== "string") {
44
+ throw new PerpError("SIGNATURE_FAILED", `Ondo keystore for ${keyId} is missing keyId/secret`, { remediation: ONDO_APPROVE_REMEDIATION });
45
+ }
46
+ if (r.keyId !== keyId) {
47
+ throw new PerpError("SIGNATURE_FAILED", `Ondo keystore for ${keyId} holds a different key id (${r.keyId})`, {
48
+ remediation: ONDO_APPROVE_REMEDIATION,
49
+ });
50
+ }
51
+ return { keyId: r.keyId, secret: r.secret };
52
+ }
53
+ /**
54
+ * Verify an API key against the venue (authenticated GET /v1/account), reject
55
+ * accounts whose perps / API-key functionality is disabled, then store it (scopes unknown —
56
+ * see below). Nothing is written if any step fails.
57
+ */
58
+ export async function approveOndoAgent(opts) {
59
+ const keyId = opts.keyId.trim();
60
+ const secret = opts.secret.trim();
61
+ assertCredentialFormat(keyId, secret);
62
+ const client = new OndoClient({
63
+ restUrl: networkConfig(ondoVenue, opts.network).restUrl,
64
+ network: opts.network,
65
+ rateLimit: ondoVenue.rateLimit,
66
+ credentials: { keyId, secret },
67
+ });
68
+ const account = await client.get("/v1/account", null);
69
+ if (account.accountState !== "open") {
70
+ throw new PerpError("APPROVE_FAILED", `Ondo Perps account ${account.accountID} is ${account.accountState}`, {
71
+ remediation: "Resolve the account state in the Ondo Perps UI, then retry",
72
+ });
73
+ }
74
+ const disabled = account.disabledFunctionality;
75
+ if (disabled !== undefined && (disabled.disablePerps === true || disabled.disableAPIKeys === true)) {
76
+ const what = [disabled.disablePerps === true ? "perps" : null, disabled.disableAPIKeys === true ? "API keys" : null].filter(Boolean).join(" and ");
77
+ throw new PerpError("APPROVE_FAILED", `Ondo Perps account ${account.accountID} has ${what} disabled`, {
78
+ remediation: "Check the account's status in the Ondo Perps UI (region / compliance restrictions), then retry",
79
+ });
80
+ }
81
+ // Scopes are not readable with the key itself: GET /v1/api_keys answers API-key auth with 403
82
+ // key_not_allowed_for_action (live mainnet 2026-10-05, although the OpenAPI spec lists ApiKeyAuth
83
+ // for it). They are recorded as unknown; a missing scope surfaces on the first call that needs it.
84
+ const keystorePath = saveVenueSecret(ONDO_AGENTS_DIR, keyId, JSON.stringify({ keyId, secret }), "");
85
+ return {
86
+ kind: "ondo",
87
+ agentName: opts.agentName,
88
+ keyId,
89
+ keystorePath,
90
+ scopes: null,
91
+ network: opts.network,
92
+ approvedAt: new Date().toISOString(),
93
+ };
94
+ }
95
+ /**
96
+ * Delete the local keystore for `keyId`. Returns true if a file was removed.
97
+ * The key itself stays valid on the venue — revoke it in the Ondo Perps UI.
98
+ */
99
+ export function revokeOndoAgentLocal(keyId) {
100
+ return deleteVenueSecret(ONDO_AGENTS_DIR, keyId);
101
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Ondo Perps raw REST client (design §9.2: raw fetch + node:crypto HMAC, no SDK).
3
+ *
4
+ * Auth (live-verified, scratchpad/sdk-findings.md §2 — the OpenAPI spec's
5
+ * `X-API-KEY-ID` header name is wrong):
6
+ * ONDO-KEY-ID key id including the "ondoKeyId_" prefix
7
+ * ONDO-TIMESTAMP ms since epoch. Docs (api-reference/api_key_authentication): "this may be
8
+ * at most 10 seconds behind or 1 second ahead of server time."
9
+ * ONDO-SIGN hex(HMAC-SHA256(secret incl. "ondoApiSecret_" prefix,
10
+ * timestamp + METHOD + path?query + body))
11
+ * The signed `path?query` is byte-identical to the request target sent on
12
+ * the wire (URLSearchParams-encoded), matching the docs' "requestPath: full
13
+ * path and query parameters of the URL" / Go `req.URL.RequestURI()`.
14
+ *
15
+ * Envelope: `{ success, result, error?, error_code?, pageInfo? }`. `pageInfo`
16
+ * is a sibling of `result` on paginated endpoints.
17
+ *
18
+ * Every request first acquires the venue rate-limit bucket (1 req/s default).
19
+ * No retries, no fallbacks (SSOT Rule #2): HTTP 429 → RATE_LIMITED, any other
20
+ * failure → PerpError carrying the venue error code.
21
+ */
22
+ import type { RateLimitPolicy } from "../venue.js";
23
+ import type { Network } from "../venue-types.js";
24
+ export interface OndoCredentials {
25
+ keyId: string;
26
+ secret: string;
27
+ }
28
+ export interface OndoClientOptions {
29
+ restUrl: string;
30
+ network: Network;
31
+ rateLimit: RateLimitPolicy;
32
+ /** null → public endpoints only. */
33
+ credentials: OndoCredentials | null;
34
+ }
35
+ export type OndoQuery = Record<string, string | number | boolean | null>;
36
+ export interface OndoPage<T> {
37
+ result: T;
38
+ nextCursor: string | null;
39
+ }
40
+ /** Per-request timeout (fetch + body read). */
41
+ export declare const ONDO_REQUEST_TIMEOUT_MS = 15000;
42
+ export declare class OndoClient {
43
+ private readonly opts;
44
+ private readonly restUrl;
45
+ private readonly rateKey;
46
+ constructor(opts: OndoClientOptions);
47
+ get hasCredentials(): boolean;
48
+ publicGet<T>(path: string, query: OndoQuery | null): Promise<T>;
49
+ publicGetPage<T>(path: string, query: OndoQuery | null): Promise<OndoPage<T>>;
50
+ get<T>(path: string, query: OndoQuery | null): Promise<T>;
51
+ getPage<T>(path: string, query: OndoQuery | null): Promise<OndoPage<T>>;
52
+ post<T>(path: string, body: unknown): Promise<T>;
53
+ delete<T>(path: string, query: OndoQuery | null): Promise<T>;
54
+ /** Endpoints that answer with a bare GenericResponse (no `result`), e.g. POST /v1/perps/leverage. */
55
+ postNoResult(path: string, body: unknown): Promise<void>;
56
+ /** Endpoints that answer with a bare GenericResponse (no `result`), e.g. DELETE /v1/perps/orders. */
57
+ deleteNoResult(path: string, query: OndoQuery | null): Promise<void>;
58
+ private sign;
59
+ private request;
60
+ }
@@ -0,0 +1,297 @@
1
+ /**
2
+ * Ondo Perps raw REST client (design §9.2: raw fetch + node:crypto HMAC, no SDK).
3
+ *
4
+ * Auth (live-verified, scratchpad/sdk-findings.md §2 — the OpenAPI spec's
5
+ * `X-API-KEY-ID` header name is wrong):
6
+ * ONDO-KEY-ID key id including the "ondoKeyId_" prefix
7
+ * ONDO-TIMESTAMP ms since epoch. Docs (api-reference/api_key_authentication): "this may be
8
+ * at most 10 seconds behind or 1 second ahead of server time."
9
+ * ONDO-SIGN hex(HMAC-SHA256(secret incl. "ondoApiSecret_" prefix,
10
+ * timestamp + METHOD + path?query + body))
11
+ * The signed `path?query` is byte-identical to the request target sent on
12
+ * the wire (URLSearchParams-encoded), matching the docs' "requestPath: full
13
+ * path and query parameters of the URL" / Go `req.URL.RequestURI()`.
14
+ *
15
+ * Envelope: `{ success, result, error?, error_code?, pageInfo? }`. `pageInfo`
16
+ * is a sibling of `result` on paginated endpoints.
17
+ *
18
+ * Every request first acquires the venue rate-limit bucket (1 req/s default).
19
+ * No retries, no fallbacks (SSOT Rule #2): HTTP 429 → RATE_LIMITED, any other
20
+ * failure → PerpError carrying the venue error code.
21
+ */
22
+ import { createHmac } from "node:crypto";
23
+ import { PerpError, orderOutcomeUnknown } from "../../errors.js";
24
+ import { acquireRate, rateLimitedError } from "../rate-limiter.js";
25
+ /** Venue error code → CLI error code. Codes not listed map to EXCHANGE_ERROR. */
26
+ const ERROR_CODE_MAP = {
27
+ // margin / balance
28
+ insufficient_margin: "MARGIN_INSUFFICIENT",
29
+ account_in_liquidation: "MARGIN_INSUFFICIENT",
30
+ insufficient_funds: "INSUFFICIENT_BALANCE",
31
+ withdrawal_error_insufficient_balance: "INSUFFICIENT_BALANCE",
32
+ // not found
33
+ order_not_found: "ORDER_NOT_FOUND",
34
+ twap_order_not_found: "ORDER_NOT_FOUND",
35
+ invalid_symbol: "SYMBOL_NOT_FOUND",
36
+ invalid_market: "SYMBOL_NOT_FOUND",
37
+ // auth
38
+ api_key_not_found: "KEY_NOT_FOUND",
39
+ auth_missing: "NO_SIGNER_AVAILABLE",
40
+ auth_invalid: "SIGNATURE_FAILED",
41
+ auth_expired: "SIGNATURE_FAILED",
42
+ signature_mismatch: "SIGNATURE_FAILED",
43
+ failed_to_decode_hex_signature: "SIGNATURE_FAILED",
44
+ failed_to_parse_timestamp: "SIGNATURE_FAILED",
45
+ timestamp_too_far: "SIGNATURE_FAILED",
46
+ key_doesnt_have_scope: "POLICY_DENIED",
47
+ ip_not_permitted: "POLICY_DENIED",
48
+ // rate limit
49
+ too_many_requests: "RATE_LIMITED",
50
+ // price / size precision
51
+ order_invalid_price: "INVALID_TICK",
52
+ stop_order_trigger_price_invalid_increment: "INVALID_TICK",
53
+ order_invalid_size: "INVALID_PARAMS",
54
+ // invalid parameters
55
+ bad_query_param: "INVALID_PARAMS",
56
+ invalid_cursor: "INVALID_PARAMS",
57
+ invalid_leverage: "INVALID_PARAMS",
58
+ invalid_market_order_fields: "INVALID_PARAMS",
59
+ invalid_order_type: "INVALID_PARAMS",
60
+ invalid_stop_order: "INVALID_PARAMS",
61
+ invalid_time_in_force: "INVALID_PARAMS",
62
+ invalid_network: "INVALID_PARAMS",
63
+ invalid_withdrawal_network: "INVALID_PARAMS",
64
+ withdrawal_invalid_amount: "INVALID_PARAMS",
65
+ withdrawal_amount_too_small: "SIZE_TOO_SMALL",
66
+ withdrawal_missing_customer_withdrawal_id: "INVALID_PARAMS",
67
+ withdrawal_address_not_found: "INVALID_PARAMS",
68
+ internal_withdrawal_address: "INVALID_PARAMS",
69
+ withdrawal_not_found: "INVALID_PARAMS",
70
+ // OpenAPI name; `transfer_amount_too_precise` kept as the earlier observed alias.
71
+ transfer_amount_precision_overflow: "INVALID_PARAMS",
72
+ transfer_amount_too_precise: "INVALID_PARAMS",
73
+ bad_withdrawal_address: "INVALID_PARAMS",
74
+ clientOrderID_chars_invalid: "INVALID_PARAMS",
75
+ clientOrderID_length_invalid: "INVALID_PARAMS",
76
+ order_negative_price: "INVALID_PARAMS",
77
+ order_negative_size: "INVALID_PARAMS",
78
+ order_price_outside_min_max_bounds: "INVALID_PARAMS",
79
+ order_price_outside_safe_bounds: "INVALID_PARAMS",
80
+ post_only_has_match: "INVALID_PARAMS",
81
+ reduce_only_flipping_position: "INVALID_PARAMS",
82
+ reduce_only_increasing_position: "INVALID_PARAMS",
83
+ reduce_only_invalid_tif: "INVALID_PARAMS",
84
+ reduce_only_no_open_position: "POSITION_NOT_FOUND",
85
+ stop_order_crosses_mark_price: "INVALID_PARAMS",
86
+ stop_order_crosses_order_price: "INVALID_PARAMS",
87
+ stop_order_trigger_price_exceeds_max: "INVALID_PARAMS",
88
+ stop_order_trigger_price_negative: "INVALID_PARAMS",
89
+ order_already_canceled: "INVALID_PARAMS",
90
+ order_already_fully_filled: "INVALID_PARAMS",
91
+ order_not_in_cancelable_state: "INVALID_PARAMS",
92
+ batch_order_empty: "INVALID_PARAMS",
93
+ batch_order_too_many_orders: "INVALID_PARAMS",
94
+ batch_cancel_empty: "INVALID_PARAMS",
95
+ batch_cancel_too_many_orders: "INVALID_PARAMS",
96
+ withdrawal_duplicate_customer_withdrawal_id: "DUPLICATE_ORDER",
97
+ clientOrderID_collision: "DUPLICATE_ORDER",
98
+ // market state
99
+ mark_price_not_available: "PRICE_STALE",
100
+ insufficient_liquidity: "ORDER_REJECTED",
101
+ market_orders_disabled: "CAPABILITY_UNSUPPORTED",
102
+ trading_disabled: "CAPABILITY_UNSUPPORTED",
103
+ feature_disabled: "CAPABILITY_UNSUPPORTED",
104
+ // transient venue-side withdrawal failures
105
+ withdrawal_try_later: "EXCHANGE_ERROR",
106
+ withdrawal_system_error: "EXCHANGE_ERROR",
107
+ withdrawal_failed: "EXCHANGE_ERROR",
108
+ // risk / limits
109
+ too_many_open_orders: "RISK_VIOLATION",
110
+ withdrawal_limit_exceeded: "RISK_VIOLATION",
111
+ order_matches_against_self: "RISK_VIOLATION",
112
+ net_position_too_large: "SIZE_TOO_LARGE",
113
+ };
114
+ /** Venue-code-specific remediation; wins over the per-ErrorCode REMEDIATION. */
115
+ const VENUE_REMEDIATION = {
116
+ bad_withdrawal_address: "Withdrawals go only to addresses in your Ondo Perps address book — add it in the Ondo Perps UI (Withdraw → address book) and wait out any cooldown",
117
+ withdrawal_address_not_found: "Withdrawals go only to addresses in your Ondo Perps address book — add it in the Ondo Perps UI (Withdraw → address book) and wait out any cooldown",
118
+ internal_withdrawal_address: "That address belongs to an Ondo Perps account — use an external address from your address book",
119
+ invalid_withdrawal_network: "Use a network the address book entry supports (avalanche | ethereum | solana)",
120
+ withdrawal_amount_too_small: "Increase the amount above the venue minimum (the withdrawal fee is shown by: perp account -e ondo)",
121
+ withdrawal_try_later: "The venue asked to retry later; check withdrawal history before resubmitting",
122
+ transfer_amount_precision_overflow: "Round the amount to the asset's precision (USDC: 6 decimals)",
123
+ transfer_amount_too_precise: "Round the amount to the asset's precision (USDC: 6 decimals)",
124
+ clientOrderID_collision: "Use a new client order id (it must be unique per account)",
125
+ clientOrderID_chars_invalid: "Client order ids may use letters, digits, '-' and '_' only",
126
+ clientOrderID_length_invalid: "Shorten the client order id",
127
+ net_position_too_large: "Reduce the size: the resulting net position exceeds the market's limit",
128
+ insufficient_liquidity: "Not enough resting liquidity — use a smaller size or a limit order",
129
+ market_orders_disabled: "Market orders are disabled on this market — place a limit order",
130
+ trading_disabled: "Trading is disabled on this market right now — check the market status (perp market list -e ondo)",
131
+ feature_disabled: "This feature is disabled for the account — check the Ondo Perps UI",
132
+ mark_price_not_available: "Mark price is unavailable — retry shortly",
133
+ };
134
+ const REMEDIATION = {
135
+ KEY_NOT_FOUND: "Re-register the Ondo API key: perp wallet agent approve ondo",
136
+ NO_SIGNER_AVAILABLE: "Register an Ondo API key: perp wallet agent approve ondo",
137
+ SIGNATURE_FAILED: "Check the system clock (the timestamp may be at most 10 s behind or 1 s ahead of server time) and re-register the key: perp wallet agent approve ondo",
138
+ POLICY_DENIED: "Grant the key the required scope (trade / transfer) or whitelist this IP in the Ondo Perps UI, then re-approve",
139
+ MARGIN_INSUFFICIENT: "Reduce size/leverage or deposit more collateral",
140
+ INSUFFICIENT_BALANCE: "Deposit more USDC or reduce the amount",
141
+ SYMBOL_NOT_FOUND: "List markets with: perp market list -e ondo",
142
+ INVALID_TICK: "Align price/size with the market tick and step (perp market list -e ondo)",
143
+ };
144
+ /** Per-request timeout (fetch + body read). */
145
+ export const ONDO_REQUEST_TIMEOUT_MS = 15_000;
146
+ export class OndoClient {
147
+ opts;
148
+ restUrl;
149
+ rateKey;
150
+ constructor(opts) {
151
+ this.opts = opts;
152
+ this.restUrl = opts.restUrl.replace(/\/+$/, "");
153
+ this.rateKey = `ondo:${opts.network}`;
154
+ }
155
+ get hasCredentials() {
156
+ return this.opts.credentials !== null;
157
+ }
158
+ // ── Public ──
159
+ async publicGet(path, query) {
160
+ return (await this.request("GET", path, query, null, false, true)).result;
161
+ }
162
+ async publicGetPage(path, query) {
163
+ return this.request("GET", path, query, null, false, true);
164
+ }
165
+ // ── Authenticated ──
166
+ async get(path, query) {
167
+ return (await this.request("GET", path, query, null, true, true)).result;
168
+ }
169
+ async getPage(path, query) {
170
+ return this.request("GET", path, query, null, true, true);
171
+ }
172
+ async post(path, body) {
173
+ return (await this.request("POST", path, null, body, true, true)).result;
174
+ }
175
+ async delete(path, query) {
176
+ return (await this.request("DELETE", path, query, null, true, true)).result;
177
+ }
178
+ /** Endpoints that answer with a bare GenericResponse (no `result`), e.g. POST /v1/perps/leverage. */
179
+ async postNoResult(path, body) {
180
+ await this.request("POST", path, null, body, true, false);
181
+ }
182
+ /** Endpoints that answer with a bare GenericResponse (no `result`), e.g. DELETE /v1/perps/orders. */
183
+ async deleteNoResult(path, query) {
184
+ await this.request("DELETE", path, query, null, true, false);
185
+ }
186
+ // ── Core ──
187
+ sign(creds, timestamp, method, pathWithQuery, body) {
188
+ return createHmac("sha256", creds.secret).update(timestamp + method + pathWithQuery + body).digest("hex");
189
+ }
190
+ async request(method, path, query, body, auth, expectResult) {
191
+ let pathWithQuery = path;
192
+ if (query) {
193
+ const qs = new URLSearchParams();
194
+ for (const [k, v] of Object.entries(query)) {
195
+ if (v !== null)
196
+ qs.append(k, String(v));
197
+ }
198
+ const s = qs.toString();
199
+ if (s)
200
+ pathWithQuery = `${path}?${s}`;
201
+ }
202
+ const bodyStr = body === null ? "" : JSON.stringify(body);
203
+ // Rate-limit wait happens BEFORE the timestamp is taken so a queued request
204
+ // never goes out with a stale ONDO-TIMESTAMP (at most 10 s behind / 1 s ahead).
205
+ await acquireRate(this.rateKey, this.opts.rateLimit, path);
206
+ const headers = { Accept: "application/json" };
207
+ if (bodyStr)
208
+ headers["Content-Type"] = "application/json";
209
+ if (auth) {
210
+ const creds = this.opts.credentials;
211
+ if (!creds) {
212
+ throw new PerpError("NO_SIGNER_AVAILABLE", `Ondo Perps: ${method} ${path} requires an API key`, {
213
+ remediation: "Register an Ondo API key: perp wallet agent approve ondo",
214
+ });
215
+ }
216
+ const ts = Date.now().toString();
217
+ headers["ONDO-KEY-ID"] = creds.keyId;
218
+ headers["ONDO-TIMESTAMP"] = ts;
219
+ headers["ONDO-SIGN"] = this.sign(creds, ts, method, pathWithQuery, bodyStr);
220
+ }
221
+ // Timeout covers the whole exchange (headers + body). A GET failure is definite
222
+ // (nothing changed); a POST failure may have reached the venue → outcome unknown
223
+ // (ORDER_PENDING, never resubmit — design §12.3).
224
+ const controller = new AbortController();
225
+ const timer = setTimeout(() => controller.abort(), ONDO_REQUEST_TIMEOUT_MS);
226
+ let res;
227
+ let text;
228
+ try {
229
+ res = await fetch(`${this.restUrl}${pathWithQuery}`, {
230
+ method,
231
+ headers,
232
+ ...(bodyStr ? { body: bodyStr } : {}),
233
+ signal: controller.signal,
234
+ });
235
+ text = await res.text();
236
+ }
237
+ catch (e) {
238
+ const aborted = controller.signal.aborted;
239
+ const reason = aborted ? `timed out after ${ONDO_REQUEST_TIMEOUT_MS}ms` : `failed: ${e instanceof Error ? e.message : String(e)}`;
240
+ if (method !== "GET") {
241
+ throw orderOutcomeUnknown("ondo", `${method} ${path}`, new Error(`request ${reason}`), `Do NOT resubmit — the request may have been applied. Check open orders, positions and balances on ondo`);
242
+ }
243
+ throw new PerpError(aborted ? "TIMEOUT" : "EXCHANGE_UNREACHABLE", `Ondo Perps: ${method} ${path} ${reason}`, {
244
+ exchange: "ondo",
245
+ remediation: `Check network connectivity to ${this.restUrl}`,
246
+ });
247
+ }
248
+ finally {
249
+ clearTimeout(timer);
250
+ }
251
+ if (res.status === 429) {
252
+ throw rateLimitedError("ondo", res.headers.get("retry-after"), text);
253
+ }
254
+ // A write (POST / DELETE) answered 5xx, or 2xx with an unreadable body, may have been applied.
255
+ const pending = (why) => orderOutcomeUnknown("ondo", `${method} ${path}`, new Error(why), "Do NOT resubmit — the request may have been applied. Check open orders, positions and balances on ondo");
256
+ if (method !== "GET" && res.status >= 500)
257
+ throw pending(`HTTP ${res.status}: ${text.slice(0, 200)}`);
258
+ let parsed;
259
+ try {
260
+ parsed = JSON.parse(text);
261
+ }
262
+ catch {
263
+ if (method !== "GET" && res.ok)
264
+ throw pending(`unreadable response (HTTP ${res.status})`);
265
+ throw new PerpError("EXCHANGE_ERROR", `Ondo Perps: ${method} ${path} returned non-JSON (HTTP ${res.status})`, {
266
+ httpStatus: res.status,
267
+ body: text.slice(0, 500),
268
+ });
269
+ }
270
+ if (!res.ok || parsed.success !== true) {
271
+ throw ondoError(method, path, res.status, parsed);
272
+ }
273
+ if (!expectResult)
274
+ return { result: undefined, nextCursor: null };
275
+ if (!("result" in parsed)) {
276
+ throw new PerpError("EXCHANGE_ERROR", `Ondo Perps: ${method} ${path} response has no result`, {
277
+ httpStatus: res.status,
278
+ body: text.slice(0, 500),
279
+ });
280
+ }
281
+ const next = parsed.pageInfo?.nextCursor;
282
+ return { result: parsed.result, nextCursor: typeof next === "string" && next !== "" ? next : null };
283
+ }
284
+ }
285
+ function ondoError(method, path, httpStatus, env) {
286
+ const venueCode = typeof env.error_code === "string" ? env.error_code : null;
287
+ const venueMsg = typeof env.error === "string" ? env.error : "unknown error";
288
+ let code = venueCode !== null && venueCode in ERROR_CODE_MAP ? ERROR_CODE_MAP[venueCode] : "EXCHANGE_ERROR";
289
+ if (code === "EXCHANGE_ERROR" && venueCode === "not_found" && (path.startsWith("/v1/perps/orders") || path.startsWith("/v1/spot/orders")))
290
+ code = "ORDER_NOT_FOUND";
291
+ const remediation = (venueCode !== null ? VENUE_REMEDIATION[venueCode] : undefined) ?? REMEDIATION[code];
292
+ return new PerpError(code, `Ondo Perps: ${venueMsg}${venueCode ? ` (${venueCode})` : ""} [${method} ${path}, HTTP ${httpStatus}]`, {
293
+ venueCode,
294
+ httpStatus,
295
+ ...(remediation !== undefined ? { remediation } : {}),
296
+ });
297
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Ondo Perps funding (mainnet only) + withdrawal address book.
3
+ *
4
+ * Deposit = a plain ERC-20 transfer of USDC to the account's own funding address
5
+ * (docs.ondoperps.xyz/funding-ondo-perps, api-reference/integration_guide "Deposit USDC").
6
+ * - The address comes from POST /v1/wallet/deposit_address/list {coins:["USDC"]}: the FIRST
7
+ * listed row for (USDC, chosen network, wallet "margin"), whose depositDestination.id must be
8
+ * this account's id (GET /v1/account accountID) — anything else is refused.
9
+ * - POST /v1/provision_address creates ANOTHER address on every call, so it is called only when
10
+ * the list has no address for that network.
11
+ * - USDC token addresses / chain ids / RPCs are pinned in the venue definition
12
+ * (venues/ondo.ts ONDO_DEPOSIT_CHAINS); decimals are read on-chain.
13
+ * - With a probe amount the transfer is two-step: probe → wait until GET /v1/wallet/deposits
14
+ * shows it confirmed → remainder. A probe that is not credited within the window stops the
15
+ * flow (ORDER_PENDING with the probe tx hash); the remainder is NOT sent.
16
+ *
17
+ * Address book: withdrawals go only to addresses on the account's address book. An address is
18
+ * added by signing a SIWE challenge with the account's wallet
19
+ * (POST /v1/auth/erc-4361/address_book/get_challenge → personal_sign → …/complete_challenge).
20
+ */
21
+ import { PerpError } from "../../errors.js";
22
+ import { type TxSigner } from "../../evm-master-tx.js";
23
+ import type { Network } from "../venue-types.js";
24
+ import { type OndoDepositChain } from "../venues/ondo.js";
25
+ /** The slice of OndoClient these flows use (authenticated calls). */
26
+ export interface OndoFundsClient {
27
+ get<T>(path: string, query: null): Promise<T>;
28
+ post<T>(path: string, body: unknown): Promise<T>;
29
+ postNoResult(path: string, body: unknown): Promise<void>;
30
+ }
31
+ export declare const ONDO_DEPOSIT_CREDIT_TIMEOUT_MS = 600000;
32
+ export declare const ONDO_DEPOSIT_CREDIT_POLL_MS = 10000;
33
+ export interface PollTiming {
34
+ timeoutMs: number;
35
+ pollMs: number;
36
+ sleep: (ms: number) => Promise<void>;
37
+ now: () => number;
38
+ }
39
+ /** Funding by on-chain transfer exists on mainnet only (the sandbox is funded with demo funds). */
40
+ export declare function requireOndoDepositNetwork(net: Network): void;
41
+ export declare function ondoDepositChain(raw: string): OndoDepositChain;
42
+ /**
43
+ * The first listed USDC funding address of `network` for the margin wallet. Returns null when the
44
+ * list has none for that network (the caller may then provision one). Refuses when the first
45
+ * listed address does not belong to `accountId`.
46
+ */
47
+ export declare function selectOndoDepositAddress(rows: unknown, network: OndoDepositChain, accountId: string): string | null;
48
+ export interface OndoFundingAddress {
49
+ accountId: string;
50
+ address: string;
51
+ provisioned: boolean;
52
+ }
53
+ /** Resolve (or, only when none exists, provision) the account's USDC funding address on `network`. */
54
+ export declare function resolveOndoDepositAddress(client: OndoFundsClient, network: OndoDepositChain): Promise<OndoFundingAddress>;
55
+ export interface OndoDepositPlan {
56
+ network: Network;
57
+ chain: OndoDepositChain;
58
+ chainId: number;
59
+ chainLabel: string;
60
+ rpcUrl: string;
61
+ token: string;
62
+ /** Read on-chain (decimals()). */
63
+ decimals: number;
64
+ amount: string;
65
+ amountRaw: bigint;
66
+ /** null = single transfer. */
67
+ probe: string | null;
68
+ probeRaw: bigint | null;
69
+ depositor: string;
70
+ depositAddress: string;
71
+ accountId: string;
72
+ tokenBalance: bigint;
73
+ }
74
+ /** Read-only checks on the pinned chain. Throws before anything is signed. */
75
+ export declare function planOndoDeposit(p: {
76
+ network: Network;
77
+ chain: OndoDepositChain;
78
+ amount: string;
79
+ probe: string | null;
80
+ depositor: string;
81
+ funding: OndoFundingAddress;
82
+ }): Promise<OndoDepositPlan>;
83
+ /**
84
+ * Wait until GET /v1/wallet/deposits lists this transaction as `confirmed`. Not listed / pending /
85
+ * unreadable until the deadline → ORDER_PENDING with the tx hash (the transfer itself is mined).
86
+ */
87
+ export declare function awaitOndoDepositCredit(readDeposits: () => Promise<unknown>, txHash: string, what: string, timing: PollTiming): Promise<void>;
88
+ export interface OndoDepositResult {
89
+ transfers: Array<{
90
+ step: "probe" | "deposit";
91
+ txHash: string;
92
+ amount: string;
93
+ }>;
94
+ depositAddress: string;
95
+ }
96
+ /**
97
+ * Send the USDC transfer(s). With a probe: probe → credited → remainder → credited; the
98
+ * remainder is never sent while the probe is uncredited (the ORDER_PENDING carries `sent`).
99
+ */
100
+ export declare function executeOndoDeposit(plan: OndoDepositPlan, sign: TxSigner, readDeposits: () => Promise<unknown>, timing: PollTiming): Promise<OndoDepositResult>;
101
+ /** Withdrawal addresses on the account's address book (GET /v1/wallet/address_book). */
102
+ export declare function ondoAddressBook(client: OndoFundsClient): Promise<string[]>;
103
+ /** Refusal for a withdraw destination that is not on the address book (nothing was sent). */
104
+ export declare function ondoAddressNotOnBook(destination: string, book: readonly string[]): PerpError;
105
+ /** A challenge the master key will sign must be about this address book entry — never an arbitrary message. */
106
+ export declare function assertOndoAddressBookChallenge(message: string, wallet: string, withdrawalAddress: string, restUrl: string): void;
107
+ export interface OndoAddressBookAddResult {
108
+ address: string;
109
+ alreadyListed: boolean;
110
+ listed: boolean;
111
+ challengeId: string | null;
112
+ }
113
+ /**
114
+ * Add the signing wallet's own address to the address book: get_challenge → personal_sign
115
+ * (EIP-191) with the wallet → complete_challenge. Challenge chainId "1" (enum: 1 | 43114).
116
+ */
117
+ export declare function addOndoAddressBookEntry(client: OndoFundsClient, p: {
118
+ wallet: string;
119
+ restUrl: string;
120
+ label: string | null;
121
+ signMessage: (message: string) => Promise<string>;
122
+ }): Promise<OndoAddressBookAddResult>;