@pear-protocol/exchanges-sdk 0.1.10 → 0.1.11

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/index.d.ts CHANGED
@@ -470,7 +470,7 @@ declare class LighterAccountSyncer extends BaseAccountSyncer<LighterRawTrade> {
470
470
  private getPositionSizeBefore;
471
471
  private getTradeFee;
472
472
  private getIntegratorFee;
473
- private feeTickToDecimal;
473
+ private feeTickToAmount;
474
474
  }
475
475
 
476
476
  interface OkxRawFill {
@@ -44,8 +44,8 @@ class LighterAccountSyncer extends BaseAccountSyncer {
44
44
  side,
45
45
  reduceOnly: this.isReduceOnly(trade),
46
46
  timestamp: trade.timestamp,
47
- tradeFee: this.feeTickToDecimal(this.getTradeFee(trade, accountSide)),
48
- builderFee: this.feeTickToDecimal(this.getIntegratorFee(trade, accountSide)),
47
+ tradeFee: this.getTradeFee(trade, accountSide),
48
+ builderFee: this.getIntegratorFee(trade, accountSide),
49
49
  liquidation: trade.type === "liquidation" ? { mark: trade.price } : null
50
50
  });
51
51
  return acc;
@@ -81,13 +81,17 @@ class LighterAccountSyncer extends BaseAccountSyncer {
81
81
  return new BigNumber(value ?? 0);
82
82
  }
83
83
  getTradeFee(trade, side) {
84
- return this.isMaker(trade, side) ? trade.maker_fee ?? 0 : trade.taker_fee ?? 0;
84
+ const rateTick = this.isMaker(trade, side) ? trade.maker_fee : trade.taker_fee;
85
+ return this.feeTickToAmount(rateTick, trade.usd_amount);
85
86
  }
86
87
  getIntegratorFee(trade, side) {
87
- return this.isMaker(trade, side) ? trade.integrator_maker_fee ?? 0 : trade.integrator_taker_fee ?? 0;
88
+ const rateTick = this.isMaker(trade, side) ? trade.integrator_maker_fee : trade.integrator_taker_fee;
89
+ return this.feeTickToAmount(rateTick, trade.usd_amount);
88
90
  }
89
- feeTickToDecimal(value) {
90
- return new BigNumber(value).abs().div(LIGHTER_FEE_TICK_DENOMINATOR).toFixed();
91
+ // Lighter fees come as ppm rates (÷1e6), not amounts — the charged fee is that rate times the trade's USD notional.
92
+ feeTickToAmount(rateTick, usdAmount) {
93
+ const rate = new BigNumber(rateTick ?? 0).abs().div(LIGHTER_FEE_TICK_DENOMINATOR);
94
+ return rate.times(usdAmount).toFixed();
91
95
  }
92
96
  }
93
97
 
@@ -0,0 +1,148 @@
1
+ import { vi, describe, beforeEach, it, expect } from 'vitest';
2
+ import { fetchTradesSince } from '../../client/lighter/rest';
3
+ import { LighterAccountSyncer } from './syncer';
4
+
5
+ vi.mock("../../client/lighter/rest", () => ({
6
+ fetchTradesSince: vi.fn()
7
+ }));
8
+ const ACCOUNT_INDEX = 87440;
9
+ const TRADES = [
10
+ {
11
+ // Real /trades row — account is taker via bid, integrator fee only:
12
+ // builderFee = 200/1e6 × 49.279769, tradeFee = 0.
13
+ trade_id: 23432371018,
14
+ trade_id_str: "23432371018",
15
+ type: "trade",
16
+ market_id: 1,
17
+ size: "0.00077",
18
+ price: "63999.7",
19
+ usd_amount: "49.279769",
20
+ ask_account_id: 281474976656674,
21
+ bid_account_id: ACCOUNT_INDEX,
22
+ is_maker_ask: true,
23
+ timestamp: 1782044759045,
24
+ taker_position_size_before: "0.00000",
25
+ maker_position_size_before: "0.00000",
26
+ integrator_taker_fee: 200
27
+ },
28
+ {
29
+ // Real /trades row — account is taker via ask, base taker fee only:
30
+ // tradeFee = 280/1e6 × 11.896090, builderFee = 0.
31
+ trade_id: 22207283246,
32
+ trade_id_str: "22207283246",
33
+ type: "trade",
34
+ market_id: 1,
35
+ size: "0.00019",
36
+ price: "62611.0",
37
+ usd_amount: "11.896090",
38
+ ask_account_id: ACCOUNT_INDEX,
39
+ bid_account_id: 587622,
40
+ is_maker_ask: false,
41
+ timestamp: 1781010160339,
42
+ taker_fee: 280,
43
+ taker_position_size_before: "0.00019",
44
+ maker_position_size_before: "0.00000"
45
+ },
46
+ {
47
+ // Real /trades row — no fee fields at all, so both fees resolve to 0.
48
+ trade_id: 23432472386,
49
+ trade_id_str: "23432472386",
50
+ type: "trade",
51
+ market_id: 1,
52
+ size: "0.00079",
53
+ price: "63973.6",
54
+ usd_amount: "50.539144",
55
+ ask_account_id: 726722,
56
+ bid_account_id: ACCOUNT_INDEX,
57
+ is_maker_ask: true,
58
+ timestamp: 1782044888766,
59
+ taker_position_size_before: "-0.00079",
60
+ maker_position_size_before: "0.00000"
61
+ },
62
+ {
63
+ // Synthetic row — the real payload only carries taker-side fees, so we construct a
64
+ // maker fill (account is the resting ask) to cover the maker_fee / integrator_maker_fee branch:
65
+ // tradeFee = 100/1e6 × 100, builderFee = 50/1e6 × 100.
66
+ trade_id: 90000000001,
67
+ trade_id_str: "90000000001",
68
+ type: "trade",
69
+ market_id: 1,
70
+ size: "0.00100",
71
+ price: "65000.0",
72
+ usd_amount: "100.000000",
73
+ ask_account_id: ACCOUNT_INDEX,
74
+ bid_account_id: 726722,
75
+ is_maker_ask: true,
76
+ timestamp: 178205e7,
77
+ maker_fee: 100,
78
+ taker_position_size_before: "0.00000",
79
+ maker_position_size_before: "0.00000",
80
+ integrator_maker_fee: 50
81
+ }
82
+ ];
83
+ function createSyncer() {
84
+ const sdk = {
85
+ core: { misc: { lighter: { createAuthToken: vi.fn().mockResolvedValue({ token: "test-token" }) } } }
86
+ };
87
+ const config = {
88
+ sdk,
89
+ connector: "lighter",
90
+ account: { exchangeIdentifier: String(ACCOUNT_INDEX) },
91
+ credentials: { api_key_index: 0, api_private_key: "test-key" },
92
+ ws: {},
93
+ demo: false
94
+ };
95
+ return new LighterAccountSyncer(config);
96
+ }
97
+ async function listFills() {
98
+ const syncer = createSyncer();
99
+ const params = {
100
+ since: /* @__PURE__ */ new Date(0),
101
+ instruments: [{ id: "1" }],
102
+ openPositions: [],
103
+ provisionalFills: []
104
+ };
105
+ return syncer.listExternalFills(params);
106
+ }
107
+ const byId = (fills, id) => fills.find((fill) => fill.exchangeFillId === id);
108
+ describe("LighterAccountSyncer fee mapping", () => {
109
+ beforeEach(() => {
110
+ vi.mocked(fetchTradesSince).mockResolvedValue(TRADES);
111
+ });
112
+ it("computes the integrator (builder) fee as the taker ppm rate times the USD notional", async () => {
113
+ const fills = await listFills();
114
+ expect(byId(fills, "23432371018")).toMatchObject({
115
+ symbol: "1",
116
+ side: "BUY",
117
+ tradeFee: "0",
118
+ builderFee: "0.0098559538"
119
+ });
120
+ });
121
+ it("computes the trade fee as the base taker ppm rate times the USD notional", async () => {
122
+ const fills = await listFills();
123
+ expect(byId(fills, "22207283246")).toMatchObject({
124
+ symbol: "1",
125
+ side: "SELL",
126
+ tradeFee: "0.0033309052",
127
+ builderFee: "0"
128
+ });
129
+ });
130
+ it("emits zero fees when the trade carries no fee fields", async () => {
131
+ const fills = await listFills();
132
+ expect(byId(fills, "23432472386")).toMatchObject({
133
+ symbol: "1",
134
+ side: "BUY",
135
+ tradeFee: "0",
136
+ builderFee: "0"
137
+ });
138
+ });
139
+ it("reads the maker-side fee fields when the account is the maker", async () => {
140
+ const fills = await listFills();
141
+ expect(byId(fills, "90000000001")).toMatchObject({
142
+ symbol: "1",
143
+ side: "SELL",
144
+ tradeFee: "0.01",
145
+ builderFee: "0.005"
146
+ });
147
+ });
148
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pear-protocol/exchanges-sdk",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Pear Protocol Exchanges SDK",
5
5
  "private": false,
6
6
  "type": "module",
@@ -22,6 +22,7 @@
22
22
  "scripts": {
23
23
  "build": "tsup",
24
24
  "clean": "rm -rf dist .turbo",
25
+ "test": "vitest run",
25
26
  "typecheck": "tsc --noEmit"
26
27
  },
27
28
  "dependencies": {
@@ -35,6 +36,7 @@
35
36
  "devDependencies": {
36
37
  "@backend/typescript-config": "workspace:*",
37
38
  "tsup": "8.5.1",
38
- "typescript": "5.9.3"
39
+ "typescript": "5.9.3",
40
+ "vitest": "4.1.2"
39
41
  }
40
42
  }