@ostium/builder-sdk 0.1.0 → 0.3.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/index.js CHANGED
@@ -254,11 +254,20 @@ var CancelOrderType = /* @__PURE__ */ ((CancelOrderType2) => {
254
254
  // src/config.ts
255
255
  var DEFAULT_PIMLICO_URL = "https://builder.ostium.io/v1/pimlico/sponsor?chainId=42161";
256
256
  var DEFAULT_PIMLICO_URL_TESTNET = "https://builder.ostium.io/v1/pimlico/sponsor?chainId=421614";
257
- function isGasless(config) {
258
- return !!config.pimlicoUrl;
257
+ function hasPrivateKey(config) {
258
+ return typeof config.privateKey === "string" && config.privateKey.length > 0;
259
259
  }
260
- function isDelegated(config) {
261
- return !!config.traderAddress;
260
+ function resolveMode(config) {
261
+ if (config.mode) return config.mode;
262
+ if (config.privateKey) {
263
+ if (config.pimlicoUrl) return config.traderAddress ? "delegated-gasless" : "self-gasless";
264
+ return config.traderAddress ? "delegated-self" : "self-self";
265
+ }
266
+ if (config.safeAddress && config.delegateAddress && config.traderAddress) return "delegated-gasless";
267
+ if (config.safeAddress && config.traderAddress) return "self-gasless";
268
+ if (config.delegateAddress && config.traderAddress) return "delegated-self";
269
+ if (config.traderAddress) return "self-self";
270
+ throw new Error("Unable to infer client mode from config");
262
271
  }
263
272
 
264
273
  // src/errors.ts
@@ -2661,8 +2670,8 @@ var DelegatedSignerStrategy = class {
2661
2670
  };
2662
2671
 
2663
2672
  // src/signer/index.ts
2664
- function createSigner(config, traderAddress, selfGasless = false) {
2665
- if (isDelegated(config) || selfGasless) {
2673
+ function createSigner(mode, traderAddress) {
2674
+ if (mode !== "self-self") {
2666
2675
  return new DelegatedSignerStrategy(traderAddress);
2667
2676
  }
2668
2677
  return new SelfSignerStrategy(traderAddress);
@@ -2686,6 +2695,12 @@ var SelfSubmissionStrategy = class {
2686
2695
  effectiveSender;
2687
2696
  send;
2688
2697
  constructor(config, testnet) {
2698
+ if (!config.privateKey) {
2699
+ throw new OstiumError(
2700
+ "privateKey is required for self-submission mode",
2701
+ "INVALID_CONFIG" /* INVALID_CONFIG */
2702
+ );
2703
+ }
2689
2704
  if (!config.rpcUrl) {
2690
2705
  throw new OstiumError(
2691
2706
  "rpcUrl is required for self-submission mode",
@@ -2751,6 +2766,12 @@ var GaslessSubmissionStrategy = class _GaslessSubmissionStrategy {
2751
2766
  this.effectiveSender = safeAccount.address;
2752
2767
  }
2753
2768
  static async create(config, testnet) {
2769
+ if (!config.privateKey) {
2770
+ throw new OstiumError(
2771
+ "privateKey is required for gasless submission mode",
2772
+ "INVALID_CONFIG" /* INVALID_CONFIG */
2773
+ );
2774
+ }
2754
2775
  if (!config.pimlicoUrl) {
2755
2776
  throw new OstiumError(
2756
2777
  "pimlicoUrl is required for gasless mode",
@@ -2819,8 +2840,8 @@ var GaslessSubmissionStrategy = class _GaslessSubmissionStrategy {
2819
2840
  };
2820
2841
 
2821
2842
  // src/submitter/index.ts
2822
- async function createSubmitter(config, testnet) {
2823
- if (isGasless(config)) {
2843
+ async function createSubmitter(config, testnet, mode) {
2844
+ if (mode === "self-gasless" || mode === "delegated-gasless") {
2824
2845
  return GaslessSubmissionStrategy.create(config, testnet);
2825
2846
  }
2826
2847
  if (!config.rpcUrl) {
@@ -2917,6 +2938,17 @@ query GetTraderOpenTrades($trader: String!, $skip: Int!, $first: Int!) {
2917
2938
  ${PAIR_FULL}
2918
2939
  }
2919
2940
  }`;
2941
+ var GetAllOpenTradesQuery = `
2942
+ query GetAllOpenTrades($skip: Int!, $first: Int!) {
2943
+ trades(
2944
+ where: { isOpen: true }
2945
+ skip: $skip first: $first
2946
+ orderBy: timestamp orderDirection: desc
2947
+ ) {
2948
+ ${TRADE_CORE}
2949
+ ${PAIR_FULL}
2950
+ }
2951
+ }`;
2920
2952
  var ORDER_FIELDS = `
2921
2953
  id tradeID limitID trader
2922
2954
  pair { id from to group { id name } }
@@ -2996,9 +3028,9 @@ query GetTraderActiveLimits($trader: String!, $skip: Int!, $first: Int!) {
2996
3028
  }`;
2997
3029
 
2998
3030
  // src/data/internal/pagination.ts
2999
- async function paginateAll(fetcher, batchSize = 1e3, max = Infinity) {
3031
+ async function paginateAll(fetcher, batchSize = 1e3, max = Infinity, initialSkip = 0) {
3000
3032
  const results = [];
3001
- let skip = 0;
3033
+ let skip = initialSkip;
3002
3034
  while (results.length < max) {
3003
3035
  const remaining = max - results.length;
3004
3036
  const fetchSize = Math.min(batchSize, remaining);
@@ -3220,10 +3252,11 @@ async function fetchLivePrices(builderApiUrl) {
3220
3252
  }
3221
3253
  return result;
3222
3254
  }
3223
- function rawTickToPublic(item) {
3255
+ function rawTickToPublic(item, pairId) {
3224
3256
  const from = normalizePairName(item.from);
3225
3257
  const to = normalizePairName(item.to);
3226
3258
  return {
3259
+ pairId,
3227
3260
  feedId: item.feed_id,
3228
3261
  pair: `${from}-${to}`,
3229
3262
  from,
@@ -3270,9 +3303,12 @@ var OstiumPriceStream = class _OstiumPriceStream {
3270
3303
  snapshotHandlers = /* @__PURE__ */ new Set();
3271
3304
  /** pairId (string) → raw "FROM-TO" name for the WS API. */
3272
3305
  pairRawNameCache;
3273
- constructor(ws, pairRawNameCache, initialSubscribeRawPairs) {
3306
+ /** raw "FROM-TO" name → pairId (string) for mapping incoming ticks back to SDK ids. */
3307
+ rawNamePairIdCache;
3308
+ constructor(ws, pairRawNameCache, rawNamePairIdCache, initialSubscribeRawPairs) {
3274
3309
  this.ws = ws;
3275
3310
  this.pairRawNameCache = pairRawNameCache;
3311
+ this.rawNamePairIdCache = rawNamePairIdCache;
3276
3312
  if (initialSubscribeRawPairs?.length) {
3277
3313
  ws.once("open", () => {
3278
3314
  ws.send(JSON.stringify({ type: "subscribe", pairs: initialSubscribeRawPairs }));
@@ -3286,10 +3322,13 @@ var OstiumPriceStream = class _OstiumPriceStream {
3286
3322
  return;
3287
3323
  }
3288
3324
  if (msg.type === "snapshot" && Array.isArray(msg.data)) {
3289
- const ticks = msg.data.map(rawTickToPublic);
3325
+ const ticks = msg.data.map(
3326
+ (item) => rawTickToPublic(item, this.rawNamePairIdCache.get(`${item.from}-${item.to}`))
3327
+ );
3290
3328
  for (const h of this.snapshotHandlers) h(ticks);
3291
3329
  } else if (msg.type === "tick" && msg.data) {
3292
- const tick = rawTickToPublic(msg.data);
3330
+ const item = msg.data;
3331
+ const tick = rawTickToPublic(item, this.rawNamePairIdCache.get(`${item.from}-${item.to}`));
3293
3332
  for (const h of this.tickHandlers) h(tick);
3294
3333
  }
3295
3334
  });
@@ -3315,7 +3354,11 @@ var OstiumPriceStream = class _OstiumPriceStream {
3315
3354
  const ws = new WS(url, {
3316
3355
  headers: { "User-Agent": "ostium-sdk", "Accept": "*/*" }
3317
3356
  });
3318
- return new _OstiumPriceStream(ws, pairRawNameCache, initial);
3357
+ const rawNamePairIdCache = /* @__PURE__ */ new Map();
3358
+ for (const [pairId, rawName] of pairRawNameCache.entries()) {
3359
+ rawNamePairIdCache.set(rawName, pairId);
3360
+ }
3361
+ return new _OstiumPriceStream(ws, pairRawNameCache, rawNamePairIdCache, initial);
3319
3362
  }
3320
3363
  /**
3321
3364
  * Register a callback that fires on every incoming price tick.
@@ -3391,6 +3434,37 @@ var OstiumPriceStream = class _OstiumPriceStream {
3391
3434
  }
3392
3435
  };
3393
3436
 
3437
+ // src/data/internal/aggregations.ts
3438
+ function aggregateMarginSummary(pairPositions) {
3439
+ let accountValue = 0;
3440
+ let totalCollateralUsed = 0;
3441
+ let totalNtlPos = 0;
3442
+ let totalRawPnlUsd = 0;
3443
+ let totalCumRollover = 0;
3444
+ let totalWithdrawable = 0;
3445
+ for (const { position } of pairPositions) {
3446
+ const collateral = parseFloat(position.collateralUsed) || 0;
3447
+ const ntl = parseFloat(position.ntl) || 0;
3448
+ const pnl = parseFloat(position.unrealizedPnl) || 0;
3449
+ const rollover = parseFloat(position.cumRollover) || 0;
3450
+ const withdrawable = parseFloat(position.maxWithdrawable) || 0;
3451
+ accountValue += collateral + pnl;
3452
+ totalCollateralUsed += collateral;
3453
+ totalNtlPos += ntl;
3454
+ totalRawPnlUsd += pnl;
3455
+ totalCumRollover += rollover;
3456
+ totalWithdrawable += withdrawable;
3457
+ }
3458
+ return {
3459
+ accountValue: accountValue.toString(),
3460
+ totalCollateralUsed: totalCollateralUsed.toString(),
3461
+ totalNtlPos: totalNtlPos.toString(),
3462
+ totalRawPnlUsd: totalRawPnlUsd.toString(),
3463
+ totalCumRollover: totalCumRollover.toString(),
3464
+ totalWithdrawable: totalWithdrawable.toString()
3465
+ };
3466
+ }
3467
+
3394
3468
  // src/data/internal/formatters.ts
3395
3469
  var MIN_NOTIONAL = "5.0";
3396
3470
  var MIN_NOTIONAL_NUM = 5;
@@ -3492,14 +3566,14 @@ function formatPosition(raw, price, pnl, maxLeverage) {
3492
3566
  returnOnEquity: roe.toString(),
3493
3567
  liquidationPx: pnl.liquidationPrice.toString(),
3494
3568
  collateralUsed: collateral.toString(),
3495
- cumRollover: pnl.rollover.toString(),
3569
+ cumRollover: (pnl.rollover * -1).toString(),
3496
3570
  ...raw.takeProfitPrice && raw.takeProfitPrice !== "0" ? { tpPx: formatTokens(raw.takeProfitPrice).toString() } : {},
3497
3571
  ...raw.stopLossPrice && raw.stopLossPrice !== "0" ? { slPx: formatTokens(raw.stopLossPrice).toString() } : {},
3498
3572
  openTimestamp: parseInt(raw.timestamp || "0") * 1e3,
3499
3573
  isDayTrade: raw.isDayTrade ?? false,
3500
- maxLeverage: maxLeverage.toString()
3501
- },
3502
- maxWithdrawable: maxWithdrawForPosition(collateral, leverage, maxLeverage).toString()
3574
+ maxLeverage: maxLeverage.toString(),
3575
+ maxWithdrawable: maxWithdrawForPosition(collateral, leverage, maxLeverage).toString()
3576
+ }
3503
3577
  };
3504
3578
  }
3505
3579
  function formatFill(raw) {
@@ -3576,6 +3650,131 @@ function formatOpenOrder(raw) {
3576
3650
  timestamp: parseInt(raw.initiatedAt || "0") * 1e3
3577
3651
  };
3578
3652
  }
3653
+
3654
+ // src/data/internal/position-updates.ts
3655
+ function cloneResponse(response) {
3656
+ return {
3657
+ pairPositions: response.pairPositions.map((pairPos) => ({
3658
+ position: { ...pairPos.position }
3659
+ })),
3660
+ marginSummary: { ...response.marginSummary },
3661
+ time: response.time
3662
+ };
3663
+ }
3664
+ function applyTickToPosition(position, tick) {
3665
+ const size = parseFloat(position.szi) || 0;
3666
+ const entry = parseFloat(position.entryPx) || 0;
3667
+ const collateral = parseFloat(position.collateralUsed) || 0;
3668
+ const maxLeverage = parseFloat(position.maxLeverage) || 0;
3669
+ const rollover = parseFloat(position.cumRollover) || 0;
3670
+ const isLong = position.side === "B";
3671
+ const ntl = size * tick.mid;
3672
+ const rawPnl = isLong ? (tick.mid - entry) * size : (entry - tick.mid) * size;
3673
+ const netPnl = rawPnl + rollover;
3674
+ const roe = collateral > 0 ? netPnl / collateral : 0;
3675
+ const currentLeverage = collateral > 0 ? ntl / collateral : 0;
3676
+ const maxWithdrawable = maxWithdrawForPosition(collateral, currentLeverage, maxLeverage);
3677
+ return {
3678
+ ...position,
3679
+ ntl: ntl.toString(),
3680
+ unrealizedPnl: netPnl.toString(),
3681
+ returnOnEquity: roe.toString(),
3682
+ maxWithdrawable: maxWithdrawable.toString()
3683
+ };
3684
+ }
3685
+ function updateResponseWithTick(current, tick, timestampMs) {
3686
+ if (!tick.pairId) return current;
3687
+ let changed = false;
3688
+ const pairPositions = current.pairPositions.map((pairPos) => {
3689
+ if (String(pairPos.position.pairId) !== String(tick.pairId)) {
3690
+ return pairPos;
3691
+ }
3692
+ changed = true;
3693
+ return {
3694
+ ...pairPos,
3695
+ position: applyTickToPosition(pairPos.position, tick)
3696
+ };
3697
+ });
3698
+ if (!changed) return current;
3699
+ return {
3700
+ pairPositions,
3701
+ marginSummary: aggregateMarginSummary(pairPositions),
3702
+ time: timestampMs
3703
+ };
3704
+ }
3705
+ var OstiumPositionUpdatesStream = class {
3706
+ priceStream;
3707
+ updateHandlers = /* @__PURE__ */ new Set();
3708
+ current;
3709
+ trackedPairIds;
3710
+ constructor(initial, trackedPairIds, priceStream) {
3711
+ this.current = cloneResponse(initial);
3712
+ this.priceStream = priceStream;
3713
+ this.trackedPairIds = new Set(trackedPairIds);
3714
+ if (!priceStream) return;
3715
+ priceStream.onSnapshot((ticks) => {
3716
+ this.ingestSnapshot(ticks);
3717
+ });
3718
+ priceStream.onTick((tick) => {
3719
+ this.ingestTick(tick);
3720
+ });
3721
+ }
3722
+ onUpdate(handler) {
3723
+ this.updateHandlers.add(handler);
3724
+ return () => {
3725
+ this.updateHandlers.delete(handler);
3726
+ };
3727
+ }
3728
+ onOpen(handler) {
3729
+ this.priceStream?.onOpen(handler);
3730
+ return this;
3731
+ }
3732
+ onError(handler) {
3733
+ this.priceStream?.onError(handler);
3734
+ return this;
3735
+ }
3736
+ onClose(handler) {
3737
+ this.priceStream?.onClose(handler);
3738
+ return this;
3739
+ }
3740
+ getCurrent() {
3741
+ return cloneResponse(this.current);
3742
+ }
3743
+ /**
3744
+ * Apply a single externally sourced price tick to the tracked positions.
3745
+ *
3746
+ * Use this when your app already has its own websocket connection and you want
3747
+ * the SDK to handle only the position recalculation logic.
3748
+ */
3749
+ ingestTick(tick) {
3750
+ if (!tick.pairId || !this.trackedPairIds.has(String(tick.pairId))) return;
3751
+ const next = updateResponseWithTick(this.current, tick, tick.timestampSeconds * 1e3);
3752
+ if (next !== this.current) {
3753
+ this.current = next;
3754
+ this.emit(next);
3755
+ }
3756
+ }
3757
+ /**
3758
+ * Apply a batch of externally sourced ticks, such as a websocket snapshot.
3759
+ */
3760
+ ingestSnapshot(ticks) {
3761
+ let next = this.current;
3762
+ for (const tick of ticks) {
3763
+ if (!tick.pairId || !this.trackedPairIds.has(String(tick.pairId))) continue;
3764
+ next = updateResponseWithTick(next, tick, tick.timestampSeconds * 1e3);
3765
+ }
3766
+ if (next !== this.current) {
3767
+ this.current = next;
3768
+ this.emit(next);
3769
+ }
3770
+ }
3771
+ close() {
3772
+ this.priceStream?.close();
3773
+ }
3774
+ emit(positions) {
3775
+ for (const handler of this.updateHandlers) handler(cloneResponse(positions));
3776
+ }
3777
+ };
3579
3778
  var EMPTY_RESULT = { long: [], short: [] };
3580
3779
  var ORDERBOOK_MAX_LEVELS = 20;
3581
3780
  function generateLogSpacedNotionals(levels, capacity) {
@@ -3760,31 +3959,40 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
3760
3959
  * PnL fields are zeroed.
3761
3960
  */
3762
3961
  async getOpenPositions(params) {
3763
- const { user, blockNumber } = params;
3962
+ const { user, blockNumber, limit = Infinity, skip: initialSkip = 0 } = params;
3963
+ const isGlobal = user === "ALL";
3764
3964
  const [trades, prices] = await Promise.all([
3765
- paginateAll(async (skip, first) => {
3766
- const data = await this.query(
3767
- GetTraderOpenTradesQuery,
3768
- { trader: user.toLowerCase(), skip, first }
3769
- );
3770
- return data.trades;
3771
- }),
3965
+ paginateAll(
3966
+ async (skip, first) => {
3967
+ if (isGlobal) {
3968
+ const data2 = await this.query(
3969
+ GetAllOpenTradesQuery,
3970
+ { skip, first }
3971
+ );
3972
+ return data2.trades;
3973
+ }
3974
+ const data = await this.query(
3975
+ GetTraderOpenTradesQuery,
3976
+ { trader: user.toLowerCase(), skip, first }
3977
+ );
3978
+ return data.trades;
3979
+ },
3980
+ 1e3,
3981
+ limit,
3982
+ initialSkip
3983
+ ),
3772
3984
  this.fetchLivePricesSafe()
3773
3985
  ]);
3774
- let totalWithdrawable = 0;
3775
3986
  const pairPositions = trades.map((trade) => {
3776
3987
  const price = prices[`${trade.pair.from}/${trade.pair.to}`];
3777
3988
  const pnl = price && blockNumber ? getTradePnL(trade, price, blockNumber) : EMPTY_PNL;
3778
3989
  const maxLev = trade.isDayTrade ? formatLeverage(trade.pair.overnightMaxLeverage) : pairMaxLeverage(trade.pair);
3779
- const pairPos = formatPosition(trade, price, pnl, maxLev);
3780
- totalWithdrawable += parseFloat(pairPos.maxWithdrawable);
3781
- return pairPos;
3990
+ return formatPosition(trade, price, pnl, maxLev);
3782
3991
  });
3783
3992
  const marginSummary = aggregateMarginSummary(pairPositions);
3784
3993
  return {
3785
3994
  pairPositions,
3786
3995
  marginSummary,
3787
- withdrawable: totalWithdrawable.toString(),
3788
3996
  time: Date.now()
3789
3997
  };
3790
3998
  }
@@ -4035,6 +4243,24 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
4035
4243
  });
4036
4244
  return OstiumPriceStream.connect(this.builderApiUrl, rawNames, cache);
4037
4245
  }
4246
+ /**
4247
+ * Stream price-driven updates for an existing `getOpenPositions()` response.
4248
+ *
4249
+ * The SDK subscribes only to the unique pairs present in `initial.pairPositions`,
4250
+ * recalculates price-sensitive fields for affected positions on each tick, and
4251
+ * emits the full updated response.
4252
+ *
4253
+ * Pass an existing `priceStream` to reuse a websocket connection your app has
4254
+ * already opened. Omit it to let the SDK open and manage a dedicated
4255
+ * connection for the tracked pair ids.
4256
+ */
4257
+ streamPositionUpdates(initial, priceStream) {
4258
+ const pairIds = [...new Set(initial.pairPositions.map(({ position }) => String(position.pairId)))];
4259
+ if (pairIds.length === 0) {
4260
+ return new OstiumPositionUpdatesStream(initial, []);
4261
+ }
4262
+ return new OstiumPositionUpdatesStream(initial, pairIds, priceStream ?? this.streamPrices(pairIds));
4263
+ }
4038
4264
  // ─────────────────────────────────────────────────────────────────────────
4039
4265
  // Internal — fetchers, cache, query wrapper
4040
4266
  // ─────────────────────────────────────────────────────────────────────────
@@ -4093,43 +4319,28 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
4093
4319
  }
4094
4320
  }
4095
4321
  };
4096
- function aggregateMarginSummary(pairPositions) {
4097
- let accountValue = 0;
4098
- let totalCollateralUsed = 0;
4099
- let totalNtlPos = 0;
4100
- let totalRawPnlUsd = 0;
4101
- for (const { position } of pairPositions) {
4102
- const collateral = parseFloat(position.collateralUsed) || 0;
4103
- const ntl = parseFloat(position.ntl) || 0;
4104
- const pnl = parseFloat(position.unrealizedPnl) || 0;
4105
- accountValue += collateral + pnl;
4106
- totalCollateralUsed += collateral;
4107
- totalNtlPos += ntl;
4108
- totalRawPnlUsd += pnl;
4109
- }
4110
- return {
4111
- accountValue: accountValue.toString(),
4112
- totalCollateralUsed: totalCollateralUsed.toString(),
4113
- totalNtlPos: totalNtlPos.toString(),
4114
- totalRawPnlUsd: totalRawPnlUsd.toString()
4115
- };
4116
- }
4117
4322
 
4118
4323
  // src/client.ts
4119
4324
  var HEX64_RE = /^0x[0-9a-fA-F]{64}$/;
4120
4325
  var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
4121
4326
  function validateConfig(config) {
4122
- if (!HEX64_RE.test(config.privateKey)) {
4327
+ if (config.privateKey && !HEX64_RE.test(config.privateKey)) {
4123
4328
  throw new OstiumError(
4124
4329
  "privateKey must be a 64-character hex string prefixed with 0x",
4125
4330
  "INVALID_CONFIG" /* INVALID_CONFIG */
4126
4331
  );
4127
4332
  }
4128
- if (config.traderAddress && !ADDRESS_RE.test(config.traderAddress)) {
4129
- throw new OstiumError(
4130
- "traderAddress must be a valid 42-character Ethereum address (0x + 40 hex chars)",
4131
- "INVALID_CONFIG" /* INVALID_CONFIG */
4132
- );
4333
+ for (const [label, value] of [
4334
+ ["traderAddress", config.traderAddress],
4335
+ ["delegateAddress", config.delegateAddress],
4336
+ ["safeAddress", config.safeAddress]
4337
+ ]) {
4338
+ if (value && !ADDRESS_RE.test(value)) {
4339
+ throw new OstiumError(
4340
+ `${label} must be a valid 42-character Ethereum address (0x + 40 hex chars)`,
4341
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4342
+ );
4343
+ }
4133
4344
  }
4134
4345
  if (config.slippageBps !== void 0 && (config.slippageBps < 0 || config.slippageBps > 500)) {
4135
4346
  throw new OstiumError(
@@ -4143,10 +4354,45 @@ function validateConfig(config) {
4143
4354
  "INVALID_CONFIG" /* INVALID_CONFIG */
4144
4355
  );
4145
4356
  }
4357
+ let mode;
4358
+ try {
4359
+ mode = resolveMode(config);
4360
+ } catch {
4361
+ throw new OstiumError(
4362
+ "Could not infer client mode from config. Provide the required addresses for the selected factory.",
4363
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4364
+ );
4365
+ }
4366
+ if (mode === "self-self" && !config.privateKey && !config.traderAddress) {
4367
+ throw new OstiumError(
4368
+ "Self + Self mode requires traderPrivateKey for submission or traderAddress for build-only usage.",
4369
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4370
+ );
4371
+ }
4372
+ if (mode === "self-gasless" && !config.privateKey && (!config.traderAddress || !config.safeAddress)) {
4373
+ throw new OstiumError(
4374
+ "Self + Gasless build-only mode requires traderAddress and safeAddress.",
4375
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4376
+ );
4377
+ }
4378
+ if (mode === "delegated-self" && (!config.traderAddress || !config.privateKey && !config.delegateAddress)) {
4379
+ throw new OstiumError(
4380
+ "Delegated + Self mode requires traderAddress and either delegatePrivateKey or delegateAddress.",
4381
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4382
+ );
4383
+ }
4384
+ if (mode === "delegated-gasless" && (!config.traderAddress || !config.privateKey && (!config.delegateAddress || !config.safeAddress))) {
4385
+ throw new OstiumError(
4386
+ "Delegated + Gasless mode requires traderAddress and either delegatePrivateKey or both delegateAddress and safeAddress.",
4387
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4388
+ );
4389
+ }
4146
4390
  }
4147
4391
  var OstiumClient = class _OstiumClient {
4148
4392
  signer;
4149
4393
  submitter;
4394
+ traderAddress;
4395
+ effectiveSender;
4150
4396
  contracts;
4151
4397
  publicClient;
4152
4398
  defaultSlippageBps;
@@ -4161,6 +4407,8 @@ var OstiumClient = class _OstiumClient {
4161
4407
  constructor(internals) {
4162
4408
  this.signer = internals.signer;
4163
4409
  this.submitter = internals.submitter;
4410
+ this.traderAddress = internals.traderAddress;
4411
+ this.effectiveSender = internals.effectiveSender;
4164
4412
  this.contracts = internals.contracts;
4165
4413
  this.publicClient = internals.publicClient;
4166
4414
  this.defaultSlippageBps = internals.defaultSlippageBps;
@@ -4185,17 +4433,20 @@ var OstiumClient = class _OstiumClient {
4185
4433
  * });
4186
4434
  * await client.openTrade({ ... });
4187
4435
  * ```
4188
- */
4436
+ */
4189
4437
  static async createSelfAndSelf(params) {
4190
- return _OstiumClient._fromConfig({
4191
- privateKey: params.traderPrivateKey,
4192
- rpcUrl: params.rpcUrl,
4438
+ const base = {
4439
+ mode: "self-self",
4193
4440
  testnet: params.testnet,
4194
4441
  slippageBps: params.slippageBps,
4195
4442
  builder: params.builder,
4196
4443
  subgraphUrl: params.subgraphUrl,
4197
- builderApiUrl: params.builderApiUrl
4198
- });
4444
+ builderApiUrl: params.builderApiUrl,
4445
+ rpcUrl: params.rpcUrl
4446
+ };
4447
+ return _OstiumClient._fromConfig(
4448
+ "traderPrivateKey" in params ? { ...base, privateKey: params.traderPrivateKey } : { ...base, traderAddress: params.traderAddress }
4449
+ );
4199
4450
  }
4200
4451
  /**
4201
4452
  * **Self + Gasless** — your EOA owns everything; a Safe relays trades for free.
@@ -4219,17 +4470,27 @@ var OstiumClient = class _OstiumClient {
4219
4470
  */
4220
4471
  static async createSelfAndGasless(params) {
4221
4472
  const defaultUrl = params.testnet ? DEFAULT_PIMLICO_URL_TESTNET : DEFAULT_PIMLICO_URL;
4222
- return _OstiumClient._fromConfig({
4223
- privateKey: params.traderPrivateKey,
4224
- pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4473
+ const base = {
4474
+ mode: "self-gasless",
4225
4475
  rpcUrl: params.rpcUrl,
4226
- sponsorshipPolicyId: params.sponsorshipPolicyId,
4227
4476
  testnet: params.testnet,
4228
4477
  slippageBps: params.slippageBps,
4229
4478
  builder: params.builder,
4230
4479
  subgraphUrl: params.subgraphUrl,
4231
4480
  builderApiUrl: params.builderApiUrl
4232
- });
4481
+ };
4482
+ return _OstiumClient._fromConfig(
4483
+ "traderPrivateKey" in params ? {
4484
+ ...base,
4485
+ privateKey: params.traderPrivateKey,
4486
+ pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4487
+ sponsorshipPolicyId: params.sponsorshipPolicyId
4488
+ } : {
4489
+ ...base,
4490
+ traderAddress: params.traderAddress,
4491
+ safeAddress: params.safeAddress
4492
+ }
4493
+ );
4233
4494
  }
4234
4495
  /**
4235
4496
  * **Delegated + Self** — a delegate EOA signs and pays gas on behalf of a trader address.
@@ -4249,8 +4510,8 @@ var OstiumClient = class _OstiumClient {
4249
4510
  * ```
4250
4511
  */
4251
4512
  static async createDelegatedAndSelf(params) {
4252
- return _OstiumClient._fromConfig({
4253
- privateKey: params.delegatePrivateKey,
4513
+ const base = {
4514
+ mode: "delegated-self",
4254
4515
  traderAddress: params.traderAddress,
4255
4516
  rpcUrl: params.rpcUrl,
4256
4517
  testnet: params.testnet,
@@ -4258,7 +4519,10 @@ var OstiumClient = class _OstiumClient {
4258
4519
  builder: params.builder,
4259
4520
  subgraphUrl: params.subgraphUrl,
4260
4521
  builderApiUrl: params.builderApiUrl
4261
- });
4522
+ };
4523
+ return _OstiumClient._fromConfig(
4524
+ "delegatePrivateKey" in params ? { ...base, privateKey: params.delegatePrivateKey } : { ...base, delegateAddress: params.delegateAddress }
4525
+ );
4262
4526
  }
4263
4527
  /**
4264
4528
  * **Delegated + Gasless** — a Safe derived from the delegate key submits sponsored
@@ -4279,18 +4543,28 @@ var OstiumClient = class _OstiumClient {
4279
4543
  */
4280
4544
  static async createDelegatedAndGasless(params) {
4281
4545
  const defaultUrl = params.testnet ? DEFAULT_PIMLICO_URL_TESTNET : DEFAULT_PIMLICO_URL;
4282
- return _OstiumClient._fromConfig({
4283
- privateKey: params.delegatePrivateKey,
4546
+ const base = {
4547
+ mode: "delegated-gasless",
4284
4548
  traderAddress: params.traderAddress,
4285
- pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4286
4549
  rpcUrl: params.rpcUrl,
4287
- sponsorshipPolicyId: params.sponsorshipPolicyId,
4288
4550
  testnet: params.testnet,
4289
4551
  slippageBps: params.slippageBps,
4290
4552
  builder: params.builder,
4291
4553
  subgraphUrl: params.subgraphUrl,
4292
4554
  builderApiUrl: params.builderApiUrl
4293
- });
4555
+ };
4556
+ return _OstiumClient._fromConfig(
4557
+ "delegatePrivateKey" in params ? {
4558
+ ...base,
4559
+ privateKey: params.delegatePrivateKey,
4560
+ pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4561
+ sponsorshipPolicyId: params.sponsorshipPolicyId
4562
+ } : {
4563
+ ...base,
4564
+ delegateAddress: params.delegateAddress,
4565
+ safeAddress: params.safeAddress
4566
+ }
4567
+ );
4294
4568
  }
4295
4569
  /**
4296
4570
  * **Read-only client** — no signer, no submitter, no privateKey required.
@@ -4335,6 +4609,7 @@ var OstiumClient = class _OstiumClient {
4335
4609
  /** Internal factory shared by all public constructors. */
4336
4610
  static async _fromConfig(config) {
4337
4611
  validateConfig(config);
4612
+ const mode = resolveMode(config);
4338
4613
  const testnet = config.testnet ?? false;
4339
4614
  const networkConfig = getNetworkConfig(testnet);
4340
4615
  const chain = testnet ? arbitrumSepolia : arbitrum;
@@ -4344,18 +4619,29 @@ var OstiumClient = class _OstiumClient {
4344
4619
  tradingStorage: networkConfig.contracts.tradingStorage,
4345
4620
  usdc: networkConfig.contracts.usdc
4346
4621
  };
4347
- const selfGasless = isGasless(config) && !isDelegated(config);
4348
- const submitter = await createSubmitter(config, testnet);
4622
+ const selfGasless = mode === "self-gasless";
4349
4623
  let traderAddress;
4350
- if (isDelegated(config)) {
4624
+ if (mode === "self-self" || mode === "self-gasless") {
4625
+ traderAddress = config.traderAddress ?? privateKeyToAccount(config.privateKey).address;
4626
+ } else {
4351
4627
  traderAddress = config.traderAddress;
4628
+ }
4629
+ const signer = createSigner(mode, traderAddress);
4630
+ let submitter;
4631
+ let effectiveSender;
4632
+ if (hasPrivateKey(config)) {
4633
+ submitter = await createSubmitter(config, testnet, mode);
4634
+ effectiveSender = submitter.effectiveSender;
4635
+ } else if (mode === "self-self") {
4636
+ effectiveSender = traderAddress;
4637
+ } else if (mode === "delegated-self") {
4638
+ effectiveSender = config.delegateAddress;
4352
4639
  } else {
4353
- traderAddress = privateKeyToAccount(config.privateKey).address;
4640
+ effectiveSender = config.safeAddress;
4354
4641
  }
4355
- const signer = createSigner(config, traderAddress, selfGasless);
4356
4642
  const publicRpc = testnet ? "https://sepolia-rollup.arbitrum.io/rpc" : "https://arb1.arbitrum.io/rpc";
4357
- const rpcUrl = config.rpcUrl ?? (isGasless(config) ? publicRpc : void 0);
4358
- if (!isGasless(config) && !config.rpcUrl) {
4643
+ const rpcUrl = config.rpcUrl ?? publicRpc;
4644
+ if (hasPrivateKey(config) && mode !== "self-gasless" && mode !== "delegated-gasless" && !config.rpcUrl) {
4359
4645
  throw new OstiumError(
4360
4646
  "rpcUrl is required for self-submission mode",
4361
4647
  "INVALID_CONFIG" /* INVALID_CONFIG */
@@ -4363,7 +4649,7 @@ var OstiumClient = class _OstiumClient {
4363
4649
  }
4364
4650
  const publicClient = createPublicClient({ chain, transport: http(rpcUrl) });
4365
4651
  let eoaSubmit;
4366
- if (selfGasless) {
4652
+ if (selfGasless && hasPrivateKey(config)) {
4367
4653
  const eoaAccount = privateKeyToAccount(config.privateKey);
4368
4654
  const eoaRpcUrl = config.rpcUrl ?? publicRpc;
4369
4655
  const eoaWalletClient = createWalletClient({ account: eoaAccount, chain, transport: http(eoaRpcUrl) });
@@ -4385,11 +4671,13 @@ var OstiumClient = class _OstiumClient {
4385
4671
  return new _OstiumClient({
4386
4672
  signer,
4387
4673
  submitter,
4674
+ traderAddress,
4675
+ effectiveSender,
4388
4676
  contracts,
4389
4677
  publicClient,
4390
4678
  defaultSlippageBps,
4391
- delegated: isDelegated(config),
4392
- gasless: isGasless(config),
4679
+ delegated: mode === "delegated-self" || mode === "delegated-gasless",
4680
+ gasless: mode === "self-gasless" || mode === "delegated-gasless",
4393
4681
  selfGasless,
4394
4682
  eoaSubmit,
4395
4683
  builderAddress: config.builder?.address,
@@ -4407,17 +4695,25 @@ var OstiumClient = class _OstiumClient {
4407
4695
  * - Self + Gasless: EOA derived from privateKey (NOT the Safe — USDC lives here).
4408
4696
  */
4409
4697
  getTraderAddress() {
4410
- if (!this.signer) {
4698
+ if (!this.traderAddress) {
4411
4699
  throw new OstiumError(
4412
4700
  "No connected trader \u2014 pass `user` explicitly or use one of the createSelfAnd*/createDelegatedAnd* factories.",
4413
4701
  "INVALID_CONFIG" /* INVALID_CONFIG */
4414
4702
  );
4415
4703
  }
4416
- return this.signer.traderAddress;
4704
+ return this.traderAddress;
4417
4705
  }
4418
- /** True when the client was created via `createReadOnly` and cannot submit transactions. */
4706
+ /** True when the client cannot submit transactions directly. */
4419
4707
  isReadOnly() {
4420
- return !this.signer || !this.submitter;
4708
+ return !this.submitter;
4709
+ }
4710
+ /** True when the client can build mode-correct unsigned transaction requests. */
4711
+ canBuildTransactions() {
4712
+ return !!this.signer && !!this.effectiveSender;
4713
+ }
4714
+ /** True when the client has the credentials needed for SDK-managed submission. */
4715
+ canSubmitTransactions() {
4716
+ return !!this.submitter;
4421
4717
  }
4422
4718
  /**
4423
4719
  * Returns the Safe smart-account address used for gasless submission.
@@ -4429,7 +4725,7 @@ var OstiumClient = class _OstiumClient {
4429
4725
  * - Self + Self / Delegated + Self: returns undefined.
4430
4726
  */
4431
4727
  getSmartAccountAddress() {
4432
- return this.gasless && this.submitter ? this.submitter.effectiveSender : void 0;
4728
+ return this.gasless ? this.effectiveSender : void 0;
4433
4729
  }
4434
4730
  // ─────────────────────────────────────────────────────────────────────────
4435
4731
  // Self + Gasless setup
@@ -4453,15 +4749,10 @@ var OstiumClient = class _OstiumClient {
4453
4749
  * ```
4454
4750
  */
4455
4751
  async setupGaslessDelegation() {
4456
- if (!this.selfGasless || !this.eoaSubmit || !this.submitter) {
4457
- throw new OstiumError(
4458
- "setupGaslessDelegation() is only available in Self + Gasless mode. Create the client with pimlicoUrl but without traderAddress.",
4459
- "INVALID_CONFIG" /* INVALID_CONFIG */
4460
- );
4461
- }
4462
- const safeAddress = this.submitter.effectiveSender;
4463
- const encoded = encodeSetDelegate(safeAddress, this.contracts.trading);
4464
- return this.submitDirectFromEoa(encoded);
4752
+ return this.submitDirectEoa(this.getSetupGaslessDelegationEncoded());
4753
+ }
4754
+ getSetupGaslessDelegationTx() {
4755
+ return this.buildDirectEoaTx(this.getSetupGaslessDelegationEncoded());
4465
4756
  }
4466
4757
  // ─────────────────────────────────────────────────────────────────────────
4467
4758
  // USDC helpers
@@ -4523,18 +4814,14 @@ var OstiumClient = class _OstiumClient {
4523
4814
  * @param amount USD amount as decimal string (e.g. "1000"), or "max" for MaxUint256.
4524
4815
  */
4525
4816
  async approveUsdc(amount) {
4526
- if (this.delegated) {
4527
- throw new OstiumError(
4528
- "approveUsdc is not available in delegated mode. The trader must approve USDC directly from their own account.",
4529
- "DELEGATION_FAILED" /* DELEGATION_FAILED */
4530
- );
4531
- }
4532
- const rawAmount = amount === "max" ? maxUint256 : parseUsdc(amount);
4533
- const encoded = encodeUsdcApprove(this.contracts.tradingStorage, rawAmount, this.contracts.usdc);
4817
+ const encoded = this.getApproveUsdcEncoded(amount);
4534
4818
  if (this.selfGasless) {
4535
- return this.submitDirectFromEoa(encoded);
4819
+ return this.submitDirectEoa(encoded);
4536
4820
  }
4537
- return this.requireWriter().submitter.submit(encoded);
4821
+ return this.submitPrepared(encoded);
4822
+ }
4823
+ getApproveUsdcTx(amount) {
4824
+ return this.buildDirectEoaTx(this.getApproveUsdcEncoded(amount));
4538
4825
  }
4539
4826
  // ─────────────────────────────────────────────────────────────────────────
4540
4827
  // Delegation helpers
@@ -4548,16 +4835,20 @@ var OstiumClient = class _OstiumClient {
4548
4835
  * update the trader's delegation on their behalf.
4549
4836
  */
4550
4837
  async setDelegate(delegateAddress) {
4551
- const encoded = encodeSetDelegate(delegateAddress, this.contracts.trading);
4552
- return this.execute(encoded);
4838
+ return this.submitPrepared(this.getSetDelegateEncoded(delegateAddress));
4839
+ }
4840
+ getSetDelegateTx(delegateAddress) {
4841
+ return this.buildPreparedTx(this.getSetDelegateEncoded(delegateAddress));
4553
4842
  }
4554
4843
  /**
4555
4844
  * Remove the current delegate on the trading contract.
4556
4845
  * Follows the same delegation-wrapping rules as setDelegate().
4557
4846
  */
4558
4847
  async removeDelegate() {
4559
- const encoded = encodeRemoveDelegate(this.contracts.trading);
4560
- return this.execute(encoded);
4848
+ return this.submitPrepared(this.getRemoveDelegateEncoded());
4849
+ }
4850
+ getRemoveDelegateTx() {
4851
+ return this.buildPreparedTx(this.getRemoveDelegateEncoded());
4561
4852
  }
4562
4853
  // ─────────────────────────────────────────────────────────────────────────
4563
4854
  // Core trading methods
@@ -4570,6 +4861,12 @@ var OstiumClient = class _OstiumClient {
4570
4861
  * the current allowance.
4571
4862
  */
4572
4863
  async openTrade(params) {
4864
+ return this.submitPrepared(this.getOpenTradeEncoded(params));
4865
+ }
4866
+ getOpenTradeTx(params) {
4867
+ return this.buildPreparedTx(this.getOpenTradeEncoded(params));
4868
+ }
4869
+ getOpenTradeEncoded(params) {
4573
4870
  const collateralNum = parseFloat(params.collateral);
4574
4871
  if (collateralNum > MAX_COLLATERAL_USD) {
4575
4872
  throw new OstiumError(
@@ -4578,7 +4875,7 @@ var OstiumClient = class _OstiumClient {
4578
4875
  );
4579
4876
  }
4580
4877
  const apiOpen = {
4581
- a: params.pairIndex,
4878
+ a: Number(params.pairId),
4582
4879
  b: params.buy,
4583
4880
  p: params.price,
4584
4881
  s: params.collateral,
@@ -4601,8 +4898,7 @@ var OstiumClient = class _OstiumClient {
4601
4898
  }
4602
4899
  const builderFee = buildBuilderFee(apiBuilder);
4603
4900
  const slippageP = params.type === "market" /* Market */ ? BigInt(this.resolveSlippage(params.slippage)) : 0n;
4604
- const encoded = encodeOpenTrade(trade, builderFee, orderType, slippageP, this.contracts.trading);
4605
- return this.execute(encoded);
4901
+ return encodeOpenTrade(trade, builderFee, orderType, slippageP, this.contracts.trading);
4606
4902
  }
4607
4903
  /**
4608
4904
  * Close an open position (full or partial).
@@ -4617,6 +4913,12 @@ var OstiumClient = class _OstiumClient {
4617
4913
  * ```
4618
4914
  */
4619
4915
  async closeTrade(params) {
4916
+ return this.submitPrepared(this.getCloseTradeEncoded(params));
4917
+ }
4918
+ getCloseTradeTx(params) {
4919
+ return this.buildPreparedTx(this.getCloseTradeEncoded(params));
4920
+ }
4921
+ getCloseTradeEncoded(params) {
4620
4922
  try {
4621
4923
  validateCloseParams({
4622
4924
  a: Number(params.pairId),
@@ -4636,7 +4938,7 @@ var OstiumClient = class _OstiumClient {
4636
4938
  const closePercentage = BigInt(Math.round(params.closePercent * 100));
4637
4939
  const marketPrice = parsePrice(params.price);
4638
4940
  const slippageP = BigInt(this.resolveSlippage(params.slippage));
4639
- const encoded = encodeCloseTradeMarket(
4941
+ return encodeCloseTradeMarket(
4640
4942
  pairIndex,
4641
4943
  index,
4642
4944
  closePercentage,
@@ -4644,7 +4946,6 @@ var OstiumClient = class _OstiumClient {
4644
4946
  slippageP,
4645
4947
  this.contracts.trading
4646
4948
  );
4647
- return this.execute(encoded);
4648
4949
  }
4649
4950
  /**
4650
4951
  * Cancel a pending order.
@@ -4667,6 +4968,12 @@ var OstiumClient = class _OstiumClient {
4667
4968
  * ```
4668
4969
  */
4669
4970
  async cancelOrder(params) {
4971
+ return this.submitPrepared(this.getCancelOrderEncoded(params));
4972
+ }
4973
+ getCancelOrderTx(params) {
4974
+ return this.buildPreparedTx(this.getCancelOrderEncoded(params));
4975
+ }
4976
+ getCancelOrderEncoded(params) {
4670
4977
  let encoded;
4671
4978
  if (params.type === "limit" /* Limit */) {
4672
4979
  const pairIdNum = Number(params.pairId);
@@ -4703,7 +5010,7 @@ var OstiumClient = class _OstiumClient {
4703
5010
  }
4704
5011
  encoded = encodeOpenTradeMarketTimeout(BigInt(params.orderId), this.contracts.trading);
4705
5012
  }
4706
- return this.execute(encoded);
5013
+ return encoded;
4707
5014
  }
4708
5015
  /**
4709
5016
  * Modify an open trade or a pending limit order.
@@ -4728,6 +5035,12 @@ var OstiumClient = class _OstiumClient {
4728
5035
  * - Both `takeProfit` and `stopLoss` without `price` → throws (send two calls).
4729
5036
  */
4730
5037
  async modifyOrder(params) {
5038
+ return this.submitPrepared(this.getModifyOrderEncoded(params));
5039
+ }
5040
+ getModifyOrderTx(params) {
5041
+ return this.buildPreparedTx(this.getModifyOrderEncoded(params));
5042
+ }
5043
+ getModifyOrderEncoded(params) {
4731
5044
  try {
4732
5045
  validateModifyParams({
4733
5046
  a: Number(params.pairId),
@@ -4767,7 +5080,7 @@ var OstiumClient = class _OstiumClient {
4767
5080
  "VALIDATION_FAILED" /* VALIDATION_FAILED */
4768
5081
  );
4769
5082
  }
4770
- return this.execute(encoded);
5083
+ return encoded;
4771
5084
  }
4772
5085
  /**
4773
5086
  * Update collateral on an open position (isolated margin).
@@ -4787,6 +5100,12 @@ var OstiumClient = class _OstiumClient {
4787
5100
  * Checks USDC allowance before top-up operations.
4788
5101
  */
4789
5102
  async updateCollateral(params) {
5103
+ return this.submitPrepared(await this.getUpdateCollateralEncoded(params));
5104
+ }
5105
+ async getUpdateCollateralTx(params) {
5106
+ return this.buildPreparedTx(await this.getUpdateCollateralEncoded(params));
5107
+ }
5108
+ async getUpdateCollateralEncoded(params) {
4790
5109
  const amount = parseFloat(params.amount);
4791
5110
  if (Number.isNaN(amount) || amount === 0) {
4792
5111
  throw new OstiumError(
@@ -4810,7 +5129,7 @@ var OstiumClient = class _OstiumClient {
4810
5129
  } else {
4811
5130
  encoded = encodeRemoveCollateral(pairIndex, index, absAmount, this.contracts.trading);
4812
5131
  }
4813
- return this.execute(encoded);
5132
+ return encoded;
4814
5133
  }
4815
5134
  // ─────────────────────────────────────────────────────────────────────────
4816
5135
  // Subgraph reads (Hyperliquid-style API)
@@ -4829,11 +5148,18 @@ var OstiumClient = class _OstiumClient {
4829
5148
  /**
4830
5149
  * Open positions + margin summary for a user. Defaults to the connected
4831
5150
  * trader. Live prices and the current block number are fetched automatically.
5151
+ *
5152
+ * - `user`: pass an address to scope to a single trader, or `'ALL'` to fetch
5153
+ * positions across every trader (no trader filter).
5154
+ * - `limit`: cap the number of positions returned (default: all).
5155
+ * - `skip`: offset into the result set for pagination (default: `0`).
4832
5156
  */
4833
5157
  async getOpenPositions(params) {
4834
5158
  return this.subgraph.getOpenPositions({
4835
5159
  user: params?.user ?? this.getTraderAddress(),
4836
- blockNumber: params?.blockNumber ?? await this.publicClient.getBlockNumber()
5160
+ blockNumber: params?.blockNumber ?? await this.publicClient.getBlockNumber(),
5161
+ limit: params?.limit,
5162
+ skip: params?.skip
4837
5163
  });
4838
5164
  }
4839
5165
  /**
@@ -4938,31 +5264,104 @@ var OstiumClient = class _OstiumClient {
4938
5264
  streamPrices(pairIds) {
4939
5265
  return this.subgraph.streamPrices(pairIds);
4940
5266
  }
5267
+ /**
5268
+ * Stream price-driven updates for an existing `getOpenPositions()` response.
5269
+ *
5270
+ * Subscribes only to the unique pairs referenced by the response and emits the
5271
+ * full updated positions payload on each relevant price update. Pass an
5272
+ * existing `priceStream` to reuse a websocket your app already owns, or omit
5273
+ * it to let the SDK create a dedicated connection.
5274
+ */
5275
+ streamPositionUpdates(initial, priceStream) {
5276
+ return this.subgraph.streamPositionUpdates(initial, priceStream);
5277
+ }
4941
5278
  // ─────────────────────────────────────────────────────────────────────────
4942
5279
  // Internal helpers
4943
5280
  // ─────────────────────────────────────────────────────────────────────────
4944
- /**
4945
- * Core execution pipeline:
4946
- * 1. signer.prepare() — optionally wraps in delegatedAction.
4947
- * 2. submitter.submit() sends via EOA walletClient or Pimlico UserOp.
4948
- */
4949
- async execute(encoded) {
4950
- const { signer, submitter } = this.requireWriter();
4951
- const prepared = signer.prepare(encoded);
4952
- return submitter.submit(prepared);
4953
- }
4954
- /** Throws if the client is read-only; otherwise returns the signer + submitter. */
4955
- requireWriter() {
4956
- if (!this.signer || !this.submitter) {
5281
+ getSetupGaslessDelegationEncoded() {
5282
+ if (!this.selfGasless || !this.effectiveSender) {
5283
+ throw new OstiumError(
5284
+ "setupGaslessDelegation() is only available in Self + Gasless mode.",
5285
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5286
+ );
5287
+ }
5288
+ return encodeSetDelegate(this.effectiveSender, this.contracts.trading);
5289
+ }
5290
+ getApproveUsdcEncoded(amount) {
5291
+ if (this.delegated) {
5292
+ throw new OstiumError(
5293
+ "approveUsdc is not available in delegated mode. The trader must approve USDC directly from their own account.",
5294
+ "DELEGATION_FAILED" /* DELEGATION_FAILED */
5295
+ );
5296
+ }
5297
+ const rawAmount = amount === "max" ? maxUint256 : parseUsdc(amount);
5298
+ return encodeUsdcApprove(this.contracts.tradingStorage, rawAmount, this.contracts.usdc);
5299
+ }
5300
+ getSetDelegateEncoded(delegateAddress) {
5301
+ return encodeSetDelegate(delegateAddress, this.contracts.trading);
5302
+ }
5303
+ getRemoveDelegateEncoded() {
5304
+ return encodeRemoveDelegate(this.contracts.trading);
5305
+ }
5306
+ prepareEncoded(encoded) {
5307
+ const signer = this.requireBuildCapability();
5308
+ return signer.prepare(encoded);
5309
+ }
5310
+ buildPreparedTx(encoded) {
5311
+ return this.toBuiltTxRequest(this.prepareEncoded(encoded));
5312
+ }
5313
+ buildDirectEoaTx(encoded) {
5314
+ if (!this.canBuildTransactions() || !this.traderAddress) {
5315
+ throw new OstiumError(
5316
+ "This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
5317
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5318
+ );
5319
+ }
5320
+ return {
5321
+ kind: "eoa",
5322
+ to: encoded.to,
5323
+ data: encoded.data,
5324
+ value: encoded.value,
5325
+ from: this.traderAddress,
5326
+ traderAddress: this.traderAddress
5327
+ };
5328
+ }
5329
+ toBuiltTxRequest(encoded) {
5330
+ if (!this.canBuildTransactions() || !this.traderAddress || !this.effectiveSender) {
4957
5331
  throw new OstiumError(
4958
- "This client is read-only. Use one of the createSelfAnd*/createDelegatedAnd* factories to enable writes.",
5332
+ "This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
4959
5333
  "INVALID_CONFIG" /* INVALID_CONFIG */
4960
5334
  );
4961
5335
  }
4962
- return { signer: this.signer, submitter: this.submitter };
5336
+ if (this.gasless) {
5337
+ return {
5338
+ kind: "safe",
5339
+ safeAddress: this.effectiveSender,
5340
+ traderAddress: this.traderAddress,
5341
+ calls: [encoded]
5342
+ };
5343
+ }
5344
+ return {
5345
+ kind: "eoa",
5346
+ to: encoded.to,
5347
+ data: encoded.data,
5348
+ value: encoded.value,
5349
+ from: this.effectiveSender,
5350
+ traderAddress: this.traderAddress
5351
+ };
5352
+ }
5353
+ async submitPrepared(encoded) {
5354
+ const submitter = this.requireSubmitCapability();
5355
+ return submitter.submit(this.prepareEncoded(encoded));
4963
5356
  }
4964
5357
  /** Direct EOA submission — used only in Self + Gasless for approveUsdc and setupGaslessDelegation. */
4965
- async submitDirectFromEoa(encoded) {
5358
+ async submitDirectEoa(encoded) {
5359
+ if (!this.eoaSubmit) {
5360
+ throw new OstiumError(
5361
+ "This client cannot submit direct EOA transactions.",
5362
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5363
+ );
5364
+ }
4966
5365
  try {
4967
5366
  return await this.eoaSubmit(encoded);
4968
5367
  } catch (err) {
@@ -4978,11 +5377,29 @@ var OstiumClient = class _OstiumClient {
4978
5377
  throw new OstiumError(`Transaction failed: ${msg}`, "SUBMISSION_FAILED" /* SUBMISSION_FAILED */, err);
4979
5378
  }
4980
5379
  }
5380
+ requireBuildCapability() {
5381
+ if (!this.signer) {
5382
+ throw new OstiumError(
5383
+ "This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
5384
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5385
+ );
5386
+ }
5387
+ return this.signer;
5388
+ }
5389
+ requireSubmitCapability() {
5390
+ if (!this.submitter) {
5391
+ throw new OstiumError(
5392
+ "This client does not have submission credentials. Use the corresponding get*Tx() method or initialize with a private key.",
5393
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5394
+ );
5395
+ }
5396
+ return this.submitter;
5397
+ }
4981
5398
  resolveSlippage(override) {
4982
5399
  return override ?? this.defaultSlippageBps;
4983
5400
  }
4984
5401
  };
4985
5402
 
4986
- export { CancelOrderType, DEFAULT_BUILDER_API_URL, DEFAULT_SLIPPAGE_PERCENTAGE, DEFAULT_SUBGRAPH_ENDPOINT, DEFAULT_SUBGRAPH_ENDPOINT_TESTNET, MAX_COLLATERAL_USD, MIN_COLLATERAL_USD, OrderType, OstiumClient, OstiumError, OstiumErrorCode, OstiumPriceStream, OstiumSubgraphClient, OstiumSubgraphError, OstiumSubgraphErrorCode, PRECISION_18, PRECISION_6, parseLeverage, parsePrice, parseUsdc };
5403
+ export { CancelOrderType, DEFAULT_BUILDER_API_URL, DEFAULT_SLIPPAGE_PERCENTAGE, DEFAULT_SUBGRAPH_ENDPOINT, DEFAULT_SUBGRAPH_ENDPOINT_TESTNET, MAX_COLLATERAL_USD, MIN_COLLATERAL_USD, OrderType, OstiumClient, OstiumError, OstiumErrorCode, OstiumPositionUpdatesStream, OstiumPriceStream, OstiumSubgraphClient, OstiumSubgraphError, OstiumSubgraphErrorCode, PRECISION_18, PRECISION_6, parseLeverage, parsePrice, parseUsdc };
4987
5404
  //# sourceMappingURL=index.js.map
4988
5405
  //# sourceMappingURL=index.js.map