@pear-protocol/exchanges-sdk 0.6.4 → 0.6.6

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 (92) hide show
  1. package/dist/client/base-ws.js +15 -6
  2. package/dist/client/binance/exchange-ws.js +22 -8
  3. package/dist/client/binance/rest.js +40 -27
  4. package/dist/client/binance/ws.js +21 -2
  5. package/dist/client/bybit/exchange-ws.js +13 -6
  6. package/dist/client/bybit/rest.js +30 -36
  7. package/dist/client/bybit/ws.js +16 -2
  8. package/dist/client/exchange-ws.js +5 -0
  9. package/dist/client/hmac.js +9 -1
  10. package/dist/client/hyperliquid/exchange-ws.js +13 -5
  11. package/dist/client/hyperliquid/rest.js +37 -7
  12. package/dist/client/hyperliquid/ws.js +10 -4
  13. package/dist/client/index.js +9 -2
  14. package/dist/client/lighter/constants.js +4 -0
  15. package/dist/client/lighter/exchange-ws.js +13 -5
  16. package/dist/client/lighter/rest.js +153 -18
  17. package/dist/client/lighter/ws.js +10 -4
  18. package/dist/client/okx/exchange-ws.js +13 -6
  19. package/dist/client/okx/rest.js +27 -24
  20. package/dist/client/okx/ws.js +5 -0
  21. package/dist/helper/factory.js +3 -0
  22. package/dist/helper/hyperliquid/helper.js +18 -23
  23. package/dist/helper/lighter/auth-token.helper.js +68 -0
  24. package/dist/helper/lighter/cctp-deposit.helper.js +297 -0
  25. package/dist/helper/lighter/ethereum/abi.js +55 -11
  26. package/dist/helper/lighter/ethereum/index.js +22 -3
  27. package/dist/helper/lighter/ethereum/usdc.helper.js +77 -0
  28. package/dist/helper/lighter/helper.js +112 -37
  29. package/dist/helper/lighter/index.js +1 -0
  30. package/dist/helper/lighter/onboarding.helper.js +28 -0
  31. package/dist/helper/lighter/types.js +6 -1
  32. package/dist/helper/subaccount.helper.js +20 -0
  33. package/dist/index.d.ts +200 -1
  34. package/dist/sdk.js +18 -7
  35. package/dist/status/base.js +16 -3
  36. package/dist/status/binance/status.js +3 -0
  37. package/dist/status/bybit/status.js +3 -0
  38. package/dist/status/factory.js +3 -0
  39. package/dist/status/hyperliquid/status.js +8 -6
  40. package/dist/status/lighter/status.js +11 -3
  41. package/dist/status/okx/status.js +3 -0
  42. package/dist/syncer/base.js +79 -24
  43. package/dist/syncer/binance/syncer.js +14 -13
  44. package/dist/syncer/bybit/syncer.js +10 -6
  45. package/dist/syncer/factory.js +3 -0
  46. package/dist/syncer/hyperliquid/funding.js +3 -0
  47. package/dist/syncer/hyperliquid/syncer.js +52 -66
  48. package/dist/syncer/lighter/funding.js +3 -0
  49. package/dist/syncer/lighter/syncer.js +26 -25
  50. package/dist/syncer/okx/syncer.js +15 -13
  51. package/dist/tracker/base.js +15 -5
  52. package/dist/tracker/binance/mapper.js +5 -1
  53. package/dist/tracker/binance/tracker.js +63 -23
  54. package/dist/tracker/binance/types.js +2 -2
  55. package/dist/tracker/bybit/mapper.js +7 -1
  56. package/dist/tracker/bybit/tracker.js +68 -24
  57. package/dist/tracker/bybit/types.js +2 -2
  58. package/dist/tracker/hyperliquid/const.js +3 -1
  59. package/dist/tracker/hyperliquid/mapper.js +36 -20
  60. package/dist/tracker/hyperliquid/tracker.js +147 -54
  61. package/dist/tracker/hyperliquid/types.js +2 -2
  62. package/dist/tracker/index.js +3 -0
  63. package/dist/tracker/lighter/mapper.js +41 -31
  64. package/dist/tracker/lighter/tracker.js +68 -27
  65. package/dist/tracker/lighter/types.js +4 -4
  66. package/dist/tracker/okx/mapper.js +8 -0
  67. package/dist/tracker/okx/tracker.js +69 -18
  68. package/dist/tracker/okx/types.js +2 -2
  69. package/dist/tracker/utils.js +4 -0
  70. package/package.json +4 -4
  71. package/dist/client/hyperliquid/client.test.js +0 -41
  72. package/dist/client/lighter/constants.test.js +0 -22
  73. package/dist/client/lighter/rest.test.js +0 -158
  74. package/dist/helper/hyperliquid/helper.test.js +0 -72
  75. package/dist/helper/lighter/helper.test.js +0 -289
  76. package/dist/sdk.test.js +0 -117
  77. package/dist/status/hyperliquid/status.test.js +0 -94
  78. package/dist/status/lighter/status.test.js +0 -86
  79. package/dist/syncer/base.test.js +0 -148
  80. package/dist/syncer/hyperliquid/funding.test.js +0 -63
  81. package/dist/syncer/hyperliquid/syncer.test.js +0 -271
  82. package/dist/syncer/lighter/funding.test.js +0 -48
  83. package/dist/syncer/lighter/syncer.test.js +0 -274
  84. package/dist/tracker/base.test.js +0 -132
  85. package/dist/tracker/binance/tracker.test.js +0 -138
  86. package/dist/tracker/bybit/tracker.test.js +0 -145
  87. package/dist/tracker/hyperliquid/mapper.test.js +0 -260
  88. package/dist/tracker/hyperliquid/tracker.test.js +0 -597
  89. package/dist/tracker/lighter/mapper.test.js +0 -199
  90. package/dist/tracker/lighter/tracker.test.js +0 -515
  91. package/dist/tracker/okx/tracker.test.js +0 -126
  92. package/dist/tracker/utils.test.js +0 -8
@@ -1,271 +0,0 @@
1
- import { vi, describe, beforeEach, it, expect } from 'vitest';
2
- import { HyperliquidAccountSyncer } from './syncer';
3
-
4
- const { fetchInfoMock } = vi.hoisted(() => ({ fetchInfoMock: vi.fn() }));
5
- vi.mock("../../client/hyperliquid/rest", () => ({ fetchInfo: fetchInfoMock }));
6
- const account = {
7
- exchangeIdentifier: "0x1111111111111111111111111111111111111111"
8
- };
9
- const instrument = {
10
- id: "HL-BTC",
11
- base: "BTC"
12
- };
13
- function rawFill(time, tid = time) {
14
- return {
15
- coin: "BTC",
16
- px: "100000",
17
- sz: "0.1",
18
- side: "B",
19
- time,
20
- startPosition: "0",
21
- dir: "Open Long",
22
- closedPnl: "0",
23
- hash: "0xabc",
24
- oid: 123,
25
- crossed: true,
26
- fee: "1",
27
- builderFee: "0.2",
28
- tid,
29
- feeToken: "USDC",
30
- twapId: null
31
- };
32
- }
33
- function createSyncer() {
34
- return new HyperliquidAccountSyncer({
35
- sdk: {},
36
- connector: "hyperliquid",
37
- account,
38
- credentials: { signer_key: "sk" },
39
- demo: true
40
- });
41
- }
42
- describe("HyperliquidAccountSyncer fill mapping", () => {
43
- beforeEach(() => {
44
- fetchInfoMock.mockReset().mockImplementation(async (body) => {
45
- if (body.type === "userTwapSliceFillsByTime") {
46
- return [];
47
- }
48
- return [
49
- {
50
- coin: "BTC",
51
- px: "100000",
52
- sz: "0.1",
53
- side: "B",
54
- time: 17e11,
55
- startPosition: "0",
56
- dir: "Open Long",
57
- closedPnl: "0",
58
- hash: "0xabc",
59
- oid: 123,
60
- crossed: true,
61
- fee: "1",
62
- builderFee: "0.2",
63
- tid: 456,
64
- feeToken: "USDC",
65
- twapId: null
66
- }
67
- ];
68
- });
69
- });
70
- it("uses a globally unique identity, excludes builder fee from trade fee, and routes to testnet", async () => {
71
- const syncer = createSyncer();
72
- const fills = await syncer.listExternalFills({
73
- since: /* @__PURE__ */ new Date(1699999e6),
74
- instruments: [instrument],
75
- openPositions: [],
76
- provisionalFills: []
77
- });
78
- expect(fills).toEqual([
79
- expect.objectContaining({
80
- exchangeFillId: "456",
81
- tradeFee: "0.8",
82
- builderFee: "0.2"
83
- })
84
- ]);
85
- expect(fetchInfoMock).toHaveBeenCalledWith(expect.objectContaining({ type: "userFillsByTime" }), true);
86
- expect(fetchInfoMock).toHaveBeenCalledWith(
87
- expect.objectContaining({ type: "userFillsByTime", aggregateByTime: true }),
88
- true
89
- );
90
- expect(fetchInfoMock).toHaveBeenCalledWith(
91
- expect.objectContaining({ type: "userTwapSliceFillsByTime", aggregateByTime: true }),
92
- true
93
- );
94
- });
95
- it("fails closed when a maker rebate cannot satisfy the backend external-fill fee contract", async () => {
96
- fetchInfoMock.mockImplementation(async (body) => {
97
- if (body.type === "userTwapSliceFillsByTime") return [];
98
- return [
99
- {
100
- coin: "BTC",
101
- px: "100000",
102
- sz: "0.1",
103
- side: "B",
104
- time: 17e11,
105
- startPosition: "0",
106
- dir: "Open Long",
107
- closedPnl: "0",
108
- hash: "0xabc",
109
- oid: 123,
110
- crossed: false,
111
- fee: "-0.08",
112
- builderFee: "0.02",
113
- tid: 456,
114
- feeToken: "USDC",
115
- twapId: null
116
- }
117
- ];
118
- });
119
- const syncer = createSyncer();
120
- await expect(
121
- syncer.listExternalFills({
122
- since: /* @__PURE__ */ new Date(1699999e6),
123
- instruments: [instrument],
124
- openPositions: [],
125
- provisionalFills: []
126
- })
127
- ).rejects.toThrow(
128
- "Hyperliquid fill 456 has a negative net trade fee (-0.1); the backend external-fill contract does not accept Hyperliquid rebates."
129
- );
130
- });
131
- it("warns when inclusive fill pagination cannot move past the current timestamp", async () => {
132
- const consoleWarn = vi.spyOn(console, "warn").mockImplementation(() => void 0);
133
- const event = {
134
- coin: "BTC",
135
- px: "100000",
136
- sz: "0.1",
137
- side: "B",
138
- time: 1e3,
139
- startPosition: "0",
140
- dir: "Open Long",
141
- closedPnl: "0",
142
- hash: "0xabc",
143
- oid: 123,
144
- crossed: true,
145
- fee: "1",
146
- builderFee: "0.2",
147
- tid: 456,
148
- feeToken: "USDC",
149
- twapId: null
150
- };
151
- fetchInfoMock.mockImplementation(
152
- async (body) => body.type === "userFillsByTime" ? Array.from({ length: 2e3 }, () => event) : []
153
- );
154
- const syncer = createSyncer();
155
- await syncer.listExternalFills({
156
- since: /* @__PURE__ */ new Date(1e3),
157
- instruments: [instrument],
158
- openPositions: [],
159
- provisionalFills: []
160
- });
161
- expect(consoleWarn).toHaveBeenCalledWith(
162
- "Hyperliquid fills pagination stalled at timestamp 1000; returned history may be incomplete."
163
- );
164
- expect(fetchInfoMock).toHaveBeenCalledTimes(2);
165
- consoleWarn.mockRestore();
166
- });
167
- it("imports the reachable history of a never-synced account whose older fills fell out of retention", async () => {
168
- let page = 0;
169
- fetchInfoMock.mockImplementation(async (body) => {
170
- if (body.type === "userTwapSliceFillsByTime") return [];
171
- if (page >= 5) return [];
172
- const firstTime = 1e3 + page * 2e3;
173
- page += 1;
174
- return Array.from({ length: 2e3 }, (_, index) => rawFill(firstTime + index));
175
- });
176
- const syncer = createSyncer();
177
- const fills = await syncer.listExternalFills({
178
- since: /* @__PURE__ */ new Date(0),
179
- instruments: [instrument],
180
- openPositions: [],
181
- provisionalFills: []
182
- });
183
- expect(fills).toHaveLength(1e4);
184
- expect(fetchInfoMock).toHaveBeenCalledWith(expect.objectContaining({ aggregateByTime: true }), true);
185
- });
186
- it("pages through a full retention window of fills", async () => {
187
- let page = 0;
188
- fetchInfoMock.mockImplementation(async (body) => {
189
- if (body.type === "userTwapSliceFillsByTime") return [];
190
- if (page >= 5) return [];
191
- const firstTime = 1e3 + page * 2e3;
192
- page += 1;
193
- return Array.from({ length: 2e3 }, (_, index) => rawFill(firstTime + index));
194
- });
195
- const syncer = createSyncer();
196
- const fills = await syncer.listExternalFills({
197
- since: /* @__PURE__ */ new Date(1e3),
198
- instruments: [instrument],
199
- openPositions: [],
200
- provisionalFills: []
201
- });
202
- expect(fills).toHaveLength(1e4);
203
- expect(fetchInfoMock).toHaveBeenCalledTimes(7);
204
- });
205
- });
206
- describe("HyperliquidAccountSyncer position and funding snapshots", () => {
207
- beforeEach(() => {
208
- fetchInfoMock.mockReset();
209
- });
210
- it("de-duplicates DEX descriptors and derives mark price from current position value", async () => {
211
- fetchInfoMock.mockImplementation(async (body) => {
212
- if (body.type === "perpDexs") return [null, null, { name: "xyz" }];
213
- if (body.type === "clearinghouseState" && body.dex === "") {
214
- return {
215
- assetPositions: [
216
- {
217
- position: {
218
- coin: "BTC",
219
- szi: "2",
220
- entryPx: "150",
221
- positionValue: "400"
222
- }
223
- }
224
- ]
225
- };
226
- }
227
- if (body.type === "clearinghouseState") return { assetPositions: [] };
228
- throw new Error(`Unexpected request: ${body.type}`);
229
- });
230
- const syncer = createSyncer();
231
- const positions = await syncer.listExternalPositions({
232
- instruments: [
233
- {
234
- ...instrument,
235
- precision: { quantity: { contractSize: 1 } }
236
- }
237
- ]
238
- });
239
- expect(positions).toEqual([
240
- expect.objectContaining({ symbol: "HL-BTC", size: "2", entryPrice: "150", markPrice: "200" })
241
- ]);
242
- expect(fetchInfoMock).toHaveBeenCalledTimes(3);
243
- });
244
- it("de-duplicates an inclusive funding boundary and stops when its cursor cannot advance", async () => {
245
- const consoleWarn = vi.spyOn(console, "warn").mockImplementation(() => void 0);
246
- const event = {
247
- time: 1e3,
248
- hash: "0xabc",
249
- delta: {
250
- type: "funding",
251
- coin: "BTC",
252
- usdc: "-2",
253
- szi: "4",
254
- fundingRate: "0.0001",
255
- nSamples: null
256
- }
257
- };
258
- fetchInfoMock.mockResolvedValue(Array.from({ length: 500 }, () => event));
259
- const syncer = createSyncer();
260
- const payments = await syncer.listExternalFunding({
261
- since: /* @__PURE__ */ new Date(1e3),
262
- instruments: [instrument]
263
- });
264
- expect(payments).toHaveLength(1);
265
- expect(fetchInfoMock).toHaveBeenCalledTimes(1);
266
- expect(consoleWarn).toHaveBeenCalledWith(
267
- "Hyperliquid funding pagination stalled at timestamp 1000; returned history may be incomplete."
268
- );
269
- consoleWarn.mockRestore();
270
- });
271
- });
@@ -1,48 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { toFundingPaymentEntries } from './funding';
3
-
4
- describe("Lighter funding mapping", () => {
5
- it("derives funding per unit against signed long and short exposure", () => {
6
- const result = toFundingPaymentEntries(
7
- [
8
- fundingEvent({ funding_id: 1, position_side: "long", change: "-2" }),
9
- fundingEvent({ funding_id: 2, position_side: "short", change: "2" })
10
- ],
11
- /* @__PURE__ */ new Set(["1"])
12
- );
13
- expect(result).toEqual([
14
- {
15
- connector: "lighter",
16
- instrumentId: "1",
17
- fundingRate: "0.0001",
18
- notional: "-2",
19
- fundingPerUnit: "-0.5",
20
- timestamp: 1e6
21
- },
22
- {
23
- connector: "lighter",
24
- instrumentId: "1",
25
- fundingRate: "0.0001",
26
- notional: "2",
27
- fundingPerUnit: "-0.5",
28
- timestamp: 1e6
29
- }
30
- ]);
31
- });
32
- it("skips funding for unknown instruments", () => {
33
- expect(toFundingPaymentEntries([fundingEvent({})], /* @__PURE__ */ new Set())).toEqual([]);
34
- });
35
- });
36
- function fundingEvent(overrides) {
37
- return {
38
- timestamp: 1e3,
39
- market_id: 1,
40
- funding_id: 1,
41
- change: "-2",
42
- discount: "0",
43
- rate: "0.0001",
44
- position_size: "4",
45
- position_side: "long",
46
- ...overrides
47
- };
48
- }
@@ -1,274 +0,0 @@
1
- import { vi, describe, beforeEach, it, expect } from 'vitest';
2
- import { fetchTradesSince, fetchPositionFundingSince, fetchAccount } from '../../client/lighter/rest';
3
- import { LighterAccountSyncer } from './syncer';
4
-
5
- vi.mock("../../client/lighter/rest", () => ({
6
- fetchAccount: vi.fn(),
7
- fetchPositionFundingSince: vi.fn(),
8
- fetchTradesSince: vi.fn()
9
- }));
10
- const ACCOUNT_INDEX = 87440;
11
- const TRADES = [
12
- {
13
- // Real /trades row — account is taker via bid, integrator fee only:
14
- // builderFee = 200/1e6 × 49.279769, tradeFee = 0.
15
- trade_id: 23432371018,
16
- trade_id_str: "23432371018",
17
- type: "trade",
18
- market_id: 1,
19
- size: "0.00077",
20
- price: "63999.7",
21
- usd_amount: "49.279769",
22
- ask_account_id: 281474976656674,
23
- bid_account_id: ACCOUNT_INDEX,
24
- is_maker_ask: true,
25
- timestamp: 1782044759045,
26
- taker_position_size_before: "0.00000",
27
- maker_position_size_before: "0.00000",
28
- integrator_taker_fee: 200
29
- },
30
- {
31
- // Real /trades row — account is taker via ask, base taker fee only:
32
- // tradeFee = 280/1e6 × 11.896090, builderFee = 0.
33
- trade_id: 22207283246,
34
- trade_id_str: "22207283246",
35
- type: "trade",
36
- market_id: 1,
37
- size: "0.00019",
38
- price: "62611.0",
39
- usd_amount: "11.896090",
40
- ask_account_id: ACCOUNT_INDEX,
41
- bid_account_id: 587622,
42
- is_maker_ask: false,
43
- timestamp: 1781010160339,
44
- taker_fee: 280,
45
- taker_position_size_before: "0.00019",
46
- maker_position_size_before: "0.00000"
47
- },
48
- {
49
- // Real /trades row — no fee fields at all, so both fees resolve to 0.
50
- trade_id: 23432472386,
51
- trade_id_str: "23432472386",
52
- type: "trade",
53
- market_id: 1,
54
- size: "0.00079",
55
- price: "63973.6",
56
- usd_amount: "50.539144",
57
- ask_account_id: 726722,
58
- bid_account_id: ACCOUNT_INDEX,
59
- is_maker_ask: true,
60
- timestamp: 1782044888766,
61
- taker_position_size_before: "-0.00079",
62
- maker_position_size_before: "0.00000"
63
- },
64
- {
65
- // Synthetic row — the real payload only carries taker-side fees, so we construct a
66
- // maker fill (account is the resting ask) to cover the maker_fee / integrator_maker_fee branch:
67
- // tradeFee = 100/1e6 × 100, builderFee = 50/1e6 × 100.
68
- trade_id: 90000000001,
69
- trade_id_str: "90000000001",
70
- type: "trade",
71
- market_id: 1,
72
- size: "0.00100",
73
- price: "65000.0",
74
- usd_amount: "100.000000",
75
- ask_account_id: ACCOUNT_INDEX,
76
- bid_account_id: 726722,
77
- is_maker_ask: true,
78
- timestamp: 178205e7,
79
- maker_fee: 100,
80
- taker_position_size_before: "0.00000",
81
- maker_position_size_before: "0.00000",
82
- integrator_maker_fee: 50
83
- },
84
- {
85
- // A flip is not a reduce-only fill: Lighter explicitly reports that the
86
- // account-side position sign changed after crossing through zero.
87
- trade_id: 90000000002,
88
- trade_id_str: "90000000002",
89
- type: "trade",
90
- market_id: 1,
91
- size: "0.00200",
92
- price: "65000.0",
93
- usd_amount: "130.000000",
94
- ask_account_id: ACCOUNT_INDEX,
95
- bid_account_id: 726722,
96
- is_maker_ask: false,
97
- timestamp: 1782050001e3,
98
- taker_position_size_before: "0.00100",
99
- taker_position_sign_changed: true,
100
- maker_position_size_before: "0.00000"
101
- }
102
- ];
103
- function createSyncer(demo = false, createAuthToken = vi.fn().mockResolvedValue({ token: "test-token" })) {
104
- const sdk = {
105
- core: { misc: { lighter: { createAuthToken } } }
106
- };
107
- const config = {
108
- sdk,
109
- connector: "lighter",
110
- account: { exchangeIdentifier: String(ACCOUNT_INDEX) },
111
- credentials: { api_key_index: 0, api_private_key: "test-key" },
112
- ws: {},
113
- demo
114
- };
115
- return new LighterAccountSyncer(config);
116
- }
117
- async function listFills(demo = false) {
118
- const syncer = createSyncer(demo);
119
- const params = {
120
- since: /* @__PURE__ */ new Date(0),
121
- instruments: [{ id: "1" }],
122
- openPositions: [],
123
- provisionalFills: []
124
- };
125
- return syncer.listExternalFills(params);
126
- }
127
- async function listFunding(demo = false) {
128
- const syncer = createSyncer(demo);
129
- const params = {
130
- since: /* @__PURE__ */ new Date(1e3),
131
- instruments: [{ id: "1" }]
132
- };
133
- return syncer.listExternalFunding(params);
134
- }
135
- async function listPositions() {
136
- const syncer = createSyncer();
137
- const params = {
138
- instruments: [
139
- {
140
- id: "1",
141
- precision: { quantity: { contractSize: 0.01 } }
142
- }
143
- ]
144
- };
145
- return syncer.listExternalPositions(params);
146
- }
147
- const byId = (fills, id) => fills.find((fill) => fill.exchangeFillId === id);
148
- describe("LighterAccountSyncer fee mapping", () => {
149
- beforeEach(() => {
150
- vi.mocked(fetchTradesSince).mockResolvedValue(TRADES);
151
- });
152
- it("computes the integrator (builder) fee as the taker ppm rate times the USD notional", async () => {
153
- const fills = await listFills();
154
- expect(byId(fills, "23432371018")).toMatchObject({
155
- symbol: "1",
156
- side: "BUY",
157
- tradeFee: "0",
158
- builderFee: "0.0098559538"
159
- });
160
- });
161
- it("computes the trade fee as the base taker ppm rate times the USD notional", async () => {
162
- const fills = await listFills();
163
- expect(byId(fills, "22207283246")).toMatchObject({
164
- symbol: "1",
165
- side: "SELL",
166
- tradeFee: "0.0033309052",
167
- builderFee: "0"
168
- });
169
- });
170
- it("emits zero fees when the trade carries no fee fields", async () => {
171
- const fills = await listFills();
172
- expect(byId(fills, "23432472386")).toMatchObject({
173
- symbol: "1",
174
- side: "BUY",
175
- tradeFee: "0",
176
- builderFee: "0"
177
- });
178
- });
179
- it("reads the maker-side fee fields when the account is the maker", async () => {
180
- const fills = await listFills();
181
- expect(byId(fills, "90000000001")).toMatchObject({
182
- symbol: "1",
183
- side: "SELL",
184
- tradeFee: "0.01",
185
- builderFee: "0.005"
186
- });
187
- });
188
- it("does not classify a position flip as reduce-only", async () => {
189
- const fills = await listFills();
190
- expect(byId(fills, "90000000002")).toMatchObject({
191
- side: "SELL",
192
- reduceOnly: false
193
- });
194
- });
195
- });
196
- describe("LighterAccountSyncer funding mapping", () => {
197
- it("fetches paginated position funding with the generated auth token", async () => {
198
- vi.mocked(fetchPositionFundingSince).mockResolvedValue([
199
- {
200
- timestamp: 1e3,
201
- market_id: 1,
202
- funding_id: 7,
203
- change: "-2",
204
- discount: "0",
205
- rate: "0.0001",
206
- position_size: "4",
207
- position_side: "long"
208
- }
209
- ]);
210
- await expect(listFunding()).resolves.toEqual([
211
- {
212
- connector: "lighter",
213
- instrumentId: "1",
214
- fundingRate: "0.0001",
215
- notional: "-2",
216
- fundingPerUnit: "-0.5",
217
- timestamp: 1e6
218
- }
219
- ]);
220
- expect(fetchPositionFundingSince).toHaveBeenCalledWith(ACCOUNT_INDEX, "test-token", /* @__PURE__ */ new Date(1e3), false);
221
- });
222
- });
223
- describe("LighterAccountSyncer demo authentication boundary", () => {
224
- it("rejects authenticated demo history reads before minting a mainnet token", async () => {
225
- vi.clearAllMocks();
226
- await expect(listFills(true)).rejects.toThrow("backend signing is mainnet-only");
227
- await expect(listFunding(true)).rejects.toThrow("backend signing is mainnet-only");
228
- expect(fetchTradesSince).not.toHaveBeenCalled();
229
- expect(fetchPositionFundingSince).not.toHaveBeenCalled();
230
- });
231
- });
232
- describe("LighterAccountSyncer position mapping", () => {
233
- it("derives mark price from position value in normalized base units", async () => {
234
- vi.mocked(fetchAccount).mockResolvedValue({
235
- code: 200,
236
- accounts: [
237
- {
238
- account_type: 0,
239
- collateral: "1000",
240
- total_asset_value: "1000",
241
- available_balance: "900",
242
- cross_asset_value: "1000",
243
- cross_initial_margin_requirement: "100",
244
- cross_maintenance_margin_requirement: "50",
245
- positions: [
246
- {
247
- market_id: 1,
248
- symbol: "BTC",
249
- initial_margin_fraction: "10",
250
- sign: -1,
251
- position: "100",
252
- avg_entry_price: "50000",
253
- position_value: "60000",
254
- unrealized_pnl: "-10000",
255
- liquidation_price: "70000",
256
- margin_mode: 0,
257
- allocated_margin: "0"
258
- }
259
- ]
260
- }
261
- ]
262
- });
263
- await expect(listPositions()).resolves.toEqual([
264
- {
265
- symbol: "1",
266
- size: "1",
267
- side: "SELL",
268
- entryPrice: "50000",
269
- markPrice: "60000"
270
- }
271
- ]);
272
- expect(fetchAccount).toHaveBeenCalledWith(ACCOUNT_INDEX, false);
273
- });
274
- });