@pear-protocol/exchanges-sdk 0.19.1 → 0.21.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 CHANGED
@@ -34,6 +34,7 @@ require an authenticated connection. Existing `createTracker(connection)` calls
34
34
  | Bybit | `bybit` | Linear Perpetuals |
35
35
  | Hyperliquid | `hyperliquid` | Perpetuals |
36
36
  | Lighter | `lighter` | Perpetuals |
37
+ | Lighter (Robinhood Chain) | `lighter_rhc` | Perpetuals |
37
38
  | OKX | `okx` | Linear SWAP |
38
39
 
39
40
  ## Installation
@@ -12,6 +12,7 @@ class LighterWs extends BaseWs {
12
12
  demo;
13
13
  constructor(connector, demo = false) {
14
14
  super(), this.connector = connector, this.demo = demo;
15
+ this.addMessageListener((data) => this.answerPing(data));
15
16
  }
16
17
  getWsUrl() {
17
18
  return getLighterWsUrl(this.connector, this.demo);
@@ -32,6 +33,20 @@ class LighterWs extends BaseWs {
32
33
  auth: authToken
33
34
  });
34
35
  }
36
+ /** The venue pings the client too; an unanswered ping can leave the socket open while data stops. */
37
+ answerPing(data) {
38
+ let frame;
39
+ try {
40
+ frame = JSON.parse(data);
41
+ } catch {
42
+ return;
43
+ }
44
+ if (frame.type === "ping") {
45
+ this.send({
46
+ type: "pong"
47
+ });
48
+ }
49
+ }
35
50
  }
36
51
 
37
52
  export { LighterWs };
package/dist/index.d.ts CHANGED
@@ -206,6 +206,8 @@ declare class LighterWs extends BaseWs {
206
206
  protected buildPingMessage(): WsMessage;
207
207
  /** Subscribes to one account channel with the authentication Lighter requires. */
208
208
  subscribe(channel: string, authToken: string): void;
209
+ /** The venue pings the client too; an unanswered ping can leave the socket open while data stops. */
210
+ private answerPing;
209
211
  }
210
212
 
211
213
  declare class LighterExchangeWs extends ExchangeWs {
@@ -1263,8 +1265,8 @@ declare abstract class BaseAccountSyncer<ExternalFill> implements AccountSyncer
1263
1265
  protected abstract isReduceOnly(fill: ExternalFill): boolean;
1264
1266
  private loadPearState;
1265
1267
  /**
1266
- * Global per-symbol cost-basis entry of existing open positions, derived by pulling each
1267
- * position's fills and folding them (mirrors example-ui). One call per open position today —
1268
+ * Global per-symbol cost-basis entry of existing open positions, derived from each
1269
+ * position's assigned ledger and remaining exposure. One call per open position today —
1268
1270
  * can collapse to a single endpoint later.
1269
1271
  */
1270
1272
  private loadEntryBySymbol;
@@ -1,4 +1,4 @@
1
- import { computeEntryPriceForAsset, computeSyncPayload } from '@pear-protocol/utils';
1
+ import { computePositionEntryPrices, abs, isZero, add, multiply, divide, computeSyncPayload } from '@pear-protocol/utils';
2
2
 
3
3
  var __defProp = Object.defineProperty;
4
4
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
@@ -43,23 +43,40 @@ class BaseAccountSyncer {
43
43
  };
44
44
  }
45
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 —
46
+ * Global per-symbol cost-basis entry of existing open positions, derived from each
47
+ * position's assigned ledger and remaining exposure. One call per open position today —
48
48
  * can collapse to a single endpoint later.
49
49
  */
50
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);
51
+ const states = await Promise.all(openPositions.map(async (position) => {
52
+ const { fills } = await this.sdk.core.fills.details(position.id);
53
+ fills.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
54
+ return {
55
+ exposure: position.exposure,
56
+ entryPrices: computePositionEntryPrices(position.exposure, fills)
57
+ };
58
+ }));
59
+ const totals = /* @__PURE__ */ new Map();
60
+ for (const state of states) {
61
+ for (const [symbol, signedQuantity] of Object.entries(state.exposure)) {
62
+ const quantity = abs(signedQuantity);
63
+ if (isZero(quantity)) {
64
+ continue;
65
+ }
66
+ const entry = state.entryPrices[symbol];
67
+ const existing = totals.get(symbol);
68
+ totals.set(symbol, {
69
+ quantity: add(existing?.quantity ?? "0", quantity).toFixed(),
70
+ notional: add(existing?.notional ?? "0", multiply(quantity, entry ?? "0")).toFixed(),
71
+ unpriced: existing?.unpriced === true || entry === void 0
72
+ });
73
+ }
58
74
  }
59
75
  const entryBySymbol = {};
60
- for (const [symbol, symbolFills] of fillsBySymbol) {
61
- const entry = computeEntryPriceForAsset(symbolFills);
62
- if (!entry.isZero()) entryBySymbol[symbol] = entry.toFixed();
76
+ for (const [symbol, total] of totals) {
77
+ if (!total.unpriced) {
78
+ entryBySymbol[symbol] = divide(total.notional, total.quantity).toFixed();
79
+ }
63
80
  }
64
81
  return entryBySymbol;
65
82
  }
@@ -86,7 +103,7 @@ class BaseAccountSyncer {
86
103
  async computeAndPostSync(state, snapshot) {
87
104
  const { openPositions, entryBySymbol, provisionalFills, watermark } = state;
88
105
  const { exchangePositions, externalFills, firstSyncStartedAt } = snapshot;
89
- const payload = await computeSyncPayload({
106
+ let payload = await computeSyncPayload({
90
107
  tradeAccountId: this.account.id,
91
108
  connector: this.connector,
92
109
  openPositions,
@@ -98,7 +115,18 @@ class BaseAccountSyncer {
98
115
  neverSynced: watermark.neverSynced
99
116
  });
100
117
  if (!payload) {
101
- return;
118
+ if (watermark.hasPendingSynthetics !== true) {
119
+ return;
120
+ }
121
+ payload = {
122
+ fills: [],
123
+ applicables: [],
124
+ exchangePositions,
125
+ meta: {
126
+ externalMaxTs: null
127
+ },
128
+ firstSyncTrim: null
129
+ };
102
130
  }
103
131
  const { firstSyncTrim } = payload;
104
132
  let meta = {
@@ -122,6 +150,7 @@ class BaseAccountSyncer {
122
150
  }
123
151
  }
124
152
  const createBody = {
153
+ fills: payload.fills,
125
154
  applicables: payload.applicables,
126
155
  exchangePositions: payload.exchangePositions,
127
156
  meta
@@ -297,7 +297,12 @@ class LighterAccountTracker extends BaseAccountTracker {
297
297
  routeRaw(raw) {
298
298
  try {
299
299
  const msg = JSON.parse(raw);
300
- if (!msg.type) return;
300
+ if (!msg.type) {
301
+ if (msg.error !== void 0) {
302
+ this.onError(new Error(`Lighter stream error: ${JSON.stringify(msg.error)}`));
303
+ }
304
+ return;
305
+ }
301
306
  if (msg.type === "subscribed/account_all" || msg.type === "update/account_all") {
302
307
  this.handle({
303
308
  kind: "account_all",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pear-protocol/exchanges-sdk",
3
- "version": "0.19.1",
3
+ "version": "0.21.0",
4
4
  "description": "Pear Protocol Exchanges SDK",
5
5
  "private": false,
6
6
  "type": "module",
@@ -22,15 +22,15 @@
22
22
  "scripts": {
23
23
  "build": "tsup",
24
24
  "clean": "rm -rf dist .turbo",
25
- "typecheck": "tsc --noEmit",
25
+ "typecheck": "bunx --package bun@1.4.3 bun --check --threads=2",
26
26
  "test": "vitest run",
27
27
  "test:watch": "vitest"
28
28
  },
29
29
  "dependencies": {
30
30
  "@nktkas/hyperliquid": "0.33.2",
31
- "@pear-protocol/core-sdk": "^2.12.0",
32
- "@pear-protocol/types": "^2.19.0",
33
- "@pear-protocol/utils": "^1.4.0",
31
+ "@pear-protocol/core-sdk": "^2.16.0",
32
+ "@pear-protocol/types": "^2.24.0",
33
+ "@pear-protocol/utils": "^1.6.0",
34
34
  "bignumber.js": "^11.1.1",
35
35
  "partysocket": "^1.0.3",
36
36
  "viem": "^2.48.8",