@pear-protocol/exchanges-sdk 0.20.0 → 0.22.0
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 +2 -4
- package/dist/client/binance/rest.js +1 -67
- package/dist/client/bybit/rest.js +1 -32
- package/dist/client/index.js +2 -4
- package/dist/client/lighter/rest.js +4 -93
- package/dist/client/lighter/ws.js +15 -0
- package/dist/client/okx/rest.js +1 -26
- package/dist/index.d.ts +3 -346
- package/dist/index.js +0 -1
- package/dist/sdk.js +0 -16
- package/dist/status/factory.js +2 -4
- package/dist/tracker/bybit/mapper.js +2 -3
- package/dist/tracker/hyperliquid/mapper.js +4 -6
- package/dist/tracker/index.js +2 -4
- package/dist/tracker/lighter/tracker.js +6 -1
- package/dist/tracker/okx/mapper.js +2 -3
- package/package.json +4 -4
- package/dist/syncer/base.js +0 -222
- package/dist/syncer/binance/syncer.js +0 -112
- package/dist/syncer/bybit/syncer.js +0 -68
- package/dist/syncer/factory.js +0 -33
- package/dist/syncer/hyperliquid/funding.js +0 -27
- package/dist/syncer/hyperliquid/syncer.js +0 -208
- package/dist/syncer/index.js +0 -7
- package/dist/syncer/lighter/funding.js +0 -29
- package/dist/syncer/lighter/syncer.js +0 -157
- package/dist/syncer/okx/syncer.js +0 -75
- package/dist/syncer/trade-fee.js +0 -21
package/dist/syncer/base.js
DELETED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
import { computeEntryPriceForAsset, computeSyncPayload } from '@pear-protocol/utils';
|
|
2
|
-
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
-
const OVERLAP_MS = 6e4;
|
|
6
|
-
class BaseAccountSyncer {
|
|
7
|
-
static {
|
|
8
|
-
__name(this, "BaseAccountSyncer");
|
|
9
|
-
}
|
|
10
|
-
sdk;
|
|
11
|
-
connector;
|
|
12
|
-
account;
|
|
13
|
-
credentials;
|
|
14
|
-
demo;
|
|
15
|
-
throttle;
|
|
16
|
-
constructor(config) {
|
|
17
|
-
this.sdk = config.sdk;
|
|
18
|
-
this.connector = config.connector;
|
|
19
|
-
this.account = config.account;
|
|
20
|
-
this.credentials = config.credentials;
|
|
21
|
-
this.demo = config.demo;
|
|
22
|
-
this.throttle = config.throttle;
|
|
23
|
-
}
|
|
24
|
-
// Phase A: Load current Pear state (positions, fills, instruments, watermark).
|
|
25
|
-
async loadPearState() {
|
|
26
|
-
const watermark = await this.sdk.core.sync.fills.since({
|
|
27
|
-
overlapMs: OVERLAP_MS
|
|
28
|
-
});
|
|
29
|
-
const [{ positions: openPositions }, { fills: provisionalFills }, { instruments }] = await Promise.all([
|
|
30
|
-
this.sdk.core.positions.openLite(),
|
|
31
|
-
this.sdk.core.fills.provisionals(),
|
|
32
|
-
this.sdk.core.instruments.lite({
|
|
33
|
-
connector: this.connector
|
|
34
|
-
})
|
|
35
|
-
]);
|
|
36
|
-
const entryBySymbol = await this.loadEntryBySymbol(openPositions);
|
|
37
|
-
return {
|
|
38
|
-
openPositions,
|
|
39
|
-
entryBySymbol,
|
|
40
|
-
provisionalFills,
|
|
41
|
-
instruments,
|
|
42
|
-
watermark
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Global per-symbol cost-basis entry of existing open positions, derived by pulling each
|
|
47
|
-
* position's fills and folding them (mirrors example-ui). One call per open position today —
|
|
48
|
-
* can collapse to a single endpoint later.
|
|
49
|
-
*/
|
|
50
|
-
async loadEntryBySymbol(openPositions) {
|
|
51
|
-
const responses = await Promise.all(openPositions.map((position) => this.sdk.core.fills.details(position.id)));
|
|
52
|
-
const fills = responses.flatMap((response) => response.fills).sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
53
|
-
const fillsBySymbol = /* @__PURE__ */ new Map();
|
|
54
|
-
for (const fill of fills) {
|
|
55
|
-
const list = fillsBySymbol.get(fill.symbol) ?? [];
|
|
56
|
-
list.push(fill);
|
|
57
|
-
fillsBySymbol.set(fill.symbol, list);
|
|
58
|
-
}
|
|
59
|
-
const entryBySymbol = {};
|
|
60
|
-
for (const [symbol, symbolFills] of fillsBySymbol) {
|
|
61
|
-
const entry = computeEntryPriceForAsset(symbolFills);
|
|
62
|
-
if (!entry.isZero()) entryBySymbol[symbol] = entry.toFixed();
|
|
63
|
-
}
|
|
64
|
-
return entryBySymbol;
|
|
65
|
-
}
|
|
66
|
-
// Phase B: Snapshot the exchange — fetch positions and fills since the watermark.
|
|
67
|
-
async snapshotExchange(state) {
|
|
68
|
-
const { openPositions, provisionalFills, instruments, watermark } = state;
|
|
69
|
-
const { firstSyncStartedAt } = watermark;
|
|
70
|
-
const externalFills = await this.listExternalFills({
|
|
71
|
-
since: new Date(watermark.sinceMs),
|
|
72
|
-
instruments,
|
|
73
|
-
openPositions,
|
|
74
|
-
provisionalFills
|
|
75
|
-
});
|
|
76
|
-
const exchangePositions = await this.listExternalPositions({
|
|
77
|
-
instruments
|
|
78
|
-
});
|
|
79
|
-
return {
|
|
80
|
-
exchangePositions,
|
|
81
|
-
externalFills,
|
|
82
|
-
firstSyncStartedAt
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
// Phase C: Compute the sync payload and post the applicables to the create endpoint.
|
|
86
|
-
async computeAndPostSync(state, snapshot) {
|
|
87
|
-
const { openPositions, entryBySymbol, provisionalFills, watermark } = state;
|
|
88
|
-
const { exchangePositions, externalFills, firstSyncStartedAt } = snapshot;
|
|
89
|
-
let payload = await computeSyncPayload({
|
|
90
|
-
tradeAccountId: this.account.id,
|
|
91
|
-
connector: this.connector,
|
|
92
|
-
openPositions,
|
|
93
|
-
entryBySymbol,
|
|
94
|
-
provisionalFills,
|
|
95
|
-
confirmedExchangeFillIds: watermark.confirmedExchangeFillIds ?? [],
|
|
96
|
-
externalFills,
|
|
97
|
-
exchangePositions,
|
|
98
|
-
neverSynced: watermark.neverSynced
|
|
99
|
-
});
|
|
100
|
-
if (!payload) {
|
|
101
|
-
if (watermark.hasPendingSynthetics !== true) {
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
payload = {
|
|
105
|
-
applicables: [],
|
|
106
|
-
exchangePositions,
|
|
107
|
-
meta: {
|
|
108
|
-
externalMaxTs: null
|
|
109
|
-
},
|
|
110
|
-
firstSyncTrim: null
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
const { firstSyncTrim } = payload;
|
|
114
|
-
let meta = {
|
|
115
|
-
...payload.meta,
|
|
116
|
-
...payload.meta.externalMaxTs === null ? {} : {
|
|
117
|
-
newestFillTs: payload.meta.externalMaxTs
|
|
118
|
-
},
|
|
119
|
-
...firstSyncStartedAt === void 0 ? {} : {
|
|
120
|
-
firstSyncStartedAt
|
|
121
|
-
},
|
|
122
|
-
// Core defers the create when any execution write postdates this server-minted token.
|
|
123
|
-
executionsUpdatedAtEpochMs: watermark.executionsUpdatedAtEpochMs
|
|
124
|
-
};
|
|
125
|
-
if (firstSyncTrim) {
|
|
126
|
-
console.log(`${this.connector}: first-sync trim \u2014 dropped ${firstSyncTrim.trimmedCount} pre-cutoff fills (cutoffTs=${firstSyncTrim.cutoffTs})`);
|
|
127
|
-
if (meta.externalMaxTs !== null && firstSyncTrim.trimmedMaxTs >= meta.externalMaxTs - OVERLAP_MS) {
|
|
128
|
-
meta = {
|
|
129
|
-
...meta,
|
|
130
|
-
externalMaxTs: meta.externalMaxTs + OVERLAP_MS + 1
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
const createBody = {
|
|
135
|
-
applicables: payload.applicables,
|
|
136
|
-
exchangePositions: payload.exchangePositions,
|
|
137
|
-
meta
|
|
138
|
-
};
|
|
139
|
-
const createBytes = new Blob([
|
|
140
|
-
JSON.stringify(createBody)
|
|
141
|
-
]).size;
|
|
142
|
-
if (createBytes > 50 * 1024) {
|
|
143
|
-
await this.sdk.core.sync.fills.createBulk(createBody);
|
|
144
|
-
} else {
|
|
145
|
-
await this.sdk.core.sync.fills.create(createBody);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Pull historical funding payments since the funding watermark and post them.
|
|
150
|
-
* Independent of the fill sync cycle (`synchronizeFills`) — funding rows are
|
|
151
|
-
* immutable and upsert idempotently, so this is driven on its own cadence.
|
|
152
|
-
*/
|
|
153
|
-
async synchronizeFunding() {
|
|
154
|
-
const [{ instruments }, watermark] = await Promise.all([
|
|
155
|
-
this.sdk.core.instruments.lite({
|
|
156
|
-
connector: this.connector
|
|
157
|
-
}),
|
|
158
|
-
this.sdk.core.sync.funding.since({
|
|
159
|
-
overlapMs: 6e4
|
|
160
|
-
})
|
|
161
|
-
]);
|
|
162
|
-
let payments;
|
|
163
|
-
try {
|
|
164
|
-
payments = await this.listExternalFunding({
|
|
165
|
-
since: new Date(watermark.sinceMs),
|
|
166
|
-
instruments
|
|
167
|
-
});
|
|
168
|
-
} catch (error) {
|
|
169
|
-
console.error(`${this.connector}: funding fetch failed \u2014 funding sync aborted, retry next tick`, error);
|
|
170
|
-
return "aborted";
|
|
171
|
-
}
|
|
172
|
-
if (payments.length === 0) {
|
|
173
|
-
return "synced";
|
|
174
|
-
}
|
|
175
|
-
const externalMaxTs = Math.max(...payments.map((payment) => payment.timestamp));
|
|
176
|
-
const createBody = {
|
|
177
|
-
payments,
|
|
178
|
-
meta: {
|
|
179
|
-
externalMaxTs
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
const bytes = new Blob([
|
|
183
|
-
JSON.stringify(createBody)
|
|
184
|
-
]).size;
|
|
185
|
-
if (bytes > 50 * 1024) {
|
|
186
|
-
await this.sdk.core.sync.funding.createBulk(createBody);
|
|
187
|
-
return "synced";
|
|
188
|
-
}
|
|
189
|
-
await this.sdk.core.sync.funding.create(createBody);
|
|
190
|
-
return "synced";
|
|
191
|
-
}
|
|
192
|
-
async synchronizeFills() {
|
|
193
|
-
const state = await this.loadPearState();
|
|
194
|
-
let snapshot;
|
|
195
|
-
try {
|
|
196
|
-
snapshot = await this.snapshotExchange(state);
|
|
197
|
-
} catch (error) {
|
|
198
|
-
console.error(`${this.connector}: position/fill fetch failed \u2014 sync cycle aborted, retry next tick`, error);
|
|
199
|
-
return "aborted";
|
|
200
|
-
}
|
|
201
|
-
try {
|
|
202
|
-
await this.computeAndPostSync(state, snapshot);
|
|
203
|
-
} catch (error) {
|
|
204
|
-
if (isSyncCreateDeferral(error)) {
|
|
205
|
-
console.log(`${this.connector}: sync cycle deferred by the server (${error.errorCode}) \u2014 retry next tick`);
|
|
206
|
-
return "deferred";
|
|
207
|
-
}
|
|
208
|
-
throw error;
|
|
209
|
-
}
|
|
210
|
-
return "synced";
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
function isSyncCreateDeferral(error) {
|
|
214
|
-
if (typeof error !== "object" || error === null) {
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
const { status, errorCode } = error;
|
|
218
|
-
return status === 409 && typeof errorCode === "string" && errorCode.startsWith("SYNC_CREATE_");
|
|
219
|
-
}
|
|
220
|
-
__name(isSyncCreateDeferral, "isSyncCreateDeferral");
|
|
221
|
-
|
|
222
|
-
export { BaseAccountSyncer };
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import BigNumber from 'bignumber.js';
|
|
2
|
-
import { fetchAllPositionRisk, fetchUserTradesSince, fetchAllOrdersSince } from '../../client/binance/rest';
|
|
3
|
-
import { BaseAccountSyncer } from '../base';
|
|
4
|
-
import { toSignedTradeFee } from '../trade-fee';
|
|
5
|
-
|
|
6
|
-
var __defProp = Object.defineProperty;
|
|
7
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
-
class BinanceAccountSyncer extends BaseAccountSyncer {
|
|
9
|
-
static {
|
|
10
|
-
__name(this, "BinanceAccountSyncer");
|
|
11
|
-
}
|
|
12
|
-
creds;
|
|
13
|
-
constructor(config) {
|
|
14
|
-
super(config);
|
|
15
|
-
this.creds = config.credentials;
|
|
16
|
-
}
|
|
17
|
-
async listExternalPositions({ instruments }) {
|
|
18
|
-
const contractSizeByInstrumentId = new Map(instruments.map((instrument) => [
|
|
19
|
-
instrument.id,
|
|
20
|
-
instrument.precision.quantity.contractSize
|
|
21
|
-
]));
|
|
22
|
-
const rawPositions = await fetchAllPositionRisk(this.creds, this.demo);
|
|
23
|
-
return rawPositions.reduce((positions, rawPosition) => {
|
|
24
|
-
const signedSize = new BigNumber(rawPosition.positionAmt);
|
|
25
|
-
if (signedSize.isZero()) {
|
|
26
|
-
return positions;
|
|
27
|
-
}
|
|
28
|
-
const contractSize = contractSizeByInstrumentId.get(rawPosition.symbol) ?? 1;
|
|
29
|
-
const size = signedSize.abs().times(contractSize).toFixed();
|
|
30
|
-
const side = signedSize.isGreaterThan(0) ? "BUY" : "SELL";
|
|
31
|
-
positions.push({
|
|
32
|
-
symbol: rawPosition.symbol,
|
|
33
|
-
size,
|
|
34
|
-
side,
|
|
35
|
-
entryPrice: rawPosition.entryPrice,
|
|
36
|
-
markPrice: rawPosition.markPrice
|
|
37
|
-
});
|
|
38
|
-
return positions;
|
|
39
|
-
}, []);
|
|
40
|
-
}
|
|
41
|
-
async listExternalFills({ since, instruments, openPositions, provisionalFills }) {
|
|
42
|
-
const knownInstrumentIds = new Set(instruments.map((instrument) => instrument.id));
|
|
43
|
-
const requestedInstrumentIds = this.collectRequestedInstrumentIds(openPositions, provisionalFills);
|
|
44
|
-
const instrumentIds = [
|
|
45
|
-
...requestedInstrumentIds
|
|
46
|
-
].filter((instrumentId) => knownInstrumentIds.has(instrumentId));
|
|
47
|
-
if (!instrumentIds.length) {
|
|
48
|
-
return [];
|
|
49
|
-
}
|
|
50
|
-
const [trades, orders] = await Promise.all([
|
|
51
|
-
fetchUserTradesSince(this.creds, instrumentIds, since, this.demo),
|
|
52
|
-
fetchAllOrdersSince(this.creds, instrumentIds, since, this.demo)
|
|
53
|
-
]);
|
|
54
|
-
const ordersBySymbolOrderId = this.mapOrdersBySymbolOrderId(orders);
|
|
55
|
-
return trades.reduce((fills, rawFill) => {
|
|
56
|
-
if (!requestedInstrumentIds.has(rawFill.symbol)) {
|
|
57
|
-
return fills;
|
|
58
|
-
}
|
|
59
|
-
const order = ordersBySymbolOrderId.get(this.orderKey(rawFill.symbol, rawFill.orderId));
|
|
60
|
-
fills.push({
|
|
61
|
-
connector: "binance",
|
|
62
|
-
exchangeFillId: rawFill.id.toString(),
|
|
63
|
-
cloid: order?.clientOrderId || null,
|
|
64
|
-
symbol: rawFill.symbol,
|
|
65
|
-
price: rawFill.price,
|
|
66
|
-
quantity: rawFill.qty,
|
|
67
|
-
side: rawFill.side,
|
|
68
|
-
reduceOnly: this.isReduceOnly(rawFill),
|
|
69
|
-
timestamp: rawFill.time,
|
|
70
|
-
tradeFee: toSignedTradeFee("binance", rawFill.commission),
|
|
71
|
-
builderFee: "0",
|
|
72
|
-
liquidation: null
|
|
73
|
-
});
|
|
74
|
-
return fills;
|
|
75
|
-
}, []);
|
|
76
|
-
}
|
|
77
|
-
async listExternalFunding(_params) {
|
|
78
|
-
throw new Error("funding sync is not implemented for binance");
|
|
79
|
-
}
|
|
80
|
-
isReduceOnly(rawFill) {
|
|
81
|
-
if (rawFill.positionSide === "LONG") return rawFill.side === "SELL";
|
|
82
|
-
if (rawFill.positionSide === "SHORT") return rawFill.side === "BUY";
|
|
83
|
-
if (rawFill.positionSide === "BOTH") {
|
|
84
|
-
return !new BigNumber(rawFill.realizedPnl || 0).isZero();
|
|
85
|
-
}
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
collectRequestedInstrumentIds(openPositions, provisionalFills) {
|
|
89
|
-
const instrumentIds = /* @__PURE__ */ new Set();
|
|
90
|
-
for (const position of openPositions) {
|
|
91
|
-
for (const instrumentId of Object.keys(position.exposure)) {
|
|
92
|
-
instrumentIds.add(instrumentId);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
for (const fill of provisionalFills) {
|
|
96
|
-
instrumentIds.add(fill.symbol);
|
|
97
|
-
}
|
|
98
|
-
return instrumentIds;
|
|
99
|
-
}
|
|
100
|
-
mapOrdersBySymbolOrderId(orders) {
|
|
101
|
-
const bySymbolOrderId = /* @__PURE__ */ new Map();
|
|
102
|
-
for (const order of orders) {
|
|
103
|
-
bySymbolOrderId.set(this.orderKey(order.symbol, order.orderId), order);
|
|
104
|
-
}
|
|
105
|
-
return bySymbolOrderId;
|
|
106
|
-
}
|
|
107
|
-
orderKey(symbol, orderId) {
|
|
108
|
-
return `${symbol}:${orderId}`;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export { BinanceAccountSyncer };
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import BigNumber from 'bignumber.js';
|
|
2
|
-
import { fetchPositions, fetchExecutionsSince } from '../../client/bybit/rest';
|
|
3
|
-
import { BaseAccountSyncer } from '../base';
|
|
4
|
-
import { toSignedTradeFee } from '../trade-fee';
|
|
5
|
-
|
|
6
|
-
var __defProp = Object.defineProperty;
|
|
7
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
-
class BybitAccountSyncer extends BaseAccountSyncer {
|
|
9
|
-
static {
|
|
10
|
-
__name(this, "BybitAccountSyncer");
|
|
11
|
-
}
|
|
12
|
-
creds;
|
|
13
|
-
constructor(config) {
|
|
14
|
-
super(config);
|
|
15
|
-
this.creds = config.credentials;
|
|
16
|
-
}
|
|
17
|
-
async listExternalPositions({ instruments }) {
|
|
18
|
-
const rawPositions = await fetchPositions(this.creds, "USDT", this.demo);
|
|
19
|
-
const contractSizeByInstrumentId = new Map(instruments.map((instrument) => [
|
|
20
|
-
instrument.id,
|
|
21
|
-
instrument.precision.quantity.contractSize
|
|
22
|
-
]));
|
|
23
|
-
return rawPositions.reduce((positions, rawPosition) => {
|
|
24
|
-
if (!rawPosition.size || rawPosition.size === "0" || !rawPosition.side) return positions;
|
|
25
|
-
const contractSize = contractSizeByInstrumentId.get(rawPosition.symbol) ?? 1;
|
|
26
|
-
const size = new BigNumber(rawPosition.size).abs().times(contractSize).toFixed();
|
|
27
|
-
positions.push({
|
|
28
|
-
symbol: rawPosition.symbol,
|
|
29
|
-
size,
|
|
30
|
-
side: rawPosition.side === "Buy" ? "BUY" : "SELL",
|
|
31
|
-
entryPrice: rawPosition.avgPrice,
|
|
32
|
-
markPrice: rawPosition.avgPrice
|
|
33
|
-
});
|
|
34
|
-
return positions;
|
|
35
|
-
}, []);
|
|
36
|
-
}
|
|
37
|
-
async listExternalFills({ since }) {
|
|
38
|
-
const executions = await fetchExecutionsSince(this.creds, since, this.demo);
|
|
39
|
-
return executions.reduce((fills, rawFill) => {
|
|
40
|
-
if (rawFill.execType === "Funding") {
|
|
41
|
-
return fills;
|
|
42
|
-
}
|
|
43
|
-
fills.push({
|
|
44
|
-
connector: "bybit",
|
|
45
|
-
exchangeFillId: rawFill.execId,
|
|
46
|
-
cloid: rawFill.orderLinkId || null,
|
|
47
|
-
symbol: rawFill.symbol,
|
|
48
|
-
price: rawFill.execPrice,
|
|
49
|
-
quantity: rawFill.execQty,
|
|
50
|
-
side: rawFill.side === "Buy" ? "BUY" : "SELL",
|
|
51
|
-
reduceOnly: this.isReduceOnly(rawFill),
|
|
52
|
-
timestamp: Number(rawFill.execTime),
|
|
53
|
-
tradeFee: toSignedTradeFee("bybit", rawFill.execFee),
|
|
54
|
-
builderFee: "0",
|
|
55
|
-
liquidation: null
|
|
56
|
-
});
|
|
57
|
-
return fills;
|
|
58
|
-
}, []);
|
|
59
|
-
}
|
|
60
|
-
async listExternalFunding(_params) {
|
|
61
|
-
throw new Error("funding sync is not implemented for bybit");
|
|
62
|
-
}
|
|
63
|
-
isReduceOnly(rawFill) {
|
|
64
|
-
return Number(rawFill.closedSize || "0") > 0;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export { BybitAccountSyncer };
|
package/dist/syncer/factory.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { BinanceAccountSyncer } from './binance/syncer';
|
|
2
|
-
import { BybitAccountSyncer } from './bybit/syncer';
|
|
3
|
-
import { HyperliquidAccountSyncer } from './hyperliquid/syncer';
|
|
4
|
-
import { LighterAccountSyncer } from './lighter/syncer';
|
|
5
|
-
import { OkxAccountSyncer } from './okx/syncer';
|
|
6
|
-
|
|
7
|
-
var __defProp = Object.defineProperty;
|
|
8
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
-
function createAccountSyncer(exchange, config) {
|
|
10
|
-
switch (exchange) {
|
|
11
|
-
case "binance":
|
|
12
|
-
return new BinanceAccountSyncer(config);
|
|
13
|
-
case "bybit":
|
|
14
|
-
return new BybitAccountSyncer(config);
|
|
15
|
-
case "okx":
|
|
16
|
-
return new OkxAccountSyncer(config);
|
|
17
|
-
case "hyperliquid":
|
|
18
|
-
return new HyperliquidAccountSyncer(config);
|
|
19
|
-
case "lighter":
|
|
20
|
-
case "lighter_rhc":
|
|
21
|
-
return new LighterAccountSyncer({
|
|
22
|
-
...config,
|
|
23
|
-
connector: exchange
|
|
24
|
-
});
|
|
25
|
-
default: {
|
|
26
|
-
const _exhaustive = exchange;
|
|
27
|
-
throw new Error(`Unsupported exchange: ${String(_exhaustive)}`);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
__name(createAccountSyncer, "createAccountSyncer");
|
|
32
|
-
|
|
33
|
-
export { createAccountSyncer };
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import BigNumber from 'bignumber.js';
|
|
2
|
-
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
5
|
-
function toFundingPaymentEntries(events, symbolToInstrumentId) {
|
|
6
|
-
return events.reduce((entries, event) => {
|
|
7
|
-
const instrumentId = symbolToInstrumentId[event.delta.coin];
|
|
8
|
-
if (!instrumentId) {
|
|
9
|
-
console.warn(`Instrument symbol ${event.delta.coin} not found in instrument symbol to id map.`);
|
|
10
|
-
return entries;
|
|
11
|
-
}
|
|
12
|
-
const size = new BigNumber(event.delta.szi);
|
|
13
|
-
const fundingPerUnit = size.isZero() ? "0" : new BigNumber(event.delta.usdc).div(size).toFixed();
|
|
14
|
-
entries.push({
|
|
15
|
-
connector: "hyperliquid",
|
|
16
|
-
instrumentId,
|
|
17
|
-
fundingRate: event.delta.fundingRate,
|
|
18
|
-
notional: event.delta.usdc,
|
|
19
|
-
fundingPerUnit,
|
|
20
|
-
timestamp: event.time
|
|
21
|
-
});
|
|
22
|
-
return entries;
|
|
23
|
-
}, []);
|
|
24
|
-
}
|
|
25
|
-
__name(toFundingPaymentEntries, "toFundingPaymentEntries");
|
|
26
|
-
|
|
27
|
-
export { toFundingPaymentEntries };
|
|
@@ -1,208 +0,0 @@
|
|
|
1
|
-
import BigNumber from 'bignumber.js';
|
|
2
|
-
import { fetchInfo } from '../../client/hyperliquid/rest';
|
|
3
|
-
import { BaseAccountSyncer } from '../base';
|
|
4
|
-
import { toFundingPaymentEntries } from './funding';
|
|
5
|
-
|
|
6
|
-
var __defProp = Object.defineProperty;
|
|
7
|
-
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
-
const HYPERLIQUID_FILL_PAGE_LIMIT = 2e3;
|
|
9
|
-
class HyperliquidAccountSyncer extends BaseAccountSyncer {
|
|
10
|
-
static {
|
|
11
|
-
__name(this, "HyperliquidAccountSyncer");
|
|
12
|
-
}
|
|
13
|
-
address;
|
|
14
|
-
perpDexNames;
|
|
15
|
-
constructor(config) {
|
|
16
|
-
super(config);
|
|
17
|
-
this.address = config.account.exchangeIdentifier;
|
|
18
|
-
this.perpDexNames = config.hyperliquidPerpDexNames;
|
|
19
|
-
}
|
|
20
|
-
async listExternalPositions({ instruments }) {
|
|
21
|
-
const instrumentByBase = new Map(instruments.map((instrument) => [
|
|
22
|
-
instrument.base,
|
|
23
|
-
{
|
|
24
|
-
id: instrument.id,
|
|
25
|
-
contractSize: instrument.precision.quantity.contractSize
|
|
26
|
-
}
|
|
27
|
-
]));
|
|
28
|
-
const dexNames = this.perpDexNames ?? await this.fetchPerpDexNames();
|
|
29
|
-
const states = await Promise.all(dexNames.map((dex) => this.fetchInfoGated({
|
|
30
|
-
type: "clearinghouseState",
|
|
31
|
-
user: this.address,
|
|
32
|
-
dex
|
|
33
|
-
})));
|
|
34
|
-
const assetPositions = states.flatMap((state) => state.assetPositions);
|
|
35
|
-
return assetPositions.reduce((positions, { position: rawPosition }) => {
|
|
36
|
-
const szi = new BigNumber(rawPosition.szi);
|
|
37
|
-
if (szi.isZero()) {
|
|
38
|
-
return positions;
|
|
39
|
-
}
|
|
40
|
-
const instrument = instrumentByBase.get(rawPosition.coin);
|
|
41
|
-
if (!instrument) {
|
|
42
|
-
console.warn(`Instrument for coin ${rawPosition.coin} not found.`);
|
|
43
|
-
return positions;
|
|
44
|
-
}
|
|
45
|
-
const size = szi.abs().times(instrument.contractSize).toFixed();
|
|
46
|
-
positions.push({
|
|
47
|
-
symbol: instrument.id,
|
|
48
|
-
size,
|
|
49
|
-
side: szi.isPositive() ? "BUY" : "SELL",
|
|
50
|
-
entryPrice: rawPosition.entryPx,
|
|
51
|
-
markPrice: new BigNumber(rawPosition.positionValue).abs().div(size).toFixed()
|
|
52
|
-
});
|
|
53
|
-
return positions;
|
|
54
|
-
}, []);
|
|
55
|
-
}
|
|
56
|
-
async listExternalFills({ since, instruments }) {
|
|
57
|
-
const instrumentSymbolToIdMap = instruments.reduce((acc, instrument) => {
|
|
58
|
-
acc[instrument.base] = instrument.id;
|
|
59
|
-
return acc;
|
|
60
|
-
}, {});
|
|
61
|
-
const allFills = [];
|
|
62
|
-
const seenTids = /* @__PURE__ */ new Set();
|
|
63
|
-
const addFill = /* @__PURE__ */ __name((fill) => {
|
|
64
|
-
if (!seenTids.has(fill.tid)) {
|
|
65
|
-
seenTids.add(fill.tid);
|
|
66
|
-
allFills.push(fill);
|
|
67
|
-
}
|
|
68
|
-
}, "addFill");
|
|
69
|
-
let cursor = since.getTime();
|
|
70
|
-
let batchSize;
|
|
71
|
-
do {
|
|
72
|
-
const batch = await this.fetchInfoGated({
|
|
73
|
-
type: "userFillsByTime",
|
|
74
|
-
user: this.address,
|
|
75
|
-
startTime: cursor,
|
|
76
|
-
aggregateByTime: true
|
|
77
|
-
});
|
|
78
|
-
batchSize = batch.length;
|
|
79
|
-
for (const rawFill of batch) {
|
|
80
|
-
addFill(rawFill);
|
|
81
|
-
}
|
|
82
|
-
if (batchSize === HYPERLIQUID_FILL_PAGE_LIMIT) {
|
|
83
|
-
const nextCursor = batch[batch.length - 1]?.time ?? cursor;
|
|
84
|
-
if (nextCursor <= cursor) {
|
|
85
|
-
warnStalledPagination("fills", cursor);
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
cursor = nextCursor;
|
|
89
|
-
}
|
|
90
|
-
} while (batchSize === HYPERLIQUID_FILL_PAGE_LIMIT);
|
|
91
|
-
cursor = since.getTime();
|
|
92
|
-
do {
|
|
93
|
-
const twapBatch = await this.fetchInfoGated({
|
|
94
|
-
type: "userTwapSliceFillsByTime",
|
|
95
|
-
user: this.address,
|
|
96
|
-
startTime: cursor,
|
|
97
|
-
aggregateByTime: true
|
|
98
|
-
});
|
|
99
|
-
batchSize = twapBatch.length;
|
|
100
|
-
for (const item of twapBatch) {
|
|
101
|
-
addFill(item.fill);
|
|
102
|
-
}
|
|
103
|
-
if (batchSize === HYPERLIQUID_FILL_PAGE_LIMIT) {
|
|
104
|
-
const nextCursor = twapBatch[twapBatch.length - 1]?.fill.time ?? cursor;
|
|
105
|
-
if (nextCursor <= cursor) {
|
|
106
|
-
warnStalledPagination("TWAP fills", cursor);
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
cursor = nextCursor;
|
|
110
|
-
}
|
|
111
|
-
} while (batchSize === HYPERLIQUID_FILL_PAGE_LIMIT);
|
|
112
|
-
allFills.sort((a, b) => a.time - b.time);
|
|
113
|
-
return allFills.reduce((fills, rawFill) => {
|
|
114
|
-
const instrumentId = instrumentSymbolToIdMap[rawFill.coin];
|
|
115
|
-
if (!instrumentId) {
|
|
116
|
-
console.warn(`Instrument symbol ${rawFill.coin} not found in instrument symbol to id map.`);
|
|
117
|
-
return fills;
|
|
118
|
-
}
|
|
119
|
-
const tradeFee = new BigNumber(rawFill.fee).minus(rawFill.builderFee ?? "0");
|
|
120
|
-
fills.push({
|
|
121
|
-
connector: "hyperliquid",
|
|
122
|
-
exchangeFillId: rawFill.tid.toFixed(),
|
|
123
|
-
cloid: rawFill.cloid ?? null,
|
|
124
|
-
symbol: instrumentId,
|
|
125
|
-
price: rawFill.px,
|
|
126
|
-
quantity: rawFill.sz,
|
|
127
|
-
side: rawFill.side === "B" ? "BUY" : "SELL",
|
|
128
|
-
reduceOnly: this.isReduceOnly(rawFill),
|
|
129
|
-
timestamp: rawFill.time,
|
|
130
|
-
tradeFee: tradeFee.toFixed(),
|
|
131
|
-
builderFee: new BigNumber(rawFill.builderFee ?? "0").abs().toFixed(),
|
|
132
|
-
// Preserve the existing marker when attribution is absent; reject explicitly named counterparties.
|
|
133
|
-
liquidation: rawFill.liquidation && (!rawFill.liquidation.liquidatedUser || rawFill.liquidation.liquidatedUser.toLowerCase() === this.address.toLowerCase()) ? {
|
|
134
|
-
mark: rawFill.liquidation.markPx
|
|
135
|
-
} : null
|
|
136
|
-
});
|
|
137
|
-
return fills;
|
|
138
|
-
}, []);
|
|
139
|
-
}
|
|
140
|
-
async listExternalFunding({ since, instruments }) {
|
|
141
|
-
const instrumentSymbolToIdMap = instruments.reduce((acc, instrument) => {
|
|
142
|
-
acc[instrument.base] = instrument.id;
|
|
143
|
-
return acc;
|
|
144
|
-
}, {});
|
|
145
|
-
const PAGE_LIMIT = 500;
|
|
146
|
-
const allEvents = /* @__PURE__ */ new Map();
|
|
147
|
-
let cursor = since.getTime();
|
|
148
|
-
let batchSize;
|
|
149
|
-
do {
|
|
150
|
-
const batch = await this.fetchInfoGated({
|
|
151
|
-
type: "userFunding",
|
|
152
|
-
user: this.address,
|
|
153
|
-
startTime: cursor
|
|
154
|
-
});
|
|
155
|
-
batchSize = batch.length;
|
|
156
|
-
for (const event of batch) {
|
|
157
|
-
allEvents.set(`${event.time}:${event.delta.coin}:${event.hash}`, event);
|
|
158
|
-
}
|
|
159
|
-
if (batchSize === PAGE_LIMIT) {
|
|
160
|
-
const nextCursor = batch[batch.length - 1]?.time ?? cursor;
|
|
161
|
-
if (nextCursor <= cursor) {
|
|
162
|
-
warnStalledPagination("funding", cursor);
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
cursor = nextCursor;
|
|
166
|
-
}
|
|
167
|
-
} while (batchSize === PAGE_LIMIT);
|
|
168
|
-
return toFundingPaymentEntries([
|
|
169
|
-
...allEvents.values()
|
|
170
|
-
], instrumentSymbolToIdMap);
|
|
171
|
-
}
|
|
172
|
-
isReduceOnly(rawFill) {
|
|
173
|
-
return rawFill.dir.includes("Close");
|
|
174
|
-
}
|
|
175
|
-
/** Fetch the perp-dex descriptors and reduce them to unique dex names ('' = the main dex). */
|
|
176
|
-
async fetchPerpDexNames() {
|
|
177
|
-
const perpDexs = await this.fetchInfoGated({
|
|
178
|
-
type: "perpDexs"
|
|
179
|
-
});
|
|
180
|
-
return [
|
|
181
|
-
...new Set(perpDexs.map((perpDex) => perpDex?.name ?? ""))
|
|
182
|
-
];
|
|
183
|
-
}
|
|
184
|
-
/**
|
|
185
|
-
* Route one info request through the optional throttle seam: gate before the
|
|
186
|
-
* call, report the response size after, surface a failure before it propagates.
|
|
187
|
-
*/
|
|
188
|
-
async fetchInfoGated(body) {
|
|
189
|
-
if (!this.throttle) {
|
|
190
|
-
return fetchInfo(body, this.demo);
|
|
191
|
-
}
|
|
192
|
-
await this.throttle.beforeRequest(body.type);
|
|
193
|
-
try {
|
|
194
|
-
const response = await fetchInfo(body, this.demo);
|
|
195
|
-
this.throttle.afterRequest(body.type, Array.isArray(response) ? response.length : 0);
|
|
196
|
-
return response;
|
|
197
|
-
} catch (error) {
|
|
198
|
-
this.throttle.afterRequestError(body.type, error);
|
|
199
|
-
throw error;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
function warnStalledPagination(stream, cursor) {
|
|
204
|
-
console.warn(`Hyperliquid ${stream} pagination stalled at timestamp ${cursor}; returned history may be incomplete.`);
|
|
205
|
-
}
|
|
206
|
-
__name(warnStalledPagination, "warnStalledPagination");
|
|
207
|
-
|
|
208
|
-
export { HyperliquidAccountSyncer };
|