perp-cli 0.9.8 → 0.12.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 (254) hide show
  1. package/README.md +30 -25
  2. package/dist/_stderr-filter.d.ts +0 -0
  3. package/dist/_stderr-filter.js +30 -0
  4. package/dist/agent-wallet/expiry.d.ts +15 -0
  5. package/dist/agent-wallet/expiry.js +26 -0
  6. package/dist/agent-wallet/lighter-keystore.d.ts +34 -0
  7. package/dist/agent-wallet/lighter-keystore.js +232 -0
  8. package/dist/agent-wallet/passphrase.d.ts +13 -0
  9. package/dist/agent-wallet/passphrase.js +54 -0
  10. package/dist/agent-wallet/signing-strategy.d.ts +67 -0
  11. package/dist/agent-wallet/signing-strategy.js +51 -0
  12. package/dist/agent-wallet/store.d.ts +37 -0
  13. package/dist/agent-wallet/store.js +172 -0
  14. package/dist/api/public/_http.d.ts +15 -0
  15. package/dist/api/public/_http.js +24 -0
  16. package/dist/api/public/hyperliquid.js +36 -24
  17. package/dist/api/public/lighter.d.ts +7 -1
  18. package/dist/api/public/lighter.js +85 -37
  19. package/dist/api/public/pacifica.js +47 -18
  20. package/dist/bot/engine.js +23 -4
  21. package/dist/bot/presets.js +3 -3
  22. package/dist/bot/strategies/funding-arb-strategy.d.ts +0 -23
  23. package/dist/bot/strategies/funding-arb-strategy.js +41 -176
  24. package/dist/bot/strategies/funding-auto.js +2 -2
  25. package/dist/bridge-engine.js +102 -49
  26. package/dist/cli-spec.js +1 -2
  27. package/dist/commands/account.js +2 -500
  28. package/dist/commands/agent.d.ts +8 -2
  29. package/dist/commands/agent.js +1903 -366
  30. package/dist/commands/alerts.js +3 -1
  31. package/dist/commands/arb/index.js +37 -7
  32. package/dist/commands/arb-auto.js +44 -11
  33. package/dist/commands/arb.js +83 -34
  34. package/dist/commands/bot.d.ts +1 -1
  35. package/dist/commands/bot.js +20 -16
  36. package/dist/commands/bridge.d.ts +1 -1
  37. package/dist/commands/bridge.js +4 -4
  38. package/dist/commands/funds.d.ts +1 -1
  39. package/dist/commands/funds.js +18 -68
  40. package/dist/commands/jobs.d.ts +1 -1
  41. package/dist/commands/jobs.js +2 -2
  42. package/dist/commands/manage.d.ts +1 -1
  43. package/dist/commands/manage.js +12 -84
  44. package/dist/commands/plan.d.ts +1 -1
  45. package/dist/commands/plan.js +2 -2
  46. package/dist/commands/portfolio.d.ts +19 -3
  47. package/dist/commands/portfolio.js +747 -163
  48. package/dist/commands/rebalance.d.ts +1 -1
  49. package/dist/commands/rebalance.js +18 -12
  50. package/dist/commands/risk.js +2 -6
  51. package/dist/commands/settings.js +30 -53
  52. package/dist/commands/trade.js +16 -15
  53. package/dist/commands/wallet.d.ts +2 -1
  54. package/dist/commands/wallet.js +1122 -176
  55. package/dist/config.d.ts +9 -0
  56. package/dist/config.js +55 -9
  57. package/dist/errors.d.ts +64 -0
  58. package/dist/errors.js +82 -2
  59. package/dist/event-stream.js +12 -1
  60. package/dist/exchanges/aster-typed-data.d.ts +156 -0
  61. package/dist/exchanges/aster-typed-data.js +189 -0
  62. package/dist/exchanges/aster.d.ts +60 -11
  63. package/dist/exchanges/aster.js +278 -136
  64. package/dist/exchanges/hyperliquid.d.ts +14 -0
  65. package/dist/exchanges/hyperliquid.js +88 -34
  66. package/dist/exchanges/lighter-spot.js +19 -5
  67. package/dist/exchanges/lighter.d.ts +66 -0
  68. package/dist/exchanges/lighter.js +213 -31
  69. package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
  70. package/dist/exchanges/pacifica-typed-data.js +74 -0
  71. package/dist/exchanges/pacifica.d.ts +29 -1
  72. package/dist/exchanges/pacifica.js +94 -6
  73. package/dist/funding/rates.js +59 -21
  74. package/dist/guardrail/contracts.d.ts +14 -0
  75. package/dist/guardrail/contracts.js +72 -0
  76. package/dist/guardrail/perp-guardrail.d.ts +21 -0
  77. package/dist/guardrail/perp-guardrail.js +145 -0
  78. package/dist/index.d.ts +1 -1
  79. package/dist/index.js +307 -362
  80. package/dist/jobs.js +3 -1
  81. package/dist/mcp-server.js +92 -88
  82. package/dist/position-history.js +47 -4
  83. package/dist/settings.d.ts +69 -3
  84. package/dist/settings.js +3 -2
  85. package/dist/signer/index.d.ts +2 -0
  86. package/dist/signer/index.js +2 -0
  87. package/dist/signer/ows-evm.d.ts +32 -0
  88. package/dist/signer/ows-evm.js +86 -0
  89. package/dist/signer/ows-loader.d.ts +2 -0
  90. package/dist/signer/ows-loader.js +6 -0
  91. package/dist/signer/ows-solana.d.ts +25 -0
  92. package/dist/signer/ows-solana.js +56 -0
  93. package/dist/smart-order.d.ts +8 -2
  94. package/dist/smart-order.js +46 -11
  95. package/dist/strategies/funding-arb.js +29 -8
  96. package/dist/trade-validator.js +30 -5
  97. package/dist/utils.d.ts +10 -0
  98. package/dist/utils.js +19 -0
  99. package/package.json +4 -2
  100. package/skills/perp-cli/SKILL.md +23 -14
  101. package/skills/perp-cli/references/agent-operations.md +6 -4
  102. package/skills/perp-cli/references/commands.md +55 -34
  103. package/skills/perp-cli/scripts/preflight.sh +4 -4
  104. package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
  105. package/dist/__tests__/alert-logic.test.d.ts +0 -1
  106. package/dist/__tests__/alert-logic.test.js +0 -107
  107. package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
  108. package/dist/__tests__/arb-auto-3dex.test.js +0 -397
  109. package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
  110. package/dist/__tests__/arb-history-stats.test.js +0 -176
  111. package/dist/__tests__/arb-logic.test.d.ts +0 -1
  112. package/dist/__tests__/arb-logic.test.js +0 -84
  113. package/dist/__tests__/arb-manage.test.d.ts +0 -1
  114. package/dist/__tests__/arb-manage.test.js +0 -253
  115. package/dist/__tests__/arb-new-features.test.d.ts +0 -1
  116. package/dist/__tests__/arb-new-features.test.js +0 -457
  117. package/dist/__tests__/arb-sizing.test.d.ts +0 -1
  118. package/dist/__tests__/arb-sizing.test.js +0 -48
  119. package/dist/__tests__/arb-state.test.d.ts +0 -1
  120. package/dist/__tests__/arb-state.test.js +0 -284
  121. package/dist/__tests__/arb-userflow.test.d.ts +0 -1
  122. package/dist/__tests__/arb-userflow.test.js +0 -945
  123. package/dist/__tests__/arb-utils.test.d.ts +0 -1
  124. package/dist/__tests__/arb-utils.test.js +0 -264
  125. package/dist/__tests__/bot-conditions.test.d.ts +0 -1
  126. package/dist/__tests__/bot-conditions.test.js +0 -341
  127. package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
  128. package/dist/__tests__/client-id-tracker.test.js +0 -137
  129. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
  130. package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
  131. package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
  132. package/dist/__tests__/commands/order-intent.test.js +0 -600
  133. package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
  134. package/dist/__tests__/commands/trade-commands.test.js +0 -821
  135. package/dist/__tests__/config.test.d.ts +0 -1
  136. package/dist/__tests__/config.test.js +0 -86
  137. package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
  138. package/dist/__tests__/cross-chain-margin.test.js +0 -287
  139. package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
  140. package/dist/__tests__/dex-asset-map.test.js +0 -191
  141. package/dist/__tests__/errors.test.d.ts +0 -1
  142. package/dist/__tests__/errors.test.js +0 -110
  143. package/dist/__tests__/event-stream.test.d.ts +0 -1
  144. package/dist/__tests__/event-stream.test.js +0 -276
  145. package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
  146. package/dist/__tests__/exchanges/interface.test.js +0 -132
  147. package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
  148. package/dist/__tests__/exchanges/mock-adapter.js +0 -137
  149. package/dist/__tests__/execution-log.test.d.ts +0 -1
  150. package/dist/__tests__/execution-log.test.js +0 -106
  151. package/dist/__tests__/funding-calc.test.d.ts +0 -1
  152. package/dist/__tests__/funding-calc.test.js +0 -71
  153. package/dist/__tests__/funding-history.test.d.ts +0 -1
  154. package/dist/__tests__/funding-history.test.js +0 -343
  155. package/dist/__tests__/funding-rates.test.d.ts +0 -1
  156. package/dist/__tests__/funding-rates.test.js +0 -342
  157. package/dist/__tests__/funding.test.d.ts +0 -1
  158. package/dist/__tests__/funding.test.js +0 -173
  159. package/dist/__tests__/gap-logic.test.d.ts +0 -1
  160. package/dist/__tests__/gap-logic.test.js +0 -43
  161. package/dist/__tests__/hip3-dex.test.d.ts +0 -1
  162. package/dist/__tests__/hip3-dex.test.js +0 -234
  163. package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
  164. package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
  165. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
  166. package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
  167. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
  168. package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
  169. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
  170. package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
  171. package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
  172. package/dist/__tests__/integration/bridge.integration.test.js +0 -309
  173. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
  174. package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
  175. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
  176. package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
  177. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
  178. package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
  179. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
  180. package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
  181. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
  182. package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
  183. package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
  184. package/dist/__tests__/integration/lighter.integration.test.js +0 -53
  185. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
  186. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
  187. package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
  188. package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
  189. package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
  190. package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
  191. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
  192. package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
  193. package/dist/__tests__/liquidity.test.d.ts +0 -1
  194. package/dist/__tests__/liquidity.test.js +0 -225
  195. package/dist/__tests__/plan-executor.test.d.ts +0 -1
  196. package/dist/__tests__/plan-executor.test.js +0 -314
  197. package/dist/__tests__/position-history.test.d.ts +0 -1
  198. package/dist/__tests__/position-history.test.js +0 -367
  199. package/dist/__tests__/retry.test.d.ts +0 -1
  200. package/dist/__tests__/retry.test.js +0 -310
  201. package/dist/__tests__/risk-assessment.test.d.ts +0 -1
  202. package/dist/__tests__/risk-assessment.test.js +0 -288
  203. package/dist/__tests__/security-adversarial.test.d.ts +0 -1
  204. package/dist/__tests__/security-adversarial.test.js +0 -574
  205. package/dist/__tests__/strategies.test.d.ts +0 -1
  206. package/dist/__tests__/strategies.test.js +0 -539
  207. package/dist/__tests__/trade-execution.test.d.ts +0 -1
  208. package/dist/__tests__/trade-execution.test.js +0 -129
  209. package/dist/__tests__/trade-validator.test.d.ts +0 -1
  210. package/dist/__tests__/trade-validator.test.js +0 -655
  211. package/dist/__tests__/utils.test.d.ts +0 -1
  212. package/dist/__tests__/utils.test.js +0 -76
  213. package/dist/arb/reconcile.d.ts +0 -28
  214. package/dist/arb/reconcile.js +0 -162
  215. package/dist/commands/alert.d.ts +0 -2
  216. package/dist/commands/alert.js +0 -421
  217. package/dist/commands/analytics.d.ts +0 -3
  218. package/dist/commands/analytics.js +0 -311
  219. package/dist/commands/arb-manage.d.ts +0 -5
  220. package/dist/commands/arb-manage.js +0 -5
  221. package/dist/commands/dashboard.d.ts +0 -3
  222. package/dist/commands/dashboard.js +0 -176
  223. package/dist/commands/deposit.d.ts +0 -4
  224. package/dist/commands/deposit.js +0 -573
  225. package/dist/commands/dex.d.ts +0 -3
  226. package/dist/commands/dex.js +0 -120
  227. package/dist/commands/env.d.ts +0 -2
  228. package/dist/commands/env.js +0 -136
  229. package/dist/commands/funding.d.ts +0 -3
  230. package/dist/commands/funding.js +0 -554
  231. package/dist/commands/gap.d.ts +0 -2
  232. package/dist/commands/gap.js +0 -305
  233. package/dist/commands/health.d.ts +0 -2
  234. package/dist/commands/health.js +0 -67
  235. package/dist/commands/multileg.d.ts +0 -16
  236. package/dist/commands/multileg.js +0 -174
  237. package/dist/commands/pnl.d.ts +0 -12
  238. package/dist/commands/pnl.js +0 -243
  239. package/dist/commands/run.d.ts +0 -3
  240. package/dist/commands/run.js +0 -202
  241. package/dist/commands/stream.d.ts +0 -5
  242. package/dist/commands/stream.js +0 -200
  243. package/dist/commands/withdraw.d.ts +0 -3
  244. package/dist/commands/withdraw.js +0 -187
  245. package/dist/exchanges/edgex.d.ts +0 -62
  246. package/dist/exchanges/edgex.js +0 -552
  247. package/dist/exchanges/nado.d.ts +0 -52
  248. package/dist/exchanges/nado.js +0 -223
  249. package/dist/funding/index.d.ts +0 -3
  250. package/dist/funding/index.js +0 -3
  251. package/dist/retry.d.ts +0 -74
  252. package/dist/retry.js +0 -129
  253. package/dist/ws/market-feed.d.ts +0 -74
  254. package/dist/ws/market-feed.js +0 -227
@@ -33,6 +33,20 @@ const RPC_URLS = {
33
33
  base: "https://mainnet.base.org",
34
34
  hyperliquid: "https://rpc.hyperliquid.xyz/evm",
35
35
  };
36
+ /**
37
+ * Look up an RPC URL for a chain. SSOT rule #2: refuse to silently fall
38
+ * through to the Arbitrum RPC for unknown chain names — that masked routing
39
+ * bugs that sent transactions to the wrong network.
40
+ */
41
+ function requireRpcUrl(chain) {
42
+ const url = RPC_URLS[chain];
43
+ if (!url) {
44
+ throw new Error(`No RPC URL configured for chain="${chain}". ` +
45
+ `Known chains: ${Object.keys(RPC_URLS).join(", ")}. ` +
46
+ `Refusing to silently fall through to the Arbitrum RPC.`);
47
+ }
48
+ return url;
49
+ }
36
50
  // ── CCTP V2 Forwarding Service ──
37
51
  // Circle Forwarding Service: Circle handles dst chain mint for you.
38
52
  // Use depositForBurnWithHook + forward hook data → no manual receiveMessage needed.
@@ -54,46 +68,50 @@ const CCTP_FORWARD_HOOK_DATA = "0x636374702d666f72776172640000000000000000000000
54
68
  * Returns maxFee in USDC subunits (6 decimals).
55
69
  */
56
70
  async function getCctpRelayFee(srcDomain, dstDomain, finalityThreshold = 2000, useForwarding = false, amountUsdc = 100) {
57
- try {
58
- const qs = useForwarding ? "?forward=true" : "";
59
- const res = await fetch(`${CCTP_FEE_API}/${srcDomain}/${dstDomain}${qs}`);
60
- if (res.ok) {
61
- const body = await res.json();
62
- // Check for forwarding error response
63
- if (body && typeof body === "object" && "error" in body) {
64
- if (useForwarding)
65
- return getCctpRelayFee(srcDomain, dstDomain, finalityThreshold, false, amountUsdc);
66
- }
67
- const schedules = body;
68
- const schedule = schedules.find(s => s.finalityThreshold === finalityThreshold) ?? schedules[0];
69
- if (schedule) {
70
- // Protocol fee: minimumFee is in basis points → actual fee = amount × bps / 10000
71
- const amountSubunits = BigInt(Math.round(amountUsdc * 1e6));
72
- const bpsRounded = BigInt(Math.round(schedule.minimumFee * 100));
73
- const protocolFee = (amountSubunits * bpsRounded) / 1000000n;
74
- // Add 20% buffer per Circle docs recommendation
75
- const protocolFeeBuffered = (protocolFee * 120n) / 100n;
76
- if (schedule.forwardFee) {
77
- // Forwarding: protocol fee + forwarding service fee
78
- const forwardFeeSubunits = BigInt(schedule.forwardFee.high);
79
- const totalMaxFee = protocolFeeBuffered + forwardFeeSubunits;
80
- const totalFeeUsdc = Number(totalMaxFee) / 1e6;
81
- return { maxFee: totalMaxFee, feeUsdc: totalFeeUsdc, forwardingAvailable: true };
82
- }
83
- // No forwarding — protocol fee only (or minimal for standard to incentivize relay)
84
- if (protocolFeeBuffered > 0n) {
85
- return { maxFee: protocolFeeBuffered, feeUsdc: Number(protocolFeeBuffered) / 1e6, forwardingAvailable: false };
86
- }
87
- // Standard (free): set small maxFee to incentivize relay
88
- return { maxFee: 10000n, feeUsdc: 0.01, forwardingAvailable: false }; // $0.01
89
- }
90
- }
71
+ // SSOT rule #2: Circle's CCTP fee schedule is the SSOT for bridge cost.
72
+ // Previously a fetch failure returned a hardcoded $0.50 / $0.25 fallback —
73
+ // that silently set maxFee at a guess, which can either burn user dollars
74
+ // (over-fee) or get the bridge stuck (under-fee). Errors must propagate so
75
+ // the caller can decide whether to retry or abort.
76
+ const qs = useForwarding ? "?forward=true" : "";
77
+ const res = await fetch(`${CCTP_FEE_API}/${srcDomain}/${dstDomain}${qs}`);
78
+ if (!res.ok) {
79
+ throw new Error(`CCTP fee API ${srcDomain}→${dstDomain} returned HTTP ${res.status}`);
91
80
  }
92
- catch {
93
- // fallback
81
+ const body = await res.json();
82
+ // Check for forwarding error response — caller can request without forwarding
83
+ if (body && typeof body === "object" && "error" in body) {
84
+ if (useForwarding)
85
+ return getCctpRelayFee(srcDomain, dstDomain, finalityThreshold, false, amountUsdc);
86
+ throw new Error(`CCTP fee API ${srcDomain}→${dstDomain} returned error: ${JSON.stringify(body)}`);
87
+ }
88
+ const schedules = body;
89
+ // Prefer exact finalityThreshold match; otherwise pick the first scheduled
90
+ // tier (Circle returns Standard + Fast in one envelope). The "first" choice
91
+ // is documented behavior, not error fallback.
92
+ const schedule = schedules.find(s => s.finalityThreshold === finalityThreshold) ?? schedules[0];
93
+ if (!schedule) {
94
+ throw new Error(`CCTP fee API ${srcDomain}→${dstDomain} returned no fee schedule`);
95
+ }
96
+ // Protocol fee: minimumFee is in basis points → actual fee = amount × bps / 10000
97
+ const amountSubunits = BigInt(Math.round(amountUsdc * 1e6));
98
+ const bpsRounded = BigInt(Math.round(schedule.minimumFee * 100));
99
+ const protocolFee = (amountSubunits * bpsRounded) / 1000000n;
100
+ // Add 20% buffer per Circle docs recommendation
101
+ const protocolFeeBuffered = (protocolFee * 120n) / 100n;
102
+ if (schedule.forwardFee) {
103
+ // Forwarding: protocol fee + forwarding service fee
104
+ const forwardFeeSubunits = BigInt(schedule.forwardFee.high);
105
+ const totalMaxFee = protocolFeeBuffered + forwardFeeSubunits;
106
+ const totalFeeUsdc = Number(totalMaxFee) / 1e6;
107
+ return { maxFee: totalMaxFee, feeUsdc: totalFeeUsdc, forwardingAvailable: true };
94
108
  }
95
- const fallback = finalityThreshold === 1000 ? 0.50 : 0.25;
96
- return { maxFee: BigInt(Math.round(fallback * 1e6)), feeUsdc: fallback, forwardingAvailable: useForwarding };
109
+ // No forwarding — protocol fee only (or minimal for standard to incentivize relay)
110
+ if (protocolFeeBuffered > 0n) {
111
+ return { maxFee: protocolFeeBuffered, feeUsdc: Number(protocolFeeBuffered) / 1e6, forwardingAvailable: false };
112
+ }
113
+ // Standard (free): set small maxFee to incentivize relay (documented Circle pattern)
114
+ return { maxFee: 10000n, feeUsdc: 0.01, forwardingAvailable: false }; // $0.01
97
115
  }
98
116
  // ── Balance Check ──
99
117
  /**
@@ -287,8 +305,25 @@ export async function getDebridgeQuote(srcChain, dstChain, amountUsdc, senderAdd
287
305
  const quote = await quoteRes.json();
288
306
  const estimation = quote.estimation;
289
307
  const dstOut = estimation?.dstChainTokenOut;
290
- const amountOut = Number(dstOut?.recommendedAmount ?? dstOut?.amount ?? 0) / 1e6;
291
- const fulfillDelay = Number(quote.order?.approximateFulfillmentDelay ?? 10);
308
+ // SSOT rule #2: bridge quote drives the user's expected amount-out. A
309
+ // missing/malformed dstChainTokenOut.amount must surface as a thrown error,
310
+ // never coerce silently to 0 (which would back-fill amountIn as fee and
311
+ // silently misrepresent the cost).
312
+ const dstAmountRaw = dstOut?.recommendedAmount ?? dstOut?.amount;
313
+ const dstAmountNumber = Number(dstAmountRaw);
314
+ if (dstAmountRaw === undefined || !Number.isFinite(dstAmountNumber)) {
315
+ throw new Error(`deBridge quote missing dstChainTokenOut.recommendedAmount/amount ` +
316
+ `(received ${JSON.stringify(dstAmountRaw)}); refusing silent 0 fallback. ` +
317
+ `Raw estimation=${JSON.stringify(estimation)}`);
318
+ }
319
+ const amountOut = dstAmountNumber / 1e6;
320
+ const order = quote.order;
321
+ const fulfillDelayRaw = Number(order?.approximateFulfillmentDelay);
322
+ // approximateFulfillmentDelay is documented as nice-to-have telemetry;
323
+ // when omitted, default to 10 (deBridge's published median for healthy
324
+ // routes). This is a documented telemetry default, NOT an error fallback —
325
+ // it does not affect the user's bridge amount/fee math.
326
+ const fulfillDelay = Number.isFinite(fulfillDelayRaw) ? fulfillDelayRaw : 10;
292
327
  return {
293
328
  provider: "debridge",
294
329
  srcChain,
@@ -395,7 +430,7 @@ async function submitSolanaTransaction(tx, signerKey) {
395
430
  */
396
431
  async function submitEvmTransaction(tx, privateKey, chain) {
397
432
  const { ethers } = await import("ethers");
398
- const rpc = RPC_URLS[chain] ?? RPC_URLS.arbitrum;
433
+ const rpc = requireRpcUrl(chain);
399
434
  const provider = new ethers.JsonRpcProvider(rpc);
400
435
  const wallet = new ethers.Wallet(privateKey, provider);
401
436
  // Check and approve USDC allowance if needed
@@ -783,7 +818,7 @@ async function executeCctpHyperCoreToEvm(dstChain, amountUsdc, signerKey, recipi
783
818
  // Other EVM chains: approve + TokenMessengerV2.depositForBurnWithHook (2-TX fallback)
784
819
  async function executeCctpEvmToHyperCore(srcChain, amountUsdc, signerKey, recipientAddress) {
785
820
  const { ethers } = await import("ethers");
786
- const srcRpc = RPC_URLS[srcChain] ?? RPC_URLS.arbitrum;
821
+ const srcRpc = requireRpcUrl(srcChain);
787
822
  const srcProvider = new ethers.JsonRpcProvider(srcRpc);
788
823
  const srcWallet = new ethers.Wallet(signerKey, srcProvider);
789
824
  const usdcAddr = USDC_ADDRESSES[srcChain];
@@ -876,7 +911,7 @@ async function executeCctpEvmToHyperCore(srcChain, amountUsdc, signerKey, recipi
876
911
  // ── CCTP: EVM → EVM ──
877
912
  async function executeCctpEvmToEvm(srcChain, dstChain, amountUsdc, signerKey, recipientAddress, fast = false) {
878
913
  const { ethers } = await import("ethers");
879
- const srcRpc = RPC_URLS[srcChain] ?? RPC_URLS.arbitrum;
914
+ const srcRpc = requireRpcUrl(srcChain);
880
915
  const srcProvider = new ethers.JsonRpcProvider(srcRpc);
881
916
  const srcWallet = new ethers.Wallet(signerKey, srcProvider);
882
917
  const usdcAddr = USDC_ADDRESSES[srcChain];
@@ -1048,7 +1083,7 @@ async function executeCctpEvmToSolana(srcChain, amountUsdc, signerKey, recipient
1048
1083
  solanaPayerKey, // Solana private key for receiveMessage relay
1049
1084
  fast = false) {
1050
1085
  const { ethers } = await import("ethers");
1051
- const srcRpc = RPC_URLS[srcChain] ?? RPC_URLS.arbitrum;
1086
+ const srcRpc = requireRpcUrl(srcChain);
1052
1087
  const srcProvider = new ethers.JsonRpcProvider(srcRpc);
1053
1088
  const srcWallet = new ethers.Wallet(signerKey, srcProvider);
1054
1089
  const usdcAddr = USDC_ADDRESSES[srcChain];
@@ -1328,18 +1363,36 @@ export async function getRelayQuote(srcChain, dstChain, amountUsdc, senderAddres
1328
1363
  const data = await res.json();
1329
1364
  const details = data.details;
1330
1365
  const fees = data.fees;
1366
+ // SSOT rule #2: refuse to fabricate amountOut/fee from `0 || subtractFee`
1367
+ // back-fill. If Relay didn't quote a real amount-out + relayer fee, the
1368
+ // user's bridge cost is unknown — surface the gap.
1331
1369
  const currencyOut = details?.currencyOut;
1332
- const amountOut = Number(currencyOut?.amountFormatted ?? 0);
1370
+ const amountFormattedRaw = currencyOut?.amountFormatted;
1371
+ const amountOut = Number(amountFormattedRaw);
1372
+ if (amountFormattedRaw === undefined || !Number.isFinite(amountOut)) {
1373
+ throw new Error(`Relay quote missing details.currencyOut.amountFormatted ` +
1374
+ `(received ${JSON.stringify(amountFormattedRaw)}); refusing silent 0 fallback. ` +
1375
+ `Raw details=${JSON.stringify(details)}`);
1376
+ }
1333
1377
  const relayerFee = fees?.relayer;
1334
- const feeUsd = Number(relayerFee?.amountUsd ?? 0);
1335
- const timeEstimate = Number(details?.timeEstimate ?? 30);
1378
+ const feeUsdRaw = relayerFee?.amountUsd;
1379
+ const feeUsd = Number(feeUsdRaw);
1380
+ if (feeUsdRaw === undefined || !Number.isFinite(feeUsd)) {
1381
+ throw new Error(`Relay quote missing fees.relayer.amountUsd ` +
1382
+ `(received ${JSON.stringify(feeUsdRaw)}); refusing silent 0 fallback. ` +
1383
+ `Raw fees=${JSON.stringify(fees)}`);
1384
+ }
1385
+ // timeEstimate is telemetry only; default 30s is documented Relay median
1386
+ // for healthy routes. Telemetry default, not error fallback.
1387
+ const timeEstimateRaw = Number(details?.timeEstimate);
1388
+ const timeEstimate = Number.isFinite(timeEstimateRaw) ? timeEstimateRaw : 30;
1336
1389
  return {
1337
1390
  provider: "relay",
1338
1391
  srcChain,
1339
1392
  dstChain,
1340
1393
  amountIn: amountUsdc,
1341
- amountOut: amountOut || (amountUsdc - feeUsd),
1342
- fee: feeUsd || (amountUsdc - amountOut),
1394
+ amountOut,
1395
+ fee: feeUsd,
1343
1396
  estimatedTime: timeEstimate,
1344
1397
  gasIncluded: true,
1345
1398
  gasNote: "Relay solver handles destination execution",
package/dist/cli-spec.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Generates a structured CLI spec from Commander's program tree.
3
- * Used by `perp api-spec` so agents can discover all commands programmatically.
3
+ * Internal helper retained for tests; no command surfaces it externally.
4
4
  */
5
5
  import { createRequire } from "node:module";
6
6
  const require = createRequire(import.meta.url);
@@ -75,7 +75,6 @@ export function getCliSpec(program) {
75
75
  "Check error.retryable before retrying failed commands",
76
76
  "Use 'perp --json trade check <symbol> <side> <size>' to validate before trading",
77
77
  "Use 'perp --json market mid <symbol>' for fast price lookups",
78
- "Use 'perp agent ping' to check exchange connectivity before operations",
79
78
  "If this CLI is useful, run 'perp settings referrals on' to support development",
80
79
  ],
81
80
  };