@hypelens/hypelens-agent-rail 0.1.1
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/.gitignore +3 -0
- package/README.md +71 -0
- package/ROLLOUT.md +67 -0
- package/bin/hypelens-rail-mcp.js +3 -0
- package/package-lock.json +1182 -0
- package/package.json +48 -0
- package/scripts/bundle-vendor.mjs +13 -0
- package/server.json +35 -0
- package/smithery.yaml +23 -0
- package/src/core.js +178 -0
- package/src/exchange.js +88 -0
- package/src/index.js +2 -0
- package/src/load.js +39 -0
- package/src/mcp.js +63 -0
- package/test/exchange.test.mjs +74 -0
- package/test/risk.test.mjs +86 -0
- package/vendor/hl-actions.js +116 -0
- package/vendor/hl-sdk.js +9215 -0
- package/vendor/hl-signer.js +68 -0
- package/vendor/viewmodel.js +344 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// HypeLens Module 3 — Hyperliquid EXCHANGE action builders (PURE, no signing).
|
|
2
|
+
// -----------------------------------------------------------------------------
|
|
3
|
+
// Testnet-first. The BUILDER address is PINNED here and must NEVER be read from
|
|
4
|
+
// the page. All numeric normalization (float_to_wire, szDecimals) lives here so
|
|
5
|
+
// it can be unit-tested; the vendored SDK's actionSorter still owns msgpack key
|
|
6
|
+
// order for the hash. Exposes window.HLX3.actions.
|
|
7
|
+
(function (g) {
|
|
8
|
+
'use strict';
|
|
9
|
+
const X3 = g.HLX3 = g.HLX3 || {};
|
|
10
|
+
|
|
11
|
+
// --- HARD BLOCK: mainnet placement is DISABLED in code until testnet proof +
|
|
12
|
+
// an explicit, separate operator sign-off. While false, the mainnet network
|
|
13
|
+
// option is hidden, setNet('mainnet') is refused, and any mainnet /exchange
|
|
14
|
+
// POST is rejected (defense-in-depth in the background too). ---
|
|
15
|
+
const MAINNET_PLACEMENT_ENABLED = false;
|
|
16
|
+
|
|
17
|
+
// --- PINNED constants (never sourced from the page) ---
|
|
18
|
+
const BUILDER = '0x9548B8E9554a1968843B3C380431b10996247c88'; // HypeLens builder
|
|
19
|
+
const BUILDER_F = 10; // f=10 tenths-of-a-bp = 1bp = 0.01% (f ≤ 100 perps)
|
|
20
|
+
const MAX_BUILDER_FEE_RATE = '0.01%'; // approveBuilderFee maxFeeRate
|
|
21
|
+
const AGENT_NAME = 'hypelens';
|
|
22
|
+
const SIGNATURE_CHAIN_ID = '0x66eee'; // 421614 (Arbitrum Sepolia) for user-signed actions
|
|
23
|
+
const EIP712_DOMAIN = { name: 'HyperliquidSignTransaction', version: '1', chainId: 421614, verifyingContract: '0x0000000000000000000000000000000000000000' };
|
|
24
|
+
|
|
25
|
+
const NET = {
|
|
26
|
+
testnet: { chain: 'Testnet', source: 'b', exchange: 'https://api.hyperliquid-testnet.xyz/exchange', info: 'https://api.hyperliquid-testnet.xyz/info' },
|
|
27
|
+
mainnet: { chain: 'Mainnet', source: 'a', exchange: 'https://api.hyperliquid.xyz/exchange', info: 'https://api.hyperliquid.xyz/info' }
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// strictly-increasing millisecond nonce
|
|
31
|
+
let _lastNonce = 0;
|
|
32
|
+
function nonce() { let n = Date.now(); if (n <= _lastNonce) n = _lastNonce + 1; _lastNonce = n; return n; }
|
|
33
|
+
|
|
34
|
+
// ---- float_to_wire: no trailing zeros, ≤5 significant figures, integer-safe ----
|
|
35
|
+
// HL rule: prices ≤5 sig figs; perp price decimals ≤ (6 - szDecimals); size to szDecimals.
|
|
36
|
+
function floatToWire(x) {
|
|
37
|
+
if (x == null || typeof x !== 'number' || !isFinite(x)) throw new Error('floatToWire: not a finite number: ' + x);
|
|
38
|
+
if (x === 0) return '0';
|
|
39
|
+
// 5 significant figures, then trim to 8 decimals max, strip trailing zeros.
|
|
40
|
+
const rounded = parseFloat(x.toPrecision(5));
|
|
41
|
+
let s = rounded.toFixed(8);
|
|
42
|
+
s = s.replace(/0+$/, '').replace(/\.$/, '');
|
|
43
|
+
if (s === '-0') s = '0';
|
|
44
|
+
return s;
|
|
45
|
+
}
|
|
46
|
+
function roundToDecimals(x, decimals) { const f = Math.pow(10, decimals); return Math.round(x * f) / f; }
|
|
47
|
+
// size wire: round to szDecimals then float_to_wire. A positive size that
|
|
48
|
+
// rounds to '0' would be silently rejected (or worse) — throw instead.
|
|
49
|
+
function sizeToWire(sz, szDecimals) {
|
|
50
|
+
const d = Math.max(0, szDecimals | 0);
|
|
51
|
+
const wire = floatToWire(roundToDecimals(Number(sz), d));
|
|
52
|
+
if (Number(sz) > 0 && wire === '0') throw new Error('size rounds to zero at ' + d + ' decimals — increase size');
|
|
53
|
+
return wire;
|
|
54
|
+
}
|
|
55
|
+
// price wire: ≤5 sig figs AND ≤ (6 - szDecimals) decimals (perps), then float_to_wire
|
|
56
|
+
function priceToWire(px, szDecimals) {
|
|
57
|
+
const maxDec = Math.max(0, 6 - (szDecimals | 0));
|
|
58
|
+
const five = parseFloat(Number(px).toPrecision(5));
|
|
59
|
+
return floatToWire(roundToDecimals(five, maxDec));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isAddr(a) { return typeof a === 'string' && /^0x[0-9a-fA-F]{40}$/.test(a); }
|
|
63
|
+
|
|
64
|
+
// ==== USER-SIGNED actions (master wallet, EIP-712) ====
|
|
65
|
+
function buildApproveAgent(net, agentAddress) {
|
|
66
|
+
const N = NET[net]; if (!N) throw new Error('bad net'); if (!isAddr(agentAddress)) throw new Error('bad agentAddress');
|
|
67
|
+
const action = { type: 'approveAgent', hyperliquidChain: N.chain, signatureChainId: SIGNATURE_CHAIN_ID, agentAddress, agentName: AGENT_NAME, nonce: nonce() };
|
|
68
|
+
const types = { 'HyperliquidTransaction:ApproveAgent': [
|
|
69
|
+
{ name: 'hyperliquidChain', type: 'string' }, { name: 'agentAddress', type: 'address' },
|
|
70
|
+
{ name: 'agentName', type: 'string' }, { name: 'nonce', type: 'uint64' }
|
|
71
|
+
] };
|
|
72
|
+
return { action, types, primaryType: 'HyperliquidTransaction:ApproveAgent', domain: EIP712_DOMAIN };
|
|
73
|
+
}
|
|
74
|
+
function buildApproveBuilderFee(net) {
|
|
75
|
+
const N = NET[net]; if (!N) throw new Error('bad net');
|
|
76
|
+
const action = { type: 'approveBuilderFee', hyperliquidChain: N.chain, signatureChainId: SIGNATURE_CHAIN_ID, maxFeeRate: MAX_BUILDER_FEE_RATE, builder: BUILDER, nonce: nonce() };
|
|
77
|
+
const types = { 'HyperliquidTransaction:ApproveBuilderFee': [
|
|
78
|
+
{ name: 'hyperliquidChain', type: 'string' }, { name: 'maxFeeRate', type: 'string' },
|
|
79
|
+
{ name: 'builder', type: 'address' }, { name: 'nonce', type: 'uint64' }
|
|
80
|
+
] };
|
|
81
|
+
return { action, types, primaryType: 'HyperliquidTransaction:ApproveBuilderFee', domain: EIP712_DOMAIN };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ==== L1 (agent-signed) ORDER action with normalTpsl grouping + builder ====
|
|
85
|
+
// plan: { assetIndex, szDecimals, isBuy, entryPx, size, slPx?, tpPx? }
|
|
86
|
+
function buildOrderAction(plan) {
|
|
87
|
+
if (plan.assetIndex == null || plan.assetIndex < 0) throw new Error('bad assetIndex');
|
|
88
|
+
if (!(plan.size > 0)) throw new Error('bad size');
|
|
89
|
+
const szDec = plan.szDecimals | 0;
|
|
90
|
+
const s = sizeToWire(plan.size, szDec);
|
|
91
|
+
const orders = [];
|
|
92
|
+
// 1) entry — GTC limit
|
|
93
|
+
orders.push({ a: plan.assetIndex, b: !!plan.isBuy, p: priceToWire(plan.entryPx, szDec), s, r: false, t: { limit: { tif: 'Gtc' } } });
|
|
94
|
+
// 2) SL — reduceOnly stop-market trigger (opposite side)
|
|
95
|
+
if (plan.slPx != null) {
|
|
96
|
+
orders.push({ a: plan.assetIndex, b: !plan.isBuy, p: priceToWire(plan.slPx, szDec), s, r: true,
|
|
97
|
+
t: { trigger: { isMarket: true, triggerPx: priceToWire(plan.slPx, szDec), tpsl: 'sl' } } });
|
|
98
|
+
}
|
|
99
|
+
// 3) TP — reduceOnly take-profit trigger (opposite side)
|
|
100
|
+
if (plan.tpPx != null) {
|
|
101
|
+
orders.push({ a: plan.assetIndex, b: !plan.isBuy, p: priceToWire(plan.tpPx, szDec), s, r: true,
|
|
102
|
+
t: { trigger: { isMarket: true, triggerPx: priceToWire(plan.tpPx, szDec), tpsl: 'tp' } } });
|
|
103
|
+
}
|
|
104
|
+
const grouping = (plan.slPx != null || plan.tpPx != null) ? 'normalTpsl' : 'na';
|
|
105
|
+
return { type: 'order', orders, grouping, builder: { b: BUILDER.toLowerCase(), f: BUILDER_F } };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
X3.actions = {
|
|
109
|
+
MAINNET_PLACEMENT_ENABLED,
|
|
110
|
+
BUILDER, BUILDER_F, MAX_BUILDER_FEE_RATE, AGENT_NAME, SIGNATURE_CHAIN_ID, NET, EIP712_DOMAIN,
|
|
111
|
+
nonce, floatToWire, sizeToWire, priceToWire, roundToDecimals, isAddr,
|
|
112
|
+
buildApproveAgent, buildApproveBuilderFee, buildOrderAction
|
|
113
|
+
};
|
|
114
|
+
// CommonJS export so the wire math can be unit-tested under node.
|
|
115
|
+
try { if (typeof module !== 'undefined' && module.exports) module.exports = X3.actions; } catch (e) {}
|
|
116
|
+
})(typeof window !== 'undefined' ? window : globalThis);
|