@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.
@@ -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);