@hypelens/hypelens-agent-rail 0.1.14 → 0.1.16
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/README.md +13 -4
- package/package.json +1 -1
- package/skill/hypelens-agent-rail/SKILL.md +5 -5
- package/src/core.js +3 -3
- package/src/exchange.js +177 -13
- package/src/mcp.js +11 -7
package/README.md
CHANGED
|
@@ -2,15 +2,24 @@
|
|
|
2
2
|
**Place, cancel, close Hyperliquid perps.** Agent wallet once · auto-round · **1bp on fills — no sub.**
|
|
3
3
|
## Install → approve → place
|
|
4
4
|
```bash
|
|
5
|
+
# MCP stdio (Claude / Cursor / any MCP host)
|
|
5
6
|
npx -y @hypelens/hypelens-agent-rail
|
|
7
|
+
|
|
8
|
+
# Claude Code one-liner
|
|
9
|
+
claude mcp add hypelens -- npx -y @hypelens/hypelens-agent-rail
|
|
10
|
+
|
|
11
|
+
# OpenClaw / ClawHub skill (skill slug, not npm alone)
|
|
12
|
+
clawhub install hypelens-agent-rail
|
|
13
|
+
# skills.sh (needs public GH; polyparlay anon currently 404)
|
|
14
|
+
npx skills add polyparlay/hypelens -s hypelens-agent-rail -y
|
|
6
15
|
```
|
|
7
|
-
1. `hl_new_agent_wallet` → `HYPELENS_AGENT_PK`
|
|
8
|
-
2. `hl_approve_payloads` —
|
|
16
|
+
1. `hl_quickstart` (optional) → `hl_new_agent_wallet` → `HYPELENS_AGENT_PK`
|
|
17
|
+
2. `hl_approve_payloads(agentAddress)` — **MASTER** signs approveAgent + ApproveBuilderFee **0.01%** once, then POST both
|
|
9
18
|
3. `hl_place_order` / `hl_cancel_order` / `hl_close_position` / `hl_positions` / `hl_balances`
|
|
10
19
|
|
|
11
|
-
**First place (testnet):** after approve,
|
|
20
|
+
**First place (testnet default):** after approve, `hl_place_order({coin:"BTC", isBuy:true, sizeUsd:12, leverage:2})` — **sizeUsd alone** auto-fetches mark and rounds size; `entryPx` optional. No feed required. On reject after sizeUsd, response includes `error` + `next` (margin / approve / min notional).
|
|
12
21
|
|
|
13
|
-
Default **testnet**. `HYPELENS_NET=mainnet` for live. Fee: **1bp on fills** — no sub.
|
|
22
|
+
Default **testnet**. `HYPELENS_NET=mainnet` for live. Fee: **1bp on fills** — no sub. Missing `HYPELENS_AGENT_PK` errors point at the same path.
|
|
14
23
|
|
|
15
24
|
## Optional after install: sizing edge
|
|
16
25
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypelens/hypelens-agent-rail",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Place, cancel, and close Hyperliquid perps from OpenClaw/MCP. Agent wallet once, auto-round, 1bp on fills (no sub). First place: hl_place_order BTC sizeUsd 12. npx @hypelens/hypelens-agent-rail",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: Hyperliquid Place
|
|
3
3
|
description: >-
|
|
4
4
|
hyperliquid place cancel close — Hyperliquid perps rail for OpenClaw/MCP.
|
|
5
5
|
Agent wallet once, auto-round, 1bp builder on fills (no sub). Keywords:
|
|
@@ -31,18 +31,18 @@ metadata:
|
|
|
31
31
|
npx -y @hypelens/hypelens-agent-rail
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
**ClawHub / OpenClaw:** `clawhub install hypelens-agent-rail` · **skills.sh:** `npx skills add polyparlay/hypelens`
|
|
34
|
+
**ClawHub / OpenClaw:** `clawhub install hypelens-agent-rail` · **skills.sh:** `npx skills add polyparlay/hypelens -s hypelens-agent-rail -y` · **MCP:** `npx -y @hypelens/hypelens-agent-rail`
|
|
35
35
|
|
|
36
36
|
## Quickstart (place-ready)
|
|
37
37
|
|
|
38
38
|
1. Call `hl_quickstart` first
|
|
39
39
|
2. `hl_new_agent_wallet` → store as `HYPELENS_AGENT_PK`
|
|
40
|
-
3. `hl_approve_payloads(agentAddress)` →
|
|
40
|
+
3. `hl_approve_payloads(agentAddress)` → **MASTER** signs approveAgent + ApproveBuilderFee **1bp** once, then POST both
|
|
41
41
|
4. `hl_place_order` / `hl_cancel_order` / `hl_close_position` / `hl_positions` / `hl_balances`
|
|
42
42
|
|
|
43
|
+
**First place (testnet default):** after approve, `hl_place_order({coin:"BTC", isBuy:true, sizeUsd:12, leverage:2})` — **sizeUsd alone** auto-fetches mark and rounds size (`entryPx` optional). Feed optional.
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
Default net is **testnet**. Set `HYPELENS_NET=mainnet` for live. Missing intel feed is **advisory** — does **not** block place.
|
|
45
|
+
Default net is **testnet**. Set `HYPELENS_NET=mainnet` for live. Missing intel feed is **advisory** — does **not** block place. Missing PK errors name the same wallet → approve → sizeUsd path.
|
|
46
46
|
|
|
47
47
|
## Sizing rule
|
|
48
48
|
|
package/src/core.js
CHANGED
|
@@ -460,7 +460,7 @@ export async function quickstart({ coin = 'BTC' } = {}) {
|
|
|
460
460
|
],
|
|
461
461
|
fee: '1bp (0.01%) on fills only',
|
|
462
462
|
package: '@hypelens/hypelens-agent-rail',
|
|
463
|
-
first_place_example: { coin: 'BTC', isBuy: true, sizeUsd: 12, leverage: 2, note: 'testnet default;
|
|
463
|
+
first_place_example: { coin: 'BTC', isBuy: true, sizeUsd: 12, leverage: 2, note: 'testnet default; sizeUsd alone → auto mark + szDecimals round; entryPx optional; feed optional' }
|
|
464
464
|
};
|
|
465
465
|
let sample_walls = null;
|
|
466
466
|
let sample_error = null;
|
|
@@ -490,12 +490,12 @@ export async function quickstart({ coin = 'BTC' } = {}) {
|
|
|
490
490
|
'1. Install: npx -y @hypelens/hypelens-agent-rail',
|
|
491
491
|
'2. hl_new_agent_wallet → HYPELENS_AGENT_PK',
|
|
492
492
|
'3. hl_approve_payloads → master signs approveAgent + ApproveBuilderFee 1bp once',
|
|
493
|
-
'4. hl_place_order
|
|
493
|
+
'4. hl_place_order({coin:"BTC", isBuy:true, sizeUsd:12, leverage:2}) — auto mark + size round; then cancel/close as needed',
|
|
494
494
|
'5. Optional after install: hl_walls / hl_whale_book / hl_cascade / hl_pretrade_check for sizing edge'
|
|
495
495
|
],
|
|
496
496
|
sizing_rule: 'Warn if sizeUsd > 20% of account equity; size from balance; confirm mark before place.',
|
|
497
497
|
first_place_example: bootstrap.first_place_example,
|
|
498
|
-
next_if_blocked: '
|
|
498
|
+
next_if_blocked: 'Missing PK: hl_new_agent_wallet → HYPELENS_AGENT_PK → hl_approve_payloads (MASTER signs) → hl_place_order(first_place_example). Wrong net: default is testnet; set HYPELENS_NET=mainnet only for live.',
|
|
499
499
|
why_route_volume_here: AGENT_VALUE,
|
|
500
500
|
sample_walls,
|
|
501
501
|
sample_error,
|
package/src/exchange.js
CHANGED
|
@@ -22,32 +22,46 @@ export function status() {
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const MISSING_PK = 'HYPELENS_AGENT_PK not set — hl_new_agent_wallet → export HYPELENS_AGENT_PK=<pk>, hl_approve_payloads (MASTER signs approveAgent + ApproveBuilderFee 1bp once), then hl_place_order({coin:"BTC", isBuy:true, sizeUsd:12, leverage:2}) on testnet (default). Set HYPELENS_NET=mainnet only for live.';
|
|
26
|
+
|
|
25
27
|
function assertPlacementAllowed(actions) {
|
|
26
28
|
if (net() === 'mainnet' && !actions.MAINNET_PLACEMENT_ENABLED) {
|
|
27
29
|
throw new Error('MAINNET PLACEMENT DISABLED — Set HYPELENS_NET=testnet.');
|
|
28
30
|
}
|
|
29
|
-
if (!process.env.HYPELENS_AGENT_PK) throw new Error(
|
|
31
|
+
if (!process.env.HYPELENS_AGENT_PK) throw new Error(MISSING_PK);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
function agentAddress() {
|
|
33
35
|
const { signer } = loadShipped();
|
|
34
|
-
if (!process.env.HYPELENS_AGENT_PK) throw new Error(
|
|
36
|
+
if (!process.env.HYPELENS_AGENT_PK) throw new Error(MISSING_PK);
|
|
35
37
|
return signer.addressFromPrivateKey(process.env.HYPELENS_AGENT_PK);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export function approvePayloads(agentAddressArg) {
|
|
39
41
|
const { actions } = loadShipped();
|
|
42
|
+
const exchange = actions.NET[net()].exchange;
|
|
40
43
|
return {
|
|
44
|
+
net: net(),
|
|
41
45
|
approveAgent: actions.buildApproveAgent(net(), agentAddressArg),
|
|
42
46
|
approveBuilderFee: actions.buildApproveBuilderFee(net()),
|
|
43
|
-
|
|
47
|
+
fee: '1bp (0.01%) on fills only — no subscription',
|
|
48
|
+
steps: [
|
|
49
|
+
'Sign approveAgent + ApproveBuilderFee with the MASTER wallet (EIP-712) — never the agent pk',
|
|
50
|
+
'POST each {action, signature, nonce} to ' + exchange,
|
|
51
|
+
'Then hl_place_order({coin:"BTC", isBuy:true, sizeUsd:12, leverage:2}) on testnet (size/px auto-round; no entryPx needed)'
|
|
52
|
+
],
|
|
53
|
+
note: 'MASTER signs both once → POST to ' + exchange + ' → first place with sizeUsd (auto mark + szDecimals round). Default net=testnet; HYPELENS_NET=mainnet for live.'
|
|
44
54
|
};
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
export function newAgentWallet() {
|
|
48
58
|
const { signer, sdk } = loadShipped();
|
|
49
59
|
const pk = sdk.randomPrivateKey();
|
|
50
|
-
return {
|
|
60
|
+
return {
|
|
61
|
+
privateKey: pk,
|
|
62
|
+
address: signer.addressFromPrivateKey(pk),
|
|
63
|
+
note: 'export HYPELENS_AGENT_PK=<privateKey>; then hl_approve_payloads(address) for MASTER to sign 1bp once; then hl_place_order({coin:"BTC", isBuy:true, sizeUsd:12, leverage:2})'
|
|
64
|
+
};
|
|
51
65
|
}
|
|
52
66
|
|
|
53
67
|
async function assetMeta(coin) {
|
|
@@ -63,6 +77,112 @@ async function assetMeta(coin) {
|
|
|
63
77
|
return { assetIndex: i, szDecimals: meta.universe[i].szDecimals, name };
|
|
64
78
|
}
|
|
65
79
|
|
|
80
|
+
/** Mark + meta for sizeUsd auto-round (one round-trip). */
|
|
81
|
+
async function assetMetaAndMark(coin) {
|
|
82
|
+
const { actions } = loadShipped();
|
|
83
|
+
const r = await fetch(actions.NET[net()].info, {
|
|
84
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
85
|
+
body: JSON.stringify({ type: 'metaAndAssetCtxs' })
|
|
86
|
+
});
|
|
87
|
+
if (!r.ok) throw new Error('metaAndAssetCtxs HTTP ' + r.status);
|
|
88
|
+
const [meta, ctxs] = await r.json();
|
|
89
|
+
const name = resolveCoin(coin).toUpperCase();
|
|
90
|
+
const i = meta.universe.findIndex((u) => u.name === name);
|
|
91
|
+
if (i < 0) throw new Error('coin not on ' + net() + ': ' + coin);
|
|
92
|
+
const markPx = Number(ctxs[i].markPx);
|
|
93
|
+
if (!(markPx > 0)) throw new Error('no markPx for ' + name + ' on ' + net());
|
|
94
|
+
return { assetIndex: i, szDecimals: meta.universe[i].szDecimals, name, markPx };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Resolve coin size + entryPx. Preferred first-place path: sizeUsd only
|
|
99
|
+
* (fetches mark, size = sizeUsd/mark, rounds to szDecimals). Explicit size+entryPx still works.
|
|
100
|
+
*/
|
|
101
|
+
async function resolveSizeAndPx({ coin, size, entryPx, sizeUsd }) {
|
|
102
|
+
const { actions } = loadShipped();
|
|
103
|
+
const needMark = entryPx == null || (size == null && sizeUsd != null);
|
|
104
|
+
const meta = needMark ? await assetMetaAndMark(coin) : await assetMeta(coin);
|
|
105
|
+
let px = entryPx != null ? Number(entryPx) : meta.markPx;
|
|
106
|
+
if (!(px > 0)) throw new Error('entryPx required (or omit and pass sizeUsd for auto mark)');
|
|
107
|
+
let sz = size != null ? Number(size) : null;
|
|
108
|
+
let usedSizeUsd = sizeUsd != null ? Number(sizeUsd) : null;
|
|
109
|
+
if (sz == null) {
|
|
110
|
+
if (!(usedSizeUsd > 0)) {
|
|
111
|
+
throw new Error('size or sizeUsd required — first place: {coin:"BTC", isBuy:true, sizeUsd:12, leverage:2} (auto mark + szDecimals round; entryPx optional)');
|
|
112
|
+
}
|
|
113
|
+
sz = usedSizeUsd / px;
|
|
114
|
+
} else if (usedSizeUsd == null) {
|
|
115
|
+
usedSizeUsd = sz * px;
|
|
116
|
+
}
|
|
117
|
+
sz = actions.roundToDecimals(sz, meta.szDecimals);
|
|
118
|
+
// Validate wire size will not collapse to 0
|
|
119
|
+
const wire = actions.sizeToWire(sz, meta.szDecimals);
|
|
120
|
+
if (wire === '0') {
|
|
121
|
+
throw new Error('size rounds to zero at ' + meta.szDecimals + ' decimals — increase sizeUsd (BTC first place: sizeUsd:12) or pass a larger size');
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
assetIndex: meta.assetIndex,
|
|
125
|
+
szDecimals: meta.szDecimals,
|
|
126
|
+
name: meta.name,
|
|
127
|
+
size: sz,
|
|
128
|
+
entryPx: px,
|
|
129
|
+
sizeUsd: usedSizeUsd,
|
|
130
|
+
markPx: meta.markPx ?? null,
|
|
131
|
+
autoRounded: size == null || entryPx == null
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Extract HL place/cancel wire error + actionable next after sizeUsd resolve. */
|
|
136
|
+
function interpretExchangeResult(body) {
|
|
137
|
+
if (!body || typeof body !== 'object') {
|
|
138
|
+
return { ok: false, error: 'empty exchange response', next: 'retry hl_place_order({coin:"BTC", isBuy:true, sizeUsd:12, leverage:2}) on testnet' };
|
|
139
|
+
}
|
|
140
|
+
if (body.status === 'err' || body.status === 'error') {
|
|
141
|
+
const raw = typeof body.response === 'string' ? body.response : JSON.stringify(body.response || body);
|
|
142
|
+
return { ok: false, error: raw, next: hintFromHlError(raw) };
|
|
143
|
+
}
|
|
144
|
+
const statuses = body?.response?.data?.statuses;
|
|
145
|
+
if (Array.isArray(statuses)) {
|
|
146
|
+
for (const s of statuses) {
|
|
147
|
+
if (s && typeof s.error === 'string' && s.error) {
|
|
148
|
+
return { ok: false, error: s.error, next: hintFromHlError(s.error), statuses };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// resting / filled / etc
|
|
152
|
+
const resting = statuses.find((s) => s && (s.resting || s.filled));
|
|
153
|
+
if (resting) return { ok: true, error: null, next: null, statuses };
|
|
154
|
+
if (statuses.length) return { ok: true, error: null, next: null, statuses };
|
|
155
|
+
}
|
|
156
|
+
if (body.status === 'ok') return { ok: true, error: null, next: null };
|
|
157
|
+
return { ok: false, error: JSON.stringify(body), next: hintFromHlError(JSON.stringify(body)) };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function hintFromHlError(msg) {
|
|
161
|
+
const m = String(msg || '').toLowerCase();
|
|
162
|
+
if (m.includes('insufficient') || m.includes('margin') || m.includes('not enough')) {
|
|
163
|
+
return 'Fund agent on this net (default testnet) or lower sizeUsd; check hl_balances.equity then retry sizeUsd place';
|
|
164
|
+
}
|
|
165
|
+
if (m.includes('does not exist') || m.includes('user or api wallet') || m.includes('unknown user')) {
|
|
166
|
+
return 'MASTER must POST approveAgent + ApproveBuilderFee 1bp (hl_approve_payloads) before first place; then sizeUsd:12';
|
|
167
|
+
}
|
|
168
|
+
if (m.includes('builder') && (m.includes('fee') || m.includes('approval') || m.includes('approve'))) {
|
|
169
|
+
return 'MASTER must sign ApproveBuilderFee 0.01% (1bp) via hl_approve_payloads and POST it; then retry sizeUsd place';
|
|
170
|
+
}
|
|
171
|
+
if (m.includes('minimum') || m.includes('min ') || m.includes('$10') || m.includes('too small')) {
|
|
172
|
+
return 'Increase sizeUsd (BTC first place: sizeUsd:12) — HL min notional; auto-round may shrink tiny sizes to zero';
|
|
173
|
+
}
|
|
174
|
+
if (m.includes('oracle') || m.includes('price') && m.includes('far')) {
|
|
175
|
+
return 'Omit entryPx and pass sizeUsd only (uses mark), or set entryPx near mark; retry hl_place_order sizeUsd';
|
|
176
|
+
}
|
|
177
|
+
if (m.includes('leverage') || m.includes('max lev')) {
|
|
178
|
+
return 'Lower leverage (first place: leverage:2) or set coin max leverage on HL, then retry sizeUsd place';
|
|
179
|
+
}
|
|
180
|
+
if (m.includes('permit') || m.includes('agent') && m.includes('not')) {
|
|
181
|
+
return 'Run hl_approve_payloads(agentAddress) — MASTER signs approveAgent once, POST, then sizeUsd place';
|
|
182
|
+
}
|
|
183
|
+
return 'Check hl_exchange_status + hl_balances; ensure MASTER approved agent+1bp builder; retry hl_place_order({coin:"BTC", isBuy:true, sizeUsd:12, leverage:2}) on testnet';
|
|
184
|
+
}
|
|
185
|
+
|
|
66
186
|
async function postL1(action) {
|
|
67
187
|
const { actions, signer } = loadShipped();
|
|
68
188
|
assertPlacementAllowed(actions);
|
|
@@ -73,7 +193,9 @@ async function postL1(action) {
|
|
|
73
193
|
body: JSON.stringify({ action: signed.action, signature: signed.signature, nonce: signed.nonce })
|
|
74
194
|
});
|
|
75
195
|
const body = await res.json().catch(() => ({}));
|
|
76
|
-
|
|
196
|
+
const interpreted = interpretExchangeResult(body);
|
|
197
|
+
const ok = res.ok && interpreted.ok;
|
|
198
|
+
return { ok, net: net(), response: body, error: interpreted.error, next: interpreted.next, statuses: interpreted.statuses || null };
|
|
77
199
|
}
|
|
78
200
|
|
|
79
201
|
async function clearinghouse(user) {
|
|
@@ -128,12 +250,14 @@ export async function getPositions({ user, coin } = {}) {
|
|
|
128
250
|
return { net: net(), user: u, positions };
|
|
129
251
|
}
|
|
130
252
|
|
|
131
|
-
export async function placeOrder({ coin, isBuy, size, entryPx, slPx = null, tpPx = null, leverage = null, override = false, skipRiskCheck = false }) {
|
|
253
|
+
export async function placeOrder({ coin, isBuy, size = null, entryPx = null, sizeUsd = null, slPx = null, tpPx = null, leverage = null, override = false, skipRiskCheck = false }) {
|
|
132
254
|
const { actions } = loadShipped();
|
|
133
255
|
assertPlacementAllowed(actions);
|
|
256
|
+
const resolved = await resolveSizeAndPx({ coin, size, entryPx, sizeUsd });
|
|
134
257
|
let risk = null;
|
|
135
|
-
|
|
136
|
-
|
|
258
|
+
const lev = leverage != null ? Number(leverage) : null;
|
|
259
|
+
if (!skipRiskCheck && lev) {
|
|
260
|
+
risk = await pretradeCheckFull({ coin, dir: isBuy ? 'long' : 'short', leverage: lev, entryPx: resolved.entryPx, sizeUsd: resolved.sizeUsd });
|
|
137
261
|
if (risk.refuseReason === 'full_feed_unconfigured') {
|
|
138
262
|
risk = { ...risk, advisory: true, feedAdvisory: true, note: (risk.note || '') + ' Place allowed without full feed; heat is advisory.' };
|
|
139
263
|
}
|
|
@@ -145,14 +269,52 @@ export async function placeOrder({ coin, isBuy, size, entryPx, slPx = null, tpPx
|
|
|
145
269
|
placed: false,
|
|
146
270
|
refused: 'liq price ' + risk.liqPx + ' lands inside a $' + wallSz + 'M wall — pass override:true to force',
|
|
147
271
|
risk,
|
|
148
|
-
builderFeeAttached: false
|
|
272
|
+
builderFeeAttached: false,
|
|
273
|
+
size: resolved.size,
|
|
274
|
+
entryPx: resolved.entryPx,
|
|
275
|
+
sizeUsd: resolved.sizeUsd
|
|
149
276
|
};
|
|
150
277
|
}
|
|
151
278
|
}
|
|
152
|
-
|
|
153
|
-
|
|
279
|
+
let sizingWarn = null;
|
|
280
|
+
try {
|
|
281
|
+
const bal = await getBalances();
|
|
282
|
+
if (bal.equity > 0 && resolved.sizeUsd > bal.equity * 0.2) {
|
|
283
|
+
sizingWarn = 'sizeUsd ' + Math.round(resolved.sizeUsd) + ' > 20% of equity ' + Math.round(bal.equity) + ' — size down or confirm';
|
|
284
|
+
}
|
|
285
|
+
} catch (_) { /* balances optional for place */ }
|
|
286
|
+
const action = actions.buildOrderAction({
|
|
287
|
+
assetIndex: resolved.assetIndex,
|
|
288
|
+
szDecimals: resolved.szDecimals,
|
|
289
|
+
isBuy,
|
|
290
|
+
entryPx: resolved.entryPx,
|
|
291
|
+
size: resolved.size,
|
|
292
|
+
slPx,
|
|
293
|
+
tpPx
|
|
294
|
+
});
|
|
154
295
|
const posted = await postL1(action);
|
|
155
|
-
|
|
296
|
+
const out = {
|
|
297
|
+
placed: posted.ok,
|
|
298
|
+
net: posted.net,
|
|
299
|
+
response: posted.response,
|
|
300
|
+
risk,
|
|
301
|
+
builderFeeAttached: posted.ok,
|
|
302
|
+
coin: resolved.name,
|
|
303
|
+
size: resolved.size,
|
|
304
|
+
entryPx: resolved.entryPx,
|
|
305
|
+
sizeUsd: resolved.sizeUsd,
|
|
306
|
+
autoRounded: resolved.autoRounded,
|
|
307
|
+
sizingWarn,
|
|
308
|
+
feeModel: '1bp on fills only — no subscription'
|
|
309
|
+
};
|
|
310
|
+
if (!posted.ok) {
|
|
311
|
+
out.error = posted.error || 'place rejected by exchange';
|
|
312
|
+
out.next = posted.next || hintFromHlError(posted.error || '');
|
|
313
|
+
if (posted.statuses) out.statuses = posted.statuses;
|
|
314
|
+
} else {
|
|
315
|
+
out.next = 'placed — cancel via hl_cancel_order; close via hl_close_position; fees 1bp on fills only';
|
|
316
|
+
}
|
|
317
|
+
return out;
|
|
156
318
|
}
|
|
157
319
|
|
|
158
320
|
/** Cancel by oid and/or cloid. */
|
|
@@ -165,7 +327,9 @@ export async function cancelOrder({ coin, oid = null, cloid = null }) {
|
|
|
165
327
|
else if (oid != null) action = actions.buildCancelAction([{ assetIndex, oid: Number(oid) }]);
|
|
166
328
|
else throw new Error('oid or cloid required — pass oid or cloid to hl_cancel_order');
|
|
167
329
|
const posted = await postL1(action);
|
|
168
|
-
|
|
330
|
+
const out = { cancelled: posted.ok, net: posted.net, response: posted.response, coin: resolveCoin(coin).toUpperCase(), oid, cloid };
|
|
331
|
+
if (!posted.ok) { out.error = posted.error || 'cancel rejected'; out.next = posted.next || hintFromHlError(posted.error || ''); }
|
|
332
|
+
return out;
|
|
169
333
|
}
|
|
170
334
|
|
|
171
335
|
/** IOC reduce-only close for a coin (uses position size if size omitted). */
|
package/src/mcp.js
CHANGED
|
@@ -13,10 +13,10 @@ const wrap = (fn) => async (args) => {
|
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export async function main() {
|
|
16
|
-
const server = new McpServer({ name: 'hypelens-agent-rail', version: '0.1.
|
|
16
|
+
const server = new McpServer({ name: 'hypelens-agent-rail', version: '0.1.16' });
|
|
17
17
|
|
|
18
18
|
server.tool('hl_quickstart',
|
|
19
|
-
'CALL FIRST: place-ready bootstrap —
|
|
19
|
+
'CALL FIRST: place-ready bootstrap — wallet + MASTER ApproveBuilderFee 1bp once, then hl_place_order sizeUsd:12. Default testnet. 1bp fills, no sub.',
|
|
20
20
|
{ coin: z.string().optional().describe("Optional coin for sample walls (default BTC)") },
|
|
21
21
|
wrap(({ coin }) => quickstart({ coin })));
|
|
22
22
|
|
|
@@ -29,7 +29,7 @@ export async function main() {
|
|
|
29
29
|
{}, wrap(() => newAgentWallet()));
|
|
30
30
|
|
|
31
31
|
server.tool('hl_approve_payloads',
|
|
32
|
-
'Setup: EIP-712 payloads for MASTER to sign once — approveAgent + ApproveBuilderFee 0.01% (1bp). No
|
|
32
|
+
'Setup: EIP-712 payloads for MASTER to sign once — approveAgent + ApproveBuilderFee 0.01% (1bp). Then POST both; then sizeUsd place. No sub.',
|
|
33
33
|
{ agentAddress: z.string().regex(/^0x[0-9a-fA-F]{40}$/) },
|
|
34
34
|
wrap(({ agentAddress }) => approvePayloads(agentAddress)));
|
|
35
35
|
|
|
@@ -44,11 +44,15 @@ export async function main() {
|
|
|
44
44
|
wrap((a) => getPositions(a)));
|
|
45
45
|
|
|
46
46
|
server.tool('hl_place_order',
|
|
47
|
-
'PLACE Hyperliquid perps @ 1bp on fills
|
|
47
|
+
'PLACE Hyperliquid perps @ 1bp on fills. Preferred: sizeUsd (auto mark + szDecimals round; entryPx optional). Or pass size+entryPx. Heat advisory. No sub.',
|
|
48
48
|
{
|
|
49
|
-
coin: z.string()
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
coin: z.string().describe('e.g. BTC'),
|
|
50
|
+
isBuy: z.boolean(),
|
|
51
|
+
sizeUsd: z.number().positive().optional().describe('Preferred first-place notional USD — auto mark + size round (e.g. 12)'),
|
|
52
|
+
size: z.number().positive().optional().describe('Coin units; optional if sizeUsd set'),
|
|
53
|
+
entryPx: z.number().positive().optional().describe('Limit px; omit with sizeUsd to use mark'),
|
|
54
|
+
slPx: z.number().positive().optional(), tpPx: z.number().positive().optional(),
|
|
55
|
+
leverage: z.number().positive().optional().describe('Optional; enables advisory pre-trade risk check (first place: 2)'),
|
|
52
56
|
override: z.boolean().optional(), skipRiskCheck: z.boolean().optional()
|
|
53
57
|
},
|
|
54
58
|
wrap((a) => placeOrder(a)));
|