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()andsubscribe()match React’suseSyncExternalStore. It follows one trader and throwsINVALID_CONFIGwhen none can be resolved, so passuseron a read-only client.state.pairMetaand the exportedPAIR_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,selectPositionsNearLiquidationandmemoSelector. Plain functions of state; React is not a dependency. previewCloseTrade(params)— the exit mirror ofpreviewOpenTrade: 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, withboundnaming 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 existingliquidationPrice()andpnl().- An optional
{ from }ongetSetDelegateTx()andgetRemoveDelegateTx(). 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. ThesetDelegatestep fromgetOnboardingStatus()is the{ from: 'trader' }transaction. maxWithdrawable(params)andmaxAddCollateral(params)— the two collateral-edit limits as pure functions.Pair.minLeverageis 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.rolloverandPosition.rolloverSnapshot— the inputsrolloverFee()needs, so rollover can be advanced locally from one pairs poll instead of one read per position.rpcWsUrlandrpcHttpUrlon the client and onstreamAccountUpdates()— any Arbitrum RPC endpoint now works for the account stream’s contract-log overlay.alchemyApiKeyremains 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 firstrefresh()that brings a position in.
Changed
graphqlis now a declared dependency. It was always required at runtime bygraphql-requestand merely happened to resolve; a locked lockfile or a conflictinggraphqlversion would have failed. Nothing to do unless your project pins a major version other than 16.
Fixed
Position.maxWithdrawablewas 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 recomputesmaxWithdrawableon 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 lastgetOpenPositions()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. maxWithdrawablecan come back smaller, and with itmarginSummary.totalWithdrawable. Anything gating a “max” input on it gets stricter, never looser.- TypeScript only:
PositiongainedrolloverSnapshotandhighestLeverage, andPairgainedrolloverandminLeverage, all required. Reading them is unaffected — the only code that stops compiling is code that builds aPositionorPairliteral, typically a test fixture or a hand-assembledOpenPositionsResponsepassed tostreamPositionUpdates(). 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.isValidiswarnings.length === 0. It is not exhaustive — chain state moves between preview and submission, so keep normal error handling aroundopenTrade().- The preview takes numbers and
isLong;openTrade()takes decimal strings andbuy.
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, plusneedsFundingfor the case no transaction can fix.getVaultBalance()— protocol vault balance in USDC, cached for 60 seconds.liquidationPrice()andpnl()— standalone trading math as pure functions over plain numbers.OstiumSubmissionPendingErrorandOstiumErrorCode.SUBMISSION_PENDING— raised when an operation was submitted but its outcome is not yet known. CarriesuserOpHash.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.txtand 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_TESTNETstill resolve, as deprecated aliases ofDEFAULT_SPONSOR_URL/DEFAULT_SPONSOR_URL_TESTNET. - Gasless submissions are now confirmed against the UserOperation receipt rather than the transaction receipt. Setting
sponsorshipPolicyId, or pointingpimlicoUrlat 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/OrderActionnow include'TopUpCollateral';Fill.type/ fillOrderTypenow 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
OstiumErrorwith codeCONTRACT_ERROR, naming the contract error. - A gasless receipt timeout no longer looks like a failure. It raises
OstiumSubmissionPendingErrorcarryinguserOpHash, so callers reconcile instead of resubmitting a trade that can still land. - Network errors carrying hex payloads are no longer misclassified as contract reverts.
sponsorshipPolicyIdis 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
sponsorshipPolicyIdnow submits through Ostium’s sponsorship endpoint and is confirmed against the UserOperation receipt, so a trade that reverts on-chain raisesOstiumErrorinstead of returning a transaction hash. - A gasless receipt wait can now end in
OstiumSubmissionPendingError. Do not resubmit on it — reconcile againstgetOpenPositions()orgetOrders(). - 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-4337handleOps.traderrestricts matching to that address;action('any'|'open'|'close') scopes it to an open or close order id. Pre-V2PriceRequestedreceipts carry no trader topic, so parsing those requires omittingtrader.streamAccountUpdates()snapshots now includecloseExecutionsper trader, surfacing close economics (px,percentProfit,usdcSentToTrader,percentageClosed,isFullClose). Each partial close of a trade appears as its own entry.OpenOrdernow includesntl,collateralUsedandleverage.
Changed
- Pair-list and live-price reads share a 5-second in-flight cache, so concurrent
getPairs(),getAllPrices(),getOpenPositions(),getSimSlippage()andgetSimOrderbook()calls no longer duplicate upstream requests. streamAccountUpdates()backs off after snapshot failures and respects upstreamRetry-After. Live-price seeding errors now reachonErrorinstead of being swallowed.
Fixed
- Stale pending market orders that no longer exist on-chain are no longer returned by
getOrders(),getBuilderOrders()orstreamAccountUpdates(). Cancelling one of them reverted. - Partial closes in
streamAccountUpdates()are no longer treated as full closes. - Streamed positions keep
idxat-1until 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 optionalsafeAddress. Use the smart-account address from a previousclient.getSmartAccountAddress()call to skip the counterfactual-address derivationeth_calland 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.idxfromgetOpenOrders()now carries the on-chain limit slot index, not the subgraph’s global order id. Pass this value directly tocancelOrder({ type: CancelOrderType.Limit })andmodifyOrder().Fill.oidandOrder.oidare 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 currentMarketOpenOrderInitiated,MarketCloseOrderInitiated, andMarketCloseOrderInitiatedV2events, while still supporting olderPriceRequestedreceipts.- 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.iotohttps://builder.prod.bedrock.ostium.io(subgraph, Pimlico sponsor, prices/OHLC, and WebSocket stream).builder.ostium.iois 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, whoseMarketCloseExecutedV2event 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/MarketCloseExecutedV2logs over HTTP from the last swept block, so events dropped during a WebSocket flap surface within roughly one poll interval instead of being lost. executedoverlay 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
pollIntervalMsraised from700to3000. 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). PasspollIntervalMsto restore a faster cadence. - The per-poll Alchemy
eth_blockNumbercall 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 acceptsuseras 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.usersgetter returning the subscribed trader addresses.- Optional
userargument toOstiumAccountUpdatesStream.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
setspagination support togetCandles(). - Added
getOrders()filters for global orders, builder address, status, pair ids, and execution time (start/endas Unix seconds UTC, inclusive bounds onexecutedAt). - Added
getBuilderOrders(builder, params?)— fetches builder-tagged open orders plus sibling close/TP/SL orders on the same positions.limitcaps phase-1 results only; phase-2 siblings are appended without a cap. - Added
builderto returnedFillandOrderobjects. - Added
ntl(USD notional) to returnedFillandOrderobjects. - Added
traderto returnedFill,Order,Position, andOpenOrderobjects. - Added
timestamp(execution time, Unix seconds UTC — subgraphexecutedAt) to returnedFillandOrderobjects. - Added
MIN_OPEN_SIZE_USDfor the fixed $5 minimum open size. - Added per-trade optional
builder.address/builder.feeBpsoverrides onopenTrade(); omitted fields fall back to client config. - Added
openFeeandcloseFee(bps) toPair—openFeeistakerFeeP / 10_000plus the configured builder fee;closeFeeis always0as 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(), receiptorderIdextraction viaextractOrderIdFromReceipt(), andattachOrderId()reconciliation for lower-latency confirmations. - Added
alchemyApiKeyas a client option for account confirmation streams. - Added account update snapshots with SDK-formatted
Order,OpenOrder, andPairPositionvalues. - Added
schedule(market hours —timezone,openingHours,alwaysOpen) toPairreturned bygetPairs(), and toPriceData/PriceTickfrom 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.