perp-cli 0.11.0 → 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 (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 +255 -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
@@ -1,812 +0,0 @@
1
- /**
2
- * Strict Bridge Integration Tests — comprehensive verification using real keys.
3
- *
4
- * Uses .env keys for address derivation and balance checks.
5
- * NO transactions are executed. NO funds are spent.
6
- *
7
- * Core verification: "sender pays only" — all bridge fees are deducted from
8
- * the source USDC amount. The recipient receives amountOut without paying
9
- * any gas or fees on the destination chain.
10
- *
11
- * Tests:
12
- * 1. Key derivation & address consistency
13
- * 2. Real balance checks on all chains
14
- * 3. CCTP quote API for all routes (including HyperCore)
15
- * 4. HyperCore CCTP fees API validation
16
- * 5. deBridge quote API with real addresses
17
- * 6. Relay quote API with real addresses
18
- * 7. getAllQuotes aggregation correctness
19
- * 8. getBestQuote selection logic
20
- * 9. Cross-provider quote comparison
21
- * 10. Edge cases and error handling
22
- * 11-14. On-chain / API verification
23
- * 15. SENDER-PAYS-ONLY: full 4-chain matrix verification
24
- * 16. Complete route matrix (all 12 directional pairs)
25
- */
26
- import { describe, it, expect, beforeAll } from "vitest";
27
- import { config } from "dotenv";
28
- import { CHAIN_IDS, USDC_ADDRESSES, CCTP_DOMAINS, EXCHANGE_TO_CHAIN, getCctpQuote, getDebridgeQuote, getRelayQuote, getAllQuotes, getBestQuote, getEvmUsdcBalance, getSolanaUsdcBalance, checkBridgeBalance, getNativeGasBalance, checkBridgeGasBalance, } from "../../bridge-engine.js";
29
- // Load .env
30
- config();
31
- const wait = (ms) => new Promise((r) => setTimeout(r, ms));
32
- // ── Derive real addresses from .env keys ──
33
- let solanaAddress;
34
- let evmAddress;
35
- describe("Strict Bridge Integration Tests", { timeout: 120000 }, () => {
36
- // ══════════════════════════════════════════════════════════
37
- // 0. Setup: derive addresses from private keys
38
- // ══════════════════════════════════════════════════════════
39
- beforeAll(async () => {
40
- // Derive Solana address
41
- const { Keypair } = await import("@solana/web3.js");
42
- const bs58 = await import("bs58");
43
- const solanaKey = process.env.pk ?? process.env.PACIFICA_PRIVATE_KEY;
44
- if (!solanaKey)
45
- throw new Error("Missing 'pk' or 'PACIFICA_PRIVATE_KEY' in .env");
46
- const keypair = Keypair.fromSecretKey(bs58.default.decode(solanaKey));
47
- solanaAddress = keypair.publicKey.toBase58();
48
- // Derive EVM address
49
- const { ethers } = await import("ethers");
50
- const evmKey = process.env.HL_PRIVATE_KEY;
51
- if (!evmKey)
52
- throw new Error("Missing 'HL_PRIVATE_KEY' in .env");
53
- const wallet = new ethers.Wallet(evmKey);
54
- evmAddress = wallet.address;
55
- });
56
- // ══════════════════════════════════════════════════════════
57
- // 1. Key & Address Validation
58
- // ══════════════════════════════════════════════════════════
59
- describe("1. Key derivation & address format", () => {
60
- it("Solana address is valid base58 (32-44 chars)", () => {
61
- expect(solanaAddress).toBeDefined();
62
- expect(solanaAddress.length).toBeGreaterThanOrEqual(32);
63
- expect(solanaAddress.length).toBeLessThanOrEqual(44);
64
- expect(solanaAddress).toMatch(/^[1-9A-HJ-NP-Za-km-z]+$/);
65
- });
66
- it("EVM address is valid checksummed address", () => {
67
- expect(evmAddress).toBeDefined();
68
- expect(evmAddress).toMatch(/^0x[0-9a-fA-F]{40}$/);
69
- });
70
- it("HL_PRIVATE_KEY and LIGHTER_PRIVATE_KEY derive same EVM address", async () => {
71
- const { ethers } = await import("ethers");
72
- const hlWallet = new ethers.Wallet(process.env.HL_PRIVATE_KEY);
73
- const lighterWallet = new ethers.Wallet(process.env.LIGHTER_PRIVATE_KEY);
74
- expect(hlWallet.address).toBe(lighterWallet.address);
75
- });
76
- });
77
- // ══════════════════════════════════════════════════════════
78
- // 2. Configuration Consistency
79
- // ══════════════════════════════════════════════════════════
80
- describe("2. Configuration consistency", () => {
81
- it("EXCHANGE_TO_CHAIN maps correctly", () => {
82
- expect(EXCHANGE_TO_CHAIN.pacifica).toBe("solana");
83
- expect(EXCHANGE_TO_CHAIN.hyperliquid).toBe("hyperliquid");
84
- expect(EXCHANGE_TO_CHAIN.lighter).toBe("arbitrum");
85
- });
86
- it("all standard chains have chain IDs", () => {
87
- expect(CHAIN_IDS.solana).toBe(7565164);
88
- expect(CHAIN_IDS.arbitrum).toBe(42161);
89
- expect(CHAIN_IDS.base).toBe(8453);
90
- });
91
- it("all standard chains have USDC addresses", () => {
92
- for (const chain of ["solana", "arbitrum", "base"]) {
93
- expect(USDC_ADDRESSES[chain]).toBeDefined();
94
- }
95
- });
96
- it("CCTP domains include hyperliquid (19)", () => {
97
- expect(CCTP_DOMAINS.solana).toBe(5);
98
- expect(CCTP_DOMAINS.arbitrum).toBe(3);
99
- expect(CCTP_DOMAINS.base).toBe(6);
100
- expect(CCTP_DOMAINS.hyperliquid).toBe(19);
101
- });
102
- it("no unsupported chains leak into config", () => {
103
- const supportedChains = ["solana", "arbitrum", "base"];
104
- for (const chain of Object.keys(CHAIN_IDS)) {
105
- expect(supportedChains).toContain(chain);
106
- }
107
- });
108
- it("CCTP domains only contain supported chains + hyperliquid", () => {
109
- const allowed = ["solana", "arbitrum", "base", "hyperliquid"];
110
- for (const chain of Object.keys(CCTP_DOMAINS)) {
111
- expect(allowed).toContain(chain);
112
- }
113
- });
114
- });
115
- // ══════════════════════════════════════════════════════════
116
- // 3. Real Balance Checks
117
- // ══════════════════════════════════════════════════════════
118
- describe("3. Real balance checks", () => {
119
- it("Solana USDC balance query succeeds", async () => {
120
- const balance = await getSolanaUsdcBalance(solanaAddress);
121
- expect(typeof balance).toBe("number");
122
- expect(balance).toBeGreaterThanOrEqual(0);
123
- });
124
- it("Arbitrum USDC balance query succeeds", async () => {
125
- const balance = await getEvmUsdcBalance("arbitrum", evmAddress);
126
- expect(typeof balance).toBe("number");
127
- expect(balance).toBeGreaterThanOrEqual(0);
128
- });
129
- it("Base USDC balance query succeeds", async () => {
130
- const balance = await getEvmUsdcBalance("base", evmAddress);
131
- expect(typeof balance).toBe("number");
132
- expect(balance).toBeGreaterThanOrEqual(0);
133
- });
134
- it("checkBridgeBalance returns consistent shape", async () => {
135
- const result = await checkBridgeBalance("solana", solanaAddress, 1);
136
- expect(typeof result.balance).toBe("number");
137
- expect(typeof result.sufficient).toBe("boolean");
138
- expect(result.sufficient).toBe(result.balance >= 1);
139
- });
140
- it("unsupported chain balance throws", async () => {
141
- await expect(getEvmUsdcBalance("fakenet", evmAddress)).rejects.toThrow();
142
- });
143
- });
144
- // ══════════════════════════════════════════════════════════
145
- // 3b. Native Gas Balance Checks
146
- // ══════════════════════════════════════════════════════════
147
- describe("3b. Native gas balance checks", () => {
148
- it("Solana SOL balance query succeeds", async () => {
149
- const balance = await getNativeGasBalance("solana", solanaAddress);
150
- expect(typeof balance).toBe("number");
151
- expect(balance).toBeGreaterThanOrEqual(0);
152
- });
153
- it("Arbitrum ETH balance query succeeds", async () => {
154
- const balance = await getNativeGasBalance("arbitrum", evmAddress);
155
- expect(typeof balance).toBe("number");
156
- expect(balance).toBeGreaterThanOrEqual(0);
157
- });
158
- it("Base ETH balance query succeeds", async () => {
159
- const balance = await getNativeGasBalance("base", evmAddress);
160
- expect(typeof balance).toBe("number");
161
- expect(balance).toBeGreaterThanOrEqual(0);
162
- });
163
- it("unsupported chain gas balance throws", async () => {
164
- await expect(getNativeGasBalance("fakenet", evmAddress)).rejects.toThrow();
165
- });
166
- it("checkBridgeGasBalance: src-only check (fast mode)", async () => {
167
- const result = await checkBridgeGasBalance("arbitrum", evmAddress, "base", evmAddress, false);
168
- expect(typeof result.ok).toBe("boolean");
169
- expect(Array.isArray(result.errors)).toBe(true);
170
- });
171
- it("checkBridgeGasBalance: src+dst check (standard mode)", async () => {
172
- const result = await checkBridgeGasBalance("arbitrum", evmAddress, "base", evmAddress, true);
173
- expect(typeof result.ok).toBe("boolean");
174
- expect(Array.isArray(result.errors)).toBe(true);
175
- });
176
- it("checkBridgeGasBalance: returns errors array for insufficient gas", async () => {
177
- // Use an address unlikely to have gas on both chains
178
- const emptyAddr = "0x000000000000000000000000000000000000dEaD";
179
- const result = await checkBridgeGasBalance("arbitrum", emptyAddr, "base", emptyAddr, true);
180
- // Shape is correct regardless of balance
181
- expect(typeof result.ok).toBe("boolean");
182
- expect(Array.isArray(result.errors)).toBe(true);
183
- if (!result.ok) {
184
- expect(result.errors[0]).toMatch(/arbitrum|base/);
185
- }
186
- });
187
- });
188
- // ══════════════════════════════════════════════════════════
189
- // 4. CCTP Quotes — Standard Routes
190
- // ══════════════════════════════════════════════════════════
191
- describe("4. CCTP standard quotes", () => {
192
- const standardRoutes = [
193
- ["solana", "arbitrum"],
194
- ["solana", "base"],
195
- ["arbitrum", "base"],
196
- ["arbitrum", "solana"],
197
- ["base", "arbitrum"],
198
- ["base", "solana"],
199
- ];
200
- for (const [src, dst] of standardRoutes) {
201
- it(`${src} → ${dst}: valid CCTP quote`, async () => {
202
- const quote = await getCctpQuote(src, dst, 100);
203
- expect(quote.provider).toBe("cctp");
204
- expect(quote.srcChain).toBe(src);
205
- expect(quote.dstChain).toBe(dst);
206
- expect(quote.amountIn).toBe(100);
207
- expect(quote.amountOut).toBeGreaterThan(0);
208
- expect(quote.amountOut).toBeLessThanOrEqual(100);
209
- expect(quote.fee).toBeGreaterThanOrEqual(0);
210
- expect(quote.fee).toBeLessThan(5); // max $5 relay fee
211
- expect(quote.estimatedTime).toBeGreaterThan(0);
212
- expect(Math.abs(quote.fee - (quote.amountIn - quote.amountOut))).toBeLessThan(0.001);
213
- expect(quote.raw).toBeDefined();
214
- });
215
- }
216
- it("large amount ($100k) quote is valid", async () => {
217
- const quote = await getCctpQuote("arbitrum", "base", 100000);
218
- expect(quote.amountOut).toBeGreaterThan(99900); // fee should be tiny relative to amount
219
- expect(quote.fee).toBeLessThan(5);
220
- });
221
- it("tiny amount ($0.01) quote is valid", async () => {
222
- const quote = await getCctpQuote("arbitrum", "base", 0.01);
223
- expect(quote.amountOut).toBeGreaterThan(-1);
224
- expect(quote.fee).toBeLessThan(1);
225
- });
226
- });
227
- // ══════════════════════════════════════════════════════════
228
- // 5. CCTP Quotes — HyperCore Routes
229
- // ══════════════════════════════════════════════════════════
230
- describe("5. HyperCore CCTP quotes", () => {
231
- it("solana → hyperliquid: valid HyperCore deposit quote", async () => {
232
- const quote = await getCctpQuote("solana", "hyperliquid", 500);
233
- expect(quote.provider).toBe("cctp");
234
- expect(quote.srcChain).toBe("solana");
235
- expect(quote.dstChain).toBe("hyperliquid");
236
- expect(quote.amountIn).toBe(500);
237
- expect(quote.amountOut).toBeGreaterThan(0);
238
- expect(quote.amountOut).toBeLessThan(500);
239
- expect(quote.fee).toBeGreaterThan(0);
240
- expect(quote.fee).toBeLessThan(5); // should be ~$0.25 (1bp + forwarding)
241
- expect(quote.estimatedTime).toBe(65); // Solana source
242
- expect(quote.gasIncluded).toBe(true);
243
- expect(quote.raw).toHaveProperty("type", "cctp-hypercore");
244
- expect(quote.raw).toHaveProperty("maxFee");
245
- });
246
- it("arbitrum → hyperliquid: valid HyperCore deposit quote", async () => {
247
- const quote = await getCctpQuote("arbitrum", "hyperliquid", 1000);
248
- expect(quote.provider).toBe("cctp");
249
- expect(quote.srcChain).toBe("arbitrum");
250
- expect(quote.dstChain).toBe("hyperliquid");
251
- expect(quote.amountIn).toBe(1000);
252
- expect(quote.amountOut).toBeGreaterThan(0);
253
- expect(quote.fee).toBeGreaterThan(0);
254
- expect(quote.fee).toBeLessThan(5);
255
- expect(quote.estimatedTime).toBe(60); // EVM-to-EVM fast
256
- expect(quote.gasIncluded).toBe(true);
257
- expect(quote.raw).toHaveProperty("type", "cctp-hypercore");
258
- });
259
- it("base → hyperliquid: valid HyperCore deposit quote", async () => {
260
- const quote = await getCctpQuote("base", "hyperliquid", 200);
261
- expect(quote.provider).toBe("cctp");
262
- expect(quote.dstChain).toBe("hyperliquid");
263
- expect(quote.amountOut).toBeGreaterThan(0);
264
- expect(quote.fee).toBeLessThan(5);
265
- expect(quote.estimatedTime).toBe(60);
266
- });
267
- it("hyperliquid → arbitrum: valid HyperCore withdrawal quote", async () => {
268
- const quote = await getCctpQuote("hyperliquid", "arbitrum", 300);
269
- expect(quote.provider).toBe("cctp");
270
- expect(quote.srcChain).toBe("hyperliquid");
271
- expect(quote.dstChain).toBe("arbitrum");
272
- expect(quote.amountIn).toBe(300);
273
- expect(quote.amountOut).toBe(299.80); // $0.20 forwarding fee
274
- expect(quote.fee).toBe(0.20);
275
- expect(quote.estimatedTime).toBe(60);
276
- expect(quote.gasIncluded).toBe(true);
277
- expect(quote.raw).toHaveProperty("type", "cctp-hypercore-withdraw");
278
- });
279
- it("hyperliquid → solana: valid HyperCore withdrawal quote", async () => {
280
- const quote = await getCctpQuote("hyperliquid", "solana", 100);
281
- expect(quote.provider).toBe("cctp");
282
- expect(quote.srcChain).toBe("hyperliquid");
283
- expect(quote.dstChain).toBe("solana");
284
- expect(quote.fee).toBe(0.20);
285
- expect(quote.amountOut).toBe(99.80);
286
- });
287
- it("HyperCore fees scale correctly with amount", async () => {
288
- const small = await getCctpQuote("arbitrum", "hyperliquid", 10);
289
- const large = await getCctpQuote("arbitrum", "hyperliquid", 10000);
290
- // Protocol fee is 1bp, so large amount pays more
291
- expect(large.fee).toBeGreaterThan(small.fee);
292
- // But fee percentage should be similar (both ~1bp + flat forwarding)
293
- const smallPct = small.fee / small.amountIn;
294
- const largePct = large.fee / large.amountIn;
295
- expect(smallPct).toBeGreaterThan(largePct); // smaller amounts pay higher % (flat fee dominates)
296
- });
297
- });
298
- // ══════════════════════════════════════════════════════════
299
- // 6. HyperCore Fees API Direct Validation
300
- // ══════════════════════════════════════════════════════════
301
- describe("6. HyperCore fees API", () => {
302
- const FEES_API = "https://iris-api.circle.com/v2/burn/USDC/fees";
303
- it("Solana (domain 5) → HyperCore (domain 19) fees API responds", async () => {
304
- const res = await fetch(`${FEES_API}/5/19?forward=true&hyperCoreDeposit=true`);
305
- expect(res.ok).toBe(true);
306
- const data = await res.json();
307
- expect(Array.isArray(data)).toBe(true);
308
- expect(data.length).toBeGreaterThan(0);
309
- const schedule = data[0];
310
- expect(schedule).toHaveProperty("finalityThreshold");
311
- expect(schedule).toHaveProperty("minimumFee");
312
- });
313
- it("Arbitrum (domain 3) → HyperCore (domain 19) fees API responds", async () => {
314
- const res = await fetch(`${FEES_API}/3/19?forward=true&hyperCoreDeposit=true`);
315
- expect(res.ok).toBe(true);
316
- const data = await res.json();
317
- expect(Array.isArray(data)).toBe(true);
318
- expect(data.length).toBeGreaterThan(0);
319
- });
320
- it("Base (domain 6) → HyperCore (domain 19) fees API responds", async () => {
321
- const res = await fetch(`${FEES_API}/6/19?forward=true&hyperCoreDeposit=true`);
322
- expect(res.ok).toBe(true);
323
- const data = await res.json();
324
- expect(Array.isArray(data)).toBe(true);
325
- });
326
- it("fees API returns forward fee structure", async () => {
327
- const res = await fetch(`${FEES_API}/3/19?forward=true&hyperCoreDeposit=true`);
328
- const data = await res.json();
329
- // Look for schedule with forwardFee
330
- const hasForwardFee = data.some(s => s.forwardFee !== undefined && s.forwardFee !== null);
331
- // At minimum, all schedules should have finalityThreshold and minimumFee
332
- for (const schedule of data) {
333
- expect(typeof schedule.finalityThreshold).toBe("number");
334
- expect(typeof schedule.minimumFee).toBe("number");
335
- }
336
- // Forward fee may or may not be present depending on API version
337
- if (hasForwardFee) {
338
- const withFee = data.find(s => s.forwardFee !== undefined);
339
- const ff = withFee.forwardFee;
340
- expect(typeof ff.low).toBe("number");
341
- expect(typeof ff.med).toBe("number");
342
- expect(typeof ff.high).toBe("number");
343
- expect(ff.med).toBeGreaterThanOrEqual(ff.low);
344
- expect(ff.high).toBeGreaterThanOrEqual(ff.med);
345
- }
346
- });
347
- });
348
- // ══════════════════════════════════════════════════════════
349
- // 7. deBridge DLN Quotes (with real addresses)
350
- // ══════════════════════════════════════════════════════════
351
- describe("7. deBridge quotes with real addresses", () => {
352
- it("solana → arbitrum: quote with real wallet addresses", async () => {
353
- const quote = await getDebridgeQuote("solana", "arbitrum", 100, solanaAddress, evmAddress);
354
- expect(quote.provider).toBe("debridge");
355
- expect(quote.srcChain).toBe("solana");
356
- expect(quote.dstChain).toBe("arbitrum");
357
- expect(quote.amountIn).toBe(100);
358
- expect(quote.amountOut).toBeGreaterThan(0);
359
- expect(quote.amountOut).toBeLessThanOrEqual(100);
360
- expect(quote.fee).toBeGreaterThanOrEqual(0);
361
- expect(quote.estimatedTime).toBeGreaterThan(0);
362
- expect(Math.abs(quote.fee - (quote.amountIn - quote.amountOut))).toBeLessThan(0.001);
363
- });
364
- it("arbitrum → base: EVM-to-EVM with real addresses", async () => {
365
- await wait(1500);
366
- const quote = await getDebridgeQuote("arbitrum", "base", 200, evmAddress, evmAddress);
367
- expect(quote.provider).toBe("debridge");
368
- expect(quote.amountOut).toBeGreaterThan(0);
369
- expect(quote.amountOut).toBeLessThanOrEqual(200);
370
- });
371
- it("base → solana: reverse route with real addresses", async () => {
372
- await wait(1500);
373
- const quote = await getDebridgeQuote("base", "solana", 50, evmAddress, solanaAddress);
374
- expect(quote.provider).toBe("debridge");
375
- expect(quote.amountOut).toBeGreaterThan(0);
376
- });
377
- it("unsupported chain throws", async () => {
378
- await expect(getDebridgeQuote("hyperliquid", "arbitrum", 100, evmAddress, evmAddress)).rejects.toThrow(/Unsupported chain/i);
379
- });
380
- });
381
- // ══════════════════════════════════════════════════════════
382
- // 8. Relay Quotes (with real addresses)
383
- // ══════════════════════════════════════════════════════════
384
- describe("8. Relay quotes with real addresses", () => {
385
- it("solana → arbitrum: Relay quote with real addresses", async () => {
386
- const quote = await getRelayQuote("solana", "arbitrum", 100, solanaAddress, evmAddress);
387
- expect(quote.provider).toBe("relay");
388
- expect(quote.srcChain).toBe("solana");
389
- expect(quote.dstChain).toBe("arbitrum");
390
- expect(quote.amountIn).toBe(100);
391
- expect(quote.amountOut).toBeGreaterThan(0);
392
- expect(quote.amountOut).toBeLessThanOrEqual(100);
393
- expect(quote.estimatedTime).toBeGreaterThan(0);
394
- });
395
- it("arbitrum → base: EVM-to-EVM Relay quote", async () => {
396
- await wait(1000);
397
- const quote = await getRelayQuote("arbitrum", "base", 500, evmAddress, evmAddress);
398
- expect(quote.provider).toBe("relay");
399
- expect(quote.amountOut).toBeGreaterThan(0);
400
- });
401
- it("base → solana: reverse Relay quote", async () => {
402
- await wait(1000);
403
- const quote = await getRelayQuote("base", "solana", 200, evmAddress, solanaAddress);
404
- expect(quote.provider).toBe("relay");
405
- expect(quote.amountOut).toBeGreaterThan(0);
406
- });
407
- });
408
- // ══════════════════════════════════════════════════════════
409
- // 9. getAllQuotes Aggregation
410
- // ══════════════════════════════════════════════════════════
411
- describe("9. getAllQuotes aggregation", () => {
412
- it("arbitrum → base: returns multiple providers sorted by amountOut", async () => {
413
- await wait(2000);
414
- const quotes = await getAllQuotes("arbitrum", "base", 1000, evmAddress, evmAddress);
415
- expect(quotes.length).toBeGreaterThanOrEqual(1);
416
- // Should be sorted by amountOut descending (best first)
417
- for (let i = 1; i < quotes.length; i++) {
418
- expect(quotes[i - 1].amountOut).toBeGreaterThanOrEqual(quotes[i].amountOut);
419
- }
420
- // All quotes should have consistent shape
421
- for (const q of quotes) {
422
- expect(["cctp", "debridge", "relay"]).toContain(q.provider);
423
- expect(q.srcChain).toBe("arbitrum");
424
- expect(q.dstChain).toBe("base");
425
- expect(q.amountIn).toBe(1000);
426
- expect(q.amountOut).toBeGreaterThan(0);
427
- expect(typeof q.fee).toBe("number");
428
- expect(typeof q.estimatedTime).toBe("number");
429
- }
430
- // CCTP should be present (it's always available for supported routes)
431
- const cctpQuote = quotes.find(q => q.provider === "cctp");
432
- expect(cctpQuote).toBeDefined();
433
- });
434
- it("solana → arbitrum: includes CCTP + at least one other provider", async () => {
435
- await wait(2000);
436
- const quotes = await getAllQuotes("solana", "arbitrum", 200, solanaAddress, evmAddress);
437
- expect(quotes.length).toBeGreaterThanOrEqual(2);
438
- const providers = new Set(quotes.map(q => q.provider));
439
- expect(providers.has("cctp")).toBe(true);
440
- // Should have debridge or relay too
441
- expect(providers.size).toBeGreaterThanOrEqual(2);
442
- });
443
- });
444
- // ══════════════════════════════════════════════════════════
445
- // 10. getBestQuote Selection
446
- // ══════════════════════════════════════════════════════════
447
- describe("10. getBestQuote selection", () => {
448
- it("best quote is cheapest provider for standard chains", async () => {
449
- const quote = await getBestQuote("arbitrum", "base", 500, evmAddress, evmAddress);
450
- // Best quote should be either CCTP or Relay (both valid, sorted by amountOut)
451
- expect(["cctp", "relay"]).toContain(quote.provider);
452
- expect(quote.amountOut).toBeGreaterThanOrEqual(498);
453
- expect(quote.fee).toBeLessThan(2);
454
- });
455
- it("solana → arbitrum: CCTP preferred", async () => {
456
- const quote = await getBestQuote("solana", "arbitrum", 1000, solanaAddress, evmAddress);
457
- expect(quote.provider).toBe("cctp");
458
- expect(quote.fee).toBeLessThan(2);
459
- });
460
- it("quote amountIn - amountOut ≈ fee", async () => {
461
- const quote = await getBestQuote("base", "solana", 100, evmAddress, solanaAddress);
462
- const calculatedFee = quote.amountIn - quote.amountOut;
463
- expect(Math.abs(calculatedFee - quote.fee)).toBeLessThan(0.01);
464
- });
465
- });
466
- // ══════════════════════════════════════════════════════════
467
- // 11. Cross-Provider Comparison
468
- // ══════════════════════════════════════════════════════════
469
- describe("11. Cross-provider fee comparison", () => {
470
- it("CCTP is cheaper than deBridge for standard routes", async () => {
471
- await wait(2000);
472
- const cctp = await getCctpQuote("arbitrum", "base", 1000);
473
- const debridge = await getDebridgeQuote("arbitrum", "base", 1000, evmAddress, evmAddress);
474
- expect(cctp.fee).toBeLessThanOrEqual(debridge.fee);
475
- expect(cctp.amountOut).toBeGreaterThanOrEqual(debridge.amountOut);
476
- });
477
- it("HyperCore CCTP fee is reasonable ($0.20-$2 range)", async () => {
478
- const quote = await getCctpQuote("arbitrum", "hyperliquid", 1000);
479
- expect(quote.fee).toBeGreaterThanOrEqual(0.10);
480
- expect(quote.fee).toBeLessThan(3);
481
- });
482
- it("HyperCore withdrawal fee is fixed at $0.20", async () => {
483
- const q100 = await getCctpQuote("hyperliquid", "arbitrum", 100);
484
- const q10000 = await getCctpQuote("hyperliquid", "arbitrum", 10000);
485
- expect(q100.fee).toBe(0.20);
486
- expect(q10000.fee).toBe(0.20);
487
- });
488
- });
489
- // ══════════════════════════════════════════════════════════
490
- // 12. Error Handling & Edge Cases
491
- // ══════════════════════════════════════════════════════════
492
- describe("12. Error handling", () => {
493
- it("CCTP quote for unsupported chain throws", async () => {
494
- await expect(getCctpQuote("polygon", "arbitrum", 100)).rejects.toThrow(/not supported/i);
495
- });
496
- it("deBridge zero amount throws", async () => {
497
- await wait(1500);
498
- await expect(getDebridgeQuote("solana", "arbitrum", 0, solanaAddress, evmAddress)).rejects.toThrow();
499
- });
500
- it("deBridge negative amount throws", async () => {
501
- await wait(1500);
502
- await expect(getDebridgeQuote("arbitrum", "base", -100, evmAddress, evmAddress)).rejects.toThrow();
503
- });
504
- it("CCTP same-chain quote still valid", async () => {
505
- const quote = await getCctpQuote("arbitrum", "arbitrum", 100);
506
- expect(quote.provider).toBe("cctp");
507
- expect(quote.amountOut).toBeGreaterThan(0);
508
- });
509
- it("all quotes return gasIncluded field", async () => {
510
- const cctp = await getCctpQuote("arbitrum", "base", 100);
511
- expect(typeof cctp.gasIncluded).toBe("boolean");
512
- const hypercore = await getCctpQuote("arbitrum", "hyperliquid", 100);
513
- expect(hypercore.gasIncluded).toBe(true);
514
- });
515
- });
516
- // ══════════════════════════════════════════════════════════
517
- // 13. HyperCore CctpForwarder Contract Verification
518
- // ══════════════════════════════════════════════════════════
519
- describe("13. HyperCore on-chain verification", () => {
520
- const HYPERCORE_FORWARDER = "0xb21D281DEdb17AE5B501F6AA8256fe38C4e45757";
521
- const HYPERCORE_RPC = "https://rpc.hyperliquid.xyz/evm";
522
- it("CctpForwarder contract exists on HyperEVM", async () => {
523
- const res = await fetch(HYPERCORE_RPC, {
524
- method: "POST",
525
- headers: { "Content-Type": "application/json" },
526
- body: JSON.stringify({
527
- jsonrpc: "2.0", id: 1,
528
- method: "eth_getCode",
529
- params: [HYPERCORE_FORWARDER, "latest"],
530
- }),
531
- });
532
- const json = await res.json();
533
- // Contract should have code (not "0x")
534
- expect(json.result).toBeDefined();
535
- expect(json.result.length).toBeGreaterThan(2);
536
- expect(json.result).not.toBe("0x");
537
- });
538
- it("CctpExtension contract exists on Arbitrum", async () => {
539
- const CCTP_EXTENSION = "0xA95d9c1F655341597C94393fDdc30cf3c08E4fcE";
540
- const res = await fetch("https://arb1.arbitrum.io/rpc", {
541
- method: "POST",
542
- headers: { "Content-Type": "application/json" },
543
- body: JSON.stringify({
544
- jsonrpc: "2.0", id: 1,
545
- method: "eth_getCode",
546
- params: [CCTP_EXTENSION, "latest"],
547
- }),
548
- });
549
- const json = await res.json();
550
- expect(json.result.length).toBeGreaterThan(2);
551
- expect(json.result).not.toBe("0x");
552
- });
553
- it("HyperEVM RPC is reachable (eth_chainId)", async () => {
554
- const res = await fetch(HYPERCORE_RPC, {
555
- method: "POST",
556
- headers: { "Content-Type": "application/json" },
557
- body: JSON.stringify({
558
- jsonrpc: "2.0", id: 1,
559
- method: "eth_chainId",
560
- params: [],
561
- }),
562
- });
563
- const json = await res.json();
564
- expect(json.result).toBeDefined();
565
- // HyperEVM chain ID = 999 (0x3e7) or similar
566
- const chainId = parseInt(json.result, 16);
567
- expect(chainId).toBeGreaterThan(0);
568
- });
569
- });
570
- // ══════════════════════════════════════════════════════════
571
- // 14. CCTP V2 Iris Attestation API
572
- // ══════════════════════════════════════════════════════════
573
- describe("14. CCTP V2 Iris attestation API", () => {
574
- it("V2 relay fee API responds for all source domains", async () => {
575
- const routes = [
576
- [5, 3], // solana → arbitrum
577
- [3, 6], // arbitrum → base
578
- [6, 5], // base → solana
579
- [3, 19], // arbitrum → hyperliquid
580
- [5, 19], // solana → hyperliquid
581
- ];
582
- for (const [src, dst] of routes) {
583
- const res = await fetch(`https://iris-api.circle.com/v2/burn/USDC/fees/${src}/${dst}`);
584
- expect(res.ok).toBe(true);
585
- const data = await res.json();
586
- expect(Array.isArray(data)).toBe(true);
587
- }
588
- });
589
- });
590
- // ══════════════════════════════════════════════════════════
591
- // 15. SENDER-PAYS-ONLY — Core Invariant
592
- // ══════════════════════════════════════════════════════════
593
- //
594
- // The fundamental rule: ALL bridge costs (protocol fee, relay fee,
595
- // gas cost) are deducted from the sender's USDC amount.
596
- // The recipient receives amountOut without paying anything.
597
- //
598
- // This is verified by checking that every quote has:
599
- // - gasIncluded === true (auto-relay, no manual dst TX needed)
600
- // - fee === amountIn - amountOut (fee is deducted from sent amount)
601
- // - amountOut > 0 and amountOut <= amountIn
602
- //
603
- // ══════════════════════════════════════════════════════════
604
- describe("15. SENDER-PAYS-ONLY verification", () => {
605
- // All 4 chains: arbitrum, base, solana, hyperliquid
606
- // Standard routes (6 pairs among arb/base/sol)
607
- const standardRoutes = [
608
- ["arbitrum", "base"],
609
- ["arbitrum", "solana"],
610
- ["base", "arbitrum"],
611
- ["base", "solana"],
612
- ["solana", "arbitrum"],
613
- ["solana", "base"],
614
- ];
615
- // HyperCore routes (deposit into + withdrawal from)
616
- const hyperCoreDepositRoutes = [
617
- ["arbitrum", "hyperliquid"],
618
- ["base", "hyperliquid"],
619
- ["solana", "hyperliquid"],
620
- ];
621
- const hyperCoreWithdrawRoutes = [
622
- ["hyperliquid", "arbitrum"],
623
- ["hyperliquid", "base"],
624
- ["hyperliquid", "solana"],
625
- ];
626
- const ALL_ROUTES = [
627
- ...standardRoutes,
628
- ...hyperCoreDepositRoutes,
629
- ...hyperCoreWithdrawRoutes,
630
- ];
631
- for (const [src, dst] of ALL_ROUTES) {
632
- it(`${src} → ${dst}: fee deducted from sender's USDC`, async () => {
633
- const amount = 100;
634
- const quote = await getCctpQuote(src, dst, amount);
635
- // 1. Fee is correctly calculated
636
- expect(quote.fee).toBeGreaterThanOrEqual(0);
637
- expect(Math.abs(quote.fee - (quote.amountIn - quote.amountOut))).toBeLessThan(0.001);
638
- // 2. Recipient gets amountOut
639
- expect(quote.amountOut).toBeGreaterThan(0);
640
- expect(quote.amountOut).toBeLessThanOrEqual(amount);
641
- // 3. Gas note present
642
- expect(quote.gasNote).toBeDefined();
643
- expect(quote.gasNote.length).toBeGreaterThan(0);
644
- });
645
- }
646
- it("fast=true: all standard routes use auto-relay (gasIncluded=true)", async () => {
647
- for (const [src, dst] of standardRoutes) {
648
- const quote = await getCctpQuote(src, dst, 100, true);
649
- expect(quote.gasIncluded).toBe(true);
650
- const raw = quote.raw;
651
- expect(Number(raw.maxFee)).toBeGreaterThan(0);
652
- expect(raw.fast).toBe(true);
653
- }
654
- });
655
- it("fast=false (default): forwarding routes have gasIncluded=true, Solana dst has gasIncluded=false", async () => {
656
- for (const [src, dst] of standardRoutes) {
657
- const quote = await getCctpQuote(src, dst, 100);
658
- const raw = quote.raw;
659
- expect(raw.fast).toBe(false);
660
- // Forwarding available for all routes except →Solana
661
- if (dst === "solana") {
662
- expect(quote.gasIncluded).toBe(false);
663
- expect(raw.forwarding).toBe(false);
664
- }
665
- else {
666
- expect(quote.gasIncluded).toBe(true);
667
- expect(raw.forwarding).toBe(true);
668
- }
669
- }
670
- });
671
- it("fast=true fees are higher than standard fees", async () => {
672
- const standard = await getCctpQuote("arbitrum", "base", 1000, false);
673
- const fast = await getCctpQuote("arbitrum", "base", 1000, true);
674
- expect(fast.fee).toBeGreaterThan(standard.fee);
675
- expect(fast.estimatedTime).toBeLessThan(standard.estimatedTime);
676
- });
677
- it("HyperCore deposits: CctpForwarder auto-deposits (always gasIncluded)", async () => {
678
- for (const [src, dst] of hyperCoreDepositRoutes) {
679
- const quote = await getCctpQuote(src, dst, 100);
680
- expect(quote.gasIncluded).toBe(true);
681
- expect(quote.gasNote).toContain("CctpForwarder");
682
- const raw = quote.raw;
683
- expect(raw.type).toBe("cctp-hypercore");
684
- expect(Number(raw.maxFee)).toBeGreaterThan(0);
685
- }
686
- });
687
- it("HyperCore withdrawals: HL handles forwarding (always gasIncluded)", async () => {
688
- for (const [src, dst] of hyperCoreWithdrawRoutes) {
689
- const quote = await getCctpQuote(src, dst, 100);
690
- expect(quote.gasIncluded).toBe(true);
691
- expect(quote.gasNote).toContain("HyperCore");
692
- expect(quote.fee).toBe(0.20);
693
- const raw = quote.raw;
694
- expect(raw.type).toBe("cctp-hypercore-withdraw");
695
- }
696
- });
697
- it("deBridge quotes include gas in fee (sender-pays)", async () => {
698
- await wait(2000);
699
- const quote = await getDebridgeQuote("arbitrum", "base", 100, evmAddress, evmAddress);
700
- expect(quote.fee).toBeGreaterThan(0);
701
- expect(Math.abs(quote.fee - (quote.amountIn - quote.amountOut))).toBeLessThan(0.001);
702
- expect(quote.gasIncluded).toBe(true);
703
- });
704
- it("Relay quotes include gas in fee (solver pays dst gas)", async () => {
705
- await wait(1000);
706
- const quote = await getRelayQuote("arbitrum", "base", 100, evmAddress, evmAddress);
707
- expect(quote.fee).toBeGreaterThanOrEqual(0);
708
- expect(Math.abs(quote.fee - (quote.amountIn - quote.amountOut))).toBeLessThan(0.001);
709
- expect(quote.gasIncluded).toBe(true);
710
- });
711
- it("getAllQuotes: fee math is correct for every provider", async () => {
712
- await wait(2000);
713
- const quotes = await getAllQuotes("arbitrum", "base", 500, evmAddress, evmAddress);
714
- for (const q of quotes) {
715
- expect(q.fee).toBeGreaterThanOrEqual(0);
716
- expect(Math.abs(q.fee - (q.amountIn - q.amountOut))).toBeLessThan(0.01);
717
- }
718
- });
719
- });
720
- // ══════════════════════════════════════════════════════════
721
- // 16. Complete 4-Chain Route Matrix
722
- // ══════════════════════════════════════════════════════════
723
- //
724
- // Verify every possible directional pair among:
725
- // arbitrum, base, solana, hyperliquid (12 pairs total)
726
- //
727
- // For each: valid quote, correct fee math, gasIncluded
728
- // ══════════════════════════════════════════════════════════
729
- describe("16. Complete 4-chain route matrix", () => {
730
- const chains = ["arbitrum", "base", "solana", "hyperliquid"];
731
- const amount = 250;
732
- for (const src of chains) {
733
- for (const dst of chains) {
734
- if (src === dst)
735
- continue;
736
- it(`[MATRIX] ${src} → ${dst}: valid quote with sender-pays`, async () => {
737
- const quote = await getCctpQuote(src, dst, amount);
738
- // Basic shape
739
- expect(quote.provider).toBe("cctp");
740
- expect(quote.srcChain).toBe(src);
741
- expect(quote.dstChain).toBe(dst);
742
- expect(quote.amountIn).toBe(amount);
743
- expect(quote.amountOut).toBeGreaterThan(0);
744
- expect(quote.amountOut).toBeLessThanOrEqual(amount);
745
- // Fee invariant: fee = amountIn - amountOut
746
- expect(Math.abs(quote.fee - (amount - quote.amountOut))).toBeLessThan(0.001);
747
- // gasIncluded: true for HyperCore, forwarding (EVM dst), and fast
748
- // Only →Solana in standard mode has gasIncluded=false (no forwarding)
749
- if (dst === "solana" && src !== "hyperliquid") {
750
- expect(quote.gasIncluded).toBe(false);
751
- }
752
- else {
753
- expect(quote.gasIncluded).toBe(true);
754
- }
755
- // Reasonable fee (< $5 for $250)
756
- expect(quote.fee).toBeLessThan(5);
757
- // Time estimate is positive
758
- expect(quote.estimatedTime).toBeGreaterThan(0);
759
- });
760
- }
761
- }
762
- it("[MATRIX] fee comparison: HyperCore deposit > standard CCTP > HyperCore withdrawal", async () => {
763
- const standardFee = (await getCctpQuote("arbitrum", "base", 1000)).fee;
764
- const depositFee = (await getCctpQuote("arbitrum", "hyperliquid", 1000)).fee;
765
- const withdrawFee = (await getCctpQuote("hyperliquid", "arbitrum", 1000)).fee;
766
- // HyperCore deposit has protocol + forwarding fee (> standard)
767
- expect(depositFee).toBeGreaterThanOrEqual(standardFee);
768
- // HyperCore withdrawal is fixed $0.20
769
- expect(withdrawFee).toBe(0.20);
770
- });
771
- it("[MATRIX] ETA comparison: fast mode Solana routes are slower than fast EVM-only", async () => {
772
- const evmToEvmFast = (await getCctpQuote("arbitrum", "base", 100, true)).estimatedTime;
773
- const solToEvmFast = (await getCctpQuote("solana", "arbitrum", 100, true)).estimatedTime;
774
- // Fast finality: Solana ~90s, EVM-only ~60s
775
- expect(solToEvmFast).toBeGreaterThanOrEqual(evmToEvmFast);
776
- // Standard finality: both positive
777
- const evmToEvmStd = (await getCctpQuote("arbitrum", "base", 100)).estimatedTime;
778
- const solToEvmStd = (await getCctpQuote("solana", "arbitrum", 100)).estimatedTime;
779
- expect(evmToEvmStd).toBeGreaterThan(0);
780
- expect(solToEvmStd).toBeGreaterThan(0);
781
- });
782
- it("[MATRIX] all standard relay fee APIs respond for 4-chain matrix", async () => {
783
- // Verify Circle's fee API works for every domain pair we support
784
- const domains = [
785
- { chain: "arbitrum", domain: 3 },
786
- { chain: "base", domain: 6 },
787
- { chain: "solana", domain: 5 },
788
- { chain: "hyperliquid", domain: 19 },
789
- ];
790
- const results = [];
791
- for (const src of domains) {
792
- for (const dst of domains) {
793
- if (src.chain === dst.chain)
794
- continue;
795
- const url = dst.domain === 19
796
- ? `https://iris-api.circle.com/v2/burn/USDC/fees/${src.domain}/${dst.domain}?forward=true&hyperCoreDeposit=true`
797
- : `https://iris-api.circle.com/v2/burn/USDC/fees/${src.domain}/${dst.domain}`;
798
- const res = await fetch(url);
799
- if (res.ok) {
800
- results.push(`${src.chain}→${dst.chain}: OK`);
801
- }
802
- else {
803
- results.push(`${src.chain}→${dst.chain}: ${res.status}`);
804
- }
805
- }
806
- }
807
- // At minimum, all standard routes should work
808
- const okCount = results.filter(r => r.includes("OK")).length;
809
- expect(okCount).toBeGreaterThanOrEqual(6); // at least standard 6 pairs
810
- });
811
- });
812
- });