@ostium/builder-sdk 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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) {
@@ -3226,10 +3247,11 @@ async function fetchLivePrices(builderApiUrl) {
3226
3247
  }
3227
3248
  return result;
3228
3249
  }
3229
- function rawTickToPublic(item) {
3250
+ function rawTickToPublic(item, pairId) {
3230
3251
  const from = normalizePairName(item.from);
3231
3252
  const to = normalizePairName(item.to);
3232
3253
  return {
3254
+ pairId,
3233
3255
  feedId: item.feed_id,
3234
3256
  pair: `${from}-${to}`,
3235
3257
  from,
@@ -3276,9 +3298,12 @@ var OstiumPriceStream = class _OstiumPriceStream {
3276
3298
  snapshotHandlers = /* @__PURE__ */ new Set();
3277
3299
  /** pairId (string) → raw "FROM-TO" name for the WS API. */
3278
3300
  pairRawNameCache;
3279
- constructor(ws, pairRawNameCache, initialSubscribeRawPairs) {
3301
+ /** raw "FROM-TO" name → pairId (string) for mapping incoming ticks back to SDK ids. */
3302
+ rawNamePairIdCache;
3303
+ constructor(ws, pairRawNameCache, rawNamePairIdCache, initialSubscribeRawPairs) {
3280
3304
  this.ws = ws;
3281
3305
  this.pairRawNameCache = pairRawNameCache;
3306
+ this.rawNamePairIdCache = rawNamePairIdCache;
3282
3307
  if (initialSubscribeRawPairs?.length) {
3283
3308
  ws.once("open", () => {
3284
3309
  ws.send(JSON.stringify({ type: "subscribe", pairs: initialSubscribeRawPairs }));
@@ -3292,10 +3317,13 @@ var OstiumPriceStream = class _OstiumPriceStream {
3292
3317
  return;
3293
3318
  }
3294
3319
  if (msg.type === "snapshot" && Array.isArray(msg.data)) {
3295
- const ticks = msg.data.map(rawTickToPublic);
3320
+ const ticks = msg.data.map(
3321
+ (item) => rawTickToPublic(item, this.rawNamePairIdCache.get(`${item.from}-${item.to}`))
3322
+ );
3296
3323
  for (const h of this.snapshotHandlers) h(ticks);
3297
3324
  } else if (msg.type === "tick" && msg.data) {
3298
- const tick = rawTickToPublic(msg.data);
3325
+ const item = msg.data;
3326
+ const tick = rawTickToPublic(item, this.rawNamePairIdCache.get(`${item.from}-${item.to}`));
3299
3327
  for (const h of this.tickHandlers) h(tick);
3300
3328
  }
3301
3329
  });
@@ -3321,7 +3349,11 @@ var OstiumPriceStream = class _OstiumPriceStream {
3321
3349
  const ws = new WS__default.default(url, {
3322
3350
  headers: { "User-Agent": "ostium-sdk", "Accept": "*/*" }
3323
3351
  });
3324
- return new _OstiumPriceStream(ws, pairRawNameCache, initial);
3352
+ const rawNamePairIdCache = /* @__PURE__ */ new Map();
3353
+ for (const [pairId, rawName] of pairRawNameCache.entries()) {
3354
+ rawNamePairIdCache.set(rawName, pairId);
3355
+ }
3356
+ return new _OstiumPriceStream(ws, pairRawNameCache, rawNamePairIdCache, initial);
3325
3357
  }
3326
3358
  /**
3327
3359
  * Register a callback that fires on every incoming price tick.
@@ -3397,6 +3429,37 @@ var OstiumPriceStream = class _OstiumPriceStream {
3397
3429
  }
3398
3430
  };
3399
3431
 
3432
+ // src/data/internal/aggregations.ts
3433
+ function aggregateMarginSummary(pairPositions) {
3434
+ let accountValue = 0;
3435
+ let totalCollateralUsed = 0;
3436
+ let totalNtlPos = 0;
3437
+ let totalRawPnlUsd = 0;
3438
+ let totalCumRollover = 0;
3439
+ let totalWithdrawable = 0;
3440
+ for (const { position } of pairPositions) {
3441
+ const collateral = parseFloat(position.collateralUsed) || 0;
3442
+ const ntl = parseFloat(position.ntl) || 0;
3443
+ const pnl = parseFloat(position.unrealizedPnl) || 0;
3444
+ const rollover = parseFloat(position.cumRollover) || 0;
3445
+ const withdrawable = parseFloat(position.maxWithdrawable) || 0;
3446
+ accountValue += collateral + pnl;
3447
+ totalCollateralUsed += collateral;
3448
+ totalNtlPos += ntl;
3449
+ totalRawPnlUsd += pnl;
3450
+ totalCumRollover += rollover;
3451
+ totalWithdrawable += withdrawable;
3452
+ }
3453
+ return {
3454
+ accountValue: accountValue.toString(),
3455
+ totalCollateralUsed: totalCollateralUsed.toString(),
3456
+ totalNtlPos: totalNtlPos.toString(),
3457
+ totalRawPnlUsd: totalRawPnlUsd.toString(),
3458
+ totalCumRollover: totalCumRollover.toString(),
3459
+ totalWithdrawable: totalWithdrawable.toString()
3460
+ };
3461
+ }
3462
+
3400
3463
  // src/data/internal/formatters.ts
3401
3464
  var MIN_NOTIONAL = "5.0";
3402
3465
  var MIN_NOTIONAL_NUM = 5;
@@ -3498,14 +3561,14 @@ function formatPosition(raw, price, pnl, maxLeverage) {
3498
3561
  returnOnEquity: roe.toString(),
3499
3562
  liquidationPx: pnl.liquidationPrice.toString(),
3500
3563
  collateralUsed: collateral.toString(),
3501
- cumRollover: pnl.rollover.toString(),
3564
+ cumRollover: (pnl.rollover * -1).toString(),
3502
3565
  ...raw.takeProfitPrice && raw.takeProfitPrice !== "0" ? { tpPx: formatTokens(raw.takeProfitPrice).toString() } : {},
3503
3566
  ...raw.stopLossPrice && raw.stopLossPrice !== "0" ? { slPx: formatTokens(raw.stopLossPrice).toString() } : {},
3504
3567
  openTimestamp: parseInt(raw.timestamp || "0") * 1e3,
3505
3568
  isDayTrade: raw.isDayTrade ?? false,
3506
- maxLeverage: maxLeverage.toString()
3507
- },
3508
- maxWithdrawable: maxWithdrawForPosition(collateral, leverage, maxLeverage).toString()
3569
+ maxLeverage: maxLeverage.toString(),
3570
+ maxWithdrawable: maxWithdrawForPosition(collateral, leverage, maxLeverage).toString()
3571
+ }
3509
3572
  };
3510
3573
  }
3511
3574
  function formatFill(raw) {
@@ -3582,6 +3645,131 @@ function formatOpenOrder(raw) {
3582
3645
  timestamp: parseInt(raw.initiatedAt || "0") * 1e3
3583
3646
  };
3584
3647
  }
3648
+
3649
+ // src/data/internal/position-updates.ts
3650
+ function cloneResponse(response) {
3651
+ return {
3652
+ pairPositions: response.pairPositions.map((pairPos) => ({
3653
+ position: { ...pairPos.position }
3654
+ })),
3655
+ marginSummary: { ...response.marginSummary },
3656
+ time: response.time
3657
+ };
3658
+ }
3659
+ function applyTickToPosition(position, tick) {
3660
+ const size = parseFloat(position.szi) || 0;
3661
+ const entry = parseFloat(position.entryPx) || 0;
3662
+ const collateral = parseFloat(position.collateralUsed) || 0;
3663
+ const maxLeverage = parseFloat(position.maxLeverage) || 0;
3664
+ const rollover = parseFloat(position.cumRollover) || 0;
3665
+ const isLong = position.side === "B";
3666
+ const ntl = size * tick.mid;
3667
+ const rawPnl = isLong ? (tick.mid - entry) * size : (entry - tick.mid) * size;
3668
+ const netPnl = rawPnl + rollover;
3669
+ const roe = collateral > 0 ? netPnl / collateral : 0;
3670
+ const currentLeverage = collateral > 0 ? ntl / collateral : 0;
3671
+ const maxWithdrawable = maxWithdrawForPosition(collateral, currentLeverage, maxLeverage);
3672
+ return {
3673
+ ...position,
3674
+ ntl: ntl.toString(),
3675
+ unrealizedPnl: netPnl.toString(),
3676
+ returnOnEquity: roe.toString(),
3677
+ maxWithdrawable: maxWithdrawable.toString()
3678
+ };
3679
+ }
3680
+ function updateResponseWithTick(current, tick, timestampMs) {
3681
+ if (!tick.pairId) return current;
3682
+ let changed = false;
3683
+ const pairPositions = current.pairPositions.map((pairPos) => {
3684
+ if (String(pairPos.position.pairId) !== String(tick.pairId)) {
3685
+ return pairPos;
3686
+ }
3687
+ changed = true;
3688
+ return {
3689
+ ...pairPos,
3690
+ position: applyTickToPosition(pairPos.position, tick)
3691
+ };
3692
+ });
3693
+ if (!changed) return current;
3694
+ return {
3695
+ pairPositions,
3696
+ marginSummary: aggregateMarginSummary(pairPositions),
3697
+ time: timestampMs
3698
+ };
3699
+ }
3700
+ var OstiumPositionUpdatesStream = class {
3701
+ priceStream;
3702
+ updateHandlers = /* @__PURE__ */ new Set();
3703
+ current;
3704
+ trackedPairIds;
3705
+ constructor(initial, trackedPairIds, priceStream) {
3706
+ this.current = cloneResponse(initial);
3707
+ this.priceStream = priceStream;
3708
+ this.trackedPairIds = new Set(trackedPairIds);
3709
+ if (!priceStream) return;
3710
+ priceStream.onSnapshot((ticks) => {
3711
+ this.ingestSnapshot(ticks);
3712
+ });
3713
+ priceStream.onTick((tick) => {
3714
+ this.ingestTick(tick);
3715
+ });
3716
+ }
3717
+ onUpdate(handler) {
3718
+ this.updateHandlers.add(handler);
3719
+ return () => {
3720
+ this.updateHandlers.delete(handler);
3721
+ };
3722
+ }
3723
+ onOpen(handler) {
3724
+ this.priceStream?.onOpen(handler);
3725
+ return this;
3726
+ }
3727
+ onError(handler) {
3728
+ this.priceStream?.onError(handler);
3729
+ return this;
3730
+ }
3731
+ onClose(handler) {
3732
+ this.priceStream?.onClose(handler);
3733
+ return this;
3734
+ }
3735
+ getCurrent() {
3736
+ return cloneResponse(this.current);
3737
+ }
3738
+ /**
3739
+ * Apply a single externally sourced price tick to the tracked positions.
3740
+ *
3741
+ * Use this when your app already has its own websocket connection and you want
3742
+ * the SDK to handle only the position recalculation logic.
3743
+ */
3744
+ ingestTick(tick) {
3745
+ if (!tick.pairId || !this.trackedPairIds.has(String(tick.pairId))) return;
3746
+ const next = updateResponseWithTick(this.current, tick, tick.timestampSeconds * 1e3);
3747
+ if (next !== this.current) {
3748
+ this.current = next;
3749
+ this.emit(next);
3750
+ }
3751
+ }
3752
+ /**
3753
+ * Apply a batch of externally sourced ticks, such as a websocket snapshot.
3754
+ */
3755
+ ingestSnapshot(ticks) {
3756
+ let next = this.current;
3757
+ for (const tick of ticks) {
3758
+ if (!tick.pairId || !this.trackedPairIds.has(String(tick.pairId))) continue;
3759
+ next = updateResponseWithTick(next, tick, tick.timestampSeconds * 1e3);
3760
+ }
3761
+ if (next !== this.current) {
3762
+ this.current = next;
3763
+ this.emit(next);
3764
+ }
3765
+ }
3766
+ close() {
3767
+ this.priceStream?.close();
3768
+ }
3769
+ emit(positions) {
3770
+ for (const handler of this.updateHandlers) handler(cloneResponse(positions));
3771
+ }
3772
+ };
3585
3773
  var EMPTY_RESULT = { long: [], short: [] };
3586
3774
  var ORDERBOOK_MAX_LEVELS = 20;
3587
3775
  function generateLogSpacedNotionals(levels, capacity) {
@@ -3777,20 +3965,16 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
3777
3965
  }),
3778
3966
  this.fetchLivePricesSafe()
3779
3967
  ]);
3780
- let totalWithdrawable = 0;
3781
3968
  const pairPositions = trades.map((trade) => {
3782
3969
  const price = prices[`${trade.pair.from}/${trade.pair.to}`];
3783
3970
  const pnl = price && blockNumber ? getTradePnL(trade, price, blockNumber) : EMPTY_PNL;
3784
3971
  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;
3972
+ return formatPosition(trade, price, pnl, maxLev);
3788
3973
  });
3789
3974
  const marginSummary = aggregateMarginSummary(pairPositions);
3790
3975
  return {
3791
3976
  pairPositions,
3792
3977
  marginSummary,
3793
- withdrawable: totalWithdrawable.toString(),
3794
3978
  time: Date.now()
3795
3979
  };
3796
3980
  }
@@ -4041,6 +4225,24 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
4041
4225
  });
4042
4226
  return OstiumPriceStream.connect(this.builderApiUrl, rawNames, cache);
4043
4227
  }
4228
+ /**
4229
+ * Stream price-driven updates for an existing `getOpenPositions()` response.
4230
+ *
4231
+ * The SDK subscribes only to the unique pairs present in `initial.pairPositions`,
4232
+ * recalculates price-sensitive fields for affected positions on each tick, and
4233
+ * emits the full updated response.
4234
+ *
4235
+ * Pass an existing `priceStream` to reuse a websocket connection your app has
4236
+ * already opened. Omit it to let the SDK open and manage a dedicated
4237
+ * connection for the tracked pair ids.
4238
+ */
4239
+ streamPositionUpdates(initial, priceStream) {
4240
+ const pairIds = [...new Set(initial.pairPositions.map(({ position }) => String(position.pairId)))];
4241
+ if (pairIds.length === 0) {
4242
+ return new OstiumPositionUpdatesStream(initial, []);
4243
+ }
4244
+ return new OstiumPositionUpdatesStream(initial, pairIds, priceStream ?? this.streamPrices(pairIds));
4245
+ }
4044
4246
  // ─────────────────────────────────────────────────────────────────────────
4045
4247
  // Internal — fetchers, cache, query wrapper
4046
4248
  // ─────────────────────────────────────────────────────────────────────────
@@ -4099,43 +4301,28 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
4099
4301
  }
4100
4302
  }
4101
4303
  };
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
4304
 
4124
4305
  // src/client.ts
4125
4306
  var HEX64_RE = /^0x[0-9a-fA-F]{64}$/;
4126
4307
  var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
4127
4308
  function validateConfig(config) {
4128
- if (!HEX64_RE.test(config.privateKey)) {
4309
+ if (config.privateKey && !HEX64_RE.test(config.privateKey)) {
4129
4310
  throw new OstiumError(
4130
4311
  "privateKey must be a 64-character hex string prefixed with 0x",
4131
4312
  "INVALID_CONFIG" /* INVALID_CONFIG */
4132
4313
  );
4133
4314
  }
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
- );
4315
+ for (const [label, value] of [
4316
+ ["traderAddress", config.traderAddress],
4317
+ ["delegateAddress", config.delegateAddress],
4318
+ ["safeAddress", config.safeAddress]
4319
+ ]) {
4320
+ if (value && !ADDRESS_RE.test(value)) {
4321
+ throw new OstiumError(
4322
+ `${label} must be a valid 42-character Ethereum address (0x + 40 hex chars)`,
4323
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4324
+ );
4325
+ }
4139
4326
  }
4140
4327
  if (config.slippageBps !== void 0 && (config.slippageBps < 0 || config.slippageBps > 500)) {
4141
4328
  throw new OstiumError(
@@ -4149,10 +4336,45 @@ function validateConfig(config) {
4149
4336
  "INVALID_CONFIG" /* INVALID_CONFIG */
4150
4337
  );
4151
4338
  }
4339
+ let mode;
4340
+ try {
4341
+ mode = resolveMode(config);
4342
+ } catch {
4343
+ throw new OstiumError(
4344
+ "Could not infer client mode from config. Provide the required addresses for the selected factory.",
4345
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4346
+ );
4347
+ }
4348
+ if (mode === "self-self" && !config.privateKey && !config.traderAddress) {
4349
+ throw new OstiumError(
4350
+ "Self + Self mode requires traderPrivateKey for submission or traderAddress for build-only usage.",
4351
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4352
+ );
4353
+ }
4354
+ if (mode === "self-gasless" && !config.privateKey && (!config.traderAddress || !config.safeAddress)) {
4355
+ throw new OstiumError(
4356
+ "Self + Gasless build-only mode requires traderAddress and safeAddress.",
4357
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4358
+ );
4359
+ }
4360
+ if (mode === "delegated-self" && (!config.traderAddress || !config.privateKey && !config.delegateAddress)) {
4361
+ throw new OstiumError(
4362
+ "Delegated + Self mode requires traderAddress and either delegatePrivateKey or delegateAddress.",
4363
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4364
+ );
4365
+ }
4366
+ if (mode === "delegated-gasless" && (!config.traderAddress || !config.privateKey && (!config.delegateAddress || !config.safeAddress))) {
4367
+ throw new OstiumError(
4368
+ "Delegated + Gasless mode requires traderAddress and either delegatePrivateKey or both delegateAddress and safeAddress.",
4369
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4370
+ );
4371
+ }
4152
4372
  }
4153
4373
  var OstiumClient = class _OstiumClient {
4154
4374
  signer;
4155
4375
  submitter;
4376
+ traderAddress;
4377
+ effectiveSender;
4156
4378
  contracts;
4157
4379
  publicClient;
4158
4380
  defaultSlippageBps;
@@ -4167,6 +4389,8 @@ var OstiumClient = class _OstiumClient {
4167
4389
  constructor(internals) {
4168
4390
  this.signer = internals.signer;
4169
4391
  this.submitter = internals.submitter;
4392
+ this.traderAddress = internals.traderAddress;
4393
+ this.effectiveSender = internals.effectiveSender;
4170
4394
  this.contracts = internals.contracts;
4171
4395
  this.publicClient = internals.publicClient;
4172
4396
  this.defaultSlippageBps = internals.defaultSlippageBps;
@@ -4191,17 +4415,20 @@ var OstiumClient = class _OstiumClient {
4191
4415
  * });
4192
4416
  * await client.openTrade({ ... });
4193
4417
  * ```
4194
- */
4418
+ */
4195
4419
  static async createSelfAndSelf(params) {
4196
- return _OstiumClient._fromConfig({
4197
- privateKey: params.traderPrivateKey,
4198
- rpcUrl: params.rpcUrl,
4420
+ const base = {
4421
+ mode: "self-self",
4199
4422
  testnet: params.testnet,
4200
4423
  slippageBps: params.slippageBps,
4201
4424
  builder: params.builder,
4202
4425
  subgraphUrl: params.subgraphUrl,
4203
- builderApiUrl: params.builderApiUrl
4204
- });
4426
+ builderApiUrl: params.builderApiUrl,
4427
+ rpcUrl: params.rpcUrl
4428
+ };
4429
+ return _OstiumClient._fromConfig(
4430
+ "traderPrivateKey" in params ? { ...base, privateKey: params.traderPrivateKey } : { ...base, traderAddress: params.traderAddress }
4431
+ );
4205
4432
  }
4206
4433
  /**
4207
4434
  * **Self + Gasless** — your EOA owns everything; a Safe relays trades for free.
@@ -4225,17 +4452,27 @@ var OstiumClient = class _OstiumClient {
4225
4452
  */
4226
4453
  static async createSelfAndGasless(params) {
4227
4454
  const defaultUrl = params.testnet ? DEFAULT_PIMLICO_URL_TESTNET : DEFAULT_PIMLICO_URL;
4228
- return _OstiumClient._fromConfig({
4229
- privateKey: params.traderPrivateKey,
4230
- pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4455
+ const base = {
4456
+ mode: "self-gasless",
4231
4457
  rpcUrl: params.rpcUrl,
4232
- sponsorshipPolicyId: params.sponsorshipPolicyId,
4233
4458
  testnet: params.testnet,
4234
4459
  slippageBps: params.slippageBps,
4235
4460
  builder: params.builder,
4236
4461
  subgraphUrl: params.subgraphUrl,
4237
4462
  builderApiUrl: params.builderApiUrl
4238
- });
4463
+ };
4464
+ return _OstiumClient._fromConfig(
4465
+ "traderPrivateKey" in params ? {
4466
+ ...base,
4467
+ privateKey: params.traderPrivateKey,
4468
+ pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4469
+ sponsorshipPolicyId: params.sponsorshipPolicyId
4470
+ } : {
4471
+ ...base,
4472
+ traderAddress: params.traderAddress,
4473
+ safeAddress: params.safeAddress
4474
+ }
4475
+ );
4239
4476
  }
4240
4477
  /**
4241
4478
  * **Delegated + Self** — a delegate EOA signs and pays gas on behalf of a trader address.
@@ -4255,8 +4492,8 @@ var OstiumClient = class _OstiumClient {
4255
4492
  * ```
4256
4493
  */
4257
4494
  static async createDelegatedAndSelf(params) {
4258
- return _OstiumClient._fromConfig({
4259
- privateKey: params.delegatePrivateKey,
4495
+ const base = {
4496
+ mode: "delegated-self",
4260
4497
  traderAddress: params.traderAddress,
4261
4498
  rpcUrl: params.rpcUrl,
4262
4499
  testnet: params.testnet,
@@ -4264,7 +4501,10 @@ var OstiumClient = class _OstiumClient {
4264
4501
  builder: params.builder,
4265
4502
  subgraphUrl: params.subgraphUrl,
4266
4503
  builderApiUrl: params.builderApiUrl
4267
- });
4504
+ };
4505
+ return _OstiumClient._fromConfig(
4506
+ "delegatePrivateKey" in params ? { ...base, privateKey: params.delegatePrivateKey } : { ...base, delegateAddress: params.delegateAddress }
4507
+ );
4268
4508
  }
4269
4509
  /**
4270
4510
  * **Delegated + Gasless** — a Safe derived from the delegate key submits sponsored
@@ -4285,18 +4525,28 @@ var OstiumClient = class _OstiumClient {
4285
4525
  */
4286
4526
  static async createDelegatedAndGasless(params) {
4287
4527
  const defaultUrl = params.testnet ? DEFAULT_PIMLICO_URL_TESTNET : DEFAULT_PIMLICO_URL;
4288
- return _OstiumClient._fromConfig({
4289
- privateKey: params.delegatePrivateKey,
4528
+ const base = {
4529
+ mode: "delegated-gasless",
4290
4530
  traderAddress: params.traderAddress,
4291
- pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4292
4531
  rpcUrl: params.rpcUrl,
4293
- sponsorshipPolicyId: params.sponsorshipPolicyId,
4294
4532
  testnet: params.testnet,
4295
4533
  slippageBps: params.slippageBps,
4296
4534
  builder: params.builder,
4297
4535
  subgraphUrl: params.subgraphUrl,
4298
4536
  builderApiUrl: params.builderApiUrl
4299
- });
4537
+ };
4538
+ return _OstiumClient._fromConfig(
4539
+ "delegatePrivateKey" in params ? {
4540
+ ...base,
4541
+ privateKey: params.delegatePrivateKey,
4542
+ pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4543
+ sponsorshipPolicyId: params.sponsorshipPolicyId
4544
+ } : {
4545
+ ...base,
4546
+ delegateAddress: params.delegateAddress,
4547
+ safeAddress: params.safeAddress
4548
+ }
4549
+ );
4300
4550
  }
4301
4551
  /**
4302
4552
  * **Read-only client** — no signer, no submitter, no privateKey required.
@@ -4341,6 +4591,7 @@ var OstiumClient = class _OstiumClient {
4341
4591
  /** Internal factory shared by all public constructors. */
4342
4592
  static async _fromConfig(config) {
4343
4593
  validateConfig(config);
4594
+ const mode = resolveMode(config);
4344
4595
  const testnet = config.testnet ?? false;
4345
4596
  const networkConfig = getNetworkConfig(testnet);
4346
4597
  const chain = testnet ? chains.arbitrumSepolia : chains.arbitrum;
@@ -4350,18 +4601,29 @@ var OstiumClient = class _OstiumClient {
4350
4601
  tradingStorage: networkConfig.contracts.tradingStorage,
4351
4602
  usdc: networkConfig.contracts.usdc
4352
4603
  };
4353
- const selfGasless = isGasless(config) && !isDelegated(config);
4354
- const submitter = await createSubmitter(config, testnet);
4604
+ const selfGasless = mode === "self-gasless";
4355
4605
  let traderAddress;
4356
- if (isDelegated(config)) {
4606
+ if (mode === "self-self" || mode === "self-gasless") {
4607
+ traderAddress = config.traderAddress ?? accounts.privateKeyToAccount(config.privateKey).address;
4608
+ } else {
4357
4609
  traderAddress = config.traderAddress;
4610
+ }
4611
+ const signer = createSigner(mode, traderAddress);
4612
+ let submitter;
4613
+ let effectiveSender;
4614
+ if (hasPrivateKey(config)) {
4615
+ submitter = await createSubmitter(config, testnet, mode);
4616
+ effectiveSender = submitter.effectiveSender;
4617
+ } else if (mode === "self-self") {
4618
+ effectiveSender = traderAddress;
4619
+ } else if (mode === "delegated-self") {
4620
+ effectiveSender = config.delegateAddress;
4358
4621
  } else {
4359
- traderAddress = accounts.privateKeyToAccount(config.privateKey).address;
4622
+ effectiveSender = config.safeAddress;
4360
4623
  }
4361
- const signer = createSigner(config, traderAddress, selfGasless);
4362
4624
  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) {
4625
+ const rpcUrl = config.rpcUrl ?? publicRpc;
4626
+ if (hasPrivateKey(config) && mode !== "self-gasless" && mode !== "delegated-gasless" && !config.rpcUrl) {
4365
4627
  throw new OstiumError(
4366
4628
  "rpcUrl is required for self-submission mode",
4367
4629
  "INVALID_CONFIG" /* INVALID_CONFIG */
@@ -4369,7 +4631,7 @@ var OstiumClient = class _OstiumClient {
4369
4631
  }
4370
4632
  const publicClient = viem.createPublicClient({ chain, transport: viem.http(rpcUrl) });
4371
4633
  let eoaSubmit;
4372
- if (selfGasless) {
4634
+ if (selfGasless && hasPrivateKey(config)) {
4373
4635
  const eoaAccount = accounts.privateKeyToAccount(config.privateKey);
4374
4636
  const eoaRpcUrl = config.rpcUrl ?? publicRpc;
4375
4637
  const eoaWalletClient = viem.createWalletClient({ account: eoaAccount, chain, transport: viem.http(eoaRpcUrl) });
@@ -4391,11 +4653,13 @@ var OstiumClient = class _OstiumClient {
4391
4653
  return new _OstiumClient({
4392
4654
  signer,
4393
4655
  submitter,
4656
+ traderAddress,
4657
+ effectiveSender,
4394
4658
  contracts,
4395
4659
  publicClient,
4396
4660
  defaultSlippageBps,
4397
- delegated: isDelegated(config),
4398
- gasless: isGasless(config),
4661
+ delegated: mode === "delegated-self" || mode === "delegated-gasless",
4662
+ gasless: mode === "self-gasless" || mode === "delegated-gasless",
4399
4663
  selfGasless,
4400
4664
  eoaSubmit,
4401
4665
  builderAddress: config.builder?.address,
@@ -4413,17 +4677,25 @@ var OstiumClient = class _OstiumClient {
4413
4677
  * - Self + Gasless: EOA derived from privateKey (NOT the Safe — USDC lives here).
4414
4678
  */
4415
4679
  getTraderAddress() {
4416
- if (!this.signer) {
4680
+ if (!this.traderAddress) {
4417
4681
  throw new OstiumError(
4418
4682
  "No connected trader \u2014 pass `user` explicitly or use one of the createSelfAnd*/createDelegatedAnd* factories.",
4419
4683
  "INVALID_CONFIG" /* INVALID_CONFIG */
4420
4684
  );
4421
4685
  }
4422
- return this.signer.traderAddress;
4686
+ return this.traderAddress;
4423
4687
  }
4424
- /** True when the client was created via `createReadOnly` and cannot submit transactions. */
4688
+ /** True when the client cannot submit transactions directly. */
4425
4689
  isReadOnly() {
4426
- return !this.signer || !this.submitter;
4690
+ return !this.submitter;
4691
+ }
4692
+ /** True when the client can build mode-correct unsigned transaction requests. */
4693
+ canBuildTransactions() {
4694
+ return !!this.signer && !!this.effectiveSender;
4695
+ }
4696
+ /** True when the client has the credentials needed for SDK-managed submission. */
4697
+ canSubmitTransactions() {
4698
+ return !!this.submitter;
4427
4699
  }
4428
4700
  /**
4429
4701
  * Returns the Safe smart-account address used for gasless submission.
@@ -4435,7 +4707,7 @@ var OstiumClient = class _OstiumClient {
4435
4707
  * - Self + Self / Delegated + Self: returns undefined.
4436
4708
  */
4437
4709
  getSmartAccountAddress() {
4438
- return this.gasless && this.submitter ? this.submitter.effectiveSender : void 0;
4710
+ return this.gasless ? this.effectiveSender : void 0;
4439
4711
  }
4440
4712
  // ─────────────────────────────────────────────────────────────────────────
4441
4713
  // Self + Gasless setup
@@ -4459,15 +4731,10 @@ var OstiumClient = class _OstiumClient {
4459
4731
  * ```
4460
4732
  */
4461
4733
  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);
4734
+ return this.submitDirectEoa(this.getSetupGaslessDelegationEncoded());
4735
+ }
4736
+ getSetupGaslessDelegationTx() {
4737
+ return this.buildDirectEoaTx(this.getSetupGaslessDelegationEncoded());
4471
4738
  }
4472
4739
  // ─────────────────────────────────────────────────────────────────────────
4473
4740
  // USDC helpers
@@ -4529,18 +4796,14 @@ var OstiumClient = class _OstiumClient {
4529
4796
  * @param amount USD amount as decimal string (e.g. "1000"), or "max" for MaxUint256.
4530
4797
  */
4531
4798
  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);
4799
+ const encoded = this.getApproveUsdcEncoded(amount);
4540
4800
  if (this.selfGasless) {
4541
- return this.submitDirectFromEoa(encoded);
4801
+ return this.submitDirectEoa(encoded);
4542
4802
  }
4543
- return this.requireWriter().submitter.submit(encoded);
4803
+ return this.submitPrepared(encoded);
4804
+ }
4805
+ getApproveUsdcTx(amount) {
4806
+ return this.buildDirectEoaTx(this.getApproveUsdcEncoded(amount));
4544
4807
  }
4545
4808
  // ─────────────────────────────────────────────────────────────────────────
4546
4809
  // Delegation helpers
@@ -4554,16 +4817,20 @@ var OstiumClient = class _OstiumClient {
4554
4817
  * update the trader's delegation on their behalf.
4555
4818
  */
4556
4819
  async setDelegate(delegateAddress) {
4557
- const encoded = encodeSetDelegate(delegateAddress, this.contracts.trading);
4558
- return this.execute(encoded);
4820
+ return this.submitPrepared(this.getSetDelegateEncoded(delegateAddress));
4821
+ }
4822
+ getSetDelegateTx(delegateAddress) {
4823
+ return this.buildPreparedTx(this.getSetDelegateEncoded(delegateAddress));
4559
4824
  }
4560
4825
  /**
4561
4826
  * Remove the current delegate on the trading contract.
4562
4827
  * Follows the same delegation-wrapping rules as setDelegate().
4563
4828
  */
4564
4829
  async removeDelegate() {
4565
- const encoded = encodeRemoveDelegate(this.contracts.trading);
4566
- return this.execute(encoded);
4830
+ return this.submitPrepared(this.getRemoveDelegateEncoded());
4831
+ }
4832
+ getRemoveDelegateTx() {
4833
+ return this.buildPreparedTx(this.getRemoveDelegateEncoded());
4567
4834
  }
4568
4835
  // ─────────────────────────────────────────────────────────────────────────
4569
4836
  // Core trading methods
@@ -4576,6 +4843,12 @@ var OstiumClient = class _OstiumClient {
4576
4843
  * the current allowance.
4577
4844
  */
4578
4845
  async openTrade(params) {
4846
+ return this.submitPrepared(this.getOpenTradeEncoded(params));
4847
+ }
4848
+ getOpenTradeTx(params) {
4849
+ return this.buildPreparedTx(this.getOpenTradeEncoded(params));
4850
+ }
4851
+ getOpenTradeEncoded(params) {
4579
4852
  const collateralNum = parseFloat(params.collateral);
4580
4853
  if (collateralNum > MAX_COLLATERAL_USD) {
4581
4854
  throw new OstiumError(
@@ -4584,7 +4857,7 @@ var OstiumClient = class _OstiumClient {
4584
4857
  );
4585
4858
  }
4586
4859
  const apiOpen = {
4587
- a: params.pairIndex,
4860
+ a: Number(params.pairId),
4588
4861
  b: params.buy,
4589
4862
  p: params.price,
4590
4863
  s: params.collateral,
@@ -4607,8 +4880,7 @@ var OstiumClient = class _OstiumClient {
4607
4880
  }
4608
4881
  const builderFee = buildBuilderFee(apiBuilder);
4609
4882
  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);
4883
+ return encodeOpenTrade(trade, builderFee, orderType, slippageP, this.contracts.trading);
4612
4884
  }
4613
4885
  /**
4614
4886
  * Close an open position (full or partial).
@@ -4623,6 +4895,12 @@ var OstiumClient = class _OstiumClient {
4623
4895
  * ```
4624
4896
  */
4625
4897
  async closeTrade(params) {
4898
+ return this.submitPrepared(this.getCloseTradeEncoded(params));
4899
+ }
4900
+ getCloseTradeTx(params) {
4901
+ return this.buildPreparedTx(this.getCloseTradeEncoded(params));
4902
+ }
4903
+ getCloseTradeEncoded(params) {
4626
4904
  try {
4627
4905
  validateCloseParams({
4628
4906
  a: Number(params.pairId),
@@ -4642,7 +4920,7 @@ var OstiumClient = class _OstiumClient {
4642
4920
  const closePercentage = BigInt(Math.round(params.closePercent * 100));
4643
4921
  const marketPrice = parsePrice(params.price);
4644
4922
  const slippageP = BigInt(this.resolveSlippage(params.slippage));
4645
- const encoded = encodeCloseTradeMarket(
4923
+ return encodeCloseTradeMarket(
4646
4924
  pairIndex,
4647
4925
  index,
4648
4926
  closePercentage,
@@ -4650,7 +4928,6 @@ var OstiumClient = class _OstiumClient {
4650
4928
  slippageP,
4651
4929
  this.contracts.trading
4652
4930
  );
4653
- return this.execute(encoded);
4654
4931
  }
4655
4932
  /**
4656
4933
  * Cancel a pending order.
@@ -4673,6 +4950,12 @@ var OstiumClient = class _OstiumClient {
4673
4950
  * ```
4674
4951
  */
4675
4952
  async cancelOrder(params) {
4953
+ return this.submitPrepared(this.getCancelOrderEncoded(params));
4954
+ }
4955
+ getCancelOrderTx(params) {
4956
+ return this.buildPreparedTx(this.getCancelOrderEncoded(params));
4957
+ }
4958
+ getCancelOrderEncoded(params) {
4676
4959
  let encoded;
4677
4960
  if (params.type === "limit" /* Limit */) {
4678
4961
  const pairIdNum = Number(params.pairId);
@@ -4709,7 +4992,7 @@ var OstiumClient = class _OstiumClient {
4709
4992
  }
4710
4993
  encoded = encodeOpenTradeMarketTimeout(BigInt(params.orderId), this.contracts.trading);
4711
4994
  }
4712
- return this.execute(encoded);
4995
+ return encoded;
4713
4996
  }
4714
4997
  /**
4715
4998
  * Modify an open trade or a pending limit order.
@@ -4734,6 +5017,12 @@ var OstiumClient = class _OstiumClient {
4734
5017
  * - Both `takeProfit` and `stopLoss` without `price` → throws (send two calls).
4735
5018
  */
4736
5019
  async modifyOrder(params) {
5020
+ return this.submitPrepared(this.getModifyOrderEncoded(params));
5021
+ }
5022
+ getModifyOrderTx(params) {
5023
+ return this.buildPreparedTx(this.getModifyOrderEncoded(params));
5024
+ }
5025
+ getModifyOrderEncoded(params) {
4737
5026
  try {
4738
5027
  validateModifyParams({
4739
5028
  a: Number(params.pairId),
@@ -4773,7 +5062,7 @@ var OstiumClient = class _OstiumClient {
4773
5062
  "VALIDATION_FAILED" /* VALIDATION_FAILED */
4774
5063
  );
4775
5064
  }
4776
- return this.execute(encoded);
5065
+ return encoded;
4777
5066
  }
4778
5067
  /**
4779
5068
  * Update collateral on an open position (isolated margin).
@@ -4793,6 +5082,12 @@ var OstiumClient = class _OstiumClient {
4793
5082
  * Checks USDC allowance before top-up operations.
4794
5083
  */
4795
5084
  async updateCollateral(params) {
5085
+ return this.submitPrepared(await this.getUpdateCollateralEncoded(params));
5086
+ }
5087
+ async getUpdateCollateralTx(params) {
5088
+ return this.buildPreparedTx(await this.getUpdateCollateralEncoded(params));
5089
+ }
5090
+ async getUpdateCollateralEncoded(params) {
4796
5091
  const amount = parseFloat(params.amount);
4797
5092
  if (Number.isNaN(amount) || amount === 0) {
4798
5093
  throw new OstiumError(
@@ -4816,7 +5111,7 @@ var OstiumClient = class _OstiumClient {
4816
5111
  } else {
4817
5112
  encoded = encodeRemoveCollateral(pairIndex, index, absAmount, this.contracts.trading);
4818
5113
  }
4819
- return this.execute(encoded);
5114
+ return encoded;
4820
5115
  }
4821
5116
  // ─────────────────────────────────────────────────────────────────────────
4822
5117
  // Subgraph reads (Hyperliquid-style API)
@@ -4944,31 +5239,104 @@ var OstiumClient = class _OstiumClient {
4944
5239
  streamPrices(pairIds) {
4945
5240
  return this.subgraph.streamPrices(pairIds);
4946
5241
  }
5242
+ /**
5243
+ * Stream price-driven updates for an existing `getOpenPositions()` response.
5244
+ *
5245
+ * Subscribes only to the unique pairs referenced by the response and emits the
5246
+ * full updated positions payload on each relevant price update. Pass an
5247
+ * existing `priceStream` to reuse a websocket your app already owns, or omit
5248
+ * it to let the SDK create a dedicated connection.
5249
+ */
5250
+ streamPositionUpdates(initial, priceStream) {
5251
+ return this.subgraph.streamPositionUpdates(initial, priceStream);
5252
+ }
4947
5253
  // ─────────────────────────────────────────────────────────────────────────
4948
5254
  // Internal helpers
4949
5255
  // ─────────────────────────────────────────────────────────────────────────
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) {
5256
+ getSetupGaslessDelegationEncoded() {
5257
+ if (!this.selfGasless || !this.effectiveSender) {
4963
5258
  throw new OstiumError(
4964
- "This client is read-only. Use one of the createSelfAnd*/createDelegatedAnd* factories to enable writes.",
5259
+ "setupGaslessDelegation() is only available in Self + Gasless mode.",
4965
5260
  "INVALID_CONFIG" /* INVALID_CONFIG */
4966
5261
  );
4967
5262
  }
4968
- return { signer: this.signer, submitter: this.submitter };
5263
+ return encodeSetDelegate(this.effectiveSender, this.contracts.trading);
5264
+ }
5265
+ getApproveUsdcEncoded(amount) {
5266
+ if (this.delegated) {
5267
+ throw new OstiumError(
5268
+ "approveUsdc is not available in delegated mode. The trader must approve USDC directly from their own account.",
5269
+ "DELEGATION_FAILED" /* DELEGATION_FAILED */
5270
+ );
5271
+ }
5272
+ const rawAmount = amount === "max" ? viem.maxUint256 : parseUsdc(amount);
5273
+ return encodeUsdcApprove(this.contracts.tradingStorage, rawAmount, this.contracts.usdc);
5274
+ }
5275
+ getSetDelegateEncoded(delegateAddress) {
5276
+ return encodeSetDelegate(delegateAddress, this.contracts.trading);
5277
+ }
5278
+ getRemoveDelegateEncoded() {
5279
+ return encodeRemoveDelegate(this.contracts.trading);
5280
+ }
5281
+ prepareEncoded(encoded) {
5282
+ const signer = this.requireBuildCapability();
5283
+ return signer.prepare(encoded);
5284
+ }
5285
+ buildPreparedTx(encoded) {
5286
+ return this.toBuiltTxRequest(this.prepareEncoded(encoded));
5287
+ }
5288
+ buildDirectEoaTx(encoded) {
5289
+ if (!this.canBuildTransactions() || !this.traderAddress) {
5290
+ throw new OstiumError(
5291
+ "This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
5292
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5293
+ );
5294
+ }
5295
+ return {
5296
+ kind: "eoa",
5297
+ to: encoded.to,
5298
+ data: encoded.data,
5299
+ value: encoded.value,
5300
+ from: this.traderAddress,
5301
+ traderAddress: this.traderAddress
5302
+ };
5303
+ }
5304
+ toBuiltTxRequest(encoded) {
5305
+ if (!this.canBuildTransactions() || !this.traderAddress || !this.effectiveSender) {
5306
+ throw new OstiumError(
5307
+ "This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
5308
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5309
+ );
5310
+ }
5311
+ if (this.gasless) {
5312
+ return {
5313
+ kind: "safe",
5314
+ safeAddress: this.effectiveSender,
5315
+ traderAddress: this.traderAddress,
5316
+ calls: [encoded]
5317
+ };
5318
+ }
5319
+ return {
5320
+ kind: "eoa",
5321
+ to: encoded.to,
5322
+ data: encoded.data,
5323
+ value: encoded.value,
5324
+ from: this.effectiveSender,
5325
+ traderAddress: this.traderAddress
5326
+ };
5327
+ }
5328
+ async submitPrepared(encoded) {
5329
+ const submitter = this.requireSubmitCapability();
5330
+ return submitter.submit(this.prepareEncoded(encoded));
4969
5331
  }
4970
5332
  /** Direct EOA submission — used only in Self + Gasless for approveUsdc and setupGaslessDelegation. */
4971
- async submitDirectFromEoa(encoded) {
5333
+ async submitDirectEoa(encoded) {
5334
+ if (!this.eoaSubmit) {
5335
+ throw new OstiumError(
5336
+ "This client cannot submit direct EOA transactions.",
5337
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5338
+ );
5339
+ }
4972
5340
  try {
4973
5341
  return await this.eoaSubmit(encoded);
4974
5342
  } catch (err) {
@@ -4984,6 +5352,24 @@ var OstiumClient = class _OstiumClient {
4984
5352
  throw new OstiumError(`Transaction failed: ${msg}`, "SUBMISSION_FAILED" /* SUBMISSION_FAILED */, err);
4985
5353
  }
4986
5354
  }
5355
+ requireBuildCapability() {
5356
+ if (!this.signer) {
5357
+ throw new OstiumError(
5358
+ "This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
5359
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5360
+ );
5361
+ }
5362
+ return this.signer;
5363
+ }
5364
+ requireSubmitCapability() {
5365
+ if (!this.submitter) {
5366
+ throw new OstiumError(
5367
+ "This client does not have submission credentials. Use the corresponding get*Tx() method or initialize with a private key.",
5368
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5369
+ );
5370
+ }
5371
+ return this.submitter;
5372
+ }
4987
5373
  resolveSlippage(override) {
4988
5374
  return override ?? this.defaultSlippageBps;
4989
5375
  }
@@ -5000,6 +5386,7 @@ exports.OrderType = OrderType;
5000
5386
  exports.OstiumClient = OstiumClient;
5001
5387
  exports.OstiumError = OstiumError;
5002
5388
  exports.OstiumErrorCode = OstiumErrorCode;
5389
+ exports.OstiumPositionUpdatesStream = OstiumPositionUpdatesStream;
5003
5390
  exports.OstiumPriceStream = OstiumPriceStream;
5004
5391
  exports.OstiumSubgraphClient = OstiumSubgraphClient;
5005
5392
  exports.OstiumSubgraphError = OstiumSubgraphError;