perp-cli 0.12.15 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (361) hide show
  1. package/README.md +278 -31
  2. package/dist/_stderr-filter.js +0 -1
  3. package/dist/agent-wallet/lighter-keystore.d.ts +11 -0
  4. package/dist/agent-wallet/lighter-keystore.js +92 -10
  5. package/dist/agent-wallet/passphrase.d.ts +1 -1
  6. package/dist/agent-wallet/passphrase.js +16 -3
  7. package/dist/agent-wallet/store.d.ts +37 -4
  8. package/dist/agent-wallet/store.js +132 -0
  9. package/dist/agent-wallet/venue-keystore.d.ts +14 -0
  10. package/dist/agent-wallet/venue-keystore.js +121 -0
  11. package/dist/api/public/hyperliquid.js +4 -4
  12. package/dist/api/public/lighter.js +5 -5
  13. package/dist/api/public/pacifica.d.ts +0 -1
  14. package/dist/api/public/pacifica.js +4 -13
  15. package/dist/api/public/urls.d.ts +9 -1
  16. package/dist/api/public/urls.js +35 -13
  17. package/dist/arb/file-lock.d.ts +11 -0
  18. package/dist/arb/file-lock.js +74 -0
  19. package/dist/arb/index.d.ts +2 -2
  20. package/dist/arb/index.js +2 -2
  21. package/dist/arb/pending-entry.d.ts +32 -0
  22. package/dist/arb/pending-entry.js +77 -0
  23. package/dist/arb/sizing.d.ts +6 -13
  24. package/dist/arb/sizing.js +12 -51
  25. package/dist/arb/state.d.ts +45 -12
  26. package/dist/arb/state.js +214 -42
  27. package/dist/arb/utils.d.ts +20 -6
  28. package/dist/arb/utils.js +43 -39
  29. package/dist/bot/conditions.d.ts +4 -4
  30. package/dist/bot/conditions.js +11 -6
  31. package/dist/bot/config.d.ts +17 -1
  32. package/dist/bot/config.js +48 -12
  33. package/dist/bot/engine.d.ts +61 -2
  34. package/dist/bot/engine.js +610 -144
  35. package/dist/bot/fill-size.d.ts +7 -0
  36. package/dist/bot/fill-size.js +18 -0
  37. package/dist/bot/index.d.ts +1 -1
  38. package/dist/bot/index.js +1 -1
  39. package/dist/bot/perp-order.d.ts +99 -0
  40. package/dist/bot/perp-order.js +299 -0
  41. package/dist/bot/presets.d.ts +7 -0
  42. package/dist/bot/presets.js +17 -0
  43. package/dist/bot/quarantine.d.ts +75 -0
  44. package/dist/bot/quarantine.js +190 -0
  45. package/dist/bot/quote-rules.d.ts +23 -0
  46. package/dist/bot/quote-rules.js +69 -0
  47. package/dist/bot/strategies/avellaneda-mm.d.ts +32 -2
  48. package/dist/bot/strategies/avellaneda-mm.js +72 -70
  49. package/dist/bot/strategies/basis-arb.js +2 -0
  50. package/dist/bot/strategies/dca-strategy.d.ts +2 -0
  51. package/dist/bot/strategies/dca-strategy.js +5 -0
  52. package/dist/bot/strategies/funding-arb-strategy.d.ts +43 -3
  53. package/dist/bot/strategies/funding-arb-strategy.js +571 -380
  54. package/dist/bot/strategies/funding-arb-utils.d.ts +48 -23
  55. package/dist/bot/strategies/funding-arb-utils.js +159 -195
  56. package/dist/bot/strategies/funding-arb-v2.d.ts +32 -0
  57. package/dist/bot/strategies/funding-arb-v2.js +258 -47
  58. package/dist/bot/strategies/funding-auto.js +215 -194
  59. package/dist/bot/strategies/spot-perp-arb-strategy.d.ts +14 -0
  60. package/dist/bot/strategies/spot-perp-arb-strategy.js +217 -50
  61. package/dist/bot/strategies/spot-perp-executor.d.ts +73 -19
  62. package/dist/bot/strategies/spot-perp-executor.js +319 -120
  63. package/dist/bot/strategies/spot-perp-state.d.ts +43 -0
  64. package/dist/bot/strategies/spot-perp-state.js +70 -0
  65. package/dist/bot/strategy-types.d.ts +6 -0
  66. package/dist/bot/tui/BotDashboard.d.ts +7 -2
  67. package/dist/bot/tui/BotDashboard.js +6 -3
  68. package/dist/bridge-engine.d.ts +120 -19
  69. package/dist/bridge-engine.js +710 -422
  70. package/dist/bridge-signers.d.ts +54 -0
  71. package/dist/bridge-signers.js +161 -0
  72. package/dist/cache.d.ts +4 -1
  73. package/dist/cache.js +11 -0
  74. package/dist/cli-flags.d.ts +50 -0
  75. package/dist/cli-flags.js +104 -0
  76. package/dist/cli-program.d.ts +35 -0
  77. package/dist/cli-program.js +286 -0
  78. package/dist/cli-spec.js +1 -1
  79. package/dist/client-id-tracker.js +11 -2
  80. package/dist/commands/account.d.ts +7 -1
  81. package/dist/commands/account.js +74 -84
  82. package/dist/commands/agent-import.d.ts +114 -0
  83. package/dist/commands/agent-import.js +794 -0
  84. package/dist/commands/agent.d.ts +232 -2
  85. package/dist/commands/agent.js +1858 -955
  86. package/dist/commands/alerts.d.ts +1 -0
  87. package/dist/commands/alerts.js +31 -28
  88. package/dist/commands/arb/index.d.ts +60 -1
  89. package/dist/commands/arb/index.js +381 -384
  90. package/dist/commands/arb-auto.d.ts +132 -4
  91. package/dist/commands/arb-auto.js +1474 -967
  92. package/dist/commands/arb.d.ts +3 -1
  93. package/dist/commands/arb.js +62 -30
  94. package/dist/commands/bot.js +127 -47
  95. package/dist/commands/bridge.d.ts +2 -1
  96. package/dist/commands/bridge.js +339 -192
  97. package/dist/commands/builder.d.ts +55 -0
  98. package/dist/commands/builder.js +387 -0
  99. package/dist/commands/funds-onchain.d.ts +32 -0
  100. package/dist/commands/funds-onchain.js +286 -0
  101. package/dist/commands/funds.d.ts +25 -0
  102. package/dist/commands/funds.js +939 -245
  103. package/dist/commands/history.js +72 -45
  104. package/dist/commands/init.d.ts +107 -15
  105. package/dist/commands/init.js +1116 -249
  106. package/dist/commands/jobs.d.ts +2 -1
  107. package/dist/commands/jobs.js +77 -15
  108. package/dist/commands/manage.js +136 -91
  109. package/dist/commands/market.d.ts +4 -1
  110. package/dist/commands/market.js +35 -32
  111. package/dist/commands/outcome.d.ts +10 -0
  112. package/dist/commands/outcome.js +396 -0
  113. package/dist/commands/plan.js +52 -12
  114. package/dist/commands/portfolio.d.ts +1 -0
  115. package/dist/commands/portfolio.js +60 -72
  116. package/dist/commands/rebalance.d.ts +2 -1
  117. package/dist/commands/rebalance.js +33 -43
  118. package/dist/commands/risk.js +46 -19
  119. package/dist/commands/settings.d.ts +1 -0
  120. package/dist/commands/settings.js +246 -51
  121. package/dist/commands/trade.d.ts +50 -0
  122. package/dist/commands/trade.js +1943 -695
  123. package/dist/commands/wallet.d.ts +30 -1
  124. package/dist/commands/wallet.js +316 -337
  125. package/dist/config.d.ts +60 -9
  126. package/dist/config.js +252 -82
  127. package/dist/constants.d.ts +29 -4
  128. package/dist/constants.js +62 -20
  129. package/dist/dashboard/server.d.ts +2 -0
  130. package/dist/dashboard/server.js +9 -6
  131. package/dist/dashboard/ui.js +6 -4
  132. package/dist/dashboard/ws-feeds.js +20 -8
  133. package/dist/dex-asset-map.js +5 -3
  134. package/dist/errors.d.ts +122 -0
  135. package/dist/errors.js +194 -0
  136. package/dist/event-stream.js +75 -19
  137. package/dist/evm-master-tx.d.ts +59 -0
  138. package/dist/evm-master-tx.js +154 -0
  139. package/dist/exchanges/adapter-network.d.ts +10 -0
  140. package/dist/exchanges/adapter-network.js +10 -0
  141. package/dist/exchanges/arcus/adapter.d.ts +196 -0
  142. package/dist/exchanges/arcus/adapter.js +1092 -0
  143. package/dist/exchanges/arcus/agent.d.ts +55 -0
  144. package/dist/exchanges/arcus/agent.js +188 -0
  145. package/dist/exchanges/arcus/client.d.ts +63 -0
  146. package/dist/exchanges/arcus/client.js +285 -0
  147. package/dist/exchanges/arcus/public.d.ts +65 -0
  148. package/dist/exchanges/arcus/public.js +138 -0
  149. package/dist/exchanges/arcus/signing.d.ts +176 -0
  150. package/dist/exchanges/arcus/signing.js +288 -0
  151. package/dist/exchanges/arcus/spot-chain.d.ts +60 -0
  152. package/dist/exchanges/arcus/spot-chain.js +241 -0
  153. package/dist/exchanges/arcus/spot-router.d.ts +177 -0
  154. package/dist/exchanges/arcus/spot-router.js +183 -0
  155. package/dist/exchanges/arcus/spot.d.ts +109 -0
  156. package/dist/exchanges/arcus/spot.js +645 -0
  157. package/dist/exchanges/aster-funds.d.ts +127 -0
  158. package/dist/exchanges/aster-funds.js +252 -0
  159. package/dist/exchanges/aster-nonce.d.ts +2 -0
  160. package/dist/exchanges/aster-nonce.js +107 -0
  161. package/dist/exchanges/aster-spot.d.ts +117 -0
  162. package/dist/exchanges/aster-spot.js +686 -0
  163. package/dist/exchanges/aster-typed-data.d.ts +133 -7
  164. package/dist/exchanges/aster-typed-data.js +121 -7
  165. package/dist/exchanges/aster.d.ts +219 -6
  166. package/dist/exchanges/aster.js +1023 -153
  167. package/dist/exchanges/builder-fee.d.ts +254 -0
  168. package/dist/exchanges/builder-fee.js +421 -0
  169. package/dist/exchanges/canonical-symbols.d.ts +32 -0
  170. package/dist/exchanges/canonical-symbols.js +96 -0
  171. package/dist/exchanges/capabilities.d.ts +7 -1
  172. package/dist/exchanges/capabilities.js +7 -0
  173. package/dist/exchanges/hl-signing.d.ts +165 -0
  174. package/dist/exchanges/hl-signing.js +158 -0
  175. package/dist/exchanges/hyperliquid-outcome.d.ts +211 -0
  176. package/dist/exchanges/hyperliquid-outcome.js +646 -0
  177. package/dist/exchanges/hyperliquid-spot.d.ts +44 -5
  178. package/dist/exchanges/hyperliquid-spot.js +308 -117
  179. package/dist/exchanges/hyperliquid.d.ts +462 -57
  180. package/dist/exchanges/hyperliquid.js +1575 -449
  181. package/dist/exchanges/index.d.ts +6 -0
  182. package/dist/exchanges/index.js +4 -0
  183. package/dist/exchanges/interface.d.ts +63 -10
  184. package/dist/exchanges/interface.js +18 -3
  185. package/dist/exchanges/ioc-outcome.d.ts +44 -0
  186. package/dist/exchanges/ioc-outcome.js +57 -0
  187. package/dist/exchanges/legacy-trading-v2.d.ts +39 -0
  188. package/dist/exchanges/legacy-trading-v2.js +940 -0
  189. package/dist/exchanges/lighter-public.d.ts +43 -0
  190. package/dist/exchanges/lighter-public.js +276 -0
  191. package/dist/exchanges/lighter-rh-deposit.d.ts +62 -0
  192. package/dist/exchanges/lighter-rh-deposit.js +185 -0
  193. package/dist/exchanges/lighter-signer.d.ts +123 -0
  194. package/dist/exchanges/lighter-signer.js +215 -0
  195. package/dist/exchanges/lighter-spot.d.ts +91 -13
  196. package/dist/exchanges/lighter-spot.js +595 -233
  197. package/dist/exchanges/lighter.d.ts +611 -39
  198. package/dist/exchanges/lighter.js +1890 -649
  199. package/dist/exchanges/ondo/adapter.d.ts +166 -0
  200. package/dist/exchanges/ondo/adapter.js +835 -0
  201. package/dist/exchanges/ondo/agent.d.ts +31 -0
  202. package/dist/exchanges/ondo/agent.js +101 -0
  203. package/dist/exchanges/ondo/client.d.ts +60 -0
  204. package/dist/exchanges/ondo/client.js +297 -0
  205. package/dist/exchanges/ondo/funds.d.ts +122 -0
  206. package/dist/exchanges/ondo/funds.js +290 -0
  207. package/dist/exchanges/ondo/public.d.ts +95 -0
  208. package/dist/exchanges/ondo/public.js +214 -0
  209. package/dist/exchanges/ondo/spot.d.ts +127 -0
  210. package/dist/exchanges/ondo/spot.js +392 -0
  211. package/dist/exchanges/outcome-interface.d.ts +140 -0
  212. package/dist/exchanges/outcome-interface.js +16 -0
  213. package/dist/exchanges/pacifica-spot.d.ts +83 -0
  214. package/dist/exchanges/pacifica-spot.js +324 -0
  215. package/dist/exchanges/pacifica-typed-data.d.ts +30 -13
  216. package/dist/exchanges/pacifica-typed-data.js +57 -9
  217. package/dist/exchanges/pacifica.d.ts +288 -4
  218. package/dist/exchanges/pacifica.js +1013 -76
  219. package/dist/exchanges/precision.d.ts +47 -0
  220. package/dist/exchanges/precision.js +209 -0
  221. package/dist/exchanges/qfex/adapter.d.ts +341 -0
  222. package/dist/exchanges/qfex/adapter.js +1383 -0
  223. package/dist/exchanges/qfex/agent.d.ts +58 -0
  224. package/dist/exchanges/qfex/agent.js +188 -0
  225. package/dist/exchanges/qfex/client.d.ts +54 -0
  226. package/dist/exchanges/qfex/client.js +138 -0
  227. package/dist/exchanges/qfex/errors.d.ts +70 -0
  228. package/dist/exchanges/qfex/errors.js +236 -0
  229. package/dist/exchanges/qfex/public.d.ts +122 -0
  230. package/dist/exchanges/qfex/public.js +268 -0
  231. package/dist/exchanges/qfex/signing.d.ts +160 -0
  232. package/dist/exchanges/qfex/signing.js +283 -0
  233. package/dist/exchanges/qfex/ws-transport.d.ts +294 -0
  234. package/dist/exchanges/qfex/ws-transport.js +798 -0
  235. package/dist/exchanges/rate-limiter.d.ts +12 -0
  236. package/dist/exchanges/rate-limiter.js +64 -0
  237. package/dist/exchanges/registry.d.ts +14 -2
  238. package/dist/exchanges/registry.js +20 -32
  239. package/dist/exchanges/slippage.d.ts +37 -0
  240. package/dist/exchanges/slippage.js +70 -0
  241. package/dist/exchanges/spot-factory.d.ts +166 -0
  242. package/dist/exchanges/spot-factory.js +431 -0
  243. package/dist/exchanges/spot-interface.d.ts +98 -4
  244. package/dist/exchanges/spot-interface.js +27 -1
  245. package/dist/exchanges/spot-result.d.ts +79 -0
  246. package/dist/exchanges/spot-result.js +273 -0
  247. package/dist/exchanges/trading-v2.d.ts +117 -0
  248. package/dist/exchanges/trading-v2.js +301 -0
  249. package/dist/exchanges/venue-types.d.ts +231 -0
  250. package/dist/exchanges/venue-types.js +87 -0
  251. package/dist/exchanges/venue.d.ts +211 -0
  252. package/dist/exchanges/venue.js +132 -0
  253. package/dist/exchanges/venues/arcus.d.ts +8 -0
  254. package/dist/exchanges/venues/arcus.js +109 -0
  255. package/dist/exchanges/venues/aster.d.ts +49 -0
  256. package/dist/exchanges/venues/aster.js +176 -0
  257. package/dist/exchanges/venues/create-helpers.d.ts +18 -0
  258. package/dist/exchanges/venues/create-helpers.js +199 -0
  259. package/dist/exchanges/venues/hyperliquid.d.ts +23 -0
  260. package/dist/exchanges/venues/hyperliquid.js +126 -0
  261. package/dist/exchanges/venues/index.d.ts +16 -0
  262. package/dist/exchanges/venues/index.js +26 -0
  263. package/dist/exchanges/venues/lighter-family.d.ts +92 -0
  264. package/dist/exchanges/venues/lighter-family.js +166 -0
  265. package/dist/exchanges/venues/lighter-rh.d.ts +7 -0
  266. package/dist/exchanges/venues/lighter-rh.js +58 -0
  267. package/dist/exchanges/venues/lighter.d.ts +6 -0
  268. package/dist/exchanges/venues/lighter.js +55 -0
  269. package/dist/exchanges/venues/ondo.d.ts +34 -0
  270. package/dist/exchanges/venues/ondo.js +140 -0
  271. package/dist/exchanges/venues/pacifica.d.ts +8 -0
  272. package/dist/exchanges/venues/pacifica.js +95 -0
  273. package/dist/exchanges/venues/qfex.d.ts +28 -0
  274. package/dist/exchanges/venues/qfex.js +132 -0
  275. package/dist/execution-log.js +10 -2
  276. package/dist/funding/legacy-public.d.ts +31 -0
  277. package/dist/funding/legacy-public.js +369 -0
  278. package/dist/funding/normalize.d.ts +28 -16
  279. package/dist/funding/normalize.js +61 -29
  280. package/dist/funding/rates.d.ts +46 -11
  281. package/dist/funding/rates.js +228 -277
  282. package/dist/guardrail/perp-guardrail.js +0 -0
  283. package/dist/index.js +157 -682
  284. package/dist/jobs.d.ts +84 -6
  285. package/dist/jobs.js +239 -41
  286. package/dist/landing.d.ts +14 -0
  287. package/dist/landing.js +36 -0
  288. package/dist/mcp-server.d.ts +14 -4
  289. package/dist/mcp-server.js +820 -402
  290. package/dist/mcp-trade-guard.d.ts +16 -0
  291. package/dist/mcp-trade-guard.js +59 -0
  292. package/dist/pacifica/client.d.ts +41 -10
  293. package/dist/pacifica/client.js +167 -63
  294. package/dist/pacifica/constants.d.ts +0 -1
  295. package/dist/pacifica/constants.js +0 -5
  296. package/dist/pacifica/deposit.d.ts +30 -3
  297. package/dist/pacifica/deposit.js +119 -2
  298. package/dist/pacifica/faucet.d.ts +29 -0
  299. package/dist/pacifica/faucet.js +100 -0
  300. package/dist/pacifica/sign-tx.d.ts +15 -0
  301. package/dist/pacifica/sign-tx.js +41 -0
  302. package/dist/pacifica/types/account.d.ts +28 -4
  303. package/dist/pacifica/types/lake.d.ts +20 -5
  304. package/dist/pacifica/types/lake.js +4 -0
  305. package/dist/pacifica/types/market.d.ts +5 -0
  306. package/dist/pacifica/types/order.d.ts +4 -1
  307. package/dist/pacifica/types/position.d.ts +9 -1
  308. package/dist/plan-executor.d.ts +10 -5
  309. package/dist/plan-executor.js +165 -54
  310. package/dist/position-history.js +13 -0
  311. package/dist/rebalance.d.ts +26 -2
  312. package/dist/rebalance.js +86 -7
  313. package/dist/relay-swap.d.ts +232 -0
  314. package/dist/relay-swap.js +630 -0
  315. package/dist/risk.js +5 -1
  316. package/dist/secret-env.d.ts +15 -0
  317. package/dist/secret-env.js +36 -0
  318. package/dist/settings.d.ts +169 -6
  319. package/dist/settings.js +219 -38
  320. package/dist/signer/ows-evm.d.ts +13 -0
  321. package/dist/signer/ows-evm.js +22 -0
  322. package/dist/smart-order.d.ts +9 -0
  323. package/dist/smart-order.js +53 -8
  324. package/dist/strategies/dca.d.ts +6 -2
  325. package/dist/strategies/dca.js +66 -26
  326. package/dist/strategies/funding-arb.d.ts +11 -1
  327. package/dist/strategies/funding-arb.js +202 -89
  328. package/dist/strategies/grid.d.ts +31 -0
  329. package/dist/strategies/grid.js +89 -11
  330. package/dist/strategies/pending-stop.d.ts +8 -0
  331. package/dist/strategies/pending-stop.js +12 -0
  332. package/dist/strategies/split-order.d.ts +45 -17
  333. package/dist/strategies/split-order.js +157 -91
  334. package/dist/strategies/trailing-stop.d.ts +2 -1
  335. package/dist/strategies/trailing-stop.js +52 -6
  336. package/dist/strategies/twap.d.ts +6 -1
  337. package/dist/strategies/twap.js +39 -11
  338. package/dist/trade-validator.d.ts +22 -0
  339. package/dist/trade-validator.js +119 -15
  340. package/dist/utils/numeric.d.ts +46 -0
  341. package/dist/utils/numeric.js +79 -0
  342. package/dist/utils.d.ts +19 -0
  343. package/dist/utils.js +96 -19
  344. package/dist/withdraw-gate.d.ts +49 -0
  345. package/dist/withdraw-gate.js +131 -0
  346. package/package.json +15 -6
  347. package/skills/perp-cli/SKILL.md +338 -134
  348. package/skills/perp-cli/references/agent-operations.md +181 -92
  349. package/skills/perp-cli/references/commands.md +475 -132
  350. package/skills/perp-cli/references/strategies.md +44 -38
  351. package/skills/perp-cli/scripts/arb-monitor.sh +22 -17
  352. package/skills/perp-cli/scripts/funding-analysis.sh +20 -10
  353. package/skills/perp-cli/scripts/preflight.sh +21 -14
  354. package/skills/perp-cli/scripts/spot-perp-scan.sh +12 -7
  355. package/skills/perp-cli/scripts/validate-arb.sh +33 -16
  356. package/vendor/lighter-signer/SHA256SUMS +2 -0
  357. package/vendor/lighter-signer/VERSION +8 -0
  358. package/vendor/lighter-signer/lighter-signer.wasm +0 -0
  359. package/vendor/lighter-signer/wasm_exec.js +575 -0
  360. package/dist/bot/strategies/funding-arb-v2-strategy.d.ts +0 -42
  361. package/dist/bot/strategies/funding-arb-v2-strategy.js +0 -864
@@ -1,7 +1,76 @@
1
+ import { unsupported } from "../exchanges/trading-v2.js";
1
2
  import chalk from "chalk";
2
3
  import { printJson, jsonOk, withJsonErrors } from "../utils.js";
3
- import { loadSettings, saveSettings } from "../settings.js";
4
+ import { loadSettings, saveSettings, SHIPPED_DEFAULT_SLIPPAGE_BPS } from "../settings.js";
4
5
  import { ENV_FILE, loadEnvFile, setEnvVar, EXCHANGE_ENV_MAP, validateKey } from "./init.js";
6
+ import { getVenue, resolveVenueId } from "../exchanges/venue.js";
7
+ import "../exchanges/venues/index.js";
8
+ import { maxDefaultSlippageBps, parseSlippageSettingBps } from "../exchanges/slippage.js";
9
+ import { PerpError } from "../errors.js";
10
+ import { isSecretEnvName } from "../secret-env.js";
11
+ import { BUILDER_FEE_CAPABILITIES, parseBuilderEntry, requireBuilderCapability } from "../exchanges/builder-fee.js";
12
+ import { validateDebridgeAffiliate, validateWithdrawGate, WITHDRAW_GATE_VENUES } from "../settings.js";
13
+ import { confirmWithdrawGateChange } from "../withdraw-gate.js";
14
+ const invalidParam = (problem) => new PerpError("INVALID_PARAMS", problem, {
15
+ remediation: "perp settings set builder.<venue> <address|code>,<feeBps> (e.g. builder.hyperliquid 0xabc…,1)",
16
+ });
17
+ /** `builder.<venue>` value "<address|code>,<feeBps>" → validated settings entry. */
18
+ function builderEntryOf(key, value) {
19
+ const venue = getVenue(key.slice("builder.".length)).id;
20
+ const cap = requireBuilderCapability(venue);
21
+ if (cap.feeKind === "share") {
22
+ // Fee share (QFEX): the code only — there is no user-paid fee to configure.
23
+ const entry = { code: value.trim() };
24
+ parseBuilderEntry(venue, entry, problem => new PerpError("INVALID_PARAMS", problem, {
25
+ remediation: `perp settings set builder.${venue} <uuid> (the builder code only — ${venue} builder codes share the venue fee; no feeBps)`,
26
+ }));
27
+ return { venue, entry };
28
+ }
29
+ const parts = value.split(",");
30
+ if (parts.length !== 2)
31
+ throw invalidParam(`${key} value must be "<${cap.identity}>,<feeBps>" (got "${value}")`);
32
+ const fee = Number(parts[1].trim());
33
+ const entry = { [cap.identity]: parts[0].trim(), feeBps: parts[1].trim() === "" ? NaN : fee };
34
+ parseBuilderEntry(venue, entry, invalidParam); // strict: identity format, fee > 0, ≤ venue cap, venue increment
35
+ return { venue, entry };
36
+ }
37
+ /**
38
+ * L5: a builder.<venue> value charges the USER on every perp order, so setting one needs an
39
+ * explicit y/N (or --yes) that shows the fee. Non-interactive / --json without --yes is refused.
40
+ */
41
+ async function confirmBuilderSetting(venue, entry, yes, json) {
42
+ if (yes)
43
+ return;
44
+ const cap = requireBuilderCapability(getVenue(venue).id);
45
+ const fee = `${cap.feeKind === "max" ? "up to " : ""}${String(entry.feeBps)} bps`;
46
+ const share = cap.feeKind === "share";
47
+ const what = share
48
+ ? `builder.${venue} = ${String(entry.code)}: your ${venue} fills are attributed to ${String(entry.code)}; you pay the normal fee (the venue shares part of it with the builder — no extra charge; attached to the trading connection from the next order)`
49
+ : `builder.${venue} = ${String(entry[cap.identity])} charges ${fee} on YOUR ${venue} perp orders (paid by you, to the builder${cap.approval === "none" ? "; no venue approval step — attached from the next order" : ""})`;
50
+ if (json || process.stdin.isTTY !== true) {
51
+ throw new PerpError("INVALID_PARAMS", `Setting a builder ${share ? "code" : "fee"} needs explicit confirmation: ${what}`, {
52
+ remediation: `Re-run with --yes to confirm: perp settings set builder.${venue} ${share ? "<uuid>" : `<${cap.identity}>,<feeBps>`} --yes`,
53
+ });
54
+ }
55
+ const { createInterface } = await import("node:readline/promises");
56
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
57
+ try {
58
+ const a = (await rl.question(` ${what}. Save? [y/N]: `)).trim().toLowerCase();
59
+ if (a !== "y" && a !== "yes")
60
+ throw new PerpError("INVALID_PARAMS", `builder.${venue} not set — cancelled, nothing was saved`);
61
+ }
62
+ finally {
63
+ rl.close();
64
+ }
65
+ }
66
+ /** Parse `slippageBps.<venue>` → venue id (aliases accepted). Throws UNKNOWN_VENUE. */
67
+ function slippageVenueOf(key) {
68
+ const venue = getVenue(key.slice("slippageBps.".length));
69
+ if (venue.capabilities === null) {
70
+ throw unsupported(venue.displayName, "trading", "no trading API, so slippageBps does not apply", "Set slippageBps only for trading venues");
71
+ }
72
+ return venue.id;
73
+ }
5
74
  // Resolve exchange alias → env var key
6
75
  function resolveEnvKey(nameOrKey) {
7
76
  // Direct env var name
@@ -30,15 +99,36 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
30
99
  .action(async () => {
31
100
  await withJsonErrors(isJson(), async () => {
32
101
  const s = loadSettings();
102
+ const defaultSlippageShipped = s.defaultSlippageBps === SHIPPED_DEFAULT_SLIPPAGE_BPS;
33
103
  if (isJson())
34
- return printJson(jsonOk(s));
104
+ return printJson(jsonOk({ ...s, defaultSlippageBpsSource: defaultSlippageShipped ? "shipped default" : "user" }));
35
105
  console.log(chalk.cyan.bold("\n CLI Settings\n"));
36
106
  console.log(` Default Exchange: ${s.defaultExchange ? chalk.cyan(s.defaultExchange) : chalk.gray("pacifica (built-in)")}`);
37
- console.log(` Referrals: ${chalk.green("MANDATORY (always on)")}`);
38
- console.log(chalk.white.bold("\n Referral Codes:"));
39
- console.log(` Pacifica: ${s.referralCodes.pacifica || chalk.gray("(none)")}`);
107
+ console.log(chalk.white.bold("\n Referral Codes (bound only by an explicit `wallet manage referral set`):"));
40
108
  console.log(` Hyperliquid: ${s.referralCodes.hyperliquid || chalk.gray("(none)")}`);
41
109
  console.log(` Lighter: ${s.referralCodes.lighter || chalk.gray("(none)")}`);
110
+ console.log(chalk.white.bold("\n Builder fees (off unless set; attached only after venue approval):"));
111
+ const builderEntries = Object.entries(s.builder);
112
+ if (builderEntries.length === 0)
113
+ console.log(chalk.gray(" (none — no builder fee is attached on any venue)"));
114
+ for (const [v, e] of builderEntries) {
115
+ const id = "address" in e ? e.address : e.code;
116
+ const fee = "feeBps" in e ? `${e.feeBps} bps` : "fee share (you pay the normal fee)";
117
+ console.log(` ${v.padEnd(14)} ${id} ${fee}${BUILDER_FEE_CAPABILITIES[v]?.feeKind === "max" ? " (max)" : ""}`);
118
+ }
119
+ console.log(chalk.white.bold("\n Withdraw gate (qfex: API-key withdrawals; hyperliquid: withdraw / transfer to a non-owner address; off unless set):"));
120
+ const gates = Object.entries(s.withdraw);
121
+ if (gates.length === 0)
122
+ console.log(chalk.gray(` (none — gated withdrawals on ${WITHDRAW_GATE_VENUES.join(", ")} are refused; Hyperliquid to the owner's own address is always allowed)`));
123
+ for (const [v, g] of gates)
124
+ console.log(` ${v.padEnd(14)} max ${g.maxPerTx}/tx, ${g.maxPerDay}/day → ${g.allowlist.join(", ")}`);
125
+ const aff = s.bridge.debridgeAffiliate;
126
+ console.log(` deBridge affiliate: ${aff === null ? chalk.gray("(none)") : `referral ${aff.referralCode ?? "-"} · fee ${aff.feePercent === null ? "none" : `${aff.feePercent}% → ${aff.feeRecipient}`}`}`);
127
+ console.log(chalk.white.bold("\n Slippage bound (bps) — market / close / trigger orders:"));
128
+ console.log(` ${"default".padEnd(14)} ${s.defaultSlippageBps}${defaultSlippageShipped ? chalk.gray(" (shipped default)") : ""}`);
129
+ for (const [v, bps] of Object.entries(s.slippageBps))
130
+ console.log(` ${v.padEnd(14)} ${bps}`);
131
+ console.log(chalk.gray(" precedence: --slippage-bps → slippageBps.<venue> → default"));
42
132
  console.log(chalk.white.bold("\n Fee Tiers:"));
43
133
  for (const [ex, fee] of Object.entries(s.fees)) {
44
134
  const tPct = (fee.taker * 100).toFixed(4);
@@ -48,10 +138,9 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
48
138
  console.log(chalk.gray(`\n Config: ~/.perp/settings.json\n`));
49
139
  });
50
140
  });
51
- // Note: `settings referrals on/off` removed in v0.12 — referral codes are
52
- // now mandatory (SSOT-aligned with Pacifica's always-on builder code).
53
- // To reset applied flags (force re-send on next connection):
54
- // perp settings set referralApplied.hyperliquid false (and same for lighter)
141
+ // HL / Lighter referral codes are applied only by the explicit
142
+ // `perp -e <hyperliquid|lighter> wallet manage referral set [code]` (never on
143
+ // adapter creation). referralCodes.<venue> is that command's default code.
55
144
  // ── settings fees ──
56
145
  // Bare `settings fees` shows current tiers (same as `settings fees show`).
57
146
  const showFees = async () => {
@@ -79,16 +168,18 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
79
168
  .action(showFees);
80
169
  fees
81
170
  .command("sync")
82
- .description("Fetch fee tiers from exchange APIs and save")
83
- .option("--exchange <name>", "Sync only a specific exchange")
84
- .action(async (opts) => {
171
+ .description("Fetch fee tiers from exchange APIs and save (-e <exchange> to sync only one)")
172
+ .action(async () => {
85
173
  await withJsonErrors(isJson(), async () => {
174
+ // -e/--exchange is the root option: read it there only when it was passed on the command
175
+ // line (never the root's default venue — no flag means "all").
176
+ const only = program.getOptionValueSource("exchange") === "cli" ? String(program.opts().exchange) : undefined;
86
177
  if (!getAdapterForExchange) {
87
178
  console.error(chalk.red(" Fee sync requires exchange adapters. Set up keys first with `perp setup`."));
88
179
  return;
89
180
  }
90
181
  const s = loadSettings();
91
- const exchanges = opts.exchange ? [opts.exchange.toLowerCase()] : ["hyperliquid", "pacifica", "lighter"];
182
+ const exchanges = only !== undefined ? [only.toLowerCase()] : ["hyperliquid", "pacifica", "lighter"];
92
183
  const results = {};
93
184
  for (const ex of exchanges) {
94
185
  try {
@@ -186,16 +277,55 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
186
277
  .description("Set a specific setting")
187
278
  .argument("<key>", "Setting key (e.g. referralCodes.hyperliquid)")
188
279
  .argument("<value>", "Setting value")
189
- .action(async (key, value) => {
280
+ .option("--yes", "Confirm a builder.<venue> fee without the y/N prompt")
281
+ .action(async (key, value, opts) => {
190
282
  await withJsonErrors(isJson(), async () => {
191
283
  const s = loadSettings();
192
284
  if (key === "default-exchange" || key === "defaultExchange") {
193
- const valid = ["pacifica", "hyperliquid", "lighter"];
194
- if (!valid.includes(value.toLowerCase())) {
195
- console.error(chalk.red(` Invalid exchange: ${value}. Use: ${valid.join(", ")}`));
196
- return;
285
+ s.defaultExchange = resolveVenueId(value);
286
+ }
287
+ else if (key === "defaultSlippageBps") {
288
+ s.defaultSlippageBps = parseSlippageSettingBps(key, value, maxDefaultSlippageBps());
289
+ }
290
+ else if (key.startsWith("slippageBps.")) {
291
+ const venue = slippageVenueOf(key);
292
+ s.slippageBps[venue] = parseSlippageSettingBps(key, value, getVenue(venue).maxSlippageBps);
293
+ }
294
+ else if (key.startsWith("builder.")) {
295
+ const { venue, entry } = builderEntryOf(key, value);
296
+ await confirmBuilderSetting(venue, entry, opts.yes === true, isJson());
297
+ s.builder[venue] = entry;
298
+ }
299
+ else if (key.startsWith("withdraw.")) {
300
+ // settings.withdraw.<venue> = "<address[+address…]>,<maxPerTx>,<maxPerDay>". Changing the
301
+ // allowlist / limits needs an interactive terminal confirmation — no --yes, never --json.
302
+ const venue = getVenue(key.slice("withdraw.".length)).id;
303
+ const parts = value.split(",");
304
+ if (parts.length !== 3) {
305
+ throw new PerpError("INVALID_PARAMS", `${key} value must be "<address[+address…]>,<maxPerTx>,<maxPerDay>" (got "${value}")`, {
306
+ remediation: `e.g. perp settings set withdraw.${venue} 0xYourWallet,100,500`,
307
+ });
197
308
  }
198
- s.defaultExchange = value.toLowerCase();
309
+ const num = (raw) => (raw.trim() === "" ? NaN : Number(raw.trim()));
310
+ const gate = validateWithdrawGate(venue, {
311
+ allowlist: parts[0].split("+").map(a => a.trim()).filter(a => a !== ""), maxPerTx: num(parts[1]), maxPerDay: num(parts[2]),
312
+ }, problem => new PerpError("INVALID_PARAMS", problem));
313
+ await confirmWithdrawGateChange(venue, gate, isJson());
314
+ s.withdraw[venue] = gate;
315
+ }
316
+ else if (key === "bridge.debridgeAffiliate.referralCode" || key === "bridge.debridgeAffiliate.fee") {
317
+ const cur = s.bridge.debridgeAffiliate ?? { referralCode: null, feePercent: null, feeRecipient: null };
318
+ let next;
319
+ if (key.endsWith(".referralCode")) {
320
+ next = { ...cur, referralCode: value };
321
+ }
322
+ else {
323
+ const parts = value.split(",");
324
+ if (parts.length !== 2)
325
+ throw invalidParam(`${key} value must be "<percent>,<recipient>" (got "${value}")`);
326
+ next = { ...cur, feePercent: parts[0].trim() === "" ? NaN : Number(parts[0].trim()), feeRecipient: parts[1].trim() };
327
+ }
328
+ s.bridge.debridgeAffiliate = validateDebridgeAffiliate(next, invalidParam);
199
329
  }
200
330
  else if (key.startsWith("referralCodes.")) {
201
331
  const exchange = key.split(".")[1];
@@ -208,7 +338,7 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
208
338
  }
209
339
  }
210
340
  else if (key.startsWith("referralApplied.")) {
211
- // Force re-apply: `set referralApplied.hyperliquid false`
341
+ // Bookkeeping only (set by `wallet manage referral set`).
212
342
  const exchange = key.split(".")[1];
213
343
  if (exchange in s.referralApplied) {
214
344
  s.referralApplied[exchange] = value === "true" || value === "on";
@@ -220,13 +350,88 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
220
350
  }
221
351
  else {
222
352
  console.error(chalk.red(` Unknown key: ${key}`));
223
- console.log(chalk.gray(" Valid keys: default-exchange, referralCodes.{pacifica,hyperliquid,lighter}, referralApplied.{hyperliquid,lighter}\n"));
353
+ console.log(chalk.gray(" Valid keys: default-exchange, referralCodes.{hyperliquid,lighter}, referralApplied.{hyperliquid,lighter}, defaultSlippageBps, slippageBps.<venue>, builder.<venue>, withdraw.<venue>, bridge.debridgeAffiliate.{referralCode,fee}\n"));
224
354
  return;
225
355
  }
226
356
  saveSettings(s);
227
357
  if (isJson())
228
358
  return printJson(jsonOk({ key, value }));
229
359
  console.log(chalk.green(`\n ${key} = ${value}\n`));
360
+ if (key.startsWith("builder.")) {
361
+ const venue = getVenue(key.slice("builder.".length)).id;
362
+ const cap = requireBuilderCapability(venue);
363
+ if (cap.feeKind === "share") {
364
+ console.log(chalk.yellow(` Your ${venue} fills are attributed to this builder code; you pay the normal fee (no extra charge).`));
365
+ console.log(chalk.gray(` Attribution cannot be confirmed from the trader side. Disable per command with --no-builder; remove with: perp settings unset builder.${venue}\n`));
366
+ return;
367
+ }
368
+ console.log(chalk.yellow(` This adds a builder fee to YOUR ${venue} perp orders (paid by you, to the builder).`));
369
+ console.log(chalk.gray(cap.approval === "master"
370
+ ? ` Nothing is attached until the venue approval is confirmed: perp wallet builder approve ${venue}\n Disable per command with --no-builder; remove with: perp settings unset builder.${venue}\n`
371
+ : ` ${venue} needs no approval — the fee is attached from the next order.\n Disable per command with --no-builder; remove with: perp settings unset builder.${venue}\n`));
372
+ }
373
+ });
374
+ });
375
+ // ── settings unset ──
376
+ settings
377
+ .command("unset")
378
+ .description("Remove a setting (slippageBps.<venue>, builder.<venue>, withdraw.<venue>, bridge.debridgeAffiliate; defaultSlippageBps restores the shipped default)")
379
+ .argument("<key>", "Setting key (e.g. slippageBps.ondo)")
380
+ .action(async (key) => {
381
+ await withJsonErrors(isJson(), async () => {
382
+ if (key === "defaultSlippageBps") {
383
+ const s = loadSettings();
384
+ s.defaultSlippageBps = SHIPPED_DEFAULT_SLIPPAGE_BPS;
385
+ saveSettings(s);
386
+ if (isJson())
387
+ return printJson(jsonOk({ key, value: SHIPPED_DEFAULT_SLIPPAGE_BPS, source: "shipped default" }));
388
+ console.log(chalk.green(`\n defaultSlippageBps = ${SHIPPED_DEFAULT_SLIPPAGE_BPS} (shipped default)\n`));
389
+ return;
390
+ }
391
+ if (key.startsWith("builder.")) {
392
+ const venue = getVenue(key.slice("builder.".length)).id;
393
+ const s = loadSettings();
394
+ const existed = s.builder[venue] !== undefined;
395
+ delete s.builder[venue];
396
+ saveSettings(s);
397
+ if (isJson())
398
+ return printJson(jsonOk({ key: `builder.${venue}`, removed: existed }));
399
+ console.log(existed ? chalk.green(`\n builder.${venue} removed — no builder fee is attached on ${venue}\n`) : chalk.gray(`\n builder.${venue} was not set\n`));
400
+ return;
401
+ }
402
+ if (key.startsWith("withdraw.")) {
403
+ // Removing the gate turns API-key withdrawals OFF for that venue — no confirmation needed.
404
+ const venue = getVenue(key.slice("withdraw.".length)).id;
405
+ const s = loadSettings();
406
+ const existed = s.withdraw[venue] !== undefined;
407
+ delete s.withdraw[venue];
408
+ saveSettings(s);
409
+ if (isJson())
410
+ return printJson(jsonOk({ key: `withdraw.${venue}`, removed: existed }));
411
+ console.log(existed ? chalk.green(`\n withdraw.${venue} removed — withdrawals on ${venue} are refused until it is set again\n`) : chalk.gray(`\n withdraw.${venue} was not set\n`));
412
+ return;
413
+ }
414
+ if (key === "bridge.debridgeAffiliate") {
415
+ const s = loadSettings();
416
+ const existed = s.bridge.debridgeAffiliate !== null;
417
+ s.bridge.debridgeAffiliate = null;
418
+ saveSettings(s);
419
+ if (isJson())
420
+ return printJson(jsonOk({ key, removed: existed }));
421
+ console.log(existed ? chalk.green(`\n ${key} removed\n`) : chalk.gray(`\n ${key} was not set\n`));
422
+ return;
423
+ }
424
+ if (!key.startsWith("slippageBps.")) {
425
+ throw new PerpError("INVALID_PARAMS", `Cannot unset "${key}"`, { remediation: "Unsettable keys: defaultSlippageBps, slippageBps.<venue>, builder.<venue>, withdraw.<venue>, bridge.debridgeAffiliate" });
426
+ }
427
+ const venue = slippageVenueOf(key);
428
+ const s = loadSettings();
429
+ const existed = s.slippageBps[venue] !== undefined;
430
+ delete s.slippageBps[venue];
431
+ saveSettings(s);
432
+ if (isJson())
433
+ return printJson(jsonOk({ key: `slippageBps.${venue}`, removed: existed }));
434
+ console.log(existed ? chalk.green(`\n slippageBps.${venue} removed\n`) : chalk.gray(`\n slippageBps.${venue} was not set\n`));
230
435
  });
231
436
  });
232
437
  // ── settings env ──
@@ -261,7 +466,7 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
261
466
  console.log(chalk.cyan.bold("\n perp-cli Configuration\n"));
262
467
  console.log(` File: ${chalk.gray(ENV_FILE)}\n`);
263
468
  if (results.length === 0) {
264
- console.log(chalk.gray(" No keys configured. Run 'perp setup' or 'perp settings env set <exchange> <key>'\n"));
469
+ console.log(chalk.gray(" No legacy keys in ~/.perp/.env. Import keys into the OWS vault: 'perp wallet set <exchange>'\n"));
265
470
  return;
266
471
  }
267
472
  for (const { name, address, source } of results) {
@@ -269,41 +474,29 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
269
474
  }
270
475
  console.log();
271
476
  });
272
- // ── settings env set <exchange|key> <value> ──
477
+ // ── settings env set <name> <value> ──
478
+ // Non-secret values only (e.g. LIGHTER_REFERRAL_CODE). SSOT Rule #3: key material is never
479
+ // written to ~/.perp/.env nor accepted on argv — exchange keys go to the OWS vault.
273
480
  env
274
481
  .command("set <name> <value>")
275
- .description("Set a key (exchange name or env var name)")
482
+ .description("Set a non-secret env value in ~/.perp/.env (private keys are refused: use 'perp wallet set <exchange>')")
276
483
  .action(async (name, value) => {
277
- const resolved = resolveEnvKey(name);
278
- if (resolved) {
279
- // Validate the key
280
- const { valid, address } = await validateKey(resolved.chain, value);
281
- if (!valid) {
282
- if (isJson()) {
283
- const { jsonError } = await import("../utils.js");
284
- return printJson(jsonError("INVALID_PARAMS", `Invalid ${resolved.chain} private key`));
285
- }
286
- console.error(chalk.red(`\n Invalid ${resolved.chain} private key.\n`));
287
- process.exit(1);
484
+ await withJsonErrors(isJson(), async () => {
485
+ const resolved = resolveEnvKey(name);
486
+ if (resolved !== null || isSecretEnvName(name)) {
487
+ throw new PerpError("INVALID_PARAMS", `Refusing to write ${resolved?.envKey ?? name} to ~/.perp/.env: key material is never stored in plaintext or accepted on the command line`, {
488
+ remediation: resolved !== null
489
+ ? "Import the key into the OWS vault: perp wallet set <exchange> (hidden prompt or stdin). Clear the key from your shell history."
490
+ : "Secrets live in the OWS vault (perp wallet set / perp wallet import) or agent keystores (perp wallet agent approve <venue>). Clear the value from your shell history.",
491
+ });
288
492
  }
289
- const normalized = resolved.chain === "evm"
290
- ? (value.startsWith("0x") ? value : `0x${value}`)
291
- : value;
292
- setEnvVar(resolved.envKey, normalized);
293
- if (isJson())
294
- return printJson(jsonOk({ key: resolved.envKey, address, file: ENV_FILE }));
295
- console.log(chalk.green(`\n ${resolved.envKey} set.`));
296
- console.log(` Address: ${chalk.gray(address)}`);
297
- console.log(` File: ${chalk.gray("~/.perp/.env")}\n`);
298
- }
299
- else {
300
- // Raw env var (e.g. LIGHTER_REFERRAL_CODE, custom vars)
493
+ // Raw non-secret env var (e.g. LIGHTER_REFERRAL_CODE, custom vars)
301
494
  setEnvVar(name, value);
302
495
  if (isJson())
303
496
  return printJson(jsonOk({ key: name, file: ENV_FILE }));
304
497
  console.log(chalk.green(`\n ${name} set.`));
305
498
  console.log(` File: ${chalk.gray("~/.perp/.env")}\n`);
306
- }
499
+ });
307
500
  });
308
501
  // ── settings env remove <name> ──
309
502
  env
@@ -323,12 +516,14 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
323
516
  }
324
517
  delete env[envKey];
325
518
  // Rewrite file
326
- const { writeFileSync } = await import("fs");
519
+ // Atomic write with an explicit 0600 (writeFileSync's mode only applies when creating the file:
520
+ // a rewrite would keep looser permissions of an existing .env).
521
+ const { writeKeystoreAtomic } = await import("../agent-wallet/venue-keystore.js");
327
522
  const lines = ["# perp-cli configuration", "# Generated by 'perp setup' — edit freely", ""];
328
523
  for (const [k, v] of Object.entries(env))
329
524
  lines.push(`${k}=${v}`);
330
525
  lines.push("");
331
- writeFileSync(ENV_FILE, lines.join("\n"), { mode: 0o600 });
526
+ writeKeystoreAtomic(ENV_FILE, lines.join("\n"));
332
527
  if (isJson())
333
528
  return printJson(jsonOk({ removed: envKey }));
334
529
  console.log(chalk.yellow(`\n ${envKey} removed from ~/.perp/.env\n`));
@@ -351,7 +546,7 @@ export function registerSettingsCommands(program, isJson, getAdapterForExchange)
351
546
  console.log(chalk.yellow("\n 'perp env' is deprecated. Use 'perp settings env' instead.\n"));
352
547
  console.log(chalk.gray(" Examples:"));
353
548
  console.log(chalk.gray(" perp settings env show"));
354
- console.log(chalk.gray(" perp settings env set <exchange> <key>"));
549
+ console.log(chalk.gray(" perp settings env set <name> <value> (non-secret values only)"));
355
550
  console.log(chalk.gray(" perp settings env remove <name>"));
356
551
  console.log(chalk.gray(" perp settings env path\n"));
357
552
  });
@@ -1,3 +1,53 @@
1
1
  import { Command } from "commander";
2
2
  import type { ExchangeAdapter } from "../exchanges/index.js";
3
+ import { PerpError } from "../errors.js";
4
+ import "../exchanges/venues/index.js";
5
+ import { type CancelResult } from "../exchanges/venue-types.js";
6
+ /**
7
+ * A cancel with anything not proven cancelled (failed, or pending / still listed open after the
8
+ * verifying re-list) → ORDER_PARTIAL carrying the whole result (non-zero exit, also in --json);
9
+ * all failed (none cancelled, none pending) → ORDER_REJECTED (definitely nothing cancelled).
10
+ */
11
+ export declare function cancelIncompleteError(r: CancelResult, scope: string): PerpError | null;
12
+ /**
13
+ * Sizes of scaled levels on the venue lot grid: the total is floored to `step`, each level is
14
+ * floor(total × pct / 100) lots and the LAST level takes the remainder — so every level is a
15
+ * multiple of the lot and the levels sum to the floored total. An off-lot level is refused by the
16
+ * venue (Pacifica 422 "Limit order amount 0.00205 is not a multiple of lot size"; Lighter 21706
17
+ * "invalid order base or quote amount" for 0.01475 TSLA at 4 size decimals). A level of zero lots
18
+ * → SIZE_TOO_SMALL.
19
+ */
20
+ export declare function scaleLevelSizes(total: string, pcts: readonly number[], step: string): string[];
21
+ /**
22
+ * Refuse (SIZE_TOO_SMALL, nothing sent) a scaled order with a level under the venue's published
23
+ * minimum order size / notional (VenueMarket.minSize / minNotional; null = none published on this
24
+ * data path → not checked). Reduce-only levels are held to it too: no venue's docs state an
25
+ * exemption (Pacifica market-info: `min_order_size` "Minimum order size (denominated in USD)",
26
+ * no exception listed).
27
+ */
28
+ export declare function requireScaleLevelMinimums(venue: string, symbol: string, levels: ReadonlyArray<{
29
+ price: string;
30
+ size: string;
31
+ }>, min: {
32
+ minSize: string | null;
33
+ minNotional: string | null;
34
+ }): void;
35
+ /** Outcome of one scaled level: placed, definitely refused, unknown (may exist), or never sent. */
36
+ export interface ScaleLevelOutcome {
37
+ price: string;
38
+ size: string;
39
+ pct: number;
40
+ outcome: "placed" | "rejected" | "unknown" | "notSent";
41
+ result: Record<string, unknown> | {
42
+ error: string;
43
+ };
44
+ }
45
+ /**
46
+ * The failure a scaled order reports unless every level was placed: any unknown outcome →
47
+ * ORDER_PENDING; every level definitely refused → ORDER_REJECTED; otherwise ORDER_PARTIAL.
48
+ * details list the placed / failed / unknown / not-sent levels.
49
+ */
50
+ export declare function scaleIncompleteError(venue: string, what: string, levels: readonly ScaleLevelOutcome[]): PerpError | null;
51
+ /** One-arg `trade cancel <x>`: true when x is an order id (numeric, UUID or 0x-hex), never a symbol. */
52
+ export declare function looksLikeOrderId(x: string): boolean;
3
53
  export declare function registerTradeCommands(program: Command, getAdapter: () => Promise<ExchangeAdapter>, isJson: () => boolean, isDryRun?: () => boolean, getAdapterForExchange?: (exchange: string) => Promise<ExchangeAdapter>): void;