@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.js CHANGED
@@ -254,11 +254,20 @@ var CancelOrderType = /* @__PURE__ */ ((CancelOrderType2) => {
254
254
  // src/config.ts
255
255
  var DEFAULT_PIMLICO_URL = "https://builder.ostium.io/v1/pimlico/sponsor?chainId=42161";
256
256
  var DEFAULT_PIMLICO_URL_TESTNET = "https://builder.ostium.io/v1/pimlico/sponsor?chainId=421614";
257
- function isGasless(config) {
258
- return !!config.pimlicoUrl;
257
+ function hasPrivateKey(config) {
258
+ return typeof config.privateKey === "string" && config.privateKey.length > 0;
259
259
  }
260
- function isDelegated(config) {
261
- return !!config.traderAddress;
260
+ function resolveMode(config) {
261
+ if (config.mode) return config.mode;
262
+ if (config.privateKey) {
263
+ if (config.pimlicoUrl) return config.traderAddress ? "delegated-gasless" : "self-gasless";
264
+ return config.traderAddress ? "delegated-self" : "self-self";
265
+ }
266
+ if (config.safeAddress && config.delegateAddress && config.traderAddress) return "delegated-gasless";
267
+ if (config.safeAddress && config.traderAddress) return "self-gasless";
268
+ if (config.delegateAddress && config.traderAddress) return "delegated-self";
269
+ if (config.traderAddress) return "self-self";
270
+ throw new Error("Unable to infer client mode from config");
262
271
  }
263
272
 
264
273
  // src/errors.ts
@@ -2661,8 +2670,8 @@ var DelegatedSignerStrategy = class {
2661
2670
  };
2662
2671
 
2663
2672
  // src/signer/index.ts
2664
- function createSigner(config, traderAddress, selfGasless = false) {
2665
- if (isDelegated(config) || selfGasless) {
2673
+ function createSigner(mode, traderAddress) {
2674
+ if (mode !== "self-self") {
2666
2675
  return new DelegatedSignerStrategy(traderAddress);
2667
2676
  }
2668
2677
  return new SelfSignerStrategy(traderAddress);
@@ -2686,6 +2695,12 @@ var SelfSubmissionStrategy = class {
2686
2695
  effectiveSender;
2687
2696
  send;
2688
2697
  constructor(config, testnet) {
2698
+ if (!config.privateKey) {
2699
+ throw new OstiumError(
2700
+ "privateKey is required for self-submission mode",
2701
+ "INVALID_CONFIG" /* INVALID_CONFIG */
2702
+ );
2703
+ }
2689
2704
  if (!config.rpcUrl) {
2690
2705
  throw new OstiumError(
2691
2706
  "rpcUrl is required for self-submission mode",
@@ -2751,6 +2766,12 @@ var GaslessSubmissionStrategy = class _GaslessSubmissionStrategy {
2751
2766
  this.effectiveSender = safeAccount.address;
2752
2767
  }
2753
2768
  static async create(config, testnet) {
2769
+ if (!config.privateKey) {
2770
+ throw new OstiumError(
2771
+ "privateKey is required for gasless submission mode",
2772
+ "INVALID_CONFIG" /* INVALID_CONFIG */
2773
+ );
2774
+ }
2754
2775
  if (!config.pimlicoUrl) {
2755
2776
  throw new OstiumError(
2756
2777
  "pimlicoUrl is required for gasless mode",
@@ -2819,8 +2840,8 @@ var GaslessSubmissionStrategy = class _GaslessSubmissionStrategy {
2819
2840
  };
2820
2841
 
2821
2842
  // src/submitter/index.ts
2822
- async function createSubmitter(config, testnet) {
2823
- if (isGasless(config)) {
2843
+ async function createSubmitter(config, testnet, mode) {
2844
+ if (mode === "self-gasless" || mode === "delegated-gasless") {
2824
2845
  return GaslessSubmissionStrategy.create(config, testnet);
2825
2846
  }
2826
2847
  if (!config.rpcUrl) {
@@ -3220,10 +3241,11 @@ async function fetchLivePrices(builderApiUrl) {
3220
3241
  }
3221
3242
  return result;
3222
3243
  }
3223
- function rawTickToPublic(item) {
3244
+ function rawTickToPublic(item, pairId) {
3224
3245
  const from = normalizePairName(item.from);
3225
3246
  const to = normalizePairName(item.to);
3226
3247
  return {
3248
+ pairId,
3227
3249
  feedId: item.feed_id,
3228
3250
  pair: `${from}-${to}`,
3229
3251
  from,
@@ -3270,9 +3292,12 @@ var OstiumPriceStream = class _OstiumPriceStream {
3270
3292
  snapshotHandlers = /* @__PURE__ */ new Set();
3271
3293
  /** pairId (string) → raw "FROM-TO" name for the WS API. */
3272
3294
  pairRawNameCache;
3273
- constructor(ws, pairRawNameCache, initialSubscribeRawPairs) {
3295
+ /** raw "FROM-TO" name → pairId (string) for mapping incoming ticks back to SDK ids. */
3296
+ rawNamePairIdCache;
3297
+ constructor(ws, pairRawNameCache, rawNamePairIdCache, initialSubscribeRawPairs) {
3274
3298
  this.ws = ws;
3275
3299
  this.pairRawNameCache = pairRawNameCache;
3300
+ this.rawNamePairIdCache = rawNamePairIdCache;
3276
3301
  if (initialSubscribeRawPairs?.length) {
3277
3302
  ws.once("open", () => {
3278
3303
  ws.send(JSON.stringify({ type: "subscribe", pairs: initialSubscribeRawPairs }));
@@ -3286,10 +3311,13 @@ var OstiumPriceStream = class _OstiumPriceStream {
3286
3311
  return;
3287
3312
  }
3288
3313
  if (msg.type === "snapshot" && Array.isArray(msg.data)) {
3289
- const ticks = msg.data.map(rawTickToPublic);
3314
+ const ticks = msg.data.map(
3315
+ (item) => rawTickToPublic(item, this.rawNamePairIdCache.get(`${item.from}-${item.to}`))
3316
+ );
3290
3317
  for (const h of this.snapshotHandlers) h(ticks);
3291
3318
  } else if (msg.type === "tick" && msg.data) {
3292
- const tick = rawTickToPublic(msg.data);
3319
+ const item = msg.data;
3320
+ const tick = rawTickToPublic(item, this.rawNamePairIdCache.get(`${item.from}-${item.to}`));
3293
3321
  for (const h of this.tickHandlers) h(tick);
3294
3322
  }
3295
3323
  });
@@ -3315,7 +3343,11 @@ var OstiumPriceStream = class _OstiumPriceStream {
3315
3343
  const ws = new WS(url, {
3316
3344
  headers: { "User-Agent": "ostium-sdk", "Accept": "*/*" }
3317
3345
  });
3318
- return new _OstiumPriceStream(ws, pairRawNameCache, initial);
3346
+ const rawNamePairIdCache = /* @__PURE__ */ new Map();
3347
+ for (const [pairId, rawName] of pairRawNameCache.entries()) {
3348
+ rawNamePairIdCache.set(rawName, pairId);
3349
+ }
3350
+ return new _OstiumPriceStream(ws, pairRawNameCache, rawNamePairIdCache, initial);
3319
3351
  }
3320
3352
  /**
3321
3353
  * Register a callback that fires on every incoming price tick.
@@ -3391,6 +3423,37 @@ var OstiumPriceStream = class _OstiumPriceStream {
3391
3423
  }
3392
3424
  };
3393
3425
 
3426
+ // src/data/internal/aggregations.ts
3427
+ function aggregateMarginSummary(pairPositions) {
3428
+ let accountValue = 0;
3429
+ let totalCollateralUsed = 0;
3430
+ let totalNtlPos = 0;
3431
+ let totalRawPnlUsd = 0;
3432
+ let totalCumRollover = 0;
3433
+ let totalWithdrawable = 0;
3434
+ for (const { position } of pairPositions) {
3435
+ const collateral = parseFloat(position.collateralUsed) || 0;
3436
+ const ntl = parseFloat(position.ntl) || 0;
3437
+ const pnl = parseFloat(position.unrealizedPnl) || 0;
3438
+ const rollover = parseFloat(position.cumRollover) || 0;
3439
+ const withdrawable = parseFloat(position.maxWithdrawable) || 0;
3440
+ accountValue += collateral + pnl;
3441
+ totalCollateralUsed += collateral;
3442
+ totalNtlPos += ntl;
3443
+ totalRawPnlUsd += pnl;
3444
+ totalCumRollover += rollover;
3445
+ totalWithdrawable += withdrawable;
3446
+ }
3447
+ return {
3448
+ accountValue: accountValue.toString(),
3449
+ totalCollateralUsed: totalCollateralUsed.toString(),
3450
+ totalNtlPos: totalNtlPos.toString(),
3451
+ totalRawPnlUsd: totalRawPnlUsd.toString(),
3452
+ totalCumRollover: totalCumRollover.toString(),
3453
+ totalWithdrawable: totalWithdrawable.toString()
3454
+ };
3455
+ }
3456
+
3394
3457
  // src/data/internal/formatters.ts
3395
3458
  var MIN_NOTIONAL = "5.0";
3396
3459
  var MIN_NOTIONAL_NUM = 5;
@@ -3492,14 +3555,14 @@ function formatPosition(raw, price, pnl, maxLeverage) {
3492
3555
  returnOnEquity: roe.toString(),
3493
3556
  liquidationPx: pnl.liquidationPrice.toString(),
3494
3557
  collateralUsed: collateral.toString(),
3495
- cumRollover: pnl.rollover.toString(),
3558
+ cumRollover: (pnl.rollover * -1).toString(),
3496
3559
  ...raw.takeProfitPrice && raw.takeProfitPrice !== "0" ? { tpPx: formatTokens(raw.takeProfitPrice).toString() } : {},
3497
3560
  ...raw.stopLossPrice && raw.stopLossPrice !== "0" ? { slPx: formatTokens(raw.stopLossPrice).toString() } : {},
3498
3561
  openTimestamp: parseInt(raw.timestamp || "0") * 1e3,
3499
3562
  isDayTrade: raw.isDayTrade ?? false,
3500
- maxLeverage: maxLeverage.toString()
3501
- },
3502
- maxWithdrawable: maxWithdrawForPosition(collateral, leverage, maxLeverage).toString()
3563
+ maxLeverage: maxLeverage.toString(),
3564
+ maxWithdrawable: maxWithdrawForPosition(collateral, leverage, maxLeverage).toString()
3565
+ }
3503
3566
  };
3504
3567
  }
3505
3568
  function formatFill(raw) {
@@ -3576,6 +3639,131 @@ function formatOpenOrder(raw) {
3576
3639
  timestamp: parseInt(raw.initiatedAt || "0") * 1e3
3577
3640
  };
3578
3641
  }
3642
+
3643
+ // src/data/internal/position-updates.ts
3644
+ function cloneResponse(response) {
3645
+ return {
3646
+ pairPositions: response.pairPositions.map((pairPos) => ({
3647
+ position: { ...pairPos.position }
3648
+ })),
3649
+ marginSummary: { ...response.marginSummary },
3650
+ time: response.time
3651
+ };
3652
+ }
3653
+ function applyTickToPosition(position, tick) {
3654
+ const size = parseFloat(position.szi) || 0;
3655
+ const entry = parseFloat(position.entryPx) || 0;
3656
+ const collateral = parseFloat(position.collateralUsed) || 0;
3657
+ const maxLeverage = parseFloat(position.maxLeverage) || 0;
3658
+ const rollover = parseFloat(position.cumRollover) || 0;
3659
+ const isLong = position.side === "B";
3660
+ const ntl = size * tick.mid;
3661
+ const rawPnl = isLong ? (tick.mid - entry) * size : (entry - tick.mid) * size;
3662
+ const netPnl = rawPnl + rollover;
3663
+ const roe = collateral > 0 ? netPnl / collateral : 0;
3664
+ const currentLeverage = collateral > 0 ? ntl / collateral : 0;
3665
+ const maxWithdrawable = maxWithdrawForPosition(collateral, currentLeverage, maxLeverage);
3666
+ return {
3667
+ ...position,
3668
+ ntl: ntl.toString(),
3669
+ unrealizedPnl: netPnl.toString(),
3670
+ returnOnEquity: roe.toString(),
3671
+ maxWithdrawable: maxWithdrawable.toString()
3672
+ };
3673
+ }
3674
+ function updateResponseWithTick(current, tick, timestampMs) {
3675
+ if (!tick.pairId) return current;
3676
+ let changed = false;
3677
+ const pairPositions = current.pairPositions.map((pairPos) => {
3678
+ if (String(pairPos.position.pairId) !== String(tick.pairId)) {
3679
+ return pairPos;
3680
+ }
3681
+ changed = true;
3682
+ return {
3683
+ ...pairPos,
3684
+ position: applyTickToPosition(pairPos.position, tick)
3685
+ };
3686
+ });
3687
+ if (!changed) return current;
3688
+ return {
3689
+ pairPositions,
3690
+ marginSummary: aggregateMarginSummary(pairPositions),
3691
+ time: timestampMs
3692
+ };
3693
+ }
3694
+ var OstiumPositionUpdatesStream = class {
3695
+ priceStream;
3696
+ updateHandlers = /* @__PURE__ */ new Set();
3697
+ current;
3698
+ trackedPairIds;
3699
+ constructor(initial, trackedPairIds, priceStream) {
3700
+ this.current = cloneResponse(initial);
3701
+ this.priceStream = priceStream;
3702
+ this.trackedPairIds = new Set(trackedPairIds);
3703
+ if (!priceStream) return;
3704
+ priceStream.onSnapshot((ticks) => {
3705
+ this.ingestSnapshot(ticks);
3706
+ });
3707
+ priceStream.onTick((tick) => {
3708
+ this.ingestTick(tick);
3709
+ });
3710
+ }
3711
+ onUpdate(handler) {
3712
+ this.updateHandlers.add(handler);
3713
+ return () => {
3714
+ this.updateHandlers.delete(handler);
3715
+ };
3716
+ }
3717
+ onOpen(handler) {
3718
+ this.priceStream?.onOpen(handler);
3719
+ return this;
3720
+ }
3721
+ onError(handler) {
3722
+ this.priceStream?.onError(handler);
3723
+ return this;
3724
+ }
3725
+ onClose(handler) {
3726
+ this.priceStream?.onClose(handler);
3727
+ return this;
3728
+ }
3729
+ getCurrent() {
3730
+ return cloneResponse(this.current);
3731
+ }
3732
+ /**
3733
+ * Apply a single externally sourced price tick to the tracked positions.
3734
+ *
3735
+ * Use this when your app already has its own websocket connection and you want
3736
+ * the SDK to handle only the position recalculation logic.
3737
+ */
3738
+ ingestTick(tick) {
3739
+ if (!tick.pairId || !this.trackedPairIds.has(String(tick.pairId))) return;
3740
+ const next = updateResponseWithTick(this.current, tick, tick.timestampSeconds * 1e3);
3741
+ if (next !== this.current) {
3742
+ this.current = next;
3743
+ this.emit(next);
3744
+ }
3745
+ }
3746
+ /**
3747
+ * Apply a batch of externally sourced ticks, such as a websocket snapshot.
3748
+ */
3749
+ ingestSnapshot(ticks) {
3750
+ let next = this.current;
3751
+ for (const tick of ticks) {
3752
+ if (!tick.pairId || !this.trackedPairIds.has(String(tick.pairId))) continue;
3753
+ next = updateResponseWithTick(next, tick, tick.timestampSeconds * 1e3);
3754
+ }
3755
+ if (next !== this.current) {
3756
+ this.current = next;
3757
+ this.emit(next);
3758
+ }
3759
+ }
3760
+ close() {
3761
+ this.priceStream?.close();
3762
+ }
3763
+ emit(positions) {
3764
+ for (const handler of this.updateHandlers) handler(cloneResponse(positions));
3765
+ }
3766
+ };
3579
3767
  var EMPTY_RESULT = { long: [], short: [] };
3580
3768
  var ORDERBOOK_MAX_LEVELS = 20;
3581
3769
  function generateLogSpacedNotionals(levels, capacity) {
@@ -3771,20 +3959,16 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
3771
3959
  }),
3772
3960
  this.fetchLivePricesSafe()
3773
3961
  ]);
3774
- let totalWithdrawable = 0;
3775
3962
  const pairPositions = trades.map((trade) => {
3776
3963
  const price = prices[`${trade.pair.from}/${trade.pair.to}`];
3777
3964
  const pnl = price && blockNumber ? getTradePnL(trade, price, blockNumber) : EMPTY_PNL;
3778
3965
  const maxLev = trade.isDayTrade ? formatLeverage(trade.pair.overnightMaxLeverage) : pairMaxLeverage(trade.pair);
3779
- const pairPos = formatPosition(trade, price, pnl, maxLev);
3780
- totalWithdrawable += parseFloat(pairPos.maxWithdrawable);
3781
- return pairPos;
3966
+ return formatPosition(trade, price, pnl, maxLev);
3782
3967
  });
3783
3968
  const marginSummary = aggregateMarginSummary(pairPositions);
3784
3969
  return {
3785
3970
  pairPositions,
3786
3971
  marginSummary,
3787
- withdrawable: totalWithdrawable.toString(),
3788
3972
  time: Date.now()
3789
3973
  };
3790
3974
  }
@@ -4035,6 +4219,24 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
4035
4219
  });
4036
4220
  return OstiumPriceStream.connect(this.builderApiUrl, rawNames, cache);
4037
4221
  }
4222
+ /**
4223
+ * Stream price-driven updates for an existing `getOpenPositions()` response.
4224
+ *
4225
+ * The SDK subscribes only to the unique pairs present in `initial.pairPositions`,
4226
+ * recalculates price-sensitive fields for affected positions on each tick, and
4227
+ * emits the full updated response.
4228
+ *
4229
+ * Pass an existing `priceStream` to reuse a websocket connection your app has
4230
+ * already opened. Omit it to let the SDK open and manage a dedicated
4231
+ * connection for the tracked pair ids.
4232
+ */
4233
+ streamPositionUpdates(initial, priceStream) {
4234
+ const pairIds = [...new Set(initial.pairPositions.map(({ position }) => String(position.pairId)))];
4235
+ if (pairIds.length === 0) {
4236
+ return new OstiumPositionUpdatesStream(initial, []);
4237
+ }
4238
+ return new OstiumPositionUpdatesStream(initial, pairIds, priceStream ?? this.streamPrices(pairIds));
4239
+ }
4038
4240
  // ─────────────────────────────────────────────────────────────────────────
4039
4241
  // Internal — fetchers, cache, query wrapper
4040
4242
  // ─────────────────────────────────────────────────────────────────────────
@@ -4093,43 +4295,28 @@ var OstiumSubgraphClient = class _OstiumSubgraphClient {
4093
4295
  }
4094
4296
  }
4095
4297
  };
4096
- function aggregateMarginSummary(pairPositions) {
4097
- let accountValue = 0;
4098
- let totalCollateralUsed = 0;
4099
- let totalNtlPos = 0;
4100
- let totalRawPnlUsd = 0;
4101
- for (const { position } of pairPositions) {
4102
- const collateral = parseFloat(position.collateralUsed) || 0;
4103
- const ntl = parseFloat(position.ntl) || 0;
4104
- const pnl = parseFloat(position.unrealizedPnl) || 0;
4105
- accountValue += collateral + pnl;
4106
- totalCollateralUsed += collateral;
4107
- totalNtlPos += ntl;
4108
- totalRawPnlUsd += pnl;
4109
- }
4110
- return {
4111
- accountValue: accountValue.toString(),
4112
- totalCollateralUsed: totalCollateralUsed.toString(),
4113
- totalNtlPos: totalNtlPos.toString(),
4114
- totalRawPnlUsd: totalRawPnlUsd.toString()
4115
- };
4116
- }
4117
4298
 
4118
4299
  // src/client.ts
4119
4300
  var HEX64_RE = /^0x[0-9a-fA-F]{64}$/;
4120
4301
  var ADDRESS_RE = /^0x[0-9a-fA-F]{40}$/;
4121
4302
  function validateConfig(config) {
4122
- if (!HEX64_RE.test(config.privateKey)) {
4303
+ if (config.privateKey && !HEX64_RE.test(config.privateKey)) {
4123
4304
  throw new OstiumError(
4124
4305
  "privateKey must be a 64-character hex string prefixed with 0x",
4125
4306
  "INVALID_CONFIG" /* INVALID_CONFIG */
4126
4307
  );
4127
4308
  }
4128
- if (config.traderAddress && !ADDRESS_RE.test(config.traderAddress)) {
4129
- throw new OstiumError(
4130
- "traderAddress must be a valid 42-character Ethereum address (0x + 40 hex chars)",
4131
- "INVALID_CONFIG" /* INVALID_CONFIG */
4132
- );
4309
+ for (const [label, value] of [
4310
+ ["traderAddress", config.traderAddress],
4311
+ ["delegateAddress", config.delegateAddress],
4312
+ ["safeAddress", config.safeAddress]
4313
+ ]) {
4314
+ if (value && !ADDRESS_RE.test(value)) {
4315
+ throw new OstiumError(
4316
+ `${label} must be a valid 42-character Ethereum address (0x + 40 hex chars)`,
4317
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4318
+ );
4319
+ }
4133
4320
  }
4134
4321
  if (config.slippageBps !== void 0 && (config.slippageBps < 0 || config.slippageBps > 500)) {
4135
4322
  throw new OstiumError(
@@ -4143,10 +4330,45 @@ function validateConfig(config) {
4143
4330
  "INVALID_CONFIG" /* INVALID_CONFIG */
4144
4331
  );
4145
4332
  }
4333
+ let mode;
4334
+ try {
4335
+ mode = resolveMode(config);
4336
+ } catch {
4337
+ throw new OstiumError(
4338
+ "Could not infer client mode from config. Provide the required addresses for the selected factory.",
4339
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4340
+ );
4341
+ }
4342
+ if (mode === "self-self" && !config.privateKey && !config.traderAddress) {
4343
+ throw new OstiumError(
4344
+ "Self + Self mode requires traderPrivateKey for submission or traderAddress for build-only usage.",
4345
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4346
+ );
4347
+ }
4348
+ if (mode === "self-gasless" && !config.privateKey && (!config.traderAddress || !config.safeAddress)) {
4349
+ throw new OstiumError(
4350
+ "Self + Gasless build-only mode requires traderAddress and safeAddress.",
4351
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4352
+ );
4353
+ }
4354
+ if (mode === "delegated-self" && (!config.traderAddress || !config.privateKey && !config.delegateAddress)) {
4355
+ throw new OstiumError(
4356
+ "Delegated + Self mode requires traderAddress and either delegatePrivateKey or delegateAddress.",
4357
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4358
+ );
4359
+ }
4360
+ if (mode === "delegated-gasless" && (!config.traderAddress || !config.privateKey && (!config.delegateAddress || !config.safeAddress))) {
4361
+ throw new OstiumError(
4362
+ "Delegated + Gasless mode requires traderAddress and either delegatePrivateKey or both delegateAddress and safeAddress.",
4363
+ "INVALID_CONFIG" /* INVALID_CONFIG */
4364
+ );
4365
+ }
4146
4366
  }
4147
4367
  var OstiumClient = class _OstiumClient {
4148
4368
  signer;
4149
4369
  submitter;
4370
+ traderAddress;
4371
+ effectiveSender;
4150
4372
  contracts;
4151
4373
  publicClient;
4152
4374
  defaultSlippageBps;
@@ -4161,6 +4383,8 @@ var OstiumClient = class _OstiumClient {
4161
4383
  constructor(internals) {
4162
4384
  this.signer = internals.signer;
4163
4385
  this.submitter = internals.submitter;
4386
+ this.traderAddress = internals.traderAddress;
4387
+ this.effectiveSender = internals.effectiveSender;
4164
4388
  this.contracts = internals.contracts;
4165
4389
  this.publicClient = internals.publicClient;
4166
4390
  this.defaultSlippageBps = internals.defaultSlippageBps;
@@ -4185,17 +4409,20 @@ var OstiumClient = class _OstiumClient {
4185
4409
  * });
4186
4410
  * await client.openTrade({ ... });
4187
4411
  * ```
4188
- */
4412
+ */
4189
4413
  static async createSelfAndSelf(params) {
4190
- return _OstiumClient._fromConfig({
4191
- privateKey: params.traderPrivateKey,
4192
- rpcUrl: params.rpcUrl,
4414
+ const base = {
4415
+ mode: "self-self",
4193
4416
  testnet: params.testnet,
4194
4417
  slippageBps: params.slippageBps,
4195
4418
  builder: params.builder,
4196
4419
  subgraphUrl: params.subgraphUrl,
4197
- builderApiUrl: params.builderApiUrl
4198
- });
4420
+ builderApiUrl: params.builderApiUrl,
4421
+ rpcUrl: params.rpcUrl
4422
+ };
4423
+ return _OstiumClient._fromConfig(
4424
+ "traderPrivateKey" in params ? { ...base, privateKey: params.traderPrivateKey } : { ...base, traderAddress: params.traderAddress }
4425
+ );
4199
4426
  }
4200
4427
  /**
4201
4428
  * **Self + Gasless** — your EOA owns everything; a Safe relays trades for free.
@@ -4219,17 +4446,27 @@ var OstiumClient = class _OstiumClient {
4219
4446
  */
4220
4447
  static async createSelfAndGasless(params) {
4221
4448
  const defaultUrl = params.testnet ? DEFAULT_PIMLICO_URL_TESTNET : DEFAULT_PIMLICO_URL;
4222
- return _OstiumClient._fromConfig({
4223
- privateKey: params.traderPrivateKey,
4224
- pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4449
+ const base = {
4450
+ mode: "self-gasless",
4225
4451
  rpcUrl: params.rpcUrl,
4226
- sponsorshipPolicyId: params.sponsorshipPolicyId,
4227
4452
  testnet: params.testnet,
4228
4453
  slippageBps: params.slippageBps,
4229
4454
  builder: params.builder,
4230
4455
  subgraphUrl: params.subgraphUrl,
4231
4456
  builderApiUrl: params.builderApiUrl
4232
- });
4457
+ };
4458
+ return _OstiumClient._fromConfig(
4459
+ "traderPrivateKey" in params ? {
4460
+ ...base,
4461
+ privateKey: params.traderPrivateKey,
4462
+ pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4463
+ sponsorshipPolicyId: params.sponsorshipPolicyId
4464
+ } : {
4465
+ ...base,
4466
+ traderAddress: params.traderAddress,
4467
+ safeAddress: params.safeAddress
4468
+ }
4469
+ );
4233
4470
  }
4234
4471
  /**
4235
4472
  * **Delegated + Self** — a delegate EOA signs and pays gas on behalf of a trader address.
@@ -4249,8 +4486,8 @@ var OstiumClient = class _OstiumClient {
4249
4486
  * ```
4250
4487
  */
4251
4488
  static async createDelegatedAndSelf(params) {
4252
- return _OstiumClient._fromConfig({
4253
- privateKey: params.delegatePrivateKey,
4489
+ const base = {
4490
+ mode: "delegated-self",
4254
4491
  traderAddress: params.traderAddress,
4255
4492
  rpcUrl: params.rpcUrl,
4256
4493
  testnet: params.testnet,
@@ -4258,7 +4495,10 @@ var OstiumClient = class _OstiumClient {
4258
4495
  builder: params.builder,
4259
4496
  subgraphUrl: params.subgraphUrl,
4260
4497
  builderApiUrl: params.builderApiUrl
4261
- });
4498
+ };
4499
+ return _OstiumClient._fromConfig(
4500
+ "delegatePrivateKey" in params ? { ...base, privateKey: params.delegatePrivateKey } : { ...base, delegateAddress: params.delegateAddress }
4501
+ );
4262
4502
  }
4263
4503
  /**
4264
4504
  * **Delegated + Gasless** — a Safe derived from the delegate key submits sponsored
@@ -4279,18 +4519,28 @@ var OstiumClient = class _OstiumClient {
4279
4519
  */
4280
4520
  static async createDelegatedAndGasless(params) {
4281
4521
  const defaultUrl = params.testnet ? DEFAULT_PIMLICO_URL_TESTNET : DEFAULT_PIMLICO_URL;
4282
- return _OstiumClient._fromConfig({
4283
- privateKey: params.delegatePrivateKey,
4522
+ const base = {
4523
+ mode: "delegated-gasless",
4284
4524
  traderAddress: params.traderAddress,
4285
- pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4286
4525
  rpcUrl: params.rpcUrl,
4287
- sponsorshipPolicyId: params.sponsorshipPolicyId,
4288
4526
  testnet: params.testnet,
4289
4527
  slippageBps: params.slippageBps,
4290
4528
  builder: params.builder,
4291
4529
  subgraphUrl: params.subgraphUrl,
4292
4530
  builderApiUrl: params.builderApiUrl
4293
- });
4531
+ };
4532
+ return _OstiumClient._fromConfig(
4533
+ "delegatePrivateKey" in params ? {
4534
+ ...base,
4535
+ privateKey: params.delegatePrivateKey,
4536
+ pimlicoUrl: params.pimlicoUrl ?? defaultUrl,
4537
+ sponsorshipPolicyId: params.sponsorshipPolicyId
4538
+ } : {
4539
+ ...base,
4540
+ delegateAddress: params.delegateAddress,
4541
+ safeAddress: params.safeAddress
4542
+ }
4543
+ );
4294
4544
  }
4295
4545
  /**
4296
4546
  * **Read-only client** — no signer, no submitter, no privateKey required.
@@ -4335,6 +4585,7 @@ var OstiumClient = class _OstiumClient {
4335
4585
  /** Internal factory shared by all public constructors. */
4336
4586
  static async _fromConfig(config) {
4337
4587
  validateConfig(config);
4588
+ const mode = resolveMode(config);
4338
4589
  const testnet = config.testnet ?? false;
4339
4590
  const networkConfig = getNetworkConfig(testnet);
4340
4591
  const chain = testnet ? arbitrumSepolia : arbitrum;
@@ -4344,18 +4595,29 @@ var OstiumClient = class _OstiumClient {
4344
4595
  tradingStorage: networkConfig.contracts.tradingStorage,
4345
4596
  usdc: networkConfig.contracts.usdc
4346
4597
  };
4347
- const selfGasless = isGasless(config) && !isDelegated(config);
4348
- const submitter = await createSubmitter(config, testnet);
4598
+ const selfGasless = mode === "self-gasless";
4349
4599
  let traderAddress;
4350
- if (isDelegated(config)) {
4600
+ if (mode === "self-self" || mode === "self-gasless") {
4601
+ traderAddress = config.traderAddress ?? privateKeyToAccount(config.privateKey).address;
4602
+ } else {
4351
4603
  traderAddress = config.traderAddress;
4604
+ }
4605
+ const signer = createSigner(mode, traderAddress);
4606
+ let submitter;
4607
+ let effectiveSender;
4608
+ if (hasPrivateKey(config)) {
4609
+ submitter = await createSubmitter(config, testnet, mode);
4610
+ effectiveSender = submitter.effectiveSender;
4611
+ } else if (mode === "self-self") {
4612
+ effectiveSender = traderAddress;
4613
+ } else if (mode === "delegated-self") {
4614
+ effectiveSender = config.delegateAddress;
4352
4615
  } else {
4353
- traderAddress = privateKeyToAccount(config.privateKey).address;
4616
+ effectiveSender = config.safeAddress;
4354
4617
  }
4355
- const signer = createSigner(config, traderAddress, selfGasless);
4356
4618
  const publicRpc = testnet ? "https://sepolia-rollup.arbitrum.io/rpc" : "https://arb1.arbitrum.io/rpc";
4357
- const rpcUrl = config.rpcUrl ?? (isGasless(config) ? publicRpc : void 0);
4358
- if (!isGasless(config) && !config.rpcUrl) {
4619
+ const rpcUrl = config.rpcUrl ?? publicRpc;
4620
+ if (hasPrivateKey(config) && mode !== "self-gasless" && mode !== "delegated-gasless" && !config.rpcUrl) {
4359
4621
  throw new OstiumError(
4360
4622
  "rpcUrl is required for self-submission mode",
4361
4623
  "INVALID_CONFIG" /* INVALID_CONFIG */
@@ -4363,7 +4625,7 @@ var OstiumClient = class _OstiumClient {
4363
4625
  }
4364
4626
  const publicClient = createPublicClient({ chain, transport: http(rpcUrl) });
4365
4627
  let eoaSubmit;
4366
- if (selfGasless) {
4628
+ if (selfGasless && hasPrivateKey(config)) {
4367
4629
  const eoaAccount = privateKeyToAccount(config.privateKey);
4368
4630
  const eoaRpcUrl = config.rpcUrl ?? publicRpc;
4369
4631
  const eoaWalletClient = createWalletClient({ account: eoaAccount, chain, transport: http(eoaRpcUrl) });
@@ -4385,11 +4647,13 @@ var OstiumClient = class _OstiumClient {
4385
4647
  return new _OstiumClient({
4386
4648
  signer,
4387
4649
  submitter,
4650
+ traderAddress,
4651
+ effectiveSender,
4388
4652
  contracts,
4389
4653
  publicClient,
4390
4654
  defaultSlippageBps,
4391
- delegated: isDelegated(config),
4392
- gasless: isGasless(config),
4655
+ delegated: mode === "delegated-self" || mode === "delegated-gasless",
4656
+ gasless: mode === "self-gasless" || mode === "delegated-gasless",
4393
4657
  selfGasless,
4394
4658
  eoaSubmit,
4395
4659
  builderAddress: config.builder?.address,
@@ -4407,17 +4671,25 @@ var OstiumClient = class _OstiumClient {
4407
4671
  * - Self + Gasless: EOA derived from privateKey (NOT the Safe — USDC lives here).
4408
4672
  */
4409
4673
  getTraderAddress() {
4410
- if (!this.signer) {
4674
+ if (!this.traderAddress) {
4411
4675
  throw new OstiumError(
4412
4676
  "No connected trader \u2014 pass `user` explicitly or use one of the createSelfAnd*/createDelegatedAnd* factories.",
4413
4677
  "INVALID_CONFIG" /* INVALID_CONFIG */
4414
4678
  );
4415
4679
  }
4416
- return this.signer.traderAddress;
4680
+ return this.traderAddress;
4417
4681
  }
4418
- /** True when the client was created via `createReadOnly` and cannot submit transactions. */
4682
+ /** True when the client cannot submit transactions directly. */
4419
4683
  isReadOnly() {
4420
- return !this.signer || !this.submitter;
4684
+ return !this.submitter;
4685
+ }
4686
+ /** True when the client can build mode-correct unsigned transaction requests. */
4687
+ canBuildTransactions() {
4688
+ return !!this.signer && !!this.effectiveSender;
4689
+ }
4690
+ /** True when the client has the credentials needed for SDK-managed submission. */
4691
+ canSubmitTransactions() {
4692
+ return !!this.submitter;
4421
4693
  }
4422
4694
  /**
4423
4695
  * Returns the Safe smart-account address used for gasless submission.
@@ -4429,7 +4701,7 @@ var OstiumClient = class _OstiumClient {
4429
4701
  * - Self + Self / Delegated + Self: returns undefined.
4430
4702
  */
4431
4703
  getSmartAccountAddress() {
4432
- return this.gasless && this.submitter ? this.submitter.effectiveSender : void 0;
4704
+ return this.gasless ? this.effectiveSender : void 0;
4433
4705
  }
4434
4706
  // ─────────────────────────────────────────────────────────────────────────
4435
4707
  // Self + Gasless setup
@@ -4453,15 +4725,10 @@ var OstiumClient = class _OstiumClient {
4453
4725
  * ```
4454
4726
  */
4455
4727
  async setupGaslessDelegation() {
4456
- if (!this.selfGasless || !this.eoaSubmit || !this.submitter) {
4457
- throw new OstiumError(
4458
- "setupGaslessDelegation() is only available in Self + Gasless mode. Create the client with pimlicoUrl but without traderAddress.",
4459
- "INVALID_CONFIG" /* INVALID_CONFIG */
4460
- );
4461
- }
4462
- const safeAddress = this.submitter.effectiveSender;
4463
- const encoded = encodeSetDelegate(safeAddress, this.contracts.trading);
4464
- return this.submitDirectFromEoa(encoded);
4728
+ return this.submitDirectEoa(this.getSetupGaslessDelegationEncoded());
4729
+ }
4730
+ getSetupGaslessDelegationTx() {
4731
+ return this.buildDirectEoaTx(this.getSetupGaslessDelegationEncoded());
4465
4732
  }
4466
4733
  // ─────────────────────────────────────────────────────────────────────────
4467
4734
  // USDC helpers
@@ -4523,18 +4790,14 @@ var OstiumClient = class _OstiumClient {
4523
4790
  * @param amount USD amount as decimal string (e.g. "1000"), or "max" for MaxUint256.
4524
4791
  */
4525
4792
  async approveUsdc(amount) {
4526
- if (this.delegated) {
4527
- throw new OstiumError(
4528
- "approveUsdc is not available in delegated mode. The trader must approve USDC directly from their own account.",
4529
- "DELEGATION_FAILED" /* DELEGATION_FAILED */
4530
- );
4531
- }
4532
- const rawAmount = amount === "max" ? maxUint256 : parseUsdc(amount);
4533
- const encoded = encodeUsdcApprove(this.contracts.tradingStorage, rawAmount, this.contracts.usdc);
4793
+ const encoded = this.getApproveUsdcEncoded(amount);
4534
4794
  if (this.selfGasless) {
4535
- return this.submitDirectFromEoa(encoded);
4795
+ return this.submitDirectEoa(encoded);
4536
4796
  }
4537
- return this.requireWriter().submitter.submit(encoded);
4797
+ return this.submitPrepared(encoded);
4798
+ }
4799
+ getApproveUsdcTx(amount) {
4800
+ return this.buildDirectEoaTx(this.getApproveUsdcEncoded(amount));
4538
4801
  }
4539
4802
  // ─────────────────────────────────────────────────────────────────────────
4540
4803
  // Delegation helpers
@@ -4548,16 +4811,20 @@ var OstiumClient = class _OstiumClient {
4548
4811
  * update the trader's delegation on their behalf.
4549
4812
  */
4550
4813
  async setDelegate(delegateAddress) {
4551
- const encoded = encodeSetDelegate(delegateAddress, this.contracts.trading);
4552
- return this.execute(encoded);
4814
+ return this.submitPrepared(this.getSetDelegateEncoded(delegateAddress));
4815
+ }
4816
+ getSetDelegateTx(delegateAddress) {
4817
+ return this.buildPreparedTx(this.getSetDelegateEncoded(delegateAddress));
4553
4818
  }
4554
4819
  /**
4555
4820
  * Remove the current delegate on the trading contract.
4556
4821
  * Follows the same delegation-wrapping rules as setDelegate().
4557
4822
  */
4558
4823
  async removeDelegate() {
4559
- const encoded = encodeRemoveDelegate(this.contracts.trading);
4560
- return this.execute(encoded);
4824
+ return this.submitPrepared(this.getRemoveDelegateEncoded());
4825
+ }
4826
+ getRemoveDelegateTx() {
4827
+ return this.buildPreparedTx(this.getRemoveDelegateEncoded());
4561
4828
  }
4562
4829
  // ─────────────────────────────────────────────────────────────────────────
4563
4830
  // Core trading methods
@@ -4570,6 +4837,12 @@ var OstiumClient = class _OstiumClient {
4570
4837
  * the current allowance.
4571
4838
  */
4572
4839
  async openTrade(params) {
4840
+ return this.submitPrepared(this.getOpenTradeEncoded(params));
4841
+ }
4842
+ getOpenTradeTx(params) {
4843
+ return this.buildPreparedTx(this.getOpenTradeEncoded(params));
4844
+ }
4845
+ getOpenTradeEncoded(params) {
4573
4846
  const collateralNum = parseFloat(params.collateral);
4574
4847
  if (collateralNum > MAX_COLLATERAL_USD) {
4575
4848
  throw new OstiumError(
@@ -4578,7 +4851,7 @@ var OstiumClient = class _OstiumClient {
4578
4851
  );
4579
4852
  }
4580
4853
  const apiOpen = {
4581
- a: params.pairIndex,
4854
+ a: Number(params.pairId),
4582
4855
  b: params.buy,
4583
4856
  p: params.price,
4584
4857
  s: params.collateral,
@@ -4601,8 +4874,7 @@ var OstiumClient = class _OstiumClient {
4601
4874
  }
4602
4875
  const builderFee = buildBuilderFee(apiBuilder);
4603
4876
  const slippageP = params.type === "market" /* Market */ ? BigInt(this.resolveSlippage(params.slippage)) : 0n;
4604
- const encoded = encodeOpenTrade(trade, builderFee, orderType, slippageP, this.contracts.trading);
4605
- return this.execute(encoded);
4877
+ return encodeOpenTrade(trade, builderFee, orderType, slippageP, this.contracts.trading);
4606
4878
  }
4607
4879
  /**
4608
4880
  * Close an open position (full or partial).
@@ -4617,6 +4889,12 @@ var OstiumClient = class _OstiumClient {
4617
4889
  * ```
4618
4890
  */
4619
4891
  async closeTrade(params) {
4892
+ return this.submitPrepared(this.getCloseTradeEncoded(params));
4893
+ }
4894
+ getCloseTradeTx(params) {
4895
+ return this.buildPreparedTx(this.getCloseTradeEncoded(params));
4896
+ }
4897
+ getCloseTradeEncoded(params) {
4620
4898
  try {
4621
4899
  validateCloseParams({
4622
4900
  a: Number(params.pairId),
@@ -4636,7 +4914,7 @@ var OstiumClient = class _OstiumClient {
4636
4914
  const closePercentage = BigInt(Math.round(params.closePercent * 100));
4637
4915
  const marketPrice = parsePrice(params.price);
4638
4916
  const slippageP = BigInt(this.resolveSlippage(params.slippage));
4639
- const encoded = encodeCloseTradeMarket(
4917
+ return encodeCloseTradeMarket(
4640
4918
  pairIndex,
4641
4919
  index,
4642
4920
  closePercentage,
@@ -4644,7 +4922,6 @@ var OstiumClient = class _OstiumClient {
4644
4922
  slippageP,
4645
4923
  this.contracts.trading
4646
4924
  );
4647
- return this.execute(encoded);
4648
4925
  }
4649
4926
  /**
4650
4927
  * Cancel a pending order.
@@ -4667,6 +4944,12 @@ var OstiumClient = class _OstiumClient {
4667
4944
  * ```
4668
4945
  */
4669
4946
  async cancelOrder(params) {
4947
+ return this.submitPrepared(this.getCancelOrderEncoded(params));
4948
+ }
4949
+ getCancelOrderTx(params) {
4950
+ return this.buildPreparedTx(this.getCancelOrderEncoded(params));
4951
+ }
4952
+ getCancelOrderEncoded(params) {
4670
4953
  let encoded;
4671
4954
  if (params.type === "limit" /* Limit */) {
4672
4955
  const pairIdNum = Number(params.pairId);
@@ -4703,7 +4986,7 @@ var OstiumClient = class _OstiumClient {
4703
4986
  }
4704
4987
  encoded = encodeOpenTradeMarketTimeout(BigInt(params.orderId), this.contracts.trading);
4705
4988
  }
4706
- return this.execute(encoded);
4989
+ return encoded;
4707
4990
  }
4708
4991
  /**
4709
4992
  * Modify an open trade or a pending limit order.
@@ -4728,6 +5011,12 @@ var OstiumClient = class _OstiumClient {
4728
5011
  * - Both `takeProfit` and `stopLoss` without `price` → throws (send two calls).
4729
5012
  */
4730
5013
  async modifyOrder(params) {
5014
+ return this.submitPrepared(this.getModifyOrderEncoded(params));
5015
+ }
5016
+ getModifyOrderTx(params) {
5017
+ return this.buildPreparedTx(this.getModifyOrderEncoded(params));
5018
+ }
5019
+ getModifyOrderEncoded(params) {
4731
5020
  try {
4732
5021
  validateModifyParams({
4733
5022
  a: Number(params.pairId),
@@ -4767,7 +5056,7 @@ var OstiumClient = class _OstiumClient {
4767
5056
  "VALIDATION_FAILED" /* VALIDATION_FAILED */
4768
5057
  );
4769
5058
  }
4770
- return this.execute(encoded);
5059
+ return encoded;
4771
5060
  }
4772
5061
  /**
4773
5062
  * Update collateral on an open position (isolated margin).
@@ -4787,6 +5076,12 @@ var OstiumClient = class _OstiumClient {
4787
5076
  * Checks USDC allowance before top-up operations.
4788
5077
  */
4789
5078
  async updateCollateral(params) {
5079
+ return this.submitPrepared(await this.getUpdateCollateralEncoded(params));
5080
+ }
5081
+ async getUpdateCollateralTx(params) {
5082
+ return this.buildPreparedTx(await this.getUpdateCollateralEncoded(params));
5083
+ }
5084
+ async getUpdateCollateralEncoded(params) {
4790
5085
  const amount = parseFloat(params.amount);
4791
5086
  if (Number.isNaN(amount) || amount === 0) {
4792
5087
  throw new OstiumError(
@@ -4810,7 +5105,7 @@ var OstiumClient = class _OstiumClient {
4810
5105
  } else {
4811
5106
  encoded = encodeRemoveCollateral(pairIndex, index, absAmount, this.contracts.trading);
4812
5107
  }
4813
- return this.execute(encoded);
5108
+ return encoded;
4814
5109
  }
4815
5110
  // ─────────────────────────────────────────────────────────────────────────
4816
5111
  // Subgraph reads (Hyperliquid-style API)
@@ -4938,31 +5233,104 @@ var OstiumClient = class _OstiumClient {
4938
5233
  streamPrices(pairIds) {
4939
5234
  return this.subgraph.streamPrices(pairIds);
4940
5235
  }
5236
+ /**
5237
+ * Stream price-driven updates for an existing `getOpenPositions()` response.
5238
+ *
5239
+ * Subscribes only to the unique pairs referenced by the response and emits the
5240
+ * full updated positions payload on each relevant price update. Pass an
5241
+ * existing `priceStream` to reuse a websocket your app already owns, or omit
5242
+ * it to let the SDK create a dedicated connection.
5243
+ */
5244
+ streamPositionUpdates(initial, priceStream) {
5245
+ return this.subgraph.streamPositionUpdates(initial, priceStream);
5246
+ }
4941
5247
  // ─────────────────────────────────────────────────────────────────────────
4942
5248
  // Internal helpers
4943
5249
  // ─────────────────────────────────────────────────────────────────────────
4944
- /**
4945
- * Core execution pipeline:
4946
- * 1. signer.prepare() — optionally wraps in delegatedAction.
4947
- * 2. submitter.submit() — sends via EOA walletClient or Pimlico UserOp.
4948
- */
4949
- async execute(encoded) {
4950
- const { signer, submitter } = this.requireWriter();
4951
- const prepared = signer.prepare(encoded);
4952
- return submitter.submit(prepared);
4953
- }
4954
- /** Throws if the client is read-only; otherwise returns the signer + submitter. */
4955
- requireWriter() {
4956
- if (!this.signer || !this.submitter) {
5250
+ getSetupGaslessDelegationEncoded() {
5251
+ if (!this.selfGasless || !this.effectiveSender) {
4957
5252
  throw new OstiumError(
4958
- "This client is read-only. Use one of the createSelfAnd*/createDelegatedAnd* factories to enable writes.",
5253
+ "setupGaslessDelegation() is only available in Self + Gasless mode.",
4959
5254
  "INVALID_CONFIG" /* INVALID_CONFIG */
4960
5255
  );
4961
5256
  }
4962
- return { signer: this.signer, submitter: this.submitter };
5257
+ return encodeSetDelegate(this.effectiveSender, this.contracts.trading);
5258
+ }
5259
+ getApproveUsdcEncoded(amount) {
5260
+ if (this.delegated) {
5261
+ throw new OstiumError(
5262
+ "approveUsdc is not available in delegated mode. The trader must approve USDC directly from their own account.",
5263
+ "DELEGATION_FAILED" /* DELEGATION_FAILED */
5264
+ );
5265
+ }
5266
+ const rawAmount = amount === "max" ? maxUint256 : parseUsdc(amount);
5267
+ return encodeUsdcApprove(this.contracts.tradingStorage, rawAmount, this.contracts.usdc);
5268
+ }
5269
+ getSetDelegateEncoded(delegateAddress) {
5270
+ return encodeSetDelegate(delegateAddress, this.contracts.trading);
5271
+ }
5272
+ getRemoveDelegateEncoded() {
5273
+ return encodeRemoveDelegate(this.contracts.trading);
5274
+ }
5275
+ prepareEncoded(encoded) {
5276
+ const signer = this.requireBuildCapability();
5277
+ return signer.prepare(encoded);
5278
+ }
5279
+ buildPreparedTx(encoded) {
5280
+ return this.toBuiltTxRequest(this.prepareEncoded(encoded));
5281
+ }
5282
+ buildDirectEoaTx(encoded) {
5283
+ if (!this.canBuildTransactions() || !this.traderAddress) {
5284
+ throw new OstiumError(
5285
+ "This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
5286
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5287
+ );
5288
+ }
5289
+ return {
5290
+ kind: "eoa",
5291
+ to: encoded.to,
5292
+ data: encoded.data,
5293
+ value: encoded.value,
5294
+ from: this.traderAddress,
5295
+ traderAddress: this.traderAddress
5296
+ };
5297
+ }
5298
+ toBuiltTxRequest(encoded) {
5299
+ if (!this.canBuildTransactions() || !this.traderAddress || !this.effectiveSender) {
5300
+ throw new OstiumError(
5301
+ "This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
5302
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5303
+ );
5304
+ }
5305
+ if (this.gasless) {
5306
+ return {
5307
+ kind: "safe",
5308
+ safeAddress: this.effectiveSender,
5309
+ traderAddress: this.traderAddress,
5310
+ calls: [encoded]
5311
+ };
5312
+ }
5313
+ return {
5314
+ kind: "eoa",
5315
+ to: encoded.to,
5316
+ data: encoded.data,
5317
+ value: encoded.value,
5318
+ from: this.effectiveSender,
5319
+ traderAddress: this.traderAddress
5320
+ };
5321
+ }
5322
+ async submitPrepared(encoded) {
5323
+ const submitter = this.requireSubmitCapability();
5324
+ return submitter.submit(this.prepareEncoded(encoded));
4963
5325
  }
4964
5326
  /** Direct EOA submission — used only in Self + Gasless for approveUsdc and setupGaslessDelegation. */
4965
- async submitDirectFromEoa(encoded) {
5327
+ async submitDirectEoa(encoded) {
5328
+ if (!this.eoaSubmit) {
5329
+ throw new OstiumError(
5330
+ "This client cannot submit direct EOA transactions.",
5331
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5332
+ );
5333
+ }
4966
5334
  try {
4967
5335
  return await this.eoaSubmit(encoded);
4968
5336
  } catch (err) {
@@ -4978,11 +5346,29 @@ var OstiumClient = class _OstiumClient {
4978
5346
  throw new OstiumError(`Transaction failed: ${msg}`, "SUBMISSION_FAILED" /* SUBMISSION_FAILED */, err);
4979
5347
  }
4980
5348
  }
5349
+ requireBuildCapability() {
5350
+ if (!this.signer) {
5351
+ throw new OstiumError(
5352
+ "This client cannot build transactions. Use one of the createSelfAnd*/createDelegatedAnd* factories.",
5353
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5354
+ );
5355
+ }
5356
+ return this.signer;
5357
+ }
5358
+ requireSubmitCapability() {
5359
+ if (!this.submitter) {
5360
+ throw new OstiumError(
5361
+ "This client does not have submission credentials. Use the corresponding get*Tx() method or initialize with a private key.",
5362
+ "INVALID_CONFIG" /* INVALID_CONFIG */
5363
+ );
5364
+ }
5365
+ return this.submitter;
5366
+ }
4981
5367
  resolveSlippage(override) {
4982
5368
  return override ?? this.defaultSlippageBps;
4983
5369
  }
4984
5370
  };
4985
5371
 
4986
- export { CancelOrderType, DEFAULT_BUILDER_API_URL, DEFAULT_SLIPPAGE_PERCENTAGE, DEFAULT_SUBGRAPH_ENDPOINT, DEFAULT_SUBGRAPH_ENDPOINT_TESTNET, MAX_COLLATERAL_USD, MIN_COLLATERAL_USD, OrderType, OstiumClient, OstiumError, OstiumErrorCode, OstiumPriceStream, OstiumSubgraphClient, OstiumSubgraphError, OstiumSubgraphErrorCode, PRECISION_18, PRECISION_6, parseLeverage, parsePrice, parseUsdc };
5372
+ export { CancelOrderType, DEFAULT_BUILDER_API_URL, DEFAULT_SLIPPAGE_PERCENTAGE, DEFAULT_SUBGRAPH_ENDPOINT, DEFAULT_SUBGRAPH_ENDPOINT_TESTNET, MAX_COLLATERAL_USD, MIN_COLLATERAL_USD, OrderType, OstiumClient, OstiumError, OstiumErrorCode, OstiumPositionUpdatesStream, OstiumPriceStream, OstiumSubgraphClient, OstiumSubgraphError, OstiumSubgraphErrorCode, PRECISION_18, PRECISION_6, parseLeverage, parsePrice, parseUsdc };
4987
5373
  //# sourceMappingURL=index.js.map
4988
5374
  //# sourceMappingURL=index.js.map