perp-cli 0.14.2 → 0.15.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 (313) hide show
  1. package/README.md +152 -13
  2. package/dist/agent-wallet/store.d.ts +3 -2
  3. package/dist/agent-wallet/store.js +2 -2
  4. package/dist/api/public/hyperliquid.js +4 -0
  5. package/dist/api/public/pacifica.js +83 -13
  6. package/dist/api/public/urls.d.ts +2 -0
  7. package/dist/api/public/urls.js +3 -0
  8. package/dist/arb/utils.d.ts +1 -2
  9. package/dist/arb/utils.js +1 -2
  10. package/dist/bot/apex/pulse.js +3 -0
  11. package/dist/bot/apex/radar.js +2 -1
  12. package/dist/bot/engine.d.ts +10 -1
  13. package/dist/bot/engine.js +14 -6
  14. package/dist/bot/indicators.d.ts +7 -0
  15. package/dist/bot/indicators.js +16 -0
  16. package/dist/bot/perp-order.d.ts +4 -4
  17. package/dist/bot/perp-order.js +5 -5
  18. package/dist/bot/quarantine.d.ts +1 -1
  19. package/dist/bot/quarantine.js +1 -1
  20. package/dist/bot/strategies/apex-strategy.js +3 -2
  21. package/dist/bot/strategies/engine-mm.js +6 -5
  22. package/dist/bot/strategies/funding-arb-strategy.js +9 -9
  23. package/dist/bot/strategies/funding-arb-utils.d.ts +15 -2
  24. package/dist/bot/strategies/funding-arb-utils.js +43 -24
  25. package/dist/bot/strategies/funding-auto.js +2 -2
  26. package/dist/bot/strategies/liquidation-mm.d.ts +1 -0
  27. package/dist/bot/strategies/liquidation-mm.js +8 -2
  28. package/dist/bot/strategies/momentum-breakout.d.ts +1 -0
  29. package/dist/bot/strategies/momentum-breakout.js +11 -3
  30. package/dist/bot/strategies/spot-perp-arb-strategy.js +23 -12
  31. package/dist/bot/strategies/spot-perp-executor.d.ts +1 -1
  32. package/dist/bot/strategies/spot-perp-executor.js +1 -1
  33. package/dist/bot/strategy-types.d.ts +2 -1
  34. package/dist/bot/tui/BotDashboard.d.ts +1 -1
  35. package/dist/bot/tui/BotDashboard.js +1 -1
  36. package/dist/cache.js +12 -6
  37. package/dist/cli-program.d.ts +15 -0
  38. package/dist/cli-program.js +125 -5
  39. package/dist/commands/agent-import.d.ts +7 -0
  40. package/dist/commands/agent-import.js +30 -1
  41. package/dist/commands/agent.d.ts +6 -3
  42. package/dist/commands/agent.js +160 -12
  43. package/dist/commands/arb/index.js +4 -4
  44. package/dist/commands/arb-auto.js +21 -17
  45. package/dist/commands/arb.d.ts +12 -0
  46. package/dist/commands/arb.js +61 -143
  47. package/dist/commands/bot.js +1 -1
  48. package/dist/commands/builder.js +1 -1
  49. package/dist/commands/funds.js +113 -3
  50. package/dist/commands/init.d.ts +1 -1
  51. package/dist/commands/init.js +27 -7
  52. package/dist/commands/manage.js +23 -21
  53. package/dist/commands/market.js +168 -36
  54. package/dist/commands/options.d.ts +9 -0
  55. package/dist/commands/options.js +134 -0
  56. package/dist/commands/pacifica-earn.d.ts +5 -0
  57. package/dist/commands/pacifica-earn.js +468 -0
  58. package/dist/commands/pacifica-orders.d.ts +14 -0
  59. package/dist/commands/pacifica-orders.js +142 -0
  60. package/dist/commands/portfolio.js +32 -47
  61. package/dist/commands/rebalance.d.ts +1 -0
  62. package/dist/commands/rebalance.js +15 -0
  63. package/dist/commands/risk.d.ts +11 -2
  64. package/dist/commands/risk.js +39 -44
  65. package/dist/commands/trade.d.ts +1 -0
  66. package/dist/commands/trade.js +63 -58
  67. package/dist/commands/wallet.js +1 -1
  68. package/dist/config.d.ts +2 -2
  69. package/dist/config.js +15 -4
  70. package/dist/constants.js +6 -0
  71. package/dist/dashboard/server.js +1 -1
  72. package/dist/dashboard/ui.js +1 -1
  73. package/dist/errors.d.ts +24 -1
  74. package/dist/errors.js +25 -3
  75. package/dist/exchanges/arcus/adapter.d.ts +7 -7
  76. package/dist/exchanges/arcus/adapter.js +12 -12
  77. package/dist/exchanges/arcus/agent.d.ts +1 -1
  78. package/dist/exchanges/arcus/agent.js +1 -1
  79. package/dist/exchanges/arcus/client.js +1 -1
  80. package/dist/exchanges/arcus/signing.d.ts +2 -3
  81. package/dist/exchanges/arcus/signing.js +1 -1
  82. package/dist/exchanges/arcus/spot-chain.d.ts +1 -1
  83. package/dist/exchanges/arcus/spot-chain.js +1 -1
  84. package/dist/exchanges/arcus/spot-router.d.ts +1 -1
  85. package/dist/exchanges/arcus/spot-router.js +2 -2
  86. package/dist/exchanges/arcus/spot.d.ts +2 -3
  87. package/dist/exchanges/arcus/spot.js +2 -3
  88. package/dist/exchanges/aster-spot.d.ts +3 -3
  89. package/dist/exchanges/aster-spot.js +6 -9
  90. package/dist/exchanges/aster-typed-data.d.ts +1 -1
  91. package/dist/exchanges/aster-typed-data.js +1 -1
  92. package/dist/exchanges/aster.d.ts +9 -4
  93. package/dist/exchanges/aster.js +125 -55
  94. package/dist/exchanges/builder-fee.d.ts +1 -1
  95. package/dist/exchanges/builder-fee.js +13 -2
  96. package/dist/exchanges/canonical-symbols.d.ts +1 -2
  97. package/dist/exchanges/canonical-symbols.js +26 -2
  98. package/dist/exchanges/derive/client.d.ts +37 -0
  99. package/dist/exchanges/derive/client.js +135 -0
  100. package/dist/exchanges/derive/options.d.ts +68 -0
  101. package/dist/exchanges/derive/options.js +360 -0
  102. package/dist/exchanges/extended/adapter.d.ts +82 -0
  103. package/dist/exchanges/extended/adapter.js +179 -0
  104. package/dist/exchanges/extended/client.d.ts +38 -0
  105. package/dist/exchanges/extended/client.js +88 -0
  106. package/dist/exchanges/extended/public.d.ts +130 -0
  107. package/dist/exchanges/extended/public.js +429 -0
  108. package/dist/exchanges/grvt/adapter.d.ts +86 -0
  109. package/dist/exchanges/grvt/adapter.js +206 -0
  110. package/dist/exchanges/grvt/client.d.ts +25 -0
  111. package/dist/exchanges/grvt/client.js +86 -0
  112. package/dist/exchanges/grvt/public.d.ts +151 -0
  113. package/dist/exchanges/grvt/public.js +438 -0
  114. package/dist/exchanges/hyperliquid-spot.d.ts +1 -1
  115. package/dist/exchanges/hyperliquid-spot.js +5 -5
  116. package/dist/exchanges/hyperliquid.d.ts +22 -16
  117. package/dist/exchanges/hyperliquid.js +80 -55
  118. package/dist/exchanges/index.d.ts +1 -1
  119. package/dist/exchanges/index.js +1 -1
  120. package/dist/exchanges/interface.d.ts +97 -8
  121. package/dist/exchanges/interface.js +19 -2
  122. package/dist/exchanges/ioc-outcome.d.ts +1 -1
  123. package/dist/exchanges/ioc-outcome.js +1 -1
  124. package/dist/exchanges/legacy-trading-v2.d.ts +2 -2
  125. package/dist/exchanges/legacy-trading-v2.js +7 -2
  126. package/dist/exchanges/lighter-public.d.ts +2 -2
  127. package/dist/exchanges/lighter-public.js +2 -2
  128. package/dist/exchanges/lighter-signer.d.ts +1 -1
  129. package/dist/exchanges/lighter-signer.js +1 -1
  130. package/dist/exchanges/lighter-spot.d.ts +3 -4
  131. package/dist/exchanges/lighter-spot.js +6 -7
  132. package/dist/exchanges/lighter.d.ts +11 -11
  133. package/dist/exchanges/lighter.js +28 -14
  134. package/dist/exchanges/ondo/adapter.d.ts +5 -6
  135. package/dist/exchanges/ondo/adapter.js +16 -14
  136. package/dist/exchanges/ondo/agent.d.ts +1 -1
  137. package/dist/exchanges/ondo/agent.js +1 -1
  138. package/dist/exchanges/ondo/client.d.ts +1 -1
  139. package/dist/exchanges/ondo/client.js +2 -2
  140. package/dist/exchanges/ondo/public.d.ts +8 -1
  141. package/dist/exchanges/ondo/public.js +13 -2
  142. package/dist/exchanges/ondo/spot.d.ts +2 -2
  143. package/dist/exchanges/ondo/spot.js +3 -3
  144. package/dist/exchanges/options-interface.d.ts +158 -0
  145. package/dist/exchanges/options-interface.js +17 -0
  146. package/dist/exchanges/options-venues.d.ts +12 -0
  147. package/dist/exchanges/options-venues.js +35 -0
  148. package/dist/exchanges/pacifica-atomic.d.ts +211 -0
  149. package/dist/exchanges/pacifica-atomic.js +440 -0
  150. package/dist/exchanges/pacifica-decimal.d.ts +10 -0
  151. package/dist/exchanges/pacifica-decimal.js +51 -0
  152. package/dist/exchanges/pacifica-presigned.d.ts +53 -0
  153. package/dist/exchanges/pacifica-presigned.js +141 -0
  154. package/dist/exchanges/pacifica-print.d.ts +200 -0
  155. package/dist/exchanges/pacifica-print.js +417 -0
  156. package/dist/exchanges/pacifica-spot.d.ts +2 -2
  157. package/dist/exchanges/pacifica-spot.js +3 -3
  158. package/dist/exchanges/pacifica-typed-data.d.ts +1 -1
  159. package/dist/exchanges/pacifica-typed-data.js +1 -1
  160. package/dist/exchanges/pacifica-vaults.d.ts +121 -0
  161. package/dist/exchanges/pacifica-vaults.js +264 -0
  162. package/dist/exchanges/pacifica.d.ts +32 -5
  163. package/dist/exchanges/pacifica.js +114 -48
  164. package/dist/exchanges/precision.d.ts +3 -1
  165. package/dist/exchanges/precision.js +9 -2
  166. package/dist/exchanges/qfex/adapter.d.ts +18 -5
  167. package/dist/exchanges/qfex/adapter.js +32 -12
  168. package/dist/exchanges/qfex/agent.d.ts +1 -1
  169. package/dist/exchanges/qfex/agent.js +1 -1
  170. package/dist/exchanges/qfex/client.d.ts +1 -1
  171. package/dist/exchanges/qfex/client.js +1 -1
  172. package/dist/exchanges/qfex/errors.d.ts +1 -1
  173. package/dist/exchanges/qfex/errors.js +2 -2
  174. package/dist/exchanges/qfex/public.d.ts +3 -3
  175. package/dist/exchanges/qfex/public.js +3 -3
  176. package/dist/exchanges/qfex/signing.d.ts +2 -2
  177. package/dist/exchanges/qfex/signing.js +4 -5
  178. package/dist/exchanges/qfex/ws-transport.d.ts +2 -2
  179. package/dist/exchanges/qfex/ws-transport.js +3 -3
  180. package/dist/exchanges/rate-limiter.d.ts +1 -1
  181. package/dist/exchanges/rate-limiter.js +5 -2
  182. package/dist/exchanges/registry.d.ts +1 -1
  183. package/dist/exchanges/registry.js +1 -1
  184. package/dist/exchanges/risex/adapter.d.ts +148 -0
  185. package/dist/exchanges/risex/adapter.js +796 -0
  186. package/dist/exchanges/risex/agent.d.ts +154 -0
  187. package/dist/exchanges/risex/agent.js +384 -0
  188. package/dist/exchanges/risex/client.d.ts +47 -0
  189. package/dist/exchanges/risex/client.js +174 -0
  190. package/dist/exchanges/risex/nonce.d.ts +43 -0
  191. package/dist/exchanges/risex/nonce.js +212 -0
  192. package/dist/exchanges/risex/public.d.ts +64 -0
  193. package/dist/exchanges/risex/public.js +150 -0
  194. package/dist/exchanges/risex/session.d.ts +71 -0
  195. package/dist/exchanges/risex/session.js +171 -0
  196. package/dist/exchanges/risex/typed-data.d.ts +173 -0
  197. package/dist/exchanges/risex/typed-data.js +240 -0
  198. package/dist/exchanges/risex/units.d.ts +29 -0
  199. package/dist/exchanges/risex/units.js +104 -0
  200. package/dist/exchanges/slippage.d.ts +1 -1
  201. package/dist/exchanges/slippage.js +1 -1
  202. package/dist/exchanges/spot-factory.d.ts +3 -3
  203. package/dist/exchanges/spot-factory.js +7 -7
  204. package/dist/exchanges/spot-interface.d.ts +5 -5
  205. package/dist/exchanges/spot-interface.js +2 -2
  206. package/dist/exchanges/spot-result.d.ts +3 -3
  207. package/dist/exchanges/spot-result.js +3 -3
  208. package/dist/exchanges/trading-v2.d.ts +3 -3
  209. package/dist/exchanges/trading-v2.js +9 -6
  210. package/dist/exchanges/txflow/adapter.d.ts +54 -0
  211. package/dist/exchanges/txflow/adapter.js +118 -0
  212. package/dist/exchanges/txflow/client.d.ts +47 -0
  213. package/dist/exchanges/txflow/client.js +153 -0
  214. package/dist/exchanges/txflow/public.d.ts +76 -0
  215. package/dist/exchanges/txflow/public.js +284 -0
  216. package/dist/exchanges/unknown-symbol.d.ts +29 -0
  217. package/dist/exchanges/unknown-symbol.js +51 -0
  218. package/dist/exchanges/variational/adapter.d.ts +71 -0
  219. package/dist/exchanges/variational/adapter.js +137 -0
  220. package/dist/exchanges/variational/client.d.ts +21 -0
  221. package/dist/exchanges/variational/client.js +66 -0
  222. package/dist/exchanges/variational/public.d.ts +126 -0
  223. package/dist/exchanges/variational/public.js +430 -0
  224. package/dist/exchanges/venue-types.d.ts +6 -7
  225. package/dist/exchanges/venue-types.js +2 -2
  226. package/dist/exchanges/venue.d.ts +51 -8
  227. package/dist/exchanges/venue.js +54 -4
  228. package/dist/exchanges/venues/arcus.d.ts +1 -2
  229. package/dist/exchanges/venues/arcus.js +5 -4
  230. package/dist/exchanges/venues/aster.d.ts +2 -2
  231. package/dist/exchanges/venues/aster.js +6 -4
  232. package/dist/exchanges/venues/create-helpers.d.ts +1 -1
  233. package/dist/exchanges/venues/create-helpers.js +3 -3
  234. package/dist/exchanges/venues/extended.d.ts +21 -0
  235. package/dist/exchanges/venues/extended.js +96 -0
  236. package/dist/exchanges/venues/grvt.d.ts +30 -0
  237. package/dist/exchanges/venues/grvt.js +98 -0
  238. package/dist/exchanges/venues/hyperliquid.d.ts +2 -2
  239. package/dist/exchanges/venues/hyperliquid.js +10 -8
  240. package/dist/exchanges/venues/index.d.ts +7 -2
  241. package/dist/exchanges/venues/index.js +8 -2
  242. package/dist/exchanges/venues/lighter-family.d.ts +7 -9
  243. package/dist/exchanges/venues/lighter-family.js +9 -11
  244. package/dist/exchanges/venues/lighter-rh.d.ts +2 -2
  245. package/dist/exchanges/venues/lighter-rh.js +7 -5
  246. package/dist/exchanges/venues/lighter.d.ts +1 -1
  247. package/dist/exchanges/venues/lighter.js +5 -3
  248. package/dist/exchanges/venues/ondo.d.ts +1 -1
  249. package/dist/exchanges/venues/ondo.js +5 -3
  250. package/dist/exchanges/venues/pacifica.d.ts +2 -2
  251. package/dist/exchanges/venues/pacifica.js +6 -4
  252. package/dist/exchanges/venues/qfex.d.ts +1 -1
  253. package/dist/exchanges/venues/qfex.js +4 -2
  254. package/dist/exchanges/venues/risex-release.d.ts +14 -0
  255. package/dist/exchanges/venues/risex-release.js +14 -0
  256. package/dist/exchanges/venues/risex.d.ts +31 -0
  257. package/dist/exchanges/venues/risex.js +116 -0
  258. package/dist/exchanges/venues/txflow.d.ts +18 -0
  259. package/dist/exchanges/venues/txflow.js +77 -0
  260. package/dist/exchanges/venues/variational.d.ts +21 -0
  261. package/dist/exchanges/venues/variational.js +85 -0
  262. package/dist/execution-log.d.ts +1 -1
  263. package/dist/funding/legacy-public.d.ts +1 -1
  264. package/dist/funding/legacy-public.js +9 -4
  265. package/dist/funding/normalize.d.ts +1 -1
  266. package/dist/funding/normalize.js +1 -1
  267. package/dist/funding/rates.d.ts +44 -4
  268. package/dist/funding/rates.js +56 -12
  269. package/dist/index.js +7 -6
  270. package/dist/jobs.d.ts +1 -0
  271. package/dist/jobs.js +25 -1
  272. package/dist/landing.d.ts +2 -0
  273. package/dist/landing.js +1 -1
  274. package/dist/mcp-pacifica-earn.d.ts +9 -0
  275. package/dist/mcp-pacifica-earn.js +58 -0
  276. package/dist/mcp-server.js +113 -59
  277. package/dist/pacifica/client.d.ts +67 -4
  278. package/dist/pacifica/client.js +170 -8
  279. package/dist/pacifica/rate-limit.d.ts +144 -0
  280. package/dist/pacifica/rate-limit.js +267 -0
  281. package/dist/pacifica/types/order.d.ts +2 -1
  282. package/dist/plan-executor.js +7 -2
  283. package/dist/rebalance.js +1 -1
  284. package/dist/risk.d.ts +2 -1
  285. package/dist/risk.js +2 -4
  286. package/dist/settings.d.ts +32 -3
  287. package/dist/signer/ows-evm.d.ts +1 -1
  288. package/dist/signer/ows-evm.js +1 -1
  289. package/dist/smart-order.d.ts +1 -1
  290. package/dist/smart-order.js +1 -1
  291. package/dist/strategies/funding-arb.d.ts +1 -1
  292. package/dist/strategies/funding-arb.js +1 -1
  293. package/dist/trade-validator.d.ts +1 -1
  294. package/dist/trade-validator.js +9 -4
  295. package/dist/utils.d.ts +12 -3
  296. package/dist/utils.js +11 -3
  297. package/dist/venue-health.d.ts +40 -0
  298. package/dist/venue-health.js +75 -0
  299. package/dist/withdraw-gate.d.ts +1 -1
  300. package/dist/withdraw-gate.js +1 -1
  301. package/dist/ws/index.d.ts +959 -0
  302. package/dist/ws/index.js +4295 -0
  303. package/dist/ws/node.d.ts +975 -0
  304. package/dist/ws/node.js +4366 -0
  305. package/dist/ws/pacifica-instrument.d.ts +8 -0
  306. package/dist/ws/pacifica-instrument.js +8 -0
  307. package/dist/ws-cli/credentials.d.ts +62 -0
  308. package/dist/ws-cli/credentials.js +126 -0
  309. package/package.json +58 -10
  310. package/skills/perp-cli/SKILL.md +62 -9
  311. package/skills/perp-cli/references/agent-operations.md +7 -0
  312. package/skills/perp-cli/references/commands.md +76 -5
  313. package/skills/perp-cli/references/ws-library.md +492 -0
@@ -0,0 +1,29 @@
1
+ /**
2
+ * RISEx unit conversions — all exact (BigInt / string), never float maths.
3
+ *
4
+ * - WAD: on-chain style 1e18-scaled integers (`/v1/positions` size / prices, IOC
5
+ * `filled_quantity`) → plain decimal strings.
6
+ * - ns: every RISEx timestamp and `funding_interval` is in NANOseconds → ms.
7
+ * - steps / ticks: orders are sent as integers — size_steps = size / step_size (uint32),
8
+ * price_ticks = price / step_price (uint24, max 16 777 215). Off-grid values are refused,
9
+ * never rounded.
10
+ */
11
+ export declare const UINT24_MAX = 16777215n;
12
+ export declare const UINT32_MAX = 4294967295n;
13
+ export declare const UINT64_MAX = 18446744073709551615n;
14
+ /** 1e18-scaled integer string ("1143307000000000000", "-2905…") → decimal string ("1.143307"). */
15
+ export declare function wadToDecimal(wad: string): string;
16
+ /** Nanosecond decimal string → unix ms (integer division; exact for ms-aligned values). */
17
+ export declare function nsToMs(ns: string | number): number;
18
+ /** ns timestamp or null for "0" / "" / absent (RISEx sends "0" for "no value"). */
19
+ export declare function nsToMsOrNull(ns: unknown): number | null;
20
+ /** Integer size in steps; refused when off the step grid, zero, or above uint32. */
21
+ export declare function toSizeSteps(size: string, stepSize: string): bigint;
22
+ /** Integer price in ticks; refused (INVALID_TICK) when off-grid, INVALID_PARAMS when above uint24. */
23
+ export declare function toPriceTicks(price: string, stepPrice: string): bigint;
24
+ /** A RISEx decimal field, or null when absent / empty (RISEx sends "" for "not set"). */
25
+ export declare function decOrNull(v: unknown): string | null;
26
+ /** Exact comparison of two plain decimal strings: -1 | 0 | 1. */
27
+ export declare function compareDecimal(a: string, b: string): -1 | 0 | 1;
28
+ /** Strip trailing zeros of a plain decimal ("83425.5900" → "83425.59"). */
29
+ export declare function trimDecimal(s: string): string;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * RISEx unit conversions — all exact (BigInt / string), never float maths.
3
+ *
4
+ * - WAD: on-chain style 1e18-scaled integers (`/v1/positions` size / prices, IOC
5
+ * `filled_quantity`) → plain decimal strings.
6
+ * - ns: every RISEx timestamp and `funding_interval` is in NANOseconds → ms.
7
+ * - steps / ticks: orders are sent as integers — size_steps = size / step_size (uint32),
8
+ * price_ticks = price / step_price (uint24, max 16 777 215). Off-grid values are refused,
9
+ * never rounded.
10
+ */
11
+ import { PerpError } from "../../errors.js";
12
+ import { exactUnits } from "../precision.js";
13
+ export const UINT24_MAX = 16777215n;
14
+ export const UINT32_MAX = 4294967295n;
15
+ export const UINT64_MAX = 18446744073709551615n;
16
+ /** 1e18-scaled integer string ("1143307000000000000", "-2905…") → decimal string ("1.143307"). */
17
+ export function wadToDecimal(wad) {
18
+ const s = wad.trim();
19
+ if (!/^-?\d+$/.test(s))
20
+ throw new PerpError("EXCHANGE_ERROR", `RISEx: "${wad}" is not a WAD integer`, { exchange: "risex" });
21
+ const neg = s.startsWith("-");
22
+ const digits = (neg ? s.slice(1) : s).replace(/^0+(?=\d)/, "");
23
+ const padded = digits.padStart(19, "0");
24
+ const int = padded.slice(0, padded.length - 18).replace(/^0+(?=\d)/, "");
25
+ const frac = padded.slice(padded.length - 18).replace(/0+$/, "");
26
+ const out = frac.length > 0 ? `${int}.${frac}` : int;
27
+ return neg && out !== "0" ? `-${out}` : out;
28
+ }
29
+ /** Nanosecond decimal string → unix ms (integer division; exact for ms-aligned values). */
30
+ export function nsToMs(ns) {
31
+ const s = String(ns).trim();
32
+ if (!/^\d+$/.test(s))
33
+ throw new PerpError("EXCHANGE_ERROR", `RISEx: "${ns}" is not a nanosecond timestamp`, { exchange: "risex" });
34
+ return Number(BigInt(s) / 1000000n);
35
+ }
36
+ /** ns timestamp or null for "0" / "" / absent (RISEx sends "0" for "no value"). */
37
+ export function nsToMsOrNull(ns) {
38
+ if (typeof ns !== "string" && typeof ns !== "number")
39
+ return null;
40
+ const s = String(ns).trim();
41
+ if (s === "" || s === "0")
42
+ return null;
43
+ return nsToMs(s);
44
+ }
45
+ /** Integer size in steps; refused when off the step grid, zero, or above uint32. */
46
+ export function toSizeSteps(size, stepSize) {
47
+ const u = exactUnits(size, stepSize);
48
+ if (u === null) {
49
+ throw new PerpError("INVALID_PARAMS", `RISEx size ${size} is not a multiple of the step size ${stepSize}`, {
50
+ exchange: "risex", remediation: `Use a size that is a multiple of ${stepSize}`,
51
+ });
52
+ }
53
+ if (u <= 0n)
54
+ throw new PerpError("SIZE_TOO_SMALL", `RISEx size ${size} is below one step (${stepSize})`, { exchange: "risex" });
55
+ if (u > UINT32_MAX) {
56
+ throw new PerpError("SIZE_TOO_LARGE", `RISEx size ${size} is ${u} steps, above the uint32 limit`, {
57
+ exchange: "risex", remediation: "Split the order",
58
+ });
59
+ }
60
+ return u;
61
+ }
62
+ /** Integer price in ticks; refused (INVALID_TICK) when off-grid, INVALID_PARAMS when above uint24. */
63
+ export function toPriceTicks(price, stepPrice) {
64
+ const u = exactUnits(price, stepPrice);
65
+ if (u === null) {
66
+ throw new PerpError("INVALID_TICK", `RISEx price ${price} is not a multiple of the tick ${stepPrice}`, {
67
+ exchange: "risex", remediation: `Use a price that is a multiple of ${stepPrice}`,
68
+ });
69
+ }
70
+ if (u <= 0n)
71
+ throw new PerpError("INVALID_PARAMS", `RISEx price ${price} must be positive`, { exchange: "risex" });
72
+ if (u > UINT24_MAX) {
73
+ throw new PerpError("INVALID_PARAMS", `RISEx price ${price} is ${u} ticks, above the uint24 order limit (${UINT24_MAX})`, { exchange: "risex" });
74
+ }
75
+ return u;
76
+ }
77
+ /** A RISEx decimal field, or null when absent / empty (RISEx sends "" for "not set"). */
78
+ export function decOrNull(v) {
79
+ if (typeof v !== "string")
80
+ return null;
81
+ const s = v.trim();
82
+ return /^-?\d+(\.\d+)?$/.test(s) ? s : null;
83
+ }
84
+ /** Exact comparison of two plain decimal strings: -1 | 0 | 1. */
85
+ export function compareDecimal(a, b) {
86
+ const parse = (s) => {
87
+ if (!/^-?\d+(\.\d+)?$/.test(s))
88
+ throw new PerpError("EXCHANGE_ERROR", `RISEx: "${s}" is not a decimal`, { exchange: "risex" });
89
+ const neg = s.startsWith("-");
90
+ const [ip, fp = ""] = (neg ? s.slice(1) : s).split(".");
91
+ return { int: BigInt(ip + fp) * (neg ? -1n : 1n), scale: fp.length };
92
+ };
93
+ const x = parse(a), y = parse(b);
94
+ const scale = Math.max(x.scale, y.scale);
95
+ const xi = x.int * 10n ** BigInt(scale - x.scale), yi = y.int * 10n ** BigInt(scale - y.scale);
96
+ return xi < yi ? -1 : xi > yi ? 1 : 0;
97
+ }
98
+ /** Strip trailing zeros of a plain decimal ("83425.5900" → "83425.59"). */
99
+ export function trimDecimal(s) {
100
+ if (!s.includes("."))
101
+ return s;
102
+ const out = s.replace(/0+$/, "").replace(/\.$/, "");
103
+ return out === "-0" ? "0" : out;
104
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Single slippage resolver for every market-type order (design §13.2, §13.10 #1).
2
+ * Single slippage resolver for every market-type order.
3
3
  *
4
4
  * Precedence (all explicit configuration, no hidden constants):
5
5
  * --slippage-bps flag → settings.slippageBps[venue] → settings.defaultSlippageBps.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Single slippage resolver for every market-type order (design §13.2, §13.10 #1).
2
+ * Single slippage resolver for every market-type order.
3
3
  *
4
4
  * Precedence (all explicit configuration, no hidden constants):
5
5
  * --slippage-bps flag → settings.slippageBps[venue] → settings.defaultSlippageBps.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Spot adapter factory — the only place spot adapters are created (design §12.4 #1).
2
+ * Spot adapter factory — the only place spot adapters are created.
3
3
  *
4
4
  * - `getSpotAdapter` returns null iff the venue has no spot (`def.spot === null`).
5
5
  * Creation / init / network errors propagate (SSOT Rule #2 — no catch).
@@ -8,8 +8,8 @@
8
8
  * so the key is the adapter's own network, not the process-wide one) and per
9
9
  * on-chain-approve option. Read-only and signed perp adapters keep separate entries.
10
10
  *
11
- * Bot scope (§12.4 #3): bots only trade spot on venues where `isBotSpotVenue(def)`.
12
- * Quote/base detection (§12.4 #6) goes through `def.spot.quoteAssets`.
11
+ * Bot scope: bots only trade spot on venues where `isBotSpotVenue(def)`.
12
+ * Quote/base detection goes through `def.spot.quoteAssets`.
13
13
  */
14
14
  import type { AssetClass, Network, VenueMarket } from "./venue-types.js";
15
15
  import type { ExchangeAdapter } from "./interface.js";
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Spot adapter factory — the only place spot adapters are created (design §12.4 #1).
2
+ * Spot adapter factory — the only place spot adapters are created.
3
3
  *
4
4
  * - `getSpotAdapter` returns null iff the venue has no spot (`def.spot === null`).
5
5
  * Creation / init / network errors propagate (SSOT Rule #2 — no catch).
@@ -8,8 +8,8 @@
8
8
  * so the key is the adapter's own network, not the process-wide one) and per
9
9
  * on-chain-approve option. Read-only and signed perp adapters keep separate entries.
10
10
  *
11
- * Bot scope (§12.4 #3): bots only trade spot on venues where `isBotSpotVenue(def)`.
12
- * Quote/base detection (§12.4 #6) goes through `def.spot.quoteAssets`.
11
+ * Bot scope: bots only trade spot on venues where `isBotSpotVenue(def)`.
12
+ * Quote/base detection goes through `def.spot.quoteAssets`.
13
13
  */
14
14
  import { classifyError, extractErrorMessage, PerpError } from "../errors.js";
15
15
  import { unsupported } from "./trading-v2.js";
@@ -84,7 +84,7 @@ export async function requireSpotAdapter(venue, perp, opts = NO_ONCHAIN_APPROVE)
84
84
  throw new Error(`unreachable: ${venue} spot definition present but no adapter`);
85
85
  return spot;
86
86
  }
87
- // ── Bot scope (§12.4 #3) ──
87
+ // ── Bot scope ──
88
88
  /**
89
89
  * Bots (funding-arb*, spot-perp-*, arb-auto, arb/index) only use spot venues with a
90
90
  * separate (or runtime-decided) spot balance and a spot↔perp transfer. Pacifica
@@ -116,7 +116,7 @@ export async function getBotSpotAdapter(name, perp) {
116
116
  return null;
117
117
  return getSpotAdapter(id, perp);
118
118
  }
119
- // ── Quote / base helpers (§12.4 #6) ──
119
+ // ── Quote / base helpers ──
120
120
  /** Upper-cased token with venue balance suffixes removed ("USDC_SPOT" / "PURR-SPOT" → base). */
121
121
  export function spotTokenBase(token) {
122
122
  return token.toUpperCase().replace(/[-_]SPOT$/, "");
@@ -171,7 +171,7 @@ export function spotSharesPerpMargin(def, perp) {
171
171
  * `shared` is carried through so aggregators can skip money already in perp equity.
172
172
  */
173
173
  export function valueSpotBalances(def, net, balances, markets, warn) {
174
- // markPrice null = no usable price (§12 SpotMarketInfo) → the token is excluded below.
174
+ // markPrice null = no usable price (SpotMarketInfo) → the token is excluded below.
175
175
  const prices = new Map(markets.flatMap(m => (m.markPrice === null ? [] : [[m.baseToken.toUpperCase(), Number(m.markPrice)]])));
176
176
  const out = [];
177
177
  for (const b of balances) {
@@ -248,7 +248,7 @@ export async function withSpotTimeout(p, what, timeoutMs = SPOT_VALUATION_TIMEOU
248
248
  clearTimeout(timer);
249
249
  }
250
250
  }
251
- // ── Transfers (§12.4 #4) ──
251
+ // ── Transfers ──
252
252
  function ownerOf(spot) {
253
253
  const o = _owner.get(spot);
254
254
  if (!o)
@@ -3,10 +3,10 @@
3
3
  * Separate from ExchangeAdapter because spot uses balance-based tracking
4
4
  * (not position-based) and has no leverage, liquidation, or funding concepts.
5
5
  *
6
- * Venues declare spot support in their VenueDefinition (`spot`, design §12);
6
+ * Venues declare spot support in their VenueDefinition (`spot`);
7
7
  * adapters are created only through `getSpotAdapter()` in spot-factory.ts.
8
8
  *
9
- * Symbol rule (§12.4 #6): `SpotMarketInfo.symbol` is "BASE/QUOTE". Adapter
9
+ * Symbol rule: `SpotMarketInfo.symbol` is "BASE/QUOTE". Adapter
10
10
  * methods accept a bare base ("ETH" → BASE/quoteAssets[0]) or "BASE/QUOTE"
11
11
  * and translate to the venue's wire form.
12
12
  */
@@ -33,11 +33,11 @@ export interface SpotBalance {
33
33
  entryNtl?: string;
34
34
  /**
35
35
  * true = this balance is the same money as the perp account (Pacifica USDC,
36
- * HL unified). Aggregators must not add it on top of perp equity (§12.4 #8).
36
+ * HL unified). Aggregators must not add it on top of perp equity.
37
37
  */
38
38
  shared: boolean;
39
39
  }
40
- /** Normalized spot order result for venues added under §12 (legacy HL/Lighter return unknown). */
40
+ /** Normalized spot order result for venue-definition spot venues (legacy HL/Lighter return unknown). */
41
41
  export interface SpotOrderResult {
42
42
  venue: string;
43
43
  symbol: string;
@@ -129,7 +129,7 @@ export interface SpotAdapter {
129
129
  asks: [string, string][];
130
130
  }>;
131
131
  getSpotBalances(): Promise<SpotBalance[]>;
132
- /** Market order within the resolved slippage bound (design §13.2 — same rule as perps). */
132
+ /** Market order within the resolved slippage bound (same rule as perps). */
133
133
  spotMarketOrder(symbol: string, side: "buy" | "sell", size: string, opts: {
134
134
  slippage: ResolvedSlippage;
135
135
  }): Promise<unknown>;
@@ -3,10 +3,10 @@
3
3
  * Separate from ExchangeAdapter because spot uses balance-based tracking
4
4
  * (not position-based) and has no leverage, liquidation, or funding concepts.
5
5
  *
6
- * Venues declare spot support in their VenueDefinition (`spot`, design §12);
6
+ * Venues declare spot support in their VenueDefinition (`spot`);
7
7
  * adapters are created only through `getSpotAdapter()` in spot-factory.ts.
8
8
  *
9
- * Symbol rule (§12.4 #6): `SpotMarketInfo.symbol` is "BASE/QUOTE". Adapter
9
+ * Symbol rule: `SpotMarketInfo.symbol` is "BASE/QUOTE". Adapter
10
10
  * methods accept a bare base ("ETH" → BASE/quoteAssets[0]) or "BASE/QUOTE"
11
11
  * and translate to the venue's wire form.
12
12
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Spot order outcome → filled size, for bots / arb (design §12.4 #5).
2
+ * Spot order outcome → filled size, for bots / arb.
3
3
  *
4
- * A resolved `spotMarketOrder()` promise does NOT mean "filled": the §12 adapters
4
+ * A resolved `spotMarketOrder()` promise does NOT mean "filled": the adapters
5
5
  * return a `SpotOrderResult` (rejected / cancelled / pending / partial / filled),
6
6
  * and the legacy Hyperliquid / Lighter adapters return a venue payload. Every bot /
7
7
  * arb spot leg goes through `spotMarketFill()` so the hedge is sized to what
@@ -31,7 +31,7 @@
31
31
  * zero delta is ORDER_PENDING (the IOC may still be unconfirmed), never "nothing
32
32
  * filled"; a failed read is never zero.
33
33
  *
34
- * Venue-final IOC outcome (design §13.10 #4): when the spot adapter is IocOutcomeCapable
34
+ * Venue-final IOC outcome: when the spot adapter is IocOutcomeCapable
35
35
  * and iocOutcomeOf(result) reports the order's FINAL filled size, that report is used in
36
36
  * preference to the balance delta, cross-checked against a fresh base-balance delta:
37
37
  * - final 0 and the balance did not move (≤ one lot) → ORDER_REJECTED (definite);
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Spot order outcome → filled size, for bots / arb (design §12.4 #5).
2
+ * Spot order outcome → filled size, for bots / arb.
3
3
  *
4
- * A resolved `spotMarketOrder()` promise does NOT mean "filled": the §12 adapters
4
+ * A resolved `spotMarketOrder()` promise does NOT mean "filled": the adapters
5
5
  * return a `SpotOrderResult` (rejected / cancelled / pending / partial / filled),
6
6
  * and the legacy Hyperliquid / Lighter adapters return a venue payload. Every bot /
7
7
  * arb spot leg goes through `spotMarketFill()` so the hedge is sized to what
@@ -31,7 +31,7 @@
31
31
  * zero delta is ORDER_PENDING (the IOC may still be unconfirmed), never "nothing
32
32
  * filled"; a failed read is never zero.
33
33
  *
34
- * Venue-final IOC outcome (design §13.10 #4): when the spot adapter is IocOutcomeCapable
34
+ * Venue-final IOC outcome: when the spot adapter is IocOutcomeCapable
35
35
  * and iocOutcomeOf(result) reports the order's FINAL filled size, that report is used in
36
36
  * preference to the balance delta, cross-checked against a fresh base-balance delta:
37
37
  * - final 0 and the balance did not move (≤ one lot) → ORDER_REJECTED (definite);
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * TradingV2 — normalized order entry for venues added under the venue
3
- * abstraction (docs/DESIGN-venue-abstraction.md §3 L3, §10.7, §10.10).
3
+ * abstraction.
4
4
  *
5
5
  * Method names deliberately do not overlap the legacy ExchangeAdapter trading
6
6
  * methods, so an adapter can implement both without overloads.
@@ -26,7 +26,7 @@ export interface TradingV2 extends VenueMarketsSource {
26
26
  }
27
27
  export declare function isTradingV2(a: unknown): a is TradingV2;
28
28
  /**
29
- * Uniform "not supported" response (design §13.6, §13.10 #13): CAPABILITY_UNSUPPORTED
29
+ * Uniform "not supported" response: CAPABILITY_UNSUPPORTED
30
30
  * with details { venue, feature, reason } and an alternative in `remediation`.
31
31
  * `venue` is a venue id or display name; `feature` a short capability name
32
32
  * ("limitOrder", "fok", "postOnly", "amend", "trading", "orderbook", ...).
@@ -72,7 +72,7 @@ export declare function validateOrderRequest(venue: VenueDefinition, caps: Venue
72
72
  /**
73
73
  * Resolve a CLI TIF input (common GTC/IOC/FOK/ALO or a venue alias GTT/GTX/TOB,
74
74
  * case-insensitive) to a value the venue declares in its capabilities. Never
75
- * substitutes a different meaning: FOK is never emulated (§13.3) and an
75
+ * substitutes a different meaning: FOK is never emulated and an
76
76
  * unknown / unoffered value is the uniform CAPABILITY_UNSUPPORTED response.
77
77
  */
78
78
  export declare function validateLegacyTif(venue: VenueDefinition, caps: VenueCapabilities, tif: string): TimeInForce;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * TradingV2 — normalized order entry for venues added under the venue
3
- * abstraction (docs/DESIGN-venue-abstraction.md §3 L3, §10.7, §10.10).
3
+ * abstraction.
4
4
  *
5
5
  * Method names deliberately do not overlap the legacy ExchangeAdapter trading
6
6
  * methods, so an adapter can implement both without overloads.
@@ -18,7 +18,7 @@ export function isTradingV2(a) {
18
18
  && typeof r.getVenueMarkets === "function";
19
19
  }
20
20
  /**
21
- * Uniform "not supported" response (design §13.6, §13.10 #13): CAPABILITY_UNSUPPORTED
21
+ * Uniform "not supported" response: CAPABILITY_UNSUPPORTED
22
22
  * with details { venue, feature, reason } and an alternative in `remediation`.
23
23
  * `venue` is a venue id or display name; `feature` a short capability name
24
24
  * ("limitOrder", "fok", "postOnly", "amend", "trading", "orderbook", ...).
@@ -113,7 +113,7 @@ export function validateOrderRequest(venue, caps, req) {
113
113
  requirePositive(req.price, "price");
114
114
  if (req.triggerPrice !== null)
115
115
  requirePositive(req.triggerPrice, "triggerPrice");
116
- // Callers resolve the common default (GTC, design §13.3) before building the request;
116
+ // Callers resolve the common default (GTC) before building the request;
117
117
  // a missing TIF here is a programming error, never a user-facing TIF_REQUIRED.
118
118
  if (needsPrice && req.tif === null) {
119
119
  throw new PerpError("INVALID_PARAMS", `${venue.displayName} ${req.type} order is missing its resolved time-in-force`);
@@ -132,7 +132,7 @@ export function validateOrderRequest(venue, caps, req) {
132
132
  const alternative = `set a take-profit / stop-loss on the position instead: perp trade tpsl ${req.symbol} <long|short> --tp <price>${caps.tpsl !== null && caps.tpsl !== "positionLevel" ? " [--size <qty>]" : ""} -e ${venue.id}`;
133
133
  throw unsupported(venue.displayName, "restingReduceOnly", `a reduce-only limit order can only be ${caps.reduceOnlyTif.join(" / ")} (got ${req.tif})`, `Use --tif ${caps.reduceOnlyTif.join(" | ")}, or ${alternative}`);
134
134
  }
135
- // Callers always pass resolveSlippage() output for market orders (§13.10 #1); a
135
+ // Callers always pass resolveSlippage() output for market orders; a
136
136
  // missing bound here is a programming error, never "send unbounded".
137
137
  if (req.type === "market" && req.slippage === null) {
138
138
  throw new PerpError("INVALID_PARAMS", `${venue.displayName} market order is missing its resolved slippage bound`);
@@ -159,7 +159,7 @@ export function validateOrderRequest(venue, caps, req) {
159
159
  }
160
160
  }
161
161
  /**
162
- * Common TIF aliases (design §13.3): a common name the venue lists under its own
162
+ * Common TIF aliases: a common name the venue lists under its own
163
163
  * spelling, and venue spellings accepted for the common name. Only same-meaning
164
164
  * pairs: GTC ↔ GTT (resting until cancelled / venue expiry), ALO ↔ GTX (post-only).
165
165
  */
@@ -172,7 +172,7 @@ const TIF_ALIASES = {
172
172
  /**
173
173
  * Resolve a CLI TIF input (common GTC/IOC/FOK/ALO or a venue alias GTT/GTX/TOB,
174
174
  * case-insensitive) to a value the venue declares in its capabilities. Never
175
- * substitutes a different meaning: FOK is never emulated (§13.3) and an
175
+ * substitutes a different meaning: FOK is never emulated and an
176
176
  * unknown / unoffered value is the uniform CAPABILITY_UNSUPPORTED response.
177
177
  */
178
178
  export function validateLegacyTif(venue, caps, tif) {
@@ -250,6 +250,9 @@ export function newVenueClientOrderId(venue) {
250
250
  return randomUUID();
251
251
  if (venue.family === "hyperliquid")
252
252
  return `0x${randomBytes(16).toString("hex")}`;
253
+ // RISEx: uint64 decimal — ms timestamp × 1e6 + 6 random digits (< 2^64).
254
+ if (venue.family === "risex")
255
+ return (BigInt(Date.now()) * 1000000n + BigInt(randomBytes(4).readUInt32BE(0) % 1_000_000)).toString();
253
256
  return newClientOrderId();
254
257
  }
255
258
  /** Finality wait after an async (orderAck "async") placement ack. */
@@ -0,0 +1,54 @@
1
+ /**
2
+ * TxFlow public-only adapter — UNOFFICIAL read-only API (see venues/txflow.ts).
3
+ *
4
+ * Market data (markets, orderbook, trades, klines) is implemented. Funding history is not
5
+ * readable (the unofficial API refuses it) and every account / trading method refuses with
6
+ * CAPABILITY_UNSUPPORTED before any request — there is no TxFlow account or order code.
7
+ */
8
+ import type { ExchangeAdapter, ExchangeBalance, ExchangeFundingPayment, ExchangeKline, ExchangeMarketInfo, ExchangeOrder, ExchangePosition, ExchangeTrade, ExchangePublicTrade } from "../interface.js";
9
+ import { type VenueDefinition } from "../venue.js";
10
+ import { type Network, type VenueMarket } from "../venue-types.js";
11
+ /** Candle intervals the TxFlow app requests (HL-style). */
12
+ export declare const TXFLOW_KLINE_INTERVALS: ReadonlySet<string>;
13
+ /** Legacy market row plus the labels every TxFlow row carries in JSON output. */
14
+ export type TxflowMarketInfo = ExchangeMarketInfo & {
15
+ venueApi: "unofficial";
16
+ fundingVerified: false;
17
+ };
18
+ export declare class TxflowPublicAdapter implements ExchangeAdapter {
19
+ readonly venue: VenueDefinition;
20
+ readonly network: Network;
21
+ readonly name: "txflow";
22
+ readonly aliases: readonly ["txf"];
23
+ private readonly client;
24
+ private dir;
25
+ constructor(venue: VenueDefinition, network: Network);
26
+ private market;
27
+ getVenueMarkets(): Promise<VenueMarket[]>;
28
+ getMarkets(): Promise<TxflowMarketInfo[]>;
29
+ getOrderbook(symbol: string): Promise<{
30
+ bids: [string, string][];
31
+ asks: [string, string][];
32
+ }>;
33
+ getRecentTrades(symbol: string, limit?: number): Promise<ExchangePublicTrade[]>;
34
+ getFundingHistory(): Promise<{
35
+ time: number;
36
+ rate: string;
37
+ price: string | null;
38
+ }[]>;
39
+ getKlines(symbol: string, interval: string, startTime: number, endTime: number): Promise<ExchangeKline[]>;
40
+ private refuse;
41
+ getBalance(): Promise<ExchangeBalance>;
42
+ getPositions(): Promise<ExchangePosition[]>;
43
+ getOpenOrders(): Promise<ExchangeOrder[]>;
44
+ getOrderHistory(): Promise<ExchangeOrder[]>;
45
+ getTradeHistory(): Promise<ExchangeTrade[]>;
46
+ getFundingPayments(): Promise<ExchangeFundingPayment[]>;
47
+ marketOrder(): Promise<unknown>;
48
+ limitOrder(): Promise<unknown>;
49
+ editOrder(): Promise<unknown>;
50
+ cancelOrder(): Promise<unknown>;
51
+ cancelAllOrders(): Promise<unknown>;
52
+ setLeverage(): Promise<unknown>;
53
+ stopOrder(): Promise<unknown>;
54
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * TxFlow public-only adapter — UNOFFICIAL read-only API (see venues/txflow.ts).
3
+ *
4
+ * Market data (markets, orderbook, trades, klines) is implemented. Funding history is not
5
+ * readable (the unofficial API refuses it) and every account / trading method refuses with
6
+ * CAPABILITY_UNSUPPORTED before any request — there is no TxFlow account or order code.
7
+ */
8
+ import { PerpError } from "../../errors.js";
9
+ import { unsupported } from "../trading-v2.js";
10
+ import { roleUnsupported, networkConfig } from "../venue.js";
11
+ import { toLegacyMarketInfo } from "../venue-types.js";
12
+ import { txflowShapeError, TxflowClient } from "./client.js";
13
+ import { fetchTxflowDirectory, fetchTxflowMarkets, resolveTxflowMarket } from "./public.js";
14
+ /** Candle intervals the TxFlow app requests (HL-style). */
15
+ export const TXFLOW_KLINE_INTERVALS = new Set(["1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "8h", "12h", "1d", "3d", "1w", "1M"]);
16
+ function dec(v, what) {
17
+ if (typeof v !== "string" || !/^-?\d+(\.\d+)?$/.test(v))
18
+ throw txflowShapeError(`${what} is not a decimal string`);
19
+ return v;
20
+ }
21
+ export class TxflowPublicAdapter {
22
+ venue;
23
+ network;
24
+ name = "txflow";
25
+ aliases = ["txf"];
26
+ client;
27
+ dir = null;
28
+ constructor(venue, network) {
29
+ this.venue = venue;
30
+ this.network = network;
31
+ this.client = new TxflowClient(networkConfig(venue, network).restUrl, venue.rateLimit);
32
+ }
33
+ async market(symbol) {
34
+ if (this.dir === null) {
35
+ this.dir = fetchTxflowDirectory(this.client);
36
+ this.dir.catch(() => { this.dir = null; });
37
+ }
38
+ return resolveTxflowMarket(await this.dir, symbol);
39
+ }
40
+ // ── Market data ──
41
+ async getVenueMarkets() {
42
+ return fetchTxflowMarkets(this.network);
43
+ }
44
+ async getMarkets() {
45
+ return (await this.getVenueMarkets()).map(m => ({ ...toLegacyMarketInfo(m), venueApi: "unofficial", fundingVerified: false }));
46
+ }
47
+ async getOrderbook(symbol) {
48
+ const m = await this.market(symbol);
49
+ const res = await this.client.info({ type: "l2Book", coin: String(m.index) });
50
+ const levels = res?.levels;
51
+ if (!Array.isArray(levels) || levels.length !== 2 || !Array.isArray(levels[0]) || !Array.isArray(levels[1])) {
52
+ throw txflowShapeError("l2Book has no [bids, asks] levels");
53
+ }
54
+ const side = (rows) => rows.map(r => {
55
+ const x = r;
56
+ return [dec(x.px, "l2Book px"), dec(x.sz, "l2Book sz")];
57
+ });
58
+ return { bids: side(levels[0]), asks: side(levels[1]) };
59
+ }
60
+ async getRecentTrades(symbol, limit = 50) {
61
+ const m = await this.market(symbol);
62
+ const res = await this.client.info({ type: "trades", coin: String(m.index) });
63
+ if (!Array.isArray(res))
64
+ throw txflowShapeError("trades is not an array");
65
+ return res.slice(0, limit).map(t => {
66
+ const x = t;
67
+ if (x.side !== "B" && x.side !== "A")
68
+ throw txflowShapeError(`trade side ${String(x.side)}`);
69
+ if (typeof x.time !== "number")
70
+ throw txflowShapeError("trade without time");
71
+ // Public tape carries no fee; "0" follows the legacy ExchangeTrade convention for public trades.
72
+ return { time: x.time, symbol: m.symbol, side: x.side === "B" ? "buy" : "sell", price: dec(x.px, "trade px"), size: dec(x.sz, "trade sz"), fee: null };
73
+ });
74
+ }
75
+ async getFundingHistory() {
76
+ throw unsupported("txflow", "fundingHistory", "TxFlow's unofficial API refuses funding-history reads (HTTP 403)", "Current TxFlow funding (unverified, per hour): perp -e txflow market info <symbol>, or perp arb scan --rates --include-unverified");
77
+ }
78
+ async getKlines(symbol, interval, startTime, endTime) {
79
+ if (!TXFLOW_KLINE_INTERVALS.has(interval)) {
80
+ throw new PerpError("INVALID_PARAMS", `TxFlow: unsupported candle interval "${interval}"`, {
81
+ remediation: `Use one of: ${[...TXFLOW_KLINE_INTERVALS].join(", ")}`,
82
+ });
83
+ }
84
+ const m = await this.market(symbol);
85
+ const res = await this.client.info({ type: "candleSnapshot", req: { coin: String(m.index), interval, startTime, endTime } });
86
+ if (!Array.isArray(res))
87
+ throw txflowShapeError("candleSnapshot is not an array");
88
+ return res.map(c => {
89
+ const x = c;
90
+ if (typeof x.t !== "number")
91
+ throw txflowShapeError("candle without t");
92
+ return {
93
+ time: x.t,
94
+ open: dec(x.o, "candle o"), high: dec(x.h, "candle h"), low: dec(x.l, "candle l"), close: dec(x.c, "candle c"),
95
+ volume: dec(x.v, "candle v"),
96
+ quoteVolume: null,
97
+ trades: typeof x.n === "number" ? x.n : null,
98
+ };
99
+ });
100
+ }
101
+ // ── Account / trading: refused (market-data-only venue, unofficial API) ──
102
+ refuse(feature) {
103
+ throw roleUnsupported(this.venue, feature);
104
+ }
105
+ async getBalance() { return this.refuse("account"); }
106
+ async getPositions() { return this.refuse("account"); }
107
+ async getOpenOrders() { return this.refuse("account"); }
108
+ async getOrderHistory() { return this.refuse("account"); }
109
+ async getTradeHistory() { return this.refuse("account"); }
110
+ async getFundingPayments() { return this.refuse("account"); }
111
+ async marketOrder() { return this.refuse("trading"); }
112
+ async limitOrder() { return this.refuse("trading"); }
113
+ async editOrder() { return this.refuse("trading"); }
114
+ async cancelOrder() { return this.refuse("trading"); }
115
+ async cancelAllOrders() { return this.refuse("trading"); }
116
+ async setLeverage() { return this.refuse("trading"); }
117
+ async stopOrder() { return this.refuse("trading"); }
118
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * TxFlow transport — UNOFFICIAL API (see venues/txflow.ts).
3
+ *
4
+ * - REST: `POST <rest>/info` with an HL-style `{type, ...}` body. Errors come back three ways and
5
+ * are all checked: a non-2xx status (403 "This action is not allowed." = the read is not on the
6
+ * venue's allowlist), HTTP 200 with a `{message}` body, or a body of an unexpected shape.
7
+ * There is no fallback for a refused read (no HL-endpoint substitution, no guessing).
8
+ * - WS one-shot: open → subscribe `allAssetCtxs` → first data frame → close (bulk mark / funding
9
+ * snapshot). The socket factory is injectable for tests.
10
+ */
11
+ import { PerpError } from "../../errors.js";
12
+ import type { RateLimitPolicy } from "../venue.js";
13
+ export declare const TXFLOW_HTTP_TIMEOUT_MS = 15000;
14
+ export declare const TXFLOW_WS_SNAPSHOT_TIMEOUT_MS = 10000;
15
+ /** Shape drift in the unofficial API: an explicit EXCHANGE_ERROR, never a guessed value. */
16
+ export declare function txflowShapeError(what: string): PerpError;
17
+ export declare class TxflowClient {
18
+ private readonly restUrl;
19
+ private readonly rateLimit;
20
+ constructor(restUrl: string, rateLimit: RateLimitPolicy);
21
+ /** POST /info. Returns the parsed body; every error path is a PerpError. */
22
+ info<T = unknown>(body: {
23
+ type: string;
24
+ } & Record<string, unknown>): Promise<T>;
25
+ }
26
+ /** The subset of a `ws` WebSocket the one-shot reader uses (a fake in tests). */
27
+ export interface WsLike {
28
+ on(event: "open", cb: () => void): unknown;
29
+ on(event: "message", cb: (data: unknown) => void): unknown;
30
+ on(event: "error", cb: (err: unknown) => void): unknown;
31
+ on(event: "close", cb: (code: number) => void): unknown;
32
+ send(data: string): void;
33
+ close(): void;
34
+ }
35
+ export type WsFactory = (url: string) => WsLike;
36
+ export declare const defaultWsFactory: WsFactory;
37
+ /** One raw `allAssetCtxs` entry: `coin` is the perpMeta `index` as a decimal string. */
38
+ export interface TxflowRawAssetCtx {
39
+ coin: string;
40
+ ctx: Record<string, unknown>;
41
+ }
42
+ /**
43
+ * Open the socket, subscribe to `allAssetCtxs`, resolve with the first data frame's `ctxs`, close.
44
+ * No frame within the timeout (or a socket error / close first) → EXCHANGE_UNREACHABLE; a frame
45
+ * of an unexpected shape → EXCHANGE_ERROR (shape drift).
46
+ */
47
+ export declare function fetchAllAssetCtxsOnce(wsUrl: string, factory?: WsFactory, timeoutMs?: number): Promise<TxflowRawAssetCtx[]>;