Skip to main content

Changelog

All notable changes to @ostium/builder-sdk will be documented in this file. The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[0.9.0] - 2026-09-23

Existing SDK calls and the values they read keep working untouched. Three things do change — see Upgrading below.

Added

  • createStore(params?) — one subscribable object holding pairs, live prices and a trader’s positions, kept current: prices over the WebSocket, pairs every 60s, positions every 15s. PnL, liquidation price and accrued rollover are recomputed locally on every tick. getState() and subscribe() match React’s useSyncExternalStore. It follows one trader and throws INVALID_CONFIG when none can be resolved, so pass user on a read-only client.
  • state.pairMeta and the exported PAIR_SNAPSHOT — pair names, category, leverage caps and minimum notional for every listed pair, available synchronously so a market list renders before the first network call. No prices or rollover data: those are always fetched live.
  • Selectors over store state — selectPosition, selectPositionsForPair, selectPrice, selectPair, selectPairMeta, selectMarkets, selectPositionsNearLiquidation and memoSelector. Plain functions of state; React is not a dependency.
  • previewCloseTrade(params) — the exit mirror of previewOpenTrade: exit price after spread, rollover settled, net PnL and proceeds, for a full or partial close.
  • getMaxCollateral(params) — the largest collateral a trade can use, with bound naming the limit that stopped it (open interest, group collateral, per-trade cap, wallet balance or allowance).
  • rolloverFee() — accrued rollover as a pure function, alongside the existing liquidationPrice() and pnl().
  • An optional { from } on getSetDelegateTx() and getRemoveDelegateTx(). The default is unchanged — with no options the transaction still follows the client’s mode. { from: 'trader' } builds it from the trader’s own account, which is what a first delegate registration needs, since the wrapped form asks a delegate that does not exist yet to authorise itself. { from: 'delegate' } states the wrapped form explicitly. The setDelegate step from getOnboardingStatus() is the { from: 'trader' } transaction.
  • maxWithdrawable(params) and maxAddCollateral(params) — the two collateral-edit limits as pure functions. Pair.minLeverage is returned too, since the add-side limit is measured against it.
  • Position.highestLeverage — the highest leverage a position has run at, which is what PnL is calculated against once collateral has been added or removed.
  • Pair.rollover and Position.rolloverSnapshot — the inputs rolloverFee() needs, so rollover can be advanced locally from one pairs poll instead of one read per position.
  • rpcWsUrl and rpcHttpUrl on the client and on streamAccountUpdates() — any Arbitrum RPC endpoint now works for the account stream’s contract-log overlay. alchemyApiKey remains fully supported.
  • OstiumPositionUpdatesStream.refresh(next) — swap the watched position set without tearing the stream down. A stream created for a trader holding nothing opens its price socket on the first refresh() that brings a position in.

Changed

  • graphql is now a declared dependency. It was always required at runtime by graphql-request and merely happened to resolve; a locked lockfile or a conflicting graphql version would have failed. Nothing to do unless your project pins a major version other than 16.

Fixed

  • Position.maxWithdrawable was only the leverage cap, and overstated what a losing position can remove — 900 against a real limit of 675 on a 1,000 USDC position at 10x with a 300 loss. It now takes the smallest of the three limits the contract checks: the leverage cap, liquidation safety, and profit protection.
  • The live price stream now reconnects after a dropped connection, with backoff, and restores its subscriptions. Previously a drop left a stream that stayed silent.
  • streamPositionUpdates() no longer recomputes maxWithdrawable on a tick — it lacks the pair data to weigh all three limits, and was reporting the leverage cap against a price-adjusted leverage. The value from the last getOpenPositions() stands.
  • streamPositionUpdates().close() detaches its handlers from a price stream you supplied, instead of leaving them attached for the life of your socket.

Upgrading

Nothing to change, but three things behave differently:
  • The price stream reconnects on its own now. If you added your own reconnect on onClose() — the only option before — remove it, or you will end up with two sockets and duplicate ticks.
  • maxWithdrawable can come back smaller, and with it marginSummary.totalWithdrawable. Anything gating a “max” input on it gets stricter, never looser.
  • TypeScript only: Position gained rolloverSnapshot and highestLeverage, and Pair gained rollover and minLeverage, all required. Reading them is unaffected — the only code that stops compiling is code that builds a Position or Pair literal, typically a test fixture or a hand-assembled OpenPositionsResponse passed to streamPositionUpdates(). Add the new fields.

[0.8.0] - 2026-09-16

Added

  • previewOpenTrade(params) — everything an order ticket renders before a trade is signed: execution price after dynamic spread, the fee breakdown, collateral backing the position, position size, liquidation price, effective max leverage and the exposure-limit check. Derived from @ostium/formulae, the same math the contracts run.
    • warnings[] covers collateral and leverage bounds, minimum position size, fees exceeding collateral, exposure limits, and market or day-trading closure. isValid is warnings.length === 0. It is not exhaustive — chain state moves between preview and submission, so keep normal error handling around openTrade().
    • The preview takes numbers and isLong; openTrade() takes decimal strings and buy.
  • getOnboardingStatus(params?) — what still stands between a trader and their first trade (USDC approval, delegate registration, gasless setup), each step carrying the transaction that clears it, plus needsFunding for the case no transaction can fix.
  • getVaultBalance() — protocol vault balance in USDC, cached for 60 seconds.
  • liquidationPrice() and pnl() — standalone trading math as pure functions over plain numbers.
  • OstiumSubmissionPendingError and OstiumErrorCode.SUBMISSION_PENDING — raised when an operation was submitted but its outcome is not yet known. Carries userOpHash.
  • examples/ — three runnable programs, published with the package: an order ticket, a live positions view, and a complete trade loop. The first two need no credentials.
  • llms.txt and a Claude skill, both published with the package: flat API references written for AI coding agents.

Changed

  • The default sponsorship endpoint is now /v1/sponsor. DEFAULT_PIMLICO_URL / DEFAULT_PIMLICO_URL_TESTNET still resolve, as deprecated aliases of DEFAULT_SPONSOR_URL / DEFAULT_SPONSOR_URL_TESTNET.
  • Gasless submissions are now confirmed against the UserOperation receipt rather than the transaction receipt. Setting sponsorshipPolicyId, or pointing pimlicoUrl at your own bundler, keeps the 0.7.x submission path.
  • A gasless receipt wait now times out after 120 seconds instead of waiting indefinitely.
  • Fill.action / OrderAction now include 'TopUpCollateral'; Fill.type / fill OrderType now include 'TOP_UP_COLLATERAL'.
  • Pair queries now request the fee and group fields the opening-fee and exposure-limit calculations need.

Fixed

  • Gasless submissions no longer report a reverted trade as a success. A UserOperation whose inner call reverts still lands on-chain as a successful transaction, so a transaction hash was returned for a trade that never opened. Such a submission now raises OstiumError with code CONTRACT_ERROR, naming the contract error.
  • A gasless receipt timeout no longer looks like a failure. It raises OstiumSubmissionPendingError carrying userOpHash, so callers reconcile instead of resubmitting a trade that can still land.
  • Network errors carrying hex payloads are no longer misclassified as contract reverts.
  • sponsorshipPolicyId is no longer silently dropped.

Upgrading

No changes are required — every 0.7.1 export still resolves and all five constructors accept their existing arguments. Three things are worth knowing:
  • A gasless config with no sponsorshipPolicyId now submits through Ostium’s sponsorship endpoint and is confirmed against the UserOperation receipt, so a trade that reverts on-chain raises OstiumError instead of returning a transaction hash.
  • A gasless receipt wait can now end in OstiumSubmissionPendingError. Do not resubmit on it — reconcile against getOpenPositions() or getOrders().
  • If you override subgraphUrl, the endpoint must serve the pair fields added in this release; pair reads request them unconditionally.

[0.7.1] - 2026-08-12

Added

  • extractOrderIdFromReceipt(receipt, trader?, action?) accepts two optional filters for receipts that bundle several operations, such as an ERC-4337 handleOps. trader restricts matching to that address; action ('any' | 'open' | 'close') scopes it to an open or close order id. Pre-V2 PriceRequested receipts carry no trader topic, so parsing those requires omitting trader.
  • streamAccountUpdates() snapshots now include closeExecutions per trader, surfacing close economics (px, percentProfit, usdcSentToTrader, percentageClosed, isFullClose). Each partial close of a trade appears as its own entry.
  • OpenOrder now includes ntl, collateralUsed and leverage.

Changed

  • Pair-list and live-price reads share a 5-second in-flight cache, so concurrent getPairs(), getAllPrices(), getOpenPositions(), getSimSlippage() and getSimOrderbook() calls no longer duplicate upstream requests.
  • streamAccountUpdates() backs off after snapshot failures and respects upstream Retry-After. Live-price seeding errors now reach onError instead of being swallowed.

Fixed

  • Stale pending market orders that no longer exist on-chain are no longer returned by getOrders(), getBuilderOrders() or streamAccountUpdates(). Cancelling one of them reverted.
  • Partial closes in streamAccountUpdates() are no longer treated as full closes.
  • Streamed positions keep idx at -1 until the on-chain trade slot is known, instead of showing an order id that could not be used for close, TP/SL or collateral calls.

[0.7.0] - 2026-07-29

Changed

  • Gasless key-mode factories (createSelfAndGasless, createDelegatedAndGasless) accept an optional safeAddress. Use the smart-account address from a previous client.getSmartAccountAddress() call to skip the counterfactual-address derivation eth_call and make gasless construction network-free. The address is deterministic per key and chain.
  • Client construction no longer blocks on a subgraph pair-list fetch. Pair metadata loads lazily on first use, so build-only consumers and price streams can start without waiting for a full pair cache.
  • streamPrices(pairIds) can be called immediately after client creation. If the pair list is still loading, the stream connects and applies the pair filter once pair metadata resolves.

Fixed

  • OpenOrder.idx from getOpenOrders() now carries the on-chain limit slot index, not the subgraph’s global order id. Pass this value directly to cancelOrder({ type: CancelOrderType.Limit }) and modifyOrder().
  • Fill.oid and Order.oid are now documented and normalized as the on-chain keeper order id, formatted as a base-10 numeric string for both fast-overlay and subgraph-indexed entries.
  • extractOrderIdFromReceipt() now decodes order ids from the current MarketOpenOrderInitiated, MarketCloseOrderInitiated, and MarketCloseOrderInitiatedV2 events, while still supporting older PriceRequested receipts.
  • Fast-overlay trades in streamAccountUpdates() seed their rollover baseline from the pair’s rollover accumulator at the open block, keeping streamed PnL and liquidation estimates aligned with indexed positions.
  • OstiumPriceStream.subscribe() and .unsubscribe() no longer drop filter messages while the socket is still connecting; pending filter changes are sent on open.
  • The account-updates WebSocket retries reconnection indefinitely with a 2-second delay instead of stopping after viem’s default retry limit.

[0.6.0] - 2026-07-23

Changed

  • BREAKING: Migrated the default builder API endpoints from https://builder.ostium.io to https://builder.prod.bedrock.ostium.io (subgraph, Pimlico sponsor, prices/OHLC, and WebSocket stream). builder.ostium.io is being deprecated; clients relying on the old default must upgrade. Consumers passing explicit URLs should update them accordingly.

Fixed

  • Ghost positions in streamAccountUpdates(): a trade closed before its open was ever observed indexed, whose MarketCloseExecutedV2 event was missed (e.g. during a WebSocket reconnect), previously lingered in the emitted snapshot forever. Three complementary fixes:
    • The account snapshot query now also fetches recently executed close/liquidation orders (15-minute lookback) and tombstones matching overlay entries, so the subgraph poll can clear a ghost even with a dead event socket.
    • New missed-event backfill: each poll sweeps MarketOpenExecuted / MarketCloseExecutedV2 logs over HTTP from the last swept block, so events dropped during a WebSocket flap surface within roughly one poll interval instead of being lost.
    • executed overlay entries are no longer exempt from the overlay TTL; an executed trade the subgraph never confirms within the TTL now expires instead of ghosting indefinitely.

[0.5.0] - 2026-07-13

Changed

  • streamAccountUpdates() now fetches the account snapshot for all subscribed traders in one batched subgraph query per poll instead of one query per trader. If the shared 1000-row window saturates (any entity set returns a full page), the poll transparently falls back to per-user queries so a busy account still cannot starve the others.
  • Default pollIntervalMs raised from 700 to 3000. Sub-second open/close confirmations come from the Alchemy event watchers (which also trigger an immediate poll), so the timer poll only paces reconciliation of changes with no watcher (limit fills, partial closes, TP/SL edits, liquidations). Pass pollIntervalMs to restore a faster cadence.
  • The per-poll Alchemy eth_blockNumber call is now served from a 30-second cache. Combined with the changes above, a 20-user stream drops from ~1,700 subgraph requests/minute to ~20, and steady-state Alchemy HTTP calls drop ~15×.

[0.4.1] - 2026-06-14

Added

  • streamAccountUpdates() now accepts user as an address array, allowing one or more trader addresses on a single stream (one WebSocket, one poll loop, one price feed). Emitted snapshots are keyed by normalized trader address with { positions, orders, limits } per trader.
  • OstiumAccountUpdatesStream.users getter returning the subscribed trader addresses.
  • Optional user argument to OstiumAccountUpdatesStream.addOptimisticOpen(params, submission?, user?) to attribute an optimistic open to a specific subscribed address. Required when streaming multiple addresses; defaults to the sole subscribed address otherwise.

[0.4.0] - 2026-06-11

Added

  • Added this changelog.
  • Added sets pagination support to getCandles().
  • Added getOrders() filters for global orders, builder address, status, pair ids, and execution time (start / end as Unix seconds UTC, inclusive bounds on executedAt).
  • Added getBuilderOrders(builder, params?) — fetches builder-tagged open orders plus sibling close/TP/SL orders on the same positions. limit caps phase-1 results only; phase-2 siblings are appended without a cap.
  • Added builder to returned Fill and Order objects.
  • Added ntl (USD notional) to returned Fill and Order objects.
  • Added trader to returned Fill, Order, Position, and OpenOrder objects.
  • Added timestamp (execution time, Unix seconds UTC — subgraph executedAt) to returned Fill and Order objects.
  • Added MIN_OPEN_SIZE_USD for the fixed $5 minimum open size.
  • Added per-trade optional builder.address / builder.feeBps overrides on openTrade(); omitted fields fall back to client config.
  • Added openFee and closeFee (bps) to Pair — openFee is takerFeeP / 10_000 plus the configured builder fee; closeFee is always 0 as there’s no closing fees on Ostium currently.
  • Added streamAccountUpdates() for low-latency account confirmations using subgraph polling, Alchemy contract-log overlays, and live price repricing for open-trade PnL.
  • Added optimistic market-open overlays to streamAccountUpdates(), receipt orderId extraction via extractOrderIdFromReceipt(), and attachOrderId() reconciliation for lower-latency confirmations.
  • Added alchemyApiKey as a client option for account confirmation streams.
  • Added account update snapshots with SDK-formatted Order, OpenOrder, and PairPosition values.
  • Added schedule (market hours — timezone, openingHours, alwaysOpen) to Pair returned by getPairs(), and to PriceData / PriceTick from the live price feed.
  • Added background SDK usage attribution: submissions that target the Trading contract report their transaction hash to the builder API (POST /v1/trade) as a fire-and-forget request that never blocks or affects trading calls.

Changed

  • Updated the default mainnet subgraph URL to https://builder.ostium.io/v1/subgraph/gn.

Fixed

  • Removed the SDK-side openTrade() maximum leverage cap so contract-side validation is authoritative.
  • Removed the internal minimum-open-size config override path.

[0.3.1]

  • Current published package version when this changelog was introduced.