@pear-protocol/exchanges-sdk 0.0.10 → 0.0.12
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 +59 -42
- package/dist/account/account-manager.d.ts +5 -11
- package/dist/account/account-manager.js +22 -60
- package/dist/account/exchange-account.d.ts +8 -10
- package/dist/account/exchange-account.js +30 -57
- package/dist/account/exchanges/base.d.ts +13 -15
- package/dist/account/exchanges/base.js +5 -27
- package/dist/account/exchanges/binance/asset-tracker.d.ts +25 -0
- package/dist/account/exchanges/binance/asset-tracker.js +38 -0
- package/dist/account/exchanges/binance/mapper.d.ts +4 -3
- package/dist/account/exchanges/binance/mapper.js +0 -1
- package/dist/account/exchanges/binance/orchestrator.d.ts +9 -12
- package/dist/account/exchanges/binance/orchestrator.js +61 -183
- package/dist/account/exchanges/binance/rest.d.ts +5 -4
- package/dist/account/exchanges/binance/rest.js +4 -20
- package/dist/account/exchanges/binance/state-handler.d.ts +32 -0
- package/dist/account/exchanges/binance/state-handler.js +113 -0
- package/dist/account/exchanges/binance/types.d.ts +3 -76
- package/dist/account/exchanges/binance/types.js +6 -0
- package/dist/account/exchanges/binance/ws.d.ts +3 -2
- package/dist/account/exchanges/binance/ws.js +6 -22
- package/dist/account/exchanges/bybit/asset-tracker.d.ts +25 -0
- package/dist/account/exchanges/bybit/asset-tracker.js +37 -0
- package/dist/account/exchanges/bybit/mapper.d.ts +4 -3
- package/dist/account/exchanges/bybit/mapper.js +5 -3
- package/dist/account/exchanges/bybit/orchestrator.d.ts +11 -12
- package/dist/account/exchanges/bybit/orchestrator.js +48 -134
- package/dist/account/exchanges/bybit/rest.d.ts +2 -3
- package/dist/account/exchanges/bybit/rest.js +4 -39
- package/dist/account/exchanges/bybit/state-handler.d.ts +22 -0
- package/dist/account/exchanges/bybit/state-handler.js +70 -0
- package/dist/account/exchanges/bybit/types.d.ts +14 -3
- package/dist/account/exchanges/bybit/types.js +7 -0
- package/dist/account/exchanges/bybit/ws.d.ts +1 -0
- package/dist/account/exchanges/bybit/ws.js +21 -12
- package/dist/account/exchanges/hyperliquid/asset-tracker.d.ts +34 -0
- package/dist/account/exchanges/hyperliquid/asset-tracker.js +83 -0
- package/dist/account/exchanges/hyperliquid/mapper.d.ts +4 -4
- package/dist/account/exchanges/hyperliquid/mapper.js +1 -0
- package/dist/account/exchanges/hyperliquid/orchestrator.d.ts +15 -28
- package/dist/account/exchanges/hyperliquid/orchestrator.js +79 -216
- package/dist/account/exchanges/hyperliquid/state-handler.d.ts +24 -0
- package/dist/account/exchanges/hyperliquid/state-handler.js +104 -0
- package/dist/account/exchanges/hyperliquid/types.d.ts +119 -5
- package/dist/account/exchanges/hyperliquid/types.js +9 -1
- package/dist/account/exchanges/index.d.ts +7 -5
- package/dist/account/exchanges/index.js +12 -4
- package/dist/account/index.d.ts +3 -1
- package/dist/account/types.d.ts +4 -73
- package/dist/account/types.js +17 -0
- package/dist/account/utils.d.ts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/types-D2dbAtin.d.ts +150 -0
- package/dist/utils/hmac.d.ts +1 -2
- package/dist/utils/hmac.js +1 -8
- package/dist/utils/ws.d.ts +6 -3
- package/dist/utils/ws.js +24 -7
- package/package.json +5 -5
- package/dist/account/exchanges/hyperliquid/rest.d.ts +0 -6
- package/dist/account/exchanges/hyperliquid/rest.js +0 -11
package/README.md
CHANGED
|
@@ -1,45 +1,40 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @pear-protocol/exchanges-sdk
|
|
2
2
|
|
|
3
|
-
Unified SDK for connecting to cryptocurrency derivative exchanges. Provides real-time account state tracking including balances, positions, and leverage management through a single, exchange-agnostic interface.
|
|
3
|
+
Unified SDK for connecting to cryptocurrency derivative exchanges. Provides real-time account state tracking including balances, positions, and leverage management through a single, exchange-agnostic interface with automatic WebSocket streaming and credential resolution.
|
|
4
4
|
|
|
5
5
|
## Supported Exchanges
|
|
6
6
|
|
|
7
7
|
| Exchange | Connector Name | Market Type |
|
|
8
8
|
|----------|---------------|-------------|
|
|
9
|
-
| Binance | `
|
|
9
|
+
| Binance | `binance` | USDM Futures |
|
|
10
10
|
| Bybit | `bybit` | Linear Perpetuals |
|
|
11
11
|
| Hyperliquid | `hyperliquid` | Perpetuals |
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
"dependencies": {
|
|
18
|
-
"@backend/exchanges-sdk": "workspace:*"
|
|
19
|
-
}
|
|
20
|
-
}
|
|
15
|
+
```bash
|
|
16
|
+
npm install @pear-protocol/exchanges-sdk
|
|
21
17
|
```
|
|
22
18
|
|
|
23
|
-
Requires `@pear-protocol/core-sdk` and `@backend/shared` as peer dependencies.
|
|
24
|
-
|
|
25
19
|
## Quick Start
|
|
26
20
|
|
|
27
21
|
```typescript
|
|
28
22
|
import PearSDK from '@pear-protocol/core-sdk';
|
|
29
|
-
import { ExchangesSDK } from '@
|
|
23
|
+
import { ExchangesSDK } from '@pear-protocol/exchanges-sdk';
|
|
30
24
|
|
|
31
25
|
const sdk = new PearSDK({ /* ... */ });
|
|
32
26
|
const exchanges = new ExchangesSDK({ sdk });
|
|
33
27
|
|
|
34
28
|
await exchanges.account.connect(tradeAccountId, 'binanceusdm');
|
|
35
29
|
|
|
30
|
+
// Track balance and positions
|
|
36
31
|
const balanceTracker = exchanges.account.trackBalance((balance) => {
|
|
37
|
-
console.log(balance.totalEquity);
|
|
32
|
+
console.log(balance.totalEquity, balance.unrealizedPnl);
|
|
38
33
|
});
|
|
39
34
|
|
|
40
35
|
const positionsTracker = exchanges.account.trackPositions((positions) => {
|
|
41
36
|
for (const pos of positions) {
|
|
42
|
-
console.log(pos.symbol, pos.side, pos.size);
|
|
37
|
+
console.log(pos.symbol, pos.side, pos.size, pos.entryPrice);
|
|
43
38
|
}
|
|
44
39
|
});
|
|
45
40
|
|
|
@@ -61,12 +56,20 @@ const exchanges = new ExchangesSDK({ sdk });
|
|
|
61
56
|
const exchanges = new ExchangesSDK({ sdk, demo: true });
|
|
62
57
|
```
|
|
63
58
|
|
|
59
|
+
The `ExchangesSDK` exposes three top-level modules:
|
|
60
|
+
|
|
61
|
+
| Module | Description |
|
|
62
|
+
|--------|-------------|
|
|
63
|
+
| `exchanges.account` | Account connection, balance/position tracking, leverage management |
|
|
64
|
+
| `exchanges.credentials` | Exchange API credential fetching and caching |
|
|
65
|
+
| `exchanges.sdk` | Reference to the underlying `PearSDK` instance |
|
|
66
|
+
|
|
64
67
|
### Connecting to an Exchange
|
|
65
68
|
|
|
66
|
-
Credentials are fetched automatically from the backend when you call `connect`.
|
|
69
|
+
Credentials are fetched automatically from the backend when you call `connect`. One exchange connection is active at a time.
|
|
67
70
|
|
|
68
71
|
```typescript
|
|
69
|
-
await exchanges.account.connect(tradeAccountId, '
|
|
72
|
+
await exchanges.account.connect(tradeAccountId, 'binance');
|
|
70
73
|
await exchanges.account.connect(tradeAccountId, 'bybit');
|
|
71
74
|
await exchanges.account.connect(tradeAccountId, 'hyperliquid');
|
|
72
75
|
```
|
|
@@ -75,12 +78,12 @@ Check connection status:
|
|
|
75
78
|
|
|
76
79
|
```typescript
|
|
77
80
|
exchanges.account.isConnected; // WebSocket is active
|
|
78
|
-
exchanges.account.isInitialized; //
|
|
81
|
+
exchanges.account.isInitialized; // first state snapshot received
|
|
79
82
|
```
|
|
80
83
|
|
|
81
84
|
### Tracking Balance
|
|
82
85
|
|
|
83
|
-
Subscribe to real-time balance updates. The callback fires on every balance change.
|
|
86
|
+
Subscribe to real-time balance updates. The callback fires on every balance change from the exchange.
|
|
84
87
|
|
|
85
88
|
```typescript
|
|
86
89
|
const tracker = exchanges.account.trackBalance((balance) => {
|
|
@@ -88,6 +91,8 @@ const tracker = exchanges.account.trackBalance((balance) => {
|
|
|
88
91
|
balance.walletBalance; // wallet balance excluding unrealized PnL
|
|
89
92
|
balance.unrealizedPnl; // total unrealized PnL
|
|
90
93
|
balance.availableToTrade; // available margin for new trades
|
|
94
|
+
balance.initialMarginUsed; // margin used by open positions
|
|
95
|
+
balance.maintenanceMargin; // maintenance margin requirement
|
|
91
96
|
balance.marginRatio; // current margin ratio
|
|
92
97
|
|
|
93
98
|
// Per-asset breakdown
|
|
@@ -115,13 +120,13 @@ Subscribe to real-time position updates. The callback fires with the full list o
|
|
|
115
120
|
```typescript
|
|
116
121
|
const tracker = exchanges.account.trackPositions((positions) => {
|
|
117
122
|
for (const pos of positions) {
|
|
118
|
-
pos.symbol;
|
|
119
|
-
pos.side;
|
|
120
|
-
pos.size;
|
|
121
|
-
pos.entryPrice;
|
|
122
|
-
pos.unrealizedPnl;
|
|
123
|
-
pos.leverage;
|
|
124
|
-
pos.marginType;
|
|
123
|
+
pos.symbol; // "BTCUSDT"
|
|
124
|
+
pos.side; // "long" | "short" | "both"
|
|
125
|
+
pos.size; // "0.5"
|
|
126
|
+
pos.entryPrice; // "65000.00"
|
|
127
|
+
pos.unrealizedPnl; // "120.50"
|
|
128
|
+
pos.leverage; // "10"
|
|
129
|
+
pos.marginType; // "cross" | "isolated"
|
|
125
130
|
pos.liquidationPrice; // "58000.00" or null
|
|
126
131
|
}
|
|
127
132
|
});
|
|
@@ -130,15 +135,17 @@ const current = tracker.get();
|
|
|
130
135
|
tracker.untrack();
|
|
131
136
|
```
|
|
132
137
|
|
|
138
|
+
Positions with a size of `"0"` are automatically removed from the tracked state.
|
|
139
|
+
|
|
133
140
|
### Tracking a Specific Asset
|
|
134
141
|
|
|
135
142
|
Retrieve leverage, margin type, and trade size constraints for a specific coin. Useful when preparing to place a trade.
|
|
136
143
|
|
|
137
144
|
```typescript
|
|
138
145
|
const tracker = exchanges.account.trackAsset('ETH', (info) => {
|
|
139
|
-
info.coin;
|
|
140
|
-
info.leverage;
|
|
141
|
-
info.marginType;
|
|
146
|
+
info.coin; // "ETH"
|
|
147
|
+
info.leverage; // "20"
|
|
148
|
+
info.marginType; // "cross" | "isolated"
|
|
142
149
|
});
|
|
143
150
|
|
|
144
151
|
const current = tracker.get();
|
|
@@ -148,8 +155,6 @@ tracker.untrack();
|
|
|
148
155
|
### Managing Leverage
|
|
149
156
|
|
|
150
157
|
```typescript
|
|
151
|
-
await exchanges.account.setLeverage('BTCUSDT', '10');
|
|
152
|
-
|
|
153
158
|
// Read cached leverage (returns null if not yet fetched)
|
|
154
159
|
const leverage = exchanges.account.getLeverage('BTCUSDT');
|
|
155
160
|
```
|
|
@@ -161,16 +166,35 @@ Access a read-only snapshot of the complete account state at any time.
|
|
|
161
166
|
```typescript
|
|
162
167
|
const state = exchanges.account.getState();
|
|
163
168
|
if (state) {
|
|
164
|
-
state.balance; // latest
|
|
165
|
-
state.positions; // Map
|
|
166
|
-
state.leverageSettings; // Map
|
|
167
|
-
state.trackedAssets; // Map
|
|
169
|
+
state.balance; // latest AccountBalance or null
|
|
170
|
+
state.positions; // Map<"SYMBOL:side", AccountPosition>
|
|
171
|
+
state.leverageSettings; // Map<symbol, leverage>
|
|
172
|
+
state.trackedAssets; // Map<coin, TrackedAssetInfo>
|
|
168
173
|
state.lastUpdated; // timestamp of last update
|
|
169
174
|
state.initialized; // true after first snapshot
|
|
170
175
|
}
|
|
171
176
|
```
|
|
172
177
|
|
|
173
|
-
###
|
|
178
|
+
### Credential Management
|
|
179
|
+
|
|
180
|
+
Credentials are resolved automatically on `connect`, but you can manage them directly:
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// Fetch credentials from the backend
|
|
184
|
+
const creds = await exchanges.credentials.fetch(tradeAccountId);
|
|
185
|
+
|
|
186
|
+
// Get cached credentials (null if not fetched)
|
|
187
|
+
const cached = exchanges.credentials.get(tradeAccountId);
|
|
188
|
+
|
|
189
|
+
// Get from cache or fetch if missing
|
|
190
|
+
const creds = await exchanges.credentials.getOrFetch(tradeAccountId);
|
|
191
|
+
|
|
192
|
+
// Clear cached credentials
|
|
193
|
+
exchanges.credentials.clear(tradeAccountId); // specific account
|
|
194
|
+
exchanges.credentials.clear(); // all accounts
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Cleanup
|
|
174
198
|
|
|
175
199
|
```typescript
|
|
176
200
|
// Disconnect but keep credentials cached for reconnection
|
|
@@ -180,10 +204,3 @@ exchanges.account.disconnect();
|
|
|
180
204
|
exchanges.destroy();
|
|
181
205
|
```
|
|
182
206
|
|
|
183
|
-
## Behavior Notes
|
|
184
|
-
|
|
185
|
-
- The WebSocket connection starts lazily when the first tracker is registered and stops when all trackers are removed.
|
|
186
|
-
- On unexpected disconnection, the SDK reconnects automatically with exponential backoff (1s to 30s).
|
|
187
|
-
- Reconnection is suppressed after explicit `disconnect()` or `destroy()` calls.
|
|
188
|
-
- Credentials are held in memory only and never persisted to disk.
|
|
189
|
-
- All numeric values (balances, sizes, prices, PnL) are returned as strings to preserve decimal precision.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import PearSDK from '@pear-protocol/core-sdk';
|
|
2
2
|
import { Connector } from '@pear-protocol/types';
|
|
3
3
|
import { Credentials } from '../credentials.js';
|
|
4
|
-
import { BalanceCallback, Tracker, AccountBalance, PositionsCallback, AccountPosition, TrackedAssetCallback, TrackedAssetInfo
|
|
4
|
+
import { B as BalanceCallback, i as Tracker, a as AccountBalance, P as PositionsCallback, c as AccountPosition, T as TrackedAssetCallback, h as TrackedAssetInfo } from '../types-D2dbAtin.js';
|
|
5
|
+
import './exchanges/bybit/types.js';
|
|
6
|
+
import './exchanges/hyperliquid/types.js';
|
|
5
7
|
|
|
6
8
|
declare class AccountManager {
|
|
7
9
|
private credentials;
|
|
8
10
|
private sdk;
|
|
9
11
|
private demo;
|
|
10
12
|
private activeAccount;
|
|
11
|
-
private pendingExchange;
|
|
12
|
-
private pendingTradeAccountId;
|
|
13
13
|
private balanceListeners;
|
|
14
14
|
private positionListeners;
|
|
15
15
|
private assetListeners;
|
|
@@ -18,17 +18,11 @@ declare class AccountManager {
|
|
|
18
18
|
trackBalance(cb: BalanceCallback): Tracker<AccountBalance | null>;
|
|
19
19
|
trackPositions(cb: PositionsCallback): Tracker<AccountPosition[]>;
|
|
20
20
|
trackAsset(coin: string, cb: TrackedAssetCallback): Tracker<TrackedAssetInfo | null>;
|
|
21
|
-
setLeverage(symbol: string, leverage: string): Promise<void>;
|
|
22
21
|
getLeverage(symbol: string): string | null;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
destroy(): void;
|
|
22
|
+
disconnect(): Promise<void>;
|
|
23
|
+
destroy(): Promise<void>;
|
|
26
24
|
get isConnected(): boolean;
|
|
27
25
|
get isInitialized(): boolean;
|
|
28
|
-
private get hasListeners();
|
|
29
|
-
private disconnectWs;
|
|
30
|
-
private maybeDisconnectWs;
|
|
31
|
-
private ensureWsStarted;
|
|
32
26
|
private resolveCredentials;
|
|
33
27
|
private startWs;
|
|
34
28
|
}
|
|
@@ -5,8 +5,6 @@ class AccountManager {
|
|
|
5
5
|
sdk;
|
|
6
6
|
demo;
|
|
7
7
|
activeAccount = null;
|
|
8
|
-
pendingExchange = null;
|
|
9
|
-
pendingTradeAccountId = null;
|
|
10
8
|
balanceListeners = /* @__PURE__ */ new Map();
|
|
11
9
|
positionListeners = /* @__PURE__ */ new Map();
|
|
12
10
|
assetListeners = /* @__PURE__ */ new Map();
|
|
@@ -16,28 +14,21 @@ class AccountManager {
|
|
|
16
14
|
this.demo = demo;
|
|
17
15
|
}
|
|
18
16
|
async connect(tradeAccountId, exchange) {
|
|
17
|
+
await this.disconnect();
|
|
19
18
|
const creds = await this.resolveCredentials(tradeAccountId, exchange);
|
|
20
|
-
this.
|
|
21
|
-
this.pendingTradeAccountId = tradeAccountId;
|
|
22
|
-
this.pendingExchange = exchange;
|
|
23
|
-
if (this.hasListeners) {
|
|
24
|
-
await this.startWs(exchange, creds);
|
|
25
|
-
}
|
|
19
|
+
await this.startWs(exchange, creds);
|
|
26
20
|
}
|
|
27
21
|
trackBalance(cb) {
|
|
28
22
|
const id = Math.random().toString(36).slice(2);
|
|
29
23
|
this.balanceListeners.set(id, cb);
|
|
30
24
|
if (this.activeAccount) {
|
|
31
|
-
this.activeAccount.trackBalance(cb);
|
|
32
|
-
} else {
|
|
33
|
-
this.ensureWsStarted();
|
|
25
|
+
this.activeAccount.trackBalance(cb, id);
|
|
34
26
|
}
|
|
35
27
|
return {
|
|
36
28
|
get: () => this.activeAccount?.getBalance() ?? null,
|
|
37
29
|
untrack: () => {
|
|
38
30
|
this.balanceListeners.delete(id);
|
|
39
31
|
this.activeAccount?.untrackBalance(id);
|
|
40
|
-
this.maybeDisconnectWs();
|
|
41
32
|
}
|
|
42
33
|
};
|
|
43
34
|
}
|
|
@@ -45,16 +36,13 @@ class AccountManager {
|
|
|
45
36
|
const id = Math.random().toString(36).slice(2);
|
|
46
37
|
this.positionListeners.set(id, cb);
|
|
47
38
|
if (this.activeAccount) {
|
|
48
|
-
this.activeAccount.trackPositions(cb);
|
|
49
|
-
} else {
|
|
50
|
-
this.ensureWsStarted();
|
|
39
|
+
this.activeAccount.trackPositions(cb, id);
|
|
51
40
|
}
|
|
52
41
|
return {
|
|
53
42
|
get: () => this.activeAccount?.getPositions() ?? [],
|
|
54
43
|
untrack: () => {
|
|
55
44
|
this.positionListeners.delete(id);
|
|
56
45
|
this.activeAccount?.untrackPositions(id);
|
|
57
|
-
this.maybeDisconnectWs();
|
|
58
46
|
}
|
|
59
47
|
};
|
|
60
48
|
}
|
|
@@ -62,39 +50,30 @@ class AccountManager {
|
|
|
62
50
|
const id = Math.random().toString(36).slice(2);
|
|
63
51
|
this.assetListeners.set(id, { coin, cb });
|
|
64
52
|
if (this.activeAccount) {
|
|
65
|
-
this.activeAccount.trackAsset(coin, cb);
|
|
66
|
-
} else {
|
|
67
|
-
this.ensureWsStarted();
|
|
53
|
+
this.activeAccount.trackAsset(coin, cb, id);
|
|
68
54
|
}
|
|
69
55
|
return {
|
|
70
56
|
get: () => this.activeAccount?.getTrackedAsset(coin) ?? null,
|
|
71
57
|
untrack: () => {
|
|
72
58
|
this.assetListeners.delete(id);
|
|
73
59
|
this.activeAccount?.untrackAsset(id);
|
|
74
|
-
this.maybeDisconnectWs();
|
|
75
60
|
}
|
|
76
61
|
};
|
|
77
62
|
}
|
|
78
|
-
async setLeverage(symbol, leverage) {
|
|
79
|
-
if (!this.activeAccount) throw new Error("No active account");
|
|
80
|
-
await this.activeAccount.setLeverage(symbol, leverage);
|
|
81
|
-
}
|
|
82
63
|
getLeverage(symbol) {
|
|
83
64
|
return this.activeAccount?.getLeverage(symbol) ?? null;
|
|
84
65
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
this.pendingExchange = null;
|
|
91
|
-
this.pendingTradeAccountId = null;
|
|
66
|
+
async disconnect() {
|
|
67
|
+
if (this.activeAccount) {
|
|
68
|
+
await this.activeAccount.disconnect();
|
|
69
|
+
this.activeAccount = null;
|
|
70
|
+
}
|
|
92
71
|
this.balanceListeners.clear();
|
|
93
72
|
this.positionListeners.clear();
|
|
94
73
|
this.assetListeners.clear();
|
|
95
74
|
}
|
|
96
|
-
destroy() {
|
|
97
|
-
this.disconnect();
|
|
75
|
+
async destroy() {
|
|
76
|
+
await this.disconnect();
|
|
98
77
|
this.credentials.clear();
|
|
99
78
|
}
|
|
100
79
|
get isConnected() {
|
|
@@ -103,26 +82,6 @@ class AccountManager {
|
|
|
103
82
|
get isInitialized() {
|
|
104
83
|
return this.activeAccount?.isInitialized ?? false;
|
|
105
84
|
}
|
|
106
|
-
get hasListeners() {
|
|
107
|
-
return this.balanceListeners.size > 0 || this.positionListeners.size > 0 || this.assetListeners.size > 0;
|
|
108
|
-
}
|
|
109
|
-
disconnectWs() {
|
|
110
|
-
if (this.activeAccount) {
|
|
111
|
-
this.activeAccount.destroy();
|
|
112
|
-
this.activeAccount = null;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
maybeDisconnectWs() {
|
|
116
|
-
if (!this.hasListeners) {
|
|
117
|
-
this.disconnectWs();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
ensureWsStarted() {
|
|
121
|
-
if (this.activeAccount || !this.pendingExchange || !this.pendingTradeAccountId) return;
|
|
122
|
-
const exchange = this.pendingExchange;
|
|
123
|
-
const tradeAccountId = this.pendingTradeAccountId;
|
|
124
|
-
this.resolveCredentials(tradeAccountId, exchange).then((creds) => this.startWs(exchange, creds)).catch((err) => console.warn("[AccountManager] Failed to start WS:", err));
|
|
125
|
-
}
|
|
126
85
|
async resolveCredentials(tradeAccountId, exchange) {
|
|
127
86
|
const creds = await this.credentials.getOrFetch(tradeAccountId);
|
|
128
87
|
if (exchange === "hyperliquid") {
|
|
@@ -134,7 +93,10 @@ class AccountManager {
|
|
|
134
93
|
if (!account.exchangeIdentifier) {
|
|
135
94
|
throw new Error(`Trade account ${tradeAccountId} has no Hyperliquid address`);
|
|
136
95
|
}
|
|
137
|
-
return {
|
|
96
|
+
return {
|
|
97
|
+
...creds,
|
|
98
|
+
address: account.exchangeIdentifier
|
|
99
|
+
};
|
|
138
100
|
}
|
|
139
101
|
return creds;
|
|
140
102
|
}
|
|
@@ -142,14 +104,14 @@ class AccountManager {
|
|
|
142
104
|
const account = new ExchangeAccount({ exchange, demo: this.demo });
|
|
143
105
|
await account.connect(creds);
|
|
144
106
|
this.activeAccount = account;
|
|
145
|
-
for (const cb of this.balanceListeners.
|
|
146
|
-
account.trackBalance(cb);
|
|
107
|
+
for (const [id, cb] of this.balanceListeners.entries()) {
|
|
108
|
+
account.trackBalance(cb, id);
|
|
147
109
|
}
|
|
148
|
-
for (const cb of this.positionListeners.
|
|
149
|
-
account.trackPositions(cb);
|
|
110
|
+
for (const [id, cb] of this.positionListeners.entries()) {
|
|
111
|
+
account.trackPositions(cb, id);
|
|
150
112
|
}
|
|
151
|
-
for (const { coin, cb } of this.assetListeners.
|
|
152
|
-
account.trackAsset(coin, cb);
|
|
113
|
+
for (const [id, { coin, cb }] of this.assetListeners.entries()) {
|
|
114
|
+
account.trackAsset(coin, cb, id);
|
|
153
115
|
}
|
|
154
116
|
}
|
|
155
117
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { b as AccountConfig, g as SupportedCredentials, B as BalanceCallback, a as AccountBalance, P as PositionsCallback, c as AccountPosition, T as TrackedAssetCallback, h as TrackedAssetInfo } from '../types-D2dbAtin.js';
|
|
2
|
+
import '@pear-protocol/types';
|
|
3
|
+
import './exchanges/bybit/types.js';
|
|
4
|
+
import './exchanges/hyperliquid/types.js';
|
|
3
5
|
|
|
4
6
|
declare class ExchangeAccount {
|
|
5
7
|
private exchange;
|
|
@@ -12,24 +14,20 @@ declare class ExchangeAccount {
|
|
|
12
14
|
private onAuthError?;
|
|
13
15
|
constructor(config: AccountConfig);
|
|
14
16
|
connect(credentials: SupportedCredentials): Promise<void>;
|
|
15
|
-
disconnect(): void
|
|
16
|
-
destroy(): void;
|
|
17
|
+
disconnect(): Promise<void>;
|
|
17
18
|
get isConnected(): boolean;
|
|
18
19
|
get isInitialized(): boolean;
|
|
19
20
|
get hasListeners(): boolean;
|
|
20
|
-
trackBalance(cb: BalanceCallback): string;
|
|
21
|
+
trackBalance(cb: BalanceCallback, id?: string): string;
|
|
21
22
|
untrackBalance(id: string): void;
|
|
22
23
|
getBalance(): AccountBalance | null;
|
|
23
|
-
trackPositions(cb: PositionsCallback): string;
|
|
24
|
+
trackPositions(cb: PositionsCallback, id?: string): string;
|
|
24
25
|
untrackPositions(id: string): void;
|
|
25
26
|
getPositions(): AccountPosition[];
|
|
26
|
-
trackAsset(coin: string, cb: TrackedAssetCallback): string;
|
|
27
|
+
trackAsset(coin: string, cb: TrackedAssetCallback, id: string): string;
|
|
27
28
|
untrackAsset(id: string): void;
|
|
28
29
|
getTrackedAsset(coin: string): TrackedAssetInfo | null;
|
|
29
30
|
getLeverage(symbol: string): string | null;
|
|
30
|
-
setLeverage(symbol: string, leverage: string): Promise<void>;
|
|
31
|
-
getState(): Readonly<ExchangeState>;
|
|
32
|
-
getExchange(): Connector;
|
|
33
31
|
private applyStateUpdate;
|
|
34
32
|
}
|
|
35
33
|
|
|
@@ -17,32 +17,32 @@ class ExchangeAccount {
|
|
|
17
17
|
this.state = createEmptyState();
|
|
18
18
|
}
|
|
19
19
|
async connect(credentials) {
|
|
20
|
-
this.disconnect();
|
|
20
|
+
await this.disconnect();
|
|
21
21
|
this.state = createEmptyState();
|
|
22
|
-
this.orchestrator = createOrchestrator(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this.
|
|
27
|
-
|
|
22
|
+
this.orchestrator = createOrchestrator(
|
|
23
|
+
this.exchange,
|
|
24
|
+
{
|
|
25
|
+
onError: (error) => {
|
|
26
|
+
console.warn(`[ExchangeAccount] ${this.exchange} error:`, error.message);
|
|
27
|
+
if (error.message.includes("auth") || error.message.includes("login")) {
|
|
28
|
+
this.onAuthError?.();
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
onStateUpdate: (update) => this.applyStateUpdate(update),
|
|
32
|
+
demo: this.demo
|
|
28
33
|
},
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
await this.orchestrator.start(credentials);
|
|
33
|
-
}
|
|
34
|
-
disconnect() {
|
|
35
|
-
if (this.orchestrator) {
|
|
36
|
-
this.orchestrator.stop();
|
|
37
|
-
this.orchestrator = null;
|
|
38
|
-
}
|
|
34
|
+
credentials
|
|
35
|
+
);
|
|
36
|
+
await this.orchestrator.start();
|
|
39
37
|
}
|
|
40
|
-
|
|
41
|
-
this.disconnect();
|
|
38
|
+
async disconnect() {
|
|
42
39
|
this.balanceListeners.clear();
|
|
43
40
|
this.positionListeners.clear();
|
|
44
41
|
this.assetListeners.clear();
|
|
45
|
-
this.
|
|
42
|
+
if (this.orchestrator) {
|
|
43
|
+
await this.orchestrator.stop();
|
|
44
|
+
this.orchestrator = null;
|
|
45
|
+
}
|
|
46
46
|
}
|
|
47
47
|
get isConnected() {
|
|
48
48
|
return this.orchestrator?.isConnected ?? false;
|
|
@@ -53,14 +53,11 @@ class ExchangeAccount {
|
|
|
53
53
|
get hasListeners() {
|
|
54
54
|
return this.balanceListeners.size > 0 || this.positionListeners.size > 0 || this.assetListeners.size > 0;
|
|
55
55
|
}
|
|
56
|
-
trackBalance(cb) {
|
|
57
|
-
|
|
56
|
+
trackBalance(cb, id) {
|
|
57
|
+
id ??= Math.random().toString(36).slice(2);
|
|
58
58
|
this.balanceListeners.set(id, cb);
|
|
59
59
|
if (this.state.balance) {
|
|
60
|
-
|
|
61
|
-
cb(this.state.balance);
|
|
62
|
-
} catch {
|
|
63
|
-
}
|
|
60
|
+
cb(this.state.balance);
|
|
64
61
|
}
|
|
65
62
|
return id;
|
|
66
63
|
}
|
|
@@ -70,14 +67,11 @@ class ExchangeAccount {
|
|
|
70
67
|
getBalance() {
|
|
71
68
|
return this.state.balance;
|
|
72
69
|
}
|
|
73
|
-
trackPositions(cb) {
|
|
74
|
-
|
|
70
|
+
trackPositions(cb, id) {
|
|
71
|
+
id ??= Math.random().toString(36).slice(2);
|
|
75
72
|
this.positionListeners.set(id, cb);
|
|
76
73
|
if (this.state.initialized) {
|
|
77
|
-
|
|
78
|
-
cb(Array.from(this.state.positions.values()));
|
|
79
|
-
} catch {
|
|
80
|
-
}
|
|
74
|
+
cb(Array.from(this.state.positions.values()));
|
|
81
75
|
}
|
|
82
76
|
return id;
|
|
83
77
|
}
|
|
@@ -87,16 +81,12 @@ class ExchangeAccount {
|
|
|
87
81
|
getPositions() {
|
|
88
82
|
return Array.from(this.state.positions.values());
|
|
89
83
|
}
|
|
90
|
-
trackAsset(coin, cb) {
|
|
91
|
-
const id = Math.random().toString(36).slice(2);
|
|
84
|
+
trackAsset(coin, cb, id) {
|
|
92
85
|
this.assetListeners.set(id, { coin, cb });
|
|
93
86
|
this.orchestrator?.trackAsset(coin);
|
|
94
87
|
const existing = this.state.trackedAssets.get(coin);
|
|
95
88
|
if (existing) {
|
|
96
|
-
|
|
97
|
-
cb(existing);
|
|
98
|
-
} catch {
|
|
99
|
-
}
|
|
89
|
+
cb(existing);
|
|
100
90
|
}
|
|
101
91
|
return id;
|
|
102
92
|
}
|
|
@@ -120,17 +110,6 @@ class ExchangeAccount {
|
|
|
120
110
|
}
|
|
121
111
|
return null;
|
|
122
112
|
}
|
|
123
|
-
async setLeverage(symbol, leverage) {
|
|
124
|
-
if (!this.orchestrator) throw new Error("Not connected");
|
|
125
|
-
await this.orchestrator.setLeverage(symbol, leverage);
|
|
126
|
-
}
|
|
127
|
-
getState() {
|
|
128
|
-
return this.state;
|
|
129
|
-
}
|
|
130
|
-
getExchange() {
|
|
131
|
-
return this.exchange;
|
|
132
|
-
}
|
|
133
|
-
// --- private ---
|
|
134
113
|
applyStateUpdate(update) {
|
|
135
114
|
if (update.balance) {
|
|
136
115
|
this.state.balance = update.balance;
|
|
@@ -151,10 +130,7 @@ class ExchangeAccount {
|
|
|
151
130
|
}
|
|
152
131
|
const positions = Array.from(this.state.positions.values());
|
|
153
132
|
for (const cb of this.positionListeners.values()) {
|
|
154
|
-
|
|
155
|
-
cb(positions);
|
|
156
|
-
} catch {
|
|
157
|
-
}
|
|
133
|
+
cb(positions);
|
|
158
134
|
}
|
|
159
135
|
}
|
|
160
136
|
if (update.leverageSettings) {
|
|
@@ -167,10 +143,7 @@ class ExchangeAccount {
|
|
|
167
143
|
this.state.trackedAssets.set(coin, info);
|
|
168
144
|
for (const listener of this.assetListeners.values()) {
|
|
169
145
|
if (listener.coin === coin) {
|
|
170
|
-
|
|
171
|
-
listener.cb(info);
|
|
172
|
-
} catch {
|
|
173
|
-
}
|
|
146
|
+
listener.cb(info);
|
|
174
147
|
}
|
|
175
148
|
}
|
|
176
149
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as StateUpdate, g as SupportedCredentials } from '../../types-D2dbAtin.js';
|
|
2
2
|
import '@pear-protocol/types';
|
|
3
|
+
import './bybit/types.js';
|
|
4
|
+
import './hyperliquid/types.js';
|
|
3
5
|
|
|
4
6
|
type StateUpdateHandler = (update: StateUpdate) => void;
|
|
5
7
|
type ErrorHandler = (error: Error) => void;
|
|
@@ -9,41 +11,37 @@ type OrchestratorConfig = {
|
|
|
9
11
|
demo: boolean;
|
|
10
12
|
};
|
|
11
13
|
interface Orchestrator {
|
|
12
|
-
|
|
13
|
-
stop: () => void;
|
|
14
|
+
stop: () => Promise<void>;
|
|
14
15
|
isConnected: boolean;
|
|
15
16
|
onAssetTracked: (coin: string) => void;
|
|
16
17
|
onAssetUntracked: (coin: string) => void;
|
|
17
|
-
setLeverage: (symbol: string, leverage: string) => Promise<void>;
|
|
18
18
|
trackAsset: (coin: string) => void;
|
|
19
19
|
untrackAsset: (coin: string) => boolean;
|
|
20
20
|
getTrackedCoins: () => ReadonlySet<string>;
|
|
21
|
+
start: () => Promise<void>;
|
|
21
22
|
}
|
|
22
23
|
declare abstract class BaseOrchestrator implements Orchestrator {
|
|
23
24
|
protected onStateUpdate: StateUpdateHandler;
|
|
24
25
|
protected onError: ErrorHandler;
|
|
25
26
|
protected demo: boolean;
|
|
26
27
|
protected credentials: SupportedCredentials | null;
|
|
27
|
-
protected
|
|
28
|
-
protected reconnectAttempts: number;
|
|
29
|
-
protected reconnectTimer: ReturnType<typeof setTimeout> | null;
|
|
28
|
+
protected stopped: boolean;
|
|
30
29
|
protected trackedCoins: Set<string>;
|
|
31
|
-
protected startGeneration: number;
|
|
32
|
-
private starting;
|
|
33
30
|
constructor(config: OrchestratorConfig);
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
/** Awaitable first-time connection. Errors propagate to caller. */
|
|
32
|
+
start(): Promise<void>;
|
|
33
|
+
abstract stop(): Promise<void>;
|
|
36
34
|
abstract get isConnected(): boolean;
|
|
37
35
|
abstract onAssetTracked(coin: string): void;
|
|
38
36
|
abstract onAssetUntracked(coin: string): void;
|
|
39
|
-
|
|
37
|
+
/** Subclass implements the actual async init (REST fetches, WS connect, etc.) */
|
|
38
|
+
protected abstract initAsync(): Promise<void>;
|
|
39
|
+
/** Subclass cleans up connections on stop. */
|
|
40
|
+
protected abstract cleanupConnections(): void | Promise<void>;
|
|
40
41
|
trackAsset(coin: string): void;
|
|
41
42
|
untrackAsset(coin: string): boolean;
|
|
42
43
|
getTrackedCoins(): ReadonlySet<string>;
|
|
43
44
|
protected canUntrackAsset(_coin: string): boolean;
|
|
44
|
-
protected scheduleReconnect(): void;
|
|
45
|
-
protected clearReconnectTimer(): void;
|
|
46
|
-
private guardedStart;
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
export { BaseOrchestrator, type Orchestrator, type OrchestratorConfig };
|