@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,274 +0,0 @@
1
- /**
2
- * Hyperliquid WebSocket Client
3
- *
4
- * Real-time fill and funding event subscription.
5
- * Auto-reconnects with exponential backoff.
6
- * Checkpoints last processed fill time for REST backfill on reconnect.
7
- */
8
-
9
- import WebSocket from 'ws';
10
- import type { PerpConfig, PerpFill } from './types.js';
11
- import { HyperliquidClient } from './client.js';
12
-
13
- export type FillCallback = (fill: PerpFill) => void;
14
- export type FundingCallback = (funding: FundingPayment) => void;
15
- export type LiquidationCallback = (instrument: string, size: number) => void;
16
-
17
- export interface FundingPayment {
18
- time: number;
19
- coin: string;
20
- usdc: string;
21
- szi: string;
22
- fundingRate: string;
23
- }
24
-
25
- export class HyperliquidWebSocket {
26
- private readonly wsUrl: string;
27
- private readonly userAddress: string;
28
- private readonly client: HyperliquidClient;
29
-
30
- private ws: WebSocket | null = null;
31
- private reconnectAttempts: number = 0;
32
- private readonly maxReconnectAttempts: number = 20;
33
- private readonly baseReconnectMs: number = 1_000;
34
- private readonly maxReconnectMs: number = 60_000;
35
- private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
36
- private pingTimer: ReturnType<typeof setInterval> | null = null;
37
- private isConnecting: boolean = false;
38
- private shouldReconnect: boolean = true;
39
-
40
- private lastProcessedFillTime: number = 0;
41
-
42
- private onFill: FillCallback | null = null;
43
- private onFunding: FundingCallback | null = null;
44
- private onLiquidation: LiquidationCallback | null = null;
45
-
46
- constructor(config: PerpConfig, userAddress: string, client: HyperliquidClient) {
47
- this.wsUrl = config.wsUrl;
48
- this.userAddress = userAddress;
49
- this.client = client;
50
- }
51
-
52
- // ── CONNECTION ─────────────────────────────────────────────
53
-
54
- async connect(): Promise<void> {
55
- if (this.ws?.readyState === WebSocket.OPEN || this.isConnecting) return;
56
-
57
- this.isConnecting = true;
58
- this.shouldReconnect = true;
59
-
60
- return new Promise<void>((resolve, reject) => {
61
- try {
62
- this.ws = new WebSocket(this.wsUrl);
63
-
64
- this.ws.on('open', () => {
65
- this.isConnecting = false;
66
- this.reconnectAttempts = 0;
67
- console.log('[perp-ws] Connected');
68
-
69
- this.subscribe({
70
- type: 'subscribe',
71
- subscription: { type: 'userFills', user: this.userAddress },
72
- });
73
-
74
- this.subscribe({
75
- type: 'subscribe',
76
- subscription: { type: 'userFundings', user: this.userAddress },
77
- });
78
-
79
- this.startPing();
80
-
81
- this.backfillMissedFills().catch((err) => {
82
- console.warn('[perp-ws] Backfill failed:', err instanceof Error ? err.message : err);
83
- });
84
-
85
- resolve();
86
- });
87
-
88
- this.ws.on('message', (data: WebSocket.Data) => {
89
- this.handleMessage(data);
90
- });
91
-
92
- this.ws.on('close', (code: number, reason: Buffer) => {
93
- this.isConnecting = false;
94
- console.log(`[perp-ws] Closed: ${code} ${reason.toString()}`);
95
- this.stopPing();
96
- this.scheduleReconnect();
97
- });
98
-
99
- this.ws.on('error', (error: Error) => {
100
- this.isConnecting = false;
101
- console.error('[perp-ws] Error:', error.message);
102
- if (this.reconnectAttempts === 0) reject(error);
103
- });
104
- } catch (error) {
105
- this.isConnecting = false;
106
- reject(error);
107
- }
108
- });
109
- }
110
-
111
- disconnect(): void {
112
- this.shouldReconnect = false;
113
- if (this.reconnectTimer) {
114
- clearTimeout(this.reconnectTimer);
115
- this.reconnectTimer = null;
116
- }
117
- this.stopPing();
118
- if (this.ws) {
119
- this.ws.removeAllListeners();
120
- if (this.ws.readyState === WebSocket.OPEN) {
121
- this.ws.close(1000, 'Client disconnect');
122
- }
123
- this.ws = null;
124
- }
125
- console.log('[perp-ws] Disconnected');
126
- }
127
-
128
- get isConnected(): boolean {
129
- return this.ws?.readyState === WebSocket.OPEN;
130
- }
131
-
132
- // ── EVENT HANDLERS ─────────────────────────────────────────
133
-
134
- onFillReceived(callback: FillCallback): void {
135
- this.onFill = callback;
136
- }
137
-
138
- onFundingReceived(callback: FundingCallback): void {
139
- this.onFunding = callback;
140
- }
141
-
142
- onLiquidationDetected(callback: LiquidationCallback): void {
143
- this.onLiquidation = callback;
144
- }
145
-
146
- getLastProcessedFillTime(): number {
147
- return this.lastProcessedFillTime;
148
- }
149
-
150
- // ── MESSAGE HANDLING ───────────────────────────────────────
151
-
152
- private handleMessage(data: WebSocket.Data): void {
153
- try {
154
- const msg = JSON.parse(data.toString());
155
- if (msg.channel === 'userFills') {
156
- this.handleFillMessage(msg.data);
157
- } else if (msg.channel === 'userFundings') {
158
- this.handleFundingMessage(msg.data);
159
- }
160
- } catch {
161
- // Ignore parse errors (pong frames, etc.)
162
- }
163
- }
164
-
165
- private handleFillMessage(fills: any[]): void {
166
- if (!Array.isArray(fills) || !this.onFill) return;
167
-
168
- for (const rawFill of fills) {
169
- const fill = this.client.parseFill(rawFill);
170
-
171
- if (fill.time > this.lastProcessedFillTime) {
172
- this.lastProcessedFillTime = fill.time;
173
- }
174
-
175
- if (fill.liquidation && this.onLiquidation) {
176
- this.onLiquidation(fill.coin, parseFloat(fill.sz));
177
- }
178
-
179
- this.onFill(fill);
180
- }
181
- }
182
-
183
- private handleFundingMessage(fundings: any[]): void {
184
- if (!Array.isArray(fundings) || !this.onFunding) return;
185
-
186
- for (const funding of fundings) {
187
- this.onFunding({
188
- time: funding.time,
189
- coin: funding.coin,
190
- usdc: funding.usdc,
191
- szi: funding.szi,
192
- fundingRate: funding.fundingRate,
193
- });
194
- }
195
- }
196
-
197
- // ── BACKFILL ───────────────────────────────────────────────
198
-
199
- private async backfillMissedFills(): Promise<void> {
200
- if (this.lastProcessedFillTime === 0 || !this.onFill) return;
201
-
202
- console.log(`[perp-ws] Backfilling fills since ${new Date(this.lastProcessedFillTime).toISOString()}`);
203
-
204
- const fills = await this.client.getUserFillsByTime(
205
- this.userAddress,
206
- this.lastProcessedFillTime + 1,
207
- );
208
-
209
- if (fills.length > 0) {
210
- console.log(`[perp-ws] Backfilled ${fills.length} missed fills`);
211
- for (const rawFill of fills) {
212
- const fill = this.client.parseFill(rawFill);
213
- if (fill.time > this.lastProcessedFillTime) {
214
- this.lastProcessedFillTime = fill.time;
215
- }
216
- if (fill.liquidation && this.onLiquidation) {
217
- this.onLiquidation(fill.coin, parseFloat(fill.sz));
218
- }
219
- this.onFill(fill);
220
- }
221
- }
222
- }
223
-
224
- // ── RECONNECTION ───────────────────────────────────────────
225
-
226
- private scheduleReconnect(): void {
227
- if (!this.shouldReconnect || this.reconnectAttempts >= this.maxReconnectAttempts) {
228
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
229
- console.error(`[perp-ws] Max reconnect attempts (${this.maxReconnectAttempts}) reached`);
230
- }
231
- return;
232
- }
233
-
234
- const delay = Math.min(
235
- this.baseReconnectMs * Math.pow(2, this.reconnectAttempts),
236
- this.maxReconnectMs,
237
- );
238
-
239
- this.reconnectAttempts++;
240
- console.log(`[perp-ws] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
241
-
242
- this.reconnectTimer = setTimeout(() => {
243
- this.connect().catch((err) => {
244
- console.error('[perp-ws] Reconnect failed:', err instanceof Error ? err.message : err);
245
- });
246
- }, delay);
247
- }
248
-
249
- // ── KEEPALIVE ──────────────────────────────────────────────
250
-
251
- private startPing(): void {
252
- this.stopPing();
253
- this.pingTimer = setInterval(() => {
254
- if (this.ws?.readyState === WebSocket.OPEN) {
255
- this.ws.send(JSON.stringify({ method: 'ping' }));
256
- }
257
- }, 25_000);
258
- }
259
-
260
- private stopPing(): void {
261
- if (this.pingTimer) {
262
- clearInterval(this.pingTimer);
263
- this.pingTimer = null;
264
- }
265
- }
266
-
267
- // ── HELPERS ────────────────────────────────────────────────
268
-
269
- private subscribe(msg: Record<string, unknown>): void {
270
- if (this.ws?.readyState === WebSocket.OPEN) {
271
- this.ws.send(JSON.stringify(msg));
272
- }
273
- }
274
- }
@@ -1,243 +0,0 @@
1
- import type { TrackedPosition, TradeRecord, PositionSummary, StrategyStore } from '@exagent/sdk';
2
-
3
- /**
4
- * Stablecoins and quote assets that should NOT be tracked as positions
5
- * on spot DEX venues (you don't want a "USDC position" when swapping).
6
- * On perp venues, ALL instruments are tracked (ETH, BTC, etc.).
7
- */
8
- const QUOTE_ASSETS = new Set(['USDC', 'USDbC', 'DAI', 'USDT', 'EURC']);
9
-
10
- /** Perp venues where positions can be short (negative quantity) */
11
- const PERP_VENUES = new Set(['hyperliquid_perp']);
12
-
13
- /** Prediction market venues */
14
- const PREDICTION_VENUES = new Set(['polymarket']);
15
-
16
- export class PositionTracker {
17
- private positions: Map<string, TrackedPosition> = new Map();
18
- private trades: TradeRecord[] = [];
19
- private realizedPnL = 0;
20
- private store: StrategyStore;
21
-
22
- constructor(store: StrategyStore) {
23
- this.store = store;
24
- this.load();
25
- }
26
-
27
- /** Build a unique position key: venue-scoped when venue is provided */
28
- private positionKey(token: string, venue?: string): string {
29
- return venue ? `${venue}:${token}` : token;
30
- }
31
-
32
- private load(): void {
33
- const saved = this.store.get<{
34
- positions: [string, TrackedPosition][];
35
- trades: TradeRecord[];
36
- realizedPnL: number;
37
- }>('position_tracker');
38
-
39
- if (saved) {
40
- this.positions = new Map(saved.positions);
41
- this.trades = saved.trades;
42
- this.realizedPnL = saved.realizedPnL;
43
- }
44
- }
45
-
46
- private save(): void {
47
- this.store.set('position_tracker', {
48
- positions: Array.from(this.positions.entries()),
49
- trades: this.trades.slice(-1000),
50
- realizedPnL: this.realizedPnL,
51
- });
52
- }
53
-
54
- /** Returns realized PnL from this trade (non-zero when closing/reducing a position) */
55
- recordBuy(
56
- token: string,
57
- quantity: number,
58
- price: number,
59
- fee: number,
60
- venue?: string,
61
- chain?: string,
62
- venueFillId?: string,
63
- ): number {
64
- // Skip quote assets on spot venues (no "USDC position" from swaps).
65
- // Perp and prediction venues track all instruments.
66
- const isPerp = venue ? PERP_VENUES.has(venue) : false;
67
- const isPrediction = venue ? PREDICTION_VENUES.has(venue) : false;
68
- if (!isPerp && !isPrediction && QUOTE_ASSETS.has(token)) return 0;
69
-
70
- const key = this.positionKey(token, venue);
71
- const existing = this.positions.get(key);
72
- let tradePnL = 0;
73
-
74
- if (existing) {
75
- if (isPerp && existing.quantity < 0) {
76
- // Buying to close/reduce a short position
77
- const closedQty = Math.min(quantity, Math.abs(existing.quantity));
78
- tradePnL = (existing.costBasisPerUnit - price) * closedQty - fee;
79
- this.realizedPnL += tradePnL;
80
-
81
- existing.quantity += quantity;
82
- if (Math.abs(existing.quantity) <= 0.000001) {
83
- this.positions.delete(key);
84
- } else if (existing.quantity > 0) {
85
- // Flipped from short to long — reset cost basis
86
- existing.costBasisPerUnit = price;
87
- }
88
- } else {
89
- // Adding to a long position (or adding to perp long)
90
- const totalQty = existing.quantity + quantity;
91
- const totalCost = existing.costBasisPerUnit * existing.quantity + price * quantity;
92
- existing.costBasisPerUnit = totalCost / totalQty;
93
- existing.quantity = totalQty;
94
- }
95
- } else {
96
- this.positions.set(key, {
97
- token,
98
- quantity,
99
- costBasisPerUnit: price,
100
- entryTimestamp: Date.now(),
101
- venue,
102
- chain,
103
- });
104
- }
105
-
106
- this.trades.push({
107
- token,
108
- action: 'buy',
109
- quantity,
110
- price,
111
- fee,
112
- timestamp: Date.now(),
113
- venue,
114
- chain,
115
- venueFillId,
116
- });
117
-
118
- this.save();
119
- return tradePnL;
120
- }
121
-
122
- /** Returns realized PnL from this trade (non-zero when closing/reducing a position) */
123
- recordSell(
124
- token: string,
125
- quantity: number,
126
- price: number,
127
- fee: number,
128
- venue?: string,
129
- chain?: string,
130
- venueFillId?: string,
131
- ): number {
132
- // Skip quote assets on spot venues (no "USDC position" from swaps).
133
- // Perp and prediction venues track all instruments.
134
- const isPerp = venue ? PERP_VENUES.has(venue) : false;
135
- const isPrediction = venue ? PREDICTION_VENUES.has(venue) : false;
136
- if (!isPerp && !isPrediction && QUOTE_ASSETS.has(token)) return 0;
137
-
138
- const key = this.positionKey(token, venue);
139
- const existing = this.positions.get(key);
140
- let tradePnL = 0;
141
-
142
- if (existing) {
143
- if (isPerp && existing.quantity > 0) {
144
- // Selling to close/reduce a long position
145
- const closedQty = Math.min(quantity, existing.quantity);
146
- tradePnL = (price - existing.costBasisPerUnit) * closedQty - fee;
147
- this.realizedPnL += tradePnL;
148
-
149
- existing.quantity -= quantity;
150
- if (Math.abs(existing.quantity) <= 0.000001) {
151
- this.positions.delete(key);
152
- } else if (existing.quantity < 0) {
153
- // Flipped from long to short — reset cost basis
154
- existing.costBasisPerUnit = price;
155
- }
156
- } else if (isPerp && existing.quantity <= 0) {
157
- // Adding to a short position
158
- const totalQty = Math.abs(existing.quantity) + quantity;
159
- const totalCost = existing.costBasisPerUnit * Math.abs(existing.quantity) + price * quantity;
160
- existing.costBasisPerUnit = totalCost / totalQty;
161
- existing.quantity -= quantity;
162
- } else {
163
- // Spot sell — standard close logic
164
- tradePnL = (price - existing.costBasisPerUnit) * quantity - fee;
165
- this.realizedPnL += tradePnL;
166
-
167
- existing.quantity -= quantity;
168
- if (existing.quantity <= 0.000001) {
169
- this.positions.delete(key);
170
- }
171
- }
172
- } else if (isPerp) {
173
- // Opening a new short position (perps only)
174
- this.positions.set(key, {
175
- token,
176
- quantity: -quantity,
177
- costBasisPerUnit: price,
178
- entryTimestamp: Date.now(),
179
- venue,
180
- chain,
181
- });
182
- }
183
-
184
- this.trades.push({
185
- token,
186
- action: 'sell',
187
- quantity,
188
- price,
189
- fee,
190
- timestamp: Date.now(),
191
- venue,
192
- chain,
193
- venueFillId,
194
- });
195
-
196
- this.save();
197
- return tradePnL;
198
- }
199
-
200
- getSummary(prices: Record<string, number>): PositionSummary {
201
- let totalUnrealizedPnL = 0;
202
-
203
- const openPositions = Array.from(this.positions.values());
204
- for (const pos of openPositions) {
205
- const currentPrice = prices[pos.token];
206
- if (currentPrice) {
207
- if (pos.quantity >= 0) {
208
- // Long: profit when price goes up
209
- totalUnrealizedPnL += (currentPrice - pos.costBasisPerUnit) * pos.quantity;
210
- } else {
211
- // Short: profit when price goes down
212
- totalUnrealizedPnL += (pos.costBasisPerUnit - currentPrice) * Math.abs(pos.quantity);
213
- }
214
- }
215
- }
216
-
217
- return {
218
- openPositions,
219
- totalUnrealizedPnL,
220
- totalRealizedPnL: this.realizedPnL,
221
- };
222
- }
223
-
224
- getPositions(): TrackedPosition[] {
225
- return Array.from(this.positions.values());
226
- }
227
-
228
- getTrades(limit?: number): TradeRecord[] {
229
- if (limit) return this.trades.slice(-limit);
230
- return [...this.trades];
231
- }
232
-
233
- getRealizedPnL(): number {
234
- return this.realizedPnL;
235
- }
236
-
237
- reset(): void {
238
- this.positions.clear();
239
- this.trades = [];
240
- this.realizedPnL = 0;
241
- this.save();
242
- }
243
- }