@exagent/agent 0.3.5 → 0.3.7

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.
Files changed (78) hide show
  1. package/dist/chunk-7UGLJO6W.js +6392 -0
  2. package/dist/chunk-EHAOPCTJ.js +6406 -0
  3. package/dist/chunk-FGMXTW5I.js +6540 -0
  4. package/dist/chunk-IVA2SCSN.js +6756 -0
  5. package/dist/chunk-JHXCSGPC.js +6352 -0
  6. package/dist/chunk-V6O4UXVN.js +6345 -0
  7. package/dist/chunk-ZRAOPQQW.js +6406 -0
  8. package/dist/cli.js +40 -98
  9. package/dist/index.d.ts +24 -2
  10. package/dist/index.js +1 -1
  11. package/package.json +17 -14
  12. package/.turbo/turbo-build.log +0 -17
  13. package/src/bridge/across.ts +0 -240
  14. package/src/bridge/bridge-manager.ts +0 -87
  15. package/src/bridge/index.ts +0 -9
  16. package/src/bridge/types.ts +0 -77
  17. package/src/chains.ts +0 -105
  18. package/src/cli.ts +0 -244
  19. package/src/config.ts +0 -499
  20. package/src/diagnostics.ts +0 -335
  21. package/src/index.ts +0 -98
  22. package/src/llm/anthropic.ts +0 -63
  23. package/src/llm/base.ts +0 -264
  24. package/src/llm/deepseek.ts +0 -48
  25. package/src/llm/google.ts +0 -63
  26. package/src/llm/groq.ts +0 -48
  27. package/src/llm/index.ts +0 -42
  28. package/src/llm/mistral.ts +0 -48
  29. package/src/llm/ollama.ts +0 -52
  30. package/src/llm/openai.ts +0 -51
  31. package/src/llm/together.ts +0 -48
  32. package/src/llm-providers.ts +0 -100
  33. package/src/logger.ts +0 -137
  34. package/src/paper/executor.ts +0 -201
  35. package/src/paper/index.ts +0 -1
  36. package/src/perp/client.ts +0 -200
  37. package/src/perp/index.ts +0 -12
  38. package/src/perp/msgpack.ts +0 -272
  39. package/src/perp/orders.ts +0 -234
  40. package/src/perp/positions.ts +0 -126
  41. package/src/perp/signer.ts +0 -277
  42. package/src/perp/types.ts +0 -192
  43. package/src/perp/websocket.ts +0 -274
  44. package/src/position-tracker.ts +0 -243
  45. package/src/prediction/client.ts +0 -281
  46. package/src/prediction/index.ts +0 -3
  47. package/src/prediction/order-manager.ts +0 -297
  48. package/src/prediction/types.ts +0 -151
  49. package/src/relay.ts +0 -254
  50. package/src/runtime.ts +0 -1755
  51. package/src/scrub-secrets.ts +0 -39
  52. package/src/setup.ts +0 -384
  53. package/src/signal.ts +0 -212
  54. package/src/spot/aerodrome.ts +0 -158
  55. package/src/spot/client.ts +0 -138
  56. package/src/spot/index.ts +0 -11
  57. package/src/spot/swap-manager.ts +0 -219
  58. package/src/spot/types.ts +0 -203
  59. package/src/spot/uniswap.ts +0 -150
  60. package/src/store.ts +0 -50
  61. package/src/strategy/index.ts +0 -2
  62. package/src/strategy/loader.ts +0 -191
  63. package/src/strategy/templates.ts +0 -125
  64. package/src/trading/index.ts +0 -2
  65. package/src/trading/market.ts +0 -120
  66. package/src/trading/risk.ts +0 -107
  67. package/src/ui.ts +0 -75
  68. package/test-bridge-arb-to-base.mjs +0 -223
  69. package/test-funded-check.mjs +0 -79
  70. package/test-funded-phase19.mjs +0 -933
  71. package/test-hl-deposit-recover.mjs +0 -281
  72. package/test-hl-withdraw.mjs +0 -372
  73. package/test-live-signing.mjs +0 -374
  74. package/test-phase7.mjs +0 -416
  75. package/test-recover-arb.mjs +0 -206
  76. package/test-spot-bridge.mjs +0 -248
  77. package/test-wallet-setup.mjs +0 -126
  78. package/tsconfig.json +0 -8
@@ -1,272 +0,0 @@
1
- /**
2
- * Minimal MessagePack Encoder
3
- *
4
- * Vendored from @std/msgpack (Deno standard library).
5
- * Required for Hyperliquid L1 action signing — actions are hashed
6
- * via msgpack(action) + nonce, NOT JSON.stringify.
7
- *
8
- * Supports: number, bigint, string, boolean, null, Uint8Array, arrays, objects.
9
- * BigInt values encode as msgpack int64/uint64.
10
- */
11
-
12
- const FOUR_BITS = 16;
13
- const FIVE_BITS = 32;
14
- const SEVEN_BITS = 128;
15
- const EIGHT_BITS = 256;
16
- const FIFTEEN_BITS = 32768;
17
- const SIXTEEN_BITS = 65536;
18
- const THIRTY_ONE_BITS = 2147483648;
19
- const THIRTY_TWO_BITS = 4294967296;
20
- const SIXTY_THREE_BITS = 9223372036854775808n;
21
- const SIXTY_FOUR_BITS = 18446744073709551616n;
22
-
23
- const textEncoder = new TextEncoder();
24
-
25
- export type MsgpackValue =
26
- | number
27
- | bigint
28
- | string
29
- | boolean
30
- | null
31
- | Uint8Array
32
- | readonly MsgpackValue[]
33
- | { [key: string]: MsgpackValue };
34
-
35
- export function encodeMsgpack(object: MsgpackValue): Uint8Array {
36
- const byteParts: Uint8Array[] = [];
37
- encodeSlice(object, byteParts);
38
- return concatBytes(...byteParts);
39
- }
40
-
41
- function concatBytes(...arrays: Uint8Array[]): Uint8Array {
42
- const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
43
- const result = new Uint8Array(totalLength);
44
- let offset = 0;
45
- for (const arr of arrays) {
46
- result.set(arr, offset);
47
- offset += arr.length;
48
- }
49
- return result;
50
- }
51
-
52
- function encodeFloat64(num: number): Uint8Array {
53
- const dataView = new DataView(new ArrayBuffer(9));
54
- dataView.setFloat64(1, num);
55
- dataView.setUint8(0, 0xcb);
56
- return new Uint8Array(dataView.buffer);
57
- }
58
-
59
- function encodeNumber(num: number): Uint8Array {
60
- if (!Number.isInteger(num)) return encodeFloat64(num);
61
-
62
- if (num < 0) {
63
- if (num >= -FIVE_BITS) return new Uint8Array([num & 0xff]);
64
- if (num >= -SEVEN_BITS) return new Uint8Array([0xd0, num & 0xff]);
65
- if (num >= -FIFTEEN_BITS) {
66
- const dv = new DataView(new ArrayBuffer(3));
67
- dv.setInt16(1, num);
68
- dv.setUint8(0, 0xd1);
69
- return new Uint8Array(dv.buffer);
70
- }
71
- if (num >= -THIRTY_ONE_BITS) {
72
- const dv = new DataView(new ArrayBuffer(5));
73
- dv.setInt32(1, num);
74
- dv.setUint8(0, 0xd2);
75
- return new Uint8Array(dv.buffer);
76
- }
77
- return encodeFloat64(num);
78
- }
79
-
80
- if (num <= 0x7f) return new Uint8Array([num]);
81
- if (num < EIGHT_BITS) return new Uint8Array([0xcc, num]);
82
- if (num < SIXTEEN_BITS) {
83
- const dv = new DataView(new ArrayBuffer(3));
84
- dv.setUint16(1, num);
85
- dv.setUint8(0, 0xcd);
86
- return new Uint8Array(dv.buffer);
87
- }
88
- if (num < THIRTY_TWO_BITS) {
89
- const dv = new DataView(new ArrayBuffer(5));
90
- dv.setUint32(1, num);
91
- dv.setUint8(0, 0xce);
92
- return new Uint8Array(dv.buffer);
93
- }
94
- return encodeFloat64(num);
95
- }
96
-
97
- function encodeSlice(object: MsgpackValue, byteParts: Uint8Array[]): void {
98
- if (object === null) {
99
- byteParts.push(new Uint8Array([0xc0]));
100
- return;
101
- }
102
- if (object === false) {
103
- byteParts.push(new Uint8Array([0xc2]));
104
- return;
105
- }
106
- if (object === true) {
107
- byteParts.push(new Uint8Array([0xc3]));
108
- return;
109
- }
110
-
111
- if (typeof object === 'number') {
112
- byteParts.push(encodeNumber(object));
113
- return;
114
- }
115
-
116
- if (typeof object === 'bigint') {
117
- if (object < 0) {
118
- if (object < -SIXTY_THREE_BITS)
119
- throw new Error('Cannot safely encode bigint larger than 64 bits');
120
- const dv = new DataView(new ArrayBuffer(9));
121
- dv.setBigInt64(1, object);
122
- dv.setUint8(0, 0xd3);
123
- byteParts.push(new Uint8Array(dv.buffer));
124
- return;
125
- }
126
- if (object >= SIXTY_FOUR_BITS)
127
- throw new Error('Cannot safely encode bigint larger than 64 bits');
128
- const dv = new DataView(new ArrayBuffer(9));
129
- dv.setBigUint64(1, object);
130
- dv.setUint8(0, 0xcf);
131
- byteParts.push(new Uint8Array(dv.buffer));
132
- return;
133
- }
134
-
135
- if (typeof object === 'string') {
136
- const encoded = textEncoder.encode(object);
137
- const len = encoded.length;
138
- if (len < FIVE_BITS) byteParts.push(new Uint8Array([0xa0 | len]));
139
- else if (len < EIGHT_BITS) byteParts.push(new Uint8Array([0xd9, len]));
140
- else if (len < SIXTEEN_BITS) {
141
- const dv = new DataView(new ArrayBuffer(3));
142
- dv.setUint16(1, len);
143
- dv.setUint8(0, 0xda);
144
- byteParts.push(new Uint8Array(dv.buffer));
145
- } else if (len < THIRTY_TWO_BITS) {
146
- const dv = new DataView(new ArrayBuffer(5));
147
- dv.setUint32(1, len);
148
- dv.setUint8(0, 0xdb);
149
- byteParts.push(new Uint8Array(dv.buffer));
150
- } else {
151
- throw new Error('Cannot safely encode string with size larger than 32 bits');
152
- }
153
- byteParts.push(encoded);
154
- return;
155
- }
156
-
157
- if (object instanceof Uint8Array) {
158
- if (object.length < EIGHT_BITS) {
159
- byteParts.push(new Uint8Array([0xc4, object.length]));
160
- } else if (object.length < SIXTEEN_BITS) {
161
- const dv = new DataView(new ArrayBuffer(3));
162
- dv.setUint16(1, object.length);
163
- dv.setUint8(0, 0xc5);
164
- byteParts.push(new Uint8Array(dv.buffer));
165
- } else if (object.length < THIRTY_TWO_BITS) {
166
- const dv = new DataView(new ArrayBuffer(5));
167
- dv.setUint32(1, object.length);
168
- dv.setUint8(0, 0xc6);
169
- byteParts.push(new Uint8Array(dv.buffer));
170
- } else {
171
- throw new Error('Cannot safely encode Uint8Array with size larger than 32 bits');
172
- }
173
- byteParts.push(object);
174
- return;
175
- }
176
-
177
- if (Array.isArray(object)) {
178
- if (object.length < FOUR_BITS) byteParts.push(new Uint8Array([0x90 | object.length]));
179
- else if (object.length < SIXTEEN_BITS) {
180
- const dv = new DataView(new ArrayBuffer(3));
181
- dv.setUint16(1, object.length);
182
- dv.setUint8(0, 0xdc);
183
- byteParts.push(new Uint8Array(dv.buffer));
184
- } else if (object.length < THIRTY_TWO_BITS) {
185
- const dv = new DataView(new ArrayBuffer(5));
186
- dv.setUint32(1, object.length);
187
- dv.setUint8(0, 0xdd);
188
- byteParts.push(new Uint8Array(dv.buffer));
189
- } else {
190
- throw new Error('Cannot safely encode array with size larger than 32 bits');
191
- }
192
- for (const obj of object) encodeSlice(obj, byteParts);
193
- return;
194
- }
195
-
196
- // Plain object → msgpack map
197
- if (typeof object === 'object') {
198
- const entries = Object.entries(object);
199
- const numKeys = entries.length;
200
- if (numKeys < FOUR_BITS) byteParts.push(new Uint8Array([0x80 | numKeys]));
201
- else if (numKeys < SIXTEEN_BITS) {
202
- const dv = new DataView(new ArrayBuffer(3));
203
- dv.setUint16(1, numKeys);
204
- dv.setUint8(0, 0xde);
205
- byteParts.push(new Uint8Array(dv.buffer));
206
- } else if (numKeys < THIRTY_TWO_BITS) {
207
- const dv = new DataView(new ArrayBuffer(5));
208
- dv.setUint32(1, numKeys);
209
- dv.setUint8(0, 0xdf);
210
- byteParts.push(new Uint8Array(dv.buffer));
211
- } else {
212
- throw new Error('Cannot safely encode map with size larger than 32 bits');
213
- }
214
- for (const [key, value] of entries) {
215
- encodeSlice(key, byteParts);
216
- encodeSlice(value as MsgpackValue, byteParts);
217
- }
218
- return;
219
- }
220
-
221
- throw new Error('Cannot safely encode value into messagepack');
222
- }
223
-
224
- // ── HELPERS ──────────────────────────────────────────────────
225
-
226
- /**
227
- * Convert large integers to BigInt for correct msgpack encoding.
228
- * Integers >= 2^32 or < -2^31 must be encoded as int64/uint64,
229
- * not as float64 (which would lose precision).
230
- */
231
- export function largeIntToBigInt(obj: MsgpackValue): MsgpackValue {
232
- if (typeof obj === 'number' && Number.isInteger(obj) && (obj >= 0x100000000 || obj < -0x80000000)) {
233
- return BigInt(obj);
234
- }
235
- if (Array.isArray(obj)) return obj.map(largeIntToBigInt);
236
- if (typeof obj === 'object' && obj !== null && !(obj instanceof Uint8Array)) {
237
- const result: Record<string, MsgpackValue> = {};
238
- for (const key in obj) {
239
- result[key] = largeIntToBigInt((obj as Record<string, MsgpackValue>)[key]);
240
- }
241
- return result;
242
- }
243
- return obj;
244
- }
245
-
246
- /**
247
- * Strip undefined keys from objects recursively.
248
- * msgpack cannot encode undefined values.
249
- */
250
- export function removeUndefinedKeys(obj: MsgpackValue): MsgpackValue {
251
- if (Array.isArray(obj)) return obj.map(removeUndefinedKeys);
252
- if (typeof obj === 'object' && obj !== null && !(obj instanceof Uint8Array)) {
253
- const result: Record<string, MsgpackValue> = {};
254
- for (const key in obj) {
255
- const val = (obj as Record<string, MsgpackValue>)[key];
256
- if (val !== undefined) {
257
- result[key] = removeUndefinedKeys(val);
258
- }
259
- }
260
- return result;
261
- }
262
- return obj;
263
- }
264
-
265
- /**
266
- * Encode a number/bigint as big-endian uint64 (8 bytes).
267
- */
268
- export function toUint64Bytes(n: bigint | number | string): Uint8Array {
269
- const bytes = new Uint8Array(8);
270
- new DataView(bytes.buffer).setBigUint64(0, BigInt(n));
271
- return bytes;
272
- }
@@ -1,234 +0,0 @@
1
- /**
2
- * Hyperliquid Order Management
3
- *
4
- * Order placement, cancellation, and leverage updates on Hyperliquid L1.
5
- * No builder fee — private group, not needed.
6
- */
7
-
8
- import type { PerpConfig, PerpTradeSignal, OrderResult } from './types.js';
9
- import { HyperliquidClient } from './client.js';
10
- import { HyperliquidSigner, getNextNonce } from './signer.js';
11
-
12
- export class HyperliquidOrderManager {
13
- private readonly client: HyperliquidClient;
14
- private readonly signer: HyperliquidSigner;
15
- private readonly config: PerpConfig;
16
-
17
- constructor(client: HyperliquidClient, signer: HyperliquidSigner, config: PerpConfig) {
18
- this.client = client;
19
- this.signer = signer;
20
- this.config = config;
21
- }
22
-
23
- async placeOrder(signal: PerpTradeSignal): Promise<OrderResult> {
24
- try {
25
- // Enforce config-based caps (skip for reduce-only/close orders)
26
- if (!signal.reduceOnly) {
27
- const notionalUSD = signal.size * signal.price;
28
- if (notionalUSD > this.config.maxNotionalUSD) {
29
- const msg = `Notional $${notionalUSD.toFixed(0)} exceeds max $${this.config.maxNotionalUSD} for ${signal.instrument}`;
30
- console.warn(`[perp] Order rejected: ${msg}`);
31
- return { success: false, status: 'error', error: msg };
32
- }
33
- if (signal.leverage > this.config.maxLeverage) {
34
- const msg = `Leverage ${signal.leverage}x exceeds max ${this.config.maxLeverage}x for ${signal.instrument}`;
35
- console.warn(`[perp] Order rejected: ${msg}`);
36
- return { success: false, status: 'error', error: msg };
37
- }
38
- }
39
-
40
- const assetIndex = await this.client.getAssetIndex(signal.instrument);
41
- const isBuy = signal.action === 'open_long' || signal.action === 'close_short';
42
-
43
- // Round size to instrument's szDecimals (Hyperliquid rejects sizes with excess precision)
44
- const szDecimals = await this.client.getSzDecimals(signal.instrument);
45
- const factor = 10 ** szDecimals;
46
- const roundedSize = Math.floor(signal.size * factor) / factor;
47
- if (roundedSize <= 0) {
48
- const msg = `Size ${signal.size} rounds to 0 at ${szDecimals} decimal places for ${signal.instrument}`;
49
- console.warn(`[perp] Order rejected: ${msg}`);
50
- return { success: false, status: 'error', error: msg };
51
- }
52
-
53
- const orderWire = {
54
- a: assetIndex,
55
- b: isBuy,
56
- p: signal.orderType === 'market' ? this.getMarketPrice(signal) : signal.price.toString(),
57
- s: roundedSize.toString(),
58
- r: signal.reduceOnly,
59
- t: signal.orderType === 'market'
60
- ? { limit: { tif: 'Ioc' as const } }
61
- : { limit: { tif: 'Gtc' as const } },
62
- };
63
-
64
- const action: Record<string, unknown> = {
65
- type: 'order',
66
- orders: [orderWire],
67
- grouping: 'na',
68
- };
69
-
70
- const nonce = getNextNonce();
71
- const { signature } = await this.signer.signAction(action, nonce);
72
-
73
- const resp = await this.exchangeRequest({
74
- action,
75
- nonce: Number(nonce),
76
- signature: {
77
- r: signature.slice(0, 66),
78
- s: `0x${signature.slice(66, 130)}`,
79
- v: parseInt(signature.slice(130, 132), 16),
80
- },
81
- vaultAddress: null,
82
- });
83
-
84
- return this.parseOrderResponse(resp);
85
- } catch (error) {
86
- const message = error instanceof Error ? error.message : String(error);
87
- console.error(`[perp] Order failed for ${signal.instrument}:`, message);
88
- return { success: false, status: 'error', error: message };
89
- }
90
- }
91
-
92
- async cancelOrder(instrument: string, orderId: number): Promise<boolean> {
93
- try {
94
- const assetIndex = await this.client.getAssetIndex(instrument);
95
-
96
- const action: Record<string, unknown> = {
97
- type: 'cancel',
98
- cancels: [{ a: assetIndex, o: orderId }],
99
- };
100
-
101
- const nonce = getNextNonce();
102
- const { signature } = await this.signer.signAction(action, nonce);
103
-
104
- await this.exchangeRequest({
105
- action,
106
- nonce: Number(nonce),
107
- signature: {
108
- r: signature.slice(0, 66),
109
- s: `0x${signature.slice(66, 130)}`,
110
- v: parseInt(signature.slice(130, 132), 16),
111
- },
112
- vaultAddress: null,
113
- });
114
-
115
- console.log(`[perp] Cancelled order ${orderId} for ${instrument}`);
116
- return true;
117
- } catch (error) {
118
- const message = error instanceof Error ? error.message : String(error);
119
- console.error(`[perp] Cancel failed for order ${orderId}:`, message);
120
- return false;
121
- }
122
- }
123
-
124
- async closePosition(instrument: string, positionSize: number): Promise<OrderResult> {
125
- const isLong = positionSize > 0;
126
- return this.placeOrder({
127
- action: isLong ? 'close_long' : 'close_short',
128
- instrument,
129
- size: Math.abs(positionSize),
130
- price: 0,
131
- leverage: 1,
132
- orderType: 'market',
133
- reduceOnly: true,
134
- confidence: 1.0,
135
- reasoning: 'Position close',
136
- });
137
- }
138
-
139
- async updateLeverage(instrument: string, leverage: number, isCross: boolean = true): Promise<boolean> {
140
- try {
141
- const assetIndex = await this.client.getAssetIndex(instrument);
142
-
143
- const action: Record<string, unknown> = {
144
- type: 'updateLeverage',
145
- asset: assetIndex,
146
- isCross,
147
- leverage,
148
- };
149
-
150
- const nonce = getNextNonce();
151
- const { signature } = await this.signer.signAction(action, nonce);
152
-
153
- await this.exchangeRequest({
154
- action,
155
- nonce: Number(nonce),
156
- signature: {
157
- r: signature.slice(0, 66),
158
- s: `0x${signature.slice(66, 130)}`,
159
- v: parseInt(signature.slice(130, 132), 16),
160
- },
161
- vaultAddress: null,
162
- });
163
-
164
- console.log(`[perp] Leverage set for ${instrument}: ${leverage}x (${isCross ? 'cross' : 'isolated'})`);
165
- return true;
166
- } catch (error) {
167
- const message = error instanceof Error ? error.message : String(error);
168
- console.error(`[perp] Leverage update failed for ${instrument}:`, message);
169
- return false;
170
- }
171
- }
172
-
173
- // ── PRIVATE ────────────────────────────────────────────────
174
-
175
- private getMarketPrice(signal: PerpTradeSignal): string {
176
- const isBuy = signal.action === 'open_long' || signal.action === 'close_short';
177
- if (signal.price > 0) {
178
- const slippage = isBuy ? 1.005 : 0.995;
179
- return (signal.price * slippage).toString();
180
- }
181
- return '0';
182
- }
183
-
184
- private parseOrderResponse(resp: any): OrderResult {
185
- if (resp?.status === 'ok' && resp?.response?.type === 'order') {
186
- const statuses = resp.response.data?.statuses || [];
187
- if (statuses.length > 0) {
188
- const status = statuses[0];
189
-
190
- if (status.filled) {
191
- return {
192
- success: true,
193
- orderId: status.filled.oid,
194
- status: 'filled',
195
- avgPrice: status.filled.avgPx,
196
- filledSize: status.filled.totalSz,
197
- };
198
- }
199
-
200
- if (status.resting) {
201
- return {
202
- success: true,
203
- orderId: status.resting.oid,
204
- status: 'resting',
205
- };
206
- }
207
-
208
- if (status.error) {
209
- return { success: false, status: 'error', error: status.error };
210
- }
211
- }
212
- }
213
-
214
- return {
215
- success: false,
216
- status: 'error',
217
- error: `Unexpected response: ${JSON.stringify(resp)}`,
218
- };
219
- }
220
-
221
- private async exchangeRequest(body: Record<string, unknown>): Promise<any> {
222
- const resp = await fetch(`${this.config.apiUrl}/exchange`, {
223
- method: 'POST',
224
- headers: { 'Content-Type': 'application/json' },
225
- body: JSON.stringify(body),
226
- });
227
-
228
- if (!resp.ok) {
229
- throw new Error(`Hyperliquid Exchange API error: ${resp.status} ${await resp.text()}`);
230
- }
231
-
232
- return resp.json();
233
- }
234
- }
@@ -1,126 +0,0 @@
1
- /**
2
- * Hyperliquid Position Tracking
3
- *
4
- * Position management with 5s cache, account summaries,
5
- * and liquidation proximity monitoring.
6
- */
7
-
8
- import type { PerpPosition, AccountSummary } from './types.js';
9
- import { HyperliquidClient } from './client.js';
10
-
11
- export class HyperliquidPositionManager {
12
- private readonly client: HyperliquidClient;
13
- private readonly userAddress: string;
14
-
15
- private cachedPositions: PerpPosition[] = [];
16
- private cachedAccount: AccountSummary | null = null;
17
- private lastRefreshAt: number = 0;
18
- private readonly cacheTtlMs: number = 5_000;
19
-
20
- constructor(client: HyperliquidClient, userAddress: string) {
21
- this.client = client;
22
- this.userAddress = userAddress;
23
- }
24
-
25
- // ── POSITION QUERIES ───────────────────────────────────────
26
-
27
- async getPositions(forceRefresh: boolean = false): Promise<PerpPosition[]> {
28
- if (!forceRefresh && this.isCacheFresh()) return this.cachedPositions;
29
- await this.refresh();
30
- return this.cachedPositions;
31
- }
32
-
33
- async getPosition(instrument: string): Promise<PerpPosition | null> {
34
- const positions = await this.getPositions();
35
- return positions.find((p) => p.instrument === instrument) ?? null;
36
- }
37
-
38
- async getAccountSummary(forceRefresh: boolean = false): Promise<AccountSummary> {
39
- if (!forceRefresh && this.isCacheFresh() && this.cachedAccount) {
40
- return this.cachedAccount;
41
- }
42
- await this.refresh();
43
- return this.cachedAccount!;
44
- }
45
-
46
- // ── LIQUIDATION MONITORING ─────────────────────────────────
47
-
48
- async getLiquidationProximity(): Promise<Map<string, number>> {
49
- const positions = await this.getPositions();
50
- const proximities = new Map<string, number>();
51
-
52
- for (const pos of positions) {
53
- if (pos.liquidationPrice <= 0 || pos.markPrice <= 0) {
54
- proximities.set(pos.instrument, 0);
55
- continue;
56
- }
57
-
58
- let proximity: number;
59
-
60
- if (pos.size > 0) {
61
- if (pos.markPrice <= pos.liquidationPrice) {
62
- proximity = 1.0;
63
- } else {
64
- const distanceToLiq = pos.markPrice - pos.liquidationPrice;
65
- const entryToLiq = pos.entryPrice - pos.liquidationPrice;
66
- proximity = entryToLiq > 0 ? 1 - distanceToLiq / entryToLiq : 0;
67
- }
68
- } else {
69
- if (pos.markPrice >= pos.liquidationPrice) {
70
- proximity = 1.0;
71
- } else {
72
- const distanceToLiq = pos.liquidationPrice - pos.markPrice;
73
- const entryToLiq = pos.liquidationPrice - pos.entryPrice;
74
- proximity = entryToLiq > 0 ? 1 - distanceToLiq / entryToLiq : 0;
75
- }
76
- }
77
-
78
- proximities.set(pos.instrument, Math.max(0, Math.min(1, proximity)));
79
- }
80
-
81
- return proximities;
82
- }
83
-
84
- async getDangerousPositions(threshold: number = 0.7): Promise<PerpPosition[]> {
85
- const positions = await this.getPositions();
86
- const proximities = await this.getLiquidationProximity();
87
- return positions.filter((p) => (proximities.get(p.instrument) ?? 0) > threshold);
88
- }
89
-
90
- // ── SUMMARY HELPERS ────────────────────────────────────────
91
-
92
- async getTotalUnrealizedPnl(): Promise<number> {
93
- const positions = await this.getPositions();
94
- return positions.reduce((sum, p) => sum + p.unrealizedPnl, 0);
95
- }
96
-
97
- async getTotalNotional(): Promise<number> {
98
- const positions = await this.getPositions();
99
- return positions.reduce((sum, p) => sum + p.notionalUSD, 0);
100
- }
101
-
102
- async getPositionCount(): Promise<number> {
103
- return (await this.getPositions()).length;
104
- }
105
-
106
- // ── CACHE ──────────────────────────────────────────────────
107
-
108
- async refresh(): Promise<void> {
109
- try {
110
- const [positions, account] = await Promise.all([
111
- this.client.getPositions(this.userAddress),
112
- this.client.getAccountSummary(this.userAddress),
113
- ]);
114
- this.cachedPositions = positions;
115
- this.cachedAccount = account;
116
- this.lastRefreshAt = Date.now();
117
- } catch (error) {
118
- const message = error instanceof Error ? error.message : String(error);
119
- console.error('[perp] Failed to refresh positions:', message);
120
- }
121
- }
122
-
123
- private isCacheFresh(): boolean {
124
- return Date.now() - this.lastRefreshAt < this.cacheTtlMs;
125
- }
126
- }