@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,1459 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { Ed25519Account, Ed25519PrivateKey } from "@aptos-labs/ts-sdk";
5
+ import { KeyPair } from "near-api-js";
6
+ import { ec, shortString, typedData as starkTypedData } from "starknet";
7
+ import { hashTypedData, hexToBytes } from "viem";
8
+ import { privateKeyToAccount } from "viem/accounts";
9
+ import { ONBOARDING_STATE_DIR } from "./paths.js";
10
+ import { ensurePrivateDir, hardenPrivateFile, PRIVATE_FILE_MODE } from "./fs-security.js";
11
+ import { recordTelemetryMetric } from "./telemetry.js";
12
+ export const ALL_SETUP_CHAINS = ["evm", "aptos", "starknet"];
13
+ export const ALL_SETUP_EXCHANGES = [
14
+ "hyperliquid",
15
+ "decibel",
16
+ "aevo",
17
+ "orderly",
18
+ "paradex",
19
+ ];
20
+ export const SETUP_MODES = ["mainnet-data", "testnet-execution", "both"];
21
+ const CHAIN_EXCHANGES = {
22
+ evm: ["hyperliquid", "aevo", "orderly"],
23
+ aptos: ["decibel"],
24
+ starknet: ["paradex"],
25
+ };
26
+ const EXCHANGE_CHAINS = {
27
+ hyperliquid: "evm",
28
+ decibel: "aptos",
29
+ aevo: "evm",
30
+ orderly: "evm",
31
+ paradex: "starknet",
32
+ };
33
+ const ORDERLY_TESTNET_API = "https://testnet-api.orderly.org/v1";
34
+ const ORDERLY_MAINNET_API = "https://api-evm.orderly.org/v1";
35
+ const ORDERLY_EIP712_VERIFYING_CONTRACT = "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC";
36
+ const ORDERLY_FALLBACK_BROKER_IDS = ["woofi_pro", "woofi_dex", "orderly"];
37
+ const AEVO_TESTNET_API = "https://api-testnet.aevo.xyz";
38
+ const AEVO_MAINNET_CHAIN_ID = 1;
39
+ const AEVO_TESTNET_CHAIN_ID = 11155111;
40
+ const PARADEX_TESTNET_API = "https://api.testnet.paradex.trade/v1";
41
+ const SECONDS_PER_DAY = 86_400;
42
+ const MILLISECONDS_PER_DAY = 86_400_000;
43
+ function dedupe(values, order) {
44
+ const set = new Set(values);
45
+ return order.filter((item) => set.has(item));
46
+ }
47
+ function randomHex(bytes) {
48
+ return `0x${randomBytes(bytes).toString("hex")}`;
49
+ }
50
+ function normalizeText(input) {
51
+ if (!input)
52
+ return undefined;
53
+ const value = input.trim();
54
+ return value.length > 0 ? value : undefined;
55
+ }
56
+ function parsePositiveInt(value, fallback) {
57
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
58
+ return Math.floor(value);
59
+ }
60
+ if (typeof value !== "string")
61
+ return fallback;
62
+ const parsed = Number.parseInt(value.trim(), 10);
63
+ if (!Number.isFinite(parsed) || parsed <= 0)
64
+ return fallback;
65
+ return parsed;
66
+ }
67
+ function isRecord(value) {
68
+ return !!value && typeof value === "object" && !Array.isArray(value);
69
+ }
70
+ function readNested(input, path) {
71
+ let cursor = input;
72
+ for (const key of path) {
73
+ if (!isRecord(cursor) || !(key in cursor)) {
74
+ return undefined;
75
+ }
76
+ cursor = cursor[key];
77
+ }
78
+ return cursor;
79
+ }
80
+ function readStringAt(input, path) {
81
+ const value = readNested(input, path);
82
+ if (typeof value !== "string")
83
+ return undefined;
84
+ const trimmed = value.trim();
85
+ return trimmed.length > 0 ? trimmed : undefined;
86
+ }
87
+ async function parseBody(response) {
88
+ const raw = (await response.text()).trim();
89
+ if (!raw)
90
+ return { raw };
91
+ try {
92
+ return { raw, json: JSON.parse(raw) };
93
+ }
94
+ catch {
95
+ return { raw };
96
+ }
97
+ }
98
+ function describeHttpError(prefix, status, payload) {
99
+ const message = readStringAt(payload.json, ["message"]) ??
100
+ readStringAt(payload.json, ["error"]) ??
101
+ payload.raw;
102
+ return `${prefix} HTTP ${status}${message ? `: ${message}` : ""}`;
103
+ }
104
+ function toParadexChainIdFelt(value) {
105
+ const trimmed = value.trim();
106
+ if (trimmed.startsWith("0x"))
107
+ return trimmed;
108
+ return shortString.encodeShortString(trimmed);
109
+ }
110
+ async function fetchParadexChainId(restUrl) {
111
+ const response = await fetch(`${restUrl.replace(/\/$/, "")}/system/config`, {
112
+ method: "GET",
113
+ headers: { accept: "application/json" },
114
+ });
115
+ if (!response.ok) {
116
+ const body = (await response.text()).trim();
117
+ throw new Error(`paradex system config HTTP ${response.status}${body ? `: ${body}` : ""}`);
118
+ }
119
+ const payload = (await response.json());
120
+ if (!payload.starknet_chain_id) {
121
+ throw new Error("paradex system config missing starknet_chain_id");
122
+ }
123
+ return toParadexChainIdFelt(payload.starknet_chain_id);
124
+ }
125
+ async function requestParadexJwtToken(args) {
126
+ const chainId = await fetchParadexChainId(args.restUrl);
127
+ const signedPost = async (typedPath, endpointPath, extraHeaders = {}) => {
128
+ const timestamp = Math.floor(Date.now() / 1000);
129
+ const expiration = timestamp + 7 * 24 * 60 * 60;
130
+ const typed = {
131
+ domain: {
132
+ name: "Paradex",
133
+ chainId,
134
+ version: "1",
135
+ },
136
+ primaryType: "Request",
137
+ types: {
138
+ StarkNetDomain: [
139
+ { name: "name", type: "felt" },
140
+ { name: "chainId", type: "felt" },
141
+ { name: "version", type: "felt" },
142
+ ],
143
+ Request: [
144
+ { name: "method", type: "felt" },
145
+ { name: "path", type: "felt" },
146
+ { name: "body", type: "felt" },
147
+ { name: "timestamp", type: "felt" },
148
+ { name: "expiration", type: "felt" },
149
+ ],
150
+ },
151
+ message: {
152
+ method: "POST",
153
+ path: typedPath,
154
+ body: "",
155
+ timestamp,
156
+ expiration,
157
+ },
158
+ };
159
+ const messageHash = starkTypedData.getMessageHash(typed, args.accountAddress);
160
+ const signature = ec.starkCurve.sign(messageHash, args.privateKey);
161
+ const signatureValue = JSON.stringify([signature.r.toString(), signature.s.toString()]);
162
+ return fetch(`${args.restUrl.replace(/\/$/, "")}${endpointPath}`, {
163
+ method: "POST",
164
+ headers: {
165
+ accept: "application/json",
166
+ "content-type": "application/json",
167
+ "PARADEX-STARKNET-ACCOUNT": args.accountAddress,
168
+ "PARADEX-STARKNET-SIGNATURE": signatureValue,
169
+ "PARADEX-TIMESTAMP": String(timestamp),
170
+ "PARADEX-SIGNATURE-EXPIRATION": String(expiration),
171
+ ...extraHeaders,
172
+ },
173
+ body: JSON.stringify({}),
174
+ });
175
+ };
176
+ const parseAuthBody = async (response) => {
177
+ const raw = (await response.text()).trim();
178
+ if (!raw) {
179
+ return { raw };
180
+ }
181
+ try {
182
+ return { ...JSON.parse(raw), raw };
183
+ }
184
+ catch {
185
+ return { raw };
186
+ }
187
+ };
188
+ let response = await signedPost("/v1/auth", "/auth");
189
+ let parsed = await parseAuthBody(response);
190
+ if (!response.ok && parsed.raw.includes("NOT_ONBOARDED")) {
191
+ const onboardingHeaders = {};
192
+ if (args.ethereumAccount) {
193
+ onboardingHeaders["PARADEX-ETHEREUM-ACCOUNT"] = args.ethereumAccount;
194
+ }
195
+ const onboardingResponse = await signedPost("/v1/onboarding", "/onboarding", onboardingHeaders);
196
+ const onboardingBody = await parseBody(onboardingResponse);
197
+ if (!onboardingResponse.ok) {
198
+ throw new Error(describeHttpError("paradex onboarding", onboardingResponse.status, onboardingBody));
199
+ }
200
+ response = await signedPost("/v1/auth", "/auth");
201
+ parsed = await parseAuthBody(response);
202
+ }
203
+ if (!response.ok) {
204
+ throw new Error(`paradex auth HTTP ${response.status}${parsed.raw ? `: ${parsed.raw}` : ""}`);
205
+ }
206
+ if (!parsed.jwt_token) {
207
+ throw new Error("paradex auth response missing jwt_token");
208
+ }
209
+ return parsed.jwt_token;
210
+ }
211
+ function parseJsonArray(value) {
212
+ if (!Array.isArray(value))
213
+ return [];
214
+ return value.filter((item) => isRecord(item));
215
+ }
216
+ function extractOrderlyAccountId(payload) {
217
+ return (readStringAt(payload, ["data", "account_id"]) ??
218
+ readStringAt(payload, ["data", "accountId"]) ??
219
+ readStringAt(payload, ["account_id"]) ??
220
+ readStringAt(payload, ["accountId"]));
221
+ }
222
+ async function resolveOrderlyBrokerId(args) {
223
+ const provided = normalizeText(args.providedBrokerId);
224
+ if (provided)
225
+ return provided;
226
+ const response = await fetch(`${args.restUrl.replace(/\/$/, "")}/public/broker/name`, {
227
+ method: "GET",
228
+ headers: { accept: "application/json" },
229
+ });
230
+ if (!response.ok)
231
+ return undefined;
232
+ const payload = await parseBody(response);
233
+ const rows = parseJsonArray(readNested(payload.json, ["data", "rows"]));
234
+ const brokerIds = rows
235
+ .map((row) => readStringAt(row, ["broker_id"]) ?? readStringAt(row, ["brokerId"]))
236
+ .filter((value) => !!value);
237
+ for (const candidate of ORDERLY_FALLBACK_BROKER_IDS) {
238
+ if (brokerIds.includes(candidate)) {
239
+ return candidate;
240
+ }
241
+ }
242
+ return brokerIds[0];
243
+ }
244
+ async function bootstrapOrderlyCredentials(args) {
245
+ const walletPrivateKey = args.profile.credentials.orderly?.tradingSecret;
246
+ if (!walletPrivateKey) {
247
+ throw new Error("missing ORDERLY_TRADING_SECRET (wallet signer) for bootstrap");
248
+ }
249
+ const restUrl = normalizeText(args.options.orderlyRestUrl) ??
250
+ normalizeText(process.env.ORDERLY_REST_URL) ??
251
+ ORDERLY_TESTNET_API;
252
+ const chainId = parsePositiveInt(args.options.orderlyChainId, 421614);
253
+ const wallet = privateKeyToAccount(walletPrivateKey);
254
+ const brokerId = await resolveOrderlyBrokerId({
255
+ restUrl,
256
+ providedBrokerId: args.options.orderlyBrokerId,
257
+ });
258
+ if (!brokerId) {
259
+ throw new Error("unable to resolve Orderly broker id; set ORDERLY_BROKER_ID or ORDERLY_TESTNET_FAUCET_BROKER_ID");
260
+ }
261
+ const baseUrl = restUrl.replace(/\/$/, "");
262
+ const query = new URLSearchParams({
263
+ address: wallet.address.toLowerCase(),
264
+ broker_id: brokerId,
265
+ chain_type: "evm",
266
+ });
267
+ let accountId;
268
+ const accountLookup = await fetch(`${baseUrl}/get_account?${query.toString()}`, {
269
+ method: "GET",
270
+ headers: { accept: "application/json" },
271
+ });
272
+ if (accountLookup.ok) {
273
+ const payload = await parseBody(accountLookup);
274
+ accountId = extractOrderlyAccountId(payload.json);
275
+ }
276
+ const domain = {
277
+ name: "Orderly",
278
+ version: "1",
279
+ chainId,
280
+ verifyingContract: ORDERLY_EIP712_VERIFYING_CONTRACT,
281
+ };
282
+ if (!accountId) {
283
+ const nonceQuery = new URLSearchParams({
284
+ broker_id: brokerId,
285
+ chain_id: String(chainId),
286
+ user_address: wallet.address.toLowerCase(),
287
+ });
288
+ const nonceResponse = await fetch(`${baseUrl}/registration_nonce?${nonceQuery.toString()}`, {
289
+ method: "GET",
290
+ headers: { accept: "application/json" },
291
+ });
292
+ const nonceBody = await parseBody(nonceResponse);
293
+ if (!nonceResponse.ok) {
294
+ throw new Error(describeHttpError("orderly registration nonce", nonceResponse.status, nonceBody));
295
+ }
296
+ const registrationNonce = readStringAt(nonceBody.json, ["data", "registration_nonce"]) ??
297
+ readStringAt(nonceBody.json, ["registration_nonce"]);
298
+ if (!registrationNonce) {
299
+ throw new Error("orderly registration nonce missing registration_nonce");
300
+ }
301
+ const timestamp = Date.now();
302
+ const registrationSignature = await wallet.signTypedData({
303
+ domain,
304
+ primaryType: "Registration",
305
+ types: {
306
+ Registration: [
307
+ { name: "brokerId", type: "string" },
308
+ { name: "chainId", type: "uint256" },
309
+ { name: "timestamp", type: "uint64" },
310
+ { name: "registrationNonce", type: "uint256" },
311
+ ],
312
+ },
313
+ message: {
314
+ brokerId,
315
+ chainId: BigInt(chainId),
316
+ timestamp: BigInt(timestamp),
317
+ registrationNonce: BigInt(registrationNonce),
318
+ },
319
+ });
320
+ const registerResponse = await fetch(`${baseUrl}/register_account`, {
321
+ method: "POST",
322
+ headers: { "content-type": "application/json" },
323
+ body: JSON.stringify({
324
+ message: {
325
+ brokerId,
326
+ chainId,
327
+ chainType: "EVM",
328
+ timestamp,
329
+ registrationNonce,
330
+ },
331
+ signature: registrationSignature,
332
+ userAddress: wallet.address.toLowerCase(),
333
+ }),
334
+ });
335
+ const registerBody = await parseBody(registerResponse);
336
+ if (!registerResponse.ok) {
337
+ throw new Error(describeHttpError("orderly register account", registerResponse.status, registerBody));
338
+ }
339
+ accountId = extractOrderlyAccountId(registerBody.json) ?? wallet.address;
340
+ }
341
+ const keyPair = KeyPair.fromRandom("ed25519");
342
+ const orderlySecret = keyPair.toString();
343
+ const orderlyKey = keyPair.getPublicKey().toString();
344
+ const timestamp = Date.now();
345
+ const expiration = timestamp + 365 * MILLISECONDS_PER_DAY;
346
+ const scope = "read,trading";
347
+ const currentKeySignature = await wallet.signTypedData({
348
+ domain,
349
+ primaryType: "AddOrderlyKey",
350
+ types: {
351
+ AddOrderlyKey: [
352
+ { name: "brokerId", type: "string" },
353
+ { name: "chainId", type: "uint256" },
354
+ { name: "orderlyKey", type: "string" },
355
+ { name: "scope", type: "string" },
356
+ { name: "timestamp", type: "uint64" },
357
+ { name: "expiration", type: "uint64" },
358
+ ],
359
+ },
360
+ message: {
361
+ brokerId,
362
+ chainId: BigInt(chainId),
363
+ orderlyKey,
364
+ scope,
365
+ timestamp: BigInt(timestamp),
366
+ expiration: BigInt(expiration),
367
+ },
368
+ });
369
+ const keyBody = {
370
+ message: {
371
+ brokerId,
372
+ chainId,
373
+ chainType: "EVM",
374
+ orderlyKey,
375
+ scope,
376
+ timestamp,
377
+ expiration,
378
+ },
379
+ signature: currentKeySignature,
380
+ userAddress: wallet.address.toLowerCase(),
381
+ };
382
+ const orderlyKeyResponse = await fetch(`${baseUrl}/orderly_key`, {
383
+ method: "POST",
384
+ headers: { "content-type": "application/json" },
385
+ body: JSON.stringify(keyBody),
386
+ });
387
+ if (!orderlyKeyResponse.ok) {
388
+ const legacySignature = await wallet.signTypedData({
389
+ domain,
390
+ primaryType: "AddOrderlyKey",
391
+ types: {
392
+ AddOrderlyKey: [
393
+ { name: "brokerId", type: "string" },
394
+ { name: "chainId", type: "uint256" },
395
+ { name: "key", type: "string" },
396
+ { name: "timestamp", type: "uint64" },
397
+ ],
398
+ },
399
+ message: {
400
+ brokerId,
401
+ chainId: BigInt(chainId),
402
+ key: orderlyKey,
403
+ timestamp: BigInt(timestamp),
404
+ },
405
+ });
406
+ const legacyResponse = await fetch(`${baseUrl}/add_orderly_key`, {
407
+ method: "POST",
408
+ headers: { "content-type": "application/json" },
409
+ body: JSON.stringify({
410
+ message: {
411
+ brokerId,
412
+ chainId,
413
+ timestamp,
414
+ key: orderlyKey,
415
+ },
416
+ signature: legacySignature,
417
+ userAddress: wallet.address.toLowerCase(),
418
+ }),
419
+ });
420
+ const legacyBody = await parseBody(legacyResponse);
421
+ if (!legacyResponse.ok) {
422
+ const modernBody = await parseBody(orderlyKeyResponse);
423
+ const modernError = describeHttpError("orderly add key", orderlyKeyResponse.status, modernBody);
424
+ const legacyError = describeHttpError("orderly add key (legacy)", legacyResponse.status, legacyBody);
425
+ throw new Error(`${modernError}; ${legacyError}`);
426
+ }
427
+ }
428
+ return {
429
+ accountId: accountId ?? wallet.address,
430
+ key: orderlyKey,
431
+ secret: orderlySecret,
432
+ brokerId,
433
+ chainId,
434
+ };
435
+ }
436
+ function extractAevoApiCredentials(payload) {
437
+ const apiKey = readStringAt(payload, ["api_key"]) ??
438
+ readStringAt(payload, ["apiKey"]) ??
439
+ readStringAt(payload, ["data", "api_key"]) ??
440
+ readStringAt(payload, ["data", "apiKey"]) ??
441
+ readStringAt(payload, ["result", "api_key"]) ??
442
+ readStringAt(payload, ["result", "apiKey"]);
443
+ const apiSecret = readStringAt(payload, ["api_secret"]) ??
444
+ readStringAt(payload, ["apiSecret"]) ??
445
+ readStringAt(payload, ["data", "api_secret"]) ??
446
+ readStringAt(payload, ["data", "apiSecret"]) ??
447
+ readStringAt(payload, ["result", "api_secret"]) ??
448
+ readStringAt(payload, ["result", "apiSecret"]);
449
+ return {
450
+ apiKey,
451
+ apiSecret,
452
+ };
453
+ }
454
+ function resolveAevoDomain(restUrl) {
455
+ const normalized = restUrl.toLowerCase();
456
+ if (normalized.includes("testnet")) {
457
+ return { name: "Aevo Testnet", chainId: AEVO_TESTNET_CHAIN_ID };
458
+ }
459
+ return { name: "Aevo Mainnet", chainId: AEVO_MAINNET_CHAIN_ID };
460
+ }
461
+ async function bootstrapAevoCredentials(args) {
462
+ const signingKey = args.profile.credentials.aevo?.signingKey;
463
+ if (!signingKey) {
464
+ throw new Error("missing AEVO_SIGNING_KEY for bootstrap");
465
+ }
466
+ const accountPrivateKeyRaw = normalizeText(args.options.aevoAccountPrivateKey) ?? signingKey;
467
+ if (!/^0x[a-fA-F0-9]{64}$/.test(accountPrivateKeyRaw)) {
468
+ throw new Error("AEVO_ACCOUNT_PRIVATE_KEY must be a 0x-prefixed 32-byte hex private key");
469
+ }
470
+ const restUrl = normalizeText(args.options.aevoRestUrl) ??
471
+ normalizeText(process.env.AEVO_REST_URL) ??
472
+ AEVO_TESTNET_API;
473
+ const baseUrl = restUrl.replace(/\/$/, "");
474
+ const account = privateKeyToAccount(accountPrivateKeyRaw);
475
+ const signingWallet = privateKeyToAccount(signingKey);
476
+ const domainConfig = resolveAevoDomain(baseUrl);
477
+ const expiry = Math.floor(Date.now() / 1000) + 365 * SECONDS_PER_DAY;
478
+ const expiryText = String(expiry);
479
+ const accountRegisterHash = hashTypedData({
480
+ domain: {
481
+ name: domainConfig.name,
482
+ version: "1",
483
+ chainId: domainConfig.chainId,
484
+ },
485
+ primaryType: "Register",
486
+ types: {
487
+ Register: [
488
+ { name: "key", type: "address" },
489
+ { name: "expiry", type: "string" },
490
+ ],
491
+ },
492
+ message: {
493
+ key: account.address,
494
+ expiry: expiryText,
495
+ },
496
+ });
497
+ const accountSignature = await account.signMessage({
498
+ message: {
499
+ raw: hexToBytes(accountRegisterHash),
500
+ },
501
+ });
502
+ const signingKeySignature = await signingWallet.signTypedData({
503
+ domain: {
504
+ name: domainConfig.name,
505
+ version: "1",
506
+ chainId: domainConfig.chainId,
507
+ },
508
+ primaryType: "SignKey",
509
+ types: {
510
+ SignKey: [
511
+ { name: "account", type: "address" },
512
+ { name: "expiry", type: "string" },
513
+ ],
514
+ },
515
+ message: {
516
+ account: account.address,
517
+ expiry: expiryText,
518
+ },
519
+ });
520
+ const registerPayload = {
521
+ account: account.address,
522
+ signing_key: signingWallet.address,
523
+ expiry: expiryText,
524
+ account_signature: accountSignature,
525
+ signing_key_signature: signingKeySignature,
526
+ };
527
+ const registerResponse = await fetch(`${baseUrl}/register`, {
528
+ method: "POST",
529
+ headers: {
530
+ "content-type": "application/json",
531
+ accept: "application/json",
532
+ },
533
+ body: JSON.stringify(registerPayload),
534
+ });
535
+ const registerBody = await parseBody(registerResponse);
536
+ if (!registerResponse.ok) {
537
+ throw new Error(describeHttpError("aevo register", registerResponse.status, registerBody));
538
+ }
539
+ let credentials = extractAevoApiCredentials(registerBody.json);
540
+ if (!credentials.apiKey || !credentials.apiSecret) {
541
+ const apiKeyResponse = await fetch(`${baseUrl}/api-key`, {
542
+ method: "POST",
543
+ headers: {
544
+ "content-type": "application/json",
545
+ accept: "application/json",
546
+ },
547
+ body: JSON.stringify(registerPayload),
548
+ });
549
+ const apiKeyBody = await parseBody(apiKeyResponse);
550
+ if (!apiKeyResponse.ok) {
551
+ throw new Error(describeHttpError("aevo api-key", apiKeyResponse.status, apiKeyBody));
552
+ }
553
+ credentials = extractAevoApiCredentials(apiKeyBody.json);
554
+ }
555
+ if (!credentials.apiKey || !credentials.apiSecret) {
556
+ throw new Error("aevo bootstrap did not return api_key/api_secret");
557
+ }
558
+ return {
559
+ apiKey: credentials.apiKey,
560
+ apiSecret: credentials.apiSecret,
561
+ accountAddress: account.address,
562
+ signingAddress: signingWallet.address,
563
+ };
564
+ }
565
+ function applyAuthOptions(profile, options = {}) {
566
+ const decibelBearer = normalizeText(options.decibelBearerToken);
567
+ if (decibelBearer && profile.credentials.decibel) {
568
+ profile.credentials.decibel.apiBearerToken = decibelBearer;
569
+ }
570
+ const aevoApiKey = normalizeText(options.aevoApiKey);
571
+ const aevoApiSecret = normalizeText(options.aevoApiSecret);
572
+ if (profile.credentials.aevo) {
573
+ if (aevoApiKey)
574
+ profile.credentials.aevo.apiKey = aevoApiKey;
575
+ if (aevoApiSecret)
576
+ profile.credentials.aevo.apiSecret = aevoApiSecret;
577
+ }
578
+ if (profile.credentials.orderly) {
579
+ const accountId = normalizeText(options.orderlyAccountId) ?? profile.wallets.evmAddress;
580
+ const key = normalizeText(options.orderlyKey);
581
+ const secret = normalizeText(options.orderlySecret);
582
+ if (accountId)
583
+ profile.credentials.orderly.accountId = accountId;
584
+ if (key)
585
+ profile.credentials.orderly.key = key;
586
+ if (secret)
587
+ profile.credentials.orderly.secret = secret;
588
+ }
589
+ if (profile.credentials.paradex) {
590
+ const bearer = normalizeText(options.paradexApiBearerToken);
591
+ const accountAddress = normalizeText(options.paradexAccountAddress);
592
+ const ethereumAccount = normalizeText(options.paradexEthereumAccount);
593
+ if (bearer)
594
+ profile.credentials.paradex.apiBearerToken = bearer;
595
+ if (accountAddress)
596
+ profile.credentials.paradex.accountAddress = accountAddress;
597
+ if (ethereumAccount)
598
+ profile.credentials.paradex.ethereumAccount = ethereumAccount;
599
+ }
600
+ refreshManualFollowUps(profile);
601
+ }
602
+ export async function bootstrapOnboardingAuth(profile, options = {}) {
603
+ applyAuthOptions(profile, options);
604
+ const results = [];
605
+ if (profile.exchanges.includes("decibel")) {
606
+ if (profile.credentials.decibel?.apiBearerToken) {
607
+ results.push({
608
+ exchange: "decibel",
609
+ service: "decibel auth",
610
+ status: "ok",
611
+ detail: "using DECIBEL_API_BEARER_TOKEN",
612
+ });
613
+ }
614
+ else {
615
+ results.push({
616
+ exchange: "decibel",
617
+ service: "decibel auth",
618
+ status: "manual",
619
+ detail: "still requires DECIBEL_API_BEARER_TOKEN",
620
+ });
621
+ }
622
+ }
623
+ if (profile.exchanges.includes("aevo")) {
624
+ const hasApiKey = !!normalizeText(profile.credentials.aevo?.apiKey);
625
+ const hasApiSecret = !!normalizeText(profile.credentials.aevo?.apiSecret);
626
+ if (hasApiKey && hasApiSecret) {
627
+ results.push({
628
+ exchange: "aevo",
629
+ service: "aevo auth",
630
+ status: "ok",
631
+ detail: "using AEVO_API_KEY and AEVO_API_SECRET",
632
+ });
633
+ }
634
+ else {
635
+ try {
636
+ const bootstrap = await bootstrapAevoCredentials({ profile, options });
637
+ if (profile.credentials.aevo) {
638
+ profile.credentials.aevo.apiKey = bootstrap.apiKey;
639
+ profile.credentials.aevo.apiSecret = bootstrap.apiSecret;
640
+ }
641
+ results.push({
642
+ exchange: "aevo",
643
+ service: "aevo auth",
644
+ status: "ok",
645
+ detail: `generated AEVO_API_KEY/AEVO_API_SECRET for ${bootstrap.accountAddress} via /register`,
646
+ });
647
+ }
648
+ catch (err) {
649
+ results.push({
650
+ exchange: "aevo",
651
+ service: "aevo auth",
652
+ status: "manual",
653
+ detail: err instanceof Error ? err.message : "still requires AEVO_API_KEY and AEVO_API_SECRET",
654
+ });
655
+ }
656
+ }
657
+ }
658
+ if (profile.exchanges.includes("orderly")) {
659
+ const hasAccountId = !!normalizeText(profile.credentials.orderly?.accountId);
660
+ const hasKey = !!normalizeText(profile.credentials.orderly?.key);
661
+ const hasSecret = !!normalizeText(profile.credentials.orderly?.secret);
662
+ if (hasAccountId && hasKey && hasSecret) {
663
+ results.push({
664
+ exchange: "orderly",
665
+ service: "orderly auth",
666
+ status: "ok",
667
+ detail: "using ORDERLY_ACCOUNT_ID, ORDERLY_KEY, ORDERLY_SECRET",
668
+ });
669
+ }
670
+ else {
671
+ try {
672
+ const defaultRestUrl = profile.mode === "mainnet-data" ? ORDERLY_MAINNET_API : ORDERLY_TESTNET_API;
673
+ const bootstrap = await bootstrapOrderlyCredentials({
674
+ profile,
675
+ options: {
676
+ ...options,
677
+ orderlyRestUrl: normalizeText(options.orderlyRestUrl) ??
678
+ normalizeText(process.env.ORDERLY_REST_URL) ??
679
+ defaultRestUrl,
680
+ },
681
+ });
682
+ if (profile.credentials.orderly) {
683
+ profile.credentials.orderly.accountId = bootstrap.accountId;
684
+ profile.credentials.orderly.key = bootstrap.key;
685
+ profile.credentials.orderly.secret = bootstrap.secret;
686
+ }
687
+ results.push({
688
+ exchange: "orderly",
689
+ service: "orderly auth",
690
+ status: "ok",
691
+ detail: `registered account + API key (broker=${bootstrap.brokerId}, chain=${bootstrap.chainId})`,
692
+ });
693
+ }
694
+ catch (err) {
695
+ results.push({
696
+ exchange: "orderly",
697
+ service: "orderly auth",
698
+ status: "manual",
699
+ detail: err instanceof Error
700
+ ? err.message
701
+ : "still requires ORDERLY_ACCOUNT_ID, ORDERLY_KEY, ORDERLY_SECRET",
702
+ });
703
+ }
704
+ }
705
+ }
706
+ if (profile.exchanges.includes("paradex")) {
707
+ const paradex = profile.credentials.paradex;
708
+ if (!paradex) {
709
+ results.push({
710
+ exchange: "paradex",
711
+ service: "paradex auth",
712
+ status: "error",
713
+ detail: "missing generated Paradex credential bundle",
714
+ });
715
+ }
716
+ else if (normalizeText(paradex.apiBearerToken)) {
717
+ results.push({
718
+ exchange: "paradex",
719
+ service: "paradex auth",
720
+ status: "ok",
721
+ detail: "using PARADEX_API_BEARER_TOKEN",
722
+ });
723
+ }
724
+ else {
725
+ const accountAddress = normalizeText(paradex.accountAddress) ??
726
+ normalizeText(process.env.PARADEX_ACCOUNT_ADDRESS) ??
727
+ normalizeText(paradex.starkPublicKey);
728
+ const privateKey = normalizeText(paradex.privateKey);
729
+ const ethereumAccount = normalizeText(paradex.ethereumAccount) ??
730
+ normalizeText(options.paradexEthereumAccount) ??
731
+ normalizeText(process.env.PARADEX_ETHEREUM_ACCOUNT) ??
732
+ normalizeText(profile.wallets.evmAddress);
733
+ if (!accountAddress || !privateKey) {
734
+ results.push({
735
+ exchange: "paradex",
736
+ service: "paradex auth",
737
+ status: "manual",
738
+ detail: "requires PARADEX_ACCOUNT_ADDRESS (or bearer token) paired with PARADEX_PRIVATE_KEY",
739
+ });
740
+ }
741
+ else {
742
+ const restUrl = normalizeText(options.paradexRestUrl) ?? PARADEX_TESTNET_API;
743
+ try {
744
+ const token = await requestParadexJwtToken({
745
+ restUrl,
746
+ accountAddress,
747
+ privateKey,
748
+ ethereumAccount,
749
+ });
750
+ paradex.apiBearerToken = token;
751
+ paradex.accountAddress = accountAddress;
752
+ if (ethereumAccount) {
753
+ paradex.ethereumAccount = ethereumAccount;
754
+ }
755
+ results.push({
756
+ exchange: "paradex",
757
+ service: "paradex auth",
758
+ status: "ok",
759
+ detail: "minted PARADEX_API_BEARER_TOKEN from Stark signature",
760
+ });
761
+ }
762
+ catch (err) {
763
+ results.push({
764
+ exchange: "paradex",
765
+ service: "paradex auth",
766
+ status: "manual",
767
+ detail: err instanceof Error ? err.message : String(err),
768
+ });
769
+ }
770
+ }
771
+ }
772
+ }
773
+ refreshManualFollowUps(profile);
774
+ return results;
775
+ }
776
+ export function parseSetupMode(value) {
777
+ const normalized = value.trim().toLowerCase();
778
+ if (SETUP_MODES.includes(normalized)) {
779
+ return normalized;
780
+ }
781
+ throw new Error(`Invalid setup mode: ${value}. Expected one of: ${SETUP_MODES.join(", ")}`);
782
+ }
783
+ export function parseSetupChains(value) {
784
+ const normalized = value
785
+ .split(",")
786
+ .map((item) => item.trim().toLowerCase())
787
+ .filter(Boolean);
788
+ if (normalized.length === 0) {
789
+ throw new Error("At least one chain is required");
790
+ }
791
+ for (const item of normalized) {
792
+ if (!ALL_SETUP_CHAINS.includes(item)) {
793
+ throw new Error(`Invalid chain: ${item}. Expected one of: ${ALL_SETUP_CHAINS.join(", ")}`);
794
+ }
795
+ }
796
+ return dedupe(normalized, ALL_SETUP_CHAINS);
797
+ }
798
+ export function parseSetupExchanges(value) {
799
+ const normalized = value
800
+ .split(",")
801
+ .map((item) => item.trim().toLowerCase())
802
+ .filter(Boolean);
803
+ if (normalized.length === 0) {
804
+ throw new Error("At least one exchange is required");
805
+ }
806
+ for (const item of normalized) {
807
+ if (!ALL_SETUP_EXCHANGES.includes(item)) {
808
+ throw new Error(`Invalid exchange: ${item}. Expected one of: ${ALL_SETUP_EXCHANGES.join(", ")}`);
809
+ }
810
+ }
811
+ return dedupe(normalized, ALL_SETUP_EXCHANGES);
812
+ }
813
+ export function resolveExchangesFromChains(chains) {
814
+ const collected = [];
815
+ for (const chain of chains) {
816
+ collected.push(...CHAIN_EXCHANGES[chain]);
817
+ }
818
+ return dedupe(collected, ALL_SETUP_EXCHANGES);
819
+ }
820
+ export function inferChainsFromExchanges(exchanges) {
821
+ const chains = exchanges.map((exchange) => EXCHANGE_CHAINS[exchange]);
822
+ return dedupe(chains, ALL_SETUP_CHAINS);
823
+ }
824
+ const ONBOARDING_PROVIDERS = {
825
+ hyperliquid: {
826
+ exchange: "hyperliquid",
827
+ chain: "evm",
828
+ manualFundingHint: "complete wallet funding in the Hyperliquid testnet web app",
829
+ },
830
+ decibel: {
831
+ exchange: "decibel",
832
+ chain: "aptos",
833
+ manualAuthFollowUp: {
834
+ exchange: "decibel",
835
+ env: ["DECIBEL_API_BEARER_TOKEN"],
836
+ detail: "Set a Decibel bearer token for authenticated reads/trading after wallet generation.",
837
+ },
838
+ fundingTask: ({ profile, options }) => requestAptosFaucet({
839
+ aptosAddress: profile.wallets.aptosAddress,
840
+ options,
841
+ }),
842
+ },
843
+ aevo: {
844
+ exchange: "aevo",
845
+ chain: "evm",
846
+ manualAuthFollowUp: {
847
+ exchange: "aevo",
848
+ env: ["AEVO_API_KEY", "AEVO_API_SECRET", "AEVO_ACCOUNT_PRIVATE_KEY"],
849
+ detail: "If auto-bootstrap fails, set AEVO_API_KEY + AEVO_API_SECRET (or provide AEVO_ACCOUNT_PRIVATE_KEY for setup bootstrap).",
850
+ },
851
+ manualFundingHint: "complete wallet funding in the Aevo testnet web app",
852
+ },
853
+ orderly: {
854
+ exchange: "orderly",
855
+ chain: "evm",
856
+ manualAuthFollowUp: {
857
+ exchange: "orderly",
858
+ env: ["ORDERLY_ACCOUNT_ID", "ORDERLY_KEY", "ORDERLY_SECRET", "ORDERLY_BROKER_ID"],
859
+ detail: "If auto-bootstrap fails, set ORDERLY_* credentials directly or provide ORDERLY_BROKER_ID so setup can register/generate keys.",
860
+ },
861
+ fundingTask: ({ profile, options }) => requestOrderlyFaucet({
862
+ walletAddress: profile.wallets.evmAddress,
863
+ options,
864
+ }),
865
+ },
866
+ paradex: {
867
+ exchange: "paradex",
868
+ chain: "starknet",
869
+ manualAuthFollowUp: {
870
+ exchange: "paradex",
871
+ env: [
872
+ "PARADEX_API_BEARER_TOKEN",
873
+ "PARADEX_ACCOUNT_ADDRESS",
874
+ "PARADEX_ETHEREUM_ACCOUNT",
875
+ ],
876
+ detail: "Provide a Paradex bearer token (or set PARADEX_ACCOUNT_ADDRESS + PARADEX_PRIVATE_KEY, plus PARADEX_ETHEREUM_ACCOUNT when onboarding is required).",
877
+ },
878
+ manualFundingHint: "complete faucet funding in Paradex developer mode",
879
+ },
880
+ };
881
+ function buildManualFollowUps(profile) {
882
+ const followUps = [];
883
+ for (const exchange of profile.exchanges) {
884
+ const provider = ONBOARDING_PROVIDERS[exchange];
885
+ if (!provider.manualAuthFollowUp) {
886
+ continue;
887
+ }
888
+ if (exchange === "decibel") {
889
+ const hasBearer = !!normalizeText(profile.credentials.decibel?.apiBearerToken);
890
+ if (!hasBearer) {
891
+ followUps.push(provider.manualAuthFollowUp);
892
+ }
893
+ continue;
894
+ }
895
+ if (exchange === "aevo") {
896
+ const hasApiKey = !!normalizeText(profile.credentials.aevo?.apiKey);
897
+ const hasApiSecret = !!normalizeText(profile.credentials.aevo?.apiSecret);
898
+ if (!hasApiKey || !hasApiSecret) {
899
+ followUps.push(provider.manualAuthFollowUp);
900
+ }
901
+ continue;
902
+ }
903
+ if (exchange === "orderly") {
904
+ const hasAccountId = !!normalizeText(profile.credentials.orderly?.accountId);
905
+ const hasKey = !!normalizeText(profile.credentials.orderly?.key);
906
+ const hasSecret = !!normalizeText(profile.credentials.orderly?.secret);
907
+ if (!hasAccountId || !hasKey || !hasSecret) {
908
+ followUps.push(provider.manualAuthFollowUp);
909
+ }
910
+ continue;
911
+ }
912
+ if (exchange === "paradex") {
913
+ const hasBearer = !!normalizeText(profile.credentials.paradex?.apiBearerToken);
914
+ const hasAccount = !!normalizeText(profile.credentials.paradex?.accountAddress) &&
915
+ !!normalizeText(profile.credentials.paradex?.privateKey);
916
+ if (!hasBearer && !hasAccount) {
917
+ followUps.push(provider.manualAuthFollowUp);
918
+ }
919
+ continue;
920
+ }
921
+ }
922
+ return followUps;
923
+ }
924
+ function refreshManualFollowUps(profile) {
925
+ profile.manualFollowUps = buildManualFollowUps(profile);
926
+ }
927
+ export function createOnboardingProfile(args) {
928
+ const exchanges = dedupe(args.exchanges, ALL_SETUP_EXCHANGES);
929
+ const chains = inferChainsFromExchanges(exchanges);
930
+ const generatedAt = new Date().toISOString();
931
+ const profile = {
932
+ mode: args.mode,
933
+ chains,
934
+ exchanges,
935
+ generatedAt,
936
+ credentials: {},
937
+ wallets: {},
938
+ manualFollowUps: [],
939
+ };
940
+ const needsEvmWallet = exchanges.some((exchange) => CHAIN_EXCHANGES.evm.includes(exchange));
941
+ if (needsEvmWallet) {
942
+ const evmPrivateKey = randomHex(32);
943
+ const evmAddress = privateKeyToAccount(evmPrivateKey).address;
944
+ profile.wallets.evmAddress = evmAddress;
945
+ if (exchanges.includes("hyperliquid")) {
946
+ profile.credentials.hyperliquid = {
947
+ privateKey: evmPrivateKey,
948
+ walletAddress: evmAddress,
949
+ };
950
+ }
951
+ if (exchanges.includes("aevo")) {
952
+ profile.credentials.aevo = {
953
+ signingKey: evmPrivateKey,
954
+ };
955
+ }
956
+ if (exchanges.includes("orderly")) {
957
+ profile.credentials.orderly = {
958
+ tradingSecret: evmPrivateKey,
959
+ };
960
+ }
961
+ }
962
+ if (exchanges.includes("decibel")) {
963
+ const aptosPrivateKeyObj = Ed25519PrivateKey.generate();
964
+ const aptosPrivateKey = aptosPrivateKeyObj.toString();
965
+ const aptosAddress = new Ed25519Account({ privateKey: aptosPrivateKeyObj })
966
+ .accountAddress
967
+ .toString();
968
+ profile.credentials.decibel = {
969
+ apiWalletPrivateKey: aptosPrivateKey,
970
+ apiWalletAddress: aptosAddress,
971
+ };
972
+ profile.wallets.aptosAddress = aptosAddress;
973
+ }
974
+ if (exchanges.includes("paradex")) {
975
+ const starkPrivateKeyBytes = ec.starkCurve.utils.randomPrivateKey();
976
+ const starkPrivateKey = `0x${Buffer.from(starkPrivateKeyBytes).toString("hex")}`;
977
+ const starkPublicKey = ec.starkCurve.getStarkKey(starkPrivateKey);
978
+ profile.credentials.paradex = {
979
+ privateKey: starkPrivateKey,
980
+ starkPublicKey,
981
+ ethereumAccount: profile.wallets.evmAddress,
982
+ };
983
+ profile.wallets.starkPublicKey = starkPublicKey;
984
+ }
985
+ refreshManualFollowUps(profile);
986
+ return profile;
987
+ }
988
+ function shouldForceTestnetEverywhere(mode) {
989
+ return mode === "testnet-execution";
990
+ }
991
+ function pushTestnetNetworkLine(lines, key, forceTestnetEverywhere) {
992
+ if (forceTestnetEverywhere) {
993
+ lines.push(`${key}=testnet`);
994
+ }
995
+ else {
996
+ lines.push(`# ${key}=testnet`);
997
+ }
998
+ }
999
+ function pushTestnetRestLine(lines, key, value, forceTestnetEverywhere) {
1000
+ if (forceTestnetEverywhere) {
1001
+ lines.push(`${key}=${value}`);
1002
+ }
1003
+ else {
1004
+ lines.push(`# ${key}=${value}`);
1005
+ }
1006
+ }
1007
+ export function buildOnboardingEnvFile(profile) {
1008
+ const forceTestnetEverywhere = shouldForceTestnetEverywhere(profile.mode);
1009
+ const lines = [
1010
+ "# Auto-generated by perps setup wizard",
1011
+ `# Generated: ${profile.generatedAt}`,
1012
+ "# SECURITY: keep this local and never commit it.",
1013
+ "",
1014
+ ];
1015
+ if (profile.mode === "both") {
1016
+ lines.push("# Mode: mainnet-data + testnet-execution");
1017
+ lines.push("# Data commands default to mainnet; execution defaults to testnet.");
1018
+ lines.push("# Exchange network/url overrides are intentionally commented to preserve command-intent defaults.");
1019
+ lines.push("");
1020
+ }
1021
+ else if (profile.mode === "testnet-execution") {
1022
+ lines.push("# Mode: force testnet for selected exchanges");
1023
+ lines.push("# Exchange network/url overrides are active below.");
1024
+ lines.push("");
1025
+ }
1026
+ else {
1027
+ lines.push("# Mode: mainnet data only");
1028
+ lines.push("# No private credentials generated in this profile.");
1029
+ lines.push("");
1030
+ }
1031
+ if (profile.exchanges.includes("hyperliquid") && profile.credentials.hyperliquid) {
1032
+ lines.push("# Hyperliquid");
1033
+ pushTestnetNetworkLine(lines, "HYPERLIQUID_NETWORK", forceTestnetEverywhere);
1034
+ lines.push(`HYPERLIQUID_PRIVATE_KEY=${profile.credentials.hyperliquid.privateKey}`);
1035
+ lines.push(`HYPERLIQUID_WALLET_ADDRESS=${profile.credentials.hyperliquid.walletAddress}`);
1036
+ lines.push("");
1037
+ }
1038
+ if (profile.exchanges.includes("decibel") && profile.credentials.decibel) {
1039
+ lines.push("# Decibel (Aptos)");
1040
+ pushTestnetNetworkLine(lines, "DECIBEL_NETWORK", forceTestnetEverywhere);
1041
+ lines.push(`DECIBEL_API_WALLET_PRIVATE_KEY=${profile.credentials.decibel.apiWalletPrivateKey}`);
1042
+ lines.push(`DECIBEL_API_WALLET_ADDRESS=${profile.credentials.decibel.apiWalletAddress}`);
1043
+ const decibelBearer = normalizeText(profile.credentials.decibel.apiBearerToken);
1044
+ if (decibelBearer) {
1045
+ lines.push(`DECIBEL_API_BEARER_TOKEN=${decibelBearer}`);
1046
+ }
1047
+ else {
1048
+ lines.push("# DECIBEL_API_BEARER_TOKEN=...");
1049
+ }
1050
+ lines.push("");
1051
+ }
1052
+ if (profile.exchanges.includes("aevo") && profile.credentials.aevo) {
1053
+ lines.push("# Aevo");
1054
+ pushTestnetNetworkLine(lines, "AEVO_NETWORK", forceTestnetEverywhere);
1055
+ pushTestnetRestLine(lines, "AEVO_REST_URL", AEVO_TESTNET_API, forceTestnetEverywhere);
1056
+ lines.push(`AEVO_SIGNING_KEY=${profile.credentials.aevo.signingKey}`);
1057
+ const aevoApiKey = normalizeText(profile.credentials.aevo.apiKey);
1058
+ const aevoApiSecret = normalizeText(profile.credentials.aevo.apiSecret);
1059
+ if (aevoApiKey) {
1060
+ lines.push(`AEVO_API_KEY=${aevoApiKey}`);
1061
+ }
1062
+ else {
1063
+ lines.push("# AEVO_API_KEY=...");
1064
+ }
1065
+ if (aevoApiSecret) {
1066
+ lines.push(`AEVO_API_SECRET=${aevoApiSecret}`);
1067
+ }
1068
+ else {
1069
+ lines.push("# AEVO_API_SECRET=...");
1070
+ }
1071
+ lines.push("# AEVO_ACCOUNT_PRIVATE_KEY=... # optional: used only for setup bootstrap if AEVO_API_* absent");
1072
+ lines.push("");
1073
+ }
1074
+ if (profile.exchanges.includes("orderly") && profile.credentials.orderly) {
1075
+ lines.push("# Orderly");
1076
+ pushTestnetNetworkLine(lines, "ORDERLY_NETWORK", forceTestnetEverywhere);
1077
+ pushTestnetRestLine(lines, "ORDERLY_REST_URL", ORDERLY_TESTNET_API, forceTestnetEverywhere);
1078
+ lines.push(`ORDERLY_TRADING_SECRET=${profile.credentials.orderly.tradingSecret}`);
1079
+ const orderlyAccountId = normalizeText(profile.credentials.orderly.accountId);
1080
+ const orderlyKey = normalizeText(profile.credentials.orderly.key);
1081
+ const orderlySecret = normalizeText(profile.credentials.orderly.secret);
1082
+ if (orderlyAccountId) {
1083
+ lines.push(`ORDERLY_ACCOUNT_ID=${orderlyAccountId}`);
1084
+ }
1085
+ else {
1086
+ lines.push("# ORDERLY_ACCOUNT_ID=0x...");
1087
+ }
1088
+ if (orderlyKey) {
1089
+ lines.push(`ORDERLY_KEY=${orderlyKey}`);
1090
+ }
1091
+ else {
1092
+ lines.push("# ORDERLY_KEY=...");
1093
+ }
1094
+ if (orderlySecret) {
1095
+ lines.push(`ORDERLY_SECRET=${orderlySecret}`);
1096
+ }
1097
+ else {
1098
+ lines.push("# ORDERLY_SECRET=...");
1099
+ }
1100
+ lines.push("");
1101
+ }
1102
+ if (profile.exchanges.includes("paradex")) {
1103
+ lines.push("# Paradex (Starknet)");
1104
+ pushTestnetNetworkLine(lines, "PARADEX_NETWORK", forceTestnetEverywhere);
1105
+ pushTestnetRestLine(lines, "PARADEX_REST_URL", PARADEX_TESTNET_API, forceTestnetEverywhere);
1106
+ if (profile.mode === "testnet-execution") {
1107
+ lines.push("PARADEX_CHAIN_ID=SN_SEPOLIA");
1108
+ }
1109
+ else {
1110
+ lines.push("# PARADEX_CHAIN_ID=SN_SEPOLIA");
1111
+ }
1112
+ if (profile.credentials.paradex) {
1113
+ lines.push(`PARADEX_PRIVATE_KEY=${profile.credentials.paradex.privateKey}`);
1114
+ lines.push(`PARADEX_STARK_PUBLIC_KEY=${profile.credentials.paradex.starkPublicKey}`);
1115
+ const paradexAccountAddress = normalizeText(profile.credentials.paradex.accountAddress);
1116
+ if (paradexAccountAddress) {
1117
+ lines.push(`PARADEX_ACCOUNT_ADDRESS=${paradexAccountAddress}`);
1118
+ }
1119
+ else {
1120
+ lines.push("# PARADEX_ACCOUNT_ADDRESS=0x...");
1121
+ }
1122
+ const paradexEthereumAccount = normalizeText(profile.credentials.paradex.ethereumAccount);
1123
+ if (paradexEthereumAccount) {
1124
+ lines.push(`PARADEX_ETHEREUM_ACCOUNT=${paradexEthereumAccount}`);
1125
+ }
1126
+ else {
1127
+ lines.push("# PARADEX_ETHEREUM_ACCOUNT=0x...");
1128
+ }
1129
+ const paradexBearer = normalizeText(profile.credentials.paradex.apiBearerToken);
1130
+ if (paradexBearer) {
1131
+ lines.push(`PARADEX_API_BEARER_TOKEN=${paradexBearer}`);
1132
+ }
1133
+ else {
1134
+ lines.push("# PARADEX_API_BEARER_TOKEN=...");
1135
+ }
1136
+ }
1137
+ else {
1138
+ lines.push("# PARADEX_ACCOUNT_ADDRESS=0x...");
1139
+ lines.push("# PARADEX_ETHEREUM_ACCOUNT=0x...");
1140
+ lines.push("# PARADEX_API_BEARER_TOKEN=...");
1141
+ }
1142
+ lines.push("");
1143
+ }
1144
+ if (profile.exchanges.includes("orderly")) {
1145
+ lines.push("# Optional Orderly setup + faucet automation");
1146
+ lines.push("# ORDERLY_BROKER_ID=...");
1147
+ lines.push("# ORDERLY_TESTNET_FAUCET_BROKER_ID=...");
1148
+ lines.push("# ORDERLY_TESTNET_FAUCET_CHAIN_ID=421614");
1149
+ lines.push("");
1150
+ }
1151
+ if (profile.exchanges.includes("decibel")) {
1152
+ lines.push("# Optional Aptos faucet automation");
1153
+ lines.push("# APTOS_TESTNET_FAUCET_JWT=...");
1154
+ lines.push("# APTOS_TESTNET_FAUCET_AMOUNT=100000000");
1155
+ lines.push("");
1156
+ }
1157
+ if (profile.mode !== "mainnet-data") {
1158
+ lines.push("# Conservative risk defaults for early testnet dry-runs");
1159
+ lines.push("RISK_MAX_POSITION_SIZE_USD=100");
1160
+ lines.push("RISK_MAX_TOTAL_EXPOSURE_USD=200");
1161
+ lines.push("RISK_MAX_LEVERAGE=2");
1162
+ lines.push("");
1163
+ }
1164
+ return `${lines.join("\n").trimEnd()}\n`;
1165
+ }
1166
+ async function requestOrderlyFaucet(args) {
1167
+ const brokerId = normalizeText(args.options.orderlyBrokerId);
1168
+ if (!brokerId) {
1169
+ return {
1170
+ exchange: "orderly",
1171
+ service: "orderly faucet",
1172
+ status: "skipped",
1173
+ detail: "missing ORDERLY_TESTNET_FAUCET_BROKER_ID",
1174
+ };
1175
+ }
1176
+ if (!args.walletAddress) {
1177
+ return {
1178
+ exchange: "orderly",
1179
+ service: "orderly faucet",
1180
+ status: "skipped",
1181
+ detail: "no EVM wallet address generated",
1182
+ };
1183
+ }
1184
+ const chainId = parsePositiveInt(args.options.orderlyChainId, 421614);
1185
+ const endpoint = "https://testnet-operator-evm.orderly.org/v1/faucet/usdc";
1186
+ const response = await fetch(endpoint, {
1187
+ method: "POST",
1188
+ headers: {
1189
+ "content-type": "application/json",
1190
+ },
1191
+ body: JSON.stringify({
1192
+ chain_id: chainId,
1193
+ broker_id: brokerId,
1194
+ user_address: args.walletAddress,
1195
+ }),
1196
+ });
1197
+ if (!response.ok) {
1198
+ const body = (await response.text()).trim();
1199
+ throw new Error(`orderly faucet HTTP ${response.status}${body ? `: ${body}` : ""}`);
1200
+ }
1201
+ return {
1202
+ exchange: "orderly",
1203
+ service: "orderly faucet",
1204
+ status: "ok",
1205
+ detail: `requested USDC for ${args.walletAddress} on chain ${chainId}`,
1206
+ };
1207
+ }
1208
+ async function requestAptosFaucet(args) {
1209
+ const jwt = normalizeText(args.options.aptosFaucetJwt);
1210
+ if (!jwt) {
1211
+ return {
1212
+ exchange: "decibel",
1213
+ service: "aptos faucet",
1214
+ status: "skipped",
1215
+ detail: "missing APTOS_TESTNET_FAUCET_JWT",
1216
+ };
1217
+ }
1218
+ if (!args.aptosAddress) {
1219
+ return {
1220
+ exchange: "decibel",
1221
+ service: "aptos faucet",
1222
+ status: "skipped",
1223
+ detail: "no Aptos wallet address generated",
1224
+ };
1225
+ }
1226
+ const amount = parsePositiveInt(args.options.aptosFaucetAmount, 100000000);
1227
+ const endpoint = new URL("https://faucet.testnet.aptoslabs.com/mint");
1228
+ endpoint.searchParams.set("address", args.aptosAddress);
1229
+ endpoint.searchParams.set("amount", String(amount));
1230
+ const response = await fetch(endpoint, {
1231
+ method: "POST",
1232
+ headers: {
1233
+ authorization: `Bearer ${jwt}`,
1234
+ "x-is-jwt": "true",
1235
+ },
1236
+ });
1237
+ if (!response.ok) {
1238
+ const body = (await response.text()).trim();
1239
+ throw new Error(`aptos faucet HTTP ${response.status}${body ? `: ${body}` : ""}`);
1240
+ }
1241
+ return {
1242
+ exchange: "decibel",
1243
+ service: "aptos faucet",
1244
+ status: "ok",
1245
+ detail: `requested ${amount} Octas for ${args.aptosAddress}`,
1246
+ };
1247
+ }
1248
+ export async function fundOnboardingWallets(profile, options = {}) {
1249
+ const results = [];
1250
+ const tasks = profile.exchanges
1251
+ .map((exchange) => ONBOARDING_PROVIDERS[exchange])
1252
+ .filter((provider) => typeof provider.fundingTask === "function")
1253
+ .map((provider) => provider.fundingTask({ profile, options }));
1254
+ const settled = await Promise.allSettled(tasks);
1255
+ for (const item of settled) {
1256
+ if (item.status === "fulfilled") {
1257
+ results.push(item.value);
1258
+ recordTelemetryMetric({
1259
+ metric: `funding.${item.value.exchange}`,
1260
+ status: item.value.status === "ok"
1261
+ ? "success"
1262
+ : item.value.status === "error"
1263
+ ? "failed"
1264
+ : item.value.status,
1265
+ });
1266
+ continue;
1267
+ }
1268
+ const message = item.reason instanceof Error ? item.reason.message : String(item.reason);
1269
+ if (message.startsWith("orderly faucet")) {
1270
+ results.push({
1271
+ exchange: "orderly",
1272
+ service: "orderly faucet",
1273
+ status: "error",
1274
+ detail: message,
1275
+ });
1276
+ recordTelemetryMetric({ metric: "funding.orderly", status: "failed" });
1277
+ continue;
1278
+ }
1279
+ if (message.startsWith("aptos faucet")) {
1280
+ results.push({
1281
+ exchange: "decibel",
1282
+ service: "aptos faucet",
1283
+ status: "error",
1284
+ detail: message,
1285
+ });
1286
+ recordTelemetryMetric({ metric: "funding.decibel", status: "failed" });
1287
+ continue;
1288
+ }
1289
+ results.push({
1290
+ exchange: "orderly",
1291
+ service: "faucet",
1292
+ status: "error",
1293
+ detail: message,
1294
+ });
1295
+ recordTelemetryMetric({ metric: "funding.orderly", status: "failed" });
1296
+ }
1297
+ for (const exchange of profile.exchanges) {
1298
+ const provider = ONBOARDING_PROVIDERS[exchange];
1299
+ if (!provider.manualFundingHint) {
1300
+ continue;
1301
+ }
1302
+ results.push({
1303
+ exchange,
1304
+ service: `${exchange} faucet`,
1305
+ status: "manual",
1306
+ detail: provider.manualFundingHint,
1307
+ });
1308
+ recordTelemetryMetric({ metric: `funding.${exchange}`, status: "manual" });
1309
+ }
1310
+ return results;
1311
+ }
1312
+ function makeOnboardingStatePath(idempotencyKey) {
1313
+ return resolve(ONBOARDING_STATE_DIR, `${idempotencyKey}.json`);
1314
+ }
1315
+ function computeIdempotencyKey(mode, exchanges) {
1316
+ const payload = `${mode}:${[...exchanges].sort().join(",")}`;
1317
+ const digest = Buffer.from(payload, "utf-8").toString("base64url").slice(0, 24);
1318
+ return `onboarding-${digest}`;
1319
+ }
1320
+ function createRunState(mode, exchanges) {
1321
+ const idempotencyKey = computeIdempotencyKey(mode, exchanges);
1322
+ const now = new Date().toISOString();
1323
+ return {
1324
+ id: `run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
1325
+ idempotencyKey,
1326
+ mode,
1327
+ exchanges: [...exchanges],
1328
+ updatedAt: now,
1329
+ statusByStep: {
1330
+ plan: "pending",
1331
+ generate: "pending",
1332
+ fund: "pending",
1333
+ verify: "pending",
1334
+ report: "pending",
1335
+ },
1336
+ notes: [],
1337
+ };
1338
+ }
1339
+ function readRunState(path) {
1340
+ if (!existsSync(path)) {
1341
+ return null;
1342
+ }
1343
+ try {
1344
+ const raw = readFileSync(path, "utf-8");
1345
+ const parsed = JSON.parse(raw);
1346
+ return parsed;
1347
+ }
1348
+ catch {
1349
+ return null;
1350
+ }
1351
+ }
1352
+ function writeRunState(path, state) {
1353
+ ensurePrivateDir(ONBOARDING_STATE_DIR);
1354
+ state.updatedAt = new Date().toISOString();
1355
+ writeFileSync(path, `${JSON.stringify(state, null, 2)}\n`, { mode: PRIVATE_FILE_MODE });
1356
+ hardenPrivateFile(path);
1357
+ }
1358
+ function markStep(state, step, status, note) {
1359
+ state.statusByStep[step] = status;
1360
+ if (note) {
1361
+ state.notes.push(note);
1362
+ }
1363
+ state.updatedAt = new Date().toISOString();
1364
+ return state;
1365
+ }
1366
+ function verifyOnboardingProfile(profile) {
1367
+ const notes = [];
1368
+ if (profile.exchanges.some((exchange) => EXCHANGE_CHAINS[exchange] === "evm") && !profile.wallets.evmAddress) {
1369
+ notes.push("Missing generated EVM wallet for selected EVM exchanges");
1370
+ }
1371
+ if (profile.exchanges.includes("decibel") && !profile.wallets.aptosAddress) {
1372
+ notes.push("Missing generated Aptos wallet for Decibel");
1373
+ }
1374
+ if (profile.exchanges.includes("paradex") && !profile.wallets.starkPublicKey) {
1375
+ notes.push("Missing generated Stark public key for Paradex");
1376
+ }
1377
+ return notes;
1378
+ }
1379
+ export async function runOnboardingStateMachine(args) {
1380
+ const idempotencyKey = computeIdempotencyKey(args.mode, args.exchanges);
1381
+ const statePath = args.statePath ? resolve(args.statePath) : makeOnboardingStatePath(idempotencyKey);
1382
+ let state = args.resume ? readRunState(statePath) : null;
1383
+ if (!state) {
1384
+ state = createRunState(args.mode, args.exchanges);
1385
+ state.idempotencyKey = idempotencyKey;
1386
+ writeRunState(statePath, state);
1387
+ }
1388
+ markStep(state, "plan", "running");
1389
+ writeRunState(statePath, state);
1390
+ markStep(state, "plan", "done", `mode=${args.mode}; exchanges=${args.exchanges.join(",")}`);
1391
+ writeRunState(statePath, state);
1392
+ if (!state.profile) {
1393
+ markStep(state, "generate", "running");
1394
+ writeRunState(statePath, state);
1395
+ state.profile = createOnboardingProfile({
1396
+ mode: args.mode,
1397
+ exchanges: args.exchanges,
1398
+ });
1399
+ }
1400
+ if (args.shouldBootstrapAuth ?? true) {
1401
+ state.authResults = await bootstrapOnboardingAuth(state.profile, args.authOptions ?? {});
1402
+ const autoReady = state.authResults.filter((result) => result.status === "ok").length;
1403
+ state.notes.push(`auth bootstrap completed (${autoReady}/${state.authResults.length} ready)`);
1404
+ }
1405
+ else {
1406
+ state.authResults = [];
1407
+ state.notes.push("auth bootstrap skipped");
1408
+ }
1409
+ markStep(state, "generate", "done");
1410
+ writeRunState(statePath, state);
1411
+ if (args.shouldFund) {
1412
+ const retries = Math.max(0, args.fundingRetries ?? 1);
1413
+ markStep(state, "fund", "running");
1414
+ writeRunState(statePath, state);
1415
+ let attempt = 0;
1416
+ let fundingResults = [];
1417
+ while (attempt <= retries) {
1418
+ attempt += 1;
1419
+ fundingResults = await fundOnboardingWallets(state.profile, args.fundingOptions ?? {});
1420
+ const failed = fundingResults.filter((result) => result.status === "error");
1421
+ if (failed.length === 0) {
1422
+ break;
1423
+ }
1424
+ if (attempt <= retries) {
1425
+ state.notes.push(`fund attempt ${attempt} had ${failed.length} errors; retrying`);
1426
+ }
1427
+ }
1428
+ state.fundingResults = fundingResults;
1429
+ markStep(state, "fund", "done");
1430
+ writeRunState(statePath, state);
1431
+ }
1432
+ else {
1433
+ state.fundingResults = [];
1434
+ markStep(state, "fund", "done", "funding skipped");
1435
+ writeRunState(statePath, state);
1436
+ }
1437
+ markStep(state, "verify", "running");
1438
+ writeRunState(statePath, state);
1439
+ const verificationNotes = verifyOnboardingProfile(state.profile);
1440
+ if (verificationNotes.length > 0) {
1441
+ for (const note of verificationNotes) {
1442
+ state.notes.push(note);
1443
+ }
1444
+ markStep(state, "verify", "error");
1445
+ }
1446
+ else {
1447
+ markStep(state, "verify", "done");
1448
+ }
1449
+ writeRunState(statePath, state);
1450
+ markStep(state, "report", "done");
1451
+ writeRunState(statePath, state);
1452
+ return {
1453
+ profile: state.profile,
1454
+ authResults: state.authResults ?? [],
1455
+ fundingResults: state.fundingResults ?? [],
1456
+ statePath,
1457
+ state,
1458
+ };
1459
+ }