Repository navigation
v0.3.0
Package version: 0.3.0. Git tag: v0.3.0. Released 2026-10-31.
What a trading bot gains. Public trade, fill, ledger transfer and
trigger-event subscriptions now deliver every event; before, all but one
event of each batch was lost. SpotConfig and the catalog expose each pair's
order limits as typed values, including the new cap on one market order's
notional, so a bot can size orders before it sends them. A clock-skew
rejection from the API gateway is the retryable Error::TimestampSkew and no
longer ends a private order stream for good; other API-key rejections keep
their code. A duplicate client order ID names the order that already holds
it, replacement orders report the quantity they inherited, and slippage caps
and trigger inputs are checked before sending the way the API checks them.
This release works on devnet today, and on testnet after testnet's next
upgrade, which brings the new private order stream.
Breaking changes
- Batched subscriptions yield every event, and the single-event decoders
(market_trade_from_bytes,user_trade_from_bytes,
ledger_transfer_from_bytes,trigger_event_from_bytes) are replaced by
their..._batch_from_bytesversions, which return one result per event. spot_config_from_protoreturnsResult.- Connect
unimplementedand a bare HTTP 501 areError::NotImplemented
instead ofError::RouteNotFoundorError::Api. max_slippage_bpsis at most 9999 for a market order that sells, every
trailing stop andtriggers.modify.- The
marktrigger price source is rejected locally. - A buy trailing stop's
limit_pricemust be at least its
activation_price. - Public structs gain fields, so code that builds them with a struct literal
must set them.
Breaking
- Batched realtime publications deliver every event. Public trades,
user fills, ledger transfers and trigger events arrive as batches; before,
each batch was decoded as one event and the rest were lost. The
subscriptions now yield every event in order. The single-event decoders
market_trade_from_bytes,user_trade_from_bytes,
ledger_transfer_from_bytesandtrigger_event_from_bytesare replaced by
market_trade_batch_from_bytes,user_trade_batch_from_bytes,
ledger_transfer_batch_from_bytesandtrigger_event_batch_from_bytes,
which return one result per event. spot_config_from_protoreturnsResult: a malformed order limit is an
error instead of an untyped value.- Connect
unimplementedand a bare HTTP 501 areError::NotImplemented
instead ofError::RouteNotFoundorError::Api. A bare HTTP 404 is still
Error::RouteNotFound;Error::is_not_implemented()is true for both. - Slippage caps match the API. A market order that sells, every trailing
stop (standalone or attached, buy or sell) andtriggers.modifytake at
most 9999 bps; they accepted 10000, which the API rejects. A buy market
order, a buy stop-loss or take-profit market child and a TWAP slice still
take 10000. - The
marktrigger price source is rejected locally (standalone triggers
and attached legs): spot markets have no mark price. - A buy trailing stop's
limit_pricemust be at least its
activation_price. - Public structs gain fields (see Added), so code that builds them with a
struct literal must set them.
Fixed
- API-gateway rejections keep their code. The gateway rejects API-key
requests with a plain JSON body such as401 {"code":"TIMESTAMP_SKEW"}.
It was reported as a genericError::Authwith no code, so a clock-skew
rejection was not retryable and ended a private order stream for good. Now
TIMESTAMP_SKEWis the retryableError::TimestampSkew, and any other
code (SIGNATURE_INVALID,KEY_DISABLED,IP_NOT_ALLOWED, ...) is the
non-retryableError::ApiKeyRejectedwith its code and HTTP status. This
covers Connect calls and the realtime token requests. - A market overview subscription created with
symbolskeeps only those
markets in buffered and live updates, not only in its first snapshot.
Added
- Typed spot-config order limits:
SpotConfig.pair_order_limits
(PairOrderLimitswithmax_limit_qty_base,max_market_qty_base,
max_order_notional_quote,max_market_notional_quoteand
max_open_orders_per_pair) andSpotConfig.max_open_orders_total. The
decimal limits areNonewhen the pair has no maximum. The catalog
exposes the same values:catalogs.pair_order_limits_for_symbol(),
pair_order_limits_for_symbol_id()andmax_open_orders_total(). Order.inherited_cum_qty: the filled quantity a replacement inherited
from its predecessors.action_takenonBatchReplaceAdmissionItemandBatchReplaceStatusItem:
replaced, oramendedfor a cancel-only outcome with no successor.- A duplicate client order ID rejection names the order that already holds
the ID, when the server knows it:Error::existing_order_id()for single
creates andBatchCreateResultItem.existing_order_idfor batch creates. LedgerTransfer.transfer_id, shared by both legs of a transfer.triggers.modifyandtriggers.resume_with/resume_by_idno longer
need a symbol; without one they target the trigger's own market.TriggerTrailingDetails.trigger_price: a trailing stop's current
threshold.VIPStatus.settled_volume_qualityandaverage_aop_quality
(VIPMetricQuality:statusandincomplete_hours). When the volume's
status isprice_unavailable,settled_volume_30d_usdis a lower bound.realtime::Client::subscribe_proto_batchfor batched channels. An event
that fails to decode is reported through the subscription's error
callback, and the rest of its batch is still delivered.auth_codes::TIMESTAMP_SKEW;Error::auth_error_code()also returns the
gateway code ofError::TimestampSkewandError::ApiKeyRejected.
Internal
sdk-capabilities.jsonno longer carriespublicDescriptorSha256orgeneratedAt, so a proto publication that only changessrc/gen/andmanifest.jsonno longer makes it stale;manifest.jsonstays the single source of the descriptor hash.