@pear-protocol/exchanges-sdk 0.5.0 → 0.5.2
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/dist/client/lighter/rest.js +11 -5
- package/dist/client/lighter/rest.test.js +9 -3
- package/dist/index.d.ts +0 -1
- package/dist/status/hyperliquid/status.js +1 -1
- package/dist/status/hyperliquid/status.test.js +6 -1
- package/dist/syncer/lighter/funding.js +2 -1
- package/dist/syncer/lighter/funding.test.js +2 -2
- package/dist/syncer/lighter/syncer.test.js +1 -1
- package/dist/tracker/hyperliquid/mapper.js +1 -3
- package/dist/tracker/hyperliquid/mapper.test.js +18 -13
- package/dist/tracker/hyperliquid/tracker.js +1 -4
- package/dist/tracker/hyperliquid/tracker.test.js +17 -7
- package/dist/tracker/hyperliquid/types.js +0 -1
- package/dist/tracker/types.js +0 -1
- package/package.json +4 -4
|
@@ -112,12 +112,13 @@ async function fetchPositionFundingSince(accountIndex, authToken, since, demo =
|
|
|
112
112
|
const results = /* @__PURE__ */ new Map();
|
|
113
113
|
const seenCursors = /* @__PURE__ */ new Set();
|
|
114
114
|
let cursor;
|
|
115
|
+
const sinceSeconds = Math.max(1, Math.floor(since.getTime() / 1e3));
|
|
115
116
|
do {
|
|
116
117
|
const params = new URLSearchParams({
|
|
117
118
|
account_index: accountIndex.toString(),
|
|
118
119
|
limit: "100",
|
|
119
120
|
side: "all",
|
|
120
|
-
start_timestamp:
|
|
121
|
+
start_timestamp: sinceSeconds.toString(),
|
|
121
122
|
...cursor ? { cursor } : {}
|
|
122
123
|
});
|
|
123
124
|
const response = await proxyFetch(`${getLighterBaseUrl(demo)}/api/v1/positionFunding?${params.toString()}`, {
|
|
@@ -125,7 +126,7 @@ async function fetchPositionFundingSince(accountIndex, authToken, since, demo =
|
|
|
125
126
|
});
|
|
126
127
|
const data = await readLighterResponse(response, "/positionFunding");
|
|
127
128
|
for (const funding of data.position_fundings ?? []) {
|
|
128
|
-
if (funding.timestamp >=
|
|
129
|
+
if (funding.timestamp >= sinceSeconds) {
|
|
129
130
|
results.set(`${funding.market_id}:${funding.funding_id}`, funding);
|
|
130
131
|
}
|
|
131
132
|
}
|
|
@@ -138,12 +139,17 @@ async function fetchPositionFundingSince(accountIndex, authToken, since, demo =
|
|
|
138
139
|
return Array.from(results.values());
|
|
139
140
|
}
|
|
140
141
|
async function readLighterResponse(response, endpoint, acceptedCodes = [200]) {
|
|
141
|
-
|
|
142
|
+
let data;
|
|
143
|
+
try {
|
|
144
|
+
data = await response.json();
|
|
145
|
+
} catch {
|
|
146
|
+
data = void 0;
|
|
147
|
+
}
|
|
148
|
+
if (!data) {
|
|
142
149
|
throw new Error(`Lighter ${endpoint} returned ${response.status}`);
|
|
143
150
|
}
|
|
144
|
-
const data = await response.json();
|
|
145
151
|
if (!acceptedCodes.includes(data.code)) {
|
|
146
|
-
throw new Error(`Lighter ${endpoint} returned ${data.code}: ${data.message ??
|
|
152
|
+
throw new Error(`Lighter ${endpoint} returned ${data.code}: ${data.message ?? `HTTP ${response.status}`}`);
|
|
147
153
|
}
|
|
148
154
|
return data;
|
|
149
155
|
}
|
|
@@ -8,7 +8,7 @@ describe("Lighter REST client", () => {
|
|
|
8
8
|
it("routes demo account reads to testnet and rejects exchange-level errors", async () => {
|
|
9
9
|
const fetchMock = vi.fn().mockResolvedValue(
|
|
10
10
|
new Response(JSON.stringify({ code: 20001, message: "account not found" }), {
|
|
11
|
-
status:
|
|
11
|
+
status: 400
|
|
12
12
|
})
|
|
13
13
|
);
|
|
14
14
|
vi.stubGlobal("fetch", fetchMock);
|
|
@@ -24,7 +24,7 @@ describe("Lighter REST client", () => {
|
|
|
24
24
|
"fetch",
|
|
25
25
|
vi.fn().mockResolvedValue(
|
|
26
26
|
new Response(JSON.stringify({ code: 21100, message: "account not found" }), {
|
|
27
|
-
status:
|
|
27
|
+
status: 400
|
|
28
28
|
})
|
|
29
29
|
)
|
|
30
30
|
);
|
|
@@ -124,9 +124,15 @@ describe("Lighter REST client", () => {
|
|
|
124
124
|
const rows = await fetchPositionFundingSince(42, "auth", /* @__PURE__ */ new Date(1e3), true);
|
|
125
125
|
expect(rows.map((row) => row.funding_id)).toEqual([1, 2, 3]);
|
|
126
126
|
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
127
|
-
expect(String(fetchMock.mock.calls[0]?.[0])).toContain("start_timestamp=
|
|
127
|
+
expect(String(fetchMock.mock.calls[0]?.[0])).toContain("start_timestamp=1");
|
|
128
128
|
expect(String(fetchMock.mock.calls[1]?.[0])).toContain("cursor=page-2");
|
|
129
129
|
});
|
|
130
|
+
it("floors an epoch watermark to start_timestamp=1 since Lighter rejects 0 in seconds", async () => {
|
|
131
|
+
const fetchMock = vi.fn().mockResolvedValue(new Response(JSON.stringify({ code: 200, position_fundings: [] }), { status: 200 }));
|
|
132
|
+
vi.stubGlobal("fetch", fetchMock);
|
|
133
|
+
await fetchPositionFundingSince(42, "auth", /* @__PURE__ */ new Date(0), true);
|
|
134
|
+
expect(String(fetchMock.mock.calls[0]?.[0])).toContain("start_timestamp=1");
|
|
135
|
+
});
|
|
130
136
|
it("stops trades pagination when the API repeats a cursor", async () => {
|
|
131
137
|
const page = new Response(JSON.stringify({ code: 200, trades: [], next_cursor: "same-page" }), { status: 200 });
|
|
132
138
|
const fetchMock = vi.fn().mockResolvedValueOnce(page).mockResolvedValueOnce(
|
package/dist/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ async function checkHyperliquidAccountStatus(config) {
|
|
|
14
14
|
const approvalAddress = metadata && "isSubaccount" in metadata && metadata.isSubaccount ? metadata.mainAccountAddress : config.account.exchangeIdentifier;
|
|
15
15
|
try {
|
|
16
16
|
const abstraction = await fetchUserAbstraction(config.account.exchangeIdentifier, config.demo);
|
|
17
|
-
const supportedAccountMode = abstraction === HyperliquidAccountType.DEFAULT || abstraction === HyperliquidAccountType.DISABLED || abstraction === HyperliquidAccountType.UNIFIED_ACCOUNT;
|
|
17
|
+
const supportedAccountMode = abstraction === HyperliquidAccountType.DEFAULT || abstraction === HyperliquidAccountType.DISABLED || abstraction === HyperliquidAccountType.UNIFIED_ACCOUNT || abstraction === HyperliquidAccountType.PORTFOLIO_MARGIN;
|
|
18
18
|
if (!supportedAccountMode) {
|
|
19
19
|
reasons.push("hyperliquid_account_mode_unsupported");
|
|
20
20
|
}
|
|
@@ -54,7 +54,7 @@ describe("checkHyperliquidAccountStatus", () => {
|
|
|
54
54
|
expect(checkApiWalletMock).toHaveBeenCalledWith({ userAddress: mainAccountAddress, agentWalletAddress });
|
|
55
55
|
expect(checkBuilderFeeApprovalMock).toHaveBeenCalledWith(mainAccountAddress, builderAddress, 10);
|
|
56
56
|
});
|
|
57
|
-
it.each(["
|
|
57
|
+
it.each(["dexAbstraction", "somethingNew"])(
|
|
58
58
|
"warns when account mode %s cannot be represented safely",
|
|
59
59
|
async (abstraction) => {
|
|
60
60
|
fetchUserAbstractionMock.mockResolvedValue(abstraction);
|
|
@@ -65,6 +65,11 @@ describe("checkHyperliquidAccountStatus", () => {
|
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
);
|
|
68
|
+
it("treats Portfolio Margin as a supported account mode", async () => {
|
|
69
|
+
fetchUserAbstractionMock.mockResolvedValue("portfolioMargin");
|
|
70
|
+
const status = await checkHyperliquidAccountStatus(statusConfig());
|
|
71
|
+
expect(status).toEqual({ status: "active", reasons: [] });
|
|
72
|
+
});
|
|
68
73
|
it("distinguishes an account-mode API failure from an unsupported mode", async () => {
|
|
69
74
|
fetchUserAbstractionMock.mockRejectedValue(new Error("503"));
|
|
70
75
|
const status = await checkHyperliquidAccountStatus(statusConfig());
|
|
@@ -16,7 +16,8 @@ function toFundingPaymentEntries(events, instrumentIds) {
|
|
|
16
16
|
fundingRate: event.rate,
|
|
17
17
|
notional: event.change,
|
|
18
18
|
fundingPerUnit,
|
|
19
|
-
|
|
19
|
+
// Lighter reports funding timestamps in seconds; the payment contract expects ms.
|
|
20
|
+
timestamp: event.timestamp * 1e3
|
|
20
21
|
});
|
|
21
22
|
return entries;
|
|
22
23
|
}, []);
|
|
@@ -17,7 +17,7 @@ describe("Lighter funding mapping", () => {
|
|
|
17
17
|
fundingRate: "0.0001",
|
|
18
18
|
notional: "-2",
|
|
19
19
|
fundingPerUnit: "-0.5",
|
|
20
|
-
timestamp:
|
|
20
|
+
timestamp: 1e6
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
connector: "lighter",
|
|
@@ -25,7 +25,7 @@ describe("Lighter funding mapping", () => {
|
|
|
25
25
|
fundingRate: "0.0001",
|
|
26
26
|
notional: "2",
|
|
27
27
|
fundingPerUnit: "-0.5",
|
|
28
|
-
timestamp:
|
|
28
|
+
timestamp: 1e6
|
|
29
29
|
}
|
|
30
30
|
]);
|
|
31
31
|
});
|
|
@@ -214,7 +214,7 @@ describe("LighterAccountSyncer funding mapping", () => {
|
|
|
214
214
|
fundingRate: "0.0001",
|
|
215
215
|
notional: "-2",
|
|
216
216
|
fundingPerUnit: "-0.5",
|
|
217
|
-
timestamp:
|
|
217
|
+
timestamp: 1e6
|
|
218
218
|
}
|
|
219
219
|
]);
|
|
220
220
|
expect(fetchPositionFundingSince).toHaveBeenCalledWith(ACCOUNT_INDEX, "test-token", /* @__PURE__ */ new Date(1e3), false);
|
|
@@ -84,10 +84,8 @@ function mapBalance(summary, spotCoinBalances, accountType, collateralTokenMap =
|
|
|
84
84
|
case HyperliquidAccountType.DISABLED:
|
|
85
85
|
return mapStandardBalance(summary, accountType, collateralTokenMap);
|
|
86
86
|
case HyperliquidAccountType.UNIFIED_ACCOUNT:
|
|
87
|
-
return mapUnifiedBalance(summary, spotCoinBalances, accountType, collateralTokenMap);
|
|
88
|
-
case HyperliquidAccountType.DEX_ABSTRACTION:
|
|
89
87
|
case HyperliquidAccountType.PORTFOLIO_MARGIN:
|
|
90
|
-
return
|
|
88
|
+
return mapUnifiedBalance(summary, spotCoinBalances, accountType, collateralTokenMap);
|
|
91
89
|
}
|
|
92
90
|
}
|
|
93
91
|
function mapPositions(raw) {
|
|
@@ -220,22 +220,27 @@ describe("Hyperliquid balance mapping", () => {
|
|
|
220
220
|
const spotBalances = /* @__PURE__ */ new Map([["USDC", { coin: "USDC", token: 0, total: "0", hold: "0" }]]);
|
|
221
221
|
expect(mapBalance(summary, spotBalances, HyperliquidAccountType.UNIFIED_ACCOUNT)?.marginRatio).toBe("1.0000");
|
|
222
222
|
});
|
|
223
|
-
it("
|
|
224
|
-
const
|
|
225
|
-
accountValue: "
|
|
226
|
-
totalMarginUsed: "
|
|
227
|
-
crossMaintenanceMarginUsed: "
|
|
228
|
-
withdrawable: "
|
|
223
|
+
it("maps Portfolio Margin balances with the same calculation as Unified Account", () => {
|
|
224
|
+
const state = buildState({
|
|
225
|
+
accountValue: "999",
|
|
226
|
+
totalMarginUsed: "30",
|
|
227
|
+
crossMaintenanceMarginUsed: "10",
|
|
228
|
+
withdrawable: "999",
|
|
229
229
|
coin: "BTC",
|
|
230
|
-
unrealizedPnl: "
|
|
230
|
+
unrealizedPnl: "5"
|
|
231
231
|
});
|
|
232
|
-
const { summary } = aggregateClearinghouseStates([
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
const { summary } = aggregateClearinghouseStates([dexState(state, 0)]);
|
|
233
|
+
const spotBalances = /* @__PURE__ */ new Map([
|
|
234
|
+
["USDC", { coin: "USDC", token: 0, total: "100", hold: "5", availableAfterMaintenance: "70" }]
|
|
235
235
|
]);
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
expect(
|
|
236
|
+
const portfolio = mapBalance(summary, spotBalances, HyperliquidAccountType.PORTFOLIO_MARGIN);
|
|
237
|
+
const unified = mapBalance(summary, spotBalances, HyperliquidAccountType.UNIFIED_ACCOUNT);
|
|
238
|
+
expect(portfolio).toMatchObject({
|
|
239
|
+
accountType: "portfolioMargin",
|
|
240
|
+
totalEquity: "100",
|
|
241
|
+
availableToTrade: "70"
|
|
242
|
+
});
|
|
243
|
+
expect({ ...portfolio, accountType: "unifiedAccount", timestamp: 0 }).toEqual({ ...unified, timestamp: 0 });
|
|
239
244
|
});
|
|
240
245
|
it("returns a zeroed aggregate for an empty DEX list", () => {
|
|
241
246
|
const { summary, assetPositions } = aggregateClearinghouseStates([]);
|
|
@@ -281,13 +281,10 @@ class HyperliquidAccountTracker extends BaseAccountTracker {
|
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
283
|
buildBalance() {
|
|
284
|
-
if (this.accountType === HyperliquidAccountType.DEX_ABSTRACTION || this.accountType === HyperliquidAccountType.PORTFOLIO_MARGIN) {
|
|
285
|
-
return null;
|
|
286
|
-
}
|
|
287
284
|
if (!this.hasClearinghouseState || !this.accountType) {
|
|
288
285
|
return void 0;
|
|
289
286
|
}
|
|
290
|
-
if (this.accountType === HyperliquidAccountType.UNIFIED_ACCOUNT && !this.hasSpotState) {
|
|
287
|
+
if ((this.accountType === HyperliquidAccountType.UNIFIED_ACCOUNT || this.accountType === HyperliquidAccountType.PORTFOLIO_MARGIN) && !this.hasSpotState) {
|
|
291
288
|
return void 0;
|
|
292
289
|
}
|
|
293
290
|
return mapBalance(this.lastSummary, this.spotCoinBalances, this.accountType, this.collateralTokenMap);
|
|
@@ -512,23 +512,33 @@ describe("HyperliquidAccountTracker live account-mode handling", () => {
|
|
|
512
512
|
expect(updates).toHaveLength(2);
|
|
513
513
|
expect(updates[1]).toBeNull();
|
|
514
514
|
});
|
|
515
|
-
it("
|
|
515
|
+
it("emits a live Portfolio Margin balance once spot state joins the clearinghouse state", () => {
|
|
516
516
|
const tracker = buildTracker();
|
|
517
517
|
const updates = [];
|
|
518
518
|
tracker.trackBalance((balance) => updates.push(balance));
|
|
519
|
-
const send = vi.fn();
|
|
520
519
|
const internals = tracker;
|
|
521
|
-
internals.accountType = HyperliquidAccountType.
|
|
520
|
+
internals.accountType = HyperliquidAccountType.PORTFOLIO_MARGIN;
|
|
521
|
+
internals.hasSpotState = false;
|
|
522
522
|
internals.hasClearinghouseState = false;
|
|
523
|
-
internals.ws = { send };
|
|
524
523
|
internals.onClearinghouseState({
|
|
525
524
|
user: address,
|
|
526
525
|
clearinghouseStates: [["", dexState("BTC", "100", "10")]]
|
|
527
526
|
});
|
|
528
|
-
internals.onWebData3(webData("portfolioMargin"));
|
|
529
527
|
expect(tracker.getBalance()).toBeNull();
|
|
530
|
-
expect(updates).
|
|
531
|
-
|
|
528
|
+
expect(updates).toEqual([]);
|
|
529
|
+
internals.onSpotState({
|
|
530
|
+
user: address,
|
|
531
|
+
spotState: {
|
|
532
|
+
balances: [{ coin: "USDC", token: 0, total: "100", hold: "5", entryNtl: "0" }],
|
|
533
|
+
tokenToAvailableAfterMaintenance: [[0, "90"]]
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
expect(tracker.getBalance()).toMatchObject({
|
|
537
|
+
accountType: "portfolioMargin",
|
|
538
|
+
walletBalance: "90",
|
|
539
|
+
availableToTrade: "90"
|
|
540
|
+
});
|
|
541
|
+
expect(updates).toHaveLength(1);
|
|
532
542
|
});
|
|
533
543
|
it("retains locked BTC active-asset data even without a user listener", () => {
|
|
534
544
|
const tracker = buildTracker();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
var HyperliquidAccountType = /* @__PURE__ */ ((HyperliquidAccountType2) => {
|
|
2
2
|
HyperliquidAccountType2["DEFAULT"] = "default";
|
|
3
|
-
HyperliquidAccountType2["DEX_ABSTRACTION"] = "dexAbstraction";
|
|
4
3
|
HyperliquidAccountType2["DISABLED"] = "disabled";
|
|
5
4
|
HyperliquidAccountType2["PORTFOLIO_MARGIN"] = "portfolioMargin";
|
|
6
5
|
HyperliquidAccountType2["UNIFIED_ACCOUNT"] = "unifiedAccount";
|
package/dist/tracker/types.js
CHANGED
|
@@ -12,7 +12,6 @@ const ACCOUNT_TYPE_LABELS = {
|
|
|
12
12
|
[BybitAccountType.PORTFOLIO_MARGIN]: "Portfolio Margin",
|
|
13
13
|
[HyperliquidAccountType.DEFAULT]: "Standard",
|
|
14
14
|
[HyperliquidAccountType.DISABLED]: "Standard",
|
|
15
|
-
[HyperliquidAccountType.DEX_ABSTRACTION]: "Standard (Shared HIP-3 Balance)",
|
|
16
15
|
[HyperliquidAccountType.PORTFOLIO_MARGIN]: "Portfolio Margin",
|
|
17
16
|
[HyperliquidAccountType.UNIFIED_ACCOUNT]: "Unified Account",
|
|
18
17
|
[LighterAccountType.CLASSIC]: "Classic",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pear-protocol/exchanges-sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Pear Protocol Exchanges SDK",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"test:watch": "vitest"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@pear-protocol/core-sdk": "^1.
|
|
31
|
-
"@pear-protocol/types": "^1.
|
|
32
|
-
"@pear-protocol/utils": "^0.
|
|
30
|
+
"@pear-protocol/core-sdk": "^1.11.1",
|
|
31
|
+
"@pear-protocol/types": "^1.14.1",
|
|
32
|
+
"@pear-protocol/utils": "^0.3.0",
|
|
33
33
|
"bignumber.js": "^11.1.1",
|
|
34
34
|
"partysocket": "^1.0.3",
|
|
35
35
|
"viem": "^2.48.8"
|