perp-cli 0.12.17 → 0.14.1

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 (363) hide show
  1. package/README.md +247 -27
  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 +303 -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 +70 -92
  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 +248 -0
  101. package/dist/commands/funds.d.ts +25 -0
  102. package/dist/commands/funds.js +925 -245
  103. package/dist/commands/history.js +102 -54
  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 +357 -459
  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 +240 -6
  166. package/dist/exchanges/aster.js +1139 -195
  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 +152 -0
  200. package/dist/exchanges/ondo/adapter.js +811 -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 +95 -0
  206. package/dist/exchanges/ondo/funds.js +198 -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 +293 -4
  218. package/dist/exchanges/pacifica.js +1044 -90
  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 +1386 -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 +29 -0
  270. package/dist/exchanges/venues/ondo.js +136 -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 +39 -0
  274. package/dist/exchanges/venues/qfex.js +140 -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 +156 -684
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +7 -2
  287. package/dist/landing.js +19 -3
  288. package/dist/mcp-server.d.ts +14 -4
  289. package/dist/mcp-server.js +821 -403
  290. package/dist/mcp-trade-guard.d.ts +16 -0
  291. package/dist/mcp-trade-guard.js +59 -0
  292. package/dist/onchain-balances.d.ts +40 -0
  293. package/dist/onchain-balances.js +173 -0
  294. package/dist/pacifica/client.d.ts +41 -10
  295. package/dist/pacifica/client.js +167 -63
  296. package/dist/pacifica/constants.d.ts +0 -1
  297. package/dist/pacifica/constants.js +0 -5
  298. package/dist/pacifica/deposit.d.ts +30 -3
  299. package/dist/pacifica/deposit.js +119 -2
  300. package/dist/pacifica/faucet.d.ts +29 -0
  301. package/dist/pacifica/faucet.js +100 -0
  302. package/dist/pacifica/sign-tx.d.ts +15 -0
  303. package/dist/pacifica/sign-tx.js +41 -0
  304. package/dist/pacifica/types/account.d.ts +28 -4
  305. package/dist/pacifica/types/lake.d.ts +20 -5
  306. package/dist/pacifica/types/lake.js +4 -0
  307. package/dist/pacifica/types/market.d.ts +5 -0
  308. package/dist/pacifica/types/order.d.ts +4 -1
  309. package/dist/pacifica/types/position.d.ts +9 -1
  310. package/dist/plan-executor.d.ts +10 -5
  311. package/dist/plan-executor.js +165 -54
  312. package/dist/position-history.js +13 -0
  313. package/dist/rebalance.d.ts +26 -2
  314. package/dist/rebalance.js +86 -7
  315. package/dist/relay-swap.d.ts +232 -0
  316. package/dist/relay-swap.js +630 -0
  317. package/dist/risk.js +5 -1
  318. package/dist/secret-env.d.ts +15 -0
  319. package/dist/secret-env.js +36 -0
  320. package/dist/settings.d.ts +169 -6
  321. package/dist/settings.js +219 -38
  322. package/dist/signer/ows-evm.d.ts +13 -0
  323. package/dist/signer/ows-evm.js +22 -0
  324. package/dist/smart-order.d.ts +9 -0
  325. package/dist/smart-order.js +53 -8
  326. package/dist/strategies/dca.d.ts +6 -2
  327. package/dist/strategies/dca.js +66 -26
  328. package/dist/strategies/funding-arb.d.ts +11 -1
  329. package/dist/strategies/funding-arb.js +202 -89
  330. package/dist/strategies/grid.d.ts +31 -0
  331. package/dist/strategies/grid.js +89 -11
  332. package/dist/strategies/pending-stop.d.ts +8 -0
  333. package/dist/strategies/pending-stop.js +12 -0
  334. package/dist/strategies/split-order.d.ts +45 -17
  335. package/dist/strategies/split-order.js +157 -91
  336. package/dist/strategies/trailing-stop.d.ts +2 -1
  337. package/dist/strategies/trailing-stop.js +52 -6
  338. package/dist/strategies/twap.d.ts +6 -1
  339. package/dist/strategies/twap.js +39 -11
  340. package/dist/trade-validator.d.ts +22 -0
  341. package/dist/trade-validator.js +119 -15
  342. package/dist/utils/numeric.d.ts +46 -0
  343. package/dist/utils/numeric.js +79 -0
  344. package/dist/utils.d.ts +19 -0
  345. package/dist/utils.js +96 -19
  346. package/dist/withdraw-gate.d.ts +49 -0
  347. package/dist/withdraw-gate.js +131 -0
  348. package/package.json +15 -6
  349. package/skills/perp-cli/SKILL.md +159 -55
  350. package/skills/perp-cli/references/agent-operations.md +112 -58
  351. package/skills/perp-cli/references/commands.md +269 -73
  352. package/skills/perp-cli/references/strategies.md +34 -31
  353. package/skills/perp-cli/scripts/arb-monitor.sh +10 -5
  354. package/skills/perp-cli/scripts/funding-analysis.sh +14 -9
  355. package/skills/perp-cli/scripts/preflight.sh +21 -14
  356. package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
  357. package/skills/perp-cli/scripts/validate-arb.sh +31 -15
  358. package/vendor/lighter-signer/SHA256SUMS +2 -0
  359. package/vendor/lighter-signer/VERSION +8 -0
  360. package/vendor/lighter-signer/lighter-signer.wasm +0 -0
  361. package/vendor/lighter-signer/wasm_exec.js +575 -0
  362. package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
  363. package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Ondo Perps funding (mainnet only). Withdrawals are web-app only (the API cannot withdraw).
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
+ import { formatUnits, parseUnits } from "ethers";
18
+ import { PerpError } from "../../errors.js";
19
+ import { ERC20, EVM_ADDRESS_RE, assertRpcChainId, erc20Balance, erc20Decimals, evmProvider, sameAddress, sendMasterTx, } from "../../evm-master-tx.js";
20
+ import { unsupported } from "../trading-v2.js";
21
+ import { ONDO_DEPOSIT_CHAINS } from "../venues/ondo.js";
22
+ export const ONDO_DEPOSIT_CREDIT_TIMEOUT_MS = 600_000;
23
+ export const ONDO_DEPOSIT_CREDIT_POLL_MS = 10_000;
24
+ function venueError(msg) {
25
+ return new PerpError("EXCHANGE_ERROR", `ondo: ${msg}`, { exchange: "ondo" });
26
+ }
27
+ /** Funding by on-chain transfer exists on mainnet only (the sandbox is funded with demo funds). */
28
+ export function requireOndoDepositNetwork(net) {
29
+ if (net === "testnet") {
30
+ throw unsupported("Ondo Perps", "deposit", "the sandbox is funded with demo funds, not on-chain transfers", "Fund the sandbox account in the sandbox app (app.ondoperps-sandbox.xyz)");
31
+ }
32
+ }
33
+ export function ondoDepositChain(raw) {
34
+ const c = raw.toLowerCase();
35
+ if (c === "arbitrum" || c === "ethereum")
36
+ return c;
37
+ throw new PerpError("INVALID_PARAMS", `Unsupported Ondo deposit chain "${raw}"`, { remediation: "Use --chain arbitrum or --chain ethereum (USDC only)" });
38
+ }
39
+ // ── Funding address ──
40
+ /**
41
+ * The first listed USDC funding address of `network` for the margin wallet. Returns null when the
42
+ * list has none for that network (the caller may then provision one). Refuses when the first
43
+ * listed address does not belong to `accountId`.
44
+ */
45
+ export function selectOndoDepositAddress(rows, network, accountId) {
46
+ if (!Array.isArray(rows))
47
+ throw venueError(`deposit_address/list returned ${JSON.stringify(rows).slice(0, 120)}, expected a list`);
48
+ for (const r of rows) {
49
+ const row = (r !== null && typeof r === "object" ? r : {});
50
+ if (row.coin !== "USDC" || row.network !== network)
51
+ continue;
52
+ const dest = row.depositDestination;
53
+ if (dest === null || dest === undefined || dest.wallet !== "margin")
54
+ continue;
55
+ if (String(dest.id) !== accountId) {
56
+ throw venueError(`the first listed USDC ${network} funding address belongs to account ${String(dest.id)}, not this account ${accountId} — refusing to send`);
57
+ }
58
+ if (typeof row.address !== "string" || !EVM_ADDRESS_RE.test(row.address)) {
59
+ throw venueError(`the listed USDC ${network} funding address is not an EVM address (${JSON.stringify(row.address)})`);
60
+ }
61
+ return row.address;
62
+ }
63
+ return null;
64
+ }
65
+ /** Resolve (or, only when none exists, provision) the account's USDC funding address on `network`. */
66
+ export async function resolveOndoDepositAddress(client, network) {
67
+ const account = await client.get("/v1/account", null);
68
+ if (typeof account.accountID !== "string" || account.accountID === "")
69
+ throw venueError("GET /v1/account returned no accountID");
70
+ const accountId = account.accountID;
71
+ const list = () => client.post("/v1/wallet/deposit_address/list", { coins: ["USDC"] });
72
+ const existing = selectOndoDepositAddress(await list(), network, accountId);
73
+ if (existing !== null)
74
+ return { accountId, address: existing, provisioned: false };
75
+ const created = await client.post("/v1/provision_address", {
76
+ network, symbol: "USDC", deposit_destination: { id: accountId, wallet: "margin" },
77
+ });
78
+ if (String(created.accountId) !== accountId) {
79
+ throw venueError(`provision_address answered for account ${String(created.accountId)}, not ${accountId} — refusing to send`);
80
+ }
81
+ const listed = selectOndoDepositAddress(await list(), network, accountId);
82
+ if (listed === null || typeof created.address !== "string" || !sameAddress(listed, created.address)) {
83
+ throw venueError(`the provisioned USDC ${network} address ${String(created.address)} is not the one the address list shows (${String(listed)}) — refusing to send`);
84
+ }
85
+ return { accountId, address: listed, provisioned: true };
86
+ }
87
+ /** Read-only checks on the pinned chain. Throws before anything is signed. */
88
+ export async function planOndoDeposit(p) {
89
+ requireOndoDepositNetwork(p.network);
90
+ if (!EVM_ADDRESS_RE.test(p.depositor)) {
91
+ throw new PerpError("NO_SIGNER_AVAILABLE", "Ondo deposit needs the master wallet address", {
92
+ remediation: "Run with the master wallet: perp --ows <wallet> funds deposit ondo <amount>",
93
+ });
94
+ }
95
+ const pins = ONDO_DEPOSIT_CHAINS[p.chain];
96
+ const rpc = evmProvider(pins.rpcUrl, pins.chainId);
97
+ await assertRpcChainId(rpc, pins.rpcUrl, pins.chainId, "ondo");
98
+ const decimals = await erc20Decimals(rpc, pins.usdc);
99
+ const raw = (x, what) => {
100
+ if (!/^\d+(\.\d+)?$/.test(x) || !(Number(x) > 0))
101
+ throw new PerpError("INVALID_PARAMS", `${what} must be a positive decimal (got "${x}")`);
102
+ try {
103
+ return parseUnits(x, decimals);
104
+ }
105
+ catch {
106
+ throw new PerpError("INVALID_PARAMS", `${what} ${x} has more than ${decimals} decimals (USDC on ${pins.label})`);
107
+ }
108
+ };
109
+ const amountRaw = raw(p.amount, "Deposit amount");
110
+ const probeRaw = p.probe === null ? null : raw(p.probe, "--probe");
111
+ if (probeRaw !== null && probeRaw >= amountRaw) {
112
+ throw new PerpError("INVALID_PARAMS", `--probe ${String(p.probe)} must be smaller than the deposit amount ${p.amount} (the remainder is sent after the probe is credited)`);
113
+ }
114
+ const [tokenBalance, gas] = await Promise.all([erc20Balance(rpc, pins.usdc, p.depositor), rpc.getBalance(p.depositor)]);
115
+ if (tokenBalance < amountRaw) {
116
+ throw new PerpError("INSUFFICIENT_BALANCE", `Wallet ${p.depositor} holds ${formatUnits(tokenBalance, decimals)} USDC on ${pins.label}; the deposit needs ${p.amount}`, {
117
+ exchange: "ondo", remediation: `Fund ${p.depositor} with USDC on ${pins.label} (chainId ${pins.chainId}) first — e.g. perp funds bridge send --to ${p.chain} --to-asset USDC`,
118
+ });
119
+ }
120
+ if (gas === 0n) {
121
+ throw new PerpError("INSUFFICIENT_BALANCE", `Wallet ${p.depositor} has no ETH for gas on ${pins.label}`, {
122
+ exchange: "ondo", remediation: `Fund ${p.depositor} with ETH on ${pins.label} (chainId ${pins.chainId})`,
123
+ });
124
+ }
125
+ return {
126
+ network: p.network, chain: p.chain, chainId: pins.chainId, chainLabel: pins.label, rpcUrl: pins.rpcUrl, token: pins.usdc, decimals,
127
+ amount: p.amount, amountRaw, probe: p.probe, probeRaw, depositor: p.depositor,
128
+ depositAddress: p.funding.address, accountId: p.funding.accountId, tokenBalance,
129
+ };
130
+ }
131
+ /**
132
+ * Wait until GET /v1/wallet/deposits lists this transaction as `confirmed`. Not listed / pending /
133
+ * unreadable until the deadline → ORDER_PENDING with the tx hash (the transfer itself is mined).
134
+ */
135
+ export async function awaitOndoDepositCredit(readDeposits, txHash, what, timing) {
136
+ const deadline = timing.now() + timing.timeoutMs;
137
+ let last = "not listed yet";
138
+ for (;;) {
139
+ try {
140
+ const rows = await readDeposits();
141
+ if (!Array.isArray(rows))
142
+ throw venueError(`GET /v1/wallet/deposits returned ${JSON.stringify(rows).slice(0, 120)}, expected a list`);
143
+ const row = rows.find(r => typeof r?.txid === "string" && r.txid.toLowerCase() === txHash.toLowerCase());
144
+ if (row !== undefined) {
145
+ if (row.status === "confirmed")
146
+ return;
147
+ last = `status ${String(row.status)}`;
148
+ }
149
+ }
150
+ catch (e) {
151
+ last = e instanceof Error ? e.message : String(e);
152
+ }
153
+ if (timing.now() + timing.pollMs > deadline)
154
+ break;
155
+ await timing.sleep(timing.pollMs);
156
+ }
157
+ throw new PerpError("ORDER_PENDING", `ondo: ${what} ${txHash} is mined but not yet credited by Ondo Perps after ${timing.timeoutMs / 1000}s (${last})`, {
158
+ exchange: "ondo", txHash,
159
+ remediation: "Do NOT send again — the transfer is on-chain and deposits can take longer to confirm. Check: perp -e ondo account balance (or the Ondo Perps app → deposits)",
160
+ });
161
+ }
162
+ /**
163
+ * Send the USDC transfer(s). With a probe: probe → credited → remainder → credited; the
164
+ * remainder is never sent while the probe is uncredited (the ORDER_PENDING carries `sent`).
165
+ */
166
+ export async function executeOndoDeposit(plan, sign, readDeposits, timing) {
167
+ const rpc = evmProvider(plan.rpcUrl, plan.chainId);
168
+ const transfers = [];
169
+ const send = async (step, raw) => {
170
+ const amount = formatUnits(raw, plan.decimals);
171
+ const { txHash } = await sendMasterTx(rpc, {
172
+ venue: "ondo", chainLabel: plan.chainLabel, chainId: plan.chainId, from: plan.depositor, to: plan.token, value: 0n,
173
+ data: ERC20.encodeFunctionData("transfer", [plan.depositAddress, raw]), label: `USDC ${step} transfer (${amount})`,
174
+ }, sign);
175
+ transfers.push({ step, txHash, amount });
176
+ try {
177
+ await awaitOndoDepositCredit(readDeposits, txHash, `${step} transfer`, timing);
178
+ }
179
+ catch (e) {
180
+ if (e instanceof PerpError && e.structured.code === "ORDER_PENDING") {
181
+ const notSent = step === "probe" ? ` The remaining ${formatUnits(plan.amountRaw - raw, plan.decimals)} USDC was NOT sent.` : "";
182
+ throw new PerpError("ORDER_PENDING", `${e.message}.${notSent}`, {
183
+ exchange: "ondo", txHash, sent: transfers, depositAddress: plan.depositAddress,
184
+ remediation: e.structured.remediation,
185
+ });
186
+ }
187
+ throw e;
188
+ }
189
+ };
190
+ if (plan.probeRaw !== null) {
191
+ await send("probe", plan.probeRaw);
192
+ await send("deposit", plan.amountRaw - plan.probeRaw);
193
+ }
194
+ else {
195
+ await send("deposit", plan.amountRaw);
196
+ }
197
+ return { transfers, depositAddress: plan.depositAddress };
198
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Ondo Perps public market data (design §10.6).
3
+ *
4
+ * Sources (all public, live-verified 2026-09-30):
5
+ * GET /v1/markets result.perps.tradingPairs[] — increments, margin
6
+ * brackets, tags (asset class), trading schedule
7
+ * GET /v1/perps/contracts result[] — ticker, funding, OI, volume, status
8
+ * GET /v1/perps/mark_prices result{market: MarkPrice} — mark price and the
9
+ * weekend discovery band (discoveryLower/Upper,
10
+ * inPriceDiscovery; not in the OpenAPI spec but
11
+ * returned live)
12
+ *
13
+ * Funding is hourly: `fundingRate` is a 1h rate (docs funding-rates), so the
14
+ * basis is perHours 1 and settlement is every hour.
15
+ *
16
+ * Symbols: venue symbol "TSLA-USD.P" → CLI symbol "TSLA". Spot pairs
17
+ * (result.spot) are not perp markets; OndoSpotAdapter reads them via
18
+ * fetchOndoSpotTradingPairs.
19
+ */
20
+ import type { VenuePublicData } from "../venue.js";
21
+ import type { AssetClass, FundingRateBasis, Network } from "../venue-types.js";
22
+ import { OndoClient } from "./client.js";
23
+ export declare const ONDO_PERP_SUFFIX = "-USD.P";
24
+ export declare const ONDO_FUNDING_BASIS: FundingRateBasis;
25
+ export declare const ONDO_SETTLEMENT_HOURS = 1;
26
+ export interface OndoMarginBracket {
27
+ positionBracketUsd: string;
28
+ maxLeverage: string;
29
+ maintenanceMarginRate: string;
30
+ maintenanceAmount: string;
31
+ }
32
+ export interface OndoTradingPair {
33
+ market: string;
34
+ pair: {
35
+ base: string;
36
+ quote: string;
37
+ };
38
+ baseIncrement: string;
39
+ quoteIncrement: string;
40
+ marginInfo: OndoMarginBracket[];
41
+ tags: string[] | null;
42
+ /** Present on listings with a trading schedule (non-crypto); absent for 24/7 crypto. */
43
+ schedule?: unknown;
44
+ /** Present (true) on delisted / paused listings. */
45
+ disabled?: boolean;
46
+ }
47
+ /** Spot pair from /v1/markets result.spot.tradingPairs (live; not in the OpenAPI spec). */
48
+ export interface OndoSpotTradingPair {
49
+ market: string;
50
+ pair: {
51
+ base: string;
52
+ quote: string;
53
+ };
54
+ longName?: string;
55
+ baseIncrement: string;
56
+ quoteIncrement: string;
57
+ makerFee: string;
58
+ takerFee: string;
59
+ tags: string[] | null;
60
+ /** Present (true) on pairs that are listed but not tradable. */
61
+ disabled?: boolean;
62
+ }
63
+ export interface OndoContract {
64
+ market: string;
65
+ disabled: boolean;
66
+ lastPrice?: string;
67
+ usdVolume?: string;
68
+ openInterest?: string;
69
+ indexPrice?: string;
70
+ fundingRate?: string;
71
+ nextFundingRateTimestamp?: string;
72
+ isClosed?: boolean;
73
+ reduceOnly?: boolean;
74
+ tags?: string[];
75
+ }
76
+ export interface OndoMarkPrice {
77
+ market: string;
78
+ markPrice: string;
79
+ oraclePrice: string;
80
+ discoveryLower?: string;
81
+ discoveryUpper?: string;
82
+ inPriceDiscovery?: boolean;
83
+ }
84
+ export declare function ondoAssetClass(market: string, tags: readonly string[] | null | undefined): AssetClass;
85
+ /** "TSLA-USD.P" → "TSLA". Throws for non-perp symbols. */
86
+ export declare function ondoPlainSymbol(venueSymbol: string): string;
87
+ /** CLI symbol ("tsla", "TSLA", "TSLA-USD.P") → venue symbol "TSLA-USD.P". */
88
+ export declare function ondoVenueSymbol(symbol: string): string;
89
+ /** Empty string / absent → null. Ondo omits or blanks fields it does not publish. */
90
+ export declare function strOrNull(v: string | undefined | null): string | null;
91
+ export declare function parseOndoTime(iso: string, what: string): number;
92
+ export declare function ondoPublicClient(net: Network): OndoClient;
93
+ export declare function fetchOndoTradingPairs(client: OndoClient): Promise<OndoTradingPair[]>;
94
+ export declare function fetchOndoSpotTradingPairs(client: OndoClient): Promise<OndoSpotTradingPair[]>;
95
+ export declare const ondoPublicData: VenuePublicData;
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Ondo Perps public market data (design §10.6).
3
+ *
4
+ * Sources (all public, live-verified 2026-09-30):
5
+ * GET /v1/markets result.perps.tradingPairs[] — increments, margin
6
+ * brackets, tags (asset class), trading schedule
7
+ * GET /v1/perps/contracts result[] — ticker, funding, OI, volume, status
8
+ * GET /v1/perps/mark_prices result{market: MarkPrice} — mark price and the
9
+ * weekend discovery band (discoveryLower/Upper,
10
+ * inPriceDiscovery; not in the OpenAPI spec but
11
+ * returned live)
12
+ *
13
+ * Funding is hourly: `fundingRate` is a 1h rate (docs funding-rates), so the
14
+ * basis is perHours 1 and settlement is every hour.
15
+ *
16
+ * Symbols: venue symbol "TSLA-USD.P" → CLI symbol "TSLA". Spot pairs
17
+ * (result.spot) are not perp markets; OndoSpotAdapter reads them via
18
+ * fetchOndoSpotTradingPairs.
19
+ */
20
+ import { PerpError } from "../../errors.js";
21
+ import { canonicalFor } from "../canonical-symbols.js";
22
+ import { networkConfig } from "../venue.js";
23
+ import { ondoVenue } from "../venues/ondo.js";
24
+ import { OndoClient } from "./client.js";
25
+ export const ONDO_PERP_SUFFIX = "-USD.P";
26
+ export const ONDO_FUNDING_BASIS = { kind: "perHours", hours: 1 };
27
+ export const ONDO_SETTLEMENT_HOURS = 1;
28
+ // ── Helpers ──
29
+ const TAG_TO_ASSET_CLASS = {
30
+ Crypto: "crypto",
31
+ Stock: "equity",
32
+ ETF: "etf",
33
+ Index: "index",
34
+ Commodity: "commodity",
35
+ FX: "fx",
36
+ };
37
+ export function ondoAssetClass(market, tags) {
38
+ if (tags === null || tags === undefined) {
39
+ throw new PerpError("EXCHANGE_ERROR", `Ondo Perps: ${market} has no tags; cannot classify asset class`);
40
+ }
41
+ const known = tags.map(t => TAG_TO_ASSET_CLASS[t]).filter((c) => c !== undefined);
42
+ if (known.length !== 1) {
43
+ throw new PerpError("EXCHANGE_ERROR", `Ondo Perps: cannot classify ${market} from tags ${JSON.stringify(tags)}`, {
44
+ remediation: "Update TAG_TO_ASSET_CLASS in src/exchanges/ondo/public.ts for the new venue tag",
45
+ });
46
+ }
47
+ return known[0];
48
+ }
49
+ /** "TSLA-USD.P" → "TSLA". Throws for non-perp symbols. */
50
+ export function ondoPlainSymbol(venueSymbol) {
51
+ if (!venueSymbol.endsWith(ONDO_PERP_SUFFIX)) {
52
+ throw new PerpError("EXCHANGE_ERROR", `Ondo Perps: unexpected perp market symbol "${venueSymbol}"`);
53
+ }
54
+ return venueSymbol.slice(0, -ONDO_PERP_SUFFIX.length);
55
+ }
56
+ /** CLI symbol ("tsla", "TSLA", "TSLA-USD.P") → venue symbol "TSLA-USD.P". */
57
+ export function ondoVenueSymbol(symbol) {
58
+ const up = symbol.trim().toUpperCase();
59
+ if (up.length === 0)
60
+ throw new PerpError("INVALID_PARAMS", "symbol is empty");
61
+ return up.endsWith(ONDO_PERP_SUFFIX) ? up : `${up}${ONDO_PERP_SUFFIX}`;
62
+ }
63
+ /** Empty string / absent → null. Ondo omits or blanks fields it does not publish. */
64
+ export function strOrNull(v) {
65
+ return v === undefined || v === null || v === "" ? null : v;
66
+ }
67
+ export function parseOndoTime(iso, what) {
68
+ const t = Date.parse(iso);
69
+ if (!Number.isFinite(t))
70
+ throw new PerpError("EXCHANGE_ERROR", `Ondo Perps: invalid ${what} timestamp "${iso}"`);
71
+ return t;
72
+ }
73
+ function marginTiersOf(pair) {
74
+ if (pair.marginInfo.length === 0) {
75
+ throw new PerpError("EXCHANGE_ERROR", `Ondo Perps: ${pair.market} has no marginInfo brackets`);
76
+ }
77
+ // Brackets are ascending by positionBracketUsd (the bracket's upper bound).
78
+ const tiers = [];
79
+ let lower = 0;
80
+ for (const b of pair.marginInfo) {
81
+ const lev = Number(b.maxLeverage);
82
+ const upper = Number(b.positionBracketUsd);
83
+ if (!Number.isFinite(lev) || lev <= 0 || !Number.isFinite(upper)) {
84
+ throw new PerpError("EXCHANGE_ERROR", `Ondo Perps: ${pair.market} invalid margin bracket ${JSON.stringify(b)}`);
85
+ }
86
+ tiers.push({ lowerBound: lower, maxLeverage: lev });
87
+ lower = upper;
88
+ }
89
+ return { maxLeverage: Math.max(...tiers.map(t => t.maxLeverage)), tiers };
90
+ }
91
+ /**
92
+ * Weekend/holiday discovery band (docs weekend-trading). Only non-crypto
93
+ * listings carry a schedule; for 24/7 crypto the session is null.
94
+ * `inPriceDiscovery` = external feeds unavailable (underlying closed) and the
95
+ * mark is bounded by [discoveryLower, discoveryUpper].
96
+ */
97
+ function sessionOf(pair, mark) {
98
+ if (pair.schedule === undefined || pair.schedule === null)
99
+ return null;
100
+ if (typeof mark.inPriceDiscovery !== "boolean")
101
+ return null;
102
+ if (!mark.inPriceDiscovery)
103
+ return { underlyingOpen: true, priceBand: null, marginMultiplier: null };
104
+ const lower = strOrNull(mark.discoveryLower);
105
+ const upper = strOrNull(mark.discoveryUpper);
106
+ return {
107
+ underlyingOpen: false,
108
+ priceBand: lower !== null && upper !== null ? { lower, upper } : null,
109
+ marginMultiplier: null,
110
+ };
111
+ }
112
+ // ── Fetchers ──
113
+ export function ondoPublicClient(net) {
114
+ return new OndoClient({
115
+ restUrl: networkConfig(ondoVenue, net).restUrl,
116
+ network: net,
117
+ rateLimit: ondoVenue.rateLimit,
118
+ credentials: null,
119
+ });
120
+ }
121
+ export async function fetchOndoTradingPairs(client) {
122
+ const r = await client.publicGet("/v1/markets", null);
123
+ return r.perps.tradingPairs;
124
+ }
125
+ export async function fetchOndoSpotTradingPairs(client) {
126
+ const r = await client.publicGet("/v1/markets", null);
127
+ if (r.spot === undefined || !Array.isArray(r.spot.tradingPairs)) {
128
+ throw new PerpError("EXCHANGE_ERROR", "Ondo Perps: /v1/markets has no result.spot.tradingPairs");
129
+ }
130
+ return r.spot.tradingPairs;
131
+ }
132
+ function byMarket(rows) {
133
+ return new Map(rows.map(r => [r.market, r]));
134
+ }
135
+ function requireRow(m, market, source) {
136
+ const row = m.get(market);
137
+ if (row === undefined) {
138
+ throw new PerpError("EXCHANGE_ERROR", `Ondo Perps: ${market} is missing from ${source}`);
139
+ }
140
+ return row;
141
+ }
142
+ async function fetchMarkets(net) {
143
+ const client = ondoPublicClient(net);
144
+ const pairs = await fetchOndoTradingPairs(client);
145
+ const contracts = byMarket(await client.publicGet("/v1/perps/contracts", null));
146
+ const marks = await client.publicGet("/v1/perps/mark_prices", null);
147
+ // A listing with no mark price has no price feed (seen on the sandbox, e.g.
148
+ // SHEIN/COPPER/NATGAS with lastPrice 0). It cannot be priced or traded, so it
149
+ // is excluded with a warning rather than given a made-up mark.
150
+ const noMark = pairs.filter(p => marks[p.market] === undefined).map(p => p.market);
151
+ if (noMark.length > 0) {
152
+ console.warn(`[ondo] excluded ${noMark.length} market(s) without a mark price: ${noMark.join(", ")}`);
153
+ }
154
+ return pairs.filter(p => marks[p.market] !== undefined).map((pair) => {
155
+ const contract = requireRow(contracts, pair.market, "/v1/perps/contracts");
156
+ const mark = marks[pair.market];
157
+ const symbol = ondoPlainSymbol(pair.market);
158
+ const assetClass = ondoAssetClass(pair.market, pair.tags);
159
+ const { maxLeverage, tiers } = marginTiersOf(pair);
160
+ return {
161
+ venue: "ondo",
162
+ venueSymbol: pair.market,
163
+ symbol,
164
+ canonical: canonicalFor("ondo", symbol, assetClass),
165
+ assetClass,
166
+ settle: "USDC",
167
+ quoteAsset: pair.pair.quote,
168
+ markPrice: mark.markPrice,
169
+ indexPrice: strOrNull(contract.indexPrice),
170
+ fundingRate: strOrNull(contract.fundingRate),
171
+ fundingRateBasis: ONDO_FUNDING_BASIS,
172
+ settlementIntervalHours: ONDO_SETTLEMENT_HOURS,
173
+ openInterest: strOrNull(contract.openInterest),
174
+ volume24h: strOrNull(contract.usdVolume),
175
+ tickSize: pair.quoteIncrement,
176
+ stepSize: pair.baseIncrement,
177
+ minSize: null,
178
+ minNotional: null,
179
+ maxLeverage,
180
+ marginTiers: tiers,
181
+ session: sessionOf(pair, mark),
182
+ tradable: pair.disabled !== true && !contract.disabled && contract.isClosed !== true && contract.reduceOnly !== true,
183
+ };
184
+ });
185
+ }
186
+ async function fetchFundingRates(net) {
187
+ const client = ondoPublicClient(net);
188
+ const contracts = await client.publicGet("/v1/perps/contracts", null);
189
+ const marks = await client.publicGet("/v1/perps/mark_prices", null);
190
+ const out = [];
191
+ for (const c of contracts) {
192
+ if (c.disabled)
193
+ continue; // delisted/paused listings carry a stale rate and a zero last price
194
+ const rate = strOrNull(c.fundingRate);
195
+ if (rate === null) {
196
+ throw new PerpError("EXCHANGE_ERROR", `Ondo Perps: ${c.market} has no fundingRate in /v1/perps/contracts`);
197
+ }
198
+ const symbol = ondoPlainSymbol(c.market);
199
+ const next = strOrNull(c.nextFundingRateTimestamp);
200
+ const mark = marks[c.market];
201
+ out.push({
202
+ venue: "ondo",
203
+ venueSymbol: c.market,
204
+ symbol,
205
+ canonical: canonicalFor("ondo", symbol, ondoAssetClass(c.market, c.tags)),
206
+ rate,
207
+ basis: ONDO_FUNDING_BASIS,
208
+ markPrice: mark === undefined ? null : mark.markPrice,
209
+ nextFundingTime: next === null ? null : parseOndoTime(next, "nextFundingRateTimestamp"),
210
+ });
211
+ }
212
+ return out;
213
+ }
214
+ export const ondoPublicData = { fetchMarkets, fetchFundingRates };
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Ondo Perps spot adapter (design §12, §12.4).
3
+ *
4
+ * Same host and HMAC API key as perps (shares OndoAdapter's OndoClient).
5
+ * Account model "separate": the Spot wallet ("spot") next to the Perps margin
6
+ * wallet ("margin"); POST /v1/spot/transfers moves funds between them. Docs
7
+ * (docs.ondoperps.xyz/api-reference/spot-api-guide, read 2026-10-05):
8
+ * "The wallet identifiers are `spot` for Spot trading, `margin` for Perps
9
+ * collateral, and `main` for the main wallet. Funds in another wallet do not
10
+ * automatically fund a Spot order."
11
+ * "`POST /v1/spot/transfers` requires `transfer`. It accepts `main` ↔ `spot`
12
+ * and `margin` ↔ `spot` transfers on the same account."
13
+ *
14
+ * Paths and shapes were first taken from the production web app (route table
15
+ * module 10215, spot trade chunk 7609, balances parser 82886) and live public
16
+ * GETs (2026-10-01); the Spot API guide now documents them:
17
+ * public GET /v1/markets result.spot.tradingPairs
18
+ * public GET /v1/spot/ticker [{market,disabled,lastPrice,bid,ask,baseVolume,quoteVolume,...}]
19
+ * public GET /v1/spot/depth ?market=TSLA-USDC&depth= → {market,time,asks,bids}
20
+ * auth GET /v1/spot/balances [{coin,total,free,usdValue}]
21
+ * auth POST /v1/spot/orders {market,type,side,size,postOnly,price?,timeInForce?}
22
+ * auth GET /v1/spot/orders ?activeOnly=true&market=&limit=&cursor=
23
+ * auth DELETE /v1/spot/orders/{id}
24
+ * auth DELETE /v1/spot/orders ?market=
25
+ * auth POST /v1/spot/transfers {symbol,amount,from:{wallet},to:{wallet}}
26
+ *
27
+ * Delayed placement (POST /v1/spot/orders/delayed) is NOT implemented. The guide
28
+ * says "If the server rejects placement with a message directing you to a delayed
29
+ * path, sign a new request using that exact path and a fresh timestamp", but it
30
+ * documents neither an error_code nor the message for that rejection ("treat
31
+ * `error` as descriptive text whose wording can change"), so there is no
32
+ * documented signal to switch paths on. A rejection that names the delayed path
33
+ * is reported as CAPABILITY_UNSUPPORTED (definitely not placed) — see submitFailure.
34
+ *
35
+ * Market orders: IOC LIMIT at
36
+ * alignProtectionPrice(slippagePrice(mid, side, bps), quoteIncrement, side)
37
+ * which is the web app's own pattern. bps = the caller's resolveSlippage()
38
+ * value (design §13.2). The mid comes from the spot
39
+ * ticker's bid/ask; a one-sided book is PRICE_STALE (no lastPrice fallback).
40
+ *
41
+ * Unverified (needs a funded key):
42
+ * - whether API-key HMAC auth is accepted on /v1/spot/* (the web app uses a
43
+ * JWT). Auth rejections there map to NOT_IMPLEMENTED with remediation;
44
+ * - balance `coin` naming/units. The app indexes balances by the
45
+ * tokenConfig id / pair base ("TSLA", not the custodied "TSLAon"), and
46
+ * tokenConfig marks equities as ledgerUnit "shares" (displayed units, not
47
+ * raw GM-token units). Values are passed through as reported;
48
+ * - whether DELETE /v1/spot/orders returns a `result` (ignored either way);
49
+ * - minimum order size (not published).
50
+ */
51
+ import type { ResolvedSlippage } from "../slippage.js";
52
+ import type { SpotAdapter, SpotBalance, SpotCancelAllCapable, SpotMarketInfo, SpotOpenOrdersCapable, SpotOrderResult, SpotTransferCapable } from "../spot-interface.js";
53
+ import type { OndoAdapter } from "./adapter.js";
54
+ /** OpenAPI SpotWalletKey.wallet (rest-spec.json: main | margin | spot); spot ↔ perp uses these two. */
55
+ type Wallet = "spot" | "margin";
56
+ export declare class OndoSpotAdapter implements SpotAdapter, SpotTransferCapable, SpotOpenOrdersCapable, SpotCancelAllCapable {
57
+ readonly name = "ondo";
58
+ private readonly perp;
59
+ /** BASE (upper-cased) → enabled pair */
60
+ private enabled;
61
+ /** wire market ("TSLA-USDC") → pair, incl. disabled (for mapping order rows) */
62
+ private byMarket;
63
+ private initialized;
64
+ constructor(perp: OndoAdapter);
65
+ private get client();
66
+ init(): Promise<void>;
67
+ private loadPairs;
68
+ /** bare base ("tsla") or "TSLA/USDC" → enabled pair (wire "TSLA-USDC"). */
69
+ private resolve;
70
+ /** wire "TSLA-USDC" → "TSLA/USDC". */
71
+ private displaySymbol;
72
+ /**
73
+ * Authenticated spot calls. HMAC acceptance on /v1/spot/* is unverified: a
74
+ * 401/403 that is not a scope/IP/clock problem is reported as NOT_IMPLEMENTED
75
+ * (the key works on perps but the spot route refuses it).
76
+ */
77
+ private authed;
78
+ private ticker;
79
+ /** Book mid from the ticker. One-sided / empty book → PRICE_STALE (no lastPrice fallback). */
80
+ private mid;
81
+ private toResult;
82
+ private place;
83
+ getSpotMarkets(): Promise<SpotMarketInfo[]>;
84
+ getSpotOrderbook(symbol: string): Promise<{
85
+ bids: [string, string][];
86
+ asks: [string, string][];
87
+ }>;
88
+ getSpotBalances(): Promise<SpotBalance[]>;
89
+ /** IOC limit at the protection price for the caller's resolved slippage bound. */
90
+ spotMarketOrder(symbol: string, side: "buy" | "sell", size: string, opts: {
91
+ slippage: ResolvedSlippage;
92
+ }): Promise<SpotOrderResult>;
93
+ /** tif: GTC (default) | IOC | ALO (= GTC + postOnly). */
94
+ spotLimitOrder(symbol: string, side: "buy" | "sell", price: string, size: string, opts?: {
95
+ tif?: string;
96
+ }): Promise<SpotOrderResult>;
97
+ spotCancelOrder(symbol: string, orderId: string): Promise<SpotOrderResult>;
98
+ getSpotOpenOrders(symbol: string | null): Promise<Array<{
99
+ orderId: string;
100
+ symbol: string;
101
+ side: "buy" | "sell";
102
+ price: string;
103
+ size: string;
104
+ filled: string;
105
+ }>>;
106
+ spotCancelAllOrders(symbol: string | null): Promise<{
107
+ venue: "ondo";
108
+ market: string | null;
109
+ cancelled: "all";
110
+ }>;
111
+ transferToSpot(amount: string, asset: string): Promise<{
112
+ venue: "ondo";
113
+ asset: string;
114
+ amount: string;
115
+ from: Wallet;
116
+ to: Wallet;
117
+ }>;
118
+ transferToPerp(amount: string, asset: string): Promise<{
119
+ venue: "ondo";
120
+ asset: string;
121
+ amount: string;
122
+ from: Wallet;
123
+ to: Wallet;
124
+ }>;
125
+ private transfer;
126
+ }
127
+ export {};