@ostium/builder-sdk 0.1.0 → 0.2.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/README.md +73 -3
- package/dist/cli.js +526 -139
- package/dist/index.cjs +517 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +173 -106
- package/dist/index.d.ts +173 -106
- package/dist/index.js +517 -131
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -243,11 +243,20 @@ function getNetworkConfig(isTestnet = false) {
|
|
|
243
243
|
// src/config.ts
|
|
244
244
|
var DEFAULT_PIMLICO_URL = "https://builder.ostium.io/v1/pimlico/sponsor?chainId=42161";
|
|
245
245
|
var DEFAULT_PIMLICO_URL_TESTNET = "https://builder.ostium.io/v1/pimlico/sponsor?chainId=421614";
|
|
246
|
-
function
|
|
247
|
-
return
|
|
246
|
+
function hasPrivateKey(config) {
|
|
247
|
+
return typeof config.privateKey === "string" && config.privateKey.length > 0;
|
|
248
248
|
}
|
|
249
|
-
function
|
|
250
|
-
return
|
|
249
|
+
function resolveMode(config) {
|
|
250
|
+
if (config.mode) return config.mode;
|
|
251
|
+
if (config.privateKey) {
|
|
252
|
+
if (config.pimlicoUrl) return config.traderAddress ? "delegated-gasless" : "self-gasless";
|
|
253
|
+
return config.traderAddress ? "delegated-self" : "self-self";
|
|
254
|
+
}
|
|
255
|
+
if (config.safeAddress && config.delegateAddress && config.traderAddress) return "delegated-gasless";
|
|
256
|
+
if (config.safeAddress && config.traderAddress) return "self-gasless";
|
|
257
|
+
if (config.delegateAddress && config.traderAddress) return "delegated-self";
|
|
258
|
+
if (config.traderAddress) return "self-self";
|
|
259
|
+
throw new Error("Unable to infer client mode from config");
|
|
251
260
|
}
|
|
252
261
|
|
|
253
262
|
// src/errors.ts
|
|
@@ -2648,8 +2657,8 @@ var DelegatedSignerStrategy = class {
|
|
|
2648
2657
|
};
|
|
2649
2658
|
|
|
2650
2659
|
// src/signer/index.ts
|
|
2651
|
-
function createSigner(
|
|
2652
|
-
if (
|
|
2660
|
+
function createSigner(mode, traderAddress) {
|
|
2661
|
+
if (mode !== "self-self") {
|
|
2653
2662
|
return new DelegatedSignerStrategy(traderAddress);
|
|
2654
2663
|
}
|
|
2655
2664
|
return new SelfSignerStrategy(traderAddress);
|
|
@@ -2673,6 +2682,12 @@ var SelfSubmissionStrategy = class {
|
|
|
2673
2682
|
effectiveSender;
|
|
2674
2683
|
send;
|
|
2675
2684
|
constructor(config, testnet) {
|
|
2685
|
+
if (!config.privateKey) {
|
|
2686
|
+
throw new OstiumError(
|
|
2687
|
+
"privateKey is required for self-submission mode",
|
|
2688
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
2689
|
+
);
|
|
2690
|
+
}
|
|
2676
2691
|
if (!config.rpcUrl) {
|
|
2677
2692
|
throw new OstiumError(
|
|
2678
2693
|
"rpcUrl is required for self-submission mode",
|
|
@@ -2738,6 +2753,12 @@ var GaslessSubmissionStrategy = class _GaslessSubmissionStrategy {
|
|
|
2738
2753
|
this.effectiveSender = safeAccount.address;
|
|
2739
2754
|
}
|
|
2740
2755
|
static async create(config, testnet) {
|
|
2756
|
+
if (!config.privateKey) {
|
|
2757
|
+
throw new OstiumError(
|
|
2758
|
+
"privateKey is required for gasless submission mode",
|
|
2759
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
2760
|
+
);
|
|
2761
|
+
}
|
|
2741
2762
|
if (!config.pimlicoUrl) {
|
|
2742
2763
|
throw new OstiumError(
|
|
2743
2764
|
"pimlicoUrl is required for gasless mode",
|
|
@@ -2806,8 +2827,8 @@ var GaslessSubmissionStrategy = class _GaslessSubmissionStrategy {
|
|
|
2806
2827
|
};
|
|
2807
2828
|
|
|
2808
2829
|
// src/submitter/index.ts
|
|
2809
|
-
async function createSubmitter(config, testnet) {
|
|
2810
|
-
if (
|
|
2830
|
+
async function createSubmitter(config, testnet, mode) {
|
|
2831
|
+
if (mode === "self-gasless" || mode === "delegated-gasless") {
|
|
2811
2832
|
return GaslessSubmissionStrategy.create(config, testnet);
|
|
2812
2833
|
}
|
|
2813
2834
|
if (!config.rpcUrl) {
|
|
@@ -3199,10 +3220,11 @@ async function fetchLivePrices(builderApiUrl) {
|
|
|
3199
3220
|
}
|
|
3200
3221
|
return result;
|
|
3201
3222
|
}
|
|
3202
|
-
function rawTickToPublic(item) {
|
|
3223
|
+
function rawTickToPublic(item, pairId) {
|
|
3203
3224
|
const from = normalizePairName(item.from);
|
|
3204
3225
|
const to = normalizePairName(item.to);
|
|
3205
3226
|
return {
|
|
3227
|
+
pairId,
|
|
3206
3228
|
feedId: item.feed_id,
|
|
3207
3229
|
pair: `${from}-${to}`,
|
|
3208
3230
|
from,
|
|
@@ -3249,9 +3271,12 @@ var OstiumPriceStream = class _OstiumPriceStream {
|
|
|
3249
3271
|
snapshotHandlers = /* @__PURE__ */ new Set();
|
|
3250
3272
|
/** pairId (string) → raw "FROM-TO" name for the WS API. */
|
|
3251
3273
|
pairRawNameCache;
|
|
3252
|
-
|
|
3274
|
+
/** raw "FROM-TO" name → pairId (string) for mapping incoming ticks back to SDK ids. */
|
|
3275
|
+
rawNamePairIdCache;
|
|
3276
|
+
constructor(ws, pairRawNameCache, rawNamePairIdCache, initialSubscribeRawPairs) {
|
|
3253
3277
|
this.ws = ws;
|
|
3254
3278
|
this.pairRawNameCache = pairRawNameCache;
|
|
3279
|
+
this.rawNamePairIdCache = rawNamePairIdCache;
|
|
3255
3280
|
if (initialSubscribeRawPairs?.length) {
|
|
3256
3281
|
ws.once("open", () => {
|
|
3257
3282
|
ws.send(JSON.stringify({ type: "subscribe", pairs: initialSubscribeRawPairs }));
|
|
@@ -3265,10 +3290,13 @@ var OstiumPriceStream = class _OstiumPriceStream {
|
|
|
3265
3290
|
return;
|
|
3266
3291
|
}
|
|
3267
3292
|
if (msg.type === "snapshot" && Array.isArray(msg.data)) {
|
|
3268
|
-
const ticks = msg.data.map(
|
|
3293
|
+
const ticks = msg.data.map(
|
|
3294
|
+
(item) => rawTickToPublic(item, this.rawNamePairIdCache.get(`${item.from}-${item.to}`))
|
|
3295
|
+
);
|
|
3269
3296
|
for (const h of this.snapshotHandlers) h(ticks);
|
|
3270
3297
|
} else if (msg.type === "tick" && msg.data) {
|
|
3271
|
-
const
|
|
3298
|
+
const item = msg.data;
|
|
3299
|
+
const tick = rawTickToPublic(item, this.rawNamePairIdCache.get(`${item.from}-${item.to}`));
|
|
3272
3300
|
for (const h of this.tickHandlers) h(tick);
|
|
3273
3301
|
}
|
|
3274
3302
|
});
|
|
@@ -3294,7 +3322,11 @@ var OstiumPriceStream = class _OstiumPriceStream {
|
|
|
3294
3322
|
const ws = new WS(url, {
|
|
3295
3323
|
headers: { "User-Agent": "ostium-sdk", "Accept": "*/*" }
|
|
3296
3324
|
});
|
|
3297
|
-
|
|
3325
|
+
const rawNamePairIdCache = /* @__PURE__ */ new Map();
|
|
3326
|
+
for (const [pairId, rawName] of pairRawNameCache.entries()) {
|
|
3327
|
+
rawNamePairIdCache.set(rawName, pairId);
|
|
3328
|
+
}
|
|
3329
|
+
return new _OstiumPriceStream(ws, pairRawNameCache, rawNamePairIdCache, initial);
|
|
3298
3330
|
}
|
|
3299
3331
|
/**
|
|
3300
3332
|
* Register a callback that fires on every incoming price tick.
|
|
@@ -3370,6 +3402,37 @@ var OstiumPriceStream = class _OstiumPriceStream {
|
|
|
3370
3402
|
}
|
|
3371
3403
|
};
|
|
3372
3404
|
|
|
3405
|
+
// src/data/internal/aggregations.ts
|
|
3406
|
+
function aggregateMarginSummary(pairPositions) {
|
|
3407
|
+
let accountValue = 0;
|
|
3408
|
+
let totalCollateralUsed = 0;
|
|
3409
|
+
let totalNtlPos = 0;
|
|
3410
|
+
let totalRawPnlUsd = 0;
|
|
3411
|
+
let totalCumRollover = 0;
|
|
3412
|
+
let totalWithdrawable = 0;
|
|
3413
|
+
for (const { position } of pairPositions) {
|
|
3414
|
+
const collateral = parseFloat(position.collateralUsed) || 0;
|
|
3415
|
+
const ntl = parseFloat(position.ntl) || 0;
|
|
3416
|
+
const pnl = parseFloat(position.unrealizedPnl) || 0;
|
|
3417
|
+
const rollover = parseFloat(position.cumRollover) || 0;
|
|
3418
|
+
const withdrawable = parseFloat(position.maxWithdrawable) || 0;
|
|
3419
|
+
accountValue += collateral + pnl;
|
|
3420
|
+
totalCollateralUsed += collateral;
|
|
3421
|
+
totalNtlPos += ntl;
|
|
3422
|
+
totalRawPnlUsd += pnl;
|
|
3423
|
+
totalCumRollover += rollover;
|
|
3424
|
+
totalWithdrawable += withdrawable;
|
|
3425
|
+
}
|
|
3426
|
+
return {
|
|
3427
|
+
accountValue: accountValue.toString(),
|
|
3428
|
+
totalCollateralUsed: totalCollateralUsed.toString(),
|
|
3429
|
+
totalNtlPos: totalNtlPos.toString(),
|
|
3430
|
+
totalRawPnlUsd: totalRawPnlUsd.toString(),
|
|
3431
|
+
totalCumRollover: totalCumRollover.toString(),
|
|
3432
|
+
totalWithdrawable: totalWithdrawable.toString()
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3373
3436
|
// src/data/internal/formatters.ts
|
|
3374
3437
|
var MIN_NOTIONAL = "5.0";
|
|
3375
3438
|
var MIN_NOTIONAL_NUM = 5;
|
|
@@ -3471,14 +3534,14 @@ function formatPosition(raw, price, pnl, maxLeverage) {
|
|
|
3471
3534
|
returnOnEquity: roe.toString(),
|
|
3472
3535
|
liquidationPx: pnl.liquidationPrice.toString(),
|
|
3473
3536
|
collateralUsed: collateral.toString(),
|
|
3474
|
-
cumRollover: pnl.rollover.toString(),
|
|
3537
|
+
cumRollover: (pnl.rollover * -1).toString(),
|
|
3475
3538
|
...raw.takeProfitPrice && raw.takeProfitPrice !== "0" ? { tpPx: formatTokens(raw.takeProfitPrice).toString() } : {},
|
|
3476
3539
|
...raw.stopLossPrice && raw.stopLossPrice !== "0" ? { slPx: formatTokens(raw.stopLossPrice).toString() } : {},
|
|
3477
3540
|
openTimestamp: parseInt(raw.timestamp || "0") * 1e3,
|
|
3478
3541
|
isDayTrade: raw.isDayTrade ?? false,
|
|
3479
|
-
maxLeverage: maxLeverage.toString()
|
|
3480
|
-
|
|
3481
|
-
|
|
3542
|
+
maxLeverage: maxLeverage.toString(),
|
|
3543
|
+
maxWithdrawable: maxWithdrawForPosition(collateral, leverage, maxLeverage).toString()
|
|
3544
|
+
}
|
|
3482
3545
|
};
|
|
3483
3546
|
}
|
|
3484
3547
|
function formatFill(raw) {
|
|
@@ -3555,6 +3618,131 @@ function formatOpenOrder(raw) {
|
|
|
3555
3618
|
timestamp: parseInt(raw.initiatedAt || "0") * 1e3
|
|
3556
3619
|
};
|
|
3557
3620
|
}
|
|
3621
|
+
|
|
3622
|
+
// src/data/internal/position-updates.ts
|
|
3623
|
+
function cloneResponse(response) {
|
|
3624
|
+
return {
|
|
3625
|
+
pairPositions: response.pairPositions.map((pairPos) => ({
|
|
3626
|
+
position: { ...pairPos.position }
|
|
3627
|
+
})),
|
|
3628
|
+
marginSummary: { ...response.marginSummary },
|
|
3629
|
+
time: response.time
|
|
3630
|
+
};
|
|
3631
|
+
}
|
|
3632
|
+
function applyTickToPosition(position, tick) {
|
|
3633
|
+
const size = parseFloat(position.szi) || 0;
|
|
3634
|
+
const entry = parseFloat(position.entryPx) || 0;
|
|
3635
|
+
const collateral = parseFloat(position.collateralUsed) || 0;
|
|
3636
|
+
const maxLeverage = parseFloat(position.maxLeverage) || 0;
|
|
3637
|
+
const rollover = parseFloat(position.cumRollover) || 0;
|
|
3638
|
+
const isLong = position.side === "B";
|
|
3639
|
+
const ntl = size * tick.mid;
|
|
3640
|
+
const rawPnl = isLong ? (tick.mid - entry) * size : (entry - tick.mid) * size;
|
|
3641
|
+
const netPnl = rawPnl + rollover;
|
|
3642
|
+
const roe = collateral > 0 ? netPnl / collateral : 0;
|
|
3643
|
+
const currentLeverage = collateral > 0 ? ntl / collateral : 0;
|
|
3644
|
+
const maxWithdrawable = maxWithdrawForPosition(collateral, currentLeverage, maxLeverage);
|
|
3645
|
+
return {
|
|
3646
|
+
...position,
|
|
3647
|
+
ntl: ntl.toString(),
|
|
3648
|
+
unrealizedPnl: netPnl.toString(),
|
|
3649
|
+
returnOnEquity: roe.toString(),
|
|
3650
|
+
maxWithdrawable: maxWithdrawable.toString()
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
function updateResponseWithTick(current, tick, timestampMs) {
|
|
3654
|
+
if (!tick.pairId) return current;
|
|
3655
|
+
let changed = false;
|
|
3656
|
+
const pairPositions = current.pairPositions.map((pairPos) => {
|
|
3657
|
+
if (String(pairPos.position.pairId) !== String(tick.pairId)) {
|
|
3658
|
+
return pairPos;
|
|
3659
|
+
}
|
|
3660
|
+
changed = true;
|
|
3661
|
+
return {
|
|
3662
|
+
...pairPos,
|
|
3663
|
+
position: applyTickToPosition(pairPos.position, tick)
|
|
3664
|
+
};
|
|
3665
|
+
});
|
|
3666
|
+
if (!changed) return current;
|
|
3667
|
+
return {
|
|
3668
|
+
pairPositions,
|
|
3669
|
+
marginSummary: aggregateMarginSummary(pairPositions),
|
|
3670
|
+
time: timestampMs
|
|
3671
|
+
};
|
|
3672
|
+
}
|
|
3673
|
+
var OstiumPositionUpdatesStream = class {
|
|
3674
|
+
priceStream;
|
|
3675
|
+
updateHandlers = /* @__PURE__ */ new Set();
|
|
3676
|
+
current;
|
|
3677
|
+
trackedPairIds;
|
|
3678
|
+
constructor(initial, trackedPairIds, priceStream) {
|
|
3679
|
+
this.current = cloneResponse(initial);
|
|
3680
|
+
this.priceStream = priceStream;
|
|
3681
|
+
this.trackedPairIds = new Set(trackedPairIds);
|
|
3682
|
+
if (!priceStream) return;
|
|
3683
|
+
priceStream.onSnapshot((ticks) => {
|
|
3684
|
+
this.ingestSnapshot(ticks);
|
|
3685
|
+
});
|
|
3686
|
+
priceStream.onTick((tick) => {
|
|
3687
|
+
this.ingestTick(tick);
|
|
3688
|
+
});
|
|
3689
|
+
}
|
|
3690
|
+
onUpdate(handler) {
|
|
3691
|
+
this.updateHandlers.add(handler);
|
|
3692
|
+
return () => {
|
|
3693
|
+
this.updateHandlers.delete(handler);
|
|
3694
|
+
};
|
|
3695
|
+
}
|
|
3696
|
+
onOpen(handler) {
|
|
3697
|
+
this.priceStream?.onOpen(handler);
|
|
3698
|
+
return this;
|
|
3699
|
+
}
|
|
3700
|
+
onError(handler) {
|
|
3701
|
+
this.priceStream?.onError(handler);
|
|
3702
|
+
return this;
|
|
3703
|
+
}
|
|
3704
|
+
onClose(handler) {
|
|
3705
|
+
this.priceStream?.onClose(handler);
|
|
3706
|
+
return this;
|
|
3707
|
+
}
|
|
3708
|
+
getCurrent() {
|
|
3709
|
+
return cloneResponse(this.current);
|
|
3710
|
+
}
|
|
3711
|
+
/**
|
|
3712
|
+
* Apply a single externally sourced price tick to the tracked positions.
|
|
3713
|
+
*
|
|
3714
|
+
* Use this when your app already has its own websocket connection and you want
|
|
3715
|
+
* the SDK to handle only the position recalculation logic.
|
|
3716
|
+
*/
|
|
3717
|
+
ingestTick(tick) {
|
|
3718
|
+
if (!tick.pairId || !this.trackedPairIds.has(String(tick.pairId))) return;
|
|
3719
|
+
const next = updateResponseWithTick(this.current, tick, tick.timestampSeconds * 1e3);
|
|
3720
|
+
if (next !== this.current) {
|
|
3721
|
+
this.current = next;
|
|
3722
|
+
this.emit(next);
|
|
3723
|
+
}
|
|
3724
|
+
}
|
|
3725
|
+
/**
|
|
3726
|
+
* Apply a batch of externally sourced ticks, such as a websocket snapshot.
|
|
3727
|
+
*/
|
|
3728
|
+
ingestSnapshot(ticks) {
|
|
3729
|
+
let next = this.current;
|
|
3730
|
+
for (const tick of ticks) {
|
|
3731
|
+
if (!tick.pairId || !this.trackedPairIds.has(String(tick.pairId))) continue;
|
|
3732
|
+
next = updateResponseWithTick(next, tick, tick.timestampSeconds * 1e3);
|
|
3733
|
+
}
|
|
3734
|
+
if (next !== this.current) {
|
|
3735
|
+
this.current = next;
|
|
3736
|
+
this.emit(next);
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
close() {
|
|
3740
|
+
this.priceStream?.close();
|
|
3741
|
+
}
|
|
3742
|
+
emit(positions) {
|
|
3743
|
+
for (const handler of this.updateHandlers) handler(cloneResponse(positions));
|
|
3744
|
+
}
|
|
3745
|
+
};
|
|
3558
3746
|
var EMPTY_RESULT = { long: [], short: [] };
|
|
3559
3747
|
var ORDERBOOK_MAX_LEVELS = 20;
|
|
3560
3748
|
function generateLogSpacedNotionals(levels, capacity) {
|
|
@@ -3750,20 +3938,16 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
|
|
|
3750
3938
|
}),
|
|
3751
3939
|
this.fetchLivePricesSafe()
|
|
3752
3940
|
]);
|
|
3753
|
-
let totalWithdrawable = 0;
|
|
3754
3941
|
const pairPositions = trades.map((trade) => {
|
|
3755
3942
|
const price = prices[`${trade.pair.from}/${trade.pair.to}`];
|
|
3756
3943
|
const pnl = price && blockNumber ? getTradePnL(trade, price, blockNumber) : EMPTY_PNL;
|
|
3757
3944
|
const maxLev = trade.isDayTrade ? formatLeverage(trade.pair.overnightMaxLeverage) : pairMaxLeverage(trade.pair);
|
|
3758
|
-
|
|
3759
|
-
totalWithdrawable += parseFloat(pairPos.maxWithdrawable);
|
|
3760
|
-
return pairPos;
|
|
3945
|
+
return formatPosition(trade, price, pnl, maxLev);
|
|
3761
3946
|
});
|
|
3762
3947
|
const marginSummary = aggregateMarginSummary(pairPositions);
|
|
3763
3948
|
return {
|
|
3764
3949
|
pairPositions,
|
|
3765
3950
|
marginSummary,
|
|
3766
|
-
withdrawable: totalWithdrawable.toString(),
|
|
3767
3951
|
time: Date.now()
|
|
3768
3952
|
};
|
|
3769
3953
|
}
|
|
@@ -4014,6 +4198,24 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
|
|
|
4014
4198
|
});
|
|
4015
4199
|
return OstiumPriceStream.connect(this.builderApiUrl, rawNames, cache);
|
|
4016
4200
|
}
|
|
4201
|
+
/**
|
|
4202
|
+
* Stream price-driven updates for an existing `getOpenPositions()` response.
|
|
4203
|
+
*
|
|
4204
|
+
* The SDK subscribes only to the unique pairs present in `initial.pairPositions`,
|
|
4205
|
+
* recalculates price-sensitive fields for affected positions on each tick, and
|
|
4206
|
+
* emits the full updated response.
|
|
4207
|
+
*
|
|
4208
|
+
* Pass an existing `priceStream` to reuse a websocket connection your app has
|
|
4209
|
+
* already opened. Omit it to let the SDK open and manage a dedicated
|
|
4210
|
+
* connection for the tracked pair ids.
|
|
4211
|
+
*/
|
|
4212
|
+
streamPositionUpdates(initial, priceStream) {
|
|
4213
|
+
const pairIds = [...new Set(initial.pairPositions.map(({ position }) => String(position.pairId)))];
|
|
4214
|
+
if (pairIds.length === 0) {
|
|
4215
|
+
return new OstiumPositionUpdatesStream(initial, []);
|
|
4216
|
+
}
|
|
4217
|
+
return new OstiumPositionUpdatesStream(initial, pairIds, priceStream ?? this.streamPrices(pairIds));
|
|
4218
|
+
}
|
|
4017
4219
|
// ─────────────────────────────────────────────────────────────────────────
|
|
4018
4220
|
// Internal — fetchers, cache, query wrapper
|
|
4019
4221
|
// ─────────────────────────────────────────────────────────────────────────
|
|
@@ -4072,43 +4274,28 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
|
|
|
4072
4274
|
}
|
|
4073
4275
|
}
|
|
4074
4276
|
};
|
|
4075
|
-
function aggregateMarginSummary(pairPositions) {
|
|
4076
|
-
let accountValue = 0;
|
|
4077
|
-
let totalCollateralUsed = 0;
|
|
4078
|
-
let totalNtlPos = 0;
|
|
4079
|
-
let totalRawPnlUsd = 0;
|
|
4080
|
-
for (const { position } of pairPositions) {
|
|
4081
|
-
const collateral = parseFloat(position.collateralUsed) || 0;
|
|
4082
|
-
const ntl = parseFloat(position.ntl) || 0;
|
|
4083
|
-
const pnl = parseFloat(position.unrealizedPnl) || 0;
|
|
4084
|
-
accountValue += collateral + pnl;
|
|
4085
|
-
totalCollateralUsed += collateral;
|
|
4086
|
-
totalNtlPos += ntl;
|
|
4087
|
-
totalRawPnlUsd += pnl;
|
|
4088
|
-
}
|
|
4089
|
-
return {
|
|
4090
|
-
accountValue: accountValue.toString(),
|
|
4091
|
-
totalCollateralUsed: totalCollateralUsed.toString(),
|
|
4092
|
-
totalNtlPos: totalNtlPos.toString(),
|
|
4093
|
-
totalRawPnlUsd: totalRawPnlUsd.toString()
|
|
4094
|
-
};
|
|
4095
|
-
}
|
|
4096
4277
|
|
|
4097
4278
|
// src/client.ts
|
|
4098
4279
|
var HEX64_RE = /^0x[0-9a-fA-F]{64}$/;
|
|
4099
4280
|
var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
|
|
4100
4281
|
function validateConfig(config) {
|
|
4101
|
-
if (!HEX64_RE.test(config.privateKey)) {
|
|
4282
|
+
if (config.privateKey && !HEX64_RE.test(config.privateKey)) {
|
|
4102
4283
|
throw new OstiumError(
|
|
4103
4284
|
"privateKey must be a 64-character hex string prefixed with 0x",
|
|
4104
4285
|
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
4105
4286
|
);
|
|
4106
4287
|
}
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4288
|
+
for (const [label, value] of [
|
|
4289
|
+
["traderAddress", config.traderAddress],
|
|
4290
|
+
["delegateAddress", config.delegateAddress],
|
|
4291
|
+
["safeAddress", config.safeAddress]
|
|
4292
|
+
]) {
|
|
4293
|
+
if (value && !ADDRESS_RE.test(value)) {
|
|
4294
|
+
throw new OstiumError(
|
|
4295
|
+
`${label} must be a valid 42-character Ethereum address (0x + 40 hex chars)`,
|
|
4296
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
4297
|
+
);
|
|
4298
|
+
}
|
|
4112
4299
|
}
|
|
4113
4300
|
if (config.slippageBps !== void 0 && (config.slippageBps < 0 || config.slippageBps > 500)) {
|
|
4114
4301
|
throw new OstiumError(
|
|
@@ -4122,10 +4309,45 @@ function validateConfig(config) {
|
|
|
4122
4309
|
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
4123
4310
|
);
|
|
4124
4311
|
}
|
|
4312
|
+
let mode;
|
|
4313
|
+
try {
|
|
4314
|
+
mode = resolveMode(config);
|
|
4315
|
+
} catch {
|
|
4316
|
+
throw new OstiumError(
|
|
4317
|
+
"Could not infer client mode from config. Provide the required addresses for the selected factory.",
|
|
4318
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
4319
|
+
);
|
|
4320
|
+
}
|
|
4321
|
+
if (mode === "self-self" && !config.privateKey && !config.traderAddress) {
|
|
4322
|
+
throw new OstiumError(
|
|
4323
|
+
"Self + Self mode requires traderPrivateKey for submission or traderAddress for build-only usage.",
|
|
4324
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
4325
|
+
);
|
|
4326
|
+
}
|
|
4327
|
+
if (mode === "self-gasless" && !config.privateKey && (!config.traderAddress || !config.safeAddress)) {
|
|
4328
|
+
throw new OstiumError(
|
|
4329
|
+
"Self + Gasless build-only mode requires traderAddress and safeAddress.",
|
|
4330
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
4331
|
+
);
|
|
4332
|
+
}
|
|
4333
|
+
if (mode === "delegated-self" && (!config.traderAddress || !config.privateKey && !config.delegateAddress)) {
|
|
4334
|
+
throw new OstiumError(
|
|
4335
|
+
"Delegated + Self mode requires traderAddress and either delegatePrivateKey or delegateAddress.",
|
|
4336
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
4337
|
+
);
|
|
4338
|
+
}
|
|
4339
|
+
if (mode === "delegated-gasless" && (!config.traderAddress || !config.privateKey && (!config.delegateAddress || !config.safeAddress))) {
|
|
4340
|
+
throw new OstiumError(
|
|
4341
|
+
"Delegated + Gasless mode requires traderAddress and either delegatePrivateKey or both delegateAddress and safeAddress.",
|
|
4342
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
4343
|
+
);
|
|
4344
|
+
}
|
|
4125
4345
|
}
|
|
4126
4346
|
var OstiumClient = class _OstiumClient {
|
|
4127
4347
|
signer;
|
|
4128
4348
|
submitter;
|
|
4349
|
+
traderAddress;
|
|
4350
|
+
effectiveSender;
|
|
4129
4351
|
contracts;
|
|
4130
4352
|
publicClient;
|
|
4131
4353
|
defaultSlippageBps;
|
|
@@ -4140,6 +4362,8 @@ var OstiumClient = class _OstiumClient {
|
|
|
4140
4362
|
constructor(internals) {
|
|
4141
4363
|
this.signer = internals.signer;
|
|
4142
4364
|
this.submitter = internals.submitter;
|
|
4365
|
+
this.traderAddress = internals.traderAddress;
|
|
4366
|
+
this.effectiveSender = internals.effectiveSender;
|
|
4143
4367
|
this.contracts = internals.contracts;
|
|
4144
4368
|
this.publicClient = internals.publicClient;
|
|
4145
4369
|
this.defaultSlippageBps = internals.defaultSlippageBps;
|
|
@@ -4164,17 +4388,20 @@ var OstiumClient = class _OstiumClient {
|
|
|
4164
4388
|
* });
|
|
4165
4389
|
* await client.openTrade({ ... });
|
|
4166
4390
|
* ```
|
|
4167
|
-
|
|
4391
|
+
*/
|
|
4168
4392
|
static async createSelfAndSelf(params) {
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
rpcUrl: params.rpcUrl,
|
|
4393
|
+
const base = {
|
|
4394
|
+
mode: "self-self",
|
|
4172
4395
|
testnet: params.testnet,
|
|
4173
4396
|
slippageBps: params.slippageBps,
|
|
4174
4397
|
builder: params.builder,
|
|
4175
4398
|
subgraphUrl: params.subgraphUrl,
|
|
4176
|
-
builderApiUrl: params.builderApiUrl
|
|
4177
|
-
|
|
4399
|
+
builderApiUrl: params.builderApiUrl,
|
|
4400
|
+
rpcUrl: params.rpcUrl
|
|
4401
|
+
};
|
|
4402
|
+
return _OstiumClient._fromConfig(
|
|
4403
|
+
"traderPrivateKey" in params ? { ...base, privateKey: params.traderPrivateKey } : { ...base, traderAddress: params.traderAddress }
|
|
4404
|
+
);
|
|
4178
4405
|
}
|
|
4179
4406
|
/**
|
|
4180
4407
|
* **Self + Gasless** — your EOA owns everything; a Safe relays trades for free.
|
|
@@ -4198,17 +4425,27 @@ var OstiumClient = class _OstiumClient {
|
|
|
4198
4425
|
*/
|
|
4199
4426
|
static async createSelfAndGasless(params) {
|
|
4200
4427
|
const defaultUrl = params.testnet ? DEFAULT_PIMLICO_URL_TESTNET : DEFAULT_PIMLICO_URL;
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
|
|
4428
|
+
const base = {
|
|
4429
|
+
mode: "self-gasless",
|
|
4204
4430
|
rpcUrl: params.rpcUrl,
|
|
4205
|
-
sponsorshipPolicyId: params.sponsorshipPolicyId,
|
|
4206
4431
|
testnet: params.testnet,
|
|
4207
4432
|
slippageBps: params.slippageBps,
|
|
4208
4433
|
builder: params.builder,
|
|
4209
4434
|
subgraphUrl: params.subgraphUrl,
|
|
4210
4435
|
builderApiUrl: params.builderApiUrl
|
|
4211
|
-
}
|
|
4436
|
+
};
|
|
4437
|
+
return _OstiumClient._fromConfig(
|
|
4438
|
+
"traderPrivateKey" in params ? {
|
|
4439
|
+
...base,
|
|
4440
|
+
privateKey: params.traderPrivateKey,
|
|
4441
|
+
pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
|
|
4442
|
+
sponsorshipPolicyId: params.sponsorshipPolicyId
|
|
4443
|
+
} : {
|
|
4444
|
+
...base,
|
|
4445
|
+
traderAddress: params.traderAddress,
|
|
4446
|
+
safeAddress: params.safeAddress
|
|
4447
|
+
}
|
|
4448
|
+
);
|
|
4212
4449
|
}
|
|
4213
4450
|
/**
|
|
4214
4451
|
* **Delegated + Self** — a delegate EOA signs and pays gas on behalf of a trader address.
|
|
@@ -4228,8 +4465,8 @@ var OstiumClient = class _OstiumClient {
|
|
|
4228
4465
|
* ```
|
|
4229
4466
|
*/
|
|
4230
4467
|
static async createDelegatedAndSelf(params) {
|
|
4231
|
-
|
|
4232
|
-
|
|
4468
|
+
const base = {
|
|
4469
|
+
mode: "delegated-self",
|
|
4233
4470
|
traderAddress: params.traderAddress,
|
|
4234
4471
|
rpcUrl: params.rpcUrl,
|
|
4235
4472
|
testnet: params.testnet,
|
|
@@ -4237,7 +4474,10 @@ var OstiumClient = class _OstiumClient {
|
|
|
4237
4474
|
builder: params.builder,
|
|
4238
4475
|
subgraphUrl: params.subgraphUrl,
|
|
4239
4476
|
builderApiUrl: params.builderApiUrl
|
|
4240
|
-
}
|
|
4477
|
+
};
|
|
4478
|
+
return _OstiumClient._fromConfig(
|
|
4479
|
+
"delegatePrivateKey" in params ? { ...base, privateKey: params.delegatePrivateKey } : { ...base, delegateAddress: params.delegateAddress }
|
|
4480
|
+
);
|
|
4241
4481
|
}
|
|
4242
4482
|
/**
|
|
4243
4483
|
* **Delegated + Gasless** — a Safe derived from the delegate key submits sponsored
|
|
@@ -4258,18 +4498,28 @@ var OstiumClient = class _OstiumClient {
|
|
|
4258
4498
|
*/
|
|
4259
4499
|
static async createDelegatedAndGasless(params) {
|
|
4260
4500
|
const defaultUrl = params.testnet ? DEFAULT_PIMLICO_URL_TESTNET : DEFAULT_PIMLICO_URL;
|
|
4261
|
-
|
|
4262
|
-
|
|
4501
|
+
const base = {
|
|
4502
|
+
mode: "delegated-gasless",
|
|
4263
4503
|
traderAddress: params.traderAddress,
|
|
4264
|
-
pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
|
|
4265
4504
|
rpcUrl: params.rpcUrl,
|
|
4266
|
-
sponsorshipPolicyId: params.sponsorshipPolicyId,
|
|
4267
4505
|
testnet: params.testnet,
|
|
4268
4506
|
slippageBps: params.slippageBps,
|
|
4269
4507
|
builder: params.builder,
|
|
4270
4508
|
subgraphUrl: params.subgraphUrl,
|
|
4271
4509
|
builderApiUrl: params.builderApiUrl
|
|
4272
|
-
}
|
|
4510
|
+
};
|
|
4511
|
+
return _OstiumClient._fromConfig(
|
|
4512
|
+
"delegatePrivateKey" in params ? {
|
|
4513
|
+
...base,
|
|
4514
|
+
privateKey: params.delegatePrivateKey,
|
|
4515
|
+
pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
|
|
4516
|
+
sponsorshipPolicyId: params.sponsorshipPolicyId
|
|
4517
|
+
} : {
|
|
4518
|
+
...base,
|
|
4519
|
+
delegateAddress: params.delegateAddress,
|
|
4520
|
+
safeAddress: params.safeAddress
|
|
4521
|
+
}
|
|
4522
|
+
);
|
|
4273
4523
|
}
|
|
4274
4524
|
/**
|
|
4275
4525
|
* **Read-only client** — no signer, no submitter, no privateKey required.
|
|
@@ -4314,6 +4564,7 @@ var OstiumClient = class _OstiumClient {
|
|
|
4314
4564
|
/** Internal factory shared by all public constructors. */
|
|
4315
4565
|
static async _fromConfig(config) {
|
|
4316
4566
|
validateConfig(config);
|
|
4567
|
+
const mode = resolveMode(config);
|
|
4317
4568
|
const testnet = config.testnet ?? false;
|
|
4318
4569
|
const networkConfig = getNetworkConfig(testnet);
|
|
4319
4570
|
const chain = testnet ? arbitrumSepolia : arbitrum;
|
|
@@ -4323,18 +4574,29 @@ var OstiumClient = class _OstiumClient {
|
|
|
4323
4574
|
tradingStorage: networkConfig.contracts.tradingStorage,
|
|
4324
4575
|
usdc: networkConfig.contracts.usdc
|
|
4325
4576
|
};
|
|
4326
|
-
const selfGasless =
|
|
4327
|
-
const submitter = await createSubmitter(config, testnet);
|
|
4577
|
+
const selfGasless = mode === "self-gasless";
|
|
4328
4578
|
let traderAddress;
|
|
4329
|
-
if (
|
|
4579
|
+
if (mode === "self-self" || mode === "self-gasless") {
|
|
4580
|
+
traderAddress = config.traderAddress ?? privateKeyToAccount(config.privateKey).address;
|
|
4581
|
+
} else {
|
|
4330
4582
|
traderAddress = config.traderAddress;
|
|
4583
|
+
}
|
|
4584
|
+
const signer = createSigner(mode, traderAddress);
|
|
4585
|
+
let submitter;
|
|
4586
|
+
let effectiveSender;
|
|
4587
|
+
if (hasPrivateKey(config)) {
|
|
4588
|
+
submitter = await createSubmitter(config, testnet, mode);
|
|
4589
|
+
effectiveSender = submitter.effectiveSender;
|
|
4590
|
+
} else if (mode === "self-self") {
|
|
4591
|
+
effectiveSender = traderAddress;
|
|
4592
|
+
} else if (mode === "delegated-self") {
|
|
4593
|
+
effectiveSender = config.delegateAddress;
|
|
4331
4594
|
} else {
|
|
4332
|
-
|
|
4595
|
+
effectiveSender = config.safeAddress;
|
|
4333
4596
|
}
|
|
4334
|
-
const signer = createSigner(config, traderAddress, selfGasless);
|
|
4335
4597
|
const publicRpc = testnet ? "https://sepolia-rollup.arbitrum.io/rpc" : "https://arb1.arbitrum.io/rpc";
|
|
4336
|
-
const rpcUrl = config.rpcUrl ??
|
|
4337
|
-
if (
|
|
4598
|
+
const rpcUrl = config.rpcUrl ?? publicRpc;
|
|
4599
|
+
if (hasPrivateKey(config) && mode !== "self-gasless" && mode !== "delegated-gasless" && !config.rpcUrl) {
|
|
4338
4600
|
throw new OstiumError(
|
|
4339
4601
|
"rpcUrl is required for self-submission mode",
|
|
4340
4602
|
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
@@ -4342,7 +4604,7 @@ var OstiumClient = class _OstiumClient {
|
|
|
4342
4604
|
}
|
|
4343
4605
|
const publicClient = createPublicClient({ chain, transport: http(rpcUrl) });
|
|
4344
4606
|
let eoaSubmit;
|
|
4345
|
-
if (selfGasless) {
|
|
4607
|
+
if (selfGasless && hasPrivateKey(config)) {
|
|
4346
4608
|
const eoaAccount = privateKeyToAccount(config.privateKey);
|
|
4347
4609
|
const eoaRpcUrl = config.rpcUrl ?? publicRpc;
|
|
4348
4610
|
const eoaWalletClient = createWalletClient({ account: eoaAccount, chain, transport: http(eoaRpcUrl) });
|
|
@@ -4364,11 +4626,13 @@ var OstiumClient = class _OstiumClient {
|
|
|
4364
4626
|
return new _OstiumClient({
|
|
4365
4627
|
signer,
|
|
4366
4628
|
submitter,
|
|
4629
|
+
traderAddress,
|
|
4630
|
+
effectiveSender,
|
|
4367
4631
|
contracts,
|
|
4368
4632
|
publicClient,
|
|
4369
4633
|
defaultSlippageBps,
|
|
4370
|
-
delegated:
|
|
4371
|
-
gasless:
|
|
4634
|
+
delegated: mode === "delegated-self" || mode === "delegated-gasless",
|
|
4635
|
+
gasless: mode === "self-gasless" || mode === "delegated-gasless",
|
|
4372
4636
|
selfGasless,
|
|
4373
4637
|
eoaSubmit,
|
|
4374
4638
|
builderAddress: config.builder?.address,
|
|
@@ -4386,17 +4650,25 @@ var OstiumClient = class _OstiumClient {
|
|
|
4386
4650
|
* - Self + Gasless: EOA derived from privateKey (NOT the Safe — USDC lives here).
|
|
4387
4651
|
*/
|
|
4388
4652
|
getTraderAddress() {
|
|
4389
|
-
if (!this.
|
|
4653
|
+
if (!this.traderAddress) {
|
|
4390
4654
|
throw new OstiumError(
|
|
4391
4655
|
"No connected trader \u2014 pass `user` explicitly or use one of the createSelfAnd*/createDelegatedAnd* factories.",
|
|
4392
4656
|
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
4393
4657
|
);
|
|
4394
4658
|
}
|
|
4395
|
-
return this.
|
|
4659
|
+
return this.traderAddress;
|
|
4396
4660
|
}
|
|
4397
|
-
/** True when the client
|
|
4661
|
+
/** True when the client cannot submit transactions directly. */
|
|
4398
4662
|
isReadOnly() {
|
|
4399
|
-
return !this.
|
|
4663
|
+
return !this.submitter;
|
|
4664
|
+
}
|
|
4665
|
+
/** True when the client can build mode-correct unsigned transaction requests. */
|
|
4666
|
+
canBuildTransactions() {
|
|
4667
|
+
return !!this.signer && !!this.effectiveSender;
|
|
4668
|
+
}
|
|
4669
|
+
/** True when the client has the credentials needed for SDK-managed submission. */
|
|
4670
|
+
canSubmitTransactions() {
|
|
4671
|
+
return !!this.submitter;
|
|
4400
4672
|
}
|
|
4401
4673
|
/**
|
|
4402
4674
|
* Returns the Safe smart-account address used for gasless submission.
|
|
@@ -4408,7 +4680,7 @@ var OstiumClient = class _OstiumClient {
|
|
|
4408
4680
|
* - Self + Self / Delegated + Self: returns undefined.
|
|
4409
4681
|
*/
|
|
4410
4682
|
getSmartAccountAddress() {
|
|
4411
|
-
return this.gasless
|
|
4683
|
+
return this.gasless ? this.effectiveSender : void 0;
|
|
4412
4684
|
}
|
|
4413
4685
|
// ─────────────────────────────────────────────────────────────────────────
|
|
4414
4686
|
// Self + Gasless setup
|
|
@@ -4432,15 +4704,10 @@ var OstiumClient = class _OstiumClient {
|
|
|
4432
4704
|
* ```
|
|
4433
4705
|
*/
|
|
4434
4706
|
async setupGaslessDelegation() {
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
);
|
|
4440
|
-
}
|
|
4441
|
-
const safeAddress = this.submitter.effectiveSender;
|
|
4442
|
-
const encoded = encodeSetDelegate(safeAddress, this.contracts.trading);
|
|
4443
|
-
return this.submitDirectFromEoa(encoded);
|
|
4707
|
+
return this.submitDirectEoa(this.getSetupGaslessDelegationEncoded());
|
|
4708
|
+
}
|
|
4709
|
+
getSetupGaslessDelegationTx() {
|
|
4710
|
+
return this.buildDirectEoaTx(this.getSetupGaslessDelegationEncoded());
|
|
4444
4711
|
}
|
|
4445
4712
|
// ─────────────────────────────────────────────────────────────────────────
|
|
4446
4713
|
// USDC helpers
|
|
@@ -4502,18 +4769,14 @@ var OstiumClient = class _OstiumClient {
|
|
|
4502
4769
|
* @param amount USD amount as decimal string (e.g. "1000"), or "max" for MaxUint256.
|
|
4503
4770
|
*/
|
|
4504
4771
|
async approveUsdc(amount) {
|
|
4505
|
-
|
|
4506
|
-
throw new OstiumError(
|
|
4507
|
-
"approveUsdc is not available in delegated mode. The trader must approve USDC directly from their own account.",
|
|
4508
|
-
"DELEGATION_FAILED" /* DELEGATION_FAILED */
|
|
4509
|
-
);
|
|
4510
|
-
}
|
|
4511
|
-
const rawAmount = amount === "max" ? maxUint256 : parseUsdc(amount);
|
|
4512
|
-
const encoded = encodeUsdcApprove(this.contracts.tradingStorage, rawAmount, this.contracts.usdc);
|
|
4772
|
+
const encoded = this.getApproveUsdcEncoded(amount);
|
|
4513
4773
|
if (this.selfGasless) {
|
|
4514
|
-
return this.
|
|
4774
|
+
return this.submitDirectEoa(encoded);
|
|
4515
4775
|
}
|
|
4516
|
-
return this.
|
|
4776
|
+
return this.submitPrepared(encoded);
|
|
4777
|
+
}
|
|
4778
|
+
getApproveUsdcTx(amount) {
|
|
4779
|
+
return this.buildDirectEoaTx(this.getApproveUsdcEncoded(amount));
|
|
4517
4780
|
}
|
|
4518
4781
|
// ─────────────────────────────────────────────────────────────────────────
|
|
4519
4782
|
// Delegation helpers
|
|
@@ -4527,16 +4790,20 @@ var OstiumClient = class _OstiumClient {
|
|
|
4527
4790
|
* update the trader's delegation on their behalf.
|
|
4528
4791
|
*/
|
|
4529
4792
|
async setDelegate(delegateAddress) {
|
|
4530
|
-
|
|
4531
|
-
|
|
4793
|
+
return this.submitPrepared(this.getSetDelegateEncoded(delegateAddress));
|
|
4794
|
+
}
|
|
4795
|
+
getSetDelegateTx(delegateAddress) {
|
|
4796
|
+
return this.buildPreparedTx(this.getSetDelegateEncoded(delegateAddress));
|
|
4532
4797
|
}
|
|
4533
4798
|
/**
|
|
4534
4799
|
* Remove the current delegate on the trading contract.
|
|
4535
4800
|
* Follows the same delegation-wrapping rules as setDelegate().
|
|
4536
4801
|
*/
|
|
4537
4802
|
async removeDelegate() {
|
|
4538
|
-
|
|
4539
|
-
|
|
4803
|
+
return this.submitPrepared(this.getRemoveDelegateEncoded());
|
|
4804
|
+
}
|
|
4805
|
+
getRemoveDelegateTx() {
|
|
4806
|
+
return this.buildPreparedTx(this.getRemoveDelegateEncoded());
|
|
4540
4807
|
}
|
|
4541
4808
|
// ─────────────────────────────────────────────────────────────────────────
|
|
4542
4809
|
// Core trading methods
|
|
@@ -4549,6 +4816,12 @@ var OstiumClient = class _OstiumClient {
|
|
|
4549
4816
|
* the current allowance.
|
|
4550
4817
|
*/
|
|
4551
4818
|
async openTrade(params) {
|
|
4819
|
+
return this.submitPrepared(this.getOpenTradeEncoded(params));
|
|
4820
|
+
}
|
|
4821
|
+
getOpenTradeTx(params) {
|
|
4822
|
+
return this.buildPreparedTx(this.getOpenTradeEncoded(params));
|
|
4823
|
+
}
|
|
4824
|
+
getOpenTradeEncoded(params) {
|
|
4552
4825
|
const collateralNum = parseFloat(params.collateral);
|
|
4553
4826
|
if (collateralNum > MAX_COLLATERAL_USD) {
|
|
4554
4827
|
throw new OstiumError(
|
|
@@ -4557,7 +4830,7 @@ var OstiumClient = class _OstiumClient {
|
|
|
4557
4830
|
);
|
|
4558
4831
|
}
|
|
4559
4832
|
const apiOpen = {
|
|
4560
|
-
a: params.
|
|
4833
|
+
a: Number(params.pairId),
|
|
4561
4834
|
b: params.buy,
|
|
4562
4835
|
p: params.price,
|
|
4563
4836
|
s: params.collateral,
|
|
@@ -4580,8 +4853,7 @@ var OstiumClient = class _OstiumClient {
|
|
|
4580
4853
|
}
|
|
4581
4854
|
const builderFee = buildBuilderFee(apiBuilder);
|
|
4582
4855
|
const slippageP = params.type === "market" /* Market */ ? BigInt(this.resolveSlippage(params.slippage)) : 0n;
|
|
4583
|
-
|
|
4584
|
-
return this.execute(encoded);
|
|
4856
|
+
return encodeOpenTrade(trade, builderFee, orderType, slippageP, this.contracts.trading);
|
|
4585
4857
|
}
|
|
4586
4858
|
/**
|
|
4587
4859
|
* Close an open position (full or partial).
|
|
@@ -4596,6 +4868,12 @@ var OstiumClient = class _OstiumClient {
|
|
|
4596
4868
|
* ```
|
|
4597
4869
|
*/
|
|
4598
4870
|
async closeTrade(params) {
|
|
4871
|
+
return this.submitPrepared(this.getCloseTradeEncoded(params));
|
|
4872
|
+
}
|
|
4873
|
+
getCloseTradeTx(params) {
|
|
4874
|
+
return this.buildPreparedTx(this.getCloseTradeEncoded(params));
|
|
4875
|
+
}
|
|
4876
|
+
getCloseTradeEncoded(params) {
|
|
4599
4877
|
try {
|
|
4600
4878
|
validateCloseParams({
|
|
4601
4879
|
a: Number(params.pairId),
|
|
@@ -4615,7 +4893,7 @@ var OstiumClient = class _OstiumClient {
|
|
|
4615
4893
|
const closePercentage = BigInt(Math.round(params.closePercent * 100));
|
|
4616
4894
|
const marketPrice = parsePrice(params.price);
|
|
4617
4895
|
const slippageP = BigInt(this.resolveSlippage(params.slippage));
|
|
4618
|
-
|
|
4896
|
+
return encodeCloseTradeMarket(
|
|
4619
4897
|
pairIndex,
|
|
4620
4898
|
index,
|
|
4621
4899
|
closePercentage,
|
|
@@ -4623,7 +4901,6 @@ var OstiumClient = class _OstiumClient {
|
|
|
4623
4901
|
slippageP,
|
|
4624
4902
|
this.contracts.trading
|
|
4625
4903
|
);
|
|
4626
|
-
return this.execute(encoded);
|
|
4627
4904
|
}
|
|
4628
4905
|
/**
|
|
4629
4906
|
* Cancel a pending order.
|
|
@@ -4646,6 +4923,12 @@ var OstiumClient = class _OstiumClient {
|
|
|
4646
4923
|
* ```
|
|
4647
4924
|
*/
|
|
4648
4925
|
async cancelOrder(params) {
|
|
4926
|
+
return this.submitPrepared(this.getCancelOrderEncoded(params));
|
|
4927
|
+
}
|
|
4928
|
+
getCancelOrderTx(params) {
|
|
4929
|
+
return this.buildPreparedTx(this.getCancelOrderEncoded(params));
|
|
4930
|
+
}
|
|
4931
|
+
getCancelOrderEncoded(params) {
|
|
4649
4932
|
let encoded;
|
|
4650
4933
|
if (params.type === "limit" /* Limit */) {
|
|
4651
4934
|
const pairIdNum = Number(params.pairId);
|
|
@@ -4682,7 +4965,7 @@ var OstiumClient = class _OstiumClient {
|
|
|
4682
4965
|
}
|
|
4683
4966
|
encoded = encodeOpenTradeMarketTimeout(BigInt(params.orderId), this.contracts.trading);
|
|
4684
4967
|
}
|
|
4685
|
-
return
|
|
4968
|
+
return encoded;
|
|
4686
4969
|
}
|
|
4687
4970
|
/**
|
|
4688
4971
|
* Modify an open trade or a pending limit order.
|
|
@@ -4707,6 +4990,12 @@ var OstiumClient = class _OstiumClient {
|
|
|
4707
4990
|
* - Both `takeProfit` and `stopLoss` without `price` → throws (send two calls).
|
|
4708
4991
|
*/
|
|
4709
4992
|
async modifyOrder(params) {
|
|
4993
|
+
return this.submitPrepared(this.getModifyOrderEncoded(params));
|
|
4994
|
+
}
|
|
4995
|
+
getModifyOrderTx(params) {
|
|
4996
|
+
return this.buildPreparedTx(this.getModifyOrderEncoded(params));
|
|
4997
|
+
}
|
|
4998
|
+
getModifyOrderEncoded(params) {
|
|
4710
4999
|
try {
|
|
4711
5000
|
validateModifyParams({
|
|
4712
5001
|
a: Number(params.pairId),
|
|
@@ -4746,7 +5035,7 @@ var OstiumClient = class _OstiumClient {
|
|
|
4746
5035
|
"VALIDATION_FAILED" /* VALIDATION_FAILED */
|
|
4747
5036
|
);
|
|
4748
5037
|
}
|
|
4749
|
-
return
|
|
5038
|
+
return encoded;
|
|
4750
5039
|
}
|
|
4751
5040
|
/**
|
|
4752
5041
|
* Update collateral on an open position (isolated margin).
|
|
@@ -4766,6 +5055,12 @@ var OstiumClient = class _OstiumClient {
|
|
|
4766
5055
|
* Checks USDC allowance before top-up operations.
|
|
4767
5056
|
*/
|
|
4768
5057
|
async updateCollateral(params) {
|
|
5058
|
+
return this.submitPrepared(await this.getUpdateCollateralEncoded(params));
|
|
5059
|
+
}
|
|
5060
|
+
async getUpdateCollateralTx(params) {
|
|
5061
|
+
return this.buildPreparedTx(await this.getUpdateCollateralEncoded(params));
|
|
5062
|
+
}
|
|
5063
|
+
async getUpdateCollateralEncoded(params) {
|
|
4769
5064
|
const amount = parseFloat(params.amount);
|
|
4770
5065
|
if (Number.isNaN(amount) || amount === 0) {
|
|
4771
5066
|
throw new OstiumError(
|
|
@@ -4789,7 +5084,7 @@ var OstiumClient = class _OstiumClient {
|
|
|
4789
5084
|
} else {
|
|
4790
5085
|
encoded = encodeRemoveCollateral(pairIndex, index, absAmount, this.contracts.trading);
|
|
4791
5086
|
}
|
|
4792
|
-
return
|
|
5087
|
+
return encoded;
|
|
4793
5088
|
}
|
|
4794
5089
|
// ─────────────────────────────────────────────────────────────────────────
|
|
4795
5090
|
// Subgraph reads (Hyperliquid-style API)
|
|
@@ -4917,31 +5212,104 @@ var OstiumClient = class _OstiumClient {
|
|
|
4917
5212
|
streamPrices(pairIds) {
|
|
4918
5213
|
return this.subgraph.streamPrices(pairIds);
|
|
4919
5214
|
}
|
|
5215
|
+
/**
|
|
5216
|
+
* Stream price-driven updates for an existing `getOpenPositions()` response.
|
|
5217
|
+
*
|
|
5218
|
+
* Subscribes only to the unique pairs referenced by the response and emits the
|
|
5219
|
+
* full updated positions payload on each relevant price update. Pass an
|
|
5220
|
+
* existing `priceStream` to reuse a websocket your app already owns, or omit
|
|
5221
|
+
* it to let the SDK create a dedicated connection.
|
|
5222
|
+
*/
|
|
5223
|
+
streamPositionUpdates(initial, priceStream) {
|
|
5224
|
+
return this.subgraph.streamPositionUpdates(initial, priceStream);
|
|
5225
|
+
}
|
|
4920
5226
|
// ─────────────────────────────────────────────────────────────────────────
|
|
4921
5227
|
// Internal helpers
|
|
4922
5228
|
// ─────────────────────────────────────────────────────────────────────────
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
* 1. signer.prepare() — optionally wraps in delegatedAction.
|
|
4926
|
-
* 2. submitter.submit() — sends via EOA walletClient or Pimlico UserOp.
|
|
4927
|
-
*/
|
|
4928
|
-
async execute(encoded) {
|
|
4929
|
-
const { signer, submitter } = this.requireWriter();
|
|
4930
|
-
const prepared = signer.prepare(encoded);
|
|
4931
|
-
return submitter.submit(prepared);
|
|
4932
|
-
}
|
|
4933
|
-
/** Throws if the client is read-only; otherwise returns the signer + submitter. */
|
|
4934
|
-
requireWriter() {
|
|
4935
|
-
if (!this.signer || !this.submitter) {
|
|
5229
|
+
getSetupGaslessDelegationEncoded() {
|
|
5230
|
+
if (!this.selfGasless || !this.effectiveSender) {
|
|
4936
5231
|
throw new OstiumError(
|
|
4937
|
-
"
|
|
5232
|
+
"setupGaslessDelegation() is only available in Self + Gasless mode.",
|
|
4938
5233
|
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
4939
5234
|
);
|
|
4940
5235
|
}
|
|
4941
|
-
return
|
|
5236
|
+
return encodeSetDelegate(this.effectiveSender, this.contracts.trading);
|
|
5237
|
+
}
|
|
5238
|
+
getApproveUsdcEncoded(amount) {
|
|
5239
|
+
if (this.delegated) {
|
|
5240
|
+
throw new OstiumError(
|
|
5241
|
+
"approveUsdc is not available in delegated mode. The trader must approve USDC directly from their own account.",
|
|
5242
|
+
"DELEGATION_FAILED" /* DELEGATION_FAILED */
|
|
5243
|
+
);
|
|
5244
|
+
}
|
|
5245
|
+
const rawAmount = amount === "max" ? maxUint256 : parseUsdc(amount);
|
|
5246
|
+
return encodeUsdcApprove(this.contracts.tradingStorage, rawAmount, this.contracts.usdc);
|
|
5247
|
+
}
|
|
5248
|
+
getSetDelegateEncoded(delegateAddress) {
|
|
5249
|
+
return encodeSetDelegate(delegateAddress, this.contracts.trading);
|
|
5250
|
+
}
|
|
5251
|
+
getRemoveDelegateEncoded() {
|
|
5252
|
+
return encodeRemoveDelegate(this.contracts.trading);
|
|
5253
|
+
}
|
|
5254
|
+
prepareEncoded(encoded) {
|
|
5255
|
+
const signer = this.requireBuildCapability();
|
|
5256
|
+
return signer.prepare(encoded);
|
|
5257
|
+
}
|
|
5258
|
+
buildPreparedTx(encoded) {
|
|
5259
|
+
return this.toBuiltTxRequest(this.prepareEncoded(encoded));
|
|
5260
|
+
}
|
|
5261
|
+
buildDirectEoaTx(encoded) {
|
|
5262
|
+
if (!this.canBuildTransactions() || !this.traderAddress) {
|
|
5263
|
+
throw new OstiumError(
|
|
5264
|
+
"This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
|
|
5265
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
5266
|
+
);
|
|
5267
|
+
}
|
|
5268
|
+
return {
|
|
5269
|
+
kind: "eoa",
|
|
5270
|
+
to: encoded.to,
|
|
5271
|
+
data: encoded.data,
|
|
5272
|
+
value: encoded.value,
|
|
5273
|
+
from: this.traderAddress,
|
|
5274
|
+
traderAddress: this.traderAddress
|
|
5275
|
+
};
|
|
5276
|
+
}
|
|
5277
|
+
toBuiltTxRequest(encoded) {
|
|
5278
|
+
if (!this.canBuildTransactions() || !this.traderAddress || !this.effectiveSender) {
|
|
5279
|
+
throw new OstiumError(
|
|
5280
|
+
"This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
|
|
5281
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
5282
|
+
);
|
|
5283
|
+
}
|
|
5284
|
+
if (this.gasless) {
|
|
5285
|
+
return {
|
|
5286
|
+
kind: "safe",
|
|
5287
|
+
safeAddress: this.effectiveSender,
|
|
5288
|
+
traderAddress: this.traderAddress,
|
|
5289
|
+
calls: [encoded]
|
|
5290
|
+
};
|
|
5291
|
+
}
|
|
5292
|
+
return {
|
|
5293
|
+
kind: "eoa",
|
|
5294
|
+
to: encoded.to,
|
|
5295
|
+
data: encoded.data,
|
|
5296
|
+
value: encoded.value,
|
|
5297
|
+
from: this.effectiveSender,
|
|
5298
|
+
traderAddress: this.traderAddress
|
|
5299
|
+
};
|
|
5300
|
+
}
|
|
5301
|
+
async submitPrepared(encoded) {
|
|
5302
|
+
const submitter = this.requireSubmitCapability();
|
|
5303
|
+
return submitter.submit(this.prepareEncoded(encoded));
|
|
4942
5304
|
}
|
|
4943
5305
|
/** Direct EOA submission — used only in Self + Gasless for approveUsdc and setupGaslessDelegation. */
|
|
4944
|
-
async
|
|
5306
|
+
async submitDirectEoa(encoded) {
|
|
5307
|
+
if (!this.eoaSubmit) {
|
|
5308
|
+
throw new OstiumError(
|
|
5309
|
+
"This client cannot submit direct EOA transactions.",
|
|
5310
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
5311
|
+
);
|
|
5312
|
+
}
|
|
4945
5313
|
try {
|
|
4946
5314
|
return await this.eoaSubmit(encoded);
|
|
4947
5315
|
} catch (err) {
|
|
@@ -4957,6 +5325,24 @@ var OstiumClient = class _OstiumClient {
|
|
|
4957
5325
|
throw new OstiumError(`Transaction failed: ${msg}`, "SUBMISSION_FAILED" /* SUBMISSION_FAILED */, err);
|
|
4958
5326
|
}
|
|
4959
5327
|
}
|
|
5328
|
+
requireBuildCapability() {
|
|
5329
|
+
if (!this.signer) {
|
|
5330
|
+
throw new OstiumError(
|
|
5331
|
+
"This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
|
|
5332
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
5333
|
+
);
|
|
5334
|
+
}
|
|
5335
|
+
return this.signer;
|
|
5336
|
+
}
|
|
5337
|
+
requireSubmitCapability() {
|
|
5338
|
+
if (!this.submitter) {
|
|
5339
|
+
throw new OstiumError(
|
|
5340
|
+
"This client does not have submission credentials. Use the corresponding get*Tx() method or initialize with a private key.",
|
|
5341
|
+
"INVALID_CONFIG" /* INVALID_CONFIG */
|
|
5342
|
+
);
|
|
5343
|
+
}
|
|
5344
|
+
return this.submitter;
|
|
5345
|
+
}
|
|
4960
5346
|
resolveSlippage(override) {
|
|
4961
5347
|
return override ?? this.defaultSlippageBps;
|
|
4962
5348
|
}
|
|
@@ -5839,7 +6225,7 @@ async function actionOpenTrade(client2) {
|
|
|
5839
6225
|
const result = await spin(
|
|
5840
6226
|
"Submitting openTrade\u2026",
|
|
5841
6227
|
() => client2.openTrade({
|
|
5842
|
-
|
|
6228
|
+
pairId,
|
|
5843
6229
|
buy: buy === "true",
|
|
5844
6230
|
price,
|
|
5845
6231
|
collateral,
|
|
@@ -5929,7 +6315,7 @@ async function actionCloseTrade(client2) {
|
|
|
5929
6315
|
}
|
|
5930
6316
|
async function actionCancelOrder(client2) {
|
|
5931
6317
|
let cancelType = "limit" /* Limit */;
|
|
5932
|
-
let
|
|
6318
|
+
let pairIdInput = "0";
|
|
5933
6319
|
let orderIndex = "0";
|
|
5934
6320
|
let orderId = "";
|
|
5935
6321
|
let retry = false;
|
|
@@ -5948,12 +6334,12 @@ async function actionCancelOrder(client2) {
|
|
|
5948
6334
|
}
|
|
5949
6335
|
case 1: {
|
|
5950
6336
|
if (cancelType === "limit" /* Limit */) {
|
|
5951
|
-
const v = await wtext({ message: "Pair
|
|
6337
|
+
const v = await wtext({ message: "Pair ID", initialValue: pairIdInput });
|
|
5952
6338
|
if (v === BACK) {
|
|
5953
6339
|
step--;
|
|
5954
6340
|
break;
|
|
5955
6341
|
}
|
|
5956
|
-
|
|
6342
|
+
pairIdInput = v;
|
|
5957
6343
|
step++;
|
|
5958
6344
|
break;
|
|
5959
6345
|
} else {
|
|
@@ -6000,7 +6386,7 @@ async function actionCancelOrder(client2) {
|
|
|
6000
6386
|
}
|
|
6001
6387
|
}
|
|
6002
6388
|
}
|
|
6003
|
-
const cancelParams = cancelType === "limit" /* Limit */ ? { type: "limit" /* Limit */, pairId: parseInt(
|
|
6389
|
+
const cancelParams = cancelType === "limit" /* Limit */ ? { type: "limit" /* Limit */, pairId: parseInt(pairIdInput), idx: parseInt(orderIndex) } : cancelType === "pendingClose" /* PendingClose */ ? { type: "pendingClose" /* PendingClose */, orderId: parseInt(orderId), retry } : { type: "pendingOpen" /* PendingOpen */, orderId: parseInt(orderId) };
|
|
6004
6390
|
const result = await spin(
|
|
6005
6391
|
"Submitting cancelOrder\u2026",
|
|
6006
6392
|
() => client2.cancelOrder(cancelParams)
|
|
@@ -6010,7 +6396,7 @@ async function actionCancelOrder(client2) {
|
|
|
6010
6396
|
async function actionModifyOrder(client2) {
|
|
6011
6397
|
const fields = await p.group(
|
|
6012
6398
|
{
|
|
6013
|
-
pairIndex: () => p.text({ message: "Pair
|
|
6399
|
+
pairIndex: () => p.text({ message: "Pair ID", initialValue: "0" }),
|
|
6014
6400
|
index: () => p.text({ message: "Trade / order index", initialValue: "0" }),
|
|
6015
6401
|
price: () => p.text({ message: "New limit price (optional)", placeholder: "leave blank to skip" }),
|
|
6016
6402
|
tp: () => p.text({ message: "New take profit (optional)", placeholder: "leave blank to skip" }),
|
|
@@ -6033,7 +6419,7 @@ async function actionModifyOrder(client2) {
|
|
|
6033
6419
|
async function actionUpdateCollateral(client2) {
|
|
6034
6420
|
const fields = await p.group(
|
|
6035
6421
|
{
|
|
6036
|
-
pairIndex: () => p.text({ message: "Pair
|
|
6422
|
+
pairIndex: () => p.text({ message: "Pair ID", initialValue: "0" }),
|
|
6037
6423
|
index: () => p.text({ message: "Trade index", initialValue: "0" }),
|
|
6038
6424
|
amount: () => p.text({
|
|
6039
6425
|
message: "Amount (positive = add, negative = remove)",
|
|
@@ -6172,7 +6558,7 @@ async function actionViewPrices(client2) {
|
|
|
6172
6558
|
}
|
|
6173
6559
|
}
|
|
6174
6560
|
async function actionViewPositions(client2) {
|
|
6175
|
-
const { pairPositions, marginSummary
|
|
6561
|
+
const { pairPositions, marginSummary } = await spin(
|
|
6176
6562
|
"Fetching open positions\u2026",
|
|
6177
6563
|
() => client2.getOpenPositions()
|
|
6178
6564
|
);
|
|
@@ -6209,7 +6595,8 @@ async function actionViewPositions(client2) {
|
|
|
6209
6595
|
p.log.info(`Total collateral: $${parseFloat(marginSummary.totalCollateralUsed).toFixed(4)}`);
|
|
6210
6596
|
p.log.info(`Total notional: $${parseFloat(marginSummary.totalNtlPos).toFixed(4)}`);
|
|
6211
6597
|
p.log.info(`Unrealized PnL: $${parseFloat(marginSummary.totalRawPnlUsd).toFixed(4)}`);
|
|
6212
|
-
p.log.info(`
|
|
6598
|
+
p.log.info(`Cum rollover: $${parseFloat(marginSummary.totalCumRollover).toFixed(4)}`);
|
|
6599
|
+
p.log.info(`Max withdrawable: $${parseFloat(marginSummary.totalWithdrawable).toFixed(4)}`);
|
|
6213
6600
|
}
|
|
6214
6601
|
async function actionViewFills(client2) {
|
|
6215
6602
|
const limitRaw = await p.text({
|