@raintree-technology/perps 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +175 -0
- package/dist/adapters/aevo.d.ts +64 -0
- package/dist/adapters/aevo.js +899 -0
- package/dist/adapters/certification.d.ts +33 -0
- package/dist/adapters/certification.js +99 -0
- package/dist/adapters/decibel/order-manager.d.ts +45 -0
- package/dist/adapters/decibel/order-manager.js +140 -0
- package/dist/adapters/decibel/rest-client.d.ts +176 -0
- package/dist/adapters/decibel/rest-client.js +155 -0
- package/dist/adapters/decibel/ws-feed.d.ts +28 -0
- package/dist/adapters/decibel/ws-feed.js +166 -0
- package/dist/adapters/decibel.d.ts +108 -0
- package/dist/adapters/decibel.js +1377 -0
- package/dist/adapters/hyperliquid.d.ts +63 -0
- package/dist/adapters/hyperliquid.js +797 -0
- package/dist/adapters/index.d.ts +11 -0
- package/dist/adapters/index.js +12 -0
- package/dist/adapters/interface.d.ts +310 -0
- package/dist/adapters/interface.js +15 -0
- package/dist/adapters/orderly.d.ts +70 -0
- package/dist/adapters/orderly.js +936 -0
- package/dist/adapters/paradex.d.ts +69 -0
- package/dist/adapters/paradex.js +862 -0
- package/dist/adapters/utils.d.ts +17 -0
- package/dist/adapters/utils.js +122 -0
- package/dist/cli/command-metadata.d.ts +2 -0
- package/dist/cli/command-metadata.js +44 -0
- package/dist/cli/context.d.ts +14 -0
- package/dist/cli/context.js +59 -0
- package/dist/cli/experience.d.ts +48 -0
- package/dist/cli/experience.js +243 -0
- package/dist/cli/ink/app/AppShell.d.ts +12 -0
- package/dist/cli/ink/app/AppShell.js +32 -0
- package/dist/cli/ink/app/MetricStrip.d.ts +6 -0
- package/dist/cli/ink/app/MetricStrip.js +14 -0
- package/dist/cli/ink/app/Panel.d.ts +9 -0
- package/dist/cli/ink/app/Panel.js +7 -0
- package/dist/cli/ink/app/ascii.d.ts +2 -0
- package/dist/cli/ink/app/ascii.js +46 -0
- package/dist/cli/ink/app/index.d.ts +5 -0
- package/dist/cli/ink/app/index.js +4 -0
- package/dist/cli/ink/app/types.d.ts +15 -0
- package/dist/cli/ink/app/types.js +1 -0
- package/dist/cli/ink/components/PnL.d.ts +12 -0
- package/dist/cli/ink/components/PnL.js +23 -0
- package/dist/cli/ink/components/Spinner.d.ts +13 -0
- package/dist/cli/ink/components/Spinner.js +13 -0
- package/dist/cli/ink/components/Table.d.ts +14 -0
- package/dist/cli/ink/components/Table.js +42 -0
- package/dist/cli/ink/components/WatchHeader.d.ts +10 -0
- package/dist/cli/ink/components/WatchHeader.js +18 -0
- package/dist/cli/ink/components/index.d.ts +4 -0
- package/dist/cli/ink/components/index.js +4 -0
- package/dist/cli/ink/index.d.ts +4 -0
- package/dist/cli/ink/index.js +4 -0
- package/dist/cli/ink/render.d.ts +12 -0
- package/dist/cli/ink/render.js +21 -0
- package/dist/cli/ink/theme.d.ts +29 -0
- package/dist/cli/ink/theme.js +40 -0
- package/dist/cli/network-defaults.d.ts +10 -0
- package/dist/cli/network-defaults.js +35 -0
- package/dist/cli/output.d.ts +11 -0
- package/dist/cli/output.js +115 -0
- package/dist/cli/program.d.ts +18 -0
- package/dist/cli/program.js +164 -0
- package/dist/cli/watch.d.ts +19 -0
- package/dist/cli/watch.js +35 -0
- package/dist/client/index.d.ts +55 -0
- package/dist/client/index.js +157 -0
- package/dist/commands/account/add.d.ts +2 -0
- package/dist/commands/account/add.js +510 -0
- package/dist/commands/account/balances-simple.d.ts +5 -0
- package/dist/commands/account/balances-simple.js +63 -0
- package/dist/commands/account/index.d.ts +2 -0
- package/dist/commands/account/index.js +17 -0
- package/dist/commands/account/ls.d.ts +2 -0
- package/dist/commands/account/ls.js +95 -0
- package/dist/commands/account/positions-simple.d.ts +5 -0
- package/dist/commands/account/positions-simple.js +77 -0
- package/dist/commands/account/remove.d.ts +2 -0
- package/dist/commands/account/remove.js +47 -0
- package/dist/commands/account/set-default.d.ts +2 -0
- package/dist/commands/account/set-default.js +47 -0
- package/dist/commands/agent/index.d.ts +2 -0
- package/dist/commands/agent/index.js +126 -0
- package/dist/commands/arb/alert.d.ts +6 -0
- package/dist/commands/arb/alert.js +88 -0
- package/dist/commands/arb/basis-execute.d.ts +6 -0
- package/dist/commands/arb/basis-execute.js +332 -0
- package/dist/commands/arb/basis.d.ts +6 -0
- package/dist/commands/arb/basis.js +181 -0
- package/dist/commands/arb/compare.d.ts +6 -0
- package/dist/commands/arb/compare.js +216 -0
- package/dist/commands/arb/execute.d.ts +6 -0
- package/dist/commands/arb/execute.js +467 -0
- package/dist/commands/arb/funding.d.ts +6 -0
- package/dist/commands/arb/funding.js +201 -0
- package/dist/commands/arb/history.d.ts +6 -0
- package/dist/commands/arb/history.js +153 -0
- package/dist/commands/arb/index.d.ts +6 -0
- package/dist/commands/arb/index.js +29 -0
- package/dist/commands/arb/positions.d.ts +6 -0
- package/dist/commands/arb/positions.js +158 -0
- package/dist/commands/arb/spread.d.ts +6 -0
- package/dist/commands/arb/spread.js +253 -0
- package/dist/commands/arb/track.d.ts +6 -0
- package/dist/commands/arb/track.js +259 -0
- package/dist/commands/asset/book-simple.d.ts +5 -0
- package/dist/commands/asset/book-simple.js +77 -0
- package/dist/commands/asset/index.d.ts +2 -0
- package/dist/commands/asset/index.js +5 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +161 -0
- package/dist/commands/config/index.d.ts +5 -0
- package/dist/commands/config/index.js +109 -0
- package/dist/commands/data/index.d.ts +31 -0
- package/dist/commands/data/index.js +1466 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +201 -0
- package/dist/commands/exchange/index.d.ts +2 -0
- package/dist/commands/exchange/index.js +107 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +48 -0
- package/dist/commands/markets/index.d.ts +2 -0
- package/dist/commands/markets/index.js +5 -0
- package/dist/commands/markets/ls-simple.d.ts +7 -0
- package/dist/commands/markets/ls-simple.js +277 -0
- package/dist/commands/operator/index.d.ts +2 -0
- package/dist/commands/operator/index.js +146 -0
- package/dist/commands/order/cancel-simple.d.ts +5 -0
- package/dist/commands/order/cancel-simple.js +104 -0
- package/dist/commands/order/index.d.ts +2 -0
- package/dist/commands/order/index.js +13 -0
- package/dist/commands/order/limit-simple.d.ts +5 -0
- package/dist/commands/order/limit-simple.js +195 -0
- package/dist/commands/order/market-simple.d.ts +5 -0
- package/dist/commands/order/market-simple.js +190 -0
- package/dist/commands/order/shared.d.ts +17 -0
- package/dist/commands/order/shared.js +51 -0
- package/dist/commands/order/trigger-simple.d.ts +5 -0
- package/dist/commands/order/trigger-simple.js +246 -0
- package/dist/commands/referral/index.d.ts +2 -0
- package/dist/commands/referral/index.js +7 -0
- package/dist/commands/referral/set.d.ts +2 -0
- package/dist/commands/referral/set.js +26 -0
- package/dist/commands/referral/status.d.ts +2 -0
- package/dist/commands/referral/status.js +31 -0
- package/dist/commands/replay/index.d.ts +2 -0
- package/dist/commands/replay/index.js +152 -0
- package/dist/commands/risk/analytics.d.ts +2 -0
- package/dist/commands/risk/analytics.js +64 -0
- package/dist/commands/risk/audit.d.ts +2 -0
- package/dist/commands/risk/audit.js +52 -0
- package/dist/commands/risk/index.d.ts +2 -0
- package/dist/commands/risk/index.js +9 -0
- package/dist/commands/risk/rules.d.ts +2 -0
- package/dist/commands/risk/rules.js +102 -0
- package/dist/commands/server.d.ts +2 -0
- package/dist/commands/server.js +208 -0
- package/dist/commands/setup/index.d.ts +2 -0
- package/dist/commands/setup/index.js +478 -0
- package/dist/commands/signal/index.d.ts +2 -0
- package/dist/commands/signal/index.js +129 -0
- package/dist/commands/state/index.d.ts +2 -0
- package/dist/commands/state/index.js +5 -0
- package/dist/commands/state/show.d.ts +2 -0
- package/dist/commands/state/show.js +105 -0
- package/dist/commands/strategy/index.d.ts +4 -0
- package/dist/commands/strategy/index.js +73 -0
- package/dist/commands/traces/index.d.ts +2 -0
- package/dist/commands/traces/index.js +76 -0
- package/dist/commands/ui/demo.d.ts +9 -0
- package/dist/commands/ui/demo.js +195 -0
- package/dist/commands/ui/index.d.ts +2 -0
- package/dist/commands/ui/index.js +7 -0
- package/dist/commands/ui/terminal.d.ts +2 -0
- package/dist/commands/ui/terminal.js +255 -0
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +98 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/lib/agent/audit.d.ts +12 -0
- package/dist/lib/agent/audit.js +13 -0
- package/dist/lib/agent/gateway.d.ts +13 -0
- package/dist/lib/agent/gateway.js +598 -0
- package/dist/lib/agent/metrics.d.ts +33 -0
- package/dist/lib/agent/metrics.js +175 -0
- package/dist/lib/agent/signature.d.ts +8 -0
- package/dist/lib/agent/signature.js +28 -0
- package/dist/lib/agent/tools.d.ts +28 -0
- package/dist/lib/agent/tools.js +453 -0
- package/dist/lib/agent/x402.d.ts +23 -0
- package/dist/lib/agent/x402.js +62 -0
- package/dist/lib/api-wallet.d.ts +69 -0
- package/dist/lib/api-wallet.js +101 -0
- package/dist/lib/balance-watcher.d.ts +25 -0
- package/dist/lib/balance-watcher.js +83 -0
- package/dist/lib/book-watcher.d.ts +25 -0
- package/dist/lib/book-watcher.js +48 -0
- package/dist/lib/config.d.ts +88 -0
- package/dist/lib/config.js +427 -0
- package/dist/lib/constants.d.ts +50 -0
- package/dist/lib/constants.js +84 -0
- package/dist/lib/contracts.d.ts +7 -0
- package/dist/lib/contracts.js +8 -0
- package/dist/lib/credential-vault.d.ts +22 -0
- package/dist/lib/credential-vault.js +109 -0
- package/dist/lib/db/accounts.d.ts +83 -0
- package/dist/lib/db/accounts.js +203 -0
- package/dist/lib/db/funding-history.d.ts +69 -0
- package/dist/lib/db/funding-history.js +183 -0
- package/dist/lib/db/index.d.ts +11 -0
- package/dist/lib/db/index.js +272 -0
- package/dist/lib/events/bus.d.ts +10 -0
- package/dist/lib/events/bus.js +17 -0
- package/dist/lib/events/types.d.ts +51 -0
- package/dist/lib/events/types.js +1 -0
- package/dist/lib/exchange.d.ts +30 -0
- package/dist/lib/exchange.js +84 -0
- package/dist/lib/execution/journal.d.ts +25 -0
- package/dist/lib/execution/journal.js +158 -0
- package/dist/lib/execution/safety.d.ts +34 -0
- package/dist/lib/execution/safety.js +197 -0
- package/dist/lib/exit-codes.d.ts +18 -0
- package/dist/lib/exit-codes.js +60 -0
- package/dist/lib/fetch.d.ts +18 -0
- package/dist/lib/fetch.js +66 -0
- package/dist/lib/fs-security.d.ts +10 -0
- package/dist/lib/fs-security.js +26 -0
- package/dist/lib/funding-tracker.d.ts +40 -0
- package/dist/lib/funding-tracker.js +118 -0
- package/dist/lib/logger.d.ts +27 -0
- package/dist/lib/logger.js +82 -0
- package/dist/lib/network-model.d.ts +13 -0
- package/dist/lib/network-model.js +30 -0
- package/dist/lib/onboarding.d.ts +133 -0
- package/dist/lib/onboarding.js +1459 -0
- package/dist/lib/operator-state.d.ts +25 -0
- package/dist/lib/operator-state.js +82 -0
- package/dist/lib/orders-watcher.d.ts +24 -0
- package/dist/lib/orders-watcher.js +74 -0
- package/dist/lib/paths.d.ts +20 -0
- package/dist/lib/paths.js +23 -0
- package/dist/lib/portfolio-watcher.d.ts +33 -0
- package/dist/lib/portfolio-watcher.js +95 -0
- package/dist/lib/position-watcher.d.ts +16 -0
- package/dist/lib/position-watcher.js +44 -0
- package/dist/lib/price-watcher.d.ts +15 -0
- package/dist/lib/price-watcher.js +84 -0
- package/dist/lib/prompts.d.ts +32 -0
- package/dist/lib/prompts.js +105 -0
- package/dist/lib/rate-limit.d.ts +32 -0
- package/dist/lib/rate-limit.js +88 -0
- package/dist/lib/risk/analytics.d.ts +39 -0
- package/dist/lib/risk/analytics.js +98 -0
- package/dist/lib/risk/drawdown.d.ts +18 -0
- package/dist/lib/risk/drawdown.js +49 -0
- package/dist/lib/risk/evaluation-log.d.ts +29 -0
- package/dist/lib/risk/evaluation-log.js +61 -0
- package/dist/lib/risk/index.d.ts +4 -0
- package/dist/lib/risk/index.js +4 -0
- package/dist/lib/risk/limits.d.ts +23 -0
- package/dist/lib/risk/limits.js +27 -0
- package/dist/lib/risk/manager.d.ts +32 -0
- package/dist/lib/risk/manager.js +85 -0
- package/dist/lib/risk/policy-middleware.d.ts +33 -0
- package/dist/lib/risk/policy-middleware.js +267 -0
- package/dist/lib/risk/position-sizer.d.ts +9 -0
- package/dist/lib/risk/position-sizer.js +14 -0
- package/dist/lib/risk/rules-store.d.ts +16 -0
- package/dist/lib/risk/rules-store.js +47 -0
- package/dist/lib/schema.d.ts +254 -0
- package/dist/lib/schema.js +199 -0
- package/dist/lib/secrets.d.ts +3 -0
- package/dist/lib/secrets.js +62 -0
- package/dist/lib/settings.d.ts +24 -0
- package/dist/lib/settings.js +86 -0
- package/dist/lib/signals.d.ts +73 -0
- package/dist/lib/signals.js +136 -0
- package/dist/lib/stable-stringify.d.ts +6 -0
- package/dist/lib/stable-stringify.js +17 -0
- package/dist/lib/state-context.d.ts +44 -0
- package/dist/lib/state-context.js +133 -0
- package/dist/lib/strategy/basis-trade.d.ts +2 -0
- package/dist/lib/strategy/basis-trade.js +24 -0
- package/dist/lib/strategy/funding-arb.d.ts +2 -0
- package/dist/lib/strategy/funding-arb.js +23 -0
- package/dist/lib/strategy/interface.d.ts +23 -0
- package/dist/lib/strategy/interface.js +1 -0
- package/dist/lib/strategy/registry.d.ts +4 -0
- package/dist/lib/strategy/registry.js +10 -0
- package/dist/lib/telemetry.d.ts +25 -0
- package/dist/lib/telemetry.js +101 -0
- package/dist/lib/trace-queries.d.ts +20 -0
- package/dist/lib/trace-queries.js +133 -0
- package/dist/lib/trace.d.ts +1 -0
- package/dist/lib/trace.js +4 -0
- package/dist/lib/trade-reputation.d.ts +6 -0
- package/dist/lib/trade-reputation.js +99 -0
- package/dist/lib/ui-tokens.d.ts +21 -0
- package/dist/lib/ui-tokens.js +26 -0
- package/dist/lib/validate.d.ts +39 -0
- package/dist/lib/validate.js +108 -0
- package/dist/lib/validation.d.ts +9 -0
- package/dist/lib/validation.js +64 -0
- package/dist/server/cache.d.ts +38 -0
- package/dist/server/cache.js +56 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +89 -0
- package/dist/server/ipc.d.ts +18 -0
- package/dist/server/ipc.js +159 -0
- package/dist/server/subscriptions.d.ts +18 -0
- package/dist/server/subscriptions.js +114 -0
- package/package.json +124 -0
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hyperliquid Adapter
|
|
3
|
+
* Implements PerpDEXAdapter interface for Hyperliquid DEX
|
|
4
|
+
*/
|
|
5
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
6
|
+
import { HttpTransport, InfoClient, ExchangeClient, } from "@nktkas/hyperliquid";
|
|
7
|
+
import { registerAdapter, } from "./interface.js";
|
|
8
|
+
export class HyperliquidAdapter {
|
|
9
|
+
info = {
|
|
10
|
+
id: "hyperliquid",
|
|
11
|
+
name: "Hyperliquid",
|
|
12
|
+
type: "dex",
|
|
13
|
+
chains: ["arbitrum"],
|
|
14
|
+
features: {
|
|
15
|
+
spot: true,
|
|
16
|
+
perp: true,
|
|
17
|
+
margin: true,
|
|
18
|
+
crossMargin: true,
|
|
19
|
+
isolatedMargin: true,
|
|
20
|
+
stopOrders: true,
|
|
21
|
+
takeProfitOrders: true,
|
|
22
|
+
postOnly: true,
|
|
23
|
+
reduceOnly: true,
|
|
24
|
+
subaccounts: true,
|
|
25
|
+
modifyOrders: true,
|
|
26
|
+
batchOrders: true,
|
|
27
|
+
cancelAllAfter: true,
|
|
28
|
+
publicTrades: true,
|
|
29
|
+
fundingHistory: false,
|
|
30
|
+
orderHistory: true,
|
|
31
|
+
mmp: false,
|
|
32
|
+
twapOrders: true,
|
|
33
|
+
},
|
|
34
|
+
urls: {
|
|
35
|
+
app: "https://app.hyperliquid.xyz",
|
|
36
|
+
api: "https://api.hyperliquid.xyz",
|
|
37
|
+
docs: "https://hyperliquid.gitbook.io/hyperliquid-docs",
|
|
38
|
+
testnet: "https://app.hyperliquid-testnet.xyz",
|
|
39
|
+
},
|
|
40
|
+
implementation: {
|
|
41
|
+
marketData: "full",
|
|
42
|
+
authenticatedReads: "full",
|
|
43
|
+
orderLifecycle: "full",
|
|
44
|
+
orderCancellation: "full",
|
|
45
|
+
subscriptions: "full",
|
|
46
|
+
advancedTrading: "full",
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
httpTransport = null;
|
|
50
|
+
wsTransport = null;
|
|
51
|
+
infoClient = null;
|
|
52
|
+
exchangeClient = null;
|
|
53
|
+
config = null;
|
|
54
|
+
walletAddress = null;
|
|
55
|
+
connected = false;
|
|
56
|
+
meta = null;
|
|
57
|
+
latestMarkPrices = new Map();
|
|
58
|
+
async connect(config) {
|
|
59
|
+
this.config = config;
|
|
60
|
+
const credentialNetwork = config.credentials?.network;
|
|
61
|
+
const isTestnet = credentialNetwork === "testnet"
|
|
62
|
+
? true
|
|
63
|
+
: credentialNetwork === "mainnet"
|
|
64
|
+
? false
|
|
65
|
+
: (config.testnet ?? false);
|
|
66
|
+
this.httpTransport = new HttpTransport({ isTestnet });
|
|
67
|
+
this.infoClient = new InfoClient({ transport: this.httpTransport });
|
|
68
|
+
if (config.credentials?.privateKey) {
|
|
69
|
+
const account = privateKeyToAccount(config.credentials.privateKey);
|
|
70
|
+
this.walletAddress = config.credentials.walletAddress || account.address;
|
|
71
|
+
this.exchangeClient = new ExchangeClient({
|
|
72
|
+
transport: this.httpTransport,
|
|
73
|
+
wallet: account,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
else if (config.credentials?.walletAddress) {
|
|
77
|
+
this.walletAddress = config.credentials.walletAddress;
|
|
78
|
+
}
|
|
79
|
+
// Fetch metadata
|
|
80
|
+
this.meta = await this.infoClient.meta();
|
|
81
|
+
this.connected = true;
|
|
82
|
+
}
|
|
83
|
+
async disconnect() {
|
|
84
|
+
if (this.wsTransport) {
|
|
85
|
+
try {
|
|
86
|
+
await this.wsTransport.close();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Best effort close for transport cleanup.
|
|
90
|
+
}
|
|
91
|
+
this.wsTransport = null;
|
|
92
|
+
}
|
|
93
|
+
this.config = null;
|
|
94
|
+
this.walletAddress = null;
|
|
95
|
+
this.meta = null;
|
|
96
|
+
this.httpTransport = null;
|
|
97
|
+
this.infoClient = null;
|
|
98
|
+
this.exchangeClient = null;
|
|
99
|
+
this.connected = false;
|
|
100
|
+
}
|
|
101
|
+
isConnected() {
|
|
102
|
+
return this.connected;
|
|
103
|
+
}
|
|
104
|
+
// --------------------------------------------------------------------------
|
|
105
|
+
// Public Market Data
|
|
106
|
+
// --------------------------------------------------------------------------
|
|
107
|
+
async getMarkets() {
|
|
108
|
+
this.ensureConnected();
|
|
109
|
+
if (!this.meta) {
|
|
110
|
+
this.meta = await this.infoClient.meta();
|
|
111
|
+
}
|
|
112
|
+
return this.meta.universe.map((asset) => ({
|
|
113
|
+
symbol: `${asset.name}-PERP`,
|
|
114
|
+
baseAsset: asset.name,
|
|
115
|
+
quoteAsset: "USD",
|
|
116
|
+
type: "perp",
|
|
117
|
+
maxLeverage: asset.maxLeverage,
|
|
118
|
+
minSize: Math.pow(10, -asset.szDecimals).toString(),
|
|
119
|
+
tickSize: "0.01", // Hyperliquid uses dynamic tick sizes
|
|
120
|
+
fundingInterval: 1, // Hourly
|
|
121
|
+
isActive: true,
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
async getMarket(symbol) {
|
|
125
|
+
const markets = await this.getMarkets();
|
|
126
|
+
const normalized = symbol.toUpperCase().replace("-PERP", "");
|
|
127
|
+
return markets.find((m) => m.baseAsset === normalized) ?? null;
|
|
128
|
+
}
|
|
129
|
+
async getTicker(market) {
|
|
130
|
+
this.ensureConnected();
|
|
131
|
+
const coin = market.toUpperCase().replace("-PERP", "");
|
|
132
|
+
const ctxs = await this.infoClient.metaAndAssetCtxs();
|
|
133
|
+
const meta = ctxs[0];
|
|
134
|
+
const assetCtxs = ctxs[1];
|
|
135
|
+
const idx = meta.universe.findIndex((u) => u.name === coin);
|
|
136
|
+
if (idx === -1)
|
|
137
|
+
throw new Error(`Market ${market} not found`);
|
|
138
|
+
const ctx = assetCtxs[idx];
|
|
139
|
+
const prevDayPx = parseFloat(ctx.prevDayPx);
|
|
140
|
+
const change = prevDayPx > 0
|
|
141
|
+
? ((parseFloat(ctx.markPx) - prevDayPx) / prevDayPx) * 100
|
|
142
|
+
: 0;
|
|
143
|
+
// Cache mark price for position enrichment
|
|
144
|
+
this.latestMarkPrices.set(coin, ctx.markPx);
|
|
145
|
+
// Fetch 24h candle for high/low
|
|
146
|
+
let high24h = "0";
|
|
147
|
+
let low24h = "0";
|
|
148
|
+
try {
|
|
149
|
+
const now = Date.now();
|
|
150
|
+
const oneDayAgo = now - 86_400_000;
|
|
151
|
+
const candles = await this.infoClient.candleSnapshot({
|
|
152
|
+
coin,
|
|
153
|
+
interval: "1d",
|
|
154
|
+
startTime: oneDayAgo,
|
|
155
|
+
endTime: now,
|
|
156
|
+
});
|
|
157
|
+
if (candles && candles.length > 0) {
|
|
158
|
+
high24h = candles[candles.length - 1].h;
|
|
159
|
+
low24h = candles[candles.length - 1].l;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// candleSnapshot may not be available for all coins
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
market: `${coin}-PERP`,
|
|
167
|
+
lastPrice: ctx.markPx,
|
|
168
|
+
markPrice: ctx.markPx,
|
|
169
|
+
indexPrice: ctx.oraclePx,
|
|
170
|
+
bid: ctx.impactPxs?.[0] ?? ctx.markPx,
|
|
171
|
+
ask: ctx.impactPxs?.[1] ?? ctx.markPx,
|
|
172
|
+
volume24h: ctx.dayNtlVlm,
|
|
173
|
+
change24h: change.toFixed(2),
|
|
174
|
+
high24h,
|
|
175
|
+
low24h,
|
|
176
|
+
openInterest: ctx.openInterest,
|
|
177
|
+
fundingRate: ctx.funding,
|
|
178
|
+
timestamp: Date.now(),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async getTickers() {
|
|
182
|
+
this.ensureConnected();
|
|
183
|
+
const ctxs = await this.infoClient.metaAndAssetCtxs();
|
|
184
|
+
const meta = ctxs[0];
|
|
185
|
+
const assetCtxs = ctxs[1];
|
|
186
|
+
return meta.universe.map((asset, idx) => {
|
|
187
|
+
const ctx = assetCtxs[idx];
|
|
188
|
+
const prevDayPx = parseFloat(ctx.prevDayPx);
|
|
189
|
+
const change = prevDayPx > 0
|
|
190
|
+
? ((parseFloat(ctx.markPx) - prevDayPx) / prevDayPx) * 100
|
|
191
|
+
: 0;
|
|
192
|
+
// Cache mark prices for position enrichment
|
|
193
|
+
this.latestMarkPrices.set(asset.name, ctx.markPx);
|
|
194
|
+
return {
|
|
195
|
+
market: `${asset.name}-PERP`,
|
|
196
|
+
lastPrice: ctx.markPx,
|
|
197
|
+
markPrice: ctx.markPx,
|
|
198
|
+
indexPrice: ctx.oraclePx,
|
|
199
|
+
bid: ctx.impactPxs?.[0] ?? ctx.markPx,
|
|
200
|
+
ask: ctx.impactPxs?.[1] ?? ctx.markPx,
|
|
201
|
+
volume24h: ctx.dayNtlVlm,
|
|
202
|
+
change24h: change.toFixed(2),
|
|
203
|
+
high24h: "0",
|
|
204
|
+
low24h: "0",
|
|
205
|
+
openInterest: ctx.openInterest,
|
|
206
|
+
fundingRate: ctx.funding,
|
|
207
|
+
timestamp: Date.now(),
|
|
208
|
+
};
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
async getOrderBook(market, depth = 20) {
|
|
212
|
+
this.ensureConnected();
|
|
213
|
+
const coin = market.toUpperCase().replace("-PERP", "");
|
|
214
|
+
const book = await this.infoClient.l2Book({ coin, nSigFigs: 5 });
|
|
215
|
+
const bids = book?.levels?.[0] ?? [];
|
|
216
|
+
const asks = book?.levels?.[1] ?? [];
|
|
217
|
+
return {
|
|
218
|
+
market: `${coin}-PERP`,
|
|
219
|
+
bids: bids.slice(0, depth).map((l) => ({ price: l.px, size: l.sz })),
|
|
220
|
+
asks: asks.slice(0, depth).map((l) => ({ price: l.px, size: l.sz })),
|
|
221
|
+
timestamp: Date.now(),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
async getFundingRate(market) {
|
|
225
|
+
const ticker = await this.getTicker(market);
|
|
226
|
+
return {
|
|
227
|
+
market: ticker.market,
|
|
228
|
+
rate: ticker.fundingRate,
|
|
229
|
+
nextFundingTime: Date.now() + 3600000, // Approximate
|
|
230
|
+
timestamp: ticker.timestamp,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
async getFundingRates() {
|
|
234
|
+
const tickers = await this.getTickers();
|
|
235
|
+
return tickers.map((t) => ({
|
|
236
|
+
market: t.market,
|
|
237
|
+
rate: t.fundingRate,
|
|
238
|
+
nextFundingTime: Date.now() + 3600000,
|
|
239
|
+
timestamp: t.timestamp,
|
|
240
|
+
}));
|
|
241
|
+
}
|
|
242
|
+
// --------------------------------------------------------------------------
|
|
243
|
+
// Account Data (requires auth)
|
|
244
|
+
// --------------------------------------------------------------------------
|
|
245
|
+
async getPositions() {
|
|
246
|
+
this.ensureAuth();
|
|
247
|
+
// Refresh mark prices from metaAndAssetCtxs for position enrichment
|
|
248
|
+
try {
|
|
249
|
+
const ctxs = await this.infoClient.metaAndAssetCtxs();
|
|
250
|
+
const meta = ctxs[0];
|
|
251
|
+
const assetCtxs = ctxs[1];
|
|
252
|
+
for (let i = 0; i < meta.universe.length; i++) {
|
|
253
|
+
this.latestMarkPrices.set(meta.universe[i].name, assetCtxs[i].markPx);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// Best effort - positions will still work with cached or entry prices
|
|
258
|
+
}
|
|
259
|
+
const state = await this.infoClient.clearinghouseState({ user: this.walletAddress });
|
|
260
|
+
const positions = state.assetPositions;
|
|
261
|
+
return positions
|
|
262
|
+
.filter((p) => parseFloat(p.position.szi) !== 0)
|
|
263
|
+
.map((p) => this.mapPosition(p.position));
|
|
264
|
+
}
|
|
265
|
+
async getPosition(market) {
|
|
266
|
+
const positions = await this.getPositions();
|
|
267
|
+
const coin = market.toUpperCase().replace("-PERP", "");
|
|
268
|
+
return positions.find((p) => p.market === `${coin}-PERP`) ?? null;
|
|
269
|
+
}
|
|
270
|
+
async getOrders(market) {
|
|
271
|
+
this.ensureAuth();
|
|
272
|
+
const orders = await this.infoClient.openOrders({ user: this.walletAddress });
|
|
273
|
+
const mapped = orders.map((o) => this.mapOrder(o));
|
|
274
|
+
if (market) {
|
|
275
|
+
const coin = market.toUpperCase().replace("-PERP", "");
|
|
276
|
+
return mapped.filter((o) => o.market === `${coin}-PERP`);
|
|
277
|
+
}
|
|
278
|
+
return mapped;
|
|
279
|
+
}
|
|
280
|
+
async getOrder(orderId) {
|
|
281
|
+
this.ensureAuth();
|
|
282
|
+
// Try direct orderStatus lookup first
|
|
283
|
+
try {
|
|
284
|
+
const status = await this.infoClient.orderStatus({
|
|
285
|
+
user: this.walletAddress,
|
|
286
|
+
oid: parseInt(orderId),
|
|
287
|
+
});
|
|
288
|
+
if (status.order) {
|
|
289
|
+
return this.mapOrder(status.order);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// Fall through to open orders scan
|
|
294
|
+
}
|
|
295
|
+
const orders = await this.getOrders();
|
|
296
|
+
return orders.find((o) => o.id === orderId) ?? null;
|
|
297
|
+
}
|
|
298
|
+
async getBalances() {
|
|
299
|
+
this.ensureAuth();
|
|
300
|
+
const state = await this.infoClient.clearinghouseState({ user: this.walletAddress });
|
|
301
|
+
// Calculate totals
|
|
302
|
+
const accountValue = parseFloat(state.marginSummary.accountValue);
|
|
303
|
+
const totalMarginUsed = parseFloat(state.marginSummary.totalMarginUsed);
|
|
304
|
+
// Unrealized PnL from positions
|
|
305
|
+
const positions = state.assetPositions;
|
|
306
|
+
const unrealizedPnl = positions.reduce((sum, p) => sum + parseFloat(p.position.unrealizedPnl || "0"), 0);
|
|
307
|
+
return [
|
|
308
|
+
{
|
|
309
|
+
asset: "USD",
|
|
310
|
+
total: accountValue.toString(),
|
|
311
|
+
available: (accountValue - totalMarginUsed).toString(),
|
|
312
|
+
locked: totalMarginUsed.toString(),
|
|
313
|
+
unrealizedPnl: unrealizedPnl.toString(),
|
|
314
|
+
marginUsed: totalMarginUsed.toString(),
|
|
315
|
+
},
|
|
316
|
+
];
|
|
317
|
+
}
|
|
318
|
+
async getTrades(market, limit = 100) {
|
|
319
|
+
this.ensureAuth();
|
|
320
|
+
const fills = await this.infoClient.userFills({ user: this.walletAddress });
|
|
321
|
+
let mapped = fills.slice(0, limit).map((f) => this.mapFill(f));
|
|
322
|
+
if (market) {
|
|
323
|
+
const coin = market.toUpperCase().replace("-PERP", "");
|
|
324
|
+
mapped = mapped.filter((t) => t.market === `${coin}-PERP`);
|
|
325
|
+
}
|
|
326
|
+
return mapped;
|
|
327
|
+
}
|
|
328
|
+
// --------------------------------------------------------------------------
|
|
329
|
+
// Trading
|
|
330
|
+
// --------------------------------------------------------------------------
|
|
331
|
+
async placeOrder(params) {
|
|
332
|
+
this.ensureExchangeClient();
|
|
333
|
+
const coin = params.market.toUpperCase().replace("-PERP", "");
|
|
334
|
+
const isBuy = params.side === "long";
|
|
335
|
+
const isMarket = params.type === "market";
|
|
336
|
+
let orderType = isMarket
|
|
337
|
+
? { market: {} }
|
|
338
|
+
: { limit: { tif: params.postOnly ? "Alo" : "Gtc" } };
|
|
339
|
+
if (params.type === "stop" || params.type === "stop_limit") {
|
|
340
|
+
if (!params.triggerPrice) {
|
|
341
|
+
throw new Error("triggerPrice is required for stop orders");
|
|
342
|
+
}
|
|
343
|
+
orderType = {
|
|
344
|
+
trigger: {
|
|
345
|
+
isMarket: params.type === "stop",
|
|
346
|
+
triggerPx: params.triggerPrice,
|
|
347
|
+
tpsl: "sl",
|
|
348
|
+
},
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
else if (params.type === "take_profit") {
|
|
352
|
+
if (!params.triggerPrice) {
|
|
353
|
+
throw new Error("triggerPrice is required for take_profit orders");
|
|
354
|
+
}
|
|
355
|
+
orderType = {
|
|
356
|
+
trigger: {
|
|
357
|
+
isMarket: true,
|
|
358
|
+
triggerPx: params.triggerPrice,
|
|
359
|
+
tpsl: "tp",
|
|
360
|
+
},
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
const orderRequest = {
|
|
364
|
+
coin,
|
|
365
|
+
is_buy: isBuy,
|
|
366
|
+
sz: parseFloat(params.size),
|
|
367
|
+
limit_px: isMarket ? null : parseFloat(params.price),
|
|
368
|
+
order_type: orderType,
|
|
369
|
+
reduce_only: params.reduceOnly ?? false,
|
|
370
|
+
};
|
|
371
|
+
await this.exchangeClient.order(orderRequest);
|
|
372
|
+
// Return the order (fetch it back for full details)
|
|
373
|
+
const orders = await this.getOrders(params.market);
|
|
374
|
+
return orders[0] || {
|
|
375
|
+
id: "pending",
|
|
376
|
+
market: `${coin}-PERP`,
|
|
377
|
+
side: params.side,
|
|
378
|
+
type: params.type,
|
|
379
|
+
size: params.size,
|
|
380
|
+
price: params.price ?? null,
|
|
381
|
+
filled: "0",
|
|
382
|
+
remaining: params.size,
|
|
383
|
+
status: "open",
|
|
384
|
+
reduceOnly: params.reduceOnly ?? false,
|
|
385
|
+
postOnly: params.postOnly ?? false,
|
|
386
|
+
timestamp: Date.now(),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
async cancelOrder(params) {
|
|
390
|
+
this.ensureExchangeClient();
|
|
391
|
+
if (!params.orderId) {
|
|
392
|
+
throw new Error("orderId is required for Hyperliquid cancelOrder");
|
|
393
|
+
}
|
|
394
|
+
let coin;
|
|
395
|
+
if (params.market) {
|
|
396
|
+
coin = params.market.toUpperCase().replace("-PERP", "");
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
// Look up the order's market from open orders
|
|
400
|
+
const orders = await this.getOrders();
|
|
401
|
+
const match = orders.find((o) => o.id === params.orderId);
|
|
402
|
+
if (match) {
|
|
403
|
+
coin = match.market.replace("-PERP", "");
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
if (!coin) {
|
|
407
|
+
throw new Error(`Could not resolve market for order ${params.orderId}`);
|
|
408
|
+
}
|
|
409
|
+
const assetIndex = this.getAssetIndex(coin);
|
|
410
|
+
try {
|
|
411
|
+
await this.exchangeClient.cancel({
|
|
412
|
+
cancels: [{
|
|
413
|
+
a: assetIndex,
|
|
414
|
+
o: parseInt(params.orderId),
|
|
415
|
+
}],
|
|
416
|
+
});
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
async cancelAllOrders(market) {
|
|
424
|
+
this.ensureExchangeClient();
|
|
425
|
+
const orders = await this.getOrders(market);
|
|
426
|
+
let cancelled = 0;
|
|
427
|
+
for (const order of orders) {
|
|
428
|
+
const success = await this.cancelOrder({
|
|
429
|
+
orderId: order.id,
|
|
430
|
+
market: order.market,
|
|
431
|
+
});
|
|
432
|
+
if (success)
|
|
433
|
+
cancelled++;
|
|
434
|
+
}
|
|
435
|
+
return cancelled;
|
|
436
|
+
}
|
|
437
|
+
async setLeverage(market, leverage) {
|
|
438
|
+
this.ensureExchangeClient();
|
|
439
|
+
const coin = market.toUpperCase().replace("-PERP", "");
|
|
440
|
+
await this.exchangeClient.updateLeverage({
|
|
441
|
+
asset: this.getAssetIndex(coin),
|
|
442
|
+
isCross: true,
|
|
443
|
+
leverage,
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
async setMarginType(market, type) {
|
|
447
|
+
this.ensureExchangeClient();
|
|
448
|
+
const coin = market.toUpperCase().replace("-PERP", "");
|
|
449
|
+
const position = await this.getPosition(market);
|
|
450
|
+
await this.exchangeClient.updateLeverage({
|
|
451
|
+
asset: this.getAssetIndex(coin),
|
|
452
|
+
isCross: type === "cross",
|
|
453
|
+
leverage: position?.leverage ?? 1,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
// --------------------------------------------------------------------------
|
|
457
|
+
// Advanced Trading
|
|
458
|
+
// --------------------------------------------------------------------------
|
|
459
|
+
async modifyOrder(params) {
|
|
460
|
+
this.ensureExchangeClient();
|
|
461
|
+
const coin = params.market.toUpperCase().replace("-PERP", "");
|
|
462
|
+
const oid = parseInt(params.orderId);
|
|
463
|
+
// Fetch existing order to get current values
|
|
464
|
+
const existing = await this.getOrder(params.orderId);
|
|
465
|
+
if (!existing) {
|
|
466
|
+
throw new Error(`Order ${params.orderId} not found`);
|
|
467
|
+
}
|
|
468
|
+
const isBuy = existing.side === "long";
|
|
469
|
+
const newPrice = params.price ? parseFloat(params.price) : parseFloat(existing.price ?? "0");
|
|
470
|
+
const newSize = params.size ? parseFloat(params.size) : parseFloat(existing.size);
|
|
471
|
+
await this.exchangeClient.modify({
|
|
472
|
+
oid,
|
|
473
|
+
order: {
|
|
474
|
+
a: this.getAssetIndex(coin),
|
|
475
|
+
b: isBuy,
|
|
476
|
+
p: newPrice.toString(),
|
|
477
|
+
s: newSize.toString(),
|
|
478
|
+
r: existing.reduceOnly,
|
|
479
|
+
t: { limit: { tif: existing.postOnly ? "Alo" : "Gtc" } },
|
|
480
|
+
},
|
|
481
|
+
});
|
|
482
|
+
// Fetch back the modified order
|
|
483
|
+
const orders = await this.getOrders(params.market);
|
|
484
|
+
return orders[0] || { ...existing, price: newPrice.toString(), size: newSize.toString() };
|
|
485
|
+
}
|
|
486
|
+
async batchPlaceOrders(paramsList) {
|
|
487
|
+
// Sequential: HL's batch endpoint is for modifications, not new orders
|
|
488
|
+
const results = [];
|
|
489
|
+
for (const params of paramsList) {
|
|
490
|
+
results.push(await this.placeOrder(params));
|
|
491
|
+
}
|
|
492
|
+
return results;
|
|
493
|
+
}
|
|
494
|
+
async batchCancelOrders(paramsList) {
|
|
495
|
+
this.ensureExchangeClient();
|
|
496
|
+
// Build batch cancel request
|
|
497
|
+
const cancels = [];
|
|
498
|
+
for (const params of paramsList) {
|
|
499
|
+
if (!params.orderId)
|
|
500
|
+
continue;
|
|
501
|
+
let coin;
|
|
502
|
+
if (params.market) {
|
|
503
|
+
coin = params.market.toUpperCase().replace("-PERP", "");
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
const orders = await this.getOrders();
|
|
507
|
+
const match = orders.find((o) => o.id === params.orderId);
|
|
508
|
+
if (match)
|
|
509
|
+
coin = match.market.replace("-PERP", "");
|
|
510
|
+
}
|
|
511
|
+
if (!coin)
|
|
512
|
+
continue;
|
|
513
|
+
cancels.push({ a: this.getAssetIndex(coin), o: parseInt(params.orderId) });
|
|
514
|
+
}
|
|
515
|
+
if (cancels.length === 0)
|
|
516
|
+
return paramsList.map(() => false);
|
|
517
|
+
try {
|
|
518
|
+
await this.exchangeClient.cancel({ cancels });
|
|
519
|
+
return paramsList.map(() => true);
|
|
520
|
+
}
|
|
521
|
+
catch {
|
|
522
|
+
// Fallback to individual cancels
|
|
523
|
+
const results = [];
|
|
524
|
+
for (const params of paramsList) {
|
|
525
|
+
try {
|
|
526
|
+
results.push(await this.cancelOrder(params));
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
results.push(false);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return results;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async cancelAllAfter(timeoutMs) {
|
|
536
|
+
this.ensureExchangeClient();
|
|
537
|
+
// Hyperliquid scheduleCancel: timeout in seconds
|
|
538
|
+
const timeoutSec = Math.max(0, Math.floor(timeoutMs / 1000));
|
|
539
|
+
await this.exchangeClient.scheduleCancel({
|
|
540
|
+
time: timeoutSec > 0 ? Date.now() + timeoutMs : null,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
async getOrderHistory(_market, limit = 100) {
|
|
544
|
+
this.ensureAuth();
|
|
545
|
+
// Hyperliquid historicalOrders endpoint
|
|
546
|
+
const response = await this.infoClient.historicalOrders({
|
|
547
|
+
user: this.walletAddress,
|
|
548
|
+
});
|
|
549
|
+
let orders = response.slice(0, limit)
|
|
550
|
+
.map((entry) => entry.order ? this.mapOrder(entry.order) : null)
|
|
551
|
+
.filter((o) => o !== null);
|
|
552
|
+
if (_market) {
|
|
553
|
+
const coin = _market.toUpperCase().replace("-PERP", "");
|
|
554
|
+
orders = orders.filter((o) => o.market === `${coin}-PERP`);
|
|
555
|
+
}
|
|
556
|
+
return orders;
|
|
557
|
+
}
|
|
558
|
+
async getFundingHistory(_market, _limit) {
|
|
559
|
+
throw new Error("Hyperliquid does not expose a per-user funding history endpoint");
|
|
560
|
+
}
|
|
561
|
+
async getPublicTrades(market, limit = 100) {
|
|
562
|
+
this.ensureConnected();
|
|
563
|
+
const coin = market.toUpperCase().replace("-PERP", "");
|
|
564
|
+
const trades = await this.infoClient.recentTrades({ coin });
|
|
565
|
+
return trades.slice(0, limit).map((t) => ({
|
|
566
|
+
id: t.tid?.toString() ?? `${t.time}`,
|
|
567
|
+
market: `${coin}-PERP`,
|
|
568
|
+
side: t.side === "B" ? "long" : "short",
|
|
569
|
+
price: t.px,
|
|
570
|
+
size: t.sz,
|
|
571
|
+
timestamp: t.time,
|
|
572
|
+
}));
|
|
573
|
+
}
|
|
574
|
+
// --------------------------------------------------------------------------
|
|
575
|
+
// Market Maker Protection
|
|
576
|
+
// --------------------------------------------------------------------------
|
|
577
|
+
async setMMP(_config) {
|
|
578
|
+
throw new Error("Hyperliquid does not support Market Maker Protection (MMP)");
|
|
579
|
+
}
|
|
580
|
+
async getMMP(_market) {
|
|
581
|
+
throw new Error("Hyperliquid does not support Market Maker Protection (MMP)");
|
|
582
|
+
}
|
|
583
|
+
async resetMMP(_market) {
|
|
584
|
+
throw new Error("Hyperliquid does not support Market Maker Protection (MMP)");
|
|
585
|
+
}
|
|
586
|
+
// --------------------------------------------------------------------------
|
|
587
|
+
// TWAP Orders
|
|
588
|
+
// --------------------------------------------------------------------------
|
|
589
|
+
async placeTWAP(params) {
|
|
590
|
+
this.ensureExchangeClient();
|
|
591
|
+
const coin = params.market.toUpperCase().replace("-PERP", "");
|
|
592
|
+
const isBuy = params.side === "long";
|
|
593
|
+
const durationSec = Math.floor(params.durationMs / 1000);
|
|
594
|
+
await this.exchangeClient.twapOrder({
|
|
595
|
+
coin,
|
|
596
|
+
is_buy: isBuy,
|
|
597
|
+
sz: parseFloat(params.size),
|
|
598
|
+
duration_s: durationSec,
|
|
599
|
+
randomize: params.randomize ?? false,
|
|
600
|
+
});
|
|
601
|
+
return {
|
|
602
|
+
id: `twap-${Date.now()}`,
|
|
603
|
+
market: `${coin}-PERP`,
|
|
604
|
+
side: params.side,
|
|
605
|
+
totalSize: params.size,
|
|
606
|
+
executedSize: "0",
|
|
607
|
+
remainingSize: params.size,
|
|
608
|
+
status: "active",
|
|
609
|
+
startTime: Date.now(),
|
|
610
|
+
endTime: Date.now() + params.durationMs,
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
async cancelTWAP(twapId) {
|
|
614
|
+
this.ensureExchangeClient();
|
|
615
|
+
try {
|
|
616
|
+
await this.exchangeClient.twapCancel({
|
|
617
|
+
token_id: parseInt(twapId.replace("twap-", "")),
|
|
618
|
+
});
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
catch {
|
|
622
|
+
return false;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
async getTWAPStatus(_twapId) {
|
|
626
|
+
throw new Error("Hyperliquid does not expose a TWAP status query endpoint");
|
|
627
|
+
}
|
|
628
|
+
// --------------------------------------------------------------------------
|
|
629
|
+
// Margin Management
|
|
630
|
+
// --------------------------------------------------------------------------
|
|
631
|
+
async updateIsolatedMargin(market, amount) {
|
|
632
|
+
this.ensureExchangeClient();
|
|
633
|
+
const coin = market.toUpperCase().replace("-PERP", "");
|
|
634
|
+
const isBuy = true; // For margin update, direction is based on position side
|
|
635
|
+
await this.exchangeClient.updateIsolatedMargin({
|
|
636
|
+
asset: this.getAssetIndex(coin),
|
|
637
|
+
isBuy,
|
|
638
|
+
ntli: parseFloat(amount),
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
subscribe(callbacks) {
|
|
642
|
+
this.ensureConnected();
|
|
643
|
+
let closed = false;
|
|
644
|
+
const emit = async () => {
|
|
645
|
+
if (closed)
|
|
646
|
+
return;
|
|
647
|
+
try {
|
|
648
|
+
if (callbacks.onTicker) {
|
|
649
|
+
const tickers = await this.getTickers();
|
|
650
|
+
for (const ticker of tickers) {
|
|
651
|
+
callbacks.onTicker(ticker);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (callbacks.onPositions && this.walletAddress) {
|
|
655
|
+
callbacks.onPositions(await this.getPositions());
|
|
656
|
+
}
|
|
657
|
+
if (callbacks.onOrders && this.walletAddress) {
|
|
658
|
+
callbacks.onOrders(await this.getOrders());
|
|
659
|
+
}
|
|
660
|
+
if (callbacks.onBalances && this.walletAddress) {
|
|
661
|
+
callbacks.onBalances(await this.getBalances());
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
catch (err) {
|
|
665
|
+
callbacks.onError?.(err instanceof Error ? err : new Error(String(err)));
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
void emit();
|
|
669
|
+
const timer = setInterval(() => {
|
|
670
|
+
void emit();
|
|
671
|
+
}, 1_000);
|
|
672
|
+
return () => {
|
|
673
|
+
closed = true;
|
|
674
|
+
clearInterval(timer);
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
subscribeOrderBook(market, callback) {
|
|
678
|
+
this.ensureConnected();
|
|
679
|
+
let closed = false;
|
|
680
|
+
const normalizedMarket = market.toUpperCase().endsWith("-PERP")
|
|
681
|
+
? market.toUpperCase()
|
|
682
|
+
: `${market.toUpperCase()}-PERP`;
|
|
683
|
+
const emit = async () => {
|
|
684
|
+
if (closed)
|
|
685
|
+
return;
|
|
686
|
+
callback(await this.getOrderBook(normalizedMarket));
|
|
687
|
+
};
|
|
688
|
+
void emit().catch(() => { });
|
|
689
|
+
const timer = setInterval(() => {
|
|
690
|
+
void emit().catch(() => { });
|
|
691
|
+
}, 500);
|
|
692
|
+
return () => {
|
|
693
|
+
closed = true;
|
|
694
|
+
clearInterval(timer);
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
subscribeTicker(market, callback) {
|
|
698
|
+
this.ensureConnected();
|
|
699
|
+
let closed = false;
|
|
700
|
+
const normalizedMarket = market.toUpperCase().endsWith("-PERP")
|
|
701
|
+
? market.toUpperCase()
|
|
702
|
+
: `${market.toUpperCase()}-PERP`;
|
|
703
|
+
const emit = async () => {
|
|
704
|
+
if (closed)
|
|
705
|
+
return;
|
|
706
|
+
callback(await this.getTicker(normalizedMarket));
|
|
707
|
+
};
|
|
708
|
+
void emit().catch(() => { });
|
|
709
|
+
const timer = setInterval(() => {
|
|
710
|
+
void emit().catch(() => { });
|
|
711
|
+
}, 1_000);
|
|
712
|
+
return () => {
|
|
713
|
+
closed = true;
|
|
714
|
+
clearInterval(timer);
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
// --------------------------------------------------------------------------
|
|
718
|
+
// Private Helpers
|
|
719
|
+
// --------------------------------------------------------------------------
|
|
720
|
+
ensureConnected() {
|
|
721
|
+
if (!this.connected || !this.infoClient) {
|
|
722
|
+
throw new Error("Not connected. Call connect() first.");
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
ensureAuth() {
|
|
726
|
+
this.ensureConnected();
|
|
727
|
+
if (!this.walletAddress) {
|
|
728
|
+
throw new Error("Wallet address required. Provide credentials in connect().");
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
ensureExchangeClient() {
|
|
732
|
+
this.ensureConnected();
|
|
733
|
+
if (!this.exchangeClient) {
|
|
734
|
+
throw new Error("Exchange client not available. Provide privateKey in credentials.");
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
getAssetIndex(coin) {
|
|
738
|
+
if (!this.meta)
|
|
739
|
+
throw new Error("Meta not loaded");
|
|
740
|
+
const idx = this.meta.universe.findIndex((u) => u.name === coin);
|
|
741
|
+
if (idx === -1)
|
|
742
|
+
throw new Error(`Asset ${coin} not found`);
|
|
743
|
+
return idx;
|
|
744
|
+
}
|
|
745
|
+
mapPosition(p) {
|
|
746
|
+
const size = parseFloat(p.szi);
|
|
747
|
+
const markPrice = this.latestMarkPrices.get(p.coin) ?? p.entryPx;
|
|
748
|
+
return {
|
|
749
|
+
market: `${p.coin}-PERP`,
|
|
750
|
+
side: size > 0 ? "long" : "short",
|
|
751
|
+
size: Math.abs(size).toString(),
|
|
752
|
+
entryPrice: p.entryPx,
|
|
753
|
+
markPrice,
|
|
754
|
+
liquidationPrice: p.liquidationPx,
|
|
755
|
+
unrealizedPnl: p.unrealizedPnl,
|
|
756
|
+
realizedPnl: "0",
|
|
757
|
+
leverage: p.leverage.value,
|
|
758
|
+
marginType: p.leverage.type === "cross" ? "cross" : "isolated",
|
|
759
|
+
margin: p.marginUsed,
|
|
760
|
+
timestamp: Date.now(),
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
mapOrder(o) {
|
|
764
|
+
const filled = parseFloat(o.origSz) - parseFloat(o.sz);
|
|
765
|
+
return {
|
|
766
|
+
id: o.oid.toString(),
|
|
767
|
+
market: `${o.coin}-PERP`,
|
|
768
|
+
side: o.side === "B" ? "long" : "short",
|
|
769
|
+
type: o.triggerPx ? "stop" : "limit",
|
|
770
|
+
size: o.origSz,
|
|
771
|
+
price: o.limitPx,
|
|
772
|
+
filled: filled.toString(),
|
|
773
|
+
remaining: o.sz,
|
|
774
|
+
status: "open",
|
|
775
|
+
reduceOnly: o.reduceOnly,
|
|
776
|
+
postOnly: false,
|
|
777
|
+
timestamp: o.timestamp,
|
|
778
|
+
triggerPrice: o.triggerPx,
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
mapFill(f) {
|
|
782
|
+
return {
|
|
783
|
+
id: `${f.oid}-${f.time}`,
|
|
784
|
+
market: `${f.coin}-PERP`,
|
|
785
|
+
side: f.side === "B" ? "long" : "short",
|
|
786
|
+
price: f.px,
|
|
787
|
+
size: f.sz,
|
|
788
|
+
fee: f.fee,
|
|
789
|
+
feeAsset: f.feeToken,
|
|
790
|
+
timestamp: f.time,
|
|
791
|
+
orderId: f.oid.toString(),
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// Register the adapter
|
|
796
|
+
registerAdapter("hyperliquid", () => new HyperliquidAdapter());
|
|
797
|
+
export default HyperliquidAdapter;
|