@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.cjs CHANGED
@@ -260,11 +260,20 @@ var CancelOrderType = /* @__PURE__ */ ((CancelOrderType2) => {
260
260
  // src/config.ts
261
261
  var DEFAULT_PIMLICO_URL = "https://builder.ostium.io/v1/pimlico/sponsor?chainId=42161";
262
262
  var DEFAULT_PIMLICO_URL_TESTNET = "https://builder.ostium.io/v1/pimlico/sponsor?chainId=421614";
263
- function isGasless(config) {
264
- return !!config.pimlicoUrl;
263
+ function hasPrivateKey(config) {
264
+ return typeof config.privateKey === "string" && config.privateKey.length > 0;
265
265
  }
266
- function isDelegated(config) {
267
- return !!config.traderAddress;
266
+ function resolveMode(config) {
267
+ if (config.mode) return config.mode;
268
+ if (config.privateKey) {
269
+ if (config.pimlicoUrl) return config.traderAddress ? "delegated-gasless" : "self-gasless";
270
+ return config.traderAddress ? "delegated-self" : "self-self";
271
+ }
272
+ if (config.safeAddress && config.delegateAddress && config.traderAddress) return "delegated-gasless";
273
+ if (config.safeAddress && config.traderAddress) return "self-gasless";
274
+ if (config.delegateAddress && config.traderAddress) return "delegated-self";
275
+ if (config.traderAddress) return "self-self";
276
+ throw new Error("Unable to infer client mode from config");
268
277
  }
269
278
 
270
279
  // src/errors.ts
@@ -2667,8 +2676,8 @@ var DelegatedSignerStrategy = class {
2667
2676
  };
2668
2677
 
2669
2678
  // src/signer/index.ts
2670
- function createSigner(config, traderAddress, selfGasless = false) {
2671
- if (isDelegated(config) || selfGasless) {
2679
+ function createSigner(mode, traderAddress) {
2680
+ if (mode !== "self-self") {
2672
2681
  return new DelegatedSignerStrategy(traderAddress);
2673
2682
  }
2674
2683
  return new SelfSignerStrategy(traderAddress);
@@ -2692,6 +2701,12 @@ var SelfSubmissionStrategy = class {
2692
2701
  effectiveSender;
2693
2702
  send;
2694
2703
  constructor(config, testnet) {
2704
+ if (!config.privateKey) {
2705
+ throw new OstiumError(
2706
+ "privateKey is required for self-submission mode",
2707
+ "INVALID_CONFIG" /* INVALID_CONFIG */
2708
+ );
2709
+ }
2695
2710
  if (!config.rpcUrl) {
2696
2711
  throw new OstiumError(
2697
2712
  "rpcUrl is required for self-submission mode",
@@ -2757,6 +2772,12 @@ var GaslessSubmissionStrategy = class _GaslessSubmissionStrategy {
2757
2772
  this.effectiveSender = safeAccount.address;
2758
2773
  }
2759
2774
  static async create(config, testnet) {
2775
+ if (!config.privateKey) {
2776
+ throw new OstiumError(
2777
+ "privateKey is required for gasless submission mode",
2778
+ "INVALID_CONFIG" /* INVALID_CONFIG */
2779
+ );
2780
+ }
2760
2781
  if (!config.pimlicoUrl) {
2761
2782
  throw new OstiumError(
2762
2783
  "pimlicoUrl is required for gasless mode",
@@ -2825,8 +2846,8 @@ var GaslessSubmissionStrategy = class _GaslessSubmissionStrategy {
2825
2846
  };
2826
2847
 
2827
2848
  // src/submitter/index.ts
2828
- async function createSubmitter(config, testnet) {
2829
- if (isGasless(config)) {
2849
+ async function createSubmitter(config, testnet, mode) {
2850
+ if (mode === "self-gasless" || mode === "delegated-gasless") {
2830
2851
  return GaslessSubmissionStrategy.create(config, testnet);
2831
2852
  }
2832
2853
  if (!config.rpcUrl) {
@@ -2923,6 +2944,17 @@ query GetTraderOpenTrades($trader: String!, $skip: Int!, $first: Int!) {
2923
2944
  ${PAIR_FULL}
2924
2945
  }
2925
2946
  }`;
2947
+ var GetAllOpenTradesQuery = `
2948
+ query GetAllOpenTrades($skip: Int!, $first: Int!) {
2949
+ trades(
2950
+ where: { isOpen: true }
2951
+ skip: $skip first: $first
2952
+ orderBy: timestamp orderDirection: desc
2953
+ ) {
2954
+ ${TRADE_CORE}
2955
+ ${PAIR_FULL}
2956
+ }
2957
+ }`;
2926
2958
  var ORDER_FIELDS = `
2927
2959
  id tradeID limitID trader
2928
2960
  pair { id from to group { id name } }
@@ -3002,9 +3034,9 @@ query GetTraderActiveLimits($trader: String!, $skip: Int!, $first: Int!) {
3002
3034
  }`;
3003
3035
 
3004
3036
  // src/data/internal/pagination.ts
3005
- async function paginateAll(fetcher, batchSize = 1e3, max = Infinity) {
3037
+ async function paginateAll(fetcher, batchSize = 1e3, max = Infinity, initialSkip = 0) {
3006
3038
  const results = [];
3007
- let skip = 0;
3039
+ let skip = initialSkip;
3008
3040
  while (results.length < max) {
3009
3041
  const remaining = max - results.length;
3010
3042
  const fetchSize = Math.min(batchSize, remaining);
@@ -3226,10 +3258,11 @@ async function fetchLivePrices(builderApiUrl) {
3226
3258
  }
3227
3259
  return result;
3228
3260
  }
3229
- function rawTickToPublic(item) {
3261
+ function rawTickToPublic(item, pairId) {
3230
3262
  const from = normalizePairName(item.from);
3231
3263
  const to = normalizePairName(item.to);
3232
3264
  return {
3265
+ pairId,
3233
3266
  feedId: item.feed_id,
3234
3267
  pair: `${from}-${to}`,
3235
3268
  from,
@@ -3276,9 +3309,12 @@ var OstiumPriceStream = class _OstiumPriceStream {
3276
3309
  snapshotHandlers = /* @__PURE__ */ new Set();
3277
3310
  /** pairId (string) → raw "FROM-TO" name for the WS API. */
3278
3311
  pairRawNameCache;
3279
- constructor(ws, pairRawNameCache, initialSubscribeRawPairs) {
3312
+ /** raw "FROM-TO" name → pairId (string) for mapping incoming ticks back to SDK ids. */
3313
+ rawNamePairIdCache;
3314
+ constructor(ws, pairRawNameCache, rawNamePairIdCache, initialSubscribeRawPairs) {
3280
3315
  this.ws = ws;
3281
3316
  this.pairRawNameCache = pairRawNameCache;
3317
+ this.rawNamePairIdCache = rawNamePairIdCache;
3282
3318
  if (initialSubscribeRawPairs?.length) {
3283
3319
  ws.once("open", () => {
3284
3320
  ws.send(JSON.stringify({ type: "subscribe", pairs: initialSubscribeRawPairs }));
@@ -3292,10 +3328,13 @@ var OstiumPriceStream = class _OstiumPriceStream {
3292
3328
  return;
3293
3329
  }
3294
3330
  if (msg.type === "snapshot" && Array.isArray(msg.data)) {
3295
- const ticks = msg.data.map(rawTickToPublic);
3331
+ const ticks = msg.data.map(
3332
+ (item) => rawTickToPublic(item, this.rawNamePairIdCache.get(`${item.from}-${item.to}`))
3333
+ );
3296
3334
  for (const h of this.snapshotHandlers) h(ticks);
3297
3335
  } else if (msg.type === "tick" && msg.data) {
3298
- const tick = rawTickToPublic(msg.data);
3336
+ const item = msg.data;
3337
+ const tick = rawTickToPublic(item, this.rawNamePairIdCache.get(`${item.from}-${item.to}`));
3299
3338
  for (const h of this.tickHandlers) h(tick);
3300
3339
  }
3301
3340
  });
@@ -3321,7 +3360,11 @@ var OstiumPriceStream = class _OstiumPriceStream {
3321
3360
  const ws = new WS__default.default(url, {
3322
3361
  headers: { "User-Agent": "ostium-sdk", "Accept": "*/*" }
3323
3362
  });
3324
- return new _OstiumPriceStream(ws, pairRawNameCache, initial);
3363
+ const rawNamePairIdCache = /* @__PURE__ */ new Map();
3364
+ for (const [pairId, rawName] of pairRawNameCache.entries()) {
3365
+ rawNamePairIdCache.set(rawName, pairId);
3366
+ }
3367
+ return new _OstiumPriceStream(ws, pairRawNameCache, rawNamePairIdCache, initial);
3325
3368
  }
3326
3369
  /**
3327
3370
  * Register a callback that fires on every incoming price tick.
@@ -3397,6 +3440,37 @@ var OstiumPriceStream = class _OstiumPriceStream {
3397
3440
  }
3398
3441
  };
3399
3442
 
3443
+ // src/data/internal/aggregations.ts
3444
+ function aggregateMarginSummary(pairPositions) {
3445
+ let accountValue = 0;
3446
+ let totalCollateralUsed = 0;
3447
+ let totalNtlPos = 0;
3448
+ let totalRawPnlUsd = 0;
3449
+ let totalCumRollover = 0;
3450
+ let totalWithdrawable = 0;
3451
+ for (const { position } of pairPositions) {
3452
+ const collateral = parseFloat(position.collateralUsed) || 0;
3453
+ const ntl = parseFloat(position.ntl) || 0;
3454
+ const pnl = parseFloat(position.unrealizedPnl) || 0;
3455
+ const rollover = parseFloat(position.cumRollover) || 0;
3456
+ const withdrawable = parseFloat(position.maxWithdrawable) || 0;
3457
+ accountValue += collateral + pnl;
3458
+ totalCollateralUsed += collateral;
3459
+ totalNtlPos += ntl;
3460
+ totalRawPnlUsd += pnl;
3461
+ totalCumRollover += rollover;
3462
+ totalWithdrawable += withdrawable;
3463
+ }
3464
+ return {
3465
+ accountValue: accountValue.toString(),
3466
+ totalCollateralUsed: totalCollateralUsed.toString(),
3467
+ totalNtlPos: totalNtlPos.toString(),
3468
+ totalRawPnlUsd: totalRawPnlUsd.toString(),
3469
+ totalCumRollover: totalCumRollover.toString(),
3470
+ totalWithdrawable: totalWithdrawable.toString()
3471
+ };
3472
+ }
3473
+
3400
3474
  // src/data/internal/formatters.ts
3401
3475
  var MIN_NOTIONAL = "5.0";
3402
3476
  var MIN_NOTIONAL_NUM = 5;
@@ -3498,14 +3572,14 @@ function formatPosition(raw, price, pnl, maxLeverage) {
3498
3572
  returnOnEquity: roe.toString(),
3499
3573
  liquidationPx: pnl.liquidationPrice.toString(),
3500
3574
  collateralUsed: collateral.toString(),
3501
- cumRollover: pnl.rollover.toString(),
3575
+ cumRollover: (pnl.rollover * -1).toString(),
3502
3576
  ...raw.takeProfitPrice && raw.takeProfitPrice !== "0" ? { tpPx: formatTokens(raw.takeProfitPrice).toString() } : {},
3503
3577
  ...raw.stopLossPrice && raw.stopLossPrice !== "0" ? { slPx: formatTokens(raw.stopLossPrice).toString() } : {},
3504
3578
  openTimestamp: parseInt(raw.timestamp || "0") * 1e3,
3505
3579
  isDayTrade: raw.isDayTrade ?? false,
3506
- maxLeverage: maxLeverage.toString()
3507
- },
3508
- maxWithdrawable: maxWithdrawForPosition(collateral, leverage, maxLeverage).toString()
3580
+ maxLeverage: maxLeverage.toString(),
3581
+ maxWithdrawable: maxWithdrawForPosition(collateral, leverage, maxLeverage).toString()
3582
+ }
3509
3583
  };
3510
3584
  }
3511
3585
  function formatFill(raw) {
@@ -3582,6 +3656,131 @@ function formatOpenOrder(raw) {
3582
3656
  timestamp: parseInt(raw.initiatedAt || "0") * 1e3
3583
3657
  };
3584
3658
  }
3659
+
3660
+ // src/data/internal/position-updates.ts
3661
+ function cloneResponse(response) {
3662
+ return {
3663
+ pairPositions: response.pairPositions.map((pairPos) => ({
3664
+ position: { ...pairPos.position }
3665
+ })),
3666
+ marginSummary: { ...response.marginSummary },
3667
+ time: response.time
3668
+ };
3669
+ }
3670
+ function applyTickToPosition(position, tick) {
3671
+ const size = parseFloat(position.szi) || 0;
3672
+ const entry = parseFloat(position.entryPx) || 0;
3673
+ const collateral = parseFloat(position.collateralUsed) || 0;
3674
+ const maxLeverage = parseFloat(position.maxLeverage) || 0;
3675
+ const rollover = parseFloat(position.cumRollover) || 0;
3676
+ const isLong = position.side === "B";
3677
+ const ntl = size * tick.mid;
3678
+ const rawPnl = isLong ? (tick.mid - entry) * size : (entry - tick.mid) * size;
3679
+ const netPnl = rawPnl + rollover;
3680
+ const roe = collateral > 0 ? netPnl / collateral : 0;
3681
+ const currentLeverage = collateral > 0 ? ntl / collateral : 0;
3682
+ const maxWithdrawable = maxWithdrawForPosition(collateral, currentLeverage, maxLeverage);
3683
+ return {
3684
+ ...position,
3685
+ ntl: ntl.toString(),
3686
+ unrealizedPnl: netPnl.toString(),
3687
+ returnOnEquity: roe.toString(),
3688
+ maxWithdrawable: maxWithdrawable.toString()
3689
+ };
3690
+ }
3691
+ function updateResponseWithTick(current, tick, timestampMs) {
3692
+ if (!tick.pairId) return current;
3693
+ let changed = false;
3694
+ const pairPositions = current.pairPositions.map((pairPos) => {
3695
+ if (String(pairPos.position.pairId) !== String(tick.pairId)) {
3696
+ return pairPos;
3697
+ }
3698
+ changed = true;
3699
+ return {
3700
+ ...pairPos,
3701
+ position: applyTickToPosition(pairPos.position, tick)
3702
+ };
3703
+ });
3704
+ if (!changed) return current;
3705
+ return {
3706
+ pairPositions,
3707
+ marginSummary: aggregateMarginSummary(pairPositions),
3708
+ time: timestampMs
3709
+ };
3710
+ }
3711
+ var OstiumPositionUpdatesStream = class {
3712
+ priceStream;
3713
+ updateHandlers = /* @__PURE__ */ new Set();
3714
+ current;
3715
+ trackedPairIds;
3716
+ constructor(initial, trackedPairIds, priceStream) {
3717
+ this.current = cloneResponse(initial);
3718
+ this.priceStream = priceStream;
3719
+ this.trackedPairIds = new Set(trackedPairIds);
3720
+ if (!priceStream) return;
3721
+ priceStream.onSnapshot((ticks) => {
3722
+ this.ingestSnapshot(ticks);
3723
+ });
3724
+ priceStream.onTick((tick) => {
3725
+ this.ingestTick(tick);
3726
+ });
3727
+ }
3728
+ onUpdate(handler) {
3729
+ this.updateHandlers.add(handler);
3730
+ return () => {
3731
+ this.updateHandlers.delete(handler);
3732
+ };
3733
+ }
3734
+ onOpen(handler) {
3735
+ this.priceStream?.onOpen(handler);
3736
+ return this;
3737
+ }
3738
+ onError(handler) {
3739
+ this.priceStream?.onError(handler);
3740
+ return this;
3741
+ }
3742
+ onClose(handler) {
3743
+ this.priceStream?.onClose(handler);
3744
+ return this;
3745
+ }
3746
+ getCurrent() {
3747
+ return cloneResponse(this.current);
3748
+ }
3749
+ /**
3750
+ * Apply a single externally sourced price tick to the tracked positions.
3751
+ *
3752
+ * Use this when your app already has its own websocket connection and you want
3753
+ * the SDK to handle only the position recalculation logic.
3754
+ */
3755
+ ingestTick(tick) {
3756
+ if (!tick.pairId || !this.trackedPairIds.has(String(tick.pairId))) return;
3757
+ const next = updateResponseWithTick(this.current, tick, tick.timestampSeconds * 1e3);
3758
+ if (next !== this.current) {
3759
+ this.current = next;
3760
+ this.emit(next);
3761
+ }
3762
+ }
3763
+ /**
3764
+ * Apply a batch of externally sourced ticks, such as a websocket snapshot.
3765
+ */
3766
+ ingestSnapshot(ticks) {
3767
+ let next = this.current;
3768
+ for (const tick of ticks) {
3769
+ if (!tick.pairId || !this.trackedPairIds.has(String(tick.pairId))) continue;
3770
+ next = updateResponseWithTick(next, tick, tick.timestampSeconds * 1e3);
3771
+ }
3772
+ if (next !== this.current) {
3773
+ this.current = next;
3774
+ this.emit(next);
3775
+ }
3776
+ }
3777
+ close() {
3778
+ this.priceStream?.close();
3779
+ }
3780
+ emit(positions) {
3781
+ for (const handler of this.updateHandlers) handler(cloneResponse(positions));
3782
+ }
3783
+ };
3585
3784
  var EMPTY_RESULT = { long: [], short: [] };
3586
3785
  var ORDERBOOK_MAX_LEVELS = 20;
3587
3786
  function generateLogSpacedNotionals(levels, capacity) {
@@ -3766,31 +3965,40 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
3766
3965
  * PnL fields are zeroed.
3767
3966
  */
3768
3967
  async getOpenPositions(params) {
3769
- const { user, blockNumber } = params;
3968
+ const { user, blockNumber, limit = Infinity, skip: initialSkip = 0 } = params;
3969
+ const isGlobal = user === "ALL";
3770
3970
  const [trades, prices] = await Promise.all([
3771
- paginateAll(async (skip, first) => {
3772
- const data = await this.query(
3773
- GetTraderOpenTradesQuery,
3774
- { trader: user.toLowerCase(), skip, first }
3775
- );
3776
- return data.trades;
3777
- }),
3971
+ paginateAll(
3972
+ async (skip, first) => {
3973
+ if (isGlobal) {
3974
+ const data2 = await this.query(
3975
+ GetAllOpenTradesQuery,
3976
+ { skip, first }
3977
+ );
3978
+ return data2.trades;
3979
+ }
3980
+ const data = await this.query(
3981
+ GetTraderOpenTradesQuery,
3982
+ { trader: user.toLowerCase(), skip, first }
3983
+ );
3984
+ return data.trades;
3985
+ },
3986
+ 1e3,
3987
+ limit,
3988
+ initialSkip
3989
+ ),
3778
3990
  this.fetchLivePricesSafe()
3779
3991
  ]);
3780
- let totalWithdrawable = 0;
3781
3992
  const pairPositions = trades.map((trade) => {
3782
3993
  const price = prices[`${trade.pair.from}/${trade.pair.to}`];
3783
3994
  const pnl = price && blockNumber ? getTradePnL(trade, price, blockNumber) : EMPTY_PNL;
3784
3995
  const maxLev = trade.isDayTrade ? formatLeverage(trade.pair.overnightMaxLeverage) : pairMaxLeverage(trade.pair);
3785
- const pairPos = formatPosition(trade, price, pnl, maxLev);
3786
- totalWithdrawable += parseFloat(pairPos.maxWithdrawable);
3787
- return pairPos;
3996
+ return formatPosition(trade, price, pnl, maxLev);
3788
3997
  });
3789
3998
  const marginSummary = aggregateMarginSummary(pairPositions);
3790
3999
  return {
3791
4000
  pairPositions,
3792
4001
  marginSummary,
3793
- withdrawable: totalWithdrawable.toString(),
3794
4002
  time: Date.now()
3795
4003
  };
3796
4004
  }
@@ -4041,6 +4249,24 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
4041
4249
  });
4042
4250
  return OstiumPriceStream.connect(this.builderApiUrl, rawNames, cache);
4043
4251
  }
4252
+ /**
4253
+ * Stream price-driven updates for an existing `getOpenPositions()` response.
4254
+ *
4255
+ * The SDK subscribes only to the unique pairs present in `initial.pairPositions`,
4256
+ * recalculates price-sensitive fields for affected positions on each tick, and
4257
+ * emits the full updated response.
4258
+ *
4259
+ * Pass an existing `priceStream` to reuse a websocket connection your app has
4260
+ * already opened. Omit it to let the SDK open and manage a dedicated
4261
+ * connection for the tracked pair ids.
4262
+ */
4263
+ streamPositionUpdates(initial, priceStream) {
4264
+ const pairIds = [...new Set(initial.pairPositions.map(({ position }) => String(position.pairId)))];
4265
+ if (pairIds.length === 0) {
4266
+ return new OstiumPositionUpdatesStream(initial, []);
4267
+ }
4268
+ return new OstiumPositionUpdatesStream(initial, pairIds, priceStream ?? this.streamPrices(pairIds));
4269
+ }
4044
4270
  // ─────────────────────────────────────────────────────────────────────────
4045
4271
  // Internal — fetchers, cache, query wrapper
4046
4272
  // ─────────────────────────────────────────────────────────────────────────
@@ -4099,43 +4325,28 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
4099
4325
  }
4100
4326
  }
4101
4327
  };
4102
- function aggregateMarginSummary(pairPositions) {
4103
- let accountValue = 0;
4104
- let totalCollateralUsed = 0;
4105
- let totalNtlPos = 0;
4106
- let totalRawPnlUsd = 0;
4107
- for (const { position } of pairPositions) {
4108
- const collateral = parseFloat(position.collateralUsed) || 0;
4109
- const ntl = parseFloat(position.ntl) || 0;
4110
- const pnl = parseFloat(position.unrealizedPnl) || 0;
4111
- accountValue += collateral + pnl;
4112
- totalCollateralUsed += collateral;
4113
- totalNtlPos += ntl;
4114
- totalRawPnlUsd += pnl;
4115
- }
4116
- return {
4117
- accountValue: accountValue.toString(),
4118
- totalCollateralUsed: totalCollateralUsed.toString(),
4119
- totalNtlPos: totalNtlPos.toString(),
4120
- totalRawPnlUsd: totalRawPnlUsd.toString()
4121
- };
4122
- }
4123
4328
 
4124
4329
  // src/client.ts
4125
4330
  var HEX64_RE = /^0x[0-9a-fA-F]{64}$/;
4126
4331
  var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
4127
4332
  function validateConfig(config) {
4128
- if (!HEX64_RE.test(config.privateKey)) {
4333
+ if (config.privateKey && !HEX64_RE.test(config.privateKey)) {
4129
4334
  throw new OstiumError(
4130
4335
  "privateKey must be a 64-character hex string prefixed with 0x",
4131
4336
  "INVALID_CONFIG" /* INVALID_CONFIG */
4132
4337
  );
4133
4338
  }
4134
- if (config.traderAddress && !ADDRESS_RE.test(config.traderAddress)) {
4135
- throw new OstiumError(
4136
- "traderAddress must be a valid 42-character Ethereum address (0x + 40 hex chars)",
4137
- "INVALID_CONFIG" /* INVALID_CONFIG */
4138
- );
4339
+ for (const [label, value] of [
4340
+ ["traderAddress", config.traderAddress],
4341
+ ["delegateAddress", config.delegateAddress],
4342
+ ["safeAddress", config.safeAddress]
4343
+ ]) {
4344
+ if (value && !ADDRESS_RE.test(value)) {
4345
+ throw new OstiumError(
4346
+ `${label} must be a valid 42-character Ethereum address (0x + 40 hex chars)`,
4347
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4348
+ );
4349
+ }
4139
4350
  }
4140
4351
  if (config.slippageBps !== void 0 && (config.slippageBps < 0 || config.slippageBps > 500)) {
4141
4352
  throw new OstiumError(
@@ -4149,10 +4360,45 @@ function validateConfig(config) {
4149
4360
  "INVALID_CONFIG" /* INVALID_CONFIG */
4150
4361
  );
4151
4362
  }
4363
+ let mode;
4364
+ try {
4365
+ mode = resolveMode(config);
4366
+ } catch {
4367
+ throw new OstiumError(
4368
+ "Could not infer client mode from config. Provide the required addresses for the selected factory.",
4369
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4370
+ );
4371
+ }
4372
+ if (mode === "self-self" && !config.privateKey && !config.traderAddress) {
4373
+ throw new OstiumError(
4374
+ "Self + Self mode requires traderPrivateKey for submission or traderAddress for build-only usage.",
4375
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4376
+ );
4377
+ }
4378
+ if (mode === "self-gasless" && !config.privateKey && (!config.traderAddress || !config.safeAddress)) {
4379
+ throw new OstiumError(
4380
+ "Self + Gasless build-only mode requires traderAddress and safeAddress.",
4381
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4382
+ );
4383
+ }
4384
+ if (mode === "delegated-self" && (!config.traderAddress || !config.privateKey && !config.delegateAddress)) {
4385
+ throw new OstiumError(
4386
+ "Delegated + Self mode requires traderAddress and either delegatePrivateKey or delegateAddress.",
4387
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4388
+ );
4389
+ }
4390
+ if (mode === "delegated-gasless" && (!config.traderAddress || !config.privateKey && (!config.delegateAddress || !config.safeAddress))) {
4391
+ throw new OstiumError(
4392
+ "Delegated + Gasless mode requires traderAddress and either delegatePrivateKey or both delegateAddress and safeAddress.",
4393
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4394
+ );
4395
+ }
4152
4396
  }
4153
4397
  var OstiumClient = class _OstiumClient {
4154
4398
  signer;
4155
4399
  submitter;
4400
+ traderAddress;
4401
+ effectiveSender;
4156
4402
  contracts;
4157
4403
  publicClient;
4158
4404
  defaultSlippageBps;
@@ -4167,6 +4413,8 @@ var OstiumClient = class _OstiumClient {
4167
4413
  constructor(internals) {
4168
4414
  this.signer = internals.signer;
4169
4415
  this.submitter = internals.submitter;
4416
+ this.traderAddress = internals.traderAddress;
4417
+ this.effectiveSender = internals.effectiveSender;
4170
4418
  this.contracts = internals.contracts;
4171
4419
  this.publicClient = internals.publicClient;
4172
4420
  this.defaultSlippageBps = internals.defaultSlippageBps;
@@ -4191,17 +4439,20 @@ var OstiumClient = class _OstiumClient {
4191
4439
  * });
4192
4440
  * await client.openTrade({ ... });
4193
4441
  * ```
4194
- */
4442
+ */
4195
4443
  static async createSelfAndSelf(params) {
4196
- return _OstiumClient._fromConfig({
4197
- privateKey: params.traderPrivateKey,
4198
- rpcUrl: params.rpcUrl,
4444
+ const base = {
4445
+ mode: "self-self",
4199
4446
  testnet: params.testnet,
4200
4447
  slippageBps: params.slippageBps,
4201
4448
  builder: params.builder,
4202
4449
  subgraphUrl: params.subgraphUrl,
4203
- builderApiUrl: params.builderApiUrl
4204
- });
4450
+ builderApiUrl: params.builderApiUrl,
4451
+ rpcUrl: params.rpcUrl
4452
+ };
4453
+ return _OstiumClient._fromConfig(
4454
+ "traderPrivateKey" in params ? { ...base, privateKey: params.traderPrivateKey } : { ...base, traderAddress: params.traderAddress }
4455
+ );
4205
4456
  }
4206
4457
  /**
4207
4458
  * **Self + Gasless** — your EOA owns everything; a Safe relays trades for free.
@@ -4225,17 +4476,27 @@ var OstiumClient = class _OstiumClient {
4225
4476
  */
4226
4477
  static async createSelfAndGasless(params) {
4227
4478
  const defaultUrl = params.testnet ? DEFAULT_PIMLICO_URL_TESTNET : DEFAULT_PIMLICO_URL;
4228
- return _OstiumClient._fromConfig({
4229
- privateKey: params.traderPrivateKey,
4230
- pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4479
+ const base = {
4480
+ mode: "self-gasless",
4231
4481
  rpcUrl: params.rpcUrl,
4232
- sponsorshipPolicyId: params.sponsorshipPolicyId,
4233
4482
  testnet: params.testnet,
4234
4483
  slippageBps: params.slippageBps,
4235
4484
  builder: params.builder,
4236
4485
  subgraphUrl: params.subgraphUrl,
4237
4486
  builderApiUrl: params.builderApiUrl
4238
- });
4487
+ };
4488
+ return _OstiumClient._fromConfig(
4489
+ "traderPrivateKey" in params ? {
4490
+ ...base,
4491
+ privateKey: params.traderPrivateKey,
4492
+ pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4493
+ sponsorshipPolicyId: params.sponsorshipPolicyId
4494
+ } : {
4495
+ ...base,
4496
+ traderAddress: params.traderAddress,
4497
+ safeAddress: params.safeAddress
4498
+ }
4499
+ );
4239
4500
  }
4240
4501
  /**
4241
4502
  * **Delegated + Self** — a delegate EOA signs and pays gas on behalf of a trader address.
@@ -4255,8 +4516,8 @@ var OstiumClient = class _OstiumClient {
4255
4516
  * ```
4256
4517
  */
4257
4518
  static async createDelegatedAndSelf(params) {
4258
- return _OstiumClient._fromConfig({
4259
- privateKey: params.delegatePrivateKey,
4519
+ const base = {
4520
+ mode: "delegated-self",
4260
4521
  traderAddress: params.traderAddress,
4261
4522
  rpcUrl: params.rpcUrl,
4262
4523
  testnet: params.testnet,
@@ -4264,7 +4525,10 @@ var OstiumClient = class _OstiumClient {
4264
4525
  builder: params.builder,
4265
4526
  subgraphUrl: params.subgraphUrl,
4266
4527
  builderApiUrl: params.builderApiUrl
4267
- });
4528
+ };
4529
+ return _OstiumClient._fromConfig(
4530
+ "delegatePrivateKey" in params ? { ...base, privateKey: params.delegatePrivateKey } : { ...base, delegateAddress: params.delegateAddress }
4531
+ );
4268
4532
  }
4269
4533
  /**
4270
4534
  * **Delegated + Gasless** — a Safe derived from the delegate key submits sponsored
@@ -4285,18 +4549,28 @@ var OstiumClient = class _OstiumClient {
4285
4549
  */
4286
4550
  static async createDelegatedAndGasless(params) {
4287
4551
  const defaultUrl = params.testnet ? DEFAULT_PIMLICO_URL_TESTNET : DEFAULT_PIMLICO_URL;
4288
- return _OstiumClient._fromConfig({
4289
- privateKey: params.delegatePrivateKey,
4552
+ const base = {
4553
+ mode: "delegated-gasless",
4290
4554
  traderAddress: params.traderAddress,
4291
- pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4292
4555
  rpcUrl: params.rpcUrl,
4293
- sponsorshipPolicyId: params.sponsorshipPolicyId,
4294
4556
  testnet: params.testnet,
4295
4557
  slippageBps: params.slippageBps,
4296
4558
  builder: params.builder,
4297
4559
  subgraphUrl: params.subgraphUrl,
4298
4560
  builderApiUrl: params.builderApiUrl
4299
- });
4561
+ };
4562
+ return _OstiumClient._fromConfig(
4563
+ "delegatePrivateKey" in params ? {
4564
+ ...base,
4565
+ privateKey: params.delegatePrivateKey,
4566
+ pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4567
+ sponsorshipPolicyId: params.sponsorshipPolicyId
4568
+ } : {
4569
+ ...base,
4570
+ delegateAddress: params.delegateAddress,
4571
+ safeAddress: params.safeAddress
4572
+ }
4573
+ );
4300
4574
  }
4301
4575
  /**
4302
4576
  * **Read-only client** — no signer, no submitter, no privateKey required.
@@ -4341,6 +4615,7 @@ var OstiumClient = class _OstiumClient {
4341
4615
  /** Internal factory shared by all public constructors. */
4342
4616
  static async _fromConfig(config) {
4343
4617
  validateConfig(config);
4618
+ const mode = resolveMode(config);
4344
4619
  const testnet = config.testnet ?? false;
4345
4620
  const networkConfig = getNetworkConfig(testnet);
4346
4621
  const chain = testnet ? chains.arbitrumSepolia : chains.arbitrum;
@@ -4350,18 +4625,29 @@ var OstiumClient = class _OstiumClient {
4350
4625
  tradingStorage: networkConfig.contracts.tradingStorage,
4351
4626
  usdc: networkConfig.contracts.usdc
4352
4627
  };
4353
- const selfGasless = isGasless(config) && !isDelegated(config);
4354
- const submitter = await createSubmitter(config, testnet);
4628
+ const selfGasless = mode === "self-gasless";
4355
4629
  let traderAddress;
4356
- if (isDelegated(config)) {
4630
+ if (mode === "self-self" || mode === "self-gasless") {
4631
+ traderAddress = config.traderAddress ?? accounts.privateKeyToAccount(config.privateKey).address;
4632
+ } else {
4357
4633
  traderAddress = config.traderAddress;
4634
+ }
4635
+ const signer = createSigner(mode, traderAddress);
4636
+ let submitter;
4637
+ let effectiveSender;
4638
+ if (hasPrivateKey(config)) {
4639
+ submitter = await createSubmitter(config, testnet, mode);
4640
+ effectiveSender = submitter.effectiveSender;
4641
+ } else if (mode === "self-self") {
4642
+ effectiveSender = traderAddress;
4643
+ } else if (mode === "delegated-self") {
4644
+ effectiveSender = config.delegateAddress;
4358
4645
  } else {
4359
- traderAddress = accounts.privateKeyToAccount(config.privateKey).address;
4646
+ effectiveSender = config.safeAddress;
4360
4647
  }
4361
- const signer = createSigner(config, traderAddress, selfGasless);
4362
4648
  const publicRpc = testnet ? "https://sepolia-rollup.arbitrum.io/rpc" : "https://arb1.arbitrum.io/rpc";
4363
- const rpcUrl = config.rpcUrl ?? (isGasless(config) ? publicRpc : void 0);
4364
- if (!isGasless(config) && !config.rpcUrl) {
4649
+ const rpcUrl = config.rpcUrl ?? publicRpc;
4650
+ if (hasPrivateKey(config) && mode !== "self-gasless" && mode !== "delegated-gasless" && !config.rpcUrl) {
4365
4651
  throw new OstiumError(
4366
4652
  "rpcUrl is required for self-submission mode",
4367
4653
  "INVALID_CONFIG" /* INVALID_CONFIG */
@@ -4369,7 +4655,7 @@ var OstiumClient = class _OstiumClient {
4369
4655
  }
4370
4656
  const publicClient = viem.createPublicClient({ chain, transport: viem.http(rpcUrl) });
4371
4657
  let eoaSubmit;
4372
- if (selfGasless) {
4658
+ if (selfGasless && hasPrivateKey(config)) {
4373
4659
  const eoaAccount = accounts.privateKeyToAccount(config.privateKey);
4374
4660
  const eoaRpcUrl = config.rpcUrl ?? publicRpc;
4375
4661
  const eoaWalletClient = viem.createWalletClient({ account: eoaAccount, chain, transport: viem.http(eoaRpcUrl) });
@@ -4391,11 +4677,13 @@ var OstiumClient = class _OstiumClient {
4391
4677
  return new _OstiumClient({
4392
4678
  signer,
4393
4679
  submitter,
4680
+ traderAddress,
4681
+ effectiveSender,
4394
4682
  contracts,
4395
4683
  publicClient,
4396
4684
  defaultSlippageBps,
4397
- delegated: isDelegated(config),
4398
- gasless: isGasless(config),
4685
+ delegated: mode === "delegated-self" || mode === "delegated-gasless",
4686
+ gasless: mode === "self-gasless" || mode === "delegated-gasless",
4399
4687
  selfGasless,
4400
4688
  eoaSubmit,
4401
4689
  builderAddress: config.builder?.address,
@@ -4413,17 +4701,25 @@ var OstiumClient = class _OstiumClient {
4413
4701
  * - Self + Gasless: EOA derived from privateKey (NOT the Safe — USDC lives here).
4414
4702
  */
4415
4703
  getTraderAddress() {
4416
- if (!this.signer) {
4704
+ if (!this.traderAddress) {
4417
4705
  throw new OstiumError(
4418
4706
  "No connected trader \u2014 pass `user` explicitly or use one of the createSelfAnd*/createDelegatedAnd* factories.",
4419
4707
  "INVALID_CONFIG" /* INVALID_CONFIG */
4420
4708
  );
4421
4709
  }
4422
- return this.signer.traderAddress;
4710
+ return this.traderAddress;
4423
4711
  }
4424
- /** True when the client was created via `createReadOnly` and cannot submit transactions. */
4712
+ /** True when the client cannot submit transactions directly. */
4425
4713
  isReadOnly() {
4426
- return !this.signer || !this.submitter;
4714
+ return !this.submitter;
4715
+ }
4716
+ /** True when the client can build mode-correct unsigned transaction requests. */
4717
+ canBuildTransactions() {
4718
+ return !!this.signer && !!this.effectiveSender;
4719
+ }
4720
+ /** True when the client has the credentials needed for SDK-managed submission. */
4721
+ canSubmitTransactions() {
4722
+ return !!this.submitter;
4427
4723
  }
4428
4724
  /**
4429
4725
  * Returns the Safe smart-account address used for gasless submission.
@@ -4435,7 +4731,7 @@ var OstiumClient = class _OstiumClient {
4435
4731
  * - Self + Self / Delegated + Self: returns undefined.
4436
4732
  */
4437
4733
  getSmartAccountAddress() {
4438
- return this.gasless && this.submitter ? this.submitter.effectiveSender : void 0;
4734
+ return this.gasless ? this.effectiveSender : void 0;
4439
4735
  }
4440
4736
  // ─────────────────────────────────────────────────────────────────────────
4441
4737
  // Self + Gasless setup
@@ -4459,15 +4755,10 @@ var OstiumClient = class _OstiumClient {
4459
4755
  * ```
4460
4756
  */
4461
4757
  async setupGaslessDelegation() {
4462
- if (!this.selfGasless || !this.eoaSubmit || !this.submitter) {
4463
- throw new OstiumError(
4464
- "setupGaslessDelegation() is only available in Self + Gasless mode. Create the client with pimlicoUrl but without traderAddress.",
4465
- "INVALID_CONFIG" /* INVALID_CONFIG */
4466
- );
4467
- }
4468
- const safeAddress = this.submitter.effectiveSender;
4469
- const encoded = encodeSetDelegate(safeAddress, this.contracts.trading);
4470
- return this.submitDirectFromEoa(encoded);
4758
+ return this.submitDirectEoa(this.getSetupGaslessDelegationEncoded());
4759
+ }
4760
+ getSetupGaslessDelegationTx() {
4761
+ return this.buildDirectEoaTx(this.getSetupGaslessDelegationEncoded());
4471
4762
  }
4472
4763
  // ─────────────────────────────────────────────────────────────────────────
4473
4764
  // USDC helpers
@@ -4529,18 +4820,14 @@ var OstiumClient = class _OstiumClient {
4529
4820
  * @param amount USD amount as decimal string (e.g. "1000"), or "max" for MaxUint256.
4530
4821
  */
4531
4822
  async approveUsdc(amount) {
4532
- if (this.delegated) {
4533
- throw new OstiumError(
4534
- "approveUsdc is not available in delegated mode. The trader must approve USDC directly from their own account.",
4535
- "DELEGATION_FAILED" /* DELEGATION_FAILED */
4536
- );
4537
- }
4538
- const rawAmount = amount === "max" ? viem.maxUint256 : parseUsdc(amount);
4539
- const encoded = encodeUsdcApprove(this.contracts.tradingStorage, rawAmount, this.contracts.usdc);
4823
+ const encoded = this.getApproveUsdcEncoded(amount);
4540
4824
  if (this.selfGasless) {
4541
- return this.submitDirectFromEoa(encoded);
4825
+ return this.submitDirectEoa(encoded);
4542
4826
  }
4543
- return this.requireWriter().submitter.submit(encoded);
4827
+ return this.submitPrepared(encoded);
4828
+ }
4829
+ getApproveUsdcTx(amount) {
4830
+ return this.buildDirectEoaTx(this.getApproveUsdcEncoded(amount));
4544
4831
  }
4545
4832
  // ─────────────────────────────────────────────────────────────────────────
4546
4833
  // Delegation helpers
@@ -4554,16 +4841,20 @@ var OstiumClient = class _OstiumClient {
4554
4841
  * update the trader's delegation on their behalf.
4555
4842
  */
4556
4843
  async setDelegate(delegateAddress) {
4557
- const encoded = encodeSetDelegate(delegateAddress, this.contracts.trading);
4558
- return this.execute(encoded);
4844
+ return this.submitPrepared(this.getSetDelegateEncoded(delegateAddress));
4845
+ }
4846
+ getSetDelegateTx(delegateAddress) {
4847
+ return this.buildPreparedTx(this.getSetDelegateEncoded(delegateAddress));
4559
4848
  }
4560
4849
  /**
4561
4850
  * Remove the current delegate on the trading contract.
4562
4851
  * Follows the same delegation-wrapping rules as setDelegate().
4563
4852
  */
4564
4853
  async removeDelegate() {
4565
- const encoded = encodeRemoveDelegate(this.contracts.trading);
4566
- return this.execute(encoded);
4854
+ return this.submitPrepared(this.getRemoveDelegateEncoded());
4855
+ }
4856
+ getRemoveDelegateTx() {
4857
+ return this.buildPreparedTx(this.getRemoveDelegateEncoded());
4567
4858
  }
4568
4859
  // ─────────────────────────────────────────────────────────────────────────
4569
4860
  // Core trading methods
@@ -4576,6 +4867,12 @@ var OstiumClient = class _OstiumClient {
4576
4867
  * the current allowance.
4577
4868
  */
4578
4869
  async openTrade(params) {
4870
+ return this.submitPrepared(this.getOpenTradeEncoded(params));
4871
+ }
4872
+ getOpenTradeTx(params) {
4873
+ return this.buildPreparedTx(this.getOpenTradeEncoded(params));
4874
+ }
4875
+ getOpenTradeEncoded(params) {
4579
4876
  const collateralNum = parseFloat(params.collateral);
4580
4877
  if (collateralNum > MAX_COLLATERAL_USD) {
4581
4878
  throw new OstiumError(
@@ -4584,7 +4881,7 @@ var OstiumClient = class _OstiumClient {
4584
4881
  );
4585
4882
  }
4586
4883
  const apiOpen = {
4587
- a: params.pairIndex,
4884
+ a: Number(params.pairId),
4588
4885
  b: params.buy,
4589
4886
  p: params.price,
4590
4887
  s: params.collateral,
@@ -4607,8 +4904,7 @@ var OstiumClient = class _OstiumClient {
4607
4904
  }
4608
4905
  const builderFee = buildBuilderFee(apiBuilder);
4609
4906
  const slippageP = params.type === "market" /* Market */ ? BigInt(this.resolveSlippage(params.slippage)) : 0n;
4610
- const encoded = encodeOpenTrade(trade, builderFee, orderType, slippageP, this.contracts.trading);
4611
- return this.execute(encoded);
4907
+ return encodeOpenTrade(trade, builderFee, orderType, slippageP, this.contracts.trading);
4612
4908
  }
4613
4909
  /**
4614
4910
  * Close an open position (full or partial).
@@ -4623,6 +4919,12 @@ var OstiumClient = class _OstiumClient {
4623
4919
  * ```
4624
4920
  */
4625
4921
  async closeTrade(params) {
4922
+ return this.submitPrepared(this.getCloseTradeEncoded(params));
4923
+ }
4924
+ getCloseTradeTx(params) {
4925
+ return this.buildPreparedTx(this.getCloseTradeEncoded(params));
4926
+ }
4927
+ getCloseTradeEncoded(params) {
4626
4928
  try {
4627
4929
  validateCloseParams({
4628
4930
  a: Number(params.pairId),
@@ -4642,7 +4944,7 @@ var OstiumClient = class _OstiumClient {
4642
4944
  const closePercentage = BigInt(Math.round(params.closePercent * 100));
4643
4945
  const marketPrice = parsePrice(params.price);
4644
4946
  const slippageP = BigInt(this.resolveSlippage(params.slippage));
4645
- const encoded = encodeCloseTradeMarket(
4947
+ return encodeCloseTradeMarket(
4646
4948
  pairIndex,
4647
4949
  index,
4648
4950
  closePercentage,
@@ -4650,7 +4952,6 @@ var OstiumClient = class _OstiumClient {
4650
4952
  slippageP,
4651
4953
  this.contracts.trading
4652
4954
  );
4653
- return this.execute(encoded);
4654
4955
  }
4655
4956
  /**
4656
4957
  * Cancel a pending order.
@@ -4673,6 +4974,12 @@ var OstiumClient = class _OstiumClient {
4673
4974
  * ```
4674
4975
  */
4675
4976
  async cancelOrder(params) {
4977
+ return this.submitPrepared(this.getCancelOrderEncoded(params));
4978
+ }
4979
+ getCancelOrderTx(params) {
4980
+ return this.buildPreparedTx(this.getCancelOrderEncoded(params));
4981
+ }
4982
+ getCancelOrderEncoded(params) {
4676
4983
  let encoded;
4677
4984
  if (params.type === "limit" /* Limit */) {
4678
4985
  const pairIdNum = Number(params.pairId);
@@ -4709,7 +5016,7 @@ var OstiumClient = class _OstiumClient {
4709
5016
  }
4710
5017
  encoded = encodeOpenTradeMarketTimeout(BigInt(params.orderId), this.contracts.trading);
4711
5018
  }
4712
- return this.execute(encoded);
5019
+ return encoded;
4713
5020
  }
4714
5021
  /**
4715
5022
  * Modify an open trade or a pending limit order.
@@ -4734,6 +5041,12 @@ var OstiumClient = class _OstiumClient {
4734
5041
  * - Both `takeProfit` and `stopLoss` without `price` → throws (send two calls).
4735
5042
  */
4736
5043
  async modifyOrder(params) {
5044
+ return this.submitPrepared(this.getModifyOrderEncoded(params));
5045
+ }
5046
+ getModifyOrderTx(params) {
5047
+ return this.buildPreparedTx(this.getModifyOrderEncoded(params));
5048
+ }
5049
+ getModifyOrderEncoded(params) {
4737
5050
  try {
4738
5051
  validateModifyParams({
4739
5052
  a: Number(params.pairId),
@@ -4773,7 +5086,7 @@ var OstiumClient = class _OstiumClient {
4773
5086
  "VALIDATION_FAILED" /* VALIDATION_FAILED */
4774
5087
  );
4775
5088
  }
4776
- return this.execute(encoded);
5089
+ return encoded;
4777
5090
  }
4778
5091
  /**
4779
5092
  * Update collateral on an open position (isolated margin).
@@ -4793,6 +5106,12 @@ var OstiumClient = class _OstiumClient {
4793
5106
  * Checks USDC allowance before top-up operations.
4794
5107
  */
4795
5108
  async updateCollateral(params) {
5109
+ return this.submitPrepared(await this.getUpdateCollateralEncoded(params));
5110
+ }
5111
+ async getUpdateCollateralTx(params) {
5112
+ return this.buildPreparedTx(await this.getUpdateCollateralEncoded(params));
5113
+ }
5114
+ async getUpdateCollateralEncoded(params) {
4796
5115
  const amount = parseFloat(params.amount);
4797
5116
  if (Number.isNaN(amount) || amount === 0) {
4798
5117
  throw new OstiumError(
@@ -4816,7 +5135,7 @@ var OstiumClient = class _OstiumClient {
4816
5135
  } else {
4817
5136
  encoded = encodeRemoveCollateral(pairIndex, index, absAmount, this.contracts.trading);
4818
5137
  }
4819
- return this.execute(encoded);
5138
+ return encoded;
4820
5139
  }
4821
5140
  // ─────────────────────────────────────────────────────────────────────────
4822
5141
  // Subgraph reads (Hyperliquid-style API)
@@ -4835,11 +5154,18 @@ var OstiumClient = class _OstiumClient {
4835
5154
  /**
4836
5155
  * Open positions + margin summary for a user. Defaults to the connected
4837
5156
  * trader. Live prices and the current block number are fetched automatically.
5157
+ *
5158
+ * - `user`: pass an address to scope to a single trader, or `'ALL'` to fetch
5159
+ * positions across every trader (no trader filter).
5160
+ * - `limit`: cap the number of positions returned (default: all).
5161
+ * - `skip`: offset into the result set for pagination (default: `0`).
4838
5162
  */
4839
5163
  async getOpenPositions(params) {
4840
5164
  return this.subgraph.getOpenPositions({
4841
5165
  user: params?.user ?? this.getTraderAddress(),
4842
- blockNumber: params?.blockNumber ?? await this.publicClient.getBlockNumber()
5166
+ blockNumber: params?.blockNumber ?? await this.publicClient.getBlockNumber(),
5167
+ limit: params?.limit,
5168
+ skip: params?.skip
4843
5169
  });
4844
5170
  }
4845
5171
  /**
@@ -4944,31 +5270,104 @@ var OstiumClient = class _OstiumClient {
4944
5270
  streamPrices(pairIds) {
4945
5271
  return this.subgraph.streamPrices(pairIds);
4946
5272
  }
5273
+ /**
5274
+ * Stream price-driven updates for an existing `getOpenPositions()` response.
5275
+ *
5276
+ * Subscribes only to the unique pairs referenced by the response and emits the
5277
+ * full updated positions payload on each relevant price update. Pass an
5278
+ * existing `priceStream` to reuse a websocket your app already owns, or omit
5279
+ * it to let the SDK create a dedicated connection.
5280
+ */
5281
+ streamPositionUpdates(initial, priceStream) {
5282
+ return this.subgraph.streamPositionUpdates(initial, priceStream);
5283
+ }
4947
5284
  // ─────────────────────────────────────────────────────────────────────────
4948
5285
  // Internal helpers
4949
5286
  // ─────────────────────────────────────────────────────────────────────────
4950
- /**
4951
- * Core execution pipeline:
4952
- * 1. signer.prepare() — optionally wraps in delegatedAction.
4953
- * 2. submitter.submit() sends via EOA walletClient or Pimlico UserOp.
4954
- */
4955
- async execute(encoded) {
4956
- const { signer, submitter } = this.requireWriter();
4957
- const prepared = signer.prepare(encoded);
4958
- return submitter.submit(prepared);
4959
- }
4960
- /** Throws if the client is read-only; otherwise returns the signer + submitter. */
4961
- requireWriter() {
4962
- if (!this.signer || !this.submitter) {
5287
+ getSetupGaslessDelegationEncoded() {
5288
+ if (!this.selfGasless || !this.effectiveSender) {
5289
+ throw new OstiumError(
5290
+ "setupGaslessDelegation() is only available in Self + Gasless mode.",
5291
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5292
+ );
5293
+ }
5294
+ return encodeSetDelegate(this.effectiveSender, this.contracts.trading);
5295
+ }
5296
+ getApproveUsdcEncoded(amount) {
5297
+ if (this.delegated) {
5298
+ throw new OstiumError(
5299
+ "approveUsdc is not available in delegated mode. The trader must approve USDC directly from their own account.",
5300
+ "DELEGATION_FAILED" /* DELEGATION_FAILED */
5301
+ );
5302
+ }
5303
+ const rawAmount = amount === "max" ? viem.maxUint256 : parseUsdc(amount);
5304
+ return encodeUsdcApprove(this.contracts.tradingStorage, rawAmount, this.contracts.usdc);
5305
+ }
5306
+ getSetDelegateEncoded(delegateAddress) {
5307
+ return encodeSetDelegate(delegateAddress, this.contracts.trading);
5308
+ }
5309
+ getRemoveDelegateEncoded() {
5310
+ return encodeRemoveDelegate(this.contracts.trading);
5311
+ }
5312
+ prepareEncoded(encoded) {
5313
+ const signer = this.requireBuildCapability();
5314
+ return signer.prepare(encoded);
5315
+ }
5316
+ buildPreparedTx(encoded) {
5317
+ return this.toBuiltTxRequest(this.prepareEncoded(encoded));
5318
+ }
5319
+ buildDirectEoaTx(encoded) {
5320
+ if (!this.canBuildTransactions() || !this.traderAddress) {
5321
+ throw new OstiumError(
5322
+ "This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
5323
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5324
+ );
5325
+ }
5326
+ return {
5327
+ kind: "eoa",
5328
+ to: encoded.to,
5329
+ data: encoded.data,
5330
+ value: encoded.value,
5331
+ from: this.traderAddress,
5332
+ traderAddress: this.traderAddress
5333
+ };
5334
+ }
5335
+ toBuiltTxRequest(encoded) {
5336
+ if (!this.canBuildTransactions() || !this.traderAddress || !this.effectiveSender) {
4963
5337
  throw new OstiumError(
4964
- "This client is read-only. Use one of the createSelfAnd*/createDelegatedAnd* factories to enable writes.",
5338
+ "This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
4965
5339
  "INVALID_CONFIG" /* INVALID_CONFIG */
4966
5340
  );
4967
5341
  }
4968
- return { signer: this.signer, submitter: this.submitter };
5342
+ if (this.gasless) {
5343
+ return {
5344
+ kind: "safe",
5345
+ safeAddress: this.effectiveSender,
5346
+ traderAddress: this.traderAddress,
5347
+ calls: [encoded]
5348
+ };
5349
+ }
5350
+ return {
5351
+ kind: "eoa",
5352
+ to: encoded.to,
5353
+ data: encoded.data,
5354
+ value: encoded.value,
5355
+ from: this.effectiveSender,
5356
+ traderAddress: this.traderAddress
5357
+ };
5358
+ }
5359
+ async submitPrepared(encoded) {
5360
+ const submitter = this.requireSubmitCapability();
5361
+ return submitter.submit(this.prepareEncoded(encoded));
4969
5362
  }
4970
5363
  /** Direct EOA submission — used only in Self + Gasless for approveUsdc and setupGaslessDelegation. */
4971
- async submitDirectFromEoa(encoded) {
5364
+ async submitDirectEoa(encoded) {
5365
+ if (!this.eoaSubmit) {
5366
+ throw new OstiumError(
5367
+ "This client cannot submit direct EOA transactions.",
5368
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5369
+ );
5370
+ }
4972
5371
  try {
4973
5372
  return await this.eoaSubmit(encoded);
4974
5373
  } catch (err) {
@@ -4984,6 +5383,24 @@ var OstiumClient = class _OstiumClient {
4984
5383
  throw new OstiumError(`Transaction failed: ${msg}`, "SUBMISSION_FAILED" /* SUBMISSION_FAILED */, err);
4985
5384
  }
4986
5385
  }
5386
+ requireBuildCapability() {
5387
+ if (!this.signer) {
5388
+ throw new OstiumError(
5389
+ "This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
5390
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5391
+ );
5392
+ }
5393
+ return this.signer;
5394
+ }
5395
+ requireSubmitCapability() {
5396
+ if (!this.submitter) {
5397
+ throw new OstiumError(
5398
+ "This client does not have submission credentials. Use the corresponding get*Tx() method or initialize with a private key.",
5399
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5400
+ );
5401
+ }
5402
+ return this.submitter;
5403
+ }
4987
5404
  resolveSlippage(override) {
4988
5405
  return override ?? this.defaultSlippageBps;
4989
5406
  }
@@ -5000,6 +5417,7 @@ exports.OrderType = OrderType;
5000
5417
  exports.OstiumClient = OstiumClient;
5001
5418
  exports.OstiumError = OstiumError;
5002
5419
  exports.OstiumErrorCode = OstiumErrorCode;
5420
+ exports.OstiumPositionUpdatesStream = OstiumPositionUpdatesStream;
5003
5421
  exports.OstiumPriceStream = OstiumPriceStream;
5004
5422
  exports.OstiumSubgraphClient = OstiumSubgraphClient;
5005
5423
  exports.OstiumSubgraphError = OstiumSubgraphError;