@pear-protocol/core-sdk 1.3.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 +165 -12
- package/dist/core/index.js +132 -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,131 @@ 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
|
+
}>;
|
|
2146
|
+
};
|
|
2147
|
+
savedBaskets: {
|
|
2148
|
+
list: (headers?: ClientHeaders) => Promise<{
|
|
2149
|
+
baskets: {
|
|
2150
|
+
id: string;
|
|
2151
|
+
name: string;
|
|
2152
|
+
legs: [{
|
|
2153
|
+
symbol: string;
|
|
2154
|
+
side: "BUY" | "SELL";
|
|
2155
|
+
weight: number;
|
|
2156
|
+
}, ...{
|
|
2157
|
+
symbol: string;
|
|
2158
|
+
side: "BUY" | "SELL";
|
|
2159
|
+
weight: number;
|
|
2160
|
+
}[]];
|
|
2161
|
+
createdAt: string;
|
|
2162
|
+
updatedAt: string;
|
|
2163
|
+
}[];
|
|
2164
|
+
}>;
|
|
2165
|
+
create: (body: HttpTypes.CreateSavedBasketPayload, headers?: ClientHeaders) => Promise<{
|
|
2166
|
+
basket: {
|
|
2167
|
+
id: string;
|
|
2168
|
+
name: string;
|
|
2169
|
+
legs: [{
|
|
2170
|
+
symbol: string;
|
|
2171
|
+
side: "BUY" | "SELL";
|
|
2172
|
+
weight: number;
|
|
2173
|
+
}, ...{
|
|
2174
|
+
symbol: string;
|
|
2175
|
+
side: "BUY" | "SELL";
|
|
2176
|
+
weight: number;
|
|
2177
|
+
}[]];
|
|
2178
|
+
createdAt: string;
|
|
2179
|
+
updatedAt: string;
|
|
2180
|
+
};
|
|
2181
|
+
}>;
|
|
2182
|
+
update: (id: string, body: HttpTypes.UpdateSavedBasketPayload, headers?: ClientHeaders) => Promise<{
|
|
2183
|
+
basket: {
|
|
2184
|
+
id: string;
|
|
2185
|
+
name: string;
|
|
2186
|
+
legs: [{
|
|
2187
|
+
symbol: string;
|
|
2188
|
+
side: "BUY" | "SELL";
|
|
2189
|
+
weight: number;
|
|
2190
|
+
}, ...{
|
|
2191
|
+
symbol: string;
|
|
2192
|
+
side: "BUY" | "SELL";
|
|
2193
|
+
weight: number;
|
|
2194
|
+
}[]];
|
|
2195
|
+
createdAt: string;
|
|
2196
|
+
updatedAt: string;
|
|
2197
|
+
};
|
|
2198
|
+
}>;
|
|
2199
|
+
delete: (id: string, headers?: ClientHeaders) => Promise<void>;
|
|
2069
2200
|
};
|
|
2070
2201
|
schedules: {
|
|
2071
2202
|
list: (query: HttpTypes.ScheduleListParamsInput, headers?: ClientHeaders) => Promise<{
|
|
@@ -2105,6 +2236,7 @@ declare class Core {
|
|
|
2105
2236
|
side: "BUY" | "SELL";
|
|
2106
2237
|
reduceOnly: boolean;
|
|
2107
2238
|
isExternal: boolean;
|
|
2239
|
+
synthetic: boolean;
|
|
2108
2240
|
quantity: string;
|
|
2109
2241
|
price: string;
|
|
2110
2242
|
usd: string;
|
|
@@ -2150,6 +2282,7 @@ declare class Core {
|
|
|
2150
2282
|
side: "BUY" | "SELL";
|
|
2151
2283
|
reduceOnly: boolean;
|
|
2152
2284
|
isExternal: boolean;
|
|
2285
|
+
synthetic: boolean;
|
|
2153
2286
|
quantity: string;
|
|
2154
2287
|
price: string;
|
|
2155
2288
|
usd: string;
|
|
@@ -2200,6 +2333,7 @@ declare class Core {
|
|
|
2200
2333
|
side: "BUY" | "SELL";
|
|
2201
2334
|
reduceOnly: boolean;
|
|
2202
2335
|
isExternal: boolean;
|
|
2336
|
+
synthetic: boolean;
|
|
2203
2337
|
quantity: string;
|
|
2204
2338
|
price: string;
|
|
2205
2339
|
usd: string;
|
|
@@ -2245,6 +2379,7 @@ declare class Core {
|
|
|
2245
2379
|
side: "BUY" | "SELL";
|
|
2246
2380
|
reduceOnly: boolean;
|
|
2247
2381
|
isExternal: boolean;
|
|
2382
|
+
synthetic: boolean;
|
|
2248
2383
|
quantity: string;
|
|
2249
2384
|
price: string;
|
|
2250
2385
|
usd: string;
|
|
@@ -2268,15 +2403,24 @@ declare class Core {
|
|
|
2268
2403
|
}>;
|
|
2269
2404
|
};
|
|
2270
2405
|
sync: {
|
|
2271
|
-
|
|
2272
|
-
since:
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
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
|
+
};
|
|
2280
2424
|
};
|
|
2281
2425
|
trade: {
|
|
2282
2426
|
open: (body: HttpTypes.CreateTradeOpen, headers?: ClientHeaders) => Promise<{
|
|
@@ -3106,6 +3250,7 @@ declare class Core {
|
|
|
3106
3250
|
side: "BUY" | "SELL";
|
|
3107
3251
|
reduceOnly: boolean;
|
|
3108
3252
|
isExternal: boolean;
|
|
3253
|
+
synthetic: boolean;
|
|
3109
3254
|
quantity: string;
|
|
3110
3255
|
price: string;
|
|
3111
3256
|
usd: string;
|
|
@@ -3238,6 +3383,7 @@ declare class Core {
|
|
|
3238
3383
|
side: "BUY" | "SELL";
|
|
3239
3384
|
reduceOnly: boolean;
|
|
3240
3385
|
isExternal: boolean;
|
|
3386
|
+
synthetic: boolean;
|
|
3241
3387
|
quantity: string;
|
|
3242
3388
|
price: string;
|
|
3243
3389
|
usd: string;
|
|
@@ -3403,6 +3549,7 @@ declare class Core {
|
|
|
3403
3549
|
side: "BUY" | "SELL";
|
|
3404
3550
|
reduceOnly: boolean;
|
|
3405
3551
|
isExternal: boolean;
|
|
3552
|
+
synthetic: boolean;
|
|
3406
3553
|
quantity: string;
|
|
3407
3554
|
price: string;
|
|
3408
3555
|
usd: string;
|
|
@@ -3523,6 +3670,7 @@ declare class Core {
|
|
|
3523
3670
|
side: "BUY" | "SELL";
|
|
3524
3671
|
reduceOnly: boolean;
|
|
3525
3672
|
isExternal: boolean;
|
|
3673
|
+
synthetic: boolean;
|
|
3526
3674
|
quantity: string;
|
|
3527
3675
|
price: string;
|
|
3528
3676
|
usd: string;
|
|
@@ -3655,6 +3803,7 @@ declare class Core {
|
|
|
3655
3803
|
side: "BUY" | "SELL";
|
|
3656
3804
|
reduceOnly: boolean;
|
|
3657
3805
|
isExternal: boolean;
|
|
3806
|
+
synthetic: boolean;
|
|
3658
3807
|
quantity: string;
|
|
3659
3808
|
price: string;
|
|
3660
3809
|
usd: string;
|
|
@@ -3820,6 +3969,7 @@ declare class Core {
|
|
|
3820
3969
|
side: "BUY" | "SELL";
|
|
3821
3970
|
reduceOnly: boolean;
|
|
3822
3971
|
isExternal: boolean;
|
|
3972
|
+
synthetic: boolean;
|
|
3823
3973
|
quantity: string;
|
|
3824
3974
|
price: string;
|
|
3825
3975
|
usd: string;
|
|
@@ -3940,6 +4090,7 @@ declare class Core {
|
|
|
3940
4090
|
side: "BUY" | "SELL";
|
|
3941
4091
|
reduceOnly: boolean;
|
|
3942
4092
|
isExternal: boolean;
|
|
4093
|
+
synthetic: boolean;
|
|
3943
4094
|
quantity: string;
|
|
3944
4095
|
price: string;
|
|
3945
4096
|
usd: string;
|
|
@@ -4072,6 +4223,7 @@ declare class Core {
|
|
|
4072
4223
|
side: "BUY" | "SELL";
|
|
4073
4224
|
reduceOnly: boolean;
|
|
4074
4225
|
isExternal: boolean;
|
|
4226
|
+
synthetic: boolean;
|
|
4075
4227
|
quantity: string;
|
|
4076
4228
|
price: string;
|
|
4077
4229
|
usd: string;
|
|
@@ -4237,6 +4389,7 @@ declare class Core {
|
|
|
4237
4389
|
side: "BUY" | "SELL";
|
|
4238
4390
|
reduceOnly: boolean;
|
|
4239
4391
|
isExternal: boolean;
|
|
4392
|
+
synthetic: boolean;
|
|
4240
4393
|
quantity: string;
|
|
4241
4394
|
price: string;
|
|
4242
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,76 @@ 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
|
+
});
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
savedBaskets = {
|
|
295
|
+
list: async (headers) => {
|
|
296
|
+
return await this.client.fetch(`/saved-baskets`, {
|
|
297
|
+
method: "GET",
|
|
298
|
+
headers
|
|
299
|
+
});
|
|
300
|
+
},
|
|
301
|
+
create: async (body, headers) => {
|
|
302
|
+
return await this.client.fetch(`/saved-baskets`, {
|
|
303
|
+
method: "POST",
|
|
304
|
+
headers,
|
|
305
|
+
body
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
update: async (id, body, headers) => {
|
|
309
|
+
return await this.client.fetch(`/saved-baskets/${id}`, {
|
|
310
|
+
method: "PATCH",
|
|
311
|
+
headers,
|
|
312
|
+
body
|
|
313
|
+
});
|
|
314
|
+
},
|
|
315
|
+
delete: async (id, headers) => {
|
|
316
|
+
return await this.client.fetch(`/saved-baskets/${id}`, {
|
|
317
|
+
method: "DELETE",
|
|
318
|
+
headers
|
|
319
|
+
});
|
|
254
320
|
}
|
|
255
321
|
};
|
|
256
322
|
schedules = {
|
|
@@ -282,33 +348,71 @@ class Core {
|
|
|
282
348
|
}
|
|
283
349
|
};
|
|
284
350
|
sync = {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
+
}
|
|
305
390
|
},
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
+
}
|
|
312
416
|
}
|
|
313
417
|
};
|
|
314
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
|
}
|