@raintree-technology/perps 0.1.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 (316) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +175 -0
  4. package/dist/adapters/aevo.d.ts +64 -0
  5. package/dist/adapters/aevo.js +899 -0
  6. package/dist/adapters/certification.d.ts +33 -0
  7. package/dist/adapters/certification.js +99 -0
  8. package/dist/adapters/decibel/order-manager.d.ts +45 -0
  9. package/dist/adapters/decibel/order-manager.js +140 -0
  10. package/dist/adapters/decibel/rest-client.d.ts +176 -0
  11. package/dist/adapters/decibel/rest-client.js +155 -0
  12. package/dist/adapters/decibel/ws-feed.d.ts +28 -0
  13. package/dist/adapters/decibel/ws-feed.js +166 -0
  14. package/dist/adapters/decibel.d.ts +108 -0
  15. package/dist/adapters/decibel.js +1377 -0
  16. package/dist/adapters/hyperliquid.d.ts +63 -0
  17. package/dist/adapters/hyperliquid.js +797 -0
  18. package/dist/adapters/index.d.ts +11 -0
  19. package/dist/adapters/index.js +12 -0
  20. package/dist/adapters/interface.d.ts +310 -0
  21. package/dist/adapters/interface.js +15 -0
  22. package/dist/adapters/orderly.d.ts +70 -0
  23. package/dist/adapters/orderly.js +936 -0
  24. package/dist/adapters/paradex.d.ts +69 -0
  25. package/dist/adapters/paradex.js +862 -0
  26. package/dist/adapters/utils.d.ts +17 -0
  27. package/dist/adapters/utils.js +122 -0
  28. package/dist/cli/command-metadata.d.ts +2 -0
  29. package/dist/cli/command-metadata.js +44 -0
  30. package/dist/cli/context.d.ts +14 -0
  31. package/dist/cli/context.js +59 -0
  32. package/dist/cli/experience.d.ts +48 -0
  33. package/dist/cli/experience.js +243 -0
  34. package/dist/cli/ink/app/AppShell.d.ts +12 -0
  35. package/dist/cli/ink/app/AppShell.js +32 -0
  36. package/dist/cli/ink/app/MetricStrip.d.ts +6 -0
  37. package/dist/cli/ink/app/MetricStrip.js +14 -0
  38. package/dist/cli/ink/app/Panel.d.ts +9 -0
  39. package/dist/cli/ink/app/Panel.js +7 -0
  40. package/dist/cli/ink/app/ascii.d.ts +2 -0
  41. package/dist/cli/ink/app/ascii.js +46 -0
  42. package/dist/cli/ink/app/index.d.ts +5 -0
  43. package/dist/cli/ink/app/index.js +4 -0
  44. package/dist/cli/ink/app/types.d.ts +15 -0
  45. package/dist/cli/ink/app/types.js +1 -0
  46. package/dist/cli/ink/components/PnL.d.ts +12 -0
  47. package/dist/cli/ink/components/PnL.js +23 -0
  48. package/dist/cli/ink/components/Spinner.d.ts +13 -0
  49. package/dist/cli/ink/components/Spinner.js +13 -0
  50. package/dist/cli/ink/components/Table.d.ts +14 -0
  51. package/dist/cli/ink/components/Table.js +42 -0
  52. package/dist/cli/ink/components/WatchHeader.d.ts +10 -0
  53. package/dist/cli/ink/components/WatchHeader.js +18 -0
  54. package/dist/cli/ink/components/index.d.ts +4 -0
  55. package/dist/cli/ink/components/index.js +4 -0
  56. package/dist/cli/ink/index.d.ts +4 -0
  57. package/dist/cli/ink/index.js +4 -0
  58. package/dist/cli/ink/render.d.ts +12 -0
  59. package/dist/cli/ink/render.js +21 -0
  60. package/dist/cli/ink/theme.d.ts +29 -0
  61. package/dist/cli/ink/theme.js +40 -0
  62. package/dist/cli/network-defaults.d.ts +10 -0
  63. package/dist/cli/network-defaults.js +35 -0
  64. package/dist/cli/output.d.ts +11 -0
  65. package/dist/cli/output.js +115 -0
  66. package/dist/cli/program.d.ts +18 -0
  67. package/dist/cli/program.js +164 -0
  68. package/dist/cli/watch.d.ts +19 -0
  69. package/dist/cli/watch.js +35 -0
  70. package/dist/client/index.d.ts +55 -0
  71. package/dist/client/index.js +157 -0
  72. package/dist/commands/account/add.d.ts +2 -0
  73. package/dist/commands/account/add.js +510 -0
  74. package/dist/commands/account/balances-simple.d.ts +5 -0
  75. package/dist/commands/account/balances-simple.js +63 -0
  76. package/dist/commands/account/index.d.ts +2 -0
  77. package/dist/commands/account/index.js +17 -0
  78. package/dist/commands/account/ls.d.ts +2 -0
  79. package/dist/commands/account/ls.js +95 -0
  80. package/dist/commands/account/positions-simple.d.ts +5 -0
  81. package/dist/commands/account/positions-simple.js +77 -0
  82. package/dist/commands/account/remove.d.ts +2 -0
  83. package/dist/commands/account/remove.js +47 -0
  84. package/dist/commands/account/set-default.d.ts +2 -0
  85. package/dist/commands/account/set-default.js +47 -0
  86. package/dist/commands/agent/index.d.ts +2 -0
  87. package/dist/commands/agent/index.js +126 -0
  88. package/dist/commands/arb/alert.d.ts +6 -0
  89. package/dist/commands/arb/alert.js +88 -0
  90. package/dist/commands/arb/basis-execute.d.ts +6 -0
  91. package/dist/commands/arb/basis-execute.js +332 -0
  92. package/dist/commands/arb/basis.d.ts +6 -0
  93. package/dist/commands/arb/basis.js +181 -0
  94. package/dist/commands/arb/compare.d.ts +6 -0
  95. package/dist/commands/arb/compare.js +216 -0
  96. package/dist/commands/arb/execute.d.ts +6 -0
  97. package/dist/commands/arb/execute.js +467 -0
  98. package/dist/commands/arb/funding.d.ts +6 -0
  99. package/dist/commands/arb/funding.js +201 -0
  100. package/dist/commands/arb/history.d.ts +6 -0
  101. package/dist/commands/arb/history.js +153 -0
  102. package/dist/commands/arb/index.d.ts +6 -0
  103. package/dist/commands/arb/index.js +29 -0
  104. package/dist/commands/arb/positions.d.ts +6 -0
  105. package/dist/commands/arb/positions.js +158 -0
  106. package/dist/commands/arb/spread.d.ts +6 -0
  107. package/dist/commands/arb/spread.js +253 -0
  108. package/dist/commands/arb/track.d.ts +6 -0
  109. package/dist/commands/arb/track.js +259 -0
  110. package/dist/commands/asset/book-simple.d.ts +5 -0
  111. package/dist/commands/asset/book-simple.js +77 -0
  112. package/dist/commands/asset/index.d.ts +2 -0
  113. package/dist/commands/asset/index.js +5 -0
  114. package/dist/commands/completion.d.ts +2 -0
  115. package/dist/commands/completion.js +161 -0
  116. package/dist/commands/config/index.d.ts +5 -0
  117. package/dist/commands/config/index.js +109 -0
  118. package/dist/commands/data/index.d.ts +31 -0
  119. package/dist/commands/data/index.js +1466 -0
  120. package/dist/commands/doctor.d.ts +2 -0
  121. package/dist/commands/doctor.js +201 -0
  122. package/dist/commands/exchange/index.d.ts +2 -0
  123. package/dist/commands/exchange/index.js +107 -0
  124. package/dist/commands/index.d.ts +2 -0
  125. package/dist/commands/index.js +48 -0
  126. package/dist/commands/markets/index.d.ts +2 -0
  127. package/dist/commands/markets/index.js +5 -0
  128. package/dist/commands/markets/ls-simple.d.ts +7 -0
  129. package/dist/commands/markets/ls-simple.js +277 -0
  130. package/dist/commands/operator/index.d.ts +2 -0
  131. package/dist/commands/operator/index.js +146 -0
  132. package/dist/commands/order/cancel-simple.d.ts +5 -0
  133. package/dist/commands/order/cancel-simple.js +104 -0
  134. package/dist/commands/order/index.d.ts +2 -0
  135. package/dist/commands/order/index.js +13 -0
  136. package/dist/commands/order/limit-simple.d.ts +5 -0
  137. package/dist/commands/order/limit-simple.js +195 -0
  138. package/dist/commands/order/market-simple.d.ts +5 -0
  139. package/dist/commands/order/market-simple.js +190 -0
  140. package/dist/commands/order/shared.d.ts +17 -0
  141. package/dist/commands/order/shared.js +51 -0
  142. package/dist/commands/order/trigger-simple.d.ts +5 -0
  143. package/dist/commands/order/trigger-simple.js +246 -0
  144. package/dist/commands/referral/index.d.ts +2 -0
  145. package/dist/commands/referral/index.js +7 -0
  146. package/dist/commands/referral/set.d.ts +2 -0
  147. package/dist/commands/referral/set.js +26 -0
  148. package/dist/commands/referral/status.d.ts +2 -0
  149. package/dist/commands/referral/status.js +31 -0
  150. package/dist/commands/replay/index.d.ts +2 -0
  151. package/dist/commands/replay/index.js +152 -0
  152. package/dist/commands/risk/analytics.d.ts +2 -0
  153. package/dist/commands/risk/analytics.js +64 -0
  154. package/dist/commands/risk/audit.d.ts +2 -0
  155. package/dist/commands/risk/audit.js +52 -0
  156. package/dist/commands/risk/index.d.ts +2 -0
  157. package/dist/commands/risk/index.js +9 -0
  158. package/dist/commands/risk/rules.d.ts +2 -0
  159. package/dist/commands/risk/rules.js +102 -0
  160. package/dist/commands/server.d.ts +2 -0
  161. package/dist/commands/server.js +208 -0
  162. package/dist/commands/setup/index.d.ts +2 -0
  163. package/dist/commands/setup/index.js +478 -0
  164. package/dist/commands/signal/index.d.ts +2 -0
  165. package/dist/commands/signal/index.js +129 -0
  166. package/dist/commands/state/index.d.ts +2 -0
  167. package/dist/commands/state/index.js +5 -0
  168. package/dist/commands/state/show.d.ts +2 -0
  169. package/dist/commands/state/show.js +105 -0
  170. package/dist/commands/strategy/index.d.ts +4 -0
  171. package/dist/commands/strategy/index.js +73 -0
  172. package/dist/commands/traces/index.d.ts +2 -0
  173. package/dist/commands/traces/index.js +76 -0
  174. package/dist/commands/ui/demo.d.ts +9 -0
  175. package/dist/commands/ui/demo.js +195 -0
  176. package/dist/commands/ui/index.d.ts +2 -0
  177. package/dist/commands/ui/index.js +7 -0
  178. package/dist/commands/ui/terminal.d.ts +2 -0
  179. package/dist/commands/ui/terminal.js +255 -0
  180. package/dist/commands/upgrade.d.ts +2 -0
  181. package/dist/commands/upgrade.js +98 -0
  182. package/dist/index.d.ts +2 -0
  183. package/dist/index.js +4 -0
  184. package/dist/lib/agent/audit.d.ts +12 -0
  185. package/dist/lib/agent/audit.js +13 -0
  186. package/dist/lib/agent/gateway.d.ts +13 -0
  187. package/dist/lib/agent/gateway.js +598 -0
  188. package/dist/lib/agent/metrics.d.ts +33 -0
  189. package/dist/lib/agent/metrics.js +175 -0
  190. package/dist/lib/agent/signature.d.ts +8 -0
  191. package/dist/lib/agent/signature.js +28 -0
  192. package/dist/lib/agent/tools.d.ts +28 -0
  193. package/dist/lib/agent/tools.js +453 -0
  194. package/dist/lib/agent/x402.d.ts +23 -0
  195. package/dist/lib/agent/x402.js +62 -0
  196. package/dist/lib/api-wallet.d.ts +69 -0
  197. package/dist/lib/api-wallet.js +101 -0
  198. package/dist/lib/balance-watcher.d.ts +25 -0
  199. package/dist/lib/balance-watcher.js +83 -0
  200. package/dist/lib/book-watcher.d.ts +25 -0
  201. package/dist/lib/book-watcher.js +48 -0
  202. package/dist/lib/config.d.ts +88 -0
  203. package/dist/lib/config.js +427 -0
  204. package/dist/lib/constants.d.ts +50 -0
  205. package/dist/lib/constants.js +84 -0
  206. package/dist/lib/contracts.d.ts +7 -0
  207. package/dist/lib/contracts.js +8 -0
  208. package/dist/lib/credential-vault.d.ts +22 -0
  209. package/dist/lib/credential-vault.js +109 -0
  210. package/dist/lib/db/accounts.d.ts +83 -0
  211. package/dist/lib/db/accounts.js +203 -0
  212. package/dist/lib/db/funding-history.d.ts +69 -0
  213. package/dist/lib/db/funding-history.js +183 -0
  214. package/dist/lib/db/index.d.ts +11 -0
  215. package/dist/lib/db/index.js +272 -0
  216. package/dist/lib/events/bus.d.ts +10 -0
  217. package/dist/lib/events/bus.js +17 -0
  218. package/dist/lib/events/types.d.ts +51 -0
  219. package/dist/lib/events/types.js +1 -0
  220. package/dist/lib/exchange.d.ts +30 -0
  221. package/dist/lib/exchange.js +84 -0
  222. package/dist/lib/execution/journal.d.ts +25 -0
  223. package/dist/lib/execution/journal.js +158 -0
  224. package/dist/lib/execution/safety.d.ts +34 -0
  225. package/dist/lib/execution/safety.js +197 -0
  226. package/dist/lib/exit-codes.d.ts +18 -0
  227. package/dist/lib/exit-codes.js +60 -0
  228. package/dist/lib/fetch.d.ts +18 -0
  229. package/dist/lib/fetch.js +66 -0
  230. package/dist/lib/fs-security.d.ts +10 -0
  231. package/dist/lib/fs-security.js +26 -0
  232. package/dist/lib/funding-tracker.d.ts +40 -0
  233. package/dist/lib/funding-tracker.js +118 -0
  234. package/dist/lib/logger.d.ts +27 -0
  235. package/dist/lib/logger.js +82 -0
  236. package/dist/lib/network-model.d.ts +13 -0
  237. package/dist/lib/network-model.js +30 -0
  238. package/dist/lib/onboarding.d.ts +133 -0
  239. package/dist/lib/onboarding.js +1459 -0
  240. package/dist/lib/operator-state.d.ts +25 -0
  241. package/dist/lib/operator-state.js +82 -0
  242. package/dist/lib/orders-watcher.d.ts +24 -0
  243. package/dist/lib/orders-watcher.js +74 -0
  244. package/dist/lib/paths.d.ts +20 -0
  245. package/dist/lib/paths.js +23 -0
  246. package/dist/lib/portfolio-watcher.d.ts +33 -0
  247. package/dist/lib/portfolio-watcher.js +95 -0
  248. package/dist/lib/position-watcher.d.ts +16 -0
  249. package/dist/lib/position-watcher.js +44 -0
  250. package/dist/lib/price-watcher.d.ts +15 -0
  251. package/dist/lib/price-watcher.js +84 -0
  252. package/dist/lib/prompts.d.ts +32 -0
  253. package/dist/lib/prompts.js +105 -0
  254. package/dist/lib/rate-limit.d.ts +32 -0
  255. package/dist/lib/rate-limit.js +88 -0
  256. package/dist/lib/risk/analytics.d.ts +39 -0
  257. package/dist/lib/risk/analytics.js +98 -0
  258. package/dist/lib/risk/drawdown.d.ts +18 -0
  259. package/dist/lib/risk/drawdown.js +49 -0
  260. package/dist/lib/risk/evaluation-log.d.ts +29 -0
  261. package/dist/lib/risk/evaluation-log.js +61 -0
  262. package/dist/lib/risk/index.d.ts +4 -0
  263. package/dist/lib/risk/index.js +4 -0
  264. package/dist/lib/risk/limits.d.ts +23 -0
  265. package/dist/lib/risk/limits.js +27 -0
  266. package/dist/lib/risk/manager.d.ts +32 -0
  267. package/dist/lib/risk/manager.js +85 -0
  268. package/dist/lib/risk/policy-middleware.d.ts +33 -0
  269. package/dist/lib/risk/policy-middleware.js +267 -0
  270. package/dist/lib/risk/position-sizer.d.ts +9 -0
  271. package/dist/lib/risk/position-sizer.js +14 -0
  272. package/dist/lib/risk/rules-store.d.ts +16 -0
  273. package/dist/lib/risk/rules-store.js +47 -0
  274. package/dist/lib/schema.d.ts +254 -0
  275. package/dist/lib/schema.js +199 -0
  276. package/dist/lib/secrets.d.ts +3 -0
  277. package/dist/lib/secrets.js +62 -0
  278. package/dist/lib/settings.d.ts +24 -0
  279. package/dist/lib/settings.js +86 -0
  280. package/dist/lib/signals.d.ts +73 -0
  281. package/dist/lib/signals.js +136 -0
  282. package/dist/lib/stable-stringify.d.ts +6 -0
  283. package/dist/lib/stable-stringify.js +17 -0
  284. package/dist/lib/state-context.d.ts +44 -0
  285. package/dist/lib/state-context.js +133 -0
  286. package/dist/lib/strategy/basis-trade.d.ts +2 -0
  287. package/dist/lib/strategy/basis-trade.js +24 -0
  288. package/dist/lib/strategy/funding-arb.d.ts +2 -0
  289. package/dist/lib/strategy/funding-arb.js +23 -0
  290. package/dist/lib/strategy/interface.d.ts +23 -0
  291. package/dist/lib/strategy/interface.js +1 -0
  292. package/dist/lib/strategy/registry.d.ts +4 -0
  293. package/dist/lib/strategy/registry.js +10 -0
  294. package/dist/lib/telemetry.d.ts +25 -0
  295. package/dist/lib/telemetry.js +101 -0
  296. package/dist/lib/trace-queries.d.ts +20 -0
  297. package/dist/lib/trace-queries.js +133 -0
  298. package/dist/lib/trace.d.ts +1 -0
  299. package/dist/lib/trace.js +4 -0
  300. package/dist/lib/trade-reputation.d.ts +6 -0
  301. package/dist/lib/trade-reputation.js +99 -0
  302. package/dist/lib/ui-tokens.d.ts +21 -0
  303. package/dist/lib/ui-tokens.js +26 -0
  304. package/dist/lib/validate.d.ts +39 -0
  305. package/dist/lib/validate.js +108 -0
  306. package/dist/lib/validation.d.ts +9 -0
  307. package/dist/lib/validation.js +64 -0
  308. package/dist/server/cache.d.ts +38 -0
  309. package/dist/server/cache.js +56 -0
  310. package/dist/server/index.d.ts +2 -0
  311. package/dist/server/index.js +89 -0
  312. package/dist/server/ipc.d.ts +18 -0
  313. package/dist/server/ipc.js +159 -0
  314. package/dist/server/subscriptions.d.ts +18 -0
  315. package/dist/server/subscriptions.js +114 -0
  316. package/package.json +124 -0
@@ -0,0 +1,862 @@
1
+ /**
2
+ * Paradex Adapter
3
+ * Implements PerpDEXAdapter interface for Paradex DEX (Starknet L2)
4
+ *
5
+ * API Docs: https://docs.paradex.trade/
6
+ * Base URL: https://api.prod.paradex.trade/v1
7
+ */
8
+ import { ec, shortString, typedData as starkTypedData } from "starknet";
9
+ import { registerAdapter, } from "./interface.js";
10
+ import { fetchWithTimeout } from "../lib/fetch.js";
11
+ import { createLogger } from "../lib/logger.js";
12
+ import { FUNDING_INTERVAL_HOURS } from "../lib/constants.js";
13
+ import { pickUnknown, pickObject, pickString, pickNumber, pickBoolean, toTimestampMs, decimalToScaled } from "./utils.js";
14
+ const log = createLogger("paradex");
15
+ const SEVEN_DAYS_SECONDS = 7 * 24 * 60 * 60;
16
+ export class ParadexAdapter {
17
+ info = {
18
+ id: "paradex",
19
+ name: "Paradex",
20
+ type: "dex",
21
+ chains: ["starknet"],
22
+ features: {
23
+ spot: false,
24
+ perp: true,
25
+ margin: true,
26
+ crossMargin: true,
27
+ isolatedMargin: true,
28
+ stopOrders: true,
29
+ takeProfitOrders: true,
30
+ postOnly: true,
31
+ reduceOnly: true,
32
+ subaccounts: false,
33
+ modifyOrders: true,
34
+ batchOrders: true,
35
+ cancelAllAfter: false,
36
+ publicTrades: true,
37
+ fundingHistory: true,
38
+ orderHistory: true,
39
+ mmp: false,
40
+ twapOrders: false,
41
+ },
42
+ urls: {
43
+ app: "https://app.paradex.trade",
44
+ api: "https://api.prod.paradex.trade/v1",
45
+ docs: "https://docs.paradex.trade",
46
+ testnet: "https://api.testnet.paradex.trade/v1",
47
+ },
48
+ implementation: {
49
+ marketData: "full",
50
+ authenticatedReads: "full",
51
+ orderLifecycle: "full",
52
+ orderCancellation: "full",
53
+ subscriptions: "full",
54
+ advancedTrading: "full",
55
+ },
56
+ };
57
+ baseUrl = "https://api.prod.paradex.trade/v1";
58
+ config = null;
59
+ connected = false;
60
+ marketsCache = null;
61
+ jwtToken = null;
62
+ starknetChainId = null;
63
+ subscriptionTimers = new Set();
64
+ async connect(config) {
65
+ this.config = config;
66
+ const credentialsUrl = config.credentials?.restUrl;
67
+ const network = config.credentials?.network;
68
+ if (credentialsUrl) {
69
+ this.baseUrl = credentialsUrl.replace(/\/$/, "");
70
+ }
71
+ else if (network === "testnet" || (network !== "mainnet" && config.testnet)) {
72
+ this.baseUrl = "https://api.testnet.paradex.trade/v1";
73
+ }
74
+ else {
75
+ this.baseUrl = "https://api.prod.paradex.trade/v1";
76
+ }
77
+ this.jwtToken = config.credentials?.apiBearerToken ?? null;
78
+ this.starknetChainId =
79
+ config.credentials?.paradexChainId && config.credentials.paradexChainId.trim().length > 0
80
+ ? this.toChainIdFelt(config.credentials.paradexChainId)
81
+ : null;
82
+ await this.fetchMarkets();
83
+ this.connected = true;
84
+ }
85
+ async disconnect() {
86
+ for (const timer of this.subscriptionTimers) {
87
+ clearInterval(timer);
88
+ }
89
+ this.subscriptionTimers.clear();
90
+ this.config = null;
91
+ this.marketsCache = null;
92
+ this.jwtToken = null;
93
+ this.starknetChainId = null;
94
+ this.connected = false;
95
+ }
96
+ isConnected() {
97
+ return this.connected;
98
+ }
99
+ // --------------------------------------------------------------------------
100
+ // Private API helpers
101
+ // --------------------------------------------------------------------------
102
+ async fetch(endpoint, options) {
103
+ const url = `${this.baseUrl}${endpoint}`;
104
+ log.debug(`Fetching ${endpoint}`);
105
+ return fetchWithTimeout(url, options);
106
+ }
107
+ async fetchPrivate(endpoint, options) {
108
+ const token = await this.ensureJwtToken();
109
+ const headers = {
110
+ Authorization: `Bearer ${token}`,
111
+ Accept: "application/json",
112
+ ...(options?.headers ?? {}),
113
+ };
114
+ return this.fetch(endpoint, {
115
+ ...options,
116
+ headers,
117
+ });
118
+ }
119
+ async ensureJwtToken() {
120
+ if (this.jwtToken)
121
+ return this.jwtToken;
122
+ const account = this.config?.credentials?.paradexAccount ?? this.config?.credentials?.accountAddress;
123
+ const privateKey = this.config?.credentials?.privateKey;
124
+ if (!account || !privateKey) {
125
+ throw new Error("Paradex private endpoints require PARADEX_API_BEARER_TOKEN or PARADEX_ACCOUNT_ADDRESS + PARADEX_PRIVATE_KEY");
126
+ }
127
+ this.jwtToken = await this.authenticate(account, privateKey);
128
+ return this.jwtToken;
129
+ }
130
+ async authenticate(accountAddress, privateKey) {
131
+ const chainId = await this.getStarkChainId();
132
+ const parsePayload = async (response) => {
133
+ const raw = (await response.text()).trim();
134
+ if (!raw) {
135
+ return { raw };
136
+ }
137
+ try {
138
+ const parsed = JSON.parse(raw);
139
+ return { raw, json: parsed };
140
+ }
141
+ catch {
142
+ return { raw };
143
+ }
144
+ };
145
+ const describeError = (prefix, status, payload) => {
146
+ const detail = (typeof payload.json?.message === "string" && payload.json.message.trim()) ||
147
+ (typeof payload.json?.error === "string" && payload.json.error.trim()) ||
148
+ payload.raw;
149
+ return `${prefix} HTTP ${status}${detail ? `: ${detail}` : ""}`;
150
+ };
151
+ const signedPost = async (typedPath, endpointPath, extraHeaders = {}) => {
152
+ const timestamp = Math.floor(Date.now() / 1000);
153
+ const expiration = timestamp + SEVEN_DAYS_SECONDS;
154
+ const typed = {
155
+ domain: {
156
+ name: "Paradex",
157
+ chainId,
158
+ version: "1",
159
+ },
160
+ primaryType: "Request",
161
+ types: {
162
+ StarkNetDomain: [
163
+ { name: "name", type: "felt" },
164
+ { name: "chainId", type: "felt" },
165
+ { name: "version", type: "felt" },
166
+ ],
167
+ Request: [
168
+ { name: "method", type: "felt" },
169
+ { name: "path", type: "felt" },
170
+ { name: "body", type: "felt" },
171
+ { name: "timestamp", type: "felt" },
172
+ { name: "expiration", type: "felt" },
173
+ ],
174
+ },
175
+ message: {
176
+ method: "POST",
177
+ path: typedPath,
178
+ body: "",
179
+ timestamp,
180
+ expiration,
181
+ },
182
+ };
183
+ const messageHash = starkTypedData.getMessageHash(typed, accountAddress);
184
+ const signature = ec.starkCurve.sign(messageHash, privateKey);
185
+ const signatureValue = JSON.stringify([signature.r.toString(), signature.s.toString()]);
186
+ return fetch(`${this.baseUrl}${endpointPath}`, {
187
+ method: "POST",
188
+ headers: {
189
+ Accept: "application/json",
190
+ "Content-Type": "application/json",
191
+ "PARADEX-STARKNET-ACCOUNT": accountAddress,
192
+ "PARADEX-STARKNET-SIGNATURE": signatureValue,
193
+ "PARADEX-TIMESTAMP": String(timestamp),
194
+ "PARADEX-SIGNATURE-EXPIRATION": String(expiration),
195
+ ...extraHeaders,
196
+ },
197
+ body: JSON.stringify({}),
198
+ });
199
+ };
200
+ let authResponse = await signedPost("/v1/auth", "/auth");
201
+ let authPayload = await parsePayload(authResponse);
202
+ if (!authResponse.ok && authPayload.raw.includes("NOT_ONBOARDED")) {
203
+ const ethereumAccount = this.config?.credentials?.paradexEthereumAccount;
204
+ const onboardingHeaders = {};
205
+ if (ethereumAccount) {
206
+ onboardingHeaders["PARADEX-ETHEREUM-ACCOUNT"] = ethereumAccount;
207
+ }
208
+ const onboardingResponse = await signedPost("/v1/onboarding", "/onboarding", onboardingHeaders);
209
+ const onboardingPayload = await parsePayload(onboardingResponse);
210
+ if (!onboardingResponse.ok) {
211
+ throw new Error(describeError("Paradex onboarding", onboardingResponse.status, onboardingPayload));
212
+ }
213
+ authResponse = await signedPost("/v1/auth", "/auth");
214
+ authPayload = await parsePayload(authResponse);
215
+ }
216
+ if (!authResponse.ok) {
217
+ throw new Error(describeError("Paradex authentication", authResponse.status, authPayload));
218
+ }
219
+ const token = (typeof authPayload.json?.jwt_token === "string" && authPayload.json.jwt_token.trim()) ||
220
+ undefined;
221
+ if (!token) {
222
+ throw new Error("Paradex authentication did not return jwt_token");
223
+ }
224
+ return token;
225
+ }
226
+ async fetchMarkets() {
227
+ if (this.marketsCache)
228
+ return this.marketsCache;
229
+ const response = await this.fetch("/markets");
230
+ this.marketsCache = response.results.filter((m) => m.asset_kind === "PERP");
231
+ return this.marketsCache;
232
+ }
233
+ async getStarkChainId() {
234
+ if (this.starknetChainId)
235
+ return this.starknetChainId;
236
+ const config = await this.fetch("/system/config");
237
+ const chainIdRaw = config.starknet_chain_id;
238
+ if (!chainIdRaw) {
239
+ throw new Error("Paradex system config missing starknet_chain_id");
240
+ }
241
+ this.starknetChainId = this.toChainIdFelt(chainIdRaw);
242
+ return this.starknetChainId;
243
+ }
244
+ toChainIdFelt(value) {
245
+ const trimmed = value.trim();
246
+ if (trimmed.startsWith("0x"))
247
+ return trimmed;
248
+ return shortString.encodeShortString(trimmed);
249
+ }
250
+ // --------------------------------------------------------------------------
251
+ // Public Market Data
252
+ // --------------------------------------------------------------------------
253
+ async getMarkets() {
254
+ const markets = await this.fetchMarkets();
255
+ return markets.map((m) => {
256
+ const imfBase = parseFloat(m.delta1_cross_margin_params.imf_base) || 0.1;
257
+ const maxLeverage = Math.floor(1 / imfBase);
258
+ return {
259
+ symbol: m.symbol,
260
+ baseAsset: m.base_currency,
261
+ quoteAsset: m.quote_currency,
262
+ type: "perp",
263
+ maxLeverage,
264
+ minSize: m.order_size_increment,
265
+ tickSize: m.price_tick_size,
266
+ fundingInterval: FUNDING_INTERVAL_HOURS,
267
+ isActive: true,
268
+ };
269
+ });
270
+ }
271
+ async getMarket(symbol) {
272
+ const markets = await this.getMarkets();
273
+ const normalized = this.normalizeSymbol(symbol);
274
+ return markets.find((m) => m.symbol === normalized) ?? null;
275
+ }
276
+ async getTicker(market) {
277
+ const symbol = this.normalizeSymbol(market);
278
+ const response = await this.fetch(`/markets/summary?market=${encodeURIComponent(symbol)}`);
279
+ if (!response.results || response.results.length === 0) {
280
+ throw new Error(`Market ${market} not found`);
281
+ }
282
+ const summary = response.results[0];
283
+ // Get high/low from summary if available, otherwise try candlesticks
284
+ let high24h = summary.high_24h ?? "0";
285
+ let low24h = summary.low_24h ?? "0";
286
+ if (high24h === "0" || low24h === "0") {
287
+ try {
288
+ const now = Math.floor(Date.now() / 1000);
289
+ const oneDayAgo = now - 86_400;
290
+ const candles = await this.fetch(`/markets/klines?market=${encodeURIComponent(symbol)}&resolution=1D&start_at=${oneDayAgo}&end_at=${now}`);
291
+ if (candles.results?.length > 0) {
292
+ const candle = candles.results[candles.results.length - 1];
293
+ if (candle.high)
294
+ high24h = candle.high;
295
+ if (candle.low)
296
+ low24h = candle.low;
297
+ }
298
+ }
299
+ catch {
300
+ // Candlestick endpoint may not be available for all markets
301
+ }
302
+ }
303
+ return {
304
+ market: symbol,
305
+ lastPrice: summary.last_traded_price,
306
+ markPrice: summary.mark_price,
307
+ indexPrice: summary.underlying_price,
308
+ bid: summary.bid,
309
+ ask: summary.ask,
310
+ volume24h: summary.volume_24h,
311
+ change24h: (parseFloat(summary.price_change_rate_24h) * 100).toFixed(2),
312
+ high24h,
313
+ low24h,
314
+ openInterest: summary.open_interest,
315
+ fundingRate: summary.funding_rate,
316
+ timestamp: summary.created_at,
317
+ };
318
+ }
319
+ async getTickers() {
320
+ const response = await this.fetch("/markets/summary?market=ALL");
321
+ return response.results.map((summary) => ({
322
+ market: summary.symbol,
323
+ lastPrice: summary.last_traded_price,
324
+ markPrice: summary.mark_price,
325
+ indexPrice: summary.underlying_price,
326
+ bid: summary.bid,
327
+ ask: summary.ask,
328
+ volume24h: summary.volume_24h,
329
+ change24h: (parseFloat(summary.price_change_rate_24h) * 100).toFixed(2),
330
+ high24h: summary.high_24h ?? "0",
331
+ low24h: summary.low_24h ?? "0",
332
+ openInterest: summary.open_interest,
333
+ fundingRate: summary.funding_rate,
334
+ timestamp: summary.created_at,
335
+ }));
336
+ }
337
+ async getOrderBook(market, depth = 20) {
338
+ const symbol = this.normalizeSymbol(market);
339
+ const response = await this.fetch(`/orderbook/${encodeURIComponent(symbol)}`);
340
+ return {
341
+ market: symbol,
342
+ bids: (response.bids ?? []).slice(0, depth).map(([price, size]) => ({ price, size })),
343
+ asks: (response.asks ?? []).slice(0, depth).map(([price, size]) => ({ price, size })),
344
+ timestamp: response.last_updated_at ?? Date.now(),
345
+ };
346
+ }
347
+ async getFundingRate(market) {
348
+ const symbol = this.normalizeSymbol(market);
349
+ const response = await this.fetch(`/markets/summary?market=${encodeURIComponent(symbol)}`);
350
+ if (!response.results || response.results.length === 0) {
351
+ return {
352
+ market: symbol,
353
+ rate: "0",
354
+ nextFundingTime: Date.now() + FUNDING_INTERVAL_HOURS * 3600000,
355
+ timestamp: Date.now(),
356
+ };
357
+ }
358
+ const summary = response.results[0];
359
+ return {
360
+ market: symbol,
361
+ rate: summary.funding_rate,
362
+ nextFundingTime: summary.next_funding_time ?? Date.now() + FUNDING_INTERVAL_HOURS * 3600000,
363
+ timestamp: summary.created_at,
364
+ };
365
+ }
366
+ async getFundingRates() {
367
+ // Use the ALL summary endpoint to get funding rates for every market
368
+ const response = await this.fetch("/markets/summary?market=ALL");
369
+ return response.results
370
+ .filter((s) => s.funding_rate && s.funding_rate !== "0")
371
+ .map((s) => ({
372
+ market: s.symbol,
373
+ rate: s.funding_rate,
374
+ nextFundingTime: s.next_funding_time ?? Date.now() + FUNDING_INTERVAL_HOURS * 3600000,
375
+ timestamp: s.created_at,
376
+ }));
377
+ }
378
+ // --------------------------------------------------------------------------
379
+ // Account Data (requires auth)
380
+ // --------------------------------------------------------------------------
381
+ async getPositions() {
382
+ const response = await this.fetchPrivate("/positions");
383
+ const rows = response.results ?? [];
384
+ return rows
385
+ .map((row) => {
386
+ const market = this.normalizeSymbol(pickString(row, ["market", "symbol"]) ?? "");
387
+ if (!market)
388
+ return null;
389
+ const size = pickNumber(row, ["size", "position_size", "qty"], 0);
390
+ if (size === 0)
391
+ return null;
392
+ const sideRaw = pickString(row, ["side"])?.toLowerCase();
393
+ const side = sideRaw === "sell" || sideRaw === "short" || size < 0 ? "short" : "long";
394
+ return {
395
+ market,
396
+ side,
397
+ size: Math.abs(size).toString(),
398
+ entryPrice: pickString(row, ["avg_entry_price", "entry_price", "average_entry_price"]) ?? "0",
399
+ markPrice: pickString(row, ["mark_price", "last_price"]) ?? "0",
400
+ liquidationPrice: pickString(row, ["liquidation_price", "liq_price"]) ?? null,
401
+ unrealizedPnl: pickString(row, ["unrealized_pnl", "upnl"]) ?? "0",
402
+ realizedPnl: pickString(row, ["realized_pnl", "rpnl"]) ?? "0",
403
+ leverage: pickNumber(row, ["leverage"], 1),
404
+ marginType: pickString(row, ["margin_type"])?.toLowerCase() || "cross",
405
+ margin: pickString(row, ["position_margin", "initial_margin"]) ?? "0",
406
+ timestamp: toTimestampMs(pickUnknown(row, ["updated_at", "created_at"]), Date.now()),
407
+ };
408
+ })
409
+ .filter((row) => row !== null);
410
+ }
411
+ async getPosition(market) {
412
+ const positions = await this.getPositions();
413
+ const symbol = this.normalizeSymbol(market);
414
+ return positions.find((p) => p.market === symbol) ?? null;
415
+ }
416
+ async getOrders(market) {
417
+ const suffix = market ? `?market=${encodeURIComponent(this.normalizeSymbol(market))}` : "";
418
+ const response = await this.fetchPrivate(`/orders${suffix}`);
419
+ return (response.results ?? [])
420
+ .map((row) => this.toOrder(row))
421
+ .filter((row) => row !== null);
422
+ }
423
+ async getOrder(orderId) {
424
+ const response = await this.fetchPrivate(`/orders/${encodeURIComponent(orderId)}`);
425
+ const mapped = this.toOrder(response);
426
+ if (mapped)
427
+ return mapped;
428
+ const orders = await this.getOrders();
429
+ return orders.find((o) => o.id === orderId) ?? null;
430
+ }
431
+ async getBalances() {
432
+ const account = await this.fetchPrivate("/account");
433
+ const accountValue = pickNumber(account, ["account_value"], 0);
434
+ const freeCollateral = pickNumber(account, ["free_collateral"], accountValue);
435
+ const unrealizedPnl = pickNumber(account, ["unrealized_pnl"], 0);
436
+ const marginUsed = Math.max(0, accountValue - freeCollateral);
437
+ return [
438
+ {
439
+ asset: "USDC",
440
+ total: accountValue.toString(),
441
+ available: freeCollateral.toString(),
442
+ locked: marginUsed.toString(),
443
+ unrealizedPnl: unrealizedPnl.toString(),
444
+ marginUsed: marginUsed.toString(),
445
+ },
446
+ ];
447
+ }
448
+ async getTrades(market, limit = 100) {
449
+ const params = new URLSearchParams();
450
+ if (market)
451
+ params.set("market", this.normalizeSymbol(market));
452
+ params.set("page_size", String(Math.max(1, Math.min(limit, 500))));
453
+ const response = await this.fetchPrivate(`/fills?${params.toString()}`);
454
+ return (response.results ?? [])
455
+ .map((row) => {
456
+ const symbol = this.normalizeSymbol(pickString(row, ["market", "symbol"]) ?? "");
457
+ if (!symbol)
458
+ return null;
459
+ const sideRaw = pickString(row, ["side"])?.toLowerCase() ?? "buy";
460
+ const side = sideRaw === "sell" ? "short" : "long";
461
+ const orderId = pickString(row, ["order_id"]);
462
+ return {
463
+ id: pickString(row, ["id", "fill_id", "trade_id"]) ??
464
+ `${symbol}-${Date.now()}`,
465
+ market: symbol,
466
+ side,
467
+ price: pickString(row, ["price", "fill_price"]) ?? "0",
468
+ size: pickString(row, ["size", "fill_size", "quantity"]) ?? "0",
469
+ fee: pickString(row, ["fee", "fee_paid"]) ?? "0",
470
+ feeAsset: pickString(row, ["fee_asset"]) ?? "USDC",
471
+ timestamp: toTimestampMs(pickUnknown(row, ["created_at", "timestamp"]), Date.now()),
472
+ ...(orderId ? { orderId } : {}),
473
+ };
474
+ })
475
+ .filter((row) => row !== null);
476
+ }
477
+ // --------------------------------------------------------------------------
478
+ // Trading
479
+ // --------------------------------------------------------------------------
480
+ async placeOrder(params) {
481
+ const account = this.config?.credentials?.paradexAccount ?? this.config?.credentials?.accountAddress;
482
+ const privateKey = this.config?.credentials?.privateKey;
483
+ if (!account || !privateKey) {
484
+ throw new Error("Paradex order placement requires PARADEX_ACCOUNT_ADDRESS and PARADEX_PRIVATE_KEY");
485
+ }
486
+ const market = this.normalizeSymbol(params.market);
487
+ const type = this.toParadexOrderType(params.type);
488
+ const side = params.side === "long" ? "BUY" : "SELL";
489
+ const size = params.size;
490
+ const price = type === "MARKET"
491
+ ? "0"
492
+ : params.price
493
+ ? params.price
494
+ : (() => {
495
+ throw new Error("Price is required for non-market orders");
496
+ })();
497
+ const signatureTimestamp = Date.now();
498
+ const signature = await this.signOrder({
499
+ account,
500
+ privateKey,
501
+ market,
502
+ side,
503
+ type,
504
+ size,
505
+ price,
506
+ signatureTimestamp,
507
+ });
508
+ const payload = {
509
+ market,
510
+ side,
511
+ type,
512
+ size,
513
+ instruction: params.postOnly ? "POST_ONLY" : "GTC",
514
+ signature,
515
+ signature_timestamp: signatureTimestamp,
516
+ client_id: params.clientOrderId,
517
+ reduce_only: params.reduceOnly ?? false,
518
+ };
519
+ if (type !== "MARKET") {
520
+ payload.price = price;
521
+ }
522
+ const response = await this.fetchPrivate("/orders", {
523
+ method: "POST",
524
+ body: JSON.stringify(payload),
525
+ });
526
+ const orderCandidate = pickObject(response, ["results.0", "data", "result"]) ?? payload;
527
+ const mapped = this.toOrder(orderCandidate);
528
+ if (mapped) {
529
+ return mapped;
530
+ }
531
+ return {
532
+ id: pickString(orderCandidate, ["id", "order_id", "client_id"]) ??
533
+ String(signatureTimestamp),
534
+ market,
535
+ side: params.side,
536
+ type: params.type,
537
+ size,
538
+ price: type === "MARKET" ? null : price,
539
+ filled: "0",
540
+ remaining: size,
541
+ status: "open",
542
+ reduceOnly: params.reduceOnly ?? false,
543
+ postOnly: params.postOnly ?? false,
544
+ timestamp: signatureTimestamp,
545
+ triggerPrice: params.triggerPrice,
546
+ };
547
+ }
548
+ async cancelOrder(params) {
549
+ let orderId = params.orderId;
550
+ if (!orderId && params.clientOrderId) {
551
+ const orders = await this.getOrders(params.market);
552
+ const match = orders.find((row) => row.id === params.clientOrderId);
553
+ orderId = match?.id;
554
+ }
555
+ if (!orderId) {
556
+ throw new Error("cancelOrder requires orderId or a resolvable clientOrderId");
557
+ }
558
+ await this.fetchPrivate(`/orders/${encodeURIComponent(orderId)}`, {
559
+ method: "DELETE",
560
+ });
561
+ return true;
562
+ }
563
+ async cancelAllOrders(market) {
564
+ const suffix = market ? `?market=${encodeURIComponent(this.normalizeSymbol(market))}` : "";
565
+ const response = await this.fetchPrivate(`/orders${suffix}`, {
566
+ method: "DELETE",
567
+ });
568
+ const explicit = pickNumber(response, ["cancelled", "count"], NaN);
569
+ if (Number.isFinite(explicit) && explicit >= 0) {
570
+ return Math.floor(explicit);
571
+ }
572
+ const results = pickUnknown(response, ["results"]);
573
+ if (Array.isArray(results)) {
574
+ return results.length;
575
+ }
576
+ return market ? 1 : 0;
577
+ }
578
+ async setLeverage(market, leverage) {
579
+ const symbol = this.normalizeSymbol(market);
580
+ await this.fetchPrivate(`/account/margin/${encodeURIComponent(symbol)}`, {
581
+ method: "POST",
582
+ body: JSON.stringify({ leverage }),
583
+ });
584
+ }
585
+ async setMarginType(market, type) {
586
+ const symbol = this.normalizeSymbol(market);
587
+ await this.fetchPrivate(`/account/margin/${encodeURIComponent(symbol)}`, {
588
+ method: "POST",
589
+ body: JSON.stringify({ margin_type: type.toUpperCase() }),
590
+ });
591
+ }
592
+ // --------------------------------------------------------------------------
593
+ // Advanced Trading
594
+ // --------------------------------------------------------------------------
595
+ async modifyOrder(params) {
596
+ const payload = {};
597
+ if (params.price)
598
+ payload.price = params.price;
599
+ if (params.size)
600
+ payload.size = params.size;
601
+ if (params.triggerPrice)
602
+ payload.trigger_price = params.triggerPrice;
603
+ const response = await this.fetchPrivate(`/orders/${encodeURIComponent(params.orderId)}`, {
604
+ method: "PUT",
605
+ body: JSON.stringify(payload),
606
+ });
607
+ const orderCandidate = pickObject(response, ["results.0", "data", "result"]);
608
+ if (orderCandidate) {
609
+ const mapped = this.toOrder(orderCandidate);
610
+ if (mapped)
611
+ return mapped;
612
+ }
613
+ const order = await this.getOrder(params.orderId);
614
+ if (order)
615
+ return order;
616
+ throw new Error(`Failed to retrieve modified order ${params.orderId}`);
617
+ }
618
+ async batchPlaceOrders(paramsList) {
619
+ // Paradex supports batch via POST /orders with array body
620
+ const results = [];
621
+ for (const params of paramsList) {
622
+ results.push(await this.placeOrder(params));
623
+ }
624
+ return results;
625
+ }
626
+ async batchCancelOrders(paramsList) {
627
+ const results = [];
628
+ for (const params of paramsList) {
629
+ try {
630
+ results.push(await this.cancelOrder(params));
631
+ }
632
+ catch {
633
+ results.push(false);
634
+ }
635
+ }
636
+ return results;
637
+ }
638
+ async cancelAllAfter(_timeoutMs) {
639
+ throw new Error("Paradex does not support cancelAllAfter (dead man's switch)");
640
+ }
641
+ async getOrderHistory(market, limit = 100) {
642
+ const params = new URLSearchParams();
643
+ if (market)
644
+ params.set("market", this.normalizeSymbol(market));
645
+ params.set("page_size", String(Math.max(1, Math.min(limit, 500))));
646
+ const response = await this.fetchPrivate(`/orders/history?${params.toString()}`);
647
+ return (response.results ?? [])
648
+ .map((row) => this.toOrder(row))
649
+ .filter((row) => row !== null);
650
+ }
651
+ async getFundingHistory(market, limit = 100) {
652
+ const params = new URLSearchParams();
653
+ if (market)
654
+ params.set("market", this.normalizeSymbol(market));
655
+ params.set("page_size", String(Math.max(1, Math.min(limit, 500))));
656
+ const response = await this.fetchPrivate(`/funding/payments?${params.toString()}`);
657
+ return (response.results ?? []).map((row) => ({
658
+ market: this.normalizeSymbol(pickString(row, ["market", "symbol"]) ?? ""),
659
+ amount: pickString(row, ["payment", "amount", "funding_payment"]) ?? "0",
660
+ rate: pickString(row, ["funding_rate", "rate"]) ?? "0",
661
+ timestamp: toTimestampMs(pickUnknown(row, ["created_at", "timestamp"]), Date.now()),
662
+ }));
663
+ }
664
+ async getPublicTrades(market, limit = 100) {
665
+ const symbol = this.normalizeSymbol(market);
666
+ const response = await this.fetch(`/trades?market=${encodeURIComponent(symbol)}&page_size=${Math.min(limit, 500)}`);
667
+ return (response.results ?? []).map((row) => ({
668
+ id: pickString(row, ["id", "trade_id"]) ?? `${Date.now()}`,
669
+ market: symbol,
670
+ side: (pickString(row, ["side"])?.toLowerCase() === "sell" ? "short" : "long"),
671
+ price: pickString(row, ["price"]) ?? "0",
672
+ size: pickString(row, ["size", "amount"]) ?? "0",
673
+ timestamp: toTimestampMs(pickUnknown(row, ["created_at", "timestamp"]), Date.now()),
674
+ }));
675
+ }
676
+ // --------------------------------------------------------------------------
677
+ // Market Maker Protection
678
+ // --------------------------------------------------------------------------
679
+ async setMMP(_config) {
680
+ throw new Error("Paradex does not support Market Maker Protection (MMP)");
681
+ }
682
+ async getMMP(_market) {
683
+ throw new Error("Paradex does not support Market Maker Protection (MMP)");
684
+ }
685
+ async resetMMP(_market) {
686
+ throw new Error("Paradex does not support Market Maker Protection (MMP)");
687
+ }
688
+ // --------------------------------------------------------------------------
689
+ // TWAP Orders
690
+ // --------------------------------------------------------------------------
691
+ async placeTWAP(_params) {
692
+ throw new Error("Paradex does not support TWAP orders");
693
+ }
694
+ async cancelTWAP(_twapId) {
695
+ throw new Error("Paradex does not support TWAP orders");
696
+ }
697
+ async getTWAPStatus(_twapId) {
698
+ throw new Error("Paradex does not support TWAP orders");
699
+ }
700
+ // --------------------------------------------------------------------------
701
+ // Margin Management
702
+ // --------------------------------------------------------------------------
703
+ async updateIsolatedMargin(_market, _amount) {
704
+ throw new Error("Paradex does not support isolated margin adjustment");
705
+ }
706
+ // --------------------------------------------------------------------------
707
+ // Subscriptions (polling)
708
+ // --------------------------------------------------------------------------
709
+ subscribe(callbacks) {
710
+ const timer = setInterval(async () => {
711
+ try {
712
+ if (callbacks.onPositions)
713
+ callbacks.onPositions(await this.getPositions());
714
+ if (callbacks.onOrders)
715
+ callbacks.onOrders(await this.getOrders());
716
+ if (callbacks.onBalances)
717
+ callbacks.onBalances(await this.getBalances());
718
+ }
719
+ catch (err) {
720
+ callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
721
+ }
722
+ }, 3000);
723
+ this.subscriptionTimers.add(timer);
724
+ return () => {
725
+ clearInterval(timer);
726
+ this.subscriptionTimers.delete(timer);
727
+ };
728
+ }
729
+ subscribeOrderBook(market, callback) {
730
+ const symbol = this.normalizeSymbol(market);
731
+ const timer = setInterval(async () => {
732
+ try {
733
+ callback(await this.getOrderBook(symbol));
734
+ }
735
+ catch {
736
+ // Ignore transient polling errors.
737
+ }
738
+ }, 1200);
739
+ this.subscriptionTimers.add(timer);
740
+ return () => {
741
+ clearInterval(timer);
742
+ this.subscriptionTimers.delete(timer);
743
+ };
744
+ }
745
+ subscribeTicker(market, callback) {
746
+ const symbol = this.normalizeSymbol(market);
747
+ const timer = setInterval(async () => {
748
+ try {
749
+ callback(await this.getTicker(symbol));
750
+ }
751
+ catch {
752
+ // Ignore transient polling errors.
753
+ }
754
+ }, 1200);
755
+ this.subscriptionTimers.add(timer);
756
+ return () => {
757
+ clearInterval(timer);
758
+ this.subscriptionTimers.delete(timer);
759
+ };
760
+ }
761
+ // --------------------------------------------------------------------------
762
+ // Private Helpers
763
+ // --------------------------------------------------------------------------
764
+ normalizeSymbol(symbol) {
765
+ const upper = symbol.toUpperCase().trim();
766
+ if (!upper)
767
+ return "";
768
+ if (upper.endsWith("-USD-PERP"))
769
+ return upper;
770
+ if (upper.endsWith("-PERP")) {
771
+ return upper.replace("-PERP", "-USD-PERP");
772
+ }
773
+ return `${upper}-USD-PERP`;
774
+ }
775
+ toParadexOrderType(type) {
776
+ if (type === "market")
777
+ return "MARKET";
778
+ return "LIMIT";
779
+ }
780
+ async signOrder(args) {
781
+ const chainId = await this.getStarkChainId();
782
+ const typed = {
783
+ domain: {
784
+ name: "Paradex",
785
+ chainId,
786
+ version: "1",
787
+ },
788
+ primaryType: "Order",
789
+ types: {
790
+ StarkNetDomain: [
791
+ { name: "name", type: "felt" },
792
+ { name: "chainId", type: "felt" },
793
+ { name: "version", type: "felt" },
794
+ ],
795
+ Order: [
796
+ { name: "timestamp", type: "felt" },
797
+ { name: "market", type: "felt" },
798
+ { name: "side", type: "felt" },
799
+ { name: "orderType", type: "felt" },
800
+ { name: "size", type: "felt" },
801
+ { name: "price", type: "felt" },
802
+ ],
803
+ },
804
+ message: {
805
+ timestamp: String(args.signatureTimestamp),
806
+ market: shortString.encodeShortString(args.market),
807
+ side: args.side === "BUY" ? "1" : "2",
808
+ orderType: shortString.encodeShortString(args.type),
809
+ size: this.toQuantums(args.size, 8),
810
+ price: this.toQuantums(args.price, 8),
811
+ },
812
+ };
813
+ const messageHash = starkTypedData.getMessageHash(typed, args.account);
814
+ const signature = ec.starkCurve.sign(messageHash, args.privateKey);
815
+ return JSON.stringify([signature.r.toString(), signature.s.toString()]);
816
+ }
817
+ toOrder(row) {
818
+ const market = this.normalizeSymbol(pickString(row, ["market", "symbol"]) ?? "");
819
+ if (!market)
820
+ return null;
821
+ const sideRaw = pickString(row, ["side"])?.toLowerCase() ?? "buy";
822
+ const side = sideRaw === "sell" ? "short" : "long";
823
+ const typeRaw = pickString(row, ["type", "order_type"])?.toLowerCase() ?? "limit";
824
+ const type = typeRaw.includes("market") ? "market" : "limit";
825
+ const size = pickString(row, ["size", "remaining_size", "quantity"]) ?? "0";
826
+ const filled = pickString(row, ["filled_size", "filled", "executed_quantity"]) ?? "0";
827
+ const remaining = pickString(row, ["remaining_size", "remaining"]) ??
828
+ Math.max(0, parseFloat(size) - parseFloat(filled)).toString();
829
+ const statusRaw = pickString(row, ["status"])?.toLowerCase() ?? "open";
830
+ const status = statusRaw.includes("cancel")
831
+ ? "cancelled"
832
+ : statusRaw.includes("fill")
833
+ ? parseFloat(remaining) > 0
834
+ ? "partial"
835
+ : "filled"
836
+ : statusRaw.includes("expire") || statusRaw.includes("reject")
837
+ ? "expired"
838
+ : "open";
839
+ return {
840
+ id: pickString(row, ["id", "order_id", "client_id"]) ?? `${Date.now()}`,
841
+ market,
842
+ side,
843
+ type,
844
+ size,
845
+ price: pickString(row, ["price"]),
846
+ filled,
847
+ remaining,
848
+ status,
849
+ reduceOnly: pickBoolean(row, ["reduce_only"], false),
850
+ postOnly: pickBoolean(row, ["post_only"], false),
851
+ timestamp: toTimestampMs(pickUnknown(row, ["created_at", "updated_at"]), Date.now()),
852
+ triggerPrice: pickString(row, ["trigger_price"]) ?? undefined,
853
+ };
854
+ }
855
+ toQuantums(value, decimals) {
856
+ const scaled = decimalToScaled(value, decimals);
857
+ return scaled.toString();
858
+ }
859
+ }
860
+ // Register the adapter
861
+ registerAdapter("paradex", () => new ParadexAdapter());
862
+ export default ParadexAdapter;