@pear-protocol/core-sdk 1.4.0 → 1.6.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.
@@ -362,6 +362,7 @@ declare class Core {
362
362
  side: "BUY" | "SELL";
363
363
  reduceOnly: boolean;
364
364
  isExternal: boolean;
365
+ synthetic: boolean;
365
366
  quantity: string;
366
367
  price: string;
367
368
  usd: string;
@@ -389,6 +390,7 @@ declare class Core {
389
390
  side: "BUY" | "SELL";
390
391
  reduceOnly: boolean;
391
392
  isExternal: boolean;
393
+ synthetic: boolean;
392
394
  quantity: string;
393
395
  price: string;
394
396
  usd: string;
@@ -532,6 +534,7 @@ declare class Core {
532
534
  side: "BUY" | "SELL";
533
535
  reduceOnly: boolean;
534
536
  isExternal: boolean;
537
+ synthetic: boolean;
535
538
  quantity: string;
536
539
  price: string;
537
540
  usd: string;
@@ -664,6 +667,7 @@ declare class Core {
664
667
  side: "BUY" | "SELL";
665
668
  reduceOnly: boolean;
666
669
  isExternal: boolean;
670
+ synthetic: boolean;
667
671
  quantity: string;
668
672
  price: string;
669
673
  usd: string;
@@ -829,6 +833,7 @@ declare class Core {
829
833
  side: "BUY" | "SELL";
830
834
  reduceOnly: boolean;
831
835
  isExternal: boolean;
836
+ synthetic: boolean;
832
837
  quantity: string;
833
838
  price: string;
834
839
  usd: string;
@@ -963,6 +968,7 @@ declare class Core {
963
968
  side: "BUY" | "SELL";
964
969
  reduceOnly: boolean;
965
970
  isExternal: boolean;
971
+ synthetic: boolean;
966
972
  quantity: string;
967
973
  price: string;
968
974
  usd: string;
@@ -1095,6 +1101,7 @@ declare class Core {
1095
1101
  side: "BUY" | "SELL";
1096
1102
  reduceOnly: boolean;
1097
1103
  isExternal: boolean;
1104
+ synthetic: boolean;
1098
1105
  quantity: string;
1099
1106
  price: string;
1100
1107
  usd: string;
@@ -1260,6 +1267,7 @@ declare class Core {
1260
1267
  side: "BUY" | "SELL";
1261
1268
  reduceOnly: boolean;
1262
1269
  isExternal: boolean;
1270
+ synthetic: boolean;
1263
1271
  quantity: string;
1264
1272
  price: string;
1265
1273
  usd: string;
@@ -1394,6 +1402,7 @@ declare class Core {
1394
1402
  side: "BUY" | "SELL";
1395
1403
  reduceOnly: boolean;
1396
1404
  isExternal: boolean;
1405
+ synthetic: boolean;
1397
1406
  quantity: string;
1398
1407
  price: string;
1399
1408
  usd: string;
@@ -1526,6 +1535,7 @@ declare class Core {
1526
1535
  side: "BUY" | "SELL";
1527
1536
  reduceOnly: boolean;
1528
1537
  isExternal: boolean;
1538
+ synthetic: boolean;
1529
1539
  quantity: string;
1530
1540
  price: string;
1531
1541
  usd: string;
@@ -1691,6 +1701,7 @@ declare class Core {
1691
1701
  side: "BUY" | "SELL";
1692
1702
  reduceOnly: boolean;
1693
1703
  isExternal: boolean;
1704
+ synthetic: boolean;
1694
1705
  quantity: string;
1695
1706
  price: string;
1696
1707
  usd: string;
@@ -1811,26 +1822,50 @@ declare class Core {
1811
1822
  list: (query?: HttpTypes.NotificationListParamsInput, headers?: ClientHeaders) => Promise<{
1812
1823
  notifications: {
1813
1824
  id: string;
1814
- category: "TAKE_PROFIT_HIT" | "STOP_LOSS_HIT" | "TRIGGER_ORDER_HIT";
1825
+ category: "TAKE_PROFIT_HIT" | "STOP_LOSS_HIT" | "TRIGGER_ORDER_HIT" | "TAKE_PROFIT_FAILED" | "STOP_LOSS_FAILED" | "TRIGGER_ORDER_FAILED" | "ATTACHED_TRIGGER_DISCARDED";
1815
1826
  parameters: {
1816
1827
  type: "take_profit";
1817
1828
  triggerId: string;
1818
1829
  positionId: string;
1819
1830
  connector: "hyperliquid" | "binance" | "bybit" | "okx" | "lighter";
1831
+ metric?: "price" | "ratio" | "weighted_ratio" | "funding_rate" | "prediction_market" | "btc_dominance" | "notional" | "upnl" | "upnl_bps" | "ratio_trailing" | "price_trailing" | "weighted_ratio_trailing" | "notional_trailing" | "upnl_trailing" | "upnl_bps_trailing" | undefined;
1832
+ level?: number | undefined;
1833
+ reason?: string | undefined;
1820
1834
  } | {
1821
1835
  type: "stop_loss";
1822
1836
  triggerId: string;
1823
1837
  positionId: string;
1824
1838
  connector: "hyperliquid" | "binance" | "bybit" | "okx" | "lighter";
1839
+ metric?: "price" | "ratio" | "weighted_ratio" | "funding_rate" | "prediction_market" | "btc_dominance" | "notional" | "upnl" | "upnl_bps" | "ratio_trailing" | "price_trailing" | "weighted_ratio_trailing" | "notional_trailing" | "upnl_trailing" | "upnl_bps_trailing" | undefined;
1840
+ level?: number | undefined;
1841
+ reason?: string | undefined;
1825
1842
  } | {
1826
1843
  type: "trigger_order";
1827
1844
  triggerId: string;
1828
1845
  positionId: string;
1829
1846
  connector: "hyperliquid" | "binance" | "bybit" | "okx" | "lighter";
1847
+ metric?: "price" | "ratio" | "weighted_ratio" | "funding_rate" | "prediction_market" | "btc_dominance" | "notional" | "upnl" | "upnl_bps" | "ratio_trailing" | "price_trailing" | "weighted_ratio_trailing" | "notional_trailing" | "upnl_trailing" | "upnl_bps_trailing" | undefined;
1848
+ level?: number | undefined;
1849
+ reason?: string | undefined;
1830
1850
  } | {
1831
1851
  type: "default_trigger";
1832
1852
  triggerId: string;
1833
1853
  connector: "hyperliquid" | "binance" | "bybit" | "okx" | "lighter";
1854
+ metric?: "price" | "ratio" | "weighted_ratio" | "funding_rate" | "prediction_market" | "btc_dominance" | "notional" | "upnl" | "upnl_bps" | "ratio_trailing" | "price_trailing" | "weighted_ratio_trailing" | "notional_trailing" | "upnl_trailing" | "upnl_bps_trailing" | undefined;
1855
+ level?: number | undefined;
1856
+ reason?: string | undefined;
1857
+ } | {
1858
+ type: "attached_trigger_discarded";
1859
+ positionId: string;
1860
+ positionKey: string;
1861
+ connector: "hyperliquid" | "binance" | "bybit" | "okx" | "lighter";
1862
+ discarded: {
1863
+ bracketType: "STOP_LOSS" | "TAKE_PROFIT";
1864
+ metric: "price" | "ratio" | "weighted_ratio" | "funding_rate" | "prediction_market" | "btc_dominance" | "notional" | "upnl" | "upnl_bps" | "ratio_trailing" | "price_trailing" | "weighted_ratio_trailing" | "notional_trailing" | "upnl_trailing" | "upnl_bps_trailing";
1865
+ reason: "TRIGGER_LIMIT" | "TRAILING_ALREADY_EXISTS";
1866
+ level?: number | undefined;
1867
+ }[];
1868
+ activeTriggerLimit: number;
1834
1869
  };
1835
1870
  isRead: boolean;
1836
1871
  createdAt: string;
@@ -1857,12 +1892,16 @@ declare class Core {
1857
1892
  };
1858
1893
  positions: {
1859
1894
  list: (query: HttpTypes.PositionListParamsInput, headers?: ClientHeaders) => Promise<{
1895
+ limit: number;
1896
+ nextCursor: string | undefined;
1860
1897
  positions: {
1861
1898
  id: string;
1862
1899
  tradeAccountId: string;
1863
1900
  connector: "hyperliquid" | "binance" | "bybit" | "okx" | "lighter";
1864
1901
  status: "OPEN" | "CLOSED";
1902
+ origin: "PARTIAL" | "PEAR" | "EXTERNAL";
1865
1903
  exposure: Record<string, `-${string}` | `+${string}`>;
1904
+ totalFundingPayment: Record<string, `-${string}` | `+${string}`>;
1866
1905
  initialExposureUSD: Record<string, `-${string}` | `+${string}`>;
1867
1906
  createdAt: string;
1868
1907
  updatedAt: string;
@@ -1874,8 +1913,9 @@ declare class Core {
1874
1913
  positions: {
1875
1914
  id: string;
1876
1915
  createdAt: string;
1877
- exposure: Record<string, string>;
1916
+ exposure: Record<string, `-${string}` | `+${string}`>;
1878
1917
  status: "OPEN";
1918
+ origin: "PARTIAL" | "PEAR" | "EXTERNAL";
1879
1919
  connector: "hyperliquid" | "binance" | "bybit" | "okx" | "lighter";
1880
1920
  }[];
1881
1921
  }>;
@@ -2058,14 +2098,77 @@ declare class Core {
2058
2098
  }>;
2059
2099
  };
2060
2100
  rebalance: {
2061
- manually: (positionId: string, body: HttpTypes.RebalanceManually, headers?: ClientHeaders) => Promise<{
2101
+ manual: (positionId: string, body: HttpTypes.RebalanceManual, headers?: ClientHeaders) => Promise<{
2062
2102
  plan: {
2063
2103
  outcome: "full_rebalance";
2064
2104
  } | {
2065
- outcome: "partial_rebalance" | "no_rebalance";
2105
+ outcome: "partial_rebalance";
2106
+ messages: [string, ...string[]];
2107
+ } | {
2108
+ outcome: "no_rebalance";
2109
+ messages: [string, ...string[]];
2110
+ } | {
2111
+ outcome: "failed";
2066
2112
  messages: [string, ...string[]];
2067
2113
  };
2068
2114
  }>;
2115
+ auto: {
2116
+ list: (query?: HttpTypes.AutoRebalanceListParamsInput, headers?: ClientHeaders) => Promise<{
2117
+ limit: number;
2118
+ nextCursor: string | undefined;
2119
+ items: {
2120
+ id: string;
2121
+ positionId: string;
2122
+ status: "ACTIVE" | "PAUSED";
2123
+ weightSource: "INITIAL_WEIGHT" | "LIVE_BETA";
2124
+ minimumDriftPct: string;
2125
+ createdAt: string;
2126
+ updatedAt: string;
2127
+ timeframe?: "1d" | "1h" | "4h" | undefined;
2128
+ }[];
2129
+ }>;
2130
+ create: (body: HttpTypes.CreateAutoRebalanceConfig, headers?: ClientHeaders) => Promise<{
2131
+ config: {
2132
+ id: string;
2133
+ positionId: string;
2134
+ status: "ACTIVE" | "PAUSED";
2135
+ weightSource: "INITIAL_WEIGHT" | "LIVE_BETA";
2136
+ minimumDriftPct: string;
2137
+ createdAt: string;
2138
+ updatedAt: string;
2139
+ timeframe?: "1d" | "1h" | "4h" | undefined;
2140
+ };
2141
+ }>;
2142
+ update: (id: string, body: HttpTypes.UpdateAutoRebalanceConfig, headers?: ClientHeaders) => Promise<{
2143
+ config: {
2144
+ id: string;
2145
+ positionId: string;
2146
+ status: "ACTIVE" | "PAUSED";
2147
+ weightSource: "INITIAL_WEIGHT" | "LIVE_BETA";
2148
+ minimumDriftPct: string;
2149
+ createdAt: string;
2150
+ updatedAt: string;
2151
+ timeframe?: "1d" | "1h" | "4h" | undefined;
2152
+ };
2153
+ }>;
2154
+ cancel: (id: string, headers?: ClientHeaders) => Promise<void>;
2155
+ };
2156
+ listEvents: (positionId: string, query?: HttpTypes.RebalanceEventListParamsInput, headers?: ClientHeaders) => Promise<{
2157
+ limit: number;
2158
+ nextCursor: string | undefined;
2159
+ items: {
2160
+ id: string;
2161
+ autoRebalanceConfigId: string;
2162
+ positionId: string;
2163
+ weightSource: "INITIAL_WEIGHT" | "LIVE_BETA";
2164
+ driftPct: string;
2165
+ currentWeights: Record<string, number>;
2166
+ targetWeights: Record<string, number>;
2167
+ status: "FAILED" | "SUCCESS";
2168
+ createdAt: string;
2169
+ error?: string | undefined;
2170
+ }[];
2171
+ }>;
2069
2172
  };
2070
2173
  savedBaskets: {
2071
2174
  list: (headers?: ClientHeaders) => Promise<{
@@ -2159,6 +2262,7 @@ declare class Core {
2159
2262
  side: "BUY" | "SELL";
2160
2263
  reduceOnly: boolean;
2161
2264
  isExternal: boolean;
2265
+ synthetic: boolean;
2162
2266
  quantity: string;
2163
2267
  price: string;
2164
2268
  usd: string;
@@ -2204,6 +2308,7 @@ declare class Core {
2204
2308
  side: "BUY" | "SELL";
2205
2309
  reduceOnly: boolean;
2206
2310
  isExternal: boolean;
2311
+ synthetic: boolean;
2207
2312
  quantity: string;
2208
2313
  price: string;
2209
2314
  usd: string;
@@ -2254,6 +2359,7 @@ declare class Core {
2254
2359
  side: "BUY" | "SELL";
2255
2360
  reduceOnly: boolean;
2256
2361
  isExternal: boolean;
2362
+ synthetic: boolean;
2257
2363
  quantity: string;
2258
2364
  price: string;
2259
2365
  usd: string;
@@ -2299,6 +2405,7 @@ declare class Core {
2299
2405
  side: "BUY" | "SELL";
2300
2406
  reduceOnly: boolean;
2301
2407
  isExternal: boolean;
2408
+ synthetic: boolean;
2302
2409
  quantity: string;
2303
2410
  price: string;
2304
2411
  usd: string;
@@ -2322,15 +2429,24 @@ declare class Core {
2322
2429
  }>;
2323
2430
  };
2324
2431
  sync: {
2325
- since: (body: HttpTypes.SyncSinceRequest, headers?: ClientHeaders) => Promise<{
2326
- since: string;
2327
- sinceMs: number;
2328
- safeTs?: number | undefined;
2329
- confirmedExchangeFillIds?: string[] | undefined;
2330
- }>;
2331
- confirm: (body: HttpTypes.ConfirmFillsRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
2332
- reconcile: (body: HttpTypes.ReconcileFillsRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
2333
- create: (body: HttpTypes.CreateFillsRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
2432
+ fills: {
2433
+ since: (body: HttpTypes.SyncFillsSinceRequest, headers?: ClientHeaders) => Promise<{
2434
+ sinceMs: number;
2435
+ neverSynced: boolean;
2436
+ confirmedExchangeFillIds?: string[] | undefined;
2437
+ }>;
2438
+ reconcile: (body: HttpTypes.ReconcileFillsRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
2439
+ confirm: (body: HttpTypes.ConfirmFillsRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
2440
+ create: (body: HttpTypes.CreateApplicablesRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
2441
+ createBulk: (body: HttpTypes.CreateApplicablesRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
2442
+ };
2443
+ funding: {
2444
+ since: (body: HttpTypes.SyncFundingSinceRequest, headers?: ClientHeaders) => Promise<{
2445
+ sinceMs: number;
2446
+ }>;
2447
+ create: (body: HttpTypes.CreateFundingPaymentsRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
2448
+ createBulk: (body: HttpTypes.CreateFundingPaymentsRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
2449
+ };
2334
2450
  };
2335
2451
  trade: {
2336
2452
  open: (body: HttpTypes.CreateTradeOpen, headers?: ClientHeaders) => Promise<{
@@ -3160,6 +3276,7 @@ declare class Core {
3160
3276
  side: "BUY" | "SELL";
3161
3277
  reduceOnly: boolean;
3162
3278
  isExternal: boolean;
3279
+ synthetic: boolean;
3163
3280
  quantity: string;
3164
3281
  price: string;
3165
3282
  usd: string;
@@ -3292,6 +3409,7 @@ declare class Core {
3292
3409
  side: "BUY" | "SELL";
3293
3410
  reduceOnly: boolean;
3294
3411
  isExternal: boolean;
3412
+ synthetic: boolean;
3295
3413
  quantity: string;
3296
3414
  price: string;
3297
3415
  usd: string;
@@ -3457,6 +3575,7 @@ declare class Core {
3457
3575
  side: "BUY" | "SELL";
3458
3576
  reduceOnly: boolean;
3459
3577
  isExternal: boolean;
3578
+ synthetic: boolean;
3460
3579
  quantity: string;
3461
3580
  price: string;
3462
3581
  usd: string;
@@ -3577,6 +3696,7 @@ declare class Core {
3577
3696
  side: "BUY" | "SELL";
3578
3697
  reduceOnly: boolean;
3579
3698
  isExternal: boolean;
3699
+ synthetic: boolean;
3580
3700
  quantity: string;
3581
3701
  price: string;
3582
3702
  usd: string;
@@ -3709,6 +3829,7 @@ declare class Core {
3709
3829
  side: "BUY" | "SELL";
3710
3830
  reduceOnly: boolean;
3711
3831
  isExternal: boolean;
3832
+ synthetic: boolean;
3712
3833
  quantity: string;
3713
3834
  price: string;
3714
3835
  usd: string;
@@ -3874,6 +3995,7 @@ declare class Core {
3874
3995
  side: "BUY" | "SELL";
3875
3996
  reduceOnly: boolean;
3876
3997
  isExternal: boolean;
3998
+ synthetic: boolean;
3877
3999
  quantity: string;
3878
4000
  price: string;
3879
4001
  usd: string;
@@ -3994,6 +4116,7 @@ declare class Core {
3994
4116
  side: "BUY" | "SELL";
3995
4117
  reduceOnly: boolean;
3996
4118
  isExternal: boolean;
4119
+ synthetic: boolean;
3997
4120
  quantity: string;
3998
4121
  price: string;
3999
4122
  usd: string;
@@ -4126,6 +4249,7 @@ declare class Core {
4126
4249
  side: "BUY" | "SELL";
4127
4250
  reduceOnly: boolean;
4128
4251
  isExternal: boolean;
4252
+ synthetic: boolean;
4129
4253
  quantity: string;
4130
4254
  price: string;
4131
4255
  usd: string;
@@ -4291,6 +4415,7 @@ declare class Core {
4291
4415
  side: "BUY" | "SELL";
4292
4416
  reduceOnly: boolean;
4293
4417
  isExternal: boolean;
4418
+ synthetic: boolean;
4294
4419
  quantity: string;
4295
4420
  price: string;
4296
4421
  usd: string;
@@ -4386,6 +4511,14 @@ declare class Core {
4386
4511
  openPositions: number;
4387
4512
  }[];
4388
4513
  }>;
4514
+ totals: (query: HttpTypes.StatisticsTotalsQueryParamsInput, headers?: ClientHeaders) => Promise<{
4515
+ data: {
4516
+ totalVolume: number;
4517
+ totalFees: number;
4518
+ totalTrades: number;
4519
+ totalUsers: number;
4520
+ };
4521
+ }>;
4389
4522
  };
4390
4523
  tca: {
4391
4524
  execution: (executionId: string, headers?: ClientHeaders) => Promise<{
@@ -1,3 +1,5 @@
1
+ import { gzipJson } from '../utils';
2
+
1
3
  class Core {
2
4
  client;
3
5
  clientId;
@@ -245,12 +247,48 @@ class Core {
245
247
  }
246
248
  };
247
249
  rebalance = {
248
- manually: async (positionId, body, headers) => {
249
- return await this.client.fetch(`/rebalance/manually/${positionId}`, {
250
+ manual: async (positionId, body, headers) => {
251
+ return await this.client.fetch(`/rebalance/manual/${positionId}`, {
250
252
  method: "POST",
251
253
  headers,
252
254
  body: this.withClientId(body)
253
255
  });
256
+ },
257
+ auto: {
258
+ list: async (query, headers) => {
259
+ return await this.client.fetch(`/rebalance/auto`, {
260
+ method: "GET",
261
+ headers,
262
+ query
263
+ });
264
+ },
265
+ create: async (body, headers) => {
266
+ return await this.client.fetch(`/rebalance/auto`, {
267
+ method: "POST",
268
+ headers,
269
+ body
270
+ });
271
+ },
272
+ update: async (id, body, headers) => {
273
+ return await this.client.fetch(`/rebalance/auto/${id}`, {
274
+ method: "PATCH",
275
+ headers,
276
+ body
277
+ });
278
+ },
279
+ cancel: async (id, headers) => {
280
+ return await this.client.fetch(`/rebalance/auto/${id}/cancel`, {
281
+ method: "POST",
282
+ headers
283
+ });
284
+ }
285
+ },
286
+ listEvents: async (positionId, query, headers) => {
287
+ return await this.client.fetch(`/rebalance/${positionId}/events`, {
288
+ method: "GET",
289
+ headers,
290
+ query
291
+ });
254
292
  }
255
293
  };
256
294
  savedBaskets = {
@@ -310,33 +348,71 @@ class Core {
310
348
  }
311
349
  };
312
350
  sync = {
313
- since: async (body, headers) => {
314
- return await this.client.fetch(`/sync/since`, {
315
- method: "POST",
316
- headers,
317
- body
318
- });
319
- },
320
- confirm: async (body, headers) => {
321
- return await this.client.fetch(`/sync/confirm`, {
322
- method: "POST",
323
- headers,
324
- body
325
- });
326
- },
327
- reconcile: async (body, headers) => {
328
- return await this.client.fetch(`/sync/reconcile`, {
329
- method: "POST",
330
- headers,
331
- body
332
- });
351
+ fills: {
352
+ since: async (body, headers) => {
353
+ return await this.client.fetch(`/sync/fills/since`, {
354
+ method: "POST",
355
+ headers,
356
+ body
357
+ });
358
+ },
359
+ reconcile: async (body, headers) => {
360
+ return await this.client.fetch(`/sync/fills/reconcile`, {
361
+ method: "POST",
362
+ headers,
363
+ body
364
+ });
365
+ },
366
+ confirm: async (body, headers) => {
367
+ return await this.client.fetch(`/sync/fills/confirm`, {
368
+ method: "POST",
369
+ headers,
370
+ body
371
+ });
372
+ },
373
+ create: async (body, headers) => {
374
+ return await this.client.fetch(`/sync/fills/create`, {
375
+ method: "POST",
376
+ headers,
377
+ body
378
+ });
379
+ },
380
+ createBulk: async (body, headers) => {
381
+ const file = await gzipJson(body);
382
+ const formData = new FormData();
383
+ formData.append("file", file, "payload.json.gz");
384
+ return await this.client.fetch(`/sync/fills/create/bulk`, {
385
+ method: "POST",
386
+ headers: { ...headers, "content-type": null },
387
+ body: formData
388
+ });
389
+ }
333
390
  },
334
- create: async (body, headers) => {
335
- return await this.client.fetch(`/sync/create`, {
336
- method: "POST",
337
- headers,
338
- body
339
- });
391
+ funding: {
392
+ since: async (body, headers) => {
393
+ return await this.client.fetch(`/sync/funding/since`, {
394
+ method: "POST",
395
+ headers,
396
+ body
397
+ });
398
+ },
399
+ create: async (body, headers) => {
400
+ return await this.client.fetch(`/sync/funding/create`, {
401
+ method: "POST",
402
+ headers,
403
+ body
404
+ });
405
+ },
406
+ createBulk: async (body, headers) => {
407
+ const file = await gzipJson(body);
408
+ const formData = new FormData();
409
+ formData.append("file", file, "payload.json.gz");
410
+ return await this.client.fetch(`/sync/funding/create/bulk`, {
411
+ method: "POST",
412
+ headers: { ...headers, "content-type": null },
413
+ body: formData
414
+ });
415
+ }
340
416
  }
341
417
  };
342
418
  trade = {
@@ -499,6 +575,13 @@ class Core {
499
575
  headers,
500
576
  query
501
577
  });
578
+ },
579
+ totals: async (query, headers) => {
580
+ return await this.client.fetch(`/statistics/totals`, {
581
+ method: "GET",
582
+ headers,
583
+ query
584
+ });
502
585
  }
503
586
  };
504
587
  tca = {
package/dist/index.js CHANGED
@@ -7,6 +7,28 @@ import { HttpClient } from './http';
7
7
  import { FetchError } from './utils';
8
8
  export * from '@pear-protocol/utils';
9
9
 
10
+ const SESSION_REFRESH_PATH = "/auth/session/refresh";
11
+ const SESSION_REFRESH_TIMEOUT_MS = 15e3;
12
+ const isSessionRefreshRequest = (request) => {
13
+ try {
14
+ return new URL(request.url).pathname.includes(SESSION_REFRESH_PATH);
15
+ } catch {
16
+ return false;
17
+ }
18
+ };
19
+ const withTimeout = async (promise, ms, label) => {
20
+ let timer;
21
+ try {
22
+ return await Promise.race([
23
+ promise,
24
+ new Promise((_, reject) => {
25
+ timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
26
+ })
27
+ ]);
28
+ } finally {
29
+ clearTimeout(timer);
30
+ }
31
+ };
10
32
  class PearSDK {
11
33
  admin;
12
34
  auth;
@@ -31,7 +53,7 @@ class PearSDK {
31
53
  const refreshSession = async () => {
32
54
  if (!refreshInFlight) {
33
55
  logger.info("[session-refresh] Refreshing session...");
34
- refreshInFlight = this.auth.refreshSession().then(() => logger.info("[session-refresh] Session refreshed successfully")).catch((err) => {
56
+ refreshInFlight = withTimeout(this.auth.refreshSession(), SESSION_REFRESH_TIMEOUT_MS, "Session refresh").then(() => logger.info("[session-refresh] Session refreshed successfully")).catch((err) => {
35
57
  logger.error("[session-refresh] Refresh failed:", String(err));
36
58
  throw err;
37
59
  }).finally(() => {
@@ -42,18 +64,25 @@ class PearSDK {
42
64
  }
43
65
  return refreshInFlight;
44
66
  };
67
+ const expire = (reason) => {
68
+ logger.error(`[session-refresh] ${reason}`);
69
+ config.onSessionExpired?.();
70
+ throw new FetchError("Session expired. Please log in again.", "Unauthorized", 401);
71
+ };
72
+ const alreadyRetried = /* @__PURE__ */ new WeakSet();
45
73
  const sessionRefreshMiddleware = async (response, retry, request) => {
46
74
  if (response.status !== 401) return response;
75
+ if (isSessionRefreshRequest(request)) return response;
47
76
  logger.warn(`[session-refresh] 401 received for ${request.method} ${request.url}`);
77
+ if (alreadyRetried.has(request)) return expire("Still 401 after refresh, giving up");
48
78
  try {
49
79
  await refreshSession();
50
- logger.info(`[session-refresh] Retrying ${request.method} ${request.url}`);
51
- return retry();
52
80
  } catch {
53
- logger.error("[session-refresh] Session expired, cannot recover");
54
- config.onSessionExpired?.();
55
- throw new FetchError("Session expired. Please log in again.", "Unauthorized", 401);
81
+ return expire("Session expired, cannot recover");
56
82
  }
83
+ alreadyRetried.add(request);
84
+ logger.info(`[session-refresh] Retrying ${request.method} ${request.url}`);
85
+ return retry();
57
86
  };
58
87
  client.useResponse(sessionRefreshMiddleware);
59
88
  }
@@ -0,0 +1,2 @@
1
+
2
+ export { }
@@ -0,0 +1,88 @@
1
+ import { describe, afterEach, vi, it, expect } from 'vitest';
2
+ import PearSDK from './index';
3
+
4
+ const json = (status, body = {}) => {
5
+ return new Response(JSON.stringify(body), {
6
+ status,
7
+ headers: { "content-type": "application/json" }
8
+ });
9
+ };
10
+ const silentLogger = {
11
+ error: () => {
12
+ },
13
+ warn: () => {
14
+ },
15
+ info: () => {
16
+ },
17
+ debug: () => {
18
+ }
19
+ };
20
+ const setup = (routes) => {
21
+ const calls = [];
22
+ const fetchMock = vi.fn(async (input) => {
23
+ const key = `${input.method} ${new URL(input.url).pathname}`;
24
+ calls.push(key);
25
+ const queue = routes[key];
26
+ if (!queue || queue.length === 0) {
27
+ throw new Error(`unexpected request: ${key}`);
28
+ }
29
+ const responder = queue.length > 1 ? queue.shift() : queue[0];
30
+ return responder();
31
+ });
32
+ vi.stubGlobal("fetch", fetchMock);
33
+ const onSessionExpired = vi.fn();
34
+ const sdk = new PearSDK({
35
+ url: "https://gateway.test",
36
+ auth: { type: "session" },
37
+ onSessionExpired,
38
+ logger: silentLogger
39
+ });
40
+ return { sdk, onSessionExpired, calls };
41
+ };
42
+ describe("session-refresh middleware", () => {
43
+ afterEach(() => {
44
+ vi.unstubAllGlobals();
45
+ vi.useRealTimers();
46
+ vi.restoreAllMocks();
47
+ });
48
+ it("rejects (never hangs) when the refresh endpoint itself 401s on a dead cookie", async () => {
49
+ const { sdk, onSessionExpired, calls } = setup({
50
+ "GET /positions": [() => json(401, { message: "unauthorized" })],
51
+ "POST /auth/session/refresh": [() => json(401, { message: "refresh failed" })]
52
+ });
53
+ await expect(sdk.core.positions.list({})).rejects.toMatchObject({ status: 401 });
54
+ expect(onSessionExpired).toHaveBeenCalledTimes(1);
55
+ expect(calls).toEqual(["GET /positions", "POST /auth/session/refresh"]);
56
+ });
57
+ it("gives up after a single retry when the request still 401s after a successful refresh", async () => {
58
+ const { sdk, onSessionExpired, calls } = setup({
59
+ "GET /positions": [() => json(401)],
60
+ "POST /auth/session/refresh": [() => json(200)]
61
+ });
62
+ await expect(sdk.core.positions.list({})).rejects.toMatchObject({ status: 401 });
63
+ expect(calls.filter((call) => call === "GET /positions")).toHaveLength(2);
64
+ expect(calls.filter((call) => call === "POST /auth/session/refresh")).toHaveLength(1);
65
+ expect(onSessionExpired).toHaveBeenCalledTimes(1);
66
+ });
67
+ it("transparently refreshes and retries the original request on a recoverable 401", async () => {
68
+ const { sdk, onSessionExpired, calls } = setup({
69
+ "GET /positions": [() => json(401), () => json(200, { positions: ["ok"] })],
70
+ "POST /auth/session/refresh": [() => json(200)]
71
+ });
72
+ await expect(sdk.core.positions.list({})).resolves.toMatchObject({ positions: ["ok"] });
73
+ expect(onSessionExpired).not.toHaveBeenCalled();
74
+ expect(calls).toEqual(["GET /positions", "POST /auth/session/refresh", "GET /positions"]);
75
+ });
76
+ it("caps a stalled refresh at the timeout instead of wedging the queue", async () => {
77
+ vi.useFakeTimers();
78
+ const { sdk, onSessionExpired } = setup({
79
+ "GET /positions": [() => json(401)],
80
+ "POST /auth/session/refresh": [() => new Promise(() => {
81
+ })]
82
+ });
83
+ const settled = expect(sdk.core.positions.list({})).rejects.toMatchObject({ status: 401 });
84
+ await vi.advanceTimersByTimeAsync(15e3);
85
+ await settled;
86
+ expect(onSessionExpired).toHaveBeenCalledTimes(1);
87
+ });
88
+ });
package/dist/types.d.ts CHANGED
@@ -111,6 +111,6 @@ type FetchArgs = Omit<RequestInit, 'headers' | 'body'> & {
111
111
  };
112
112
  type RequestMiddleware = (request: Request, next: () => Promise<Response>) => Promise<Response>;
113
113
  type ResponseMiddleware = (response: Response, retry: () => Promise<Response>, request: Request) => Promise<Response>;
114
- type ClientIdBody = HttpTypes.RebalanceManually | HttpTypes.CreateSchedule | HttpTypes.CreateTradeOpen | HttpTypes.CreateTradeAdjust | HttpTypes.CreateTradeClose | HttpTypes.CreateTradeCloseAll | HttpTypes.CreateTriggerOpen | HttpTypes.CreateTriggerClose;
114
+ type ClientIdBody = HttpTypes.RebalanceManual | HttpTypes.CreateSchedule | HttpTypes.CreateTradeOpen | HttpTypes.CreateTradeAdjust | HttpTypes.CreateTradeClose | HttpTypes.CreateTradeCloseAll | HttpTypes.CreateTriggerOpen | HttpTypes.CreateTriggerClose;
115
115
 
116
116
  export type { AuthMode, ClientHeaders, ClientIdBody, FetchArgs, FetchInput, FetchParams, Logger, RequestMiddleware, ResponseMiddleware, SDKConfig };
package/dist/utils.d.ts CHANGED
@@ -9,5 +9,6 @@ declare class FetchError extends Error {
9
9
  statusText: string | undefined;
10
10
  constructor(message: string, statusText?: string, status?: number);
11
11
  }
12
+ declare function gzipJson(value: unknown): Promise<Blob>;
12
13
 
13
- export { FetchError, getBaseUrl, normalizeRequest, normalizeResponseData };
14
+ export { FetchError, getBaseUrl, gzipJson, normalizeRequest, normalizeResponseData };
package/dist/utils.js CHANGED
@@ -44,5 +44,9 @@ class FetchError extends Error {
44
44
  this.status = status;
45
45
  }
46
46
  }
47
+ async function gzipJson(value) {
48
+ const stream = new Blob([JSON.stringify(value)]).stream().pipeThrough(new CompressionStream("gzip"));
49
+ return await new Response(stream).blob();
50
+ }
47
51
 
48
- export { FetchError, getBaseUrl, normalizeRequest, normalizeResponseData };
52
+ export { FetchError, getBaseUrl, gzipJson, normalizeRequest, normalizeResponseData };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pear-protocol/core-sdk",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "Pear Protocol Core SDK",
5
5
  "private": false,
6
6
  "type": "module",
@@ -22,17 +22,20 @@
22
22
  "scripts": {
23
23
  "build": "tsup",
24
24
  "clean": "rm -rf dist .turbo",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
25
27
  "typecheck": "tsc --noEmit"
26
28
  },
27
29
  "dependencies": {
28
- "@pear-protocol/types": "^1.4.0",
29
- "@pear-protocol/utils": "^0.0.23",
30
+ "@pear-protocol/types": "^1.8.0",
31
+ "@pear-protocol/utils": "^0.1.0",
30
32
  "qs": "^6.14.0"
31
33
  },
32
34
  "devDependencies": {
33
35
  "@backend/typescript-config": "workspace:*",
34
36
  "@types/qs": "^6.14.0",
35
37
  "tsup": "8.5.1",
36
- "typescript": "5.9.3"
38
+ "typescript": "5.9.3",
39
+ "vitest": "4.1.2"
37
40
  }
38
41
  }