perp-cli 0.3.3

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 (325) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -0
  3. package/dist/__tests__/alert-logic.test.d.ts +1 -0
  4. package/dist/__tests__/alert-logic.test.js +107 -0
  5. package/dist/__tests__/arb-auto-3dex.test.d.ts +1 -0
  6. package/dist/__tests__/arb-auto-3dex.test.js +397 -0
  7. package/dist/__tests__/arb-history-stats.test.d.ts +1 -0
  8. package/dist/__tests__/arb-history-stats.test.js +176 -0
  9. package/dist/__tests__/arb-logic.test.d.ts +1 -0
  10. package/dist/__tests__/arb-logic.test.js +84 -0
  11. package/dist/__tests__/arb-manage.test.d.ts +1 -0
  12. package/dist/__tests__/arb-manage.test.js +253 -0
  13. package/dist/__tests__/arb-new-features.test.d.ts +1 -0
  14. package/dist/__tests__/arb-new-features.test.js +457 -0
  15. package/dist/__tests__/arb-sizing.test.d.ts +1 -0
  16. package/dist/__tests__/arb-sizing.test.js +48 -0
  17. package/dist/__tests__/arb-state.test.d.ts +1 -0
  18. package/dist/__tests__/arb-state.test.js +284 -0
  19. package/dist/__tests__/arb-userflow.test.d.ts +1 -0
  20. package/dist/__tests__/arb-userflow.test.js +945 -0
  21. package/dist/__tests__/arb-utils.test.d.ts +1 -0
  22. package/dist/__tests__/arb-utils.test.js +264 -0
  23. package/dist/__tests__/bot-conditions.test.d.ts +1 -0
  24. package/dist/__tests__/bot-conditions.test.js +341 -0
  25. package/dist/__tests__/client-id-tracker.test.d.ts +1 -0
  26. package/dist/__tests__/client-id-tracker.test.js +137 -0
  27. package/dist/__tests__/commands/new-atomic-commands.test.d.ts +1 -0
  28. package/dist/__tests__/commands/new-atomic-commands.test.js +502 -0
  29. package/dist/__tests__/commands/order-intent.test.d.ts +1 -0
  30. package/dist/__tests__/commands/order-intent.test.js +600 -0
  31. package/dist/__tests__/commands/trade-commands.test.d.ts +1 -0
  32. package/dist/__tests__/commands/trade-commands.test.js +821 -0
  33. package/dist/__tests__/config.test.d.ts +1 -0
  34. package/dist/__tests__/config.test.js +86 -0
  35. package/dist/__tests__/cross-chain-margin.test.d.ts +1 -0
  36. package/dist/__tests__/cross-chain-margin.test.js +287 -0
  37. package/dist/__tests__/dex-asset-map.test.d.ts +1 -0
  38. package/dist/__tests__/dex-asset-map.test.js +191 -0
  39. package/dist/__tests__/errors.test.d.ts +1 -0
  40. package/dist/__tests__/errors.test.js +110 -0
  41. package/dist/__tests__/event-stream.test.d.ts +1 -0
  42. package/dist/__tests__/event-stream.test.js +276 -0
  43. package/dist/__tests__/exchanges/interface.test.d.ts +1 -0
  44. package/dist/__tests__/exchanges/interface.test.js +132 -0
  45. package/dist/__tests__/exchanges/mock-adapter.d.ts +69 -0
  46. package/dist/__tests__/exchanges/mock-adapter.js +137 -0
  47. package/dist/__tests__/execution-log.test.d.ts +1 -0
  48. package/dist/__tests__/execution-log.test.js +106 -0
  49. package/dist/__tests__/funding-calc.test.d.ts +1 -0
  50. package/dist/__tests__/funding-calc.test.js +71 -0
  51. package/dist/__tests__/funding-history.test.d.ts +1 -0
  52. package/dist/__tests__/funding-history.test.js +343 -0
  53. package/dist/__tests__/funding-rates.test.d.ts +1 -0
  54. package/dist/__tests__/funding-rates.test.js +342 -0
  55. package/dist/__tests__/funding.test.d.ts +1 -0
  56. package/dist/__tests__/funding.test.js +173 -0
  57. package/dist/__tests__/gap-logic.test.d.ts +1 -0
  58. package/dist/__tests__/gap-logic.test.js +43 -0
  59. package/dist/__tests__/hip3-dex.test.d.ts +1 -0
  60. package/dist/__tests__/hip3-dex.test.js +234 -0
  61. package/dist/__tests__/integration/agent-features.integration.test.d.ts +1 -0
  62. package/dist/__tests__/integration/agent-features.integration.test.js +553 -0
  63. package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +13 -0
  64. package/dist/__tests__/integration/atomic-commands.integration.test.js +246 -0
  65. package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +1 -0
  66. package/dist/__tests__/integration/bridge-simulation.integration.test.js +453 -0
  67. package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +1 -0
  68. package/dist/__tests__/integration/bridge-strict.integration.test.js +812 -0
  69. package/dist/__tests__/integration/bridge.integration.test.d.ts +1 -0
  70. package/dist/__tests__/integration/bridge.integration.test.js +309 -0
  71. package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +1 -0
  72. package/dist/__tests__/integration/cli-e2e.integration.test.js +202 -0
  73. package/dist/__tests__/integration/dex-arb.integration.test.d.ts +1 -0
  74. package/dist/__tests__/integration/dex-arb.integration.test.js +116 -0
  75. package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +13 -0
  76. package/dist/__tests__/integration/envelope-consistency.integration.test.js +205 -0
  77. package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +1 -0
  78. package/dist/__tests__/integration/hip3-dex.integration.test.js +147 -0
  79. package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +1 -0
  80. package/dist/__tests__/integration/hyperliquid.integration.test.js +79 -0
  81. package/dist/__tests__/integration/lighter.integration.test.d.ts +1 -0
  82. package/dist/__tests__/integration/lighter.integration.test.js +53 -0
  83. package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +9 -0
  84. package/dist/__tests__/integration/new-commands-e2e.integration.test.js +236 -0
  85. package/dist/__tests__/integration/order-verification.integration.test.d.ts +1 -0
  86. package/dist/__tests__/integration/order-verification.integration.test.js +321 -0
  87. package/dist/__tests__/integration/pacifica.integration.test.d.ts +1 -0
  88. package/dist/__tests__/integration/pacifica.integration.test.js +75 -0
  89. package/dist/__tests__/integration/response-shapes.integration.test.d.ts +1 -0
  90. package/dist/__tests__/integration/response-shapes.integration.test.js +278 -0
  91. package/dist/__tests__/liquidity.test.d.ts +1 -0
  92. package/dist/__tests__/liquidity.test.js +225 -0
  93. package/dist/__tests__/plan-executor.test.d.ts +1 -0
  94. package/dist/__tests__/plan-executor.test.js +314 -0
  95. package/dist/__tests__/position-history.test.d.ts +1 -0
  96. package/dist/__tests__/position-history.test.js +367 -0
  97. package/dist/__tests__/retry.test.d.ts +1 -0
  98. package/dist/__tests__/retry.test.js +310 -0
  99. package/dist/__tests__/risk-assessment.test.d.ts +1 -0
  100. package/dist/__tests__/risk-assessment.test.js +145 -0
  101. package/dist/__tests__/security-adversarial.test.d.ts +1 -0
  102. package/dist/__tests__/security-adversarial.test.js +574 -0
  103. package/dist/__tests__/strategies.test.d.ts +1 -0
  104. package/dist/__tests__/strategies.test.js +539 -0
  105. package/dist/__tests__/trade-execution.test.d.ts +1 -0
  106. package/dist/__tests__/trade-execution.test.js +129 -0
  107. package/dist/__tests__/trade-validator.test.d.ts +1 -0
  108. package/dist/__tests__/trade-validator.test.js +655 -0
  109. package/dist/__tests__/utils.test.d.ts +1 -0
  110. package/dist/__tests__/utils.test.js +76 -0
  111. package/dist/api/public/hyperliquid.d.ts +18 -0
  112. package/dist/api/public/hyperliquid.js +82 -0
  113. package/dist/api/public/index.d.ts +8 -0
  114. package/dist/api/public/index.js +8 -0
  115. package/dist/api/public/lighter.d.ts +24 -0
  116. package/dist/api/public/lighter.js +100 -0
  117. package/dist/api/public/pacifica.d.ts +17 -0
  118. package/dist/api/public/pacifica.js +54 -0
  119. package/dist/api/public/urls.d.ts +12 -0
  120. package/dist/api/public/urls.js +33 -0
  121. package/dist/arb/history-stats.d.ts +44 -0
  122. package/dist/arb/history-stats.js +135 -0
  123. package/dist/arb/index.d.ts +4 -0
  124. package/dist/arb/index.js +4 -0
  125. package/dist/arb/sizing.d.ts +23 -0
  126. package/dist/arb/sizing.js +96 -0
  127. package/dist/arb/state.d.ts +51 -0
  128. package/dist/arb/state.js +112 -0
  129. package/dist/arb/utils.d.ts +81 -0
  130. package/dist/arb/utils.js +267 -0
  131. package/dist/arb-history-stats.d.ts +5 -0
  132. package/dist/arb-history-stats.js +5 -0
  133. package/dist/arb-sizing.d.ts +5 -0
  134. package/dist/arb-sizing.js +5 -0
  135. package/dist/arb-state.d.ts +5 -0
  136. package/dist/arb-state.js +5 -0
  137. package/dist/arb-utils.d.ts +5 -0
  138. package/dist/arb-utils.js +5 -0
  139. package/dist/bot/conditions.d.ts +32 -0
  140. package/dist/bot/conditions.js +141 -0
  141. package/dist/bot/config.d.ts +76 -0
  142. package/dist/bot/config.js +160 -0
  143. package/dist/bot/engine.d.ts +8 -0
  144. package/dist/bot/engine.js +519 -0
  145. package/dist/bot/presets.d.ts +11 -0
  146. package/dist/bot/presets.js +296 -0
  147. package/dist/bridge-engine.d.ts +133 -0
  148. package/dist/bridge-engine.js +1487 -0
  149. package/dist/cache.d.ts +25 -0
  150. package/dist/cache.js +99 -0
  151. package/dist/cli-spec.d.ts +50 -0
  152. package/dist/cli-spec.js +75 -0
  153. package/dist/client-id-tracker.d.ts +25 -0
  154. package/dist/client-id-tracker.js +76 -0
  155. package/dist/commands/account.d.ts +3 -0
  156. package/dist/commands/account.js +425 -0
  157. package/dist/commands/agent.d.ts +3 -0
  158. package/dist/commands/agent.js +386 -0
  159. package/dist/commands/alert.d.ts +2 -0
  160. package/dist/commands/alert.js +421 -0
  161. package/dist/commands/analytics.d.ts +3 -0
  162. package/dist/commands/analytics.js +311 -0
  163. package/dist/commands/arb/index.d.ts +3 -0
  164. package/dist/commands/arb/index.js +921 -0
  165. package/dist/commands/arb-auto.d.ts +54 -0
  166. package/dist/commands/arb-auto.js +1328 -0
  167. package/dist/commands/arb-manage.d.ts +5 -0
  168. package/dist/commands/arb-manage.js +5 -0
  169. package/dist/commands/arb.d.ts +2 -0
  170. package/dist/commands/arb.js +347 -0
  171. package/dist/commands/backtest.d.ts +2 -0
  172. package/dist/commands/backtest.js +327 -0
  173. package/dist/commands/bot.d.ts +3 -0
  174. package/dist/commands/bot.js +412 -0
  175. package/dist/commands/bridge.d.ts +2 -0
  176. package/dist/commands/bridge.js +396 -0
  177. package/dist/commands/dashboard.d.ts +3 -0
  178. package/dist/commands/dashboard.js +176 -0
  179. package/dist/commands/deposit.d.ts +4 -0
  180. package/dist/commands/deposit.js +573 -0
  181. package/dist/commands/dex.d.ts +3 -0
  182. package/dist/commands/dex.js +114 -0
  183. package/dist/commands/env.d.ts +2 -0
  184. package/dist/commands/env.js +136 -0
  185. package/dist/commands/funding.d.ts +2 -0
  186. package/dist/commands/funding.js +347 -0
  187. package/dist/commands/gap.d.ts +2 -0
  188. package/dist/commands/gap.js +305 -0
  189. package/dist/commands/health.d.ts +2 -0
  190. package/dist/commands/health.js +67 -0
  191. package/dist/commands/history.d.ts +2 -0
  192. package/dist/commands/history.js +235 -0
  193. package/dist/commands/init.d.ts +15 -0
  194. package/dist/commands/init.js +266 -0
  195. package/dist/commands/jobs.d.ts +2 -0
  196. package/dist/commands/jobs.js +133 -0
  197. package/dist/commands/manage.d.ts +4 -0
  198. package/dist/commands/manage.js +309 -0
  199. package/dist/commands/market.d.ts +3 -0
  200. package/dist/commands/market.js +225 -0
  201. package/dist/commands/plan.d.ts +3 -0
  202. package/dist/commands/plan.js +95 -0
  203. package/dist/commands/portfolio.d.ts +3 -0
  204. package/dist/commands/portfolio.js +169 -0
  205. package/dist/commands/rebalance.d.ts +3 -0
  206. package/dist/commands/rebalance.js +293 -0
  207. package/dist/commands/risk.d.ts +3 -0
  208. package/dist/commands/risk.js +169 -0
  209. package/dist/commands/run.d.ts +3 -0
  210. package/dist/commands/run.js +202 -0
  211. package/dist/commands/settings.d.ts +2 -0
  212. package/dist/commands/settings.js +102 -0
  213. package/dist/commands/stream.d.ts +5 -0
  214. package/dist/commands/stream.js +123 -0
  215. package/dist/commands/trade.d.ts +3 -0
  216. package/dist/commands/trade.js +1273 -0
  217. package/dist/commands/wallet.d.ts +14 -0
  218. package/dist/commands/wallet.js +602 -0
  219. package/dist/commands/withdraw.d.ts +3 -0
  220. package/dist/commands/withdraw.js +187 -0
  221. package/dist/config.d.ts +5 -0
  222. package/dist/config.js +68 -0
  223. package/dist/cross-chain-margin.d.ts +46 -0
  224. package/dist/cross-chain-margin.js +107 -0
  225. package/dist/dashboard/server.d.ts +80 -0
  226. package/dist/dashboard/server.js +340 -0
  227. package/dist/dashboard/ui.d.ts +4 -0
  228. package/dist/dashboard/ui.js +538 -0
  229. package/dist/dashboard/ws-feeds.d.ts +29 -0
  230. package/dist/dashboard/ws-feeds.js +660 -0
  231. package/dist/dex-asset-map.d.ts +80 -0
  232. package/dist/dex-asset-map.js +201 -0
  233. package/dist/errors.d.ts +109 -0
  234. package/dist/errors.js +84 -0
  235. package/dist/event-stream.d.ts +25 -0
  236. package/dist/event-stream.js +168 -0
  237. package/dist/exchanges/hyperliquid.d.ts +212 -0
  238. package/dist/exchanges/hyperliquid.js +931 -0
  239. package/dist/exchanges/interface.d.ts +95 -0
  240. package/dist/exchanges/interface.js +5 -0
  241. package/dist/exchanges/lighter.d.ts +159 -0
  242. package/dist/exchanges/lighter.js +793 -0
  243. package/dist/exchanges/pacifica.d.ts +51 -0
  244. package/dist/exchanges/pacifica.js +248 -0
  245. package/dist/execution-log.d.ts +36 -0
  246. package/dist/execution-log.js +102 -0
  247. package/dist/funding/history.d.ts +63 -0
  248. package/dist/funding/history.js +266 -0
  249. package/dist/funding/index.d.ts +3 -0
  250. package/dist/funding/index.js +3 -0
  251. package/dist/funding/normalize.d.ts +39 -0
  252. package/dist/funding/normalize.js +66 -0
  253. package/dist/funding/rates.d.ts +45 -0
  254. package/dist/funding/rates.js +172 -0
  255. package/dist/funding-history.d.ts +5 -0
  256. package/dist/funding-history.js +5 -0
  257. package/dist/funding-rates.d.ts +5 -0
  258. package/dist/funding-rates.js +5 -0
  259. package/dist/funding.d.ts +5 -0
  260. package/dist/funding.js +5 -0
  261. package/dist/index.d.ts +2 -0
  262. package/dist/index.js +458 -0
  263. package/dist/jobs.d.ts +37 -0
  264. package/dist/jobs.js +152 -0
  265. package/dist/liquidity.d.ts +34 -0
  266. package/dist/liquidity.js +100 -0
  267. package/dist/mcp-server.d.ts +9 -0
  268. package/dist/mcp-server.js +1206 -0
  269. package/dist/pacifica/client.d.ts +111 -0
  270. package/dist/pacifica/client.js +310 -0
  271. package/dist/pacifica/constants.d.ts +27 -0
  272. package/dist/pacifica/constants.js +47 -0
  273. package/dist/pacifica/deposit.d.ts +14 -0
  274. package/dist/pacifica/deposit.js +78 -0
  275. package/dist/pacifica/index.d.ts +6 -0
  276. package/dist/pacifica/index.js +11 -0
  277. package/dist/pacifica/signing.d.ts +49 -0
  278. package/dist/pacifica/signing.js +97 -0
  279. package/dist/pacifica/types/account.d.ts +42 -0
  280. package/dist/pacifica/types/account.js +1 -0
  281. package/dist/pacifica/types/index.d.ts +6 -0
  282. package/dist/pacifica/types/index.js +6 -0
  283. package/dist/pacifica/types/lake.d.ts +18 -0
  284. package/dist/pacifica/types/lake.js +1 -0
  285. package/dist/pacifica/types/market.d.ts +64 -0
  286. package/dist/pacifica/types/market.js +1 -0
  287. package/dist/pacifica/types/order.d.ts +92 -0
  288. package/dist/pacifica/types/order.js +1 -0
  289. package/dist/pacifica/types/position.d.ts +25 -0
  290. package/dist/pacifica/types/position.js +1 -0
  291. package/dist/pacifica/types/ws.d.ts +34 -0
  292. package/dist/pacifica/types/ws.js +41 -0
  293. package/dist/pacifica/ws-client.d.ts +42 -0
  294. package/dist/pacifica/ws-client.js +180 -0
  295. package/dist/plan-executor.d.ts +48 -0
  296. package/dist/plan-executor.js +280 -0
  297. package/dist/position-history.d.ts +68 -0
  298. package/dist/position-history.js +222 -0
  299. package/dist/rebalance.d.ts +64 -0
  300. package/dist/rebalance.js +142 -0
  301. package/dist/retry.d.ts +74 -0
  302. package/dist/retry.js +129 -0
  303. package/dist/risk.d.ts +48 -0
  304. package/dist/risk.js +156 -0
  305. package/dist/settings.d.ts +19 -0
  306. package/dist/settings.js +45 -0
  307. package/dist/shared-api.d.ts +5 -0
  308. package/dist/shared-api.js +5 -0
  309. package/dist/strategies/dca.d.ts +25 -0
  310. package/dist/strategies/dca.js +114 -0
  311. package/dist/strategies/funding-arb.d.ts +15 -0
  312. package/dist/strategies/funding-arb.js +281 -0
  313. package/dist/strategies/grid.d.ts +34 -0
  314. package/dist/strategies/grid.js +185 -0
  315. package/dist/strategies/trailing-stop.d.ts +17 -0
  316. package/dist/strategies/trailing-stop.js +121 -0
  317. package/dist/strategies/twap.d.ts +20 -0
  318. package/dist/strategies/twap.js +78 -0
  319. package/dist/trade-validator.d.ts +39 -0
  320. package/dist/trade-validator.js +154 -0
  321. package/dist/utils.d.ts +38 -0
  322. package/dist/utils.js +110 -0
  323. package/package.json +63 -0
  324. package/skills/perp-cli/SKILL.md +149 -0
  325. package/skills/perp-cli/references/commands.md +143 -0
@@ -0,0 +1,453 @@
1
+ /**
2
+ * CCTP V2 Simulation Tests — verify bridge implementation correctness
3
+ * WITHOUT sending real transactions.
4
+ *
5
+ * Tests:
6
+ * 1. EVM V2 contract existence (eth_getCode) on key chains
7
+ * 2. Solana program existence on mainnet
8
+ * 3. EVM depositForBurn V2 staticCall simulation
9
+ * 4. Solana depositForBurn simulateTransaction
10
+ * 5. PDA derivation correctness
11
+ * 6. V2 Iris attestation API reachability
12
+ * 7. HyperCore CCTP fees API
13
+ */
14
+ import { describe, it, expect } from "vitest";
15
+ // Re-export constants for testing
16
+ import { CCTP_DOMAINS, CHAIN_IDS, USDC_ADDRESSES, EVM_TOKEN_MINTER_V2, } from "../../bridge-engine.js";
17
+ // V2 contract addresses (same on all EVM chains per Circle docs)
18
+ const EVM_TOKEN_MESSENGER_V2 = "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d";
19
+ const EVM_MESSAGE_TRANSMITTER_V2 = "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64";
20
+ // Solana CCTP V2 programs
21
+ const CCTP_SOLANA_TMM = "CCTPV2vPZJS2u2BBsUoscuikbYjnpFmbFsvVuJdgUMQe";
22
+ const CCTP_SOLANA_MT = "CCTPV2Sm4AdWt5296sk4P66VBZ7bEhcARwFaaS9YPbeC";
23
+ // RPC endpoints (public, free)
24
+ const RPC = {
25
+ arbitrum: "https://arb1.arbitrum.io/rpc",
26
+ base: "https://mainnet.base.org",
27
+ solana: "https://api.mainnet-beta.solana.com",
28
+ };
29
+ // Helper: JSON-RPC call (with retry for flaky RPCs)
30
+ async function ethCall(rpc, method, params, retries = 2) {
31
+ for (let attempt = 0; attempt <= retries; attempt++) {
32
+ try {
33
+ const res = await fetch(rpc, {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
37
+ });
38
+ const text = await res.text();
39
+ let json;
40
+ try {
41
+ json = JSON.parse(text);
42
+ }
43
+ catch {
44
+ if (attempt < retries) {
45
+ await new Promise(r => setTimeout(r, 500));
46
+ continue;
47
+ }
48
+ throw new Error(`RPC returned invalid JSON from ${rpc}: ${text.slice(0, 200)}`);
49
+ }
50
+ if (json.error)
51
+ throw new Error(`RPC error: ${json.error.message ?? json.error.code ?? JSON.stringify(json.error)}`);
52
+ return json.result;
53
+ }
54
+ catch (err) {
55
+ if (attempt < retries && err.message?.includes("invalid JSON")) {
56
+ await new Promise(r => setTimeout(r, 500));
57
+ continue;
58
+ }
59
+ throw err;
60
+ }
61
+ }
62
+ throw new Error(`ethCall failed after ${retries + 1} attempts`);
63
+ }
64
+ // Helper: Solana RPC call
65
+ async function solanaRpc(method, params) {
66
+ const res = await fetch(RPC.solana, {
67
+ method: "POST",
68
+ headers: { "Content-Type": "application/json" },
69
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
70
+ });
71
+ const json = await res.json();
72
+ if (json.error)
73
+ throw new Error(`Solana RPC error: ${json.error.message}`);
74
+ return json.result;
75
+ }
76
+ describe("CCTP V2 Simulation Tests", { timeout: 60000 }, () => {
77
+ // ══════════════════════════════════════════════════════════
78
+ // 1. EVM V2 Contract Existence
79
+ // ══════════════════════════════════════════════════════════
80
+ describe("EVM V2 contract verification", () => {
81
+ const chainsToCheck = ["arbitrum", "base"];
82
+ for (const chain of chainsToCheck) {
83
+ it(`${chain}: TokenMessengerV2 has deployed code`, async () => {
84
+ const code = await ethCall(RPC[chain], "eth_getCode", [EVM_TOKEN_MESSENGER_V2, "latest"]);
85
+ expect(typeof code).toBe("string");
86
+ expect(code.length).toBeGreaterThan(10); // not "0x" (empty)
87
+ expect(code).not.toBe("0x");
88
+ });
89
+ it(`${chain}: MessageTransmitterV2 has deployed code`, async () => {
90
+ const code = await ethCall(RPC[chain], "eth_getCode", [EVM_MESSAGE_TRANSMITTER_V2, "latest"]);
91
+ expect(typeof code).toBe("string");
92
+ expect(code.length).toBeGreaterThan(10);
93
+ expect(code).not.toBe("0x");
94
+ });
95
+ it(`${chain}: TokenMinterV2 has deployed code`, async () => {
96
+ const code = await ethCall(RPC[chain], "eth_getCode", [EVM_TOKEN_MINTER_V2, "latest"]);
97
+ expect(typeof code).toBe("string");
98
+ expect(code.length).toBeGreaterThan(10);
99
+ expect(code).not.toBe("0x");
100
+ });
101
+ }
102
+ });
103
+ // ══════════════════════════════════════════════════════════
104
+ // 2. Solana Program Existence
105
+ // ══════════════════════════════════════════════════════════
106
+ describe("Solana CCTP V2 program verification", () => {
107
+ it("TokenMessengerMinter program exists and is executable", async () => {
108
+ const result = await solanaRpc("getAccountInfo", [
109
+ CCTP_SOLANA_TMM,
110
+ { encoding: "jsonParsed" },
111
+ ]);
112
+ expect(result.value).not.toBeNull();
113
+ expect(result.value.executable).toBe(true);
114
+ // Should be owned by BPF loader
115
+ expect(result.value.owner).toMatch(/^BPFLoader/);
116
+ });
117
+ it("MessageTransmitter program exists and is executable", async () => {
118
+ const result = await solanaRpc("getAccountInfo", [
119
+ CCTP_SOLANA_MT,
120
+ { encoding: "jsonParsed" },
121
+ ]);
122
+ expect(result.value).not.toBeNull();
123
+ expect(result.value.executable).toBe(true);
124
+ expect(result.value.owner).toMatch(/^BPFLoader/);
125
+ });
126
+ it("USDC mint exists on Solana mainnet", async () => {
127
+ const result = await solanaRpc("getAccountInfo", [
128
+ USDC_ADDRESSES.solana,
129
+ { encoding: "jsonParsed" },
130
+ ]);
131
+ expect(result.value).not.toBeNull();
132
+ expect(result.value.data.parsed.type).toBe("mint");
133
+ });
134
+ });
135
+ // ══════════════════════════════════════════════════════════
136
+ // 3. EVM depositForBurn V2 staticCall Simulation
137
+ // ══════════════════════════════════════════════════════════
138
+ describe("EVM depositForBurn V2 staticCall simulation", () => {
139
+ // We use eth_call to simulate depositForBurn — it won't actually execute
140
+ // but verifies the ABI signature matches the deployed V2 contract.
141
+ // We use a random "from" address with no USDC — the call will revert
142
+ // with an understandable error (insufficient balance), NOT "invalid method".
143
+ const DUMMY_SENDER = "0x0000000000000000000000000000000000000001";
144
+ it("arbitrum → base: V2 depositForBurn ABI is accepted by contract", async () => {
145
+ const { ethers } = await import("ethers");
146
+ const iface = new ethers.Interface([
147
+ "function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken, bytes32 destinationCaller, uint256 maxFee, uint32 minFinalityThreshold) returns (uint64 nonce)",
148
+ ]);
149
+ const calldata = iface.encodeFunctionData("depositForBurn", [
150
+ 1000000n, // 1 USDC
151
+ CCTP_DOMAINS.base, // destination domain
152
+ ethers.zeroPadValue(DUMMY_SENDER, 32), // mintRecipient
153
+ USDC_ADDRESSES.arbitrum, // burnToken
154
+ ethers.ZeroHash, // destinationCaller (permissionless)
155
+ 0n, // maxFee (standard)
156
+ 2000, // minFinalityThreshold (finalized)
157
+ ]);
158
+ // eth_call will revert because DUMMY_SENDER has no USDC,
159
+ // but the revert message should NOT be "invalid function selector"
160
+ try {
161
+ await ethCall(RPC.arbitrum, "eth_call", [
162
+ { from: DUMMY_SENDER, to: EVM_TOKEN_MESSENGER_V2, data: calldata },
163
+ "latest",
164
+ ]);
165
+ // If it doesn't revert, that's also fine (unlikely without USDC)
166
+ }
167
+ catch (err) {
168
+ const msg = err.message;
169
+ // Should NOT indicate unknown function selector
170
+ expect(msg).not.toContain("invalid opcode");
171
+ expect(msg).not.toContain("unrecognized function selector");
172
+ }
173
+ });
174
+ it("base → arbitrum: V2 7-param ABI matches contract", async () => {
175
+ const { ethers } = await import("ethers");
176
+ const iface = new ethers.Interface([
177
+ "function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken, bytes32 destinationCaller, uint256 maxFee, uint32 minFinalityThreshold) returns (uint64 nonce)",
178
+ ]);
179
+ // Encode with correct V2 params
180
+ const calldata = iface.encodeFunctionData("depositForBurn", [
181
+ 500000n, // 0.5 USDC
182
+ CCTP_DOMAINS.arbitrum,
183
+ ethers.zeroPadValue(DUMMY_SENDER, 32),
184
+ USDC_ADDRESSES.base,
185
+ ethers.ZeroHash,
186
+ 0n,
187
+ 2000,
188
+ ]);
189
+ // The function selector (first 4 bytes) should match V2's depositForBurn
190
+ const selector = calldata.slice(0, 10); // "0x" + 8 hex chars
191
+ // V1 4-param selector would be different — verify we're using V2
192
+ const v1Iface = new ethers.Interface([
193
+ "function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken)",
194
+ ]);
195
+ const v1Selector = v1Iface.getFunction("depositForBurn").selector;
196
+ expect(selector).not.toBe(v1Selector);
197
+ // Verify this selector is recognized by the base contract
198
+ try {
199
+ await ethCall(RPC.base, "eth_call", [
200
+ { from: DUMMY_SENDER, to: EVM_TOKEN_MESSENGER_V2, data: calldata },
201
+ "latest",
202
+ ]);
203
+ }
204
+ catch (err) {
205
+ const msg = err.message;
206
+ expect(msg).not.toContain("unrecognized function selector");
207
+ }
208
+ });
209
+ it("V2 function selector is correct (0x7d213921 for 7-param)", async () => {
210
+ const { ethers } = await import("ethers");
211
+ const iface = new ethers.Interface([
212
+ "function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken, bytes32 destinationCaller, uint256 maxFee, uint32 minFinalityThreshold) returns (uint64 nonce)",
213
+ ]);
214
+ const fn = iface.getFunction("depositForBurn");
215
+ // The exact selector depends on the full signature — just verify it's consistent
216
+ expect(fn.selector).toBeTruthy();
217
+ expect(fn.selector.length).toBe(10); // "0x" + 8 hex
218
+ // Verify it's NOT the V1 selector
219
+ const v1Iface = new ethers.Interface([
220
+ "function depositForBurn(uint256 amount, uint32 destinationDomain, bytes32 mintRecipient, address burnToken)",
221
+ ]);
222
+ expect(fn.selector).not.toBe(v1Iface.getFunction("depositForBurn").selector);
223
+ });
224
+ });
225
+ // ══════════════════════════════════════════════════════════
226
+ // 4. Solana PDA Derivation Verification
227
+ // ══════════════════════════════════════════════════════════
228
+ describe("Solana PDA derivation correctness", () => {
229
+ it("key state PDAs exist on-chain (token_messenger, token_minter, local_token, message_transmitter)", async () => {
230
+ const { PublicKey } = await import("@solana/web3.js");
231
+ const tokenMessengerMinter = new PublicKey(CCTP_SOLANA_TMM);
232
+ const messageTransmitterProgram = new PublicKey(CCTP_SOLANA_MT);
233
+ const usdcMint = new PublicKey(USDC_ADDRESSES.solana);
234
+ // Derive PDAs — only data accounts that should exist on-chain.
235
+ // sender_authority and event_authority PDAs are virtual/derived-only signers
236
+ // (no data stored on-chain), so we skip them.
237
+ const [tokenMessenger] = PublicKey.findProgramAddressSync([Buffer.from("token_messenger")], tokenMessengerMinter);
238
+ const [tokenMinter] = PublicKey.findProgramAddressSync([Buffer.from("token_minter")], tokenMessengerMinter);
239
+ const [localToken] = PublicKey.findProgramAddressSync([Buffer.from("local_token"), usdcMint.toBuffer()], tokenMessengerMinter);
240
+ const [messageTransmitterAccount] = PublicKey.findProgramAddressSync([Buffer.from("message_transmitter")], messageTransmitterProgram);
241
+ const keys = [tokenMessenger, tokenMinter, localToken, messageTransmitterAccount];
242
+ const result = await solanaRpc("getMultipleAccounts", [
243
+ keys.map(k => k.toBase58()),
244
+ { encoding: "base64" },
245
+ ]);
246
+ const labels = ["tokenMessenger", "tokenMinter", "localToken", "messageTransmitter"];
247
+ for (let i = 0; i < keys.length; i++) {
248
+ expect(result.value[i], `${labels[i]} PDA should exist on-chain`).not.toBeNull();
249
+ }
250
+ });
251
+ it("remote_token_messenger PDAs use string domain seeds", async () => {
252
+ const { PublicKey } = await import("@solana/web3.js");
253
+ const tokenMessengerMinter = new PublicKey(CCTP_SOLANA_TMM);
254
+ // Test supported domains
255
+ const domainsToCheck = [
256
+ { name: "arbitrum", domain: 3 },
257
+ { name: "base", domain: 6 },
258
+ ];
259
+ for (const { name, domain } of domainsToCheck) {
260
+ // String seed (correct V2 approach)
261
+ const [rtmString] = PublicKey.findProgramAddressSync([Buffer.from("remote_token_messenger"), Buffer.from(String(domain))], tokenMessengerMinter);
262
+ // Verify this PDA exists on-chain
263
+ const result = await solanaRpc("getAccountInfo", [
264
+ rtmString.toBase58(),
265
+ { encoding: "base64" },
266
+ ]);
267
+ expect(result.value).not.toBeNull();
268
+ }
269
+ });
270
+ it("binary domain seed does NOT produce the correct PDA", async () => {
271
+ const { PublicKey } = await import("@solana/web3.js");
272
+ const tokenMessengerMinter = new PublicKey(CCTP_SOLANA_TMM);
273
+ // Binary u32 LE seed (WRONG approach)
274
+ const binaryBuf = Buffer.alloc(4);
275
+ binaryBuf.writeUInt32LE(3); // arbitrum domain
276
+ const [rtmBinary] = PublicKey.findProgramAddressSync([Buffer.from("remote_token_messenger"), binaryBuf], tokenMessengerMinter);
277
+ // String seed (correct approach)
278
+ const [rtmString] = PublicKey.findProgramAddressSync([Buffer.from("remote_token_messenger"), Buffer.from("3")], tokenMessengerMinter);
279
+ // They must be different
280
+ expect(rtmBinary.toBase58()).not.toBe(rtmString.toBase58());
281
+ // Only the string-seed PDA should exist on-chain
282
+ const [binaryResult, stringResult] = await Promise.all([
283
+ solanaRpc("getAccountInfo", [rtmBinary.toBase58(), { encoding: "base64" }]),
284
+ solanaRpc("getAccountInfo", [rtmString.toBase58(), { encoding: "base64" }]),
285
+ ]);
286
+ expect(binaryResult.value).toBeNull(); // binary seed = WRONG PDA
287
+ expect(stringResult.value).not.toBeNull(); // string seed = correct PDA
288
+ });
289
+ });
290
+ // ══════════════════════════════════════════════════════════
291
+ // 5. Solana Transaction Simulation (depositForBurn)
292
+ // ══════════════════════════════════════════════════════════
293
+ describe("Solana depositForBurn simulation", () => {
294
+ it("simulated tx fails with expected error (not 'invalid account')", async () => {
295
+ const { PublicKey, Keypair, TransactionMessage, VersionedTransaction, SystemProgram } = await import("@solana/web3.js");
296
+ const { createHash } = await import("crypto");
297
+ const tokenMessengerMinter = new PublicKey(CCTP_SOLANA_TMM);
298
+ const messageTransmitterProgram = new PublicKey(CCTP_SOLANA_MT);
299
+ const usdcMint = new PublicKey(USDC_ADDRESSES.solana);
300
+ const tokenProgram = new PublicKey("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
301
+ // Generate a temporary keypair (has no SOL or USDC)
302
+ const keypair = Keypair.generate();
303
+ // Derive all PDAs (matching bridge-engine.ts exactly)
304
+ const [senderAuthority] = PublicKey.findProgramAddressSync([Buffer.from("sender_authority")], tokenMessengerMinter);
305
+ const [tokenMessenger] = PublicKey.findProgramAddressSync([Buffer.from("token_messenger")], tokenMessengerMinter);
306
+ const dstDomain = CCTP_DOMAINS.arbitrum; // 3
307
+ const [remoteTokenMessenger] = PublicKey.findProgramAddressSync([Buffer.from("remote_token_messenger"), Buffer.from(String(dstDomain))], tokenMessengerMinter);
308
+ const [tokenMinter] = PublicKey.findProgramAddressSync([Buffer.from("token_minter")], tokenMessengerMinter);
309
+ const [localToken] = PublicKey.findProgramAddressSync([Buffer.from("local_token"), usdcMint.toBuffer()], tokenMessengerMinter);
310
+ const [messageTransmitterAccount] = PublicKey.findProgramAddressSync([Buffer.from("message_transmitter")], messageTransmitterProgram);
311
+ const [denylistAccount] = PublicKey.findProgramAddressSync([Buffer.from("denylist_account"), keypair.publicKey.toBuffer()], tokenMessengerMinter);
312
+ const [burnTokenAccount] = PublicKey.findProgramAddressSync([keypair.publicKey.toBuffer(), tokenProgram.toBuffer(), usdcMint.toBuffer()], new PublicKey("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"));
313
+ const eventDataKeypair = Keypair.generate();
314
+ const [eventAuthority] = PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], tokenMessengerMinter);
315
+ const [mtEventAuthority] = PublicKey.findProgramAddressSync([Buffer.from("__event_authority")], messageTransmitterProgram);
316
+ // Build instruction data (deposit_for_burn)
317
+ const discriminator = createHash("sha256").update("global:deposit_for_burn").digest().subarray(0, 8);
318
+ const amountBuf = Buffer.alloc(8);
319
+ amountBuf.writeBigUInt64LE(BigInt(1000000)); // 1 USDC
320
+ const domainBuf = Buffer.alloc(4);
321
+ domainBuf.writeUInt32LE(dstDomain);
322
+ const recipientBytes = Buffer.alloc(32);
323
+ Buffer.from("0000000000000000000000000000000000000001", "hex").copy(recipientBytes, 12);
324
+ const destinationCaller = Buffer.alloc(32);
325
+ const maxFeeBuf = Buffer.alloc(8);
326
+ maxFeeBuf.writeBigUInt64LE(BigInt(0));
327
+ const minFinalityBuf = Buffer.alloc(4);
328
+ minFinalityBuf.writeUInt32LE(2000);
329
+ const data = Buffer.concat([
330
+ discriminator, amountBuf, domainBuf, recipientBytes,
331
+ destinationCaller, maxFeeBuf, minFinalityBuf,
332
+ ]);
333
+ const instruction = {
334
+ programId: tokenMessengerMinter,
335
+ keys: [
336
+ { pubkey: keypair.publicKey, isSigner: true, isWritable: true }, // 0
337
+ { pubkey: keypair.publicKey, isSigner: true, isWritable: true }, // 1
338
+ { pubkey: senderAuthority, isSigner: false, isWritable: false }, // 2
339
+ { pubkey: burnTokenAccount, isSigner: false, isWritable: true }, // 3
340
+ { pubkey: denylistAccount, isSigner: false, isWritable: false }, // 4
341
+ { pubkey: messageTransmitterAccount, isSigner: false, isWritable: true }, // 5
342
+ { pubkey: tokenMessenger, isSigner: false, isWritable: false }, // 6
343
+ { pubkey: remoteTokenMessenger, isSigner: false, isWritable: false }, // 7
344
+ { pubkey: tokenMinter, isSigner: false, isWritable: false }, // 8
345
+ { pubkey: localToken, isSigner: false, isWritable: true }, // 9
346
+ { pubkey: usdcMint, isSigner: false, isWritable: true }, // 10
347
+ { pubkey: eventDataKeypair.publicKey, isSigner: true, isWritable: true }, // 11
348
+ { pubkey: messageTransmitterProgram, isSigner: false, isWritable: false }, // 12
349
+ { pubkey: tokenMessengerMinter, isSigner: false, isWritable: false }, // 13
350
+ { pubkey: tokenProgram, isSigner: false, isWritable: false }, // 14
351
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, // 15
352
+ { pubkey: eventAuthority, isSigner: false, isWritable: false }, // 16
353
+ { pubkey: tokenMessengerMinter, isSigner: false, isWritable: false }, // 17
354
+ { pubkey: mtEventAuthority, isSigner: false, isWritable: false }, // 18
355
+ { pubkey: messageTransmitterProgram, isSigner: false, isWritable: false }, // 19
356
+ ],
357
+ data,
358
+ };
359
+ // Get recent blockhash for simulation
360
+ const blockhashResult = await solanaRpc("getLatestBlockhash", [{ commitment: "confirmed" }]);
361
+ const messageV0 = new TransactionMessage({
362
+ payerKey: keypair.publicKey,
363
+ recentBlockhash: blockhashResult.value.blockhash,
364
+ instructions: [instruction],
365
+ }).compileToV0Message();
366
+ const transaction = new VersionedTransaction(messageV0);
367
+ transaction.sign([keypair, eventDataKeypair]);
368
+ // Simulate — expect failure due to no USDC balance, NOT due to invalid accounts/instruction
369
+ const serialized = Buffer.from(transaction.serialize()).toString("base64");
370
+ const simResult = await solanaRpc("simulateTransaction", [
371
+ serialized,
372
+ {
373
+ encoding: "base64",
374
+ sigVerify: false, // skip sig verification for simulation
375
+ replaceRecentBlockhash: true,
376
+ },
377
+ ]);
378
+ // The simulation WILL fail (no USDC balance / no SOL for rent), but the error
379
+ // should be program-level or resource-level, NOT structural account errors.
380
+ expect(simResult.value.err).not.toBeNull(); // should fail
381
+ const logs = simResult.value.logs ?? [];
382
+ const logStr = logs.join("\n");
383
+ const errStr = JSON.stringify(simResult.value.err);
384
+ // If there are logs, the program was at least partially invoked.
385
+ // If no logs but err = "AccountNotFound" or "InsufficientFundsForRent",
386
+ // that's also acceptable — means accounts structure was correct but
387
+ // the keypair simply has no SOL.
388
+ if (logs.length > 0) {
389
+ // Program was invoked — check it's our program, not a structural failure
390
+ expect(logStr).toContain("CCTPV2vPZJS2u2BBsUoscuikbYjnpFmbFsvVuJdgUMQe");
391
+ }
392
+ else {
393
+ // No logs = TX failed pre-execution (no SOL for fees/rent)
394
+ // This is OK as long as it's not an "invalid instruction" error
395
+ expect(errStr).not.toContain("InvalidAccountData");
396
+ expect(errStr).not.toContain("MissingRequiredSignature");
397
+ }
398
+ // Should NOT have these structural errors in either case:
399
+ expect(logStr).not.toContain("invalid program argument");
400
+ expect(logStr).not.toContain("An account required by the instruction is missing");
401
+ });
402
+ });
403
+ // ══════════════════════════════════════════════════════════
404
+ // 6. V2 Iris Attestation API Reachability
405
+ // ══════════════════════════════════════════════════════════
406
+ describe("Circle V2 Iris attestation API", () => {
407
+ it("V2 endpoint responds (GET /v2/messages/{domain})", async () => {
408
+ // Query with a non-existent tx hash — should return 200 with empty messages
409
+ const res = await fetch("https://iris-api.circle.com/v2/messages/3?transactionHash=0x0000000000000000000000000000000000000000000000000000000000000000");
410
+ // API should respond (200 or 404, not 5xx)
411
+ expect(res.status).toBeLessThan(500);
412
+ if (res.ok) {
413
+ const data = await res.json();
414
+ // Empty array or empty messages is expected for fake txHash
415
+ expect(data.messages).toBeDefined();
416
+ }
417
+ });
418
+ it("V2 endpoint supports our source domains", async () => {
419
+ const sourceDomains = [3, 5, 6]; // arbitrum, solana, base
420
+ const fakeTx = "0x0000000000000000000000000000000000000000000000000000000000000000";
421
+ for (const domain of sourceDomains) {
422
+ const res = await fetch(`https://iris-api.circle.com/v2/messages/${domain}?transactionHash=${fakeTx}`);
423
+ expect(res.status).toBeLessThan(500);
424
+ }
425
+ });
426
+ });
427
+ // ══════════════════════════════════════════════════════════
428
+ // 8. CCTP Domain Consistency
429
+ // ══════════════════════════════════════════════════════════
430
+ describe("CCTP domain and address consistency", () => {
431
+ it("all CCTP domains map to valid chain IDs", () => {
432
+ for (const [chain, domain] of Object.entries(CCTP_DOMAINS)) {
433
+ if (chain === "hyperliquid")
434
+ continue; // HyperCore uses CctpForwarder, not standard chain ID
435
+ expect(typeof domain).toBe("number");
436
+ expect(domain).toBeGreaterThanOrEqual(0);
437
+ expect(CHAIN_IDS[chain]).toBeGreaterThan(0);
438
+ }
439
+ });
440
+ it("all EVM CCTP chains have USDC addresses", () => {
441
+ const evmChains = Object.keys(CCTP_DOMAINS).filter(c => c !== "solana" && c !== "hyperliquid");
442
+ for (const chain of evmChains) {
443
+ expect(USDC_ADDRESSES[chain]).toMatch(/^0x[a-fA-F0-9]{40}$/);
444
+ }
445
+ });
446
+ it("V2 domain IDs match Circle documentation", () => {
447
+ expect(CCTP_DOMAINS.arbitrum).toBe(3);
448
+ expect(CCTP_DOMAINS.solana).toBe(5);
449
+ expect(CCTP_DOMAINS.base).toBe(6);
450
+ expect(CCTP_DOMAINS.hyperliquid).toBe(19);
451
+ });
452
+ });
453
+ });