# Changelog

## 0.4.3 - 2026-11-05

- `buildDepositFundsIxs` funds a buy written by a vault (`VAULT_WRITER | TAKER_BUY`): only a
  vault holder is refused, as on chain. 0.4.2 refused every vault flag, so a wallet could not
  fund a vault-written buy and it settled as not exercised.
- `buildSettlePositionIx` refuses a SOL unwrap only when the unwrapping side is a vault
  (`unwrapUserSol` with a vault writer, `unwrapHolderSol` with a vault holder), as on chain.
  A funded buy written by a vault can now unwrap to its wallet holder.
- Breaking: `RfqClosedReason` drops `ladder_timeout`, which the server never sends; an
  `RfqClosed` carrying it is now rejected like any unknown reason.

## 0.4.2 - 2026-11-04

Requires the vault-writer program upgrade.

- Vaults write what wallets buy. `OpenPositionForVaultWriter` and `OpenSpreadForVaultWriter`
  (formerly `…ForVaultAsTaker`) take `holderKind`: 0 when a maker holds (premium from its
  custody), 1 when a wallet holds (the wallet signs slot 1, repeats in slot 7 and pays from its
  quote account; the vault delegate signs the quote). `openPositionForWalletHolder` and
  `openSpreadForWalletHolder` build it; `verifyVaultOpen` takes intent `kind: "buy"` and
  `verifyAndSignTakerFill` routes a buy quote from a vault there.
- Breaking renames by position role, opcodes unchanged: `OpenPositionForVaultHolder` /
  `OpenSpreadForVaultHolder` (formerly `…AsMaker`), `FundPositionForVaultHolder` (formerly
  `VaultFundMakerPosition`), event `VaultHolderPositionFunded`. Vault writer slots are
  `participant`, `counterparty`, `premiumSource`; vault holder slots `writer`,
  `writerUnderlyingAta`, `writerQuoteAta`. `OpenPosition`/`OpenSpread` slots are `taker`,
  `writerUnderlyingAta`, `takerQuoteAta`; `DepositFundsToPosition` `holderMakerPda`;
  `MakerDeposit`/`MakerWithdraw` `makerOwnerAta`.
- Delegate permissions: `OPEN_AS_WRITER`, `SIGN_HOLDER_QUOTES`, `FUND_HOLDER` (formerly
  `OPEN_TAKER`, `SIGN_MAKER_QUOTES`, `FUND_MAKER`) and new `SIGN_WRITER_QUOTES` (`1 << 6`);
  `VAULT_DELEGATE_PERMISSIONS_KNOWN_MASK` is `0x7f`.
- Events version 2: `PositionOpened` for every open (writer, holder, `flags` = `Position.flags`,
  terms, gross and net premium, collateral) and `VaultCycleOpened` per vault side replace
  `OpenPosition`, `OpenSpread`, `OpenTakerBuy`, `OpenTakerBuySpread`, `VaultPositionOpened` and
  `VaultSpreadOpened`. Kinds renumbered: `SettleSpread` 79, `VaultSpreadClosed` 80,
  `TakerBuyFunded` 81 (field `holder`), `TakerBuyNotExercised` 82. Version 1 events no longer
  decode.
- Error `NotPositionHolder` (1111) when a deposit's signer is not the position holder.

## 0.4.1 - 2026-11-03

- `RfqClosed.reason` is a closed union, `RfqClosedReason`, listed in `RFQ_CLOSED_REASONS`; an
  unknown reason is rejected as an unreadable frame. New reason `no_eligible_makers`: no maker
  could receive the RFQ, so the server closes it at once instead of at its TTL.

## 0.4.0 - 2026-11-03

- Breaking rename, bytes unchanged: position roles are `writer` and `holder`. `Position`
  `takerOwner`/`makerOwner` are `writer`/`holder` (`POSITION_OFFSET_WRITER` 8,
  `POSITION_OFFSET_HOLDER` 40). Instruction inputs: `DepositFundsToPosition` `holder`,
  `holderFundingAta`; `SettlePosition` `writerDestAta`, `holderDestAta`;
  `LiquidatePosition` `writerDestAta`; `buildDepositFundsToPositionIx` takes `holder`.
  Events: `SettlePosition.holder`; `SettleSpread` `writer`, `holder`, `holderPayoff`,
  `writerReturn`; `TakerBuyNotExercised` `holder`, `writer` (formerly `user`, `makerOwner`).
  `fetchPositionsByHolder`, `fetchPositionsByWriter` and their `Decoded`/`AndStatus` forms
  replace the maker/taker names; `spreadSettlement` returns `{ holderPayoff, writerReturn }`.
  WS cash settlement fields are `holder_payoff` and `writer_return`.
- Breaking rename, wire unchanged: instructions `DepositPremium`/`WithdrawPremium` are
  `MakerDeposit`/`MakerWithdraw` (opcodes 4 and 5), events likewise (kinds 4 and 5), and the
  account `makerPremiumAta` is `makerCustodyAta`. Builders follow: `buildMakerDepositIx`,
  `buildMakerWithdrawIx`, `getMakerDepositInstruction`, `MAKER_DEPOSIT_DISCRIMINATOR`.

Requires the taker-buy program: `OpenPosition` data is now 75 bytes and `OpenSpread` 83
bytes, and the old program rejects both.

### Added

- `PositionState` `{ state: "terminal_pending", slot, outcome }`: the chain has settled or
  liquidated the position, but the server has not sealed that slot. It replaces the brief
  `unrenderable` entry such a position used to show; caps and balances follow the sealed state.
- `covered_call`, `cash_secured_put`, `call_spread`, `put_spread` and `cash_put` accept
  `side: "buy"` in order ids, WS terms and `verifyAndSignTakerFill`. Vault fills stay
  sell-only. `buildCashSettleIx` settles a bought spread; event `OpenTakerBuySpread`.
- `flows.buildDepositFundsIxs` funds an Open position for its `holder`: the buyer's
  deposit leaves the Maker PDA slot unread and wraps SOL for a native settlement mint.
- `buildSettlePositionIx` settles an unfunded ITM buy, and `unwrapHolderSol` with
  `holderDestAta` unwraps a funded buyer's payout.
- WS `PositionSettlement` variant `not_exercised { settlement_price }` for an unfunded ITM buy.
- `OpenTakerBuy` (83), `TakerBuyFunded` (84) and `TakerBuyNotExercised` (85) events, `TakerBuyNotLiquidatable` (1110),
  `isTakerBuyPosition`, `POSITION_FLAG_TAKER_BUY`.

### Changed

- `buildOpenPositionIx` requires `side`. On a buy, `userUnderlyingAta` is the Maker PDA
  custody, and `assertSupportedPosition` admits flag bit 2.
- `buildLiquidatePositionIx` refuses a taker-buy position.

## 0.3.2

- An unknown or undecodable server frame now triggers the same state reconciliation as a
  reconnect when `autoReconcile` is on, at most once every 5 seconds. A skipped frame may
  have carried a fill or a position change.

## 0.3.1

Requires a server with paged lists. The WS protocol version stays `1.0.0`, so an
older SDK passes Hello and then fails on the changed list requests.

### Changed

- Every WS list takes a required `limit` (at most 200) and a tagged `cursor`, and returns
  a required nullable `next_cursor`.
- List methods now collect all pages through `fetchAllPages`, so existing calls keep
  returning full lists.
- REST lists take an optional `limit` (default 50, at most 200, 100 for vault requests)
  and an optional opaque `cursor`.

### Added

- Page methods for loading one page at a time, such as `getPositionsPageAsync`.
- `getPositionSummaryAsync`: premium totals per quote mint, including spreads.
- `fetchAllPages`, a shared helper that follows `next_cursor`.

## 0.3.0

Breaking release. Targets WS protocol `1.0.0` and the program build with spreads. Spreads
and cash puts are part of the base protocol. The wire is closed: every field is present,
absent values are `null`, and unknown message types, codes and statuses are rejected.

### Breaking: WS client

- `ActaWsClient` options require `onDisconnect`, a non-empty `products` list,
  `clientName` and `clientVersion`. Feature negotiation and `FEATURE_*` are removed.
- `subscribe(channels, mints)`, `addMints` and `removeMints` take a required `MintFilter`.
  Use `ALL_MINTS` for every mint.
- `getPositions`, `getMakerPositions`, `getMarketsForMaker`, `getMyTrades` and
  `getExpiries` take the full typed query: `QueryFilter<T>`, `TimeBound`, and a required
  `limit` with `PageCursor<I>` where the read is paged. `ALL_POSITIONS` is the unfiltered
  position query. `PositionStatus` drops `"none"`.
- `getMyQuotes(scope)` takes `{ type: "live" }` or `{ type: "history", limit, cursor }`.
  `getTokenCaps(includeMarkets)` requires the flag.
- `buildSignedQuoteFromRfq`, `submitQuoteSigned` and `submitReplaceQuoteSigned` take
  `terms` (and `side`) instead of `strike`.
- `verifyAndSignTakerFill` requires `keeper` and `makerVault` (`null` for an ordinary
  maker). Spread RFQs also need `underlying_decimals` and `quote_decimals`.
- The `tradeExecuted` event no longer passes a stats delta.
- A frame that fails wire validation is dropped and reported once per message type per
  connection as an `invalid_frame` error.
- `WsChannel` is the closed list of server channels instead of `string`.

### Breaking: wire types

- RFQs, quotes, positions and trades carry `terms: OptionTerms` and `side` instead of
  `position_type` and `strike`. `RfqOrderOption` is `OptionTerms`. Products are
  `covered_call`, `cash_secured_put`, `call_spread`, `put_spread` and `cash_put`.
- Positions carry `state` (`open`, `funded`, `liquidated` or `settled` with
  `settlement`), both decimals, `total_premium` (net quote atoms) and `revision`. The
  `status` and `reconciliation_status` fields are removed. `PositionUpdated` drops
  `caps_snapshot`.
- `TokenInfo.role` is `quote` or `underlying` with `size_rule`, `strike_tick` and
  `quote_mints`. Markets carry a required `products` list.
- `QuoteCheck.verdict` and `EarnAssetSummary.apr` are tagged unions.
  `QuoteRefreshRequested.reason` and `QuoteExpired.reason` are `QuoteExpiryReason`.
  `RfqSkipped` drops `reason`; `cap_detail` names the failing cap.
- `AuthError.message` and `QuoteRejected.message` are required. Nullable fields such as
  `maker_pda`, `RfqCreated.request_id` and `replaced_order_id` are `T | null`, not optional.
- `WsErrorCode` is closed. Added `invalid_limit`, `invalid_timeout` and the `spread_*` codes. Removed
  `invalid_cursor`, `invalid_position_status` and `invalid_query_scope`.
- `StatsDelta`, `TradeExecuted.stats_delta`, `MakerMarketInfo.stats` and `MarketStats`
  are removed. `include_stats` is removed from queries.
- Wire addresses are validated on decode and typed as `Address`.

### Breaking: HTTP

- `Venue` requires `protocol_fee_bps_premium` and `protocol_fee_bps_volume`. Its count
  limits are `CountLimit` (`unlimited` or `limited { value }`).
- `Vault.capacity` (`unlimited` or `limited { cap_limit, remaining }`) replaces the
  top-level `cap_limit`. `Vault.metadata` is `VaultMetadata | null`.

### Breaking: chain

- `OpenPositionForVaultAsMaker`, `OpenPositionForVaultAsTaker` and
  `OpenPositionBetweenVaults` take a `keeper` signer as the position rent payer.
- `PositionType` adds `CallSpread` and `PutSpread`. `EventKind` adds `OpenSpread`,
  `SettleSpread`, `VaultSpreadOpened` and `VaultSpreadClosed`.

### Added

- Cash-settled spreads on chain: `buildOpenSpreadIx`, `buildOpenSpreadFlowIxs`,
  `buildCashSettleIx`, the 190-byte spread order layout (`encodeSpreadOrderPreimage`,
  `computeSpreadOrderId`, `signSpreadOrder`) and checked math (`spreadCollateral`,
  `spreadPayoff`, `spreadSettlement`, `spreadPremium`, `priceSpread`).
  `buildSettlePositionIx` settles spreads through the cash path.
- Vault spreads through `manager.spreads` (`VaultManagerSpreadPlanner`).
- Caps responses include `quote_liabilities` (`QuoteLiabilityCapInfo` with a tagged
  `policy`).
- `CapErrorData` adds `quote_liability_cap_exceeded` and
  `maker_quote_liability_cap_exceeded` for cash-settled spread liability.
- The client keeps a position cache keyed by PDA and emits `positionUpdated` only for a
  newer `revision`. `comparePositionRevision` is exported.
- `VaultIntentError` is exported from the WS entry point as well.
- `executionTerms` maps `cash_put(K)` to `put_spread(0, K)`. `WS_PROTOCOL_VERSION` is
  exported.

## 0.2.5

- `WsErrorCode` gains `insufficient_collateral` (wallet taker cannot fund the RFQ's collateral; rejected before broadcast) and `time_overflow`.
- Breaking: `VaultRulesView` requires `live_share_supply`; final withdrawal uses it, falling back to `total_shares` only when null.

## 0.2.4

- `verifyVaultSwap` accepts a served RecoveryStage half (recovery request, stage step).
- `http`: `Vault.second_leg_generation` (decimal string) increments when governance rolls the second mint; the server already sends it.
- Breaking, wire: `WelcomeMessage` carries `min_quote_validity_seconds` and `settlement_buffer_seconds`, the venue's quote window also served by `GET /api/v1/venue`.
- Breaking: `VaultRulesView` and `VaultCapitalView` take `staging`. While a staging slot is open, `processEquityBlock`, `processIdleWithdrawalBlock`, `processFinalWithdrawalBlock` and `finalizeCycleBlock` return the new `"staged"` block and `nextCapitalStep` returns `null` where it would compute equity or finalize, as the program fails those with `VaultStagingActive`.
- Package: the ESM build resolves bare `"."` imports in generated code.
- Breaking: swap venues allow only registered instructions. `SwapVenues` (3364 bytes) holds `venues: SwapVenue[32]` (`program`, `selectorCount`, `selectors: SwapSelector[8]` with `{ len, bytes }`); a hop's data must start with one of its program's selectors (1–8 byte prefixes), otherwise error `SwapVenueInstructionNotAllowed` (1109). `flows.buildSetSwapVenueIx({ admin, venueProgram, selectors })` sets or replaces the list, `flows.buildRemoveSwapVenueIx({ admin, venueProgram })` removes the program, `swapSelector(prefix)`; event `SwapVenueSet` adds `selectorCount, selectors`. Presets `JUPITER_V6_VENUE`, `EXPONENT_GENERIC_SY_VENUE`, `EXPONENT_CLMM_VENUE`, `EXPONENT_CORE_VENUE`, `DEVNET_SWAP_ROUTER_VENUE`; `MAX_VENUE_SELECTORS`, `MAX_SELECTOR_LENGTH`, `EXPONENT_*_PROGRAM`. The devnet mock router now takes Jupiter's `route` discriminator before its payload.
- Second-leg rollover: `VaultGovernanceUpdate` `{ kind: "secondLeg"; value: newMint }` (kind 6) and `governance.applySecondLeg(proposalPayer, newMint, priceSection?)`; `applyUpdate("secondLeg")` throws. `Vault.secondLegGeneration` (offset 512, `reserved` is now 24 words); event `VaultSecondLegRolled` (EventKind 78); errors `VaultSecondLegNotEmpty` 1107, `VaultSecondLegTemplateMismatch` 1108.
- Breaking: vault pricing reads `PriceRoute` accounts (`["price_route", mint]`) instead of `OracleSource` + Pyth update. `VaultManagerAddresses.mainOracleSource` / `secondOracleSource` become `mainPriceRoute` / `secondPriceRoute`; `processEquity`, `processIdleWithdrawal`, `topUpManagerStake`, `finalizeShutdown`, `governance.applyUpdate` (fees, HWM reset), `swaps.emergencyFinalizeWithdrawOnly` and `VaultSwapPlan` take a `priceSection` (main route and its brick accounts, then the second's) instead of `mainPriceUpdate` / `secondPriceUpdate`. `resolveVaultPriceSection(rpc, planner, pythShard)` / `fetchVaultPriceRoutes` + `vaultPriceSection` replace `resolveVaultPriceFeeds` / `resolveVaultPriceUpdates` / `PairPriceFeeds`.
- `PriceRoute` helpers: `findPriceRoutePda`, `assertSupportedPriceRoute`, `decodePriceRouteAccount`, brick builders `pythBrick` / `splStakePoolRate` / `exponentPtBacking` / `exponentStakePoolPtRoute`, `encodeRoute`, `routeBricks`, `priceRouteState`, `programDataAddress`, `priceRouteAccounts` / `priceRouteBrickAccounts`, `evaluatePriceRoute`, and `flows.buildSetPriceRouteIx` / `buildClosePriceRouteIx` (timelock only). The `acta-price` evaluation (`encodeBricks`, `decodeBricks`, `validateRoute`, `evaluateRoute`, `pythPriceUpdate`, `ptDiscount`, `priceProduct`, `priceTo1e9`) is ported with golden vectors from the Rust crate.
- `http`: `Vault.staging: VaultStaging | null` (`mint`, `from_leg: VaultLeg`, `amount`, `staged_at`) is the leg balance the vault holds in another mint's ATA, pending return to a leg.
- Breaking, wire: `SwapQuoteResultData` drops `main_price_update` / `second_price_update`; the served transaction carries the price section.
- Breaking: `VaultSwapIntent` takes `priceRoutes: { main, second, shards }`; the served price section must equal each route's accounts on one of `shards`, otherwise `VaultIntentError` `{ kind: "priceSection", leg }` (was `priceUpdate`).
- Breaking: vault swaps route through hops into registered swap venues. `VaultSwapPlan` takes `hops: VaultSwapHop[]` (`{ program, source, destination, accounts, data }`, 1–4 hops chaining the input leg ATA through transit ATAs to the output leg ATA, leg ATAs nowhere else, vault canonical ATAs of other mints allowed as scratch, at most 64 CPI accounts per hop, 128 account slots across hops (`MAX_VAULT_SWAP_PATH_ACCOUNTS`, each hop's accounts plus its 3 fixed ones) and 1024 data bytes in total; `amountIn` is the most the input leg may spend) instead of `router` / `routeAccounts` / `routeData`; a Jupiter route is one hop. The fixed `jupiterProgram` account is replaced by the `SwapVenues` registry, the last fixed account (rebalance and staging 14 fixed accounts, conversions 11, emergency 10); the wire header ends with `hop_count` (`VAULT_REBALANCE_WIRE_HEADER_LENGTH` 27, `VAULT_CONVERSION_WIRE_HEADER_LENGTH` 26), each hop adds `account_count u8, data_len u16, amount_offset u16 (0xffff = none), [scale_num u64, scale_den u64 when set], data` and the accounts `program, source, destination, ..`. `swaps.spotRebalance` / `recoveryConvertToMain` / `closingConvertToMain` / `emergencyConvertToMain` are async; `appendVaultSwapHops` is synchronous. `validateVaultSwapRoute` / `appendVaultSwapRoute` are replaced by `validateVaultSwapHops` / `appendVaultSwapHops`; new `MAX_VAULT_SWAP_HOPS`. `VaultSwapHop.amountOffset: VaultSwapAmountOffset` (`{ offset, num, den }`; not on the first hop, `offset + 8 <= data.length`, nonzero `num` and `den`): the program writes `floor(((received + 1) * num - 1) / den)` (u64 LE) at `offset`, `received` being what the hop's source got earlier in the swap.
- `SwapVenues` registry account (`["swap_venues"]`, discriminator 16, up to `MAX_SWAP_VENUES` = 32 programs), `findSwapVenuesPda`, `VaultManagerAddresses.swapVenues`, `flows.buildSetSwapVenueIx` (opcode 75, timelock only, error `SwapVenuesFull` 1105) and event `SwapVenueSet` (75).
- Breaking: `VaultSwapIntent.router` is removed. `verifyVaultSwap` rebuilds the served hops (venue PDAs, chain from input to output leg, caps) and rejects a hop account that makes any pinned read-only account (fixed, price section, venue, program) writable, or that is the vault's share mint or share ATA.
- Breaking: two-stage swaps v2. `VaultStagingPlan.generation` (required: `stagedGeneration + 1` to stage, `stagedGeneration` to unstage), `VaultStagingMode.RecoveryStage` with `mainPriceSection`, `VAULT_STAGING_WIRE_HEADER_LENGTH` 36; `Vault.stagedKind`, `Vault.stagedGeneration`; `VaultStaged`/`VaultUnstaged` carry `generation`; error `VaultStagingGenerationMismatch` (1106). `VaultSwapIntent.staging: VaultStagingSlot` (the decoded `Vault`'s `stagedMint`, `stagedAmount`, `stagedGeneration`) is required: `verifyVaultSwap` refuses a stage while a slot is open (no top-ups), an unstage with nothing staged, of another mint or of less or more than the whole `stagedAmount`, and a served `generation` other than the slot's (`{ kind: "terms" }`). `http`: `VaultStaging.generation` names the open slot.
- Breaking: staged vault swaps. `swaps.stagingSwap` builds `VaultStagingSwap` (opcode 76; `VaultStagingMode`, `VaultStagingLeg`, `VaultStagingAsset`); `SwapQuoteResultData.staging?: VaultStagingStep` marks a served staging half, which `verifyVaultSwap` checks against the new required `VaultPriceRoutes.staging` (accepted staging mints' routes; `[]` refuses staged swaps, `{ kind: "priceSection", leg: "staging" }`). Routes must now price the vault's main and second mints.
- Timelock: wrapped data longer than 128 bytes (up to 1024) is committed unpadded under `acta:pend.v3`, as the program does; `buildGenericPendingPreimage` returns that preimage.

## 0.2.3

- `http`: `Vault.metadata?: VaultMetadata` (`name`; optional `curator`, `strategy`, `description`, `website`, `twitter` handle and `logo_url`) carries the operator-curated display fields `/api/v1/vaults` now serves. They are display only; `governance` stays the authority.
- `http.depositorPerformance({ history, shares, sharePrice, generationStart })`: FIFO deposit lots of the current generation from a wallet's vault history (processed deposits open lots at `boundary_price`, processed withdrawals consume the oldest, cancels and refunds are ignored, events up to the `generationStart` second are dropped), per-lot `returnSinceEntryPct` (null for a zero entry or current price), `value = floor(shares × sharePrice / 1e9)` and the unconverted `secondRedeemed`. No money gain: shares are transferable.
- `chain.flows` vault lifecycle rules (`requestDepositBlock`, `cancelDepositBlock`, `refundDepositBlock`, `requestWithdrawBlock`, `cancelWithdrawBlock`, `selfProcess*Block`, …) are the eligibility source for apps; tests now cover the program's cutoff, fallback-delay and Closing edges.
- Breaking, `http`: `Vault.performance` is a nullable object (`capital_generation`, `computed_at`, `covered`, `returns` 7d/30d/90d/all, `apy_30d`/`apy_90d`, drawdown, cycle stats) replacing the `status` union; `VaultTrackRecord` is removed. `Vault` gains `capacity`, `created_at`, `generation_started_at`, `tvl_usd_1e9` + `price_time`, `premium`, `fees`, `depositors_in_generation`; `ActaHttpClient.vaultExposure(pda)` reads `/vaults/{pda}/exposure` (`VaultExposure`).

- Breaking, wire: `QuoteFilledMessage` carries `market_pda`, `position_type` and `expiry_ts: number | null` (Unix seconds; `null` when the server no longer knows the market, then resolve it from `market_pda`), so a fill is attributable without the RFQ.
- `ws`: `totalPremiumQuoteAtoms(price, quantity, underlyingDecimals, quoteDecimals)` returns the fill premium in quote atoms as a `bigint`, rounded exactly as the contract's `scaled_quote_amount` (throws past u64); `quantityToUnits(quantity, underlyingDecimals)` converts underlying atoms for display (1e9 prices: `scaledPriceToFloat`).
- `DEVNET_API`, `DEVNET_WS`, `MAINNET_API`, `MAINNET_WS`: the public origins, from the package root and `/ws`.

## 0.2.2

- `QuoteRejectReasonValue` adds `rate_limited` and `trading_paused`. Quote-plane messages (quotes and cancels) over the per-connection quote bucket are rejected without closing the socket; exhausting the general message bucket still closes with 4003.
- `ServerCloseCode`: codes on server-initiated closes (4000 `idle_timeout`, 4001 `auth_timeout`, 4002 `slow_consumer`, 4003 `rate_limited`, 4004 `session_replaced`, 4005 `hello_timeout`), visible through the `disconnected` event. `ClientCloseCode` names the codes the client reports in `disconnected` for its own drops (pong timeout 4900, was 4000; failed authentication 4901, was 4001), so neither collides with a server code; they are not sent to the server.
- `SwapQuoteResult` carries `main_price_update` and `second_price_update`, the Pyth accounts the served swap was built with. New vault swap error codes `vault_swap_oracle_stale` and `vault_swap_route_unavailable`.
- Breaking: `createRfq`, `acceptQuote` and `submitSignedSponsoredTx` return `Promise<RequestId>` that resolves on the correlated `CommandAccepted` and rejects with `ActaWsRequestError` on the correlated `RequestError` (or timeout/disconnect); each takes optional `RequestOptions`. `RfqRequest` carries `request_id` (replacing `client_request_id`, still the idempotency key), which `RfqCreated` echoes.
- `ActaWsRequestError.delivery` keeps `RequestError.data.delivery` (`not_forwarded` / `unknown`, `null` when absent). `not_forwarded` covers that attempt only.
- Breaking: the client-raised error tag `generic` is renamed `client_generic` so it cannot be confused with the server's `Generic`.
- The `positions` channel is removed; `PositionUpdated` and `MakerCapsUpdated` are delivered to the owning maker without a subscription.
- WS `verifyAndSignTakerFill({ txBase64, quote, rfq, taker, tables, bounds, signer })` signs taker slot 1 of a served maker-quote fill only after rebuilding it from the quote and RFQ terms (`TakerFillRfq`): program and market PDA, order id (must equal `quote.order_id`), the maker's Ed25519 check over it right before `OpenPosition`, `OpenPosition` accounts and arguments, ATA creates, an optional wSOL wrap of at most the collateral, a relayer fee payer other than the taker, no other signers or instructions, compute budget within `bounds`. Mismatches throw `VaultIntentError`. `signer` may be a `WalletTransactionSigner` (`signTransaction` over wire bytes), whose result is verified again. `flows.verifyTakerFill` / `TakerFillIntent` verify without signing. Ordinary maker-quote fills should be signed with it instead of `signSponsoredTxBase64`, whose `expectedMessageBytes` a taker cannot build. A vault maker's fill goes through the same call with `makerVault`, the on-chain `Vault` at `quote.maker` (new `fetchMakerVault(rpc, maker)` returns it, or `null` for any account that is not a program-owned vault): the account must derive to `quote.maker`, the open is rebuilt from it as a `fill` intent of `verifyVaultOpen` (operator key = `primaryDelegate`) and slot 0 is signed, the taker being the fee payer. Without `makerVault` only the ordinary shape verifies.
- Breaking: `VaultSwapIntent` takes `priceFeeds: PairPriceFeeds { main, second, shards }` instead of `mainPriceUpdate` / `secondPriceUpdate`. The quote's `main_price_update` / `second_price_update` must each be the Pyth push-oracle account of its feed on one of `shards`, otherwise `VaultIntentError` `{ kind: "priceUpdate", leg }`; the verified accounts build the expected swap. `resolveVaultPriceFeeds(rpc, planner, shards = [0])` reads the pair's feed ids from its `OracleSource` accounts.
- `SwapQuoteResult` and `SponsoredTxToSign` carry `last_valid_block_height`; the `sponsoredTxToSign` event passes it as a fourth argument.
- Breaking: `AcceptQuoteMessage` and the `SubmitSignedSponsoredTx` data require `request_id`; `buildAcceptQuoteMessage` takes an optional `requestId`. The `requestError` event payload gains optional `delivery`.
- `WsErrorCode` lists every `Generic.code` and `AuthError.reason` the server sends; both fields are typed with it. Vault sessions get `getVaultSwapStatus({ execution_id })` (sends `GetSwapStatus`) and the `SwapStatus` / `SwapSettled` messages (`SwapExecutionState`), with `swapStatus` / `swapSettled` events; `CommandAccepted` has a `commandAccepted` event.
- A `WalletTransactionSigner`'s result is pre-filtered before it is returned: at most 1232 bytes that round-trip unchanged through the transaction and message codecs, a v0 message that is the verified one or recompiles (as `v0::Message::try_compile` without lookup tables) to exactly it, and exactly the served signature slots (`[empty relayer, taker]`, or `[taker]` for a vault maker's fill) with a valid taker signature. Anything else, including malformed bytes, is `VaultIntentError` `{ kind: "walletMessage" }`. The relayer re-validates the submitted transaction and is authoritative; the client check may accept a transaction the server then rejects.
- `resolveVaultPriceFeeds` / `resolveVaultPriceUpdates` take the planner's `programAddress` and reject an `OracleSource` that is not program-owned or has the wrong discriminator, version or size; `assertSupportedOracleSource` also requires `pad0` and `reserved` to be zero.
- `WalletStandardMessageSigner.signMessages` is typed as returning signature maps keyed by signer address; the request carries `signatures: {}`. `signSponsoredTxBase64` is deprecated for takers.
- `buildWrapSolInstructions` takes an optional `funder` (the account whose lamports are wrapped; default `payer`).

## 0.2.1

- `processDeposits`, `processWithdrawals`, `processFinalWithdrawal` and `liquidateVaultPosition` throw on the 0.1.6 payout fields (`refundDestination`, `shareDestination`, `takerDestination`), on any other unknown payout field and on extra positional arguments, naming the 0.2.0 shape. 0.2.0 ignored them in JavaScript and paid the canonical ATAs.
- README corrections: the /vault auth challenge format (`assertVaultAuthChallenge`, rejected by `validateAuthChallenge`), `vaultCatalogChanged` delivery (best-effort, /taker and /maker `vaults` subscribers only; re-list after reconnect), the vault-quote signing slot (taker pays and signs slot 0 via `verifyAndSignSponsoredTx`), which builders still default to SPL Token, the payout renames, and the vault error codes.

## 0.2.0

- New server message `VaultCatalogChanged` (`{ vault_pda }`) on the `vaults` channel after a vault is created, re-delegated or closed, and client event `vaultCatalogChanged`.
- Breaking: `VaultSwapPlan` takes `router`, the swap router pinned by the target program build (`JUPITER_V6_PROGRAM`; the devnet build pins `DEVNET_SWAP_ROUTER`). `VaultSwapIntent.router` defaults to `JUPITER_V6_PROGRAM`.
- Breaking: removed `NonceGenerator` and the `./nonce` entry (use `ws.generateNonce`), the unscoped `decodeActaEventLine` / `decodeActaEventsFromLogs` (use `decodeActaEventsFromLogsScoped`), `ActaWsClient.acceptQuoteByOrderId` (use `acceptQuote`), `chain.u64Value`, and the `flows.{QUEUE,CANCEL,EXECUTE}_PENDING_ACTION_DISCRIMINATOR` duplicates of the generated constants. `computeApyFromAmounts` / `computeApyFromScaledPrices` drop the unused `positionType` and `strikePrice` / `strike1e9` and require the spot price. `findOraclePda` accepts `programId` (default: the global program id). Builders that resolve a mint's token program throw when given neither `tokenProgram` nor `rpc` instead of assuming SPL Token.
- Breaking (pre-launch renumbering): instruction opcodes are contiguous 0–72 (SetAcceptingDeposits 46→44, VaultSpotRebalance 47→45, RecoveryConvertToMain 48→46, RotateVaultDelegate 49→47, UnlockManagerStake 50→48, FreezeVault 51→49, UnfreezeVault 52→50, EmergencyStartUnwind 53→51, EmergencyRefundDeposit 54→52, EmergencyConvertToMain 55→53, EmergencyFinalizeWithdrawOnly 56→54, ProposeVaultUpdate 57→55, ApplyVaultUpdate 58→56, CancelVaultUpdate 59→57, AcceptVaultGovernance 60→58, CloseVault 63→59, ProcessFinalWithdrawal 64→60, OpenPositionBetweenVaults 65→61, DisableVaultDelegate 72→62, RevokeVaultGovernanceNomination 73→63, BootstrapVault 74→64, TopUpManagerStake 75→65, ApplyVaultFeeRecipientUpdate 76→66, StartVaultShutdown 77→67, ClosingConvertToMain 78→68, FinalizeVaultShutdown 79→69, SetVaultManagerStakeMinBps 80→70, SetVaultCapitalFallbackDelay 81→71, CorrectOraclePrice 82→72); program errors are contiguous from 1000 in declaration order (removed `AccountNotInitialized`, `MakerNotRegistered`, `OracleSourceNotFound`, `VaultUnknownPermissionBits`, `VaultUnknownCapabilityBits`; `OracleStale` is `OraclePriceNotSet`). Removed accounts: `topupFeeFund` `mint`; `globalConfig` from `unfreezeVault`, `emergencyStartUnwind`, `emergencyConvertToMain`, `emergencyFinalizeWithdrawOnly` and `cancelVaultUpdate`.
- Vault capital batches take payouts: `processDeposits` / `processWithdrawals` accept addresses or `{ user, refundTo?, sharesTo? }` / `{ user, sharesBackTo? }`, and `processFinalWithdrawal` an address or a `WithdrawalPayout`; the `…WithDestinations` / `…WithShareDestinations` builders are gone. `shareReturnAccount(kind, user)` gives the fallback share account the program accepts when the canonical ATA is blocked. `liquidateVaultPosition(liquidator, writer, position)` takes an address or `{ owner, paidTo? }`.
- Removed: instruction `migrateVault` (83), `flows.migrateVault`, errors `VaultMigrationRequired` (1161) and `VaultAlreadyMigrated` (1162), `EventKind.VaultMigrated` and the `VaultMigrated` variant of `ActaEvent`. The program has no account migration; earlier vaults are closed before the upgrade.
- `assertSupportedVault` checks only the discriminator and version; phase, safety, pending, capital-boundary, reserved-byte and fallback-delay invariants are the program's. `VAULT_MAX_CAPITAL_FALLBACK_DELAY_SECS` is removed.
- `VAULT_VERSION` is 1 (the 712-byte layout). `decodeVaultAccount(data)` returns the `Vault` and accepts only that layout; `VAULT_ACCOUNT_LAYOUTS`, `VaultAccountLayout`, `VaultAccount`, `VaultBoundaryFields` and `needsMigration` are removed.
- Event decoding knows only the current layout. Removed: `ACTA_EVENT_LAYOUTS`, `CURRENT_ACTA_EVENT_LAYOUT`, `VAULT_STATE_TAIL_LAYOUTS`, `CURRENT_VAULT_STATE_TAIL`, `layoutHasField`, `vaultPositionCreatedAt`, `vaultStateTailBoundary`, `DecodedActaEvent.layout` / `.vaultStateTail`. Kind 73 is not decoded.
- `signSponsoredTxBase64` no longer takes `takerSignatureIndex`; it always signs the taker slot 1. Vault writers sign slot 0 only through `verifyAndSignSponsoredTx` / `verifyAndSignSwap`.
- Mint token programs are resolved at `confirmed` commitment, so a mint created just before the call is found.
- `@solana/transactions` is no longer a runtime dependency.
- Removed: `signSponsoredTxBase64Unverified`, `ORACLE_OFFSET_AUTHORITY`, the ignored `rentSysvar` option of `buildCreateAtaInstruction` / `ensureDestAta`, `buildPositionPda` (use `findPositionPda`), `flows.wrappedInstructionParts` and the `WsI64` type.

## 0.1.6-vaults.4

- `flows.verifyVaultSwap` compares the served minimum output with `floor(quotedOut * (10000 - slippage_bps) / 10000)`, the router's rounding; a quote whose division leaves a remainder no longer fails with "minimum output exceeds the requested slippage".
- `ACTA_EVENT_LAYOUTS` lists every deployed event layout since e71da13: current, a 132-byte `VaultStateTail` on any tail event, and `VaultPositionOpened` without `createdAt` plus a 132-byte tail. `DecodedActaEvent.layout` names the match; `vaultPositionCreatedAt(decoded)` is `null` when the field was not emitted.
- New instruction `migrateVault` (83, permissionless; payer, vault, system program) and helper `flows.migrateVault(payer, vault)`: grows a 688-byte version-1 `Vault` to 712 bytes, version 2. Event `VaultMigrated` (73). Errors `VaultMigrationRequired` (1161) on any other instruction with a version-1 vault, `VaultAlreadyMigrated` (1162).
- `VAULT_VERSION` is 2. `decodeVaultAccount(data)` reads both layouts (`VAULT_ACCOUNT_LAYOUTS`) and returns `{ vault, layout, needsMigration, boundaryFields }`; for version 1 `closingStartedAt` reads 0, `boundaryEquity` / `boundaryShares` read `lastEquity` / `totalShares` (as `MigrateVault` sets them) and `boundaryFields` is `null`.
- Event decoding accepts the 132-byte legacy `VaultStateTail` (`VAULT_STATE_TAIL_LAYOUTS`); `DecodedActaEvent.vaultStateTail` names the layout and `vaultStateTailBoundary(decoded)` is `null` for a legacy tail.
- Checked vault signing: `flows.verifyVaultOpen` / `flows.verifyVaultSwap` (`VaultOpenIntent` `sale` / `betweenVaults` / `fill`, `VaultSwapIntent`, `ComputeBudgetBounds`, `VaultIntentError` with the first `mismatch`) and `flows.servedLookupTableKeys`; WS `verifyAndSignSponsoredTx` and `verifyAndSignSwap` sign only a served transaction that matches the intent. `VaultSwapIntent.minOutputFloor` (the operator's own minimum output) rejects a served minimum below it.
- Breaking: `VaultHttpClient` / `VaultApiError` are `ActaHttpClient` / `ActaHttpError`. `venue()`, `tokens()` (`Map` keyed by mint) and `markets(filter)` (`MarketFilter`: `underlying`, `quote`, `expiry`, `is_put`, `active`; the server defaults to active markets, `active: false` returns every market it holds in memory, finalized ones only since it started) revalidate with `If-None-Match` and reuse the cached body on 304. `unsettledVaultPositions(pda)` follows `next_cursor`. The `Tokens` and `TokenDetails` DTOs are removed.
- Breaking: one `TokenInfo` on both transports: `mint`, `symbol`, `decimals`, optional `oracle_feed_ids`; underlyings add `size_rule`, `strike_tick`, `quote_mints`. WS `TokensMessage` is `{ request_id, tokens }` and the `tokens` event receives `TokenInfo[]`. `MarketInfo` gains `status` (`MarketStatus`) and optional `settlement_price`.
- Breaking: `getTokens()` and `getMarketDescriptors()` take no arguments (`active_only` is gone; the server answers from its active snapshot). `deriveTokenLists` is removed; use `getTokens()` or `ActaHttpClient.tokens()`.
- New server message `ReferenceChanged` (`ReferenceChangedData { tokens_etag, markets_etag }`) and client event `referenceChanged`.
- `SwapQuoteResultData` names the `SwapQuoteResult` payload.
- `Vault` grows to 712 bytes: `closingStartedAt`, `boundaryEquity`, `boundaryShares` before `reserved`; `VaultStateTail` carries the same three fields (event envelope 448 bytes).
- New instruction `correctOraclePrice` (82, cold only) and events `OraclePriceCorrected` (71) and `VaultFeeRecipientSet` (72; fee-recipient rotation no longer emits `VaultSeedPolicySet`).
- `finalizeVaultShutdown` account 0 is `caller`: governance, or anyone after `closingStartedAt + capitalFallbackDelaySecs`.
- Errors: `VaultFeeRecipientMismatch` (1159), `VaultInvalidFeeRecipient` (1160); removed never-thrown 1025, 1030, 1031, 1034, 1153.
- `VaultPositionReference` names its market by `underlyingMint` and `quoteMint` instead of `orientation`; `VaultOrientation` and `resolveVaultOrientation` are gone. Vault legs are selected by mint.
- `Maker` exposes its 4 alignment bytes as `pad0`; `getMakerSize()` is 328 and `reserved` starts at offset 72. `assertSupportedMaker` rejects non-zero `pad0`.
- `VaultPositionOpened` carries `createdAt` (the Position's `created_at`) before `state`.
- `createVault` passes both mints' `OracleSource` PDAs; the program rejects a pair without a Pyth feed (`VaultPythFeedMissing`, 1157).
- ATA create instructions omit the Rent sysvar; `rentSysvar` is ignored.
- `ORACLE_OFFSET_AUTHORITY` is deprecated; use `ORACLE_OFFSET_FEED_ID`.
- `buildSettlePositionIx` / `buildLiquidatePositionIx` take `{ programId, settler | liquidator, position, market }` (decoded accounts) and append the vault accounts for vault positions; the address-based builders are removed. Both are exported from `chain` and `chain.flows`. `flows.buildSettlePositionFromAccounts` is renamed `buildSettlePositionIx`; settlement `unwrapUserSol: true` takes a `userDestAta` and replaces `taker` / `flags`.
- Event decoding returns `null` for an event with trailing bytes; the 48-byte `UpdateOraclePrice` fallback (implied `DirectHot`) is removed.
- `assertSupportedPosition` (and every Position fetch) rejects schema v1; `LEGACY_POSITION_VERSION` is removed.
- `flows.vaultCapitalBatchComputeUnits` and its `VAULT_CAPITAL_*_COMPUTE_UNITS*` constants mirror the Rust `vault_capital_batch_compute_units`.
- Ops scripts settle and liquidate vault positions.

## 0.1.6-vaults.3

- Governance and delegate may share a key. Vault maker recovery uses the granted `SIGN_MAKER_QUOTES` permission; governance identity alone does not grant it. Requires the matching backend update.

Changed:

- Vault events carry absolute post-state instead of deltas: `VaultStateTail` (`state`) on every event that writes hot vault fields, `VaultConfigTail` (`config`) on config events, whole `VaultPolicyRow` / `VaultDelegateRow` rows. Fields a tail already carries are gone from the events.
- `VaultDepositRequested` / `VaultWithdrawRequested` carry `createdAt`; `VaultCreated` carries decimals; `VaultConfigured` carries the delegate row, policy, config and state.
- Batch capital events carry each user's post-state.
- `Vault.capitalGeneration` (increments at final redemption) and `Vault.managerStakeShares`; `reserved` is 32 words.
- `unlockManagerStake` marks the vault writable.

Added:

- `ClientRole` and `WsEndpointKind` include `"vault"`.
- Vault program errors (1100–1156) carry messages.
- `flows.nextCapitalStep` / `planWithdrawals`: the reference operator's capital crank as pure functions over the vault DTO.
- `flows` vault rules: depositor and operator action predicates checked against the contract FSM; self-processed withdrawals are blocked in `closing` and in unpriced `idle` until `processIdleWithdrawal`.
- `pythPriceFeedAccount` / `resolveVaultPriceUpdates`: Pyth push-oracle price accounts for `processEquity`.
- `pythPriceFeedAccount` rejects a shard outside u16.
- `Vault.deposit_window_set_ts`: chain time of the last `SetAcceptingDeposits`, or `null`.
- wSOL helpers: `buildWrapSolInstructions`, `syncNativeInstruction`, `closeTokenAccountInstruction`, `NATIVE_MINT`.
- Vault capital batches take at most `VAULT_CAPITAL_BATCH_USERS` (3) users: a four-user batch overflows the 1232-byte packet.
- `SwapQuote` names the leg with `direction` (`main_to_second` / `second_to_main`) instead of `input_mint` / `output_mint`.
- Vault rebalance wire header is 28 bytes (conversions 27); routes go through `JUPITER_V6_PROGRAM` only.
- Recovery/closing/emergency conversion builders no longer take `direction` and use a 27-byte header; `spotRebalance` takes `VaultRebalancePlan`.
- `VaultHttpClient.vaultDepositRequests` / `vaultWithdrawRequests`: paged pending request queues.
- `@acta-markets/ts-sdk/http`: `VaultHttpClient` and DTO types for the vault read API; `VaultApiError.retryable` for 429 and 503 except `db_disabled`.
- Auth challenges are bound to the signing identity: takers to their wallet, vault operators to their operator and vault PDA.
- Open/Settle/Liquidate discover two unknown mint programs with one RPC; supplied programs avoid lookup.
- ATA planning derives each address once. Vault helpers share the existing strict u64 validator; accepted values and instruction bytes are unchanged.
- Swap routes accept repeated canonical accounts, preserve their order, and keep PDA signing inside CPI.
- Swap retries accept caller UUIDs, normalized to lowercase; swap slippage is a wire `u16`.
- Vault auth and recovery callbacks stay on their operator connection.
- Owner execution lookup: `GetOrderStatus` on `/vault` and the three standard endpoints.
- Typed `/vault` projection notifications.
- Swap amounts and deadlines: backend-compatible `WsU64String` decimal strings, full u64 in both directions.
- `/vault` operator authentication; typed, idempotent rebalance/recovery quote-submit messages with stable error codes.
- Typed depositor/manager planning: automatic PDA/ATA derivation and legacy-safe capital batches.
- Typed manager governance, position lifecycles and route-bound Jupiter spot/recovery planners, including mixed legacy SPL/Token-2022.
- V1 manager API: separate position reference/quote types, Rust-parity addresses and complete static policy/mask/swap checks.
- `VaultManagerPlanner.fromVault`: existing vault account plus transaction actor.
- Vault PDA helpers (`findVaultPda`, `findVaultPolicyPda`, `findVaultDelegatePda`, `findPendingVaultUpdatePda` and the request/escrow PDAs), `assertSupportedVault`, `timelocked`, `decodeActaEventsFromLogsScoped`, `VaultDelegatePermission` / `hasVaultPermission`.
- `lookupTables` option on `buildAndSignTransactionFromIxs`, `sendAndConfirmIxs` and `ActaChainClient`.

Fixed:

- `ORACLE_OFFSET_FEED_ID` is 64 (was 96, which points into `reserved`). `ORACLE_OFFSET_AUTHORITY` is kept for compatibility; the Oracle account has no authority field.
- `computeUserPriceFromGross` rejects `feeBps` outside `0..=10000`; order signature helpers reject signatures that are not 64 bytes.
- Position, PremiumConfig, Maker and Oracle decoders accept accounts longer than the current layout; Market requires at least its size. Version, flag and reserved-byte checks are unchanged.

## 0.1.6

- Validate the server's canonical maker/taker auth challenge before invoking any signer, including reconnects and replacement challenges. Raw clients can use `validateAuthChallenge`; authentication and order-signing wire bytes are unchanged.
- Quote signing derives the market PDA locally and validates its program, chain, option type and token pair before calling the signer. The primary RFQ strike works without alternative strikes.
- Sponsored transaction signing rejects a declared signer address that differs from the required signature slot.

## 0.1.5

- Order lifecycle callbacks require `orderVersion`; missing or invalid wire versions are rejected before dispatch.
- `submitQuoteSigned` and `submitReplaceQuoteSigned` reject if authentication changes while signing and do not send on the replacement session.

## 0.1.4

- Discard auth results and event continuations from old connections and signatures for superseded challenges.
- A new auth provider opens a new connection and clears the previous implicit resume credential. Resume write failures emit `error`.
- Manual reconnect cancels the previous reconnect timer.
- Timed-out unsent requests leave the Welcome queue. Duplicate pending IDs are rejected; synchronous write failures clear pending requests. Queue discards return `queue_overflow`.
- Request IDs remain UUIDs without Web Crypto.
- Sponsored transactions select the required transaction signer's signature.

## 0.1.3

- `GetOrderStatus`: `/maker`, `/maker/data`, `/taker`, matching the backend. Public API and wire formats unchanged.

## 0.1.2

- Required: `Welcome.server_time_unix_ms`, `AuthSuccess.expires_at`, `RfqBroadcast.sent_at_unix_ms`, `instruction_index` on all six known chain events. Missing/null fields fail before dispatch; wire units and protocol version are unchanged.
- `authenticated` always includes resume credential expiry in Unix seconds, not socket lifetime. Unknown future event variants retain existing handling.

## 0.1.0-beta - 2026-09-03

- Backend wire fields: `RfqBroadcast.sent_at_unix_ms`, position `reconciliation_status`, `unrenderable` / `unrenderable_positions`, `GetPositions` filters, chain-event `instruction_index`, `QuoteRejected` reasons `unauthorized` / `internal_error`, optional `EarnAssetSummary.min_apr/max_apr`, `FEATURE_*` constants.
- Wire errors: `ServerError`. SDK errors: `ClientError` (`generic`, `feature_unsupported`, `unsafe_integer`). The `error` event carries `ActaWsClientError`.
- Typed `ActaWsClientEvents` without an index signature; new `makerCapsUpdated`, `quoteCheck`, `tokenCaps`, `unknownMessage` events.
- Every (re)auth re-reads taker `GetMyActiveRfqs` + `GetPositions`, or maker `GetMmSummary` + `GetActiveRfqs` + `GetMyQuotes` + `GetMyTrades`, by endpoint plane. No `Subscribe` on `/maker/data`.
- `cancelOnDisconnect`; missing requested `Welcome` features cause terminal `feature_unsupported`, without reconnect.
- Silent sockets reconnect after `pongTimeoutMs` (10s). Backoff resets after `backoffResetAfterMs` (30s) of uptime past Welcome.
- Promise API: `request(responseType, message)` correlates by `request_id`; `getPositionsAsync`, `getMyActiveRfqsAsync`, `getActiveRfqsAsync`, `checkQuoteAsync`, `getMmSummaryAsync`, `getMyQuotesAsync` reject with `ActaWsRequestError`.
- Full-u64 `nonce: bigint` in `QuoteMessage`, `ReplaceQuoteMessage`, `QuoteReceivedMessage`: exact inbound parsing, outbound bare JSON numbers. `generateNonce()` returns a random u64 bigint.
- Other inbound literals above 2^53 emit `unsafe_integer` once per message type per connection; dispatch continues.
- `state.positionsPartial` marks capped `MmSummary` positions. `connected` means open and unauthenticated, including after `LogoutSuccess`. `onerror` emits `disconnected` before reconnect. Failed auth closes the socket for `connectAndAuthenticate` retry.

## 0.0.26-beta - 2026-07-15

- Narrow browser imports: `chain`, `ws`, `ws/keypair`, `types`, `nonce`.
- Tighter order, settlement and program-ID handling.
- Backend-compatible WebSocket errors and referral/invite messages.

## 0.0.25-beta

- Governance and timelock bindings; clean, side-effect-free, reproducible `prepack` builds.

## 0.0.24-beta

- `getMakerBalances` / `MakerBalances` → `getMmSummary` / `MmSummaryData`. Balances: `MakerBalanceCapInfo { mint, symbol, decimals, deposited, committed, available }`.
- Owner-only `PositionUpdated.caps_snapshot`; no follow-up `GetMyCaps` needed.
- `AuthSuccess.maker_pda`; `authenticated` callback: `(sessionId, expiresAt, makerPda)`.
- `MakerPositionInfo` / `MakerQuoteInfo` / `MakerTradeInfo`: underlying/quote `mint/symbol/decimals`. `MakerPositionInfo`: `status` union (`"none" | "open" | "funded" | "liquidated" | "settled"`) and `settlement_price`.
- New events: `QuoteRejected`, `BatchQuotesAck`, `CancelAllQuotesAck`.
