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,553 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll } from "vitest";
|
|
2
|
+
import { HyperliquidAdapter } from "../../exchanges/hyperliquid.js";
|
|
3
|
+
import { validateTrade } from "../../trade-validator.js";
|
|
4
|
+
import { startEventStream } from "../../event-stream.js";
|
|
5
|
+
import { classifyError, ERROR_CODES } from "../../errors.js";
|
|
6
|
+
import { validatePlan } from "../../plan-executor.js";
|
|
7
|
+
/**
|
|
8
|
+
* Integration tests for agent-friendly features against REAL Hyperliquid mainnet API.
|
|
9
|
+
*
|
|
10
|
+
* These tests are READ-ONLY — no orders are placed, no private key is needed for
|
|
11
|
+
* most operations. We use a dummy private key to satisfy the adapter constructor
|
|
12
|
+
* and derive a deterministic (empty) address for account queries.
|
|
13
|
+
*
|
|
14
|
+
* Run:
|
|
15
|
+
* pnpm --filter perp-cli test -- --testPathPattern integration/agent-features
|
|
16
|
+
*/
|
|
17
|
+
// Dummy private key — valid 32-byte hex, derives a real (but empty) address.
|
|
18
|
+
// No funds, no positions — that is expected and part of the test.
|
|
19
|
+
const DUMMY_PRIVATE_KEY = "0x" + "1".repeat(64);
|
|
20
|
+
/** Helper: find market by base symbol (handles BTC vs BTC-PERP) */
|
|
21
|
+
function findMarket(markets, base) {
|
|
22
|
+
return markets.find(m => m.symbol === base || m.symbol === `${base}-PERP` || m.symbol.replace(/-PERP$/, "") === base);
|
|
23
|
+
}
|
|
24
|
+
describe("Agent Features Integration (Hyperliquid Mainnet)", () => {
|
|
25
|
+
let adapter;
|
|
26
|
+
beforeAll(async () => {
|
|
27
|
+
adapter = new HyperliquidAdapter(DUMMY_PRIVATE_KEY, false);
|
|
28
|
+
await adapter.init();
|
|
29
|
+
}, 30_000);
|
|
30
|
+
// ─────────────────────────────────────────────────────────────────
|
|
31
|
+
// 1. trade-validator with real market data
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────
|
|
33
|
+
describe("trade-validator with real market data", () => {
|
|
34
|
+
it("validates a BTC market buy and returns correct structure", async () => {
|
|
35
|
+
const result = await validateTrade(adapter, {
|
|
36
|
+
symbol: "BTC",
|
|
37
|
+
side: "buy",
|
|
38
|
+
size: 0.001,
|
|
39
|
+
type: "market",
|
|
40
|
+
});
|
|
41
|
+
// Structure checks
|
|
42
|
+
expect(result).toHaveProperty("valid");
|
|
43
|
+
expect(typeof result.valid).toBe("boolean");
|
|
44
|
+
expect(Array.isArray(result.checks)).toBe(true);
|
|
45
|
+
expect(result.checks.length).toBeGreaterThan(0);
|
|
46
|
+
expect(result).toHaveProperty("timestamp");
|
|
47
|
+
expect(new Date(result.timestamp).getTime()).toBeGreaterThan(0);
|
|
48
|
+
// Symbol check must pass — BTC exists on Hyperliquid
|
|
49
|
+
const symbolCheck = result.checks.find(c => c.check === "symbol_valid");
|
|
50
|
+
expect(symbolCheck).toBeTruthy();
|
|
51
|
+
expect(symbolCheck.passed).toBe(true);
|
|
52
|
+
expect(symbolCheck.message).toContain("BTC");
|
|
53
|
+
// Market info must have a real mark price
|
|
54
|
+
expect(result.marketInfo).toBeTruthy();
|
|
55
|
+
expect(result.marketInfo.symbol).toBe("BTC");
|
|
56
|
+
expect(result.marketInfo.markPrice).toBeGreaterThan(0);
|
|
57
|
+
expect(result.marketInfo.maxLeverage).toBeGreaterThan(0);
|
|
58
|
+
// Estimated cost should exist
|
|
59
|
+
expect(result.estimatedCost).toBeTruthy();
|
|
60
|
+
expect(result.estimatedCost.margin).toBeGreaterThan(0);
|
|
61
|
+
expect(result.estimatedCost.fee).toBeGreaterThan(0);
|
|
62
|
+
}, 30_000);
|
|
63
|
+
it("validates ETH and SOL symbols as valid", async () => {
|
|
64
|
+
const [ethResult, solResult] = await Promise.all([
|
|
65
|
+
validateTrade(adapter, { symbol: "ETH", side: "sell", size: 0.01, type: "market" }),
|
|
66
|
+
validateTrade(adapter, { symbol: "SOL", side: "buy", size: 0.1, type: "market" }),
|
|
67
|
+
]);
|
|
68
|
+
const ethSymbol = ethResult.checks.find(c => c.check === "symbol_valid");
|
|
69
|
+
expect(ethSymbol?.passed).toBe(true);
|
|
70
|
+
expect(ethResult.marketInfo?.markPrice).toBeGreaterThan(0);
|
|
71
|
+
const solSymbol = solResult.checks.find(c => c.check === "symbol_valid");
|
|
72
|
+
expect(solSymbol?.passed).toBe(true);
|
|
73
|
+
expect(solResult.marketInfo?.markPrice).toBeGreaterThan(0);
|
|
74
|
+
}, 30_000);
|
|
75
|
+
it("rejects a non-existent symbol", async () => {
|
|
76
|
+
const result = await validateTrade(adapter, {
|
|
77
|
+
symbol: "XYZNOTREAL999",
|
|
78
|
+
side: "buy",
|
|
79
|
+
size: 1,
|
|
80
|
+
type: "market",
|
|
81
|
+
});
|
|
82
|
+
expect(result.valid).toBe(false);
|
|
83
|
+
const symbolCheck = result.checks.find(c => c.check === "symbol_valid");
|
|
84
|
+
expect(symbolCheck).toBeTruthy();
|
|
85
|
+
expect(symbolCheck.passed).toBe(false);
|
|
86
|
+
expect(symbolCheck.message).toContain("XYZNOTREAL999");
|
|
87
|
+
// Early return means no market info
|
|
88
|
+
expect(result.marketInfo).toBeUndefined();
|
|
89
|
+
}, 30_000);
|
|
90
|
+
it("balance check fails for empty account (dummy key)", async () => {
|
|
91
|
+
const result = await validateTrade(adapter, {
|
|
92
|
+
symbol: "BTC",
|
|
93
|
+
side: "buy",
|
|
94
|
+
size: 0.001,
|
|
95
|
+
type: "market",
|
|
96
|
+
});
|
|
97
|
+
// With a dummy key the account has $0 balance, so balance check should fail
|
|
98
|
+
// (unless the trade is tiny enough that margin rounds to 0)
|
|
99
|
+
const balanceCheck = result.checks.find(c => c.check === "balance_sufficient");
|
|
100
|
+
expect(balanceCheck).toBeTruthy();
|
|
101
|
+
// The check should fail because 0 available < margin required for BTC
|
|
102
|
+
expect(balanceCheck.passed).toBe(false);
|
|
103
|
+
expect(balanceCheck.message).toContain("Insufficient");
|
|
104
|
+
}, 30_000);
|
|
105
|
+
it("liquidity check runs for market orders", async () => {
|
|
106
|
+
const result = await validateTrade(adapter, {
|
|
107
|
+
symbol: "BTC",
|
|
108
|
+
side: "buy",
|
|
109
|
+
size: 0.001,
|
|
110
|
+
type: "market",
|
|
111
|
+
});
|
|
112
|
+
const liqCheck = result.checks.find(c => c.check === "liquidity_ok");
|
|
113
|
+
expect(liqCheck).toBeTruthy();
|
|
114
|
+
// BTC is highly liquid — a 0.001 BTC order should pass liquidity check
|
|
115
|
+
expect(liqCheck.passed).toBe(true);
|
|
116
|
+
}, 30_000);
|
|
117
|
+
});
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────
|
|
119
|
+
// 2. event-stream with real data (single poll)
|
|
120
|
+
// ─────────────────────────────────────────────────────────────────
|
|
121
|
+
describe("event-stream with real data", () => {
|
|
122
|
+
it("completes a single poll cycle without crashing", async () => {
|
|
123
|
+
const events = [];
|
|
124
|
+
const controller = new AbortController();
|
|
125
|
+
// Abort immediately after the first poll completes.
|
|
126
|
+
// startEventStream does an initial poll, then enters the while-loop.
|
|
127
|
+
// We signal abort so the loop exits after the first iteration.
|
|
128
|
+
setTimeout(() => controller.abort(), 100);
|
|
129
|
+
await startEventStream(adapter, {
|
|
130
|
+
intervalMs: 60_000, // long interval so only the initial poll runs
|
|
131
|
+
onEvent: (event) => {
|
|
132
|
+
events.push(event);
|
|
133
|
+
},
|
|
134
|
+
signal: controller.signal,
|
|
135
|
+
});
|
|
136
|
+
// With an empty account we expect 0 events (no positions, no orders, no balance changes).
|
|
137
|
+
// The key assertion: it did NOT throw. Real API data was fetched and processed.
|
|
138
|
+
expect(Array.isArray(events)).toBe(true);
|
|
139
|
+
// If any events were emitted, they must have valid structure
|
|
140
|
+
for (const ev of events) {
|
|
141
|
+
expect(ev).toHaveProperty("type");
|
|
142
|
+
expect(ev).toHaveProperty("exchange");
|
|
143
|
+
expect(ev).toHaveProperty("timestamp");
|
|
144
|
+
expect(ev).toHaveProperty("data");
|
|
145
|
+
expect(ev.exchange).toBe("hyperliquid");
|
|
146
|
+
expect(new Date(ev.timestamp).getTime()).toBeGreaterThan(0);
|
|
147
|
+
}
|
|
148
|
+
}, 30_000);
|
|
149
|
+
it("emits error event on invalid adapter state gracefully", async () => {
|
|
150
|
+
// Create an adapter that will fail on API calls (testnet URL with mainnet-ish key)
|
|
151
|
+
// Actually, even a bad address just returns empty data, not errors.
|
|
152
|
+
// Instead, test that abort signal works correctly.
|
|
153
|
+
const events = [];
|
|
154
|
+
const controller = new AbortController();
|
|
155
|
+
// Abort before even the first poll interval elapses
|
|
156
|
+
controller.abort();
|
|
157
|
+
await startEventStream(adapter, {
|
|
158
|
+
intervalMs: 1000,
|
|
159
|
+
onEvent: (event) => events.push(event),
|
|
160
|
+
signal: controller.signal,
|
|
161
|
+
});
|
|
162
|
+
// The initial poll should still have run (abort is checked after poll)
|
|
163
|
+
// No crash = success
|
|
164
|
+
expect(true).toBe(true);
|
|
165
|
+
}, 30_000);
|
|
166
|
+
});
|
|
167
|
+
// ─────────────────────────────────────────────────────────────────
|
|
168
|
+
// 3. errors.ts classifyError with real exchange error patterns
|
|
169
|
+
// ─────────────────────────────────────────────────────────────────
|
|
170
|
+
describe("classifyError with real exchange error patterns", () => {
|
|
171
|
+
it("classifies 'Insufficient margin' as MARGIN_INSUFFICIENT", () => {
|
|
172
|
+
const err = classifyError(new Error("Insufficient margin for this order"), "hyperliquid");
|
|
173
|
+
expect(err.code).toBe("MARGIN_INSUFFICIENT");
|
|
174
|
+
expect(err.retryable).toBe(false);
|
|
175
|
+
expect(err.exchange).toBe("hyperliquid");
|
|
176
|
+
expect(err.status).toBe(400);
|
|
177
|
+
});
|
|
178
|
+
it("classifies 'Asset not found' as SYMBOL_NOT_FOUND", () => {
|
|
179
|
+
const err = classifyError(new Error("Asset not found: XYZABC"), "hyperliquid");
|
|
180
|
+
expect(err.code).toBe("SYMBOL_NOT_FOUND");
|
|
181
|
+
expect(err.retryable).toBe(false);
|
|
182
|
+
expect(err.status).toBe(404);
|
|
183
|
+
});
|
|
184
|
+
it("classifies rate limit response as RATE_LIMITED", () => {
|
|
185
|
+
const err = classifyError(new Error("429 Too Many Requests - rate limit exceeded"), "hyperliquid");
|
|
186
|
+
expect(err.code).toBe("RATE_LIMITED");
|
|
187
|
+
expect(err.retryable).toBe(true);
|
|
188
|
+
expect(err.retryAfterMs).toBe(1000);
|
|
189
|
+
expect(err.status).toBe(429);
|
|
190
|
+
});
|
|
191
|
+
it("classifies network error as EXCHANGE_UNREACHABLE", () => {
|
|
192
|
+
const cases = [
|
|
193
|
+
"fetch failed: ECONNREFUSED",
|
|
194
|
+
"ENOTFOUND api.hyperliquid.xyz",
|
|
195
|
+
"Network error: unable to reach server",
|
|
196
|
+
];
|
|
197
|
+
for (const msg of cases) {
|
|
198
|
+
const err = classifyError(new Error(msg), "hyperliquid");
|
|
199
|
+
expect(err.code).toBe("EXCHANGE_UNREACHABLE");
|
|
200
|
+
expect(err.retryable).toBe(true);
|
|
201
|
+
expect(err.status).toBe(503);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
it("classifies timeout as TIMEOUT", () => {
|
|
205
|
+
const err = classifyError(new Error("Request timed out after 30000ms"), "hyperliquid");
|
|
206
|
+
expect(err.code).toBe("TIMEOUT");
|
|
207
|
+
expect(err.retryable).toBe(true);
|
|
208
|
+
expect(err.status).toBe(504);
|
|
209
|
+
});
|
|
210
|
+
it("classifies unknown Hyperliquid error as EXCHANGE_ERROR", () => {
|
|
211
|
+
const err = classifyError(new Error("Some unexpected internal error from HL"), "hyperliquid");
|
|
212
|
+
expect(err.code).toBe("EXCHANGE_ERROR");
|
|
213
|
+
expect(err.exchange).toBe("hyperliquid");
|
|
214
|
+
expect(err.retryable).toBe(true);
|
|
215
|
+
});
|
|
216
|
+
it("preserves original error message in structured output", () => {
|
|
217
|
+
const originalMsg = "Margin not enough for cross-mode order on BTC";
|
|
218
|
+
const err = classifyError(new Error(originalMsg), "hyperliquid");
|
|
219
|
+
expect(err.message).toBe(originalMsg);
|
|
220
|
+
});
|
|
221
|
+
it("handles string inputs (non-Error objects)", () => {
|
|
222
|
+
const err = classifyError("rate limit hit", "hyperliquid");
|
|
223
|
+
expect(err.code).toBe("RATE_LIMITED");
|
|
224
|
+
expect(err.message).toBe("rate limit hit");
|
|
225
|
+
});
|
|
226
|
+
it("all ERROR_CODES have consistent structure", () => {
|
|
227
|
+
for (const [key, val] of Object.entries(ERROR_CODES)) {
|
|
228
|
+
expect(val.code).toBe(key);
|
|
229
|
+
expect(typeof val.status).toBe("number");
|
|
230
|
+
expect(typeof val.retryable).toBe("boolean");
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
// ─────────────────────────────────────────────────────────────────
|
|
235
|
+
// 4. plan-executor validatePlan with realistic plans
|
|
236
|
+
// ─────────────────────────────────────────────────────────────────
|
|
237
|
+
describe("plan-executor validatePlan with realistic plans", () => {
|
|
238
|
+
it("validates a realistic multi-step BTC long plan", () => {
|
|
239
|
+
const plan = {
|
|
240
|
+
version: "1.0",
|
|
241
|
+
exchange: "hyperliquid",
|
|
242
|
+
description: "Open leveraged BTC long with stop loss and take profit",
|
|
243
|
+
steps: [
|
|
244
|
+
{
|
|
245
|
+
id: "check-balance",
|
|
246
|
+
action: "check_balance",
|
|
247
|
+
params: { minAvailable: 100 },
|
|
248
|
+
onFailure: "abort",
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: "set-lev",
|
|
252
|
+
action: "set_leverage",
|
|
253
|
+
params: { symbol: "BTC", leverage: 10 },
|
|
254
|
+
dependsOn: "check-balance",
|
|
255
|
+
onFailure: "abort",
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
id: "open-long",
|
|
259
|
+
action: "market_order",
|
|
260
|
+
params: { symbol: "BTC", side: "buy", size: "0.001" },
|
|
261
|
+
dependsOn: "set-lev",
|
|
262
|
+
onFailure: "abort",
|
|
263
|
+
clientId: "btc-long-001",
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: "set-sl",
|
|
267
|
+
action: "stop_order",
|
|
268
|
+
params: { symbol: "BTC", side: "sell", size: "0.001", triggerPrice: "90000" },
|
|
269
|
+
dependsOn: "open-long",
|
|
270
|
+
onFailure: "skip",
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
id: "verify-position",
|
|
274
|
+
action: "check_position",
|
|
275
|
+
params: { symbol: "BTC", mustExist: true },
|
|
276
|
+
dependsOn: "open-long",
|
|
277
|
+
onFailure: "skip",
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
};
|
|
281
|
+
const result = validatePlan(plan);
|
|
282
|
+
expect(result.valid).toBe(true);
|
|
283
|
+
expect(result.errors).toHaveLength(0);
|
|
284
|
+
});
|
|
285
|
+
it("validates a multi-asset hedging plan", () => {
|
|
286
|
+
const plan = {
|
|
287
|
+
version: "1.0",
|
|
288
|
+
exchange: "hyperliquid",
|
|
289
|
+
description: "Hedge BTC long with ETH and SOL shorts",
|
|
290
|
+
steps: [
|
|
291
|
+
{
|
|
292
|
+
id: "long-btc",
|
|
293
|
+
action: "market_order",
|
|
294
|
+
params: { symbol: "BTC", side: "buy", size: "0.01" },
|
|
295
|
+
onFailure: "abort",
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
id: "short-eth",
|
|
299
|
+
action: "market_order",
|
|
300
|
+
params: { symbol: "ETH", side: "sell", size: "0.1" },
|
|
301
|
+
dependsOn: "long-btc",
|
|
302
|
+
onFailure: "skip",
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
id: "short-sol",
|
|
306
|
+
action: "market_order",
|
|
307
|
+
params: { symbol: "SOL", side: "sell", size: "1" },
|
|
308
|
+
dependsOn: "long-btc",
|
|
309
|
+
onFailure: "skip",
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
id: "wait-settle",
|
|
313
|
+
action: "wait",
|
|
314
|
+
params: { ms: 2000 },
|
|
315
|
+
dependsOn: "long-btc",
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: "check-btc-pos",
|
|
319
|
+
action: "check_position",
|
|
320
|
+
params: { symbol: "BTC", mustExist: true },
|
|
321
|
+
dependsOn: "wait-settle",
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
};
|
|
325
|
+
const result = validatePlan(plan);
|
|
326
|
+
expect(result.valid).toBe(true);
|
|
327
|
+
expect(result.errors).toHaveLength(0);
|
|
328
|
+
});
|
|
329
|
+
it("validates a close-all-and-cancel plan", () => {
|
|
330
|
+
const plan = {
|
|
331
|
+
version: "1.0",
|
|
332
|
+
description: "Emergency: cancel all orders and close BTC position",
|
|
333
|
+
steps: [
|
|
334
|
+
{
|
|
335
|
+
id: "cancel-all",
|
|
336
|
+
action: "cancel_all",
|
|
337
|
+
params: {},
|
|
338
|
+
onFailure: "skip",
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
id: "close-btc",
|
|
342
|
+
action: "close_position",
|
|
343
|
+
params: { symbol: "BTC" },
|
|
344
|
+
dependsOn: "cancel-all",
|
|
345
|
+
onFailure: "rollback",
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
};
|
|
349
|
+
const result = validatePlan(plan);
|
|
350
|
+
expect(result.valid).toBe(true);
|
|
351
|
+
expect(result.errors).toHaveLength(0);
|
|
352
|
+
});
|
|
353
|
+
it("rejects plan with missing required params", () => {
|
|
354
|
+
const plan = {
|
|
355
|
+
version: "1.0",
|
|
356
|
+
steps: [
|
|
357
|
+
{
|
|
358
|
+
id: "bad-order",
|
|
359
|
+
action: "market_order",
|
|
360
|
+
params: { symbol: "BTC" }, // missing side and size
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
};
|
|
364
|
+
const result = validatePlan(plan);
|
|
365
|
+
expect(result.valid).toBe(false);
|
|
366
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
367
|
+
expect(result.errors.some(e => e.includes("side"))).toBe(true);
|
|
368
|
+
expect(result.errors.some(e => e.includes("size"))).toBe(true);
|
|
369
|
+
});
|
|
370
|
+
it("rejects plan with invalid action", () => {
|
|
371
|
+
const plan = {
|
|
372
|
+
version: "1.0",
|
|
373
|
+
steps: [
|
|
374
|
+
{
|
|
375
|
+
id: "bad-action",
|
|
376
|
+
action: "nuke_everything",
|
|
377
|
+
params: {},
|
|
378
|
+
},
|
|
379
|
+
],
|
|
380
|
+
};
|
|
381
|
+
const result = validatePlan(plan);
|
|
382
|
+
expect(result.valid).toBe(false);
|
|
383
|
+
expect(result.errors.some(e => e.includes("invalid action"))).toBe(true);
|
|
384
|
+
});
|
|
385
|
+
it("rejects plan with duplicate step IDs", () => {
|
|
386
|
+
const plan = {
|
|
387
|
+
version: "1.0",
|
|
388
|
+
steps: [
|
|
389
|
+
{ id: "step-1", action: "check_balance", params: {} },
|
|
390
|
+
{ id: "step-1", action: "check_balance", params: {} },
|
|
391
|
+
],
|
|
392
|
+
};
|
|
393
|
+
const result = validatePlan(plan);
|
|
394
|
+
expect(result.valid).toBe(false);
|
|
395
|
+
expect(result.errors.some(e => e.includes("duplicate"))).toBe(true);
|
|
396
|
+
});
|
|
397
|
+
it("rejects plan with non-existent dependsOn reference", () => {
|
|
398
|
+
const plan = {
|
|
399
|
+
version: "1.0",
|
|
400
|
+
steps: [
|
|
401
|
+
{
|
|
402
|
+
id: "step-1",
|
|
403
|
+
action: "market_order",
|
|
404
|
+
params: { symbol: "BTC", side: "buy", size: "0.001" },
|
|
405
|
+
dependsOn: "phantom-step",
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
};
|
|
409
|
+
const result = validatePlan(plan);
|
|
410
|
+
expect(result.valid).toBe(false);
|
|
411
|
+
expect(result.errors.some(e => e.includes("phantom-step"))).toBe(true);
|
|
412
|
+
});
|
|
413
|
+
it("rejects plan with wrong version", () => {
|
|
414
|
+
const plan = {
|
|
415
|
+
version: "2.0",
|
|
416
|
+
steps: [{ id: "s1", action: "check_balance", params: {} }],
|
|
417
|
+
};
|
|
418
|
+
const result = validatePlan(plan);
|
|
419
|
+
expect(result.valid).toBe(false);
|
|
420
|
+
expect(result.errors.some(e => e.includes("version"))).toBe(true);
|
|
421
|
+
});
|
|
422
|
+
it("validates a limit order plan with price param", () => {
|
|
423
|
+
const plan = {
|
|
424
|
+
version: "1.0",
|
|
425
|
+
steps: [
|
|
426
|
+
{
|
|
427
|
+
id: "limit-buy",
|
|
428
|
+
action: "limit_order",
|
|
429
|
+
params: { symbol: "ETH", side: "buy", size: "0.1", price: "1500" },
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
};
|
|
433
|
+
const result = validatePlan(plan);
|
|
434
|
+
expect(result.valid).toBe(true);
|
|
435
|
+
});
|
|
436
|
+
it("rejects limit order without price", () => {
|
|
437
|
+
const plan = {
|
|
438
|
+
version: "1.0",
|
|
439
|
+
steps: [
|
|
440
|
+
{
|
|
441
|
+
id: "bad-limit",
|
|
442
|
+
action: "limit_order",
|
|
443
|
+
params: { symbol: "ETH", side: "buy", size: "0.1" },
|
|
444
|
+
},
|
|
445
|
+
],
|
|
446
|
+
};
|
|
447
|
+
const result = validatePlan(plan);
|
|
448
|
+
expect(result.valid).toBe(false);
|
|
449
|
+
expect(result.errors.some(e => e.includes("price"))).toBe(true);
|
|
450
|
+
});
|
|
451
|
+
});
|
|
452
|
+
// ─────────────────────────────────────────────────────────────────
|
|
453
|
+
// 5. Schema generation with real adapter context
|
|
454
|
+
// ─────────────────────────────────────────────────────────────────
|
|
455
|
+
describe("schema and adapter capabilities", () => {
|
|
456
|
+
it("adapter exposes correct name", () => {
|
|
457
|
+
expect(adapter.name).toBe("hyperliquid");
|
|
458
|
+
});
|
|
459
|
+
it("adapter has loaded a non-empty asset map after init", async () => {
|
|
460
|
+
// HL SDK mainnet uses BTC-PERP style names in asset map
|
|
461
|
+
const markets = await adapter.getMarkets();
|
|
462
|
+
expect(markets.length).toBeGreaterThan(50);
|
|
463
|
+
// Verify we can look up at least one symbol using the exact format from the map
|
|
464
|
+
const firstSymbol = markets[0].symbol;
|
|
465
|
+
expect(adapter.getAssetIndex(firstSymbol)).toBeGreaterThanOrEqual(0);
|
|
466
|
+
}, 30_000);
|
|
467
|
+
it("adapter getAssetIndex throws for unknown symbol", () => {
|
|
468
|
+
expect(() => adapter.getAssetIndex("XYZNOTREAL999FAKE")).toThrow("Unknown symbol");
|
|
469
|
+
});
|
|
470
|
+
it("adapter getMarkets returns well-formed data from real API", async () => {
|
|
471
|
+
const markets = await adapter.getMarkets();
|
|
472
|
+
expect(markets.length).toBeGreaterThan(50); // HL has 100+ perps
|
|
473
|
+
const btc = findMarket(markets, "BTC");
|
|
474
|
+
expect(btc).toBeTruthy();
|
|
475
|
+
expect(Number(btc.markPrice)).toBeGreaterThan(1000);
|
|
476
|
+
expect(btc.maxLeverage).toBeGreaterThanOrEqual(20);
|
|
477
|
+
expect(Number(btc.volume24h)).toBeGreaterThan(0);
|
|
478
|
+
expect(Number(btc.openInterest)).toBeGreaterThan(0);
|
|
479
|
+
// All markets have required fields
|
|
480
|
+
for (const m of markets) {
|
|
481
|
+
expect(m.symbol).toBeTruthy();
|
|
482
|
+
expect(typeof m.markPrice).toBe("string");
|
|
483
|
+
expect(typeof m.fundingRate).toBe("string");
|
|
484
|
+
expect(typeof m.maxLeverage).toBe("number");
|
|
485
|
+
}
|
|
486
|
+
}, 30_000);
|
|
487
|
+
it("adapter getOrderbook returns bids and asks for BTC", async () => {
|
|
488
|
+
// HL SDK mainnet may need exact symbol (BTC-PERP)
|
|
489
|
+
const markets = await adapter.getMarkets();
|
|
490
|
+
const btcSym = findMarket(markets, "BTC").symbol;
|
|
491
|
+
const book = await adapter.getOrderbook(btcSym);
|
|
492
|
+
expect(book.bids.length).toBeGreaterThan(0);
|
|
493
|
+
expect(book.asks.length).toBeGreaterThan(0);
|
|
494
|
+
// Bids and asks are [price, size] tuples
|
|
495
|
+
const [bidPrice, bidSize] = book.bids[0];
|
|
496
|
+
expect(Number(bidPrice)).toBeGreaterThan(0);
|
|
497
|
+
expect(Number(bidSize)).toBeGreaterThan(0);
|
|
498
|
+
const [askPrice, askSize] = book.asks[0];
|
|
499
|
+
expect(Number(askPrice)).toBeGreaterThan(0);
|
|
500
|
+
expect(Number(askSize)).toBeGreaterThan(0);
|
|
501
|
+
// Best ask should be >= best bid
|
|
502
|
+
expect(Number(askPrice)).toBeGreaterThanOrEqual(Number(bidPrice));
|
|
503
|
+
}, 30_000);
|
|
504
|
+
it("adapter getBalance returns numeric fields for empty account", async () => {
|
|
505
|
+
const balance = await adapter.getBalance();
|
|
506
|
+
expect(balance).toHaveProperty("equity");
|
|
507
|
+
expect(balance).toHaveProperty("available");
|
|
508
|
+
expect(balance).toHaveProperty("marginUsed");
|
|
509
|
+
expect(balance).toHaveProperty("unrealizedPnl");
|
|
510
|
+
// All should be parseable as numbers
|
|
511
|
+
expect(Number.isFinite(Number(balance.equity))).toBe(true);
|
|
512
|
+
expect(Number.isFinite(Number(balance.available))).toBe(true);
|
|
513
|
+
expect(Number.isFinite(Number(balance.marginUsed))).toBe(true);
|
|
514
|
+
expect(Number.isFinite(Number(balance.unrealizedPnl))).toBe(true);
|
|
515
|
+
}, 30_000);
|
|
516
|
+
it("adapter getPositions returns an array (empty for dummy account)", async () => {
|
|
517
|
+
const positions = await adapter.getPositions();
|
|
518
|
+
expect(Array.isArray(positions)).toBe(true);
|
|
519
|
+
// Dummy account should have no positions
|
|
520
|
+
expect(positions.length).toBe(0);
|
|
521
|
+
}, 30_000);
|
|
522
|
+
it("adapter getOpenOrders returns an array (empty for dummy account)", async () => {
|
|
523
|
+
const orders = await adapter.getOpenOrders();
|
|
524
|
+
expect(Array.isArray(orders)).toBe(true);
|
|
525
|
+
expect(orders.length).toBe(0);
|
|
526
|
+
}, 30_000);
|
|
527
|
+
it("ERROR_CODES cover all expected agent error scenarios", () => {
|
|
528
|
+
const expectedCodes = [
|
|
529
|
+
"INVALID_PARAMS",
|
|
530
|
+
"SYMBOL_NOT_FOUND",
|
|
531
|
+
"ORDER_NOT_FOUND",
|
|
532
|
+
"POSITION_NOT_FOUND",
|
|
533
|
+
"INSUFFICIENT_BALANCE",
|
|
534
|
+
"MARGIN_INSUFFICIENT",
|
|
535
|
+
"SIZE_TOO_SMALL",
|
|
536
|
+
"SIZE_TOO_LARGE",
|
|
537
|
+
"RISK_VIOLATION",
|
|
538
|
+
"DUPLICATE_ORDER",
|
|
539
|
+
"EXCHANGE_UNREACHABLE",
|
|
540
|
+
"RATE_LIMITED",
|
|
541
|
+
"PRICE_STALE",
|
|
542
|
+
"SIGNATURE_FAILED",
|
|
543
|
+
"EXCHANGE_ERROR",
|
|
544
|
+
"TIMEOUT",
|
|
545
|
+
"UNKNOWN",
|
|
546
|
+
];
|
|
547
|
+
for (const code of expectedCodes) {
|
|
548
|
+
expect(ERROR_CODES).toHaveProperty(code);
|
|
549
|
+
expect(ERROR_CODES[code].code).toBe(code);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
});
|
|
553
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration tests for new atomic commands against Hyperliquid mainnet (read-only).
|
|
3
|
+
*
|
|
4
|
+
* Tests:
|
|
5
|
+
* - market mid: real orderbook mid price
|
|
6
|
+
* - account margin: position-not-found for dummy/real account
|
|
7
|
+
* - trade status: order-not-found for dummy order
|
|
8
|
+
* - trade fills: empty fills for dummy account / real fills if any
|
|
9
|
+
* - Error response shapes and classification
|
|
10
|
+
*
|
|
11
|
+
* Requires: HYPERLIQUID_PRIVATE_KEY or HL_PRIVATE_KEY env var
|
|
12
|
+
*/
|
|
13
|
+
import "dotenv/config";
|