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.
- package/LICENSE +21 -0
- package/README.md +293 -0
- package/dist/__tests__/alert-logic.test.d.ts +1 -0
- package/dist/__tests__/alert-logic.test.js +107 -0
- package/dist/__tests__/arb-auto-3dex.test.d.ts +1 -0
- package/dist/__tests__/arb-auto-3dex.test.js +397 -0
- package/dist/__tests__/arb-history-stats.test.d.ts +1 -0
- package/dist/__tests__/arb-history-stats.test.js +176 -0
- package/dist/__tests__/arb-logic.test.d.ts +1 -0
- package/dist/__tests__/arb-logic.test.js +84 -0
- package/dist/__tests__/arb-manage.test.d.ts +1 -0
- package/dist/__tests__/arb-manage.test.js +253 -0
- package/dist/__tests__/arb-new-features.test.d.ts +1 -0
- package/dist/__tests__/arb-new-features.test.js +457 -0
- package/dist/__tests__/arb-sizing.test.d.ts +1 -0
- package/dist/__tests__/arb-sizing.test.js +48 -0
- package/dist/__tests__/arb-state.test.d.ts +1 -0
- package/dist/__tests__/arb-state.test.js +284 -0
- package/dist/__tests__/arb-userflow.test.d.ts +1 -0
- package/dist/__tests__/arb-userflow.test.js +945 -0
- package/dist/__tests__/arb-utils.test.d.ts +1 -0
- package/dist/__tests__/arb-utils.test.js +264 -0
- package/dist/__tests__/bot-conditions.test.d.ts +1 -0
- package/dist/__tests__/bot-conditions.test.js +341 -0
- package/dist/__tests__/client-id-tracker.test.d.ts +1 -0
- package/dist/__tests__/client-id-tracker.test.js +137 -0
- package/dist/__tests__/commands/new-atomic-commands.test.d.ts +1 -0
- package/dist/__tests__/commands/new-atomic-commands.test.js +502 -0
- package/dist/__tests__/commands/order-intent.test.d.ts +1 -0
- package/dist/__tests__/commands/order-intent.test.js +600 -0
- package/dist/__tests__/commands/trade-commands.test.d.ts +1 -0
- package/dist/__tests__/commands/trade-commands.test.js +821 -0
- package/dist/__tests__/config.test.d.ts +1 -0
- package/dist/__tests__/config.test.js +86 -0
- package/dist/__tests__/cross-chain-margin.test.d.ts +1 -0
- package/dist/__tests__/cross-chain-margin.test.js +287 -0
- package/dist/__tests__/dex-asset-map.test.d.ts +1 -0
- package/dist/__tests__/dex-asset-map.test.js +191 -0
- package/dist/__tests__/errors.test.d.ts +1 -0
- package/dist/__tests__/errors.test.js +110 -0
- package/dist/__tests__/event-stream.test.d.ts +1 -0
- package/dist/__tests__/event-stream.test.js +276 -0
- package/dist/__tests__/exchanges/interface.test.d.ts +1 -0
- package/dist/__tests__/exchanges/interface.test.js +132 -0
- package/dist/__tests__/exchanges/mock-adapter.d.ts +69 -0
- package/dist/__tests__/exchanges/mock-adapter.js +137 -0
- package/dist/__tests__/execution-log.test.d.ts +1 -0
- package/dist/__tests__/execution-log.test.js +106 -0
- package/dist/__tests__/funding-calc.test.d.ts +1 -0
- package/dist/__tests__/funding-calc.test.js +71 -0
- package/dist/__tests__/funding-history.test.d.ts +1 -0
- package/dist/__tests__/funding-history.test.js +343 -0
- package/dist/__tests__/funding-rates.test.d.ts +1 -0
- package/dist/__tests__/funding-rates.test.js +342 -0
- package/dist/__tests__/funding.test.d.ts +1 -0
- package/dist/__tests__/funding.test.js +173 -0
- package/dist/__tests__/gap-logic.test.d.ts +1 -0
- package/dist/__tests__/gap-logic.test.js +43 -0
- package/dist/__tests__/hip3-dex.test.d.ts +1 -0
- package/dist/__tests__/hip3-dex.test.js +234 -0
- package/dist/__tests__/integration/agent-features.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/agent-features.integration.test.js +553 -0
- package/dist/__tests__/integration/atomic-commands.integration.test.d.ts +13 -0
- package/dist/__tests__/integration/atomic-commands.integration.test.js +246 -0
- package/dist/__tests__/integration/bridge-simulation.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/bridge-simulation.integration.test.js +453 -0
- package/dist/__tests__/integration/bridge-strict.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/bridge-strict.integration.test.js +812 -0
- package/dist/__tests__/integration/bridge.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/bridge.integration.test.js +309 -0
- package/dist/__tests__/integration/cli-e2e.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/cli-e2e.integration.test.js +202 -0
- package/dist/__tests__/integration/dex-arb.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/dex-arb.integration.test.js +116 -0
- package/dist/__tests__/integration/envelope-consistency.integration.test.d.ts +13 -0
- package/dist/__tests__/integration/envelope-consistency.integration.test.js +205 -0
- package/dist/__tests__/integration/hip3-dex.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/hip3-dex.integration.test.js +147 -0
- package/dist/__tests__/integration/hyperliquid.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/hyperliquid.integration.test.js +79 -0
- package/dist/__tests__/integration/lighter.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/lighter.integration.test.js +53 -0
- package/dist/__tests__/integration/new-commands-e2e.integration.test.d.ts +9 -0
- package/dist/__tests__/integration/new-commands-e2e.integration.test.js +236 -0
- package/dist/__tests__/integration/order-verification.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/order-verification.integration.test.js +321 -0
- package/dist/__tests__/integration/pacifica.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/pacifica.integration.test.js +75 -0
- package/dist/__tests__/integration/response-shapes.integration.test.d.ts +1 -0
- package/dist/__tests__/integration/response-shapes.integration.test.js +278 -0
- package/dist/__tests__/liquidity.test.d.ts +1 -0
- package/dist/__tests__/liquidity.test.js +225 -0
- package/dist/__tests__/plan-executor.test.d.ts +1 -0
- package/dist/__tests__/plan-executor.test.js +314 -0
- package/dist/__tests__/position-history.test.d.ts +1 -0
- package/dist/__tests__/position-history.test.js +367 -0
- package/dist/__tests__/retry.test.d.ts +1 -0
- package/dist/__tests__/retry.test.js +310 -0
- package/dist/__tests__/risk-assessment.test.d.ts +1 -0
- package/dist/__tests__/risk-assessment.test.js +145 -0
- package/dist/__tests__/security-adversarial.test.d.ts +1 -0
- package/dist/__tests__/security-adversarial.test.js +574 -0
- package/dist/__tests__/strategies.test.d.ts +1 -0
- package/dist/__tests__/strategies.test.js +539 -0
- package/dist/__tests__/trade-execution.test.d.ts +1 -0
- package/dist/__tests__/trade-execution.test.js +129 -0
- package/dist/__tests__/trade-validator.test.d.ts +1 -0
- package/dist/__tests__/trade-validator.test.js +655 -0
- package/dist/__tests__/utils.test.d.ts +1 -0
- package/dist/__tests__/utils.test.js +76 -0
- package/dist/api/public/hyperliquid.d.ts +18 -0
- package/dist/api/public/hyperliquid.js +82 -0
- package/dist/api/public/index.d.ts +8 -0
- package/dist/api/public/index.js +8 -0
- package/dist/api/public/lighter.d.ts +24 -0
- package/dist/api/public/lighter.js +100 -0
- package/dist/api/public/pacifica.d.ts +17 -0
- package/dist/api/public/pacifica.js +54 -0
- package/dist/api/public/urls.d.ts +12 -0
- package/dist/api/public/urls.js +33 -0
- package/dist/arb/history-stats.d.ts +44 -0
- package/dist/arb/history-stats.js +135 -0
- package/dist/arb/index.d.ts +4 -0
- package/dist/arb/index.js +4 -0
- package/dist/arb/sizing.d.ts +23 -0
- package/dist/arb/sizing.js +96 -0
- package/dist/arb/state.d.ts +51 -0
- package/dist/arb/state.js +112 -0
- package/dist/arb/utils.d.ts +81 -0
- package/dist/arb/utils.js +267 -0
- package/dist/arb-history-stats.d.ts +5 -0
- package/dist/arb-history-stats.js +5 -0
- package/dist/arb-sizing.d.ts +5 -0
- package/dist/arb-sizing.js +5 -0
- package/dist/arb-state.d.ts +5 -0
- package/dist/arb-state.js +5 -0
- package/dist/arb-utils.d.ts +5 -0
- package/dist/arb-utils.js +5 -0
- package/dist/bot/conditions.d.ts +32 -0
- package/dist/bot/conditions.js +141 -0
- package/dist/bot/config.d.ts +76 -0
- package/dist/bot/config.js +160 -0
- package/dist/bot/engine.d.ts +8 -0
- package/dist/bot/engine.js +519 -0
- package/dist/bot/presets.d.ts +11 -0
- package/dist/bot/presets.js +296 -0
- package/dist/bridge-engine.d.ts +133 -0
- package/dist/bridge-engine.js +1487 -0
- package/dist/cache.d.ts +25 -0
- package/dist/cache.js +99 -0
- package/dist/cli-spec.d.ts +50 -0
- package/dist/cli-spec.js +75 -0
- package/dist/client-id-tracker.d.ts +25 -0
- package/dist/client-id-tracker.js +76 -0
- package/dist/commands/account.d.ts +3 -0
- package/dist/commands/account.js +425 -0
- package/dist/commands/agent.d.ts +3 -0
- package/dist/commands/agent.js +386 -0
- package/dist/commands/alert.d.ts +2 -0
- package/dist/commands/alert.js +421 -0
- package/dist/commands/analytics.d.ts +3 -0
- package/dist/commands/analytics.js +311 -0
- package/dist/commands/arb/index.d.ts +3 -0
- package/dist/commands/arb/index.js +921 -0
- package/dist/commands/arb-auto.d.ts +54 -0
- package/dist/commands/arb-auto.js +1328 -0
- package/dist/commands/arb-manage.d.ts +5 -0
- package/dist/commands/arb-manage.js +5 -0
- package/dist/commands/arb.d.ts +2 -0
- package/dist/commands/arb.js +347 -0
- package/dist/commands/backtest.d.ts +2 -0
- package/dist/commands/backtest.js +327 -0
- package/dist/commands/bot.d.ts +3 -0
- package/dist/commands/bot.js +412 -0
- package/dist/commands/bridge.d.ts +2 -0
- package/dist/commands/bridge.js +396 -0
- package/dist/commands/dashboard.d.ts +3 -0
- package/dist/commands/dashboard.js +176 -0
- package/dist/commands/deposit.d.ts +4 -0
- package/dist/commands/deposit.js +573 -0
- package/dist/commands/dex.d.ts +3 -0
- package/dist/commands/dex.js +114 -0
- package/dist/commands/env.d.ts +2 -0
- package/dist/commands/env.js +136 -0
- package/dist/commands/funding.d.ts +2 -0
- package/dist/commands/funding.js +347 -0
- package/dist/commands/gap.d.ts +2 -0
- package/dist/commands/gap.js +305 -0
- package/dist/commands/health.d.ts +2 -0
- package/dist/commands/health.js +67 -0
- package/dist/commands/history.d.ts +2 -0
- package/dist/commands/history.js +235 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +266 -0
- package/dist/commands/jobs.d.ts +2 -0
- package/dist/commands/jobs.js +133 -0
- package/dist/commands/manage.d.ts +4 -0
- package/dist/commands/manage.js +309 -0
- package/dist/commands/market.d.ts +3 -0
- package/dist/commands/market.js +225 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.js +95 -0
- package/dist/commands/portfolio.d.ts +3 -0
- package/dist/commands/portfolio.js +169 -0
- package/dist/commands/rebalance.d.ts +3 -0
- package/dist/commands/rebalance.js +293 -0
- package/dist/commands/risk.d.ts +3 -0
- package/dist/commands/risk.js +169 -0
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.js +202 -0
- package/dist/commands/settings.d.ts +2 -0
- package/dist/commands/settings.js +102 -0
- package/dist/commands/stream.d.ts +5 -0
- package/dist/commands/stream.js +123 -0
- package/dist/commands/trade.d.ts +3 -0
- package/dist/commands/trade.js +1273 -0
- package/dist/commands/wallet.d.ts +14 -0
- package/dist/commands/wallet.js +602 -0
- package/dist/commands/withdraw.d.ts +3 -0
- package/dist/commands/withdraw.js +187 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +68 -0
- package/dist/cross-chain-margin.d.ts +46 -0
- package/dist/cross-chain-margin.js +107 -0
- package/dist/dashboard/server.d.ts +80 -0
- package/dist/dashboard/server.js +340 -0
- package/dist/dashboard/ui.d.ts +4 -0
- package/dist/dashboard/ui.js +538 -0
- package/dist/dashboard/ws-feeds.d.ts +29 -0
- package/dist/dashboard/ws-feeds.js +660 -0
- package/dist/dex-asset-map.d.ts +80 -0
- package/dist/dex-asset-map.js +201 -0
- package/dist/errors.d.ts +109 -0
- package/dist/errors.js +84 -0
- package/dist/event-stream.d.ts +25 -0
- package/dist/event-stream.js +168 -0
- package/dist/exchanges/hyperliquid.d.ts +212 -0
- package/dist/exchanges/hyperliquid.js +931 -0
- package/dist/exchanges/interface.d.ts +95 -0
- package/dist/exchanges/interface.js +5 -0
- package/dist/exchanges/lighter.d.ts +159 -0
- package/dist/exchanges/lighter.js +793 -0
- package/dist/exchanges/pacifica.d.ts +51 -0
- package/dist/exchanges/pacifica.js +248 -0
- package/dist/execution-log.d.ts +36 -0
- package/dist/execution-log.js +102 -0
- package/dist/funding/history.d.ts +63 -0
- package/dist/funding/history.js +266 -0
- package/dist/funding/index.d.ts +3 -0
- package/dist/funding/index.js +3 -0
- package/dist/funding/normalize.d.ts +39 -0
- package/dist/funding/normalize.js +66 -0
- package/dist/funding/rates.d.ts +45 -0
- package/dist/funding/rates.js +172 -0
- package/dist/funding-history.d.ts +5 -0
- package/dist/funding-history.js +5 -0
- package/dist/funding-rates.d.ts +5 -0
- package/dist/funding-rates.js +5 -0
- package/dist/funding.d.ts +5 -0
- package/dist/funding.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +458 -0
- package/dist/jobs.d.ts +37 -0
- package/dist/jobs.js +152 -0
- package/dist/liquidity.d.ts +34 -0
- package/dist/liquidity.js +100 -0
- package/dist/mcp-server.d.ts +9 -0
- package/dist/mcp-server.js +1206 -0
- package/dist/pacifica/client.d.ts +111 -0
- package/dist/pacifica/client.js +310 -0
- package/dist/pacifica/constants.d.ts +27 -0
- package/dist/pacifica/constants.js +47 -0
- package/dist/pacifica/deposit.d.ts +14 -0
- package/dist/pacifica/deposit.js +78 -0
- package/dist/pacifica/index.d.ts +6 -0
- package/dist/pacifica/index.js +11 -0
- package/dist/pacifica/signing.d.ts +49 -0
- package/dist/pacifica/signing.js +97 -0
- package/dist/pacifica/types/account.d.ts +42 -0
- package/dist/pacifica/types/account.js +1 -0
- package/dist/pacifica/types/index.d.ts +6 -0
- package/dist/pacifica/types/index.js +6 -0
- package/dist/pacifica/types/lake.d.ts +18 -0
- package/dist/pacifica/types/lake.js +1 -0
- package/dist/pacifica/types/market.d.ts +64 -0
- package/dist/pacifica/types/market.js +1 -0
- package/dist/pacifica/types/order.d.ts +92 -0
- package/dist/pacifica/types/order.js +1 -0
- package/dist/pacifica/types/position.d.ts +25 -0
- package/dist/pacifica/types/position.js +1 -0
- package/dist/pacifica/types/ws.d.ts +34 -0
- package/dist/pacifica/types/ws.js +41 -0
- package/dist/pacifica/ws-client.d.ts +42 -0
- package/dist/pacifica/ws-client.js +180 -0
- package/dist/plan-executor.d.ts +48 -0
- package/dist/plan-executor.js +280 -0
- package/dist/position-history.d.ts +68 -0
- package/dist/position-history.js +222 -0
- package/dist/rebalance.d.ts +64 -0
- package/dist/rebalance.js +142 -0
- package/dist/retry.d.ts +74 -0
- package/dist/retry.js +129 -0
- package/dist/risk.d.ts +48 -0
- package/dist/risk.js +156 -0
- package/dist/settings.d.ts +19 -0
- package/dist/settings.js +45 -0
- package/dist/shared-api.d.ts +5 -0
- package/dist/shared-api.js +5 -0
- package/dist/strategies/dca.d.ts +25 -0
- package/dist/strategies/dca.js +114 -0
- package/dist/strategies/funding-arb.d.ts +15 -0
- package/dist/strategies/funding-arb.js +281 -0
- package/dist/strategies/grid.d.ts +34 -0
- package/dist/strategies/grid.js +185 -0
- package/dist/strategies/trailing-stop.d.ts +17 -0
- package/dist/strategies/trailing-stop.js +121 -0
- package/dist/strategies/twap.d.ts +20 -0
- package/dist/strategies/twap.js +78 -0
- package/dist/trade-validator.d.ts +39 -0
- package/dist/trade-validator.js +154 -0
- package/dist/utils.d.ts +38 -0
- package/dist/utils.js +110 -0
- package/package.json +63 -0
- package/skills/perp-cli/SKILL.md +149 -0
- 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
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|