perp-cli 0.11.0 → 0.12.1

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 (240) 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/bridge-engine.js +102 -49
  21. package/dist/cli-spec.js +1 -2
  22. package/dist/commands/account.js +2 -500
  23. package/dist/commands/agent.d.ts +8 -2
  24. package/dist/commands/agent.js +1903 -366
  25. package/dist/commands/alerts.js +3 -1
  26. package/dist/commands/arb/index.js +37 -7
  27. package/dist/commands/arb-auto.js +44 -11
  28. package/dist/commands/arb.js +83 -34
  29. package/dist/commands/bot.d.ts +1 -1
  30. package/dist/commands/bot.js +18 -14
  31. package/dist/commands/bridge.d.ts +1 -1
  32. package/dist/commands/bridge.js +4 -4
  33. package/dist/commands/funds.d.ts +1 -1
  34. package/dist/commands/funds.js +18 -68
  35. package/dist/commands/jobs.d.ts +1 -1
  36. package/dist/commands/jobs.js +2 -2
  37. package/dist/commands/manage.d.ts +1 -1
  38. package/dist/commands/manage.js +12 -84
  39. package/dist/commands/plan.d.ts +1 -1
  40. package/dist/commands/plan.js +2 -2
  41. package/dist/commands/portfolio.d.ts +19 -3
  42. package/dist/commands/portfolio.js +747 -163
  43. package/dist/commands/rebalance.d.ts +1 -1
  44. package/dist/commands/rebalance.js +18 -12
  45. package/dist/commands/risk.js +2 -6
  46. package/dist/commands/settings.js +30 -53
  47. package/dist/commands/trade.js +16 -15
  48. package/dist/commands/wallet.d.ts +2 -1
  49. package/dist/commands/wallet.js +756 -28
  50. package/dist/errors.d.ts +64 -0
  51. package/dist/errors.js +82 -2
  52. package/dist/event-stream.js +12 -1
  53. package/dist/exchanges/aster-typed-data.d.ts +156 -0
  54. package/dist/exchanges/aster-typed-data.js +189 -0
  55. package/dist/exchanges/aster.d.ts +60 -11
  56. package/dist/exchanges/aster.js +278 -136
  57. package/dist/exchanges/hyperliquid.d.ts +14 -0
  58. package/dist/exchanges/hyperliquid.js +84 -34
  59. package/dist/exchanges/lighter-spot.js +14 -3
  60. package/dist/exchanges/lighter.d.ts +66 -0
  61. package/dist/exchanges/lighter.js +199 -27
  62. package/dist/exchanges/pacifica-typed-data.d.ts +75 -0
  63. package/dist/exchanges/pacifica-typed-data.js +74 -0
  64. package/dist/exchanges/pacifica.d.ts +29 -1
  65. package/dist/exchanges/pacifica.js +94 -6
  66. package/dist/funding/rates.js +59 -21
  67. package/dist/guardrail/perp-guardrail.js +9 -1
  68. package/dist/index.d.ts +1 -1
  69. package/dist/index.js +268 -360
  70. package/dist/jobs.js +3 -1
  71. package/dist/mcp-server.js +85 -83
  72. package/dist/position-history.js +47 -4
  73. package/dist/settings.d.ts +67 -3
  74. package/dist/settings.js +1 -2
  75. package/dist/signer/ows-evm.d.ts +14 -0
  76. package/dist/signer/ows-evm.js +28 -9
  77. package/dist/smart-order.d.ts +8 -2
  78. package/dist/smart-order.js +46 -11
  79. package/dist/strategies/funding-arb.js +29 -8
  80. package/dist/trade-validator.js +30 -5
  81. package/dist/utils.d.ts +10 -0
  82. package/dist/utils.js +19 -0
  83. package/package.json +2 -2
  84. package/skills/perp-cli/SKILL.md +23 -14
  85. package/skills/perp-cli/references/agent-operations.md +6 -4
  86. package/skills/perp-cli/references/commands.md +55 -34
  87. package/skills/perp-cli/scripts/preflight.sh +4 -4
  88. package/skills/perp-cli/scripts/spot-perp-scan.sh +2 -2
  89. package/dist/__tests__/alert-logic.test.d.ts +0 -1
  90. package/dist/__tests__/alert-logic.test.js +0 -107
  91. package/dist/__tests__/arb-auto-3dex.test.d.ts +0 -1
  92. package/dist/__tests__/arb-auto-3dex.test.js +0 -397
  93. package/dist/__tests__/arb-history-stats.test.d.ts +0 -1
  94. package/dist/__tests__/arb-history-stats.test.js +0 -176
  95. package/dist/__tests__/arb-logic.test.d.ts +0 -1
  96. package/dist/__tests__/arb-logic.test.js +0 -84
  97. package/dist/__tests__/arb-manage.test.d.ts +0 -1
  98. package/dist/__tests__/arb-manage.test.js +0 -253
  99. package/dist/__tests__/arb-new-features.test.d.ts +0 -1
  100. package/dist/__tests__/arb-new-features.test.js +0 -457
  101. package/dist/__tests__/arb-sizing.test.d.ts +0 -1
  102. package/dist/__tests__/arb-sizing.test.js +0 -48
  103. package/dist/__tests__/arb-state.test.d.ts +0 -1
  104. package/dist/__tests__/arb-state.test.js +0 -284
  105. package/dist/__tests__/arb-userflow.test.d.ts +0 -1
  106. package/dist/__tests__/arb-userflow.test.js +0 -945
  107. package/dist/__tests__/arb-utils.test.d.ts +0 -1
  108. package/dist/__tests__/arb-utils.test.js +0 -264
  109. package/dist/__tests__/bot-conditions.test.d.ts +0 -1
  110. package/dist/__tests__/bot-conditions.test.js +0 -341
  111. package/dist/__tests__/client-id-tracker.test.d.ts +0 -1
  112. package/dist/__tests__/client-id-tracker.test.js +0 -137
  113. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +0 -1
  114. package/dist/__tests__/commands/new-atomic-commands.test.js +0 -502
  115. package/dist/__tests__/commands/order-intent.test.d.ts +0 -1
  116. package/dist/__tests__/commands/order-intent.test.js +0 -600
  117. package/dist/__tests__/commands/trade-commands.test.d.ts +0 -1
  118. package/dist/__tests__/commands/trade-commands.test.js +0 -821
  119. package/dist/__tests__/config.test.d.ts +0 -1
  120. package/dist/__tests__/config.test.js +0 -86
  121. package/dist/__tests__/cross-chain-margin.test.d.ts +0 -1
  122. package/dist/__tests__/cross-chain-margin.test.js +0 -287
  123. package/dist/__tests__/dex-asset-map.test.d.ts +0 -1
  124. package/dist/__tests__/dex-asset-map.test.js +0 -191
  125. package/dist/__tests__/errors.test.d.ts +0 -1
  126. package/dist/__tests__/errors.test.js +0 -110
  127. package/dist/__tests__/event-stream.test.d.ts +0 -1
  128. package/dist/__tests__/event-stream.test.js +0 -276
  129. package/dist/__tests__/exchanges/interface.test.d.ts +0 -1
  130. package/dist/__tests__/exchanges/interface.test.js +0 -132
  131. package/dist/__tests__/exchanges/mock-adapter.d.ts +0 -69
  132. package/dist/__tests__/exchanges/mock-adapter.js +0 -137
  133. package/dist/__tests__/execution-log.test.d.ts +0 -1
  134. package/dist/__tests__/execution-log.test.js +0 -106
  135. package/dist/__tests__/funding-calc.test.d.ts +0 -1
  136. package/dist/__tests__/funding-calc.test.js +0 -71
  137. package/dist/__tests__/funding-history.test.d.ts +0 -1
  138. package/dist/__tests__/funding-history.test.js +0 -343
  139. package/dist/__tests__/funding-rates.test.d.ts +0 -1
  140. package/dist/__tests__/funding-rates.test.js +0 -342
  141. package/dist/__tests__/funding.test.d.ts +0 -1
  142. package/dist/__tests__/funding.test.js +0 -173
  143. package/dist/__tests__/gap-logic.test.d.ts +0 -1
  144. package/dist/__tests__/gap-logic.test.js +0 -43
  145. package/dist/__tests__/hip3-dex.test.d.ts +0 -1
  146. package/dist/__tests__/hip3-dex.test.js +0 -234
  147. package/dist/__tests__/integration/agent-features.integration.test.d.ts +0 -1
  148. package/dist/__tests__/integration/agent-features.integration.test.js +0 -553
  149. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +0 -13
  150. package/dist/__tests__/integration/atomic-commands.integration.test.js +0 -246
  151. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +0 -1
  152. package/dist/__tests__/integration/bridge-simulation.integration.test.js +0 -453
  153. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +0 -1
  154. package/dist/__tests__/integration/bridge-strict.integration.test.js +0 -812
  155. package/dist/__tests__/integration/bridge.integration.test.d.ts +0 -1
  156. package/dist/__tests__/integration/bridge.integration.test.js +0 -309
  157. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +0 -1
  158. package/dist/__tests__/integration/cli-e2e.integration.test.js +0 -202
  159. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +0 -1
  160. package/dist/__tests__/integration/dex-arb.integration.test.js +0 -116
  161. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +0 -13
  162. package/dist/__tests__/integration/envelope-consistency.integration.test.js +0 -205
  163. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +0 -1
  164. package/dist/__tests__/integration/hip3-dex.integration.test.js +0 -147
  165. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +0 -1
  166. package/dist/__tests__/integration/hyperliquid.integration.test.js +0 -79
  167. package/dist/__tests__/integration/lighter.integration.test.d.ts +0 -1
  168. package/dist/__tests__/integration/lighter.integration.test.js +0 -53
  169. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +0 -9
  170. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +0 -236
  171. package/dist/__tests__/integration/order-verification.integration.test.d.ts +0 -1
  172. package/dist/__tests__/integration/order-verification.integration.test.js +0 -321
  173. package/dist/__tests__/integration/pacifica.integration.test.d.ts +0 -1
  174. package/dist/__tests__/integration/pacifica.integration.test.js +0 -75
  175. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +0 -1
  176. package/dist/__tests__/integration/response-shapes.integration.test.js +0 -278
  177. package/dist/__tests__/liquidity.test.d.ts +0 -1
  178. package/dist/__tests__/liquidity.test.js +0 -225
  179. package/dist/__tests__/plan-executor.test.d.ts +0 -1
  180. package/dist/__tests__/plan-executor.test.js +0 -314
  181. package/dist/__tests__/position-history.test.d.ts +0 -1
  182. package/dist/__tests__/position-history.test.js +0 -367
  183. package/dist/__tests__/retry.test.d.ts +0 -1
  184. package/dist/__tests__/retry.test.js +0 -310
  185. package/dist/__tests__/risk-assessment.test.d.ts +0 -1
  186. package/dist/__tests__/risk-assessment.test.js +0 -288
  187. package/dist/__tests__/security-adversarial.test.d.ts +0 -1
  188. package/dist/__tests__/security-adversarial.test.js +0 -574
  189. package/dist/__tests__/strategies.test.d.ts +0 -1
  190. package/dist/__tests__/strategies.test.js +0 -539
  191. package/dist/__tests__/trade-execution.test.d.ts +0 -1
  192. package/dist/__tests__/trade-execution.test.js +0 -129
  193. package/dist/__tests__/trade-validator.test.d.ts +0 -1
  194. package/dist/__tests__/trade-validator.test.js +0 -655
  195. package/dist/__tests__/utils.test.d.ts +0 -1
  196. package/dist/__tests__/utils.test.js +0 -76
  197. package/dist/arb/reconcile.d.ts +0 -28
  198. package/dist/arb/reconcile.js +0 -162
  199. package/dist/commands/alert.d.ts +0 -2
  200. package/dist/commands/alert.js +0 -421
  201. package/dist/commands/analytics.d.ts +0 -3
  202. package/dist/commands/analytics.js +0 -311
  203. package/dist/commands/arb-manage.d.ts +0 -5
  204. package/dist/commands/arb-manage.js +0 -5
  205. package/dist/commands/dashboard.d.ts +0 -3
  206. package/dist/commands/dashboard.js +0 -176
  207. package/dist/commands/deposit.d.ts +0 -4
  208. package/dist/commands/deposit.js +0 -573
  209. package/dist/commands/dex.d.ts +0 -3
  210. package/dist/commands/dex.js +0 -120
  211. package/dist/commands/env.d.ts +0 -2
  212. package/dist/commands/env.js +0 -136
  213. package/dist/commands/funding.d.ts +0 -3
  214. package/dist/commands/funding.js +0 -554
  215. package/dist/commands/gap.d.ts +0 -2
  216. package/dist/commands/gap.js +0 -305
  217. package/dist/commands/health.d.ts +0 -2
  218. package/dist/commands/health.js +0 -67
  219. package/dist/commands/multileg.d.ts +0 -16
  220. package/dist/commands/multileg.js +0 -174
  221. package/dist/commands/ows.d.ts +0 -2
  222. package/dist/commands/ows.js +0 -887
  223. package/dist/commands/pnl.d.ts +0 -12
  224. package/dist/commands/pnl.js +0 -243
  225. package/dist/commands/run.d.ts +0 -3
  226. package/dist/commands/run.js +0 -202
  227. package/dist/commands/stream.d.ts +0 -5
  228. package/dist/commands/stream.js +0 -200
  229. package/dist/commands/withdraw.d.ts +0 -3
  230. package/dist/commands/withdraw.js +0 -187
  231. package/dist/exchanges/edgex.d.ts +0 -62
  232. package/dist/exchanges/edgex.js +0 -552
  233. package/dist/exchanges/nado.d.ts +0 -52
  234. package/dist/exchanges/nado.js +0 -223
  235. package/dist/funding/index.d.ts +0 -3
  236. package/dist/funding/index.js +0 -3
  237. package/dist/retry.d.ts +0 -74
  238. package/dist/retry.js +0 -129
  239. package/dist/ws/market-feed.d.ts +0 -74
  240. 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
  };