@pear-protocol/core-sdk 1.4.0 → 1.5.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/dist/core/index.d.ts +111 -12
- package/dist/core/index.js +104 -28
- package/dist/index.js +35 -6
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.js +88 -0
- package/dist/types.d.ts +1 -1
- package/dist/utils.d.ts +2 -1
- package/dist/utils.js +5 -1
- package/package.json +7 -4
package/dist/core/index.d.ts
CHANGED
|
@@ -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;
|
|
@@ -1862,7 +1873,9 @@ declare class Core {
|
|
|
1862
1873
|
tradeAccountId: string;
|
|
1863
1874
|
connector: "hyperliquid" | "binance" | "bybit" | "okx" | "lighter";
|
|
1864
1875
|
status: "OPEN" | "CLOSED";
|
|
1876
|
+
origin: "PARTIAL" | "PEAR" | "EXTERNAL";
|
|
1865
1877
|
exposure: Record<string, `-${string}` | `+${string}`>;
|
|
1878
|
+
totalFundingPayment: Record<string, `-${string}` | `+${string}`>;
|
|
1866
1879
|
initialExposureUSD: Record<string, `-${string}` | `+${string}`>;
|
|
1867
1880
|
createdAt: string;
|
|
1868
1881
|
updatedAt: string;
|
|
@@ -1874,8 +1887,9 @@ declare class Core {
|
|
|
1874
1887
|
positions: {
|
|
1875
1888
|
id: string;
|
|
1876
1889
|
createdAt: string;
|
|
1877
|
-
exposure: Record<string, string
|
|
1890
|
+
exposure: Record<string, `-${string}` | `+${string}`>;
|
|
1878
1891
|
status: "OPEN";
|
|
1892
|
+
origin: "PARTIAL" | "PEAR" | "EXTERNAL";
|
|
1879
1893
|
connector: "hyperliquid" | "binance" | "bybit" | "okx" | "lighter";
|
|
1880
1894
|
}[];
|
|
1881
1895
|
}>;
|
|
@@ -2058,14 +2072,77 @@ declare class Core {
|
|
|
2058
2072
|
}>;
|
|
2059
2073
|
};
|
|
2060
2074
|
rebalance: {
|
|
2061
|
-
|
|
2075
|
+
manual: (positionId: string, body: HttpTypes.RebalanceManual, headers?: ClientHeaders) => Promise<{
|
|
2062
2076
|
plan: {
|
|
2063
2077
|
outcome: "full_rebalance";
|
|
2064
2078
|
} | {
|
|
2065
|
-
outcome: "partial_rebalance"
|
|
2079
|
+
outcome: "partial_rebalance";
|
|
2080
|
+
messages: [string, ...string[]];
|
|
2081
|
+
} | {
|
|
2082
|
+
outcome: "no_rebalance";
|
|
2083
|
+
messages: [string, ...string[]];
|
|
2084
|
+
} | {
|
|
2085
|
+
outcome: "failed";
|
|
2066
2086
|
messages: [string, ...string[]];
|
|
2067
2087
|
};
|
|
2068
2088
|
}>;
|
|
2089
|
+
auto: {
|
|
2090
|
+
list: (query?: HttpTypes.AutoRebalanceListParamsInput, headers?: ClientHeaders) => Promise<{
|
|
2091
|
+
limit: number;
|
|
2092
|
+
nextCursor: string | undefined;
|
|
2093
|
+
items: {
|
|
2094
|
+
id: string;
|
|
2095
|
+
positionId: string;
|
|
2096
|
+
status: "ACTIVE" | "PAUSED";
|
|
2097
|
+
weightSource: "INITIAL_WEIGHT" | "LIVE_BETA";
|
|
2098
|
+
minimumDriftPct: string;
|
|
2099
|
+
createdAt: string;
|
|
2100
|
+
updatedAt: string;
|
|
2101
|
+
timeframe?: "1d" | "1h" | "4h" | undefined;
|
|
2102
|
+
}[];
|
|
2103
|
+
}>;
|
|
2104
|
+
create: (body: HttpTypes.CreateAutoRebalanceConfig, headers?: ClientHeaders) => Promise<{
|
|
2105
|
+
config: {
|
|
2106
|
+
id: string;
|
|
2107
|
+
positionId: string;
|
|
2108
|
+
status: "ACTIVE" | "PAUSED";
|
|
2109
|
+
weightSource: "INITIAL_WEIGHT" | "LIVE_BETA";
|
|
2110
|
+
minimumDriftPct: string;
|
|
2111
|
+
createdAt: string;
|
|
2112
|
+
updatedAt: string;
|
|
2113
|
+
timeframe?: "1d" | "1h" | "4h" | undefined;
|
|
2114
|
+
};
|
|
2115
|
+
}>;
|
|
2116
|
+
update: (id: string, body: HttpTypes.UpdateAutoRebalanceConfig, headers?: ClientHeaders) => Promise<{
|
|
2117
|
+
config: {
|
|
2118
|
+
id: string;
|
|
2119
|
+
positionId: string;
|
|
2120
|
+
status: "ACTIVE" | "PAUSED";
|
|
2121
|
+
weightSource: "INITIAL_WEIGHT" | "LIVE_BETA";
|
|
2122
|
+
minimumDriftPct: string;
|
|
2123
|
+
createdAt: string;
|
|
2124
|
+
updatedAt: string;
|
|
2125
|
+
timeframe?: "1d" | "1h" | "4h" | undefined;
|
|
2126
|
+
};
|
|
2127
|
+
}>;
|
|
2128
|
+
cancel: (id: string, headers?: ClientHeaders) => Promise<void>;
|
|
2129
|
+
};
|
|
2130
|
+
listEvents: (positionId: string, query?: HttpTypes.RebalanceEventListParamsInput, headers?: ClientHeaders) => Promise<{
|
|
2131
|
+
limit: number;
|
|
2132
|
+
nextCursor: string | undefined;
|
|
2133
|
+
items: {
|
|
2134
|
+
id: string;
|
|
2135
|
+
autoRebalanceConfigId: string;
|
|
2136
|
+
positionId: string;
|
|
2137
|
+
weightSource: "INITIAL_WEIGHT" | "LIVE_BETA";
|
|
2138
|
+
driftPct: string;
|
|
2139
|
+
currentWeights: Record<string, number>;
|
|
2140
|
+
targetWeights: Record<string, number>;
|
|
2141
|
+
status: "FAILED" | "SUCCESS";
|
|
2142
|
+
createdAt: string;
|
|
2143
|
+
error?: string | undefined;
|
|
2144
|
+
}[];
|
|
2145
|
+
}>;
|
|
2069
2146
|
};
|
|
2070
2147
|
savedBaskets: {
|
|
2071
2148
|
list: (headers?: ClientHeaders) => Promise<{
|
|
@@ -2159,6 +2236,7 @@ declare class Core {
|
|
|
2159
2236
|
side: "BUY" | "SELL";
|
|
2160
2237
|
reduceOnly: boolean;
|
|
2161
2238
|
isExternal: boolean;
|
|
2239
|
+
synthetic: boolean;
|
|
2162
2240
|
quantity: string;
|
|
2163
2241
|
price: string;
|
|
2164
2242
|
usd: string;
|
|
@@ -2204,6 +2282,7 @@ declare class Core {
|
|
|
2204
2282
|
side: "BUY" | "SELL";
|
|
2205
2283
|
reduceOnly: boolean;
|
|
2206
2284
|
isExternal: boolean;
|
|
2285
|
+
synthetic: boolean;
|
|
2207
2286
|
quantity: string;
|
|
2208
2287
|
price: string;
|
|
2209
2288
|
usd: string;
|
|
@@ -2254,6 +2333,7 @@ declare class Core {
|
|
|
2254
2333
|
side: "BUY" | "SELL";
|
|
2255
2334
|
reduceOnly: boolean;
|
|
2256
2335
|
isExternal: boolean;
|
|
2336
|
+
synthetic: boolean;
|
|
2257
2337
|
quantity: string;
|
|
2258
2338
|
price: string;
|
|
2259
2339
|
usd: string;
|
|
@@ -2299,6 +2379,7 @@ declare class Core {
|
|
|
2299
2379
|
side: "BUY" | "SELL";
|
|
2300
2380
|
reduceOnly: boolean;
|
|
2301
2381
|
isExternal: boolean;
|
|
2382
|
+
synthetic: boolean;
|
|
2302
2383
|
quantity: string;
|
|
2303
2384
|
price: string;
|
|
2304
2385
|
usd: string;
|
|
@@ -2322,15 +2403,24 @@ declare class Core {
|
|
|
2322
2403
|
}>;
|
|
2323
2404
|
};
|
|
2324
2405
|
sync: {
|
|
2325
|
-
|
|
2326
|
-
since:
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2406
|
+
fills: {
|
|
2407
|
+
since: (body: HttpTypes.SyncFillsSinceRequest, headers?: ClientHeaders) => Promise<{
|
|
2408
|
+
sinceMs: number;
|
|
2409
|
+
neverSynced: boolean;
|
|
2410
|
+
confirmedExchangeFillIds?: string[] | undefined;
|
|
2411
|
+
}>;
|
|
2412
|
+
reconcile: (body: HttpTypes.ReconcileFillsRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
|
|
2413
|
+
confirm: (body: HttpTypes.ConfirmFillsRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
|
|
2414
|
+
create: (body: HttpTypes.CreateApplicablesRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
|
|
2415
|
+
createBulk: (body: HttpTypes.CreateApplicablesRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
|
|
2416
|
+
};
|
|
2417
|
+
funding: {
|
|
2418
|
+
since: (body: HttpTypes.SyncFundingSinceRequest, headers?: ClientHeaders) => Promise<{
|
|
2419
|
+
sinceMs: number;
|
|
2420
|
+
}>;
|
|
2421
|
+
create: (body: HttpTypes.CreateFundingPaymentsRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
|
|
2422
|
+
createBulk: (body: HttpTypes.CreateFundingPaymentsRequest, headers?: ClientHeaders) => Promise<Record<string, never>>;
|
|
2423
|
+
};
|
|
2334
2424
|
};
|
|
2335
2425
|
trade: {
|
|
2336
2426
|
open: (body: HttpTypes.CreateTradeOpen, headers?: ClientHeaders) => Promise<{
|
|
@@ -3160,6 +3250,7 @@ declare class Core {
|
|
|
3160
3250
|
side: "BUY" | "SELL";
|
|
3161
3251
|
reduceOnly: boolean;
|
|
3162
3252
|
isExternal: boolean;
|
|
3253
|
+
synthetic: boolean;
|
|
3163
3254
|
quantity: string;
|
|
3164
3255
|
price: string;
|
|
3165
3256
|
usd: string;
|
|
@@ -3292,6 +3383,7 @@ declare class Core {
|
|
|
3292
3383
|
side: "BUY" | "SELL";
|
|
3293
3384
|
reduceOnly: boolean;
|
|
3294
3385
|
isExternal: boolean;
|
|
3386
|
+
synthetic: boolean;
|
|
3295
3387
|
quantity: string;
|
|
3296
3388
|
price: string;
|
|
3297
3389
|
usd: string;
|
|
@@ -3457,6 +3549,7 @@ declare class Core {
|
|
|
3457
3549
|
side: "BUY" | "SELL";
|
|
3458
3550
|
reduceOnly: boolean;
|
|
3459
3551
|
isExternal: boolean;
|
|
3552
|
+
synthetic: boolean;
|
|
3460
3553
|
quantity: string;
|
|
3461
3554
|
price: string;
|
|
3462
3555
|
usd: string;
|
|
@@ -3577,6 +3670,7 @@ declare class Core {
|
|
|
3577
3670
|
side: "BUY" | "SELL";
|
|
3578
3671
|
reduceOnly: boolean;
|
|
3579
3672
|
isExternal: boolean;
|
|
3673
|
+
synthetic: boolean;
|
|
3580
3674
|
quantity: string;
|
|
3581
3675
|
price: string;
|
|
3582
3676
|
usd: string;
|
|
@@ -3709,6 +3803,7 @@ declare class Core {
|
|
|
3709
3803
|
side: "BUY" | "SELL";
|
|
3710
3804
|
reduceOnly: boolean;
|
|
3711
3805
|
isExternal: boolean;
|
|
3806
|
+
synthetic: boolean;
|
|
3712
3807
|
quantity: string;
|
|
3713
3808
|
price: string;
|
|
3714
3809
|
usd: string;
|
|
@@ -3874,6 +3969,7 @@ declare class Core {
|
|
|
3874
3969
|
side: "BUY" | "SELL";
|
|
3875
3970
|
reduceOnly: boolean;
|
|
3876
3971
|
isExternal: boolean;
|
|
3972
|
+
synthetic: boolean;
|
|
3877
3973
|
quantity: string;
|
|
3878
3974
|
price: string;
|
|
3879
3975
|
usd: string;
|
|
@@ -3994,6 +4090,7 @@ declare class Core {
|
|
|
3994
4090
|
side: "BUY" | "SELL";
|
|
3995
4091
|
reduceOnly: boolean;
|
|
3996
4092
|
isExternal: boolean;
|
|
4093
|
+
synthetic: boolean;
|
|
3997
4094
|
quantity: string;
|
|
3998
4095
|
price: string;
|
|
3999
4096
|
usd: string;
|
|
@@ -4126,6 +4223,7 @@ declare class Core {
|
|
|
4126
4223
|
side: "BUY" | "SELL";
|
|
4127
4224
|
reduceOnly: boolean;
|
|
4128
4225
|
isExternal: boolean;
|
|
4226
|
+
synthetic: boolean;
|
|
4129
4227
|
quantity: string;
|
|
4130
4228
|
price: string;
|
|
4131
4229
|
usd: string;
|
|
@@ -4291,6 +4389,7 @@ declare class Core {
|
|
|
4291
4389
|
side: "BUY" | "SELL";
|
|
4292
4390
|
reduceOnly: boolean;
|
|
4293
4391
|
isExternal: boolean;
|
|
4392
|
+
synthetic: boolean;
|
|
4294
4393
|
quantity: string;
|
|
4295
4394
|
price: string;
|
|
4296
4395
|
usd: string;
|
package/dist/core/index.js
CHANGED
|
@@ -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
|
-
|
|
249
|
-
return await this.client.fetch(`/rebalance/
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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 = {
|
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
|
-
|
|
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,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.
|
|
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.
|
|
3
|
+
"version": "1.5.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.
|
|
29
|
-
"@pear-protocol/utils": "^0.0
|
|
30
|
+
"@pear-protocol/types": "^1.6.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
|
}
|