Releases

Changelog

What shipped across the sufwan-local-test platform and the @agg-build packages.

Weekly entries cover everything that shipped across the platform, including API and data-pipeline work. Version entries are the published @agg-build packages — those four are version-linked, so a release usually bumps them together, and the tags show which ones shipped. Subscribe via RSS.

2026-11-05 · v4.37.0

  • sdk, hooks: Add optional orderbookUrl so the orderbook channel can use a second socket, and orderbookLatency to opt orderbook subscriptions into the realtime tier. With that URL set, orderbook liveness follows the orderbook socket and an orderbook-only subscription does not hold the primary socket open.
  • ui: The place-order panel no longer shows the self_custody_app_fee custody warning. A partner fee that a self-custody wallet cannot pay is now waived and the trade fills without it, so the user has nothing to act on.
  • ui: Replace the placeholder Lighter monogram with its official brand-kit icon and theme-aware colors, and use official application artwork for image logos.
  • ui: Show Lighter balances in the manage-balances modal with their venue label.

2026-11-04 · v4.36.1

  • Withdrawals can pay out to HyperEVM (chain 999) in USDC or USDT (USD₮0). useWithdrawFlow offers it as a network even though deposit addresses never list it (HyperEVM is a withdrawal destination only), and the UI shows its name, icon and explorer link.

2026-11-03 · v4.36.0

  • sdk: listVenueEvents({ live: true }) returns only sports events in play: open, with a sport, and a gameStartTime within the last 6 hours. Pass the returned ids to GET /sports/live for scores and game state.

  • sdk: getLeaderboardSeries accepts interval: "hour" for one user's stats per hour over a timestamp window, and series points carry a start timestamp. getLeaderboard timestamp windows accept up to 20 userIds.

  • sdk: Document bridge_shortfall as a partialFillReason: the bridge delivered less than quoted, so the order was downsized before submit. It replaces the previously documented but never emitted bridge_short. It replaces venue_capacity on a downsized market order that ends partial_fill; a downsized order the venue filled in full still ends filled.

    Market-order partial_fills now also carry remainingSizeRaw (requested shares minus filled shares). It stays null on a filled market order; limit orders keep reporting it as before.

  • sdk: Retry explicitly unavailable WebSocket snapshots for active outcomes with bounded jittered backoff, without reconnecting healthy sockets. Limit recovery to three attempts per outcome and twenty outcomes per second per socket; cancel obsolete retries after snapshots, unsubscribe or disconnect.

  • hooks, ui: Refresh buy quotes as soon as the orderbook moves under them, instead of waiting for the next poll.

    • Hooks: useSmartRouteBookRefresh({ venueMarketOutcomeId, quote, refetch, isFetching, enabled?, minIntervalMs? }) watches the outcome's live orderbook and calls refetch when a level the quote depends on changes: on each venue the quote fills, the levels at or below the worst price it took there; on every other venue, the levels at or below the route's worst price. It refetches at most once every 2 seconds and only while enabled, live updates are on and the page is visible. Pass it the values from useSmartRoute. The quote shown is always the server's, and useSmartRoute's polling stays as the fallback.
    • UI: the place-order panel uses it for buy quotes while the panel is active and the user isn't editing or submitting.
  • hooks: The wagmi wallet signer now checks which chain the wallet is on at signing time before switching chains for a typed-data request. A run of requests signed back to back can change chain in between, for example a Hyperliquid chainId-1 signature followed by a Polygon one. Previously the signer used the chain from the last render, skipped the switch, and the wallet refused the request ("Provided chainId … must match the active chainId …").

  • ui: Keep order progress moving forward: the "Submitting order" step now sits just before the fill, so setup and bridging show their own progress instead of resetting to the first step, and the step being waited on always shows a spinner.

2026-11-03 · v4.35.0

  • Add PromoBanner to @agg-build/ui/primitives: a full-width brand strip for the top of a page.
    • text and href set the message and where it links (opens in a new tab).
    • Pass onDismiss to show a close button; the caller decides what dismissing means (for example, remembering it in storage).

2026-11-02 · v4.34.1

  • getLeaderboard() accepts ISO-8601 timestamps (with an offset) for from and to, alongside UTC days. With the client's server-side apiKey and a single-user lookup it returns that user's row over [from, to) — for windows shorter than a UTC day — with rank: null. to defaults to now; at most 366 days. Without an API key issued for that app, a timestamp window returns 403.

2026-10-30 · v4.34.0

  • sdk: listVenueEvents({ live: true }) returns only sports events in play: open, with a sport, and a gameStartTime within the last 6 hours. Pass the returned ids to GET /sports/live for scores and game state.

  • sdk: Document bridge_shortfall as a partialFillReason: the bridge delivered less than quoted, so the order was downsized before submit. It replaces the previously documented but never emitted bridge_short. It replaces venue_capacity on a downsized market order that ends partial_fill; a downsized order the venue filled in full still ends filled.

    Market-order partial_fills now also carry remainingSizeRaw (requested shares minus filled shares). It stays null on a filled market order; limit orders keep reporting it as before.

  • hooks: The wagmi wallet signer now checks which chain the wallet is on at signing time before switching chains for a typed-data request. A run of requests signed back to back can change chain in between, for example a Hyperliquid chainId-1 signature followed by a Polygon one. Previously the signer used the chain from the last render, skipped the switch, and the wallet refused the request ("Provided chainId … must match the active chainId …").

2026-10-29 · v4.33.0

  • sdk, hooks: Add the catalog feed: get new, changed and deleted events and markets pushed instead of polling the list endpoints.

    • client.watchCatalog({ since, venues?, entities?, matchedOnly?, onChange, onSynced?, onError?, onResyncRequired? }) keeps your copy of the catalog in sync. It subscribes to the WebSocket catalog channel, catches up over GET /catalog/changes on start and after every reconnect, and calls onChange once per newer state of each event or market, with duplicates already removed. Save handle.lastChangedAt and pass it back as since to resume after a restart. If that point is more than 24 hours old, onResyncRequired fires: re-list, then call handle.resync(startOfReList). With matchedOnly: true you get only cross-venue matched entities, plus a final update when one you already received leaves its cluster or is deleted.
    • client.getCatalogChanges({ since | cursor, limit?, venues?, entities? }) for paging the REST endpoint directly. A position older than the 24-hour window throws an AggApiError with code resync_required (isCatalogResyncRequired(err)).
    • AggWebSocket.subscribeCatalog(filter, cb, { onSubscribed?, onInterrupted? }) for the raw live channel.
    • New types: CatalogChange, CatalogEventSnapshot, CatalogMarketSnapshot, CatalogEntity, WsCatalogChange and related types.
    • Hooks: useCatalogFeed(options) wraps watchCatalog and returns { status, lastChangedAt, error, resync }.
  • sdk: Market outcomes now carry winnerSource ("venue" or "matched"), so you can tell a winner sufwan-local-test inferred (from matched markets on other venues, or from the moneyline for a soccer ±0.5 spread) apart from the venue's own result. It is on REST outcomes, the WebSocket market_resolved frame and market.resolved webhook outcomes, and is null (REST) or absent (WebSocket, webhook) when winner is null or predates this field.

    ProphetX names no winner when a market resolves, so sufwan-local-test fills one from agreeing matched markets about every 15 minutes. That matched winner can change when ProphetX settles (for example a void after a tennis retirement), which flips the source to venue and bumps the market's updatedAt. The matched fill sends no market_resolved event or webhook, and the correction may or may not send one, so poll updatedAt to catch both. Settle held ProphetX positions only on winnerSource: "venue" or refundAtCost: true. See Market resolution in the docs.

  • sdk, ui: custody=self balances: each chain row now carries walletAddress, the linked wallet the balance belongs to. A row for the wallet itself names that wallet. A row for its Polymarket deposit wallet or legacy wallet names the wallet that owns it.

    The "Trade from" and "Withdraw from" selectors now include a wallet's deposit wallet and legacy Safe in its balance. A legacy proxy, which can only be spent on Polymarket, is still left out.

  • sdk: client.getWithdrawalQuote({ ..., signingAddress }) quotes the Max a linked self-custody wallet can withdraw, using the same source rules the withdrawal itself applies, so a withdrawal of exactly that amount is always accepted. previewWithdraw already accepts signingAddress and now prices the withdrawal from that wallet.

  • Self-custody withdrawals from wallets that cannot sign an EIP-7702 batch (e.g. MetaMask).

    • SDK: getWithdrawalQuote, withdrawPreview and withdrawManaged accept approveMode: "sponsored" | "user_broadcast" alongside signingAddress. Under user_broadcast the wallet is never asked for a transaction or gas: a same-chain native USDC withdrawal is two EIP-3009 signatures, and sources that would need an approval or a batch (e.g. USDT, USDC.e) are left out of Max, preview and the withdrawal.
    • Hooks: useWithdrawFlow and useWithdrawPreview send the wallet's approveMode (picked by the same rule as a fill, now exported as selfCustodyApproveMode(walletSigner, addresses)). useWithdrawPreview no longer sends max with a signingAddress, which the server refused.
    • UI: place-order uses the shared selfCustodyApproveMode rule; behaviour unchanged.
  • sdk: Add self-custody withdrawals.

    • client.withdrawManaged({ ..., signingAddress }) withdraws from a wallet linked to the account instead of the managed wallet. The response carries pendingSignatures once the withdrawal is waiting on that wallet.
    • client.submitWithdrawalSignatures(withdrawalId, signatures) answers those requests (POST /execution/withdrawals/:withdrawalId/signatures) and returns the withdrawal's current status.
    • New type: SubmitSignature.
  • sdk: Add matchedAt to ExecutionStatusOrder: set when the venue has matched an order whose fill still awaits the venue's confirmation (TipRun L2 approval, usually ~15s). It is a signal, not a fill; status, event and the filled amounts change only once the fill is confirmed.

  • sdk, hooks: The search option on getVenueEvents / getVenueMarkets and on useVenueEvents / useVenueMarkets is deprecated. Use client.search({ q, type: "events" | "markets" }) for text search. search keeps working: it is a case-insensitive substring match on the event title (or the market question and its event title), now limited to rows the search index matches by word or typed prefix, so a fragment inside a word (for example PI in GPI) no longer matches.

  • sdk: Coalesce concurrent event-detail reads, bound their concurrency and queue, and delay subsequent reads after HTTP 429 according to Retry-After plus jitter. Abort subscribers independently and cancel obsolete reads when the authentication session changes. Existing request signatures and server errors are preserved; writes are never retried automatically.

  • sdk, hooks: Wake self-custody status polling when an authenticated signature-wait event arrives for the current order. Keep timed polling as recovery and coalesce notifications received during an outstanding status request.

  • hooks, ui: AggWalletSigner has a new optional member, canSignEip7702Authorization(address). Return true when your signRequest can sign an eip7702_authorization for that wallet, for example when it wraps a local account or an embedded wallet's 7702 API. PlaceOrder then fills self-custody orders in sponsored approve mode when it returns true for the signing wallet and, on a buy, every funding wallet. Without the member, or when it returns false, PlaceOrder keeps using user_broadcast, and a retry of a failed fill reuses the mode the first attempt was sent with. The bundled wagmi signer does not set it: a browser wallet cannot sign an EIP-7702 authorization, so it stays on user_broadcast.

  • hooks, ui: Add an opt-in self-custody option to the withdraw modal.

    • New feature flag features.enableSelfCustodyWithdraw (default false). When it is on, tradingCustody allows self-custody, an AggWalletSignerProvider is present and the user has a linked EVM wallet, WithdrawModal shows a "Withdraw from" selector. The Agg balance stays the default; with the flag off nothing changes.
    • With a wallet selected, the modal's balance and Max come from getWithdrawalQuote({ ..., signingAddress }), the most that wallet can withdraw, so Max is always accepted. It previews fees with signingAddress like the managed preview, sends signingAddress (never max), and asks the wallet for each signature the withdrawal needs. A declined signature ends the flow and the withdrawal expires.
    • Hooks: useWithdrawFlow({ ..., signingAddress }), useWithdrawPreview({ ..., signingAddress }) and useWithdrawalSignatures({ withdrawalId, signingAddress, pendingSignatures }). useSelfCustodySigning takes an optional submit so it can answer a withdrawal as well as a fill.
    • WithdrawModal controlled props accept an optional selfCustody (WithdrawSelfCustodyProps).
  • hooks, ui: The withdraw modal's "Withdraw from" row for the selected wallet now shows what can be withdrawn to the chosen token and network. That is the same server Max the Max button fills in, so the two numbers match. Unselected wallets, and the "Trade from" selector, still show the total held.

    useWithdrawFlow adds withdrawFlow.withdrawable (self-custody only), and TradeFromSelect takes an optional selectedWalletUsd.

  • hooks, ui: Add ensureHyperliquidAgentFor(walletSigner, masterAddress, onApproving?): before a Hyperliquid self-custody fill, it throws WalletNotConnectedError naming the master wallet when that wallet is not connected, and otherwise approves a Hyperliquid agent key in this browser when one is missing, expired or replaced. The positions-table quick sell now runs it before a Hyperliquid fill, as the order ticket already did, so a sell from an unconnected wallet no longer fails at signing with the agent's address.

  • hooks: useWithdrawFlow: a wallet (self-custody) Max now follows the server quote. Changing the destination chain, token or wallet after clicking Max updates the amount to the new Max instead of keeping the old one.

  • hooks, ui: Keep midpoint request cleanup tied to the effect that issued the request.

    Keep comparison chart selections stable when additional markets have cached prices before they load.

  • hooks, ui: Show "Matched on {venue}, confirming..." on the place-order fill step while a venue has matched the order but not yet confirmed the fill (ExecutionStatusOrder.matchedAt, e.g. TipRun L2 approval). The step never says filled until the fill is confirmed. New label: trading.matchedConfirmingOnVenue.

  • ui: PlaceOrder now refuses a Hyperliquid wallet trade before it is sent when the wallet holding the position is not connected, with a WalletNotConnectedError naming that wallet. Previously the order was parked against the account's Hyperliquid agent and failed at signing with the agent's address, which is not a wallet the user can connect.

  • ui: Raise the minimum @radix-ui/* versions so installs resolve @radix-ui/react-slot@1.5.0. Older Slot releases (1.2.x, pulled in by @radix-ui/react-dialog@1.1.15) build a new composed ref on every render, which can cause "Maximum update depth exceeded" crashes under React 19.

2026-10-27 · v4.32.0

  • sdk: Market outcomes now carry winnerSource ("venue" or "matched"), so you can tell a winner sufwan-local-test inferred (from matched markets on other venues, or from the moneyline for a soccer ±0.5 spread) apart from the venue's own result. It is on REST outcomes, the WebSocket market_resolved frame and market.resolved webhook outcomes, and is null (REST) or absent (WebSocket, webhook) when winner is null or predates this field.

    ProphetX names no winner when a market resolves, so sufwan-local-test fills one from agreeing matched markets about every 15 minutes. That matched winner can change when ProphetX settles (for example a void after a tennis retirement), which flips the source to venue and bumps the market's updatedAt. The matched fill sends no market_resolved event or webhook, and the correction may or may not send one, so poll updatedAt to catch both. Settle held ProphetX positions only on winnerSource: "venue" or refundAtCost: true. See Market resolution in the docs.

  • sdk, hooks: The search option on getVenueEvents / getVenueMarkets and on useVenueEvents / useVenueMarkets is deprecated. Use client.search({ q, type: "events" | "markets" }) for text search. search keeps working: it is a case-insensitive substring match on the event title (or the market question and its event title), now limited to rows the search index matches by word or typed prefix, so a fragment inside a word (for example PI in GPI) no longer matches.

  • hooks, ui: AggWalletSigner has a new optional member, canSignEip7702Authorization(address). Return true when your signRequest can sign an eip7702_authorization for that wallet, for example when it wraps a local account or an embedded wallet's 7702 API. PlaceOrder then fills self-custody orders in sponsored approve mode when it returns true for the signing wallet and, on a buy, every funding wallet. Without the member, or when it returns false, PlaceOrder keeps using user_broadcast, and a retry of a failed fill reuses the mode the first attempt was sent with. The bundled wagmi signer does not set it: a browser wallet cannot sign an EIP-7702 authorization, so it stays on user_broadcast.

  • hooks, ui: Add ensureHyperliquidAgentFor(walletSigner, masterAddress, onApproving?): before a Hyperliquid self-custody fill, it throws WalletNotConnectedError naming the master wallet when that wallet is not connected, and otherwise approves a Hyperliquid agent key in this browser when one is missing, expired or replaced. The positions-table quick sell now runs it before a Hyperliquid fill, as the order ticket already did, so a sell from an unconnected wallet no longer fails at signing with the agent's address.

  • ui: PlaceOrder now refuses a Hyperliquid wallet trade before it is sent when the wallet holding the position is not connected, with a WalletNotConnectedError naming that wallet. Previously the order was parked against the account's Hyperliquid agent and failed at signing with the agent's address, which is not a wallet the user can connect.

2026-10-27 · v4.31.0

  • Add redeemSelfCustody to redeem a resolved position held in a self-custody wallet. Limitless, Predict and Myriad prompt a transaction (the user pays gas); Polymarket prompts one typed-data signature. fillSelfCustody and redeemSelfCustody accept a per-call signer. Position legs carry signerAddress for self-custody holdings, and computeClosedPositionTotals returns signerAddressByOutcome. useRedeem().mutateAsync takes that map and redeems those outcomes from the wallet signer, one prompt at a time, and the rest through client.redeem. The Claim winnings button uses it, so self-custody winners can be claimed in the UI.
  • sdk: Add finalizedAt to fees.actual.appFee on orders: the time the app fee stopped changing (collected in full, skipped, or no longer retried). It is absent, never null, while the fee may still change, so a $0 under_collected without it is still being retried.
  • sdk, hooks: Self-custody Hyperliquid buys funded from the deposit wallet now sign their app-fee pre-sign. fillSelfCustody and useSelfCustodySigning held that pre-sign until a Polymarket order signature (poly-sign-) was submitted, so on a Hyperliquid order (hl-sign-) it was never signed. The fee then fell to a prompt after the fill, which often went unsigned. The new isOrderSignatureStep export recognises both order steps; isPolymarketOrderSignatureStep is unchanged.
  • sdk, hooks: Deprecate includeDirectVenueMarkets on listRecurringCryptoMarkets. The API now ignores it: every recurring crypto window is listed from sufwan-local-test's own market data before it opens, so the response is the same with or without it. The SDK still accepts and sends the option, and the recurring-markets hooks no longer set it.
  • hooks: useMarketChart keeps retrying a warming chart at the server's retry hint for up to about 30 seconds instead of giving up after 8 attempts, so a chart whose history is still filling renders as soon as the data lands instead of staying empty until the next refetch.
  • hooks, ui: Sports pages paint the first market page while later pages load, and price only the section on screen. Order books mount the rows around the spread. The first midpoint fetch for a visible set asks for every market that still has no price, and later polls ask only for cold books. Scrolling retries a midpoint request that was still in flight. The chart summary waits to load the deferred event description so that request does not compete with the first market and order-book reads.
  • hooks, ui: Selling a self-custody position from the positions table now works. The quick-sell button quotes against the wallet that holds the position and fills through fillSelfCustody with the AggWalletSignerProvider signer. Before, it always sent a managed sell and got insufficient_position. A row whose legs sit with different holders hides quick sell; sell it from the market page. Adds signerFor(walletSigner, address) to @agg-build/hooks, which connects the wallet if needed and returns the signer to pass as the SDK's per-call signer.

2026-10-24 · v4.30.0

  • Add redeemSelfCustody to redeem a resolved position held in a self-custody wallet. Limitless, Predict and Myriad prompt a transaction (the user pays gas); Polymarket prompts one typed-data signature. fillSelfCustody and redeemSelfCustody accept a per-call signer. Position legs carry signerAddress for self-custody holdings, and computeClosedPositionTotals returns signerAddressByOutcome. useRedeem().mutateAsync takes that map and redeems those outcomes from the wallet signer, one prompt at a time, and the rest through client.redeem. The Claim winnings button uses it, so self-custody winners can be claimed in the UI.
  • sdk: Add finalizedAt to fees.actual.appFee on orders: the time the app fee stopped changing (collected in full, skipped, or no longer retried). It is absent, never null, while the fee may still change, so a $0 under_collected without it is still being retried.
  • hooks, ui: Selling a self-custody position from the positions table now works. The quick-sell button quotes against the wallet that holds the position and fills through fillSelfCustody with the AggWalletSignerProvider signer. Before, it always sent a managed sell and got insufficient_position. A row whose legs sit with different holders hides quick sell; sell it from the market page. Adds signerFor(walletSigner, address) to @agg-build/hooks, which connects the wallet if needed and returns the signer to pass as the SDK's per-call signer.

2026-10-23 · v4.29.1

  • Deprecate includeDirectVenueMarkets on listRecurringCryptoMarkets. The API now ignores it: every recurring crypto window is listed from sufwan-local-test's own market data before it opens, so the response is the same with or without it. The SDK still accepts and sends the option, and the recurring-markets hooks no longer set it.
  • hooks: useMarketChart keeps retrying a warming chart at the server's retry hint for up to about 30 seconds instead of giving up after 8 attempts, so a chart whose history is still filling renders as soon as the data lands instead of staying empty until the next refetch.

2026-10-22 · v4.29.0

  • @agg-build/sdk exports the self-custody signing policy fillSelfCustody uses (orderSignatureRound, signatureTimeoutMs, userSignerAddresses, isPostFillFeeRequest, areOrdersFilled, isOptionalFeeRequest, isPolymarketOrderSignatureStep, isSameWalletAddress, withTimeout). useSelfCustodySigning now follows it too: it takes the fill's signingAddress, so an optional request the user's own wallet signs (a referral code) waits for the user instead of being dropped after 15 seconds, and with ordersFilled a declined post-fill sell fee no longer reports a failed fill. PlaceOrder passes both.

    @agg-build/hooks also exports isWalletUserRejection(error), the one check for a user declining a wallet prompt (EIP-1193 code 4001 or the usual wallet copy); @agg-build/auth and PlaceOrder use it. The open-order Cancel button is disabled, with the reason as its tooltip, on a Hyperliquid order placed from the user's own wallet, which can't be cancelled from this UI yet.

  • Self-custody trading UI (off by default: tradingCustody defaults to "managed"). The SDK types fundingAddresses, smart-route signingAddress/fundingAddresses and custodyWarnings, and disconnectAccount(provider, { providerAccountId }). Hooks add features.tradingCustody ("managed" default, "both", or "self-custody"; any other value is "managed") with getTradingCustodyMode / useTradingCustodyMode, features.enableMultiWalletAccountLink, AggWalletSignerProvider, useSelfCustodySigning, useLinkedWallets, and a @agg-build/hooks/self-custody entry with a wagmi + Solana signer. The UI adds a "Trade from" selector and "Pay from" list to PlaceOrder, a wallet line in the order progress view, a wallet-only "self-custody" mode (no Agg balance option; a "Link an Ethereum wallet to trade" prompt when none is linked; Agg balance, Deposit and Withdraw hidden in the connect button and profile page), and wallet Disconnect plus "ETH wallet" / "SOL wallet" Connect rows in the Profile modal (mirrored as Link rows in the Trade from menu); requestLinkWalletOpen(chain) asks for a wallet on that chain. Auth adds WalletLinkBridge, which links the wallet through the same SIWE/SIWS flow as sign-in (auth methods accept mode: "link"). SIWS now connects MetaMask's Solana wallet, which reports its account after connecting.

    Open-order rows now expose signerAddress, and PlaceOrder signs cancels of wallet orders with that wallet. Wallet fills use approveMode: "user_broadcast". useSelfCustodySigning skips a declined or unanswerable optional signature instead of failing the trade and holds an optional fee pre-sign until the Polymarket order signature is in, PlaceOrder keeps answering post-fill wallet requests (the exact sell fee) until the run is terminal, and useExecutionProgress returns isTerminal.

  • sdk: WebSocket hardening for trading and server-side use:

    • Server-side clients authenticate the WebSocket handshake with their API key: createAggClient({ apiKey, wsUrl }) on Node 22+ sends x-app-api-key, so backends no longer set an Origin. AggWebSocket accepts webSocketImpl and headers for other runtimes. The key is never sent from a browser.
    • User events carry an eventId. After a reconnect the SDK resumes from the last one, so fills, balance and withdrawal updates sent while the connection was down are replayed, with duplicates dropped. New onResumed callback: complete: false means reconcile over REST.
    • New subscribeAppEvents() streams every user event of the app to an API-key connection.
    • New onAuthExpiring / onAuthExpired callbacks. Clients created by AggClient refresh the token when the gateway warns, and re-authenticate the socket.
    • New onStreamInterrupted callback, plus connection_rejected and stream_interrupted diagnostics.
    • A socket held only for user events now reconnects after a drop. Reconnect backoff is jittered. Configuration errors (close codes 4001 and 4003) are no longer retried until connect() is called. Id lists are split into frames of at most 500.
    • WsError carries a machine-readable code (WsErrorCode) and the affected outcomeIds.
  • sdk: Declare @sinclair/typebox as a dependency. Published SDK declarations now import Static from TypeBox instead of bundling those types.

  • sdk: Self-custody fills (signingAddress) now execute Limitless market orders; the wallet signs the order, a one-time registration, and broadcasts missing Limitless approvals on Base.

    A wallet that already has its own Limitless account (made on limitless.exchange or through another app), or whose Limitless account is in smart-wallet trading mode, cannot be traded through a partner app. Self-custody quotes now route such wallets away from Limitless with a self_custody_limitless_foreign_profile or self_custody_limitless_smart_wallet warning, and fills refuse them with quote_self_custody_limitless_foreign_profile / quote_self_custody_limitless_smart_wallet before any funds move.

  • sdk: Add priceChange24h to venue market outcomes: the outcome's price change over the last 24 hours in absolute probability points (0.05 = up 5 cents). null means unknown, never zero.

  • sdk: Self-custody fills (signingAddress) now execute Predict and Myriad market orders. Predict asks the wallet for a personal_sign auth message and the EIP-712 order, and has the wallet broadcast missing USDT and conditional-token approvals on BNB; its buy minimum is $1 notional. Myriad asks for the EIP-712 order and has the wallet broadcast missing USD1 and conditional-token approvals on BNB. A wallet that is EIP-7702 delegated on BNB is asked to sign a revocation of that delegation before the order is posted. Both venues are market fills only: self-custody limit orders are refused and positions not created through sufwan-local-test are not offered for sale.

  • sdk: fillSelfCustody no longer throws when the user declines the exact post-fill sell-fee signature: the orders have already filled, so it returns the filled result and the fee stays uncollected.

  • sdk: Docs: the self-custody recipe gains a Solana section. Link a Solana wallet with provider: siws, name it in fundingAddresses (EVM hex or base58 entries are both accepted), and handle the solana_transaction signature request in the signer: its payload carries messageBase64 / transactionBase64; sign the unmodified message and return the hex-encoded 64-byte ed25519 signature. sufwan-local-test co-signs as fee payer, so the wallet needs no SOL. Novig and ProphetX clearinghouse self-custody orders are documented as not yet enabled (their direct backend is unaffected). No SDK code changed.

  • sdk: Self-custody fills (signingAddress) can now include Kalshi legs and Novig / ProphetX legs on the app's direct backend. These legs trade on the user's own connected account and cash, so they ask the wallet for no signature, and a route that mixes them with wallet-signed venues no longer fails to start. Kalshi needs the user's stored Kalshi API key; without it the fill is refused with quote_insufficient_balance. Direct Novig / ProphetX settlement is credited to the account that placed the order, so a user holding both a managed and a self-custody position on the same outcome is paid once. Self-custody limit orders, and sells of positions sufwan-local-test did not create, stay refused for these venues.

  • sdk: Self-custody fills (signingAddress) now execute TipRun market orders, buys and sells, funded from the wallet's Arbitrum USDC (bridged in when needed). A buy deposits only the shortfall into the wallet's TipRun account: cash already in that account is used first. TipRun account cash cannot be allocated as a separate funding source. The wallet signs an EIP-712 RegisterAccount only when it has no TipRun account yet (an existing one is adopted), an EIP-712 Permit for each buy that deposits, and the EIP-712 LimitOrder. TipRun self-custody limit orders and withdrawals are not supported; withdraw in TipRun's own app.

    A wallet that owns more than one TipRun account cannot be traded: self-custody quotes route it away from TipRun with a self_custody_tiprun_ambiguous_account warning, and fills refuse it with quote_self_custody_tiprun_ambiguous_account before any funds move.

  • sdk, hooks: The signed-in trade socket stays open through quoting, so a fill is not missed while the browser is still connecting. Sending an order reuses that socket and only confirms the user stream when that confirmation has not already been sent.

  • sdk: WsTrade frames carry an optional tradeId, the venue's own id for the fill (Hyperliquid today), so clients can drop duplicate trades reliably.

  • hooks, ui: Self-custody Hyperliquid trades now work from browser wallets such as MetaMask. Hyperliquid order signatures use chainId 1337, which browser wallets refuse to sign. Before the first Hyperliquid fill or limit order from a wallet, the wallet signs a one-time Hyperliquid approveAgent on its active chain. That creates an agent key named agg, valid for 90 days and stored in the browser's localStorage. The agent key then signs Hyperliquid orders locally, with no wallet prompt. It can trade the account but cannot move funds. useWagmiSolanaWalletSigner takes a new hyperliquidNetwork option ("mainnet" by default), and AggWalletSigner gains optional canSignAs and ensureHyperliquidAgent members. The trade's execution line shows "Enable trading on Hyperliquid (one-time signature)" while the approval is open. The API uses the agent only when HL_AGENT_SIGNING_ENABLED is on.

  • hooks: Chart history retries for a range the server reported as warming go straight to /charts/bars instead of re-reading the history manifest first, saving one round-trip before a cold chart range appears.

  • hooks, ui: Event and market views keep a single orderbook socket for each visible market. useMarketOrderbook({ live: false }) reads a parent-owned book and still REST-seeds it, without opening another subscription. Homepage event cards compare only their own midpoint rows.

  • hooks: Keep execution status reconciliation running when polling inputs change during an outstanding request, so completed trades can reach their final UI state after missed WebSocket events.

  • hooks: The demo starts the default homepage event list from its inline preload. useVenueEvents uses that response when the query string matches, and sends the normal request for every other query.

  • hooks: Preserve market traversal order when batching midpoint requests while keeping canonical query cache keys.

  • hooks, ui: Use order WebSocket frames as the fast path for Place Order confirmation, and keep status polling as the reconcile path. A submitted order's confirmation now survives navigation: Place Order restores it when its event is opened again (including after a reload in the same tab), and orders left mid-fill are polled in the background so their fill or failure toast still arrives.

  • hooks, ui: Open positions have a Sell button. It sells the position, and a toast reports when the order fills or when the sell cannot be submitted.

  • hooks, ui: Avoid fetching child categories for unrelated homepage tabs, share top-level category requests, and cache category results for five minutes across navigation, including empty responses.

  • ui: Probability charts fit the value axis to the data, draw evenly spaced lines that hold the last price, label each line's end with its name and price, and mark every line under the crosshair while dimming the time after it. LineChartSeries gains an optional endLabel.

    The event chart opens on the lines a reader expects first: the Yes side of each market, and the moneyline on sports events. No sides, spreads, totals and further markets stay listed and turn on from their pills; up to four markets are listed. Live lines end on the live price, every event chart opens on ALL, and ProphetX and Novig (no price history) no longer appear in charts.

  • ui: Left-align the BETDEX balance line under the order balance so it lines up with the "Trade from" balance row.

  • ui: Line charts start at the beginning of the selected range: before a market's first price the line is drawn flat at that price, as Kalshi does, instead of leaving the start of the chart empty.

  • ui: Collapsed event cards request live prices only for the two outcome rows that are visible, plus those markets' matched siblings. Scrolling that card loads prices for the remaining embedded markets.

  • ui: Event chart lines follow the API's market order (by chance on multi-option events) instead of re-ranking by volume.

  • ui: Event detail charts open on the 24H (1D) range instead of ALL. Resolved events still open on ALL, since their last 24 hours have nothing to show.

  • ui: The event chart picks its markets by the page's live prices instead of the API's stored order, ranking once per venue so lines do not swap as prices move. EventMarketsChart takes an optional prices map of each outcome's own venue price, or null while those load (no lines are drawn until then). The chart's hover tooltip, shown when more than four lines are on, now follows the crosshair instead of sitting fixed in the corner.

  • ui: Name venue setup for the venue on a bridged order, and label a buy's potential return "To win" instead of "Payout".

  • ui: Preserve quarter-line precision in sports line selectors and signed spread outcome labels.

  • ui: With event discovery URL persistence on, the home page no longer waits for GET /categories before rendering its first event list when the URL names no category or subcategory. The default "all" tab is known up front, so the first titles paint as soon as the list arrives.

  • ui: Switching markets reuses the image the event page already decoded, so the order ticket updates without a loading flash. A position opens that market on the sell side for the full size. Profile tabs stay in view while the position rows scroll. A subcategory click highlights that row before the homepage re-renders the event list.

  • ui: Order book depth bars animate with transform: scaleX instead of width, so live book updates no longer re-lay out every row. The bars look and animate the same.

  • ui: Keep market quotes and submission tied to the current buy/sell amount while input debouncing settles, and skip buy previews below the existing UI minimum.

  • ui: Smoother event list scrolling and search results fixes:

    • Event cards keep their lazy-market placeholder row still until a page of markets is actually loading, and the event list's pagination skeleton tiles stay still until the next page is fetching. Idle infinite pulses forced a main-thread layerize every frame and capped list scrolling at 60fps.
    • Order book rows only re-render when a level's price, size, total, venues or depth bar changed, instead of re-rendering the whole depth on every live update.
    • Full search results now use the scrollable venue strip, so the venue filters no longer overflow the header, and the category tabs stay visible so results can be filtered by category.

2026-10-20 · v4.28.2

  • Add priceChange24h to venue market outcomes: the outcome's price change over the last 24 hours in absolute probability points (0.05 = up 5 cents). null means unknown, never zero.

2026-10-17 · v4.28.1

  • useRecurringCryptoMarkets now loads a live recurring market's timeline in one request: the timeline query is bounded to the windows the pills render (two back, four ahead), and the viewed window is found inside it instead of by a separate lookup.

2026-10-16 · v4.28.0

  • sdk, hooks, ui: Search: the deep option on search() / useSearch is deprecated and no longer requests the server-side reranker. useMarketSearch submits and the home page full results now run a normal full search. The option still type-checks, so existing code keeps compiling; it will be removed in a future major version.
  • sdk: Clarify effective fill-price reporting in order responses. executionPrice is filled USD divided by received/sold shares, including fee effects already reflected in those quantities. Missing fill quantities produce null rather than a quoted-price fallback; quotedPriceRaw remains the separate quote reference.
  • sdk: Keep active WebSockets authenticated when sessions refresh, and reset private event delivery safely when accounts change.
  • sdk, hooks: handleAuthCallback() now throws AggIdentityExistsError (code: "identity_exists", existingMethod, attemptedMethod) when an app that refuses duplicate identities sends a sign-in back to the user's existing method. requestAggAuthChooserOpen() accepts an optional { highlightMethod }. UserProfile gains optional otherIdentities.
  • ui, auth: When an app refuses duplicate identities for a shared email, the sign-in chooser now reopens on the user's existing method with a short explanation, and after they sign in offers to add the method they first tried. The profile shows a notice when the user has another identity with the same email. Upgrade before enabling "Use existing sign-in for shared emails" in the admin dashboard.
  • ui: User profile: resolved positions in the Closed tab are no longer hidden by the small-position share filter.

2026-10-15 · v4.27.7

  • Venue-account balance rows from getManagedBalances carry registeredByAgg: true when sufwan-local-test registered the TipRun account for the user, false when sufwan-local-test reused an account the user already had.

2026-10-15 · v4.27.6

  • executeManaged accepts an optional externalId, your own idempotency key for the trade. It is saved on every order the quote splits into and carried on each trades.filled webhook, and reusing it on a different quote returns 409. getManagedBalances, getPositions, getExecutionOrders, getUserActivity and getDepositAddresses accept { signal } to cancel the request. Execution orders carry cancelReason, and the event detail carries settlementDiffStatus (computed, pending or not_applicable) to explain a null settlementDiff.

2026-10-15 · v4.27.5

  • getExecutionOrders accepts clientOrderId to look up a limit order by the id you placed it with — for example, after a retry returns 409. TradesFilledEvent now carries clientOrderId (a limit order's id, null otherwise).

2026-10-14 · v4.27.4

  • Keep active WebSockets authenticated when sessions refresh, and reset private event delivery safely when accounts change.

2026-10-14 · v4.27.3

  • Document that OrderbookState.marketId holds the outcome id, not a venue market id, and deprecate it in favour of outcomeId. WebSocket orderbook frames are keyed by outcome and carry no market id, so snapshotToOrderbook sets marketId to the outcome id. The same applies to useAggOrderbook: its marketId option and data.venueMarketId hold the outcome id. Use the REST venueMarketId or your own outcome → market mapping when you need the market. No behaviour change.
  • sdk: Document that SmartRouteResponse.positionAvailability is returned on every sell quote, ok included, not only on insufficient_position, and that it is a snapshot rather than a reservation.

2026-10-14 · v4.27.2

  • Document executionPriceRaw on order events as the fill's own price, filledAmountRaw / actualSharesRaw, rather than the quote. A sell's is net of fee; most venues report a buy's fee separately in actualVenueFeeRaw. On Hyperliquid the filled event still carries the quote; the realized price lands on the order shortly after.

2026-10-14 · v4.27.1

  • Sports event pages no longer render regulation-only books as a second Moneyline card. A market whose question names a 60-minute regulation window ("Dallas Stars To Win (60 Min)", "Draw (60 Min)") settles a regulation tie differently from the overtime-inclusive moneyline, so isBinarySportsMoneylineMarket now rejects it. Its win/draw/win legs group into one "Regulation Time (3-Way)" card with a pill per leg.

2026-10-13 · v4.27.0

  • sdk: Expose optional accepted price bounds and orderbook freshness metadata on smart-route quotes.

  • sdk: Submit order signatures before optional Polymarket buy fee pre-signs, and bound the optional fee prompt so a rejection or unanswered signer cannot discard the order.

  • sdk: Wait up to 55 seconds (was 15) for an optional Polymarket buy fee pre-sign, so a slow wallet's approval is used instead of being discarded and asked for again after the fill. The order is never held by it.

  • sdk: Recognize the ProphetX venue-rate-limit code and venue in public error response types. Preserve retry hints without automatically replaying execution requests.

  • Modernize package builds with tsdown, preserve CJS/ESM exports, and share the SDK's public type contracts across hooks. Add opt-in React Compiler output with a React 18-compatible runtime while validating applications on React 19.

  • sdk: Do not sign an optional Polymarket buy fee pre-sign until the fill's order signatures are submitted. A fee prompt listed before the order prompt no longer stops polling and holds the order for up to 55 seconds.

  • sdk: Sign an optional Polymarket buy fee pre-sign only after the Polymarket order signature itself is submitted. An auth or setup signature posted before the order prompt exists no longer unlocks it, so it cannot hold the order for up to 55 seconds.

  • sdk: Withdrawal status sources can carry an optional pendingReason (for example awaiting_venue_approval) while a venue-account source is submitted but not yet confirmed, so apps can show what the withdrawal is waiting on.

  • hooks, ui: New opt-in features.uncrossMergedOrderbook UI config flag (default false). When enabled, the merged cross-venue orderbook in MarketDetails and the chart orderbook nets locked or crossed levels — one venue's bid at or above another venue's ask — the way a single matching engine would, so the same price never appears on both sides and the spread is never negative. mergeVenueOutcomeOrderbooks takes the matching uncross option. Display only; quotes still route against the real venue books.

  • hooks: useSettlementDetailsData scopes its selected-market lookup by the market's own venueEventId, so /venue-markets returns the market's description and rules again. The settlement panel showed no rules when the lookup was by externalIdentifier alone.

  • hooks, ui: Background smart-route polls no longer block the BUY button. PlaceOrder used to swap the CTA for "Refreshing quotes…" on every poll; it now keeps the current quote usable and updates the route cards in place when the response changes. The CTA is still disabled while a quote for a new amount loads.

    useSmartRoute now reports loadingReason: "updating-route" when the previous amount's quote is shown as placeholder data. Before, it reported "refreshing-quotes" because TanStack Query v5 leaves isLoading false in that case.

  • ui: Pin only the highest-volume moneyline card on sports event pages; other moneyline-typed cards (e.g. a Tie leg) sort by total volume with the rest.

  • ui: Show the settled per-share price on resolved split-payout winners (for example a 50/50 tie paying 50¢) instead of a flat 100¢ in the profile positions list.

  • ui: Sort sports event market cards by total volume (every market in the card plus its matched cross-venue markets), keeping moneyline cards pinned on top, instead of a fixed moneyline → spread → total order.

  • ui: Always show the period heading on sports period tabs (Halves, Quarters, …), so a tab holding only one period still says which half or quarter its lines are for.

2026-10-06 · v4.26.1

  • Show settlement differences under the event chart again when the event page loads from the bootstrap payload. The summary now loads the deferred event details itself instead of reading the empty bootstrap value. useSettlementDetailsData accepts an omitted market for event-only loads.

2026-10-05 · v4.26.0

  • sdk: Leaderboard by category, league and event, with win rate. getLeaderboard() takes categoryIds (one category; a league is a category), venueEventIds (up to 5 events, merged across venues), userIds (up to 100 users) and now forwards the single-user lookups (userId, externalId, username, walletAddress). Rows gain wins, losses and winRate: wins and losses count settled position legs by the sign of their settlement P&L (a zero settlement, such as a void, is neither; a position sold before settlement counts toward P&L only), and winRate is wins / (wins + losses), null before any settlement. getLeaderboardSeries() takes categoryIds, makes the user optional (no user = the whole app's daily totals), and returns wins, losses, winRate, cumulativeWinRate per point plus range totals; userId is null for an app-wide series. getPositions() / getExecutionPositions() take categoryIds and sort: "realizedPnl", and each position outcome carries realizedPnl.

  • BatchOrderbooksResponse gains optional sizeStepRaw and minSizeRaw (6-decimal share units) next to tickSize on each venueOrderbooks entry, each matchedMarkets item and requestedMarket: Polymarket 0.01 share, Hyperliquid whole shares, PRED 0.001 share and TipRun 0.01 share, plus the market's minimum order size. Other venues omit both.

    useLimitOrderVenueQuotes now returns each venue's sizeStepRaw and minSizeRaw from that orderbook metadata, plus isMetadataPending, and resolveLimitOrderSizeRulesByVenue is exported.

    The PlaceOrder limit form takes its size step and minimum from the market's orderbook metadata instead of the per-venue quote, so they no longer depend on the entered amount or on a quote resolving. Submit on a venue with a size step waits only for that metadata to load. Venues with no step keep the same sizing as before.

  • sdk, ui: SmartRouteResponse gains optional sizeStepRaw and minSizeRaw (6-decimal share units). A quote restricted to one venue (allowedVenues of length 1) returns that venue's limit-order size step and minimum: TipRun from the contract, Hyperliquid whole shares, PRED 0.001 share, and Polymarket 0.01 share plus the market minimum. Other venues omit both.

    The PlaceOrder limit form floors the order size to that step, so a dollar-based limit buy never spends more than you enter, marks a venue whose minimum the amount misses as unavailable so another venue is picked, and blocks submit when every venue is below its minimum. Venues with no step keep the same sizing as before.

  • sdk: Add a per-outcome orderbook watchdog to AggWebSocket. Sockets from AggClient.createWebSocket() now check, once a minute, any subscribed book with no update for 5 minutes against GET /orderbooks/outcomes. If the server's seq matches, the market is just quiet and nothing happens. If it differs, the SDK requests a resnapshot; if the next check still differs, it unsubscribes and resubscribes that outcome. A book whose snapshot never arrives, or whose recovery stalls, is retried the same way. Outcomes the server can't serve are counted as checked, so a large subscription set rotates through every book. Each check costs one market-data quota unit per silent outcome (at most 100 per call), is abandoned after 15 seconds, and pauses for Retry-After on a 429. New diagnostics: book_watchdog_resync and book_watchdog_error.

    On by default for createWebSocket(). Pass { bookWatchdog: false } to turn it off, or { bookWatchdog: { silenceMs, checkIntervalMs } } to tune it. A directly constructed AggWebSocket enables it with the new bookWatchdog option.

    AggWebSocket.authenticate(token) now also stores the token for reconnects, so a socket that reconnects after a token refresh authenticates as the user instead of dropping to app level with the old token.

  • sdk, ui: Sell quotes report estimatedNetProceeds, net of partner app and referral fees; the place-order proceeds use it.

  • hooks, ui: Limit-order tick and size rules are now fetched for every venue's own market, not only the selected one, so matched venues such as TipRun round sizes to their own step.

2026-10-01 · v4.25.0

  • Add referralActivationSponsorAddress to getSmartRoute() and placeOrder(). On a user-paid referral routed to a self-custody Hyperliquid buy, the named wallet pays Hyperliquid's one-time 1 USDC activation fee for a referrer that has never received anything on HyperCore. Before the fill your signer receives an optional hl_activation_sponsor request with that wallet as signerAddress; declining it never affects the trade. The quote's referral echoes it as activationSponsorAddress.

    fillSelfCustody() now asks your signer for required requests before optional ones and still submits everything in one batch. An optional request signed by a wallet other than the user's (e.g. a partner's activation sponsor) is bounded by OPTIONAL_SIGNATURE_TIMEOUT_MS (15 s, exported); after that it is declined and the required signatures go out without it, so a hung sponsor signer can delay the order's signature by at most that long. Optional requests the user's own wallet signs (such as an hl_referral prompt) are awaited as before.

2026-10-01 · v4.24.0

  • sdk: Add getLeaderboardSeries() for GET /leaderboard/series: one user's leaderboard stats (orders, volume, spent, realized P&L) per UTC day over a range (default 30 days, max 366), with a running cumulative realized P&L, ready to chart. Look the user up by exactly one of userId, externalId, username or walletAddress. P&L is gross of venue and app fees; unrealized P&L is not included.

  • Add an opt-in minVolume filter. getVenueEvents, getVenueMarkets, getVenueEventById and getEventBootstrap accept minVolume (USD lifetime volume floor); useVenueEvents, useVenueMarkets, useVenueEvent and useEnrichedVenueEvent forward it; set config.market.minVolume on AggProvider to hide events and markets below the floor across the events list, event cards, the event page and the sports event page. Unset by default — no behavior change.

    useVenueMarkets' React Query key gains a trailing minVolume slot (now 17 elements), and useVenueEvent's key gains one too (now ["venue-event", eventId, cluster, minVolume]); code that hand-builds either key for setQueryData/getQueryData must append it.

  • sdk: Add per-trade venue-specific fees: appFeeBipsByVenue and referrerFeeBipsByVenue on getSmartRoute and placeOrder, with the per-venue rates reported on the quote's fee breakdown.

2026-10-01 · v4.23.0

  • Add Novig Direct account connections, per-organization execution mode, and native-cash trading in the shared order ticket. Users connect a Novig v3 trading key for a funded subaccount; wallet and clearinghouse funds remain separate.
  • ui: Show the shares a BETDEX runner holds when its market is folded under another venue's Yes/No market, so Sell works from that page. In Manage balances, venue badges wrap instead of overlapping the refill status when a network lists four venues.
  • ui: List BETDEX with the other Solana venues in Manage balances, and drop its separate row from the Balance card.

2026-09-30 · v4.22.6

  • Sports spread and combined-leg outcome pills now take their price and venue logo from the same cross-venue selector as every other market pill (live best price first, then REST), instead of a fixed REST midpoint paired with the anchor venue's logo. A spread pill could previously show a price no venue offered.

2026-09-30 · v4.22.5

  • Sports spread/total cards rank lines by the volume of the whole matched cluster (every venue offering the line), not just the anchor venue's market, when choosing the default line.

2026-09-30 · v4.22.4

  • Sports spread/total cards now open on the main line. Among lines priced within 30–70¢ the highest-volume one is the default; with none in that band, the line closest to 50/50 is. The card follows that default once live midpoints load, unless the user or URL already picked a line.

2026-09-30 · v4.22.3

  • Order and fee types now carry field descriptions and exact value sets: order status is typed with every order status, fees.venueFeeBasis, fees.actual.appFee.status and fees.actual.referral.status are typed unions, and money fields document their units. No runtime behavior changes.
  • Clarify redeemStatus and redeem leg ineligible: it now covers outcomes that lost, venues that settle on their own, and self-custody positions claimed on the venue. Documentation only; no runtime behavior changes.

2026-09-30 · v4.22.2

  • Sports spread/total cards now open on the main line. When line volumes tie (most lines report zero volume), the rail defaults to the line priced closest to 50/50 instead of the lowest rung, and follows it once live midpoints load unless the user or URL already picked a line.

2026-09-29 · v4.22.1

  • WsResolvedOutcome (the market_resolved stream event) now carries optional payoutPerShare and refundAtCost, so a voided or split market (e.g. payoutPerShare: 0.5 on every side) can be told apart from a $1 win.

2026-09-29 · v4.22.0

  • Add the trades.fee_reconciled webhook event type (TradesFeeReconciledEvent). It fires once per self-custody trade when the app fee reaches its final state, a few seconds after trades.filled, with the full fee breakdown and transaction hashes.

2026-09-29 · v4.21.2

  • SignatureRequest gains optional. fillSelfCustody now skips an optional request (such as a Hyperliquid partner referral, purpose: "hl_referral") when your signer throws or it has expired, instead of failing the fill; required requests behave exactly as before.

2026-09-29 · v4.21.1

  • Restore compact venue badges and outcome pills on event comparison charts. Show only the three highest-volume markets for the selected venue, keeping both Yes/No histories available and allowing each to be hidden independently. Remove the series pager and bound chart requests and live subscriptions to visible outcomes in those three markets.
  • ui: Show a single-market 1X2 result (team / Draw / team) as the Moneyline card once it anchors the event's matched moneyline legs.

2026-09-28 · v4.21.0

  • Use Trading access for BETDEX configuration and the normal wallet transfer flow for funding. Show real venue prices in Smart Routing before funding. The Buy action prepares the user's owned account, journals an idempotent transfer, waits for native credit and obtains a fresh executable quote within the original spend and slippage.

  • hooks, ui: Replace the event list venue tabs with a logo strip. Matched · All stay as pills, then each venue is a 28px logo tile that names itself on hover and when active. Venues that do not fit the width the strip is given fold into a +N chip that lists the folded venues; the active venue always stays in the strip. On phones the strip spans the row and Matched · All shrink to glyph tiles. The strip is exported as EventListVenueStrip, and a deep-linked venue is no longer reset while /app/config is loading.

    Hooks adds the eventList.moreVenuesAria and eventList.moreVenuesListAria labels.

  • ui: Submit executable BETDEX quotes through the normal managed order API. The server prepares any required wallet transfer or bridge, confirms credit in the user's owned account, and executes the accepted order; the Buy button no longer runs a separate client funding sequence.

2026-09-27 · v4.20.1

  • Support self-custody post-fill fee authorization operations for HyperCore and Polymarket wallets, including user-paid referrals, while polling through fee completion.

2026-09-26 · v4.20.0

  • Add appFeeBips to getSmartRoute and placeOrder: a per-trade app fee (0–10000 bips) that replaces the app's configured rate. Honoured only on requests authenticated with an app API key.

2026-09-25 · v4.19.3

  • Outcomes now include refundAtCost.

    When a venue voids a market and returns every holder their stake (ProphetX), both outcomes carry winner: true, payoutPerShare: null and refundAtCost: true. The refund per share is the holder's own average entry price. Check refundAtCost before payoutPerShare and the winner ? 1/winnerCount : 0 fallback.

  • skipBalance on placeOrder no longer requires skipQuote. Without skipQuote it keeps the smart route but skips the quote-time balance refresh, so the route uses the last stored balances; the fund hold and balance check still run. Requires a read_write API key.

2026-09-25 · v4.19.2

  • Venue markets and their matched cross-venue siblings now include volume24hr, the rolling 24-hour USD volume reported by each venue. A value of 0 can mean the venue does not report a 24h figure.

2026-09-25 · v4.19.1

  • Use Trading access for BETDEX configuration and the normal wallet transfer flow for funding. Show real venue prices in Smart Routing before funding. The Buy action prepares the user's owned account, journals an idempotent transfer, waits for native credit and obtains a fresh executable quote within the original spend and slippage.
  • ui: Submit executable BETDEX quotes through the normal managed order API. The server prepares any required wallet transfer or bridge, confirms credit in the user's owned account, and executes the accepted order; the Buy button no longer runs a separate client funding sequence.

2026-09-25 · v4.19.0

  • sdk: Add cancelVenueOrder() to cancel a Polymarket or Hyperliquid limit order a linked self-custody wallet placed outside sufwan-local-test. cancelManagedOrder() now calls POST /execution/orders/cancel; POST /execution/orders/{orderId}/cancel is deprecated.
  • sdk: Add the deposits.confirmed webhook event type (DepositsConfirmedEvent) to the server-side webhook types.
  • Add the trader leaderboard
    • client.getLeaderboard(params?) calls GET /leaderboard: traders ranked by realized P&L or volume over an inclusive UTC day range, cursor-paginated, rank exact within the top 100.
    • useLeaderboard({ period, metric, limit }) wraps it with the 1d | 7d | 30d | all presets and flattens pages; leaderboardRange(period) is exported for custom UIs.
    • LeaderboardPage (@agg-build/ui/pages) renders the board with period and rank-by controls, a highlighted "You" row, and identity fields gated by the app's leaderboardIdentity setting. New labels.leaderboard block for copy overrides.

2026-09-24 · v4.18.2

  • Show BETDEX prices, history and artwork: re-poll incomplete midpoint responses instead of caching them for the session, seed BETDEX outcome books on market cards, start the initial chart range at market creation when the start date is a future kickoff, explain missing BETDEX hosted cash instead of reporting no orderbooks, and show the venue mark when an event or market has no image.
  • ui: Show existing pre-kickoff history and selected native outcome prices in sports pages, and use venue artwork for an image-less order ticket.

2026-09-24 · v4.18.1

  • Add a BETDEX venue tab to the event list. Like other opt-in venues, it stays hidden for apps that disable BETDEX, and while app config is still loading.
  • Show BETDEX and Tip Run in the default event venue selector when the app enables them.

2026-09-24 · v4.18.0

  • Add explicit BETDEX hosted-account setup, account status and venue-held USDC balances. Hosted balances remain separate from signing-wallet balances. The profile uses the existing labels and UI primitives, distinguishes sandbox funds, and disables custody in paper mode. BETDEX remains disabled by default.

    Add owned BETDEX account activity to the API, SDK, hooks and expandable profile view. Exact signed ledger entries retain their native precision and distinguish credits, transfers, payouts, refunds and commissions. They are not presented as spendable cash, per-order fees or withdrawal-completion evidence.

    Describe BETDEX's published fee as 1% of net market winnings and explain its settlement timing in quote previews. Actual per-order charges remain unknown until supported by authoritative settlement evidence.

  • Register and activate the BETDEX venue identifier, labels and native logo. BETDEX remains disabled by default for existing apps.

  • Add an optional native Worker browser adapter and framework-independent market-data engine. The demo runs the shared socket protocol, incremental orderbook checksums, candle accumulation, and chart history decoding off the main thread with bounded delivery. Hooks use per-outcome external stores, websocket-first snapshot selection and one abortable batch fallback. SDK browser, Node and React Native defaults remain compatible; browser-only APIs use the separate @agg-build/sdk/browser entry point.

    Lean bootstrap settlement details load for the selected visible market only, with centralized loading/error/retry states and request cancellation.

    Market-data reads now have scoped rate budgets and expose server retry hints through AggApiError.retryAfterMs. Search and chart hooks honor those hints without adding automatic trading retries. Public API guides document the endpoint contracts, quotas and Worker lifecycle.

  • Show a user's open limit orders on the event page. A new card between the event hero and the markets list lists every resting limit order on the markets on screen — the whole matched cross-venue cluster — with its limit price, live mark, fill progress, total and expiration, and cancels in place. The card is gated on the showLimitOrders feature flag and hidden in paper mode, matching the trade panel and the profile, and states a failed order query instead of rendering as empty. Adds eventMarketPage.openOrders labels and an openOrders entry to EventMarketPageClassNames.

    getExecutionOrders and useExecutionOrders accept venueMarketIds to scope the order list to a set of venue markets. New useExecutionOrdersForMarkets takes any number of markets, batches them 75 per request to keep URLs short (as getMidpoints does) and merges the results. The event page uses it, so the card loads with one request for nearly every event instead of one per open status.

  • Support organization-configured ProphetX Direct accounts alongside clearinghouse execution. Add the ProphetX connection modal, isolated venue cash balances, execution-backend metadata, and cancellation events. Direct accounts use their own ProphetX funds and do not support funding transfers through sufwan-local-test.

    Direct positions settle on ProphetX; native selling is not exposed by the current integration.

  • sdk, hooks: getManagedBalances and useManagedBalances accept custody: "self".

    It returns cash held by the wallets linked to the account — plus their Polymarket deposit and legacy Safe/proxy wallets — instead of the managed wallet. Each chain row then carries address and custodyKind (EOA, DEPOSIT_WALLET or POLYMARKET_LEGACY, with legacyKind naming the legacy shape). Omitting custody returns exactly what it did before.

  • Represent native venue collateral with custodyKind: "VENUE_ACCOUNT" and sourceVenueAccountId / sourceExternalAccountId on smart-route allocations, including solo quotes. Native IDs remain strings; these are account claims, not EVM addresses. Existing EOA and deposit-wallet allocation shapes remain compatible. The SDK also exposes the existing optional sourceAddress field.

    Execution previews do not show native TipRun cash as an on-chain bridge merely because another venue uses the same source chain. This contract change does not enable TipRun or publish/deploy a live integration.

    Register Tip Run in the venue enum, official logo and consumer labels. Preserve native account identity/freshness on managed cash, positions and withdrawal responses, with a separate native-cash row in balance management. Quote refresh keys ignore observation-clock-only changes so balance polling does not abort an otherwise unchanged route. Existing EOA and other venue behavior is retained.

  • sdk: Self-custody fills that include a Kalshi leg are now admitted. Kalshi is funded from the user's own venue cash and signed with the user's own API key, so a mixed Polymarket + Kalshi route with signingAddress no longer returns quote_self_custody_unsupported_venue. A self-custody redeem-only fill on Kalshi now returns quote_self_custody_redeem_unsupported instead of quote_self_custody_unsupported_venue (both terminal).

    On apps that charge a partner fee (or user-paid referral), a Kalshi-only self-custody fill is still refused with quote_self_custody_app_fee — there is no funding batch for the fee to ride; mixed routes with an EVM leg collect the whole-quote fee there and are fine.

  • hooks, ui: Add owned BETDEX deposit and withdrawal controls, with exact USDC amounts, persistent withdrawal intents and status recovery after reload.

  • hooks, ui: Add a probability chart presentation with native historical samples, precise percentage labels, real aggregated orderbook liquidity, and optional multi-outcome overlays. Both sides keep independent histories and can be hidden through the chart legend.

    Short recurring contracts use their own opening and closing times. Their live edges advance with the clock between quote updates and stop at close, without writing synthetic observations to the history cache. Existing streaming and candlestick presentations remain available, and partner applications can opt into the event overlay with defaultChartView="all".

  • hooks: Cancel obsolete event-list requests when filters change or the last consumer leaves. Avoid restoring pagination while disabled, another request is pending, or a page has failed, while preserving completed cached pages and shared consumers.

  • hooks, ui: Show BETDEX hosted cash in the trade panel, check route funding against the correct account, and open BETDEX account controls for native funding needs.

  • hooks, ui: Limit orders get the same submitting and success states as market orders: a progress view with a "<venue> limit order" row while the order is sent, and a success view that shows the order amount at its limit price and the payout if it fills, with no "Better" line. Adds the orderAmount, orderLimitPrice, limitVenueOrder, limitOrderSubmitting and limitOrderSubmitted trading labels.

  • hooks, ui: Serve 2x image variants to high-density screens so event and market thumbnails render sharp, falling back to the 1x variant for assets that do not have one yet.

  • hooks, ui: Keep visible market-card prices live within a bounded subscription budget, retaining selected and expanded cards when offscreen and releasing other subscriptions on viewport exit or cleanup. Preserve explicit parent-owned streams and standalone live-card behavior. Scope live selectors to the relevant market cluster.

    Honor disabled market queries in eager pagination so a bootstrap-seeded progressive event does not silently fetch every remaining page. Add a 150-market Storybook state and public performance notes. Public hook signatures and REST/WebSocket contracts remain unchanged.

    Bound the all-markets comparison chart to eight active outcome series with accessible pagination and a retained selection. Hidden and off-page lines release history observers and live subscriptions; usable series render while other series warm. Centralized labels and a 150-market comparison story document the controls.

  • ui: Event list design QA: 40px venue tab bar and sort select, skeleton tiles instead of dimmed results while filters refetch, venue and sort reset on category change, category folders that collapse when the path is controlled, and footer venue logos that reveal their colours on hover.

  • ui: Use BETDEX outcome titles in order-book headers and last-price labels while preserving native routing identities.

  • ui: Loading skeletons no longer blend together: the event hero's chart block is inset like the loaded plot (so it no longer fuses with the toolbar pills above it), and the order panel shows a skeleton block instead of a literal "$0" amount.

  • ui: Match loading skeletons to the components they stand in for (event tiles, event list header and sidebar, event hero, market rows, order panel, settlement, search results, profile positions) so content no longer shifts when it loads.

  • ui: Keep comparison chart legend widths stable while history loads, prices update, or series become hidden/unavailable. Reserve the configured status text width without adding it to accessible names, and keep long labels within narrow viewports.

2026-09-23 · v4.17.4

  • Add totalCostIncFees to VenueSoloQuote

    Each single-venue quote now carries totalCostIncFees: the router's total cost plus that venue's own app fee and user-paid referral fee — the same composition as the route's totalCostIncFees.

2026-09-22 · v4.17.3

  • Positions whose shares Agg has no purchase record for — bought directly on Polymarket or Hyperliquid, or transferred in — no longer render a fabricated entry price and profit. When the venue has no acquisition price we can reconcile, the API marks the outcome costBasisUnknown and the profile now shows "—" for entry price, amount traded and P&L instead of 0¢ and a 100% gain. Quantity and current value are known and still shown.

2026-09-21 · v4.17.2

  • Show the Kalshi balance as a row inside the profile Balance card instead of a separate card, and remove it from the header and profile menu. The kalshiBalance prop on AuthConnectButtonView is removed, and the default userProfile.balance.availableBalance label is now "Balance".

  • hooks, ui: Reworked the Connect Kalshi modal to match the design. It now opens scrolled to its header (the API Key ID input is focused explicitly rather than racing Radix for it), renders the Kalshi logo at 60px on desktop and 40px inline with the title on mobile, and drops the non-interactive "Having trouble?" help links. Below md it lays out as a bottom sheet with left-aligned copy and a sticky Cancel / Verify Connection footer; the success confirmation uses the narrower 480px card with a tinted success mark and a secondary Done action.

    Modal.Container accepts an onOpenAutoFocus handler so a modal can place initial focus itself instead of competing with Radix's default. The now-unused onboarding.connectKalshiModal.help labels have been removed.

  • hooks, ui: When the user has no Kalshi account connected, Smart Routing now selects the next best route instead of Kalshi, and the Kalshi route card shows a warning icon with "Requires Kalshi account". Kalshi stays selectable; choosing it still prompts the user to connect, and that button now reads "Connect Kalshi to trade".

  • hooks, ui: Show the side and the live price on open limit orders. The outcome pill now reads "Buy Yes" / "Sell Yes" instead of the bare outcome, and the limit price carries the current market price beneath it ("now 25¢"). The mark is matched at the outcome level from GET /midpoints, so the two sides of one market show their own prices; an order whose outcome has no live book keeps the limit price alone rather than showing a guessed mark.

  • ui: Honor the Closed bucket for resolved Kalshi winners and display their Won result on desktop and mobile rows.

  • ui: Settlement: list every cluster venue on each key difference and under Full rules by venue, drop differences whose venues all say the same thing, stop repeating the question in the body when the description restates it, hide the cross-venue differences section for single-venue clusters, and scope the venue-rule hover state to its toggle.

  • ui: Remove the average-price liquidity warning banner from the order panel while preserving regional restrictions, fee disclosures, and footer spacing.

2026-09-18 · v4.17.1

  • Ignore funding fields with skipQuote

    allowedSourceChainIds and chainBalances are now ignored when skipQuote is set, instead of returning 400. A skipQuote order is always funded on the venue's own chain.

2026-09-18 · v4.17.0

  • Support PT30M and PT4H recurring crypto cadences

    RecurringCryptoDuration now includes PT30M and PT4H alongside the existing PT5M, PT15M, PT1H, and P1D. The API can now return recurring crypto up/down markets on these cadences (Hyperliquid sub-daily windows), and the SDK, hooks (slug/aggKey parsing, canonical window polling), and UI (recurring cadence labels) recognize them end to end.

    RECURRENCE_CADENCES — the cadence list behind the recurrence filter on listVenueEvents / useVenueEvents — gains the same two entries, so filtering venue events by them type-checks.

  • sdk: Add skipQuote, skipBalance and price to PlaceOrderParams

    POST /execution/orders can now skip the smart-route solver (skipQuote) and, on buys, the balance check (skipBalance) to send one order straight to predict or hyperliquid. price sets the trade price for a skipQuote order. These optional fields let a TypeScript partner pass them without a cast.

  • sdk: Document skipQuote on novig and prophetx

    PlaceOrderBody.skipQuote now lists novig and prophetx alongside predict and hyperliquid. price is ignored on prophetx, which prices the order itself within slipCapBps.

2026-09-16 · v4.16.0

  • Expose minOrderSize and tickSize on venue markets

    Limit orders are placed without a quote, so nothing validated size or price before the request. GET /venue-markets (and the markets embedded in venue events) now carry minOrderSize and tickSize.

    minOrderSize is populated for Polymarket (the market's own floor, the exact value POST /execution/limit-orders validates sizeRaw against) and PRED (0.001). Elsewhere it is null, which means no value was ingested — not that the market has no minimum; other venues enforce floors this field does not carry.

    Align limitPriceRaw to the runtime-inferred tick from the orderbook endpoints where one is available; tickSize is the ingestion-time seed and the live grid can be finer.

2026-09-16 · v4.15.2

  • Expose source-chain restrictions and per-chain funding budgets on direct order requests.

2026-09-15 · v4.15.1

  • Discovery outcomes now include payoutPerShare.

    Events, markets, and outcomes responses return each outcome's settled per-share payout alongside winner. It is set when a market pays more than one outcome — a Polymarket 50/50 pays 0.5 on each side, a Novig void refunds each side at its price — and is null otherwise, in which case winner ? 1/winnerCount : 0 still applies.

2026-09-15 · v4.15.0

  • sdk: placeLimitOrder accepts fundingAddresses for self-custody limit buys.

    Pass other wallets linked to the same account alongside signingAddress. Their balances can fund the buy by bridging in, the same way a self-custody market fill funds from several wallets. On the venue's own chain only the signing wallet's balance is used.

  • ui: Paper-mode and card interaction fixes.

    • PlaceOrder no longer asks a user to connect Kalshi when the app is in paper mode. Paper fills are simulated and never use venue keys, matching how the shared KYC prompt is already skipped for paper orders.
    • EventListItemDetails is only clickable when an onClick is passed. It previously always attached a click handler, so the event hero showed a pointer cursor with nothing to click.
    • Card only lifts on hover (hover:shadow-agg-card-hover) when it is interactive (onClick or href). Static cards keep their resting shadow.

2026-09-15 · v4.14.0

  • Add label and showWhenAuthenticated props to PaperTradingBadge.

    label overrides the pill text for one placement (defaults to labels.paperTrading.badge). showWhenAuthenticated keeps the mobile brand pill visible for signed-in users, which is hidden by default because the full label does not fit beside the wallet chip. Both are optional; existing usage renders exactly as before.

2026-09-14 · v4.13.2

  • Group order book rows to the nearest 0.1¢.

    Venues that quote finer than 0.1¢ (Hyperliquid, PRED, ProphetX) produced several order book rows that all displayed the same price, e.g. three "8.5¢" rows. Levels are now grouped to the nearest 0.1¢ — the precision the order book renders — with shares and venue breakdown summed into one row, including across venues.

    Total still sums each level's real price × size, and spread and midpoint use the real best bid and ask. This is display-only: quotes and execution use the ungrouped venue books.

  • Show ProphetX and Novig in the profile modal's Trading access tab.

    The tab lists venues from a fixed set that did not include ProphetX or Novig, so enabling either venue for an app never added its row. Both now appear alongside the other venues and follow the same app-config and geo-block rules.

2026-09-14 · v4.13.1

  • Type client.verify() for Privy sign-in.

    client.verify({ kind: "privy", privyToken }) already worked at runtime, but AggAuthVerifyBody only allowed { message, signature }, so TypeScript callers got a compile error. AggAuthVerifyBody is now a union matching POST /auth/verify:

    // wallet (SIWE / SIWS) — unchanged, `kind` may be omitted
    { message, signature, earlyAccessCode? }
    
    // privy
    { kind: "privy", privyToken, earlyAccessCode? }

    The two branches are also exported as AggAuthWalletVerifyBody and AggAuthPrivyVerifyBody. Existing wallet callers need no changes.

2026-09-14 · v4.13.0

  • Accept a Privy access token at POST /auth/verify.

    Partner apps that use Privy as their wallet provider can let users sign in with a token minted by that same Privy app, alongside the existing OAuth, email, and wallet-signature methods. There is no separate Auth-tab setting: saving Privy under Settings → Wallet Provider turns it on.

    VerifyBody becomes a union discriminated on kind:

    // wallet — unchanged, and `kind` may be omitted
    { message, signature, earlyAccessCode? }
    
    // privy
    { kind: "privy", privyToken, earlyAccessCode? }

    Existing callers keep working. Every wallet caller predates the discriminator, so kind is optional on that branch and existing payloads still validate. Code that reads body.message off a VerifyBody value without narrowing will need a kind check.

    This extends the existing endpoint rather than adding a new one: how identity is proven is the only thing that differs between the two. Principal creation, the early-access gate, user creation, and token issuing are shared, so the branch lives in the auth strategy and the route is unchanged.

    Privy auth stores no extra configuration: access tokens are ES256 JWTs verified against Privy's published public keys, using the Privy app ID from the wallet provider credentials. Every failure is a 401 with a message.

2026-09-14 · v4.12.0

  • Expose the linked signing wallet and signing quote ID for self-custody limit orders, and accept a fresh cancellation signature without changing existing managed-order calls.

    Support Hyperliquid GTC/ALO self-custody limit buys and sells, with client-held EOA or agent signing and signed cancellation payloads.

2026-09-12 · v4.11.3

  • sdk: Add settlementFee to smart-route buy fills and document that estimatedPayout is net of it. settlementFee is what the venue takes from a leg's $1 payout if the outcome wins — today only Hyperliquid HIP-4's close fee — and is not a cost at fill.
  • ui: Show and rank net payout in the place-order panel: buy routes use estimated payout after settlement fees, and sell routes show proceeds after fill fees. Keep solo routes from distinct outcomes on the same venue visible.

2026-09-11 · v4.11.2

  • Document SmartRouteBridgeStep.amount and SmartRouteBridgeStep.cost. cost is the delivery margin sent on top of the route's allocations, not the expected bridge fee — read feeBreakdown.bridgeFees for that.

2026-09-10 · v4.11.1

  • Fee breakdown: fold the builder fee into the Venue row instead of listing it separately, and render a partner's non-zero app fee as a dollar amount on the agg.market row. The 0% fee badge and the 0% platform fees trust line now appear only when the app fee really is zero, and the app fee is counted in the estimated-fees total.

2026-09-10 · v4.11.0

  • sdk, hooks: Remove the categoryIds filter from getVenueMarkets / useVenueMarkets. Category is an event-level attribute, so filtering markets by it was slow on large categories and is better expressed as an event filter. Filter events by category instead — getVenueEvents({ categoryIds, status }) — then fetch that event's markets with getVenueMarkets({ venueEventId }).
  • Report the builder fee as its own line in the quote fee breakdown. It was previously folded into venueFees, which labelled a partner's own charge as the venue's. Fee totals are unchanged; the builder slice is now visible separately on the route breakdown, on each per-venue breakdown, and on every fill. Also surfaces the partner app fee on quotes that are not executable and on per-venue quotes, where it was reserved from the budget but never reported.

2026-09-10 · v4.10.0

  • Add PRED (pred.app) as the distinct pred venue across SDK request and response types. PRED is unrelated to Predict.fun (predict) and settles through a per-user Safe on Base.

    PRED custody uses the existing custodyKind: "DEPOSIT_WALLET" discriminator, qualified by venue: "pred" and its custody address. Existing venues' source shapes remain unchanged. Hooks and UI add the corresponding PRED labels, filters and logo.

    PRED is default-hidden for existing and newly created apps until an app explicitly removes pred from its disabledVenues list.

2026-09-09 · v4.9.2

  • Use Clearinghouse executable prices and normalized amounts on ProphetX tickets. Preserve the reviewed spend and output limits during refresh, and show quote failures before order execution starts.

2026-09-08 · v4.9.1

  • Verify that ProphetX fixed-input quotes show no execution-reserve amount or held-funds tooltip. ProphetX quote sizing uses the available book without a speculative commission or slippage reserve; other venues retain their existing behavior.

2026-09-08 · v4.9.0

  • sdk, hooks: App config is now cached per App and refreshed on a bounded schedule.

    AggClient exposes a public appId getter, and useAppConfig / useCachedAppConfig key their React Query entry as ["agg", "app-config", appId] instead of the previously global ["agg", "app-config"]. Two clients for different Apps sharing one QueryClient no longer read each other's config — which matters now that the response carries the app-level tradingMode that every execution surface renders from. Admin-key-only clients get a "__no-app__" scope. Use the exported appConfigQueryKey(appId) to seed or invalidate the entry yourself.

    The query keeps its existing 5-minute staleTime and adds a matching refetchInterval, so a tab left open on one screen still picks up an App being flipped between paper and live instead of needing a reload. Nothing else about its refresh behaviour changes: refetchOnWindowFocus and refetchOnReconnect are left at the host QueryClient's settings rather than forced.

    The interval is skipped entirely while a tab is unfocused, so a background tab costs nothing. A stale tradingMode is cosmetic in any case — the API resolves mode from the App on every request, so a client holding the old value renders the wrong chrome but cannot execute in the wrong mode.

  • Integrate shared clearinghouse KYC for Novig and ProphetX using authoritative wallet status, skip repeat verification for approved wallets, and remove the Kalshi v2 KYC prompt while retaining account connection. Add user-scoped status queries, pending-decision handling, and protection against duplicate or stale verification actions.

  • Separate clearing-house execution reserves from estimated quote fees. Preview-only slippage tolerance no longer inflates Novig or ProphetX funding reserves; actual execution reserves and spend-cap checks remain enforced. Quotes expose an optional executionReserve amount and the trading panel explains it separately from fees.

  • sdk: Mark ProphetX as an active, Solana-settled venue. It stays disabled by default per app until the app explicitly enables it.

  • hooks: Keep live managed balance queries refreshed every five seconds while the page is visible. This lets the shared provider and balance hook recover when a fill notification arrives before the settled balance snapshot. Existing post-trade invalidation remains immediate; paper balances retain their event-driven behavior.

  • hooks, ui: Keep temporarily unavailable venue quotes visible as disabled comparison cards, including ProphetX when clearing-house readiness fails. Preserve available price previews and support a customizable availability label.

  • ui: Paper trading UI, gated so live apps render exactly as they do today.

    An app is paper or live at the App level (tradingMode on GET /app/config), and the components resolve it through useResolvedExecutionMode from @agg-build/hooks. In a paper app the library now renders a "Paper trading" header pill, a three-step onboarding modal, and Buying Power / Earnings cards on the profile, and it hides the surfaces that have no paper equivalent — deposit, withdraw, venue cash balances, and the Limit order tab.

    Nothing here changes how a live app renders. Every layout change this feature needed is behind the paper branch, including the ones that would otherwise have been global:

    • Header keeps its 24px mobile inset and no brand-group gap unless a brandAccessory is supplied.
    • The sign-in button keeps the large preset at every breakpoint; the tighter 79x28 mobile size is paper-only, and the signed-out control is no longer wrapped in an extra flex box.
    • The profile summary grid keeps its 1fr / 300–420px columns and 32px gap; the wider 626px column is paper-only.
    • Profile summary cards keep 20px padding and wrapping labels; the compact 16px padding and single-line label are paper-only.
    • A card that supplies no caption renders its value exactly as before, with no extra row wrapper.

    Header gains an optional brandAccessory slot, rendered after the logo inside the brand group. Supplying it also opts that header into the brand-group gap and the 16px mobile inset the accessory layout is drawn against — so pass it conditionally rather than handing it a child that hides itself, since the header cannot see that the element it was given rendered null.

    Modal.Container gains a center class slot for overriding the default bottom-sheet positioning below md.

2026-09-06 · v4.8.1

  • fillSelfCustody now follows the pollAfterMs the server sends on each /execution/status response instead of sleeping a fixed 2s, and takes a short first poll rather than burning a full interval before its first read. The API asks for 1s today, so a pending signature request is found in roughly half the time. SELF_CUSTODY_POLL_INTERVAL_MS is unchanged and is still the fallback when the server does not send the field. Server values are clamped to 250ms–30s.
  • Add venueMarketOutcomeId to VenueSoloQuote. A venue can list the same matched outcome more than once (two Hyperliquid deployers on one game), so venue alone does not identify a solo card. Key on venue plus venueMarketOutcomeId when tracking a selected route.

2026-09-03 · v4.8.0

  • Self-custody fills can now be funded from a legacy Polymarket Safe — the Gnosis Safe Polymarket deployed for a user before they joined this platform — at any venue, not just Polymarket. Nothing changes for wallets that never had one.
    • SignatureRequestType gains "safe_tx". The payload is the Safe transaction as EIP-712 typed data, but the wallet must personal_sign the RAW 32 bytes of hashTypedData(payload) (viem: signMessage({ message: { raw: hash } }); MetaMask: personal_sign with the hex hash). The wallet shows a hash rather than a structured prompt — that is the only shape Polymarket's relayer accepts, so a plain signTypedData over the same struct is rejected. payload.safeTxHash carries the digest for convenience; the server recomputes it and never trusts the copy. As with every request type, throwing from your signer is not a rejection: the request expires and the fill fails.
    • SmartRouteAllocation.custodyKind gains "POLYMARKET_LEGACY", with custodyAddress set to the Safe. Signers that switch on custodyKind should treat it as a source the user does not hold directly.

2026-09-02 · v4.7.4

  • sdk: Correct what approveMode says about cost.

    The previous sponsored docblock described only the one-time ERC-20 approve and could be read as saying bridging has no cost. It does: the bridge fee is deducted from the amount delivered on every bridge, and one-time costs such as a Hyperliquid first-deposit activation still apply.

    The wording now describes what each mode does to the wallet — whether it is asked for gas — without implying anything about the total cost of a bridge, and points at feeBreakdown.bridgeFees and feeBreakdown.setupCosts on a deepEstimate quote as the figures to actually read. Documentation only; no behaviour changes.

  • sdk, ui: Surface Hyperliquid's one-time address activation fee as a setup cost.

    Hyperliquid charges 1 USDC to activate an address on its first deposit. Deep-estimate quotes (deepEstimate=true) now carry it as a feeBreakdown.setupCosts line with kind: "hyperliquidActivation", alreadyPaid: true once the address exists on Hyperliquid, and it counts toward setupCostsTotal and totalCost. The fee is deducted inside the bridge leg rather than run as its own step, so the execution progress view does not render a setup step for it.

  • sdk, hooks: Fix two EIP-4361 conformance bugs that made SIWE sign-in fail with a misleading Invalid SIWE signature 401.

    • buildSiweMessage now keeps the empty statement slot when no statement is given. The ABNF is address LF LF [statement] LF LF, so omitting the blank line produced a message no conforming parser accepts.
    • buildSiweMessage now applies the EIP-55 checksum itself, in any input casing, instead of building a message the server rejects before it ever checks the signature. wagmi users were unaffected because its connectors already normalise; partners bringing their own wallet were not. The SDK stays dependency-free: it carries a self-contained Keccak-256 for this (toChecksumAddress is exported for partners who want it directly).

2026-09-02 · v4.7.3

  • Add ProphetX to the Venue enum. Discovery only for now, so the venue is in INACTIVE_VENUES and hidden by default; it renders with the neutral venue icon until brand artwork lands.

    Add ProphetX to the event list venue tabs. The tab follows the same per-app gate as every other venue, so it is hidden for any app whose /app/config lists prophetx in disabledVenues.

  • ui: Fix duplicate cards on the sports market detail page.

    • Group decomposed N-way legs (halftime result, second-half result, first to score, NRFI) into one card with a pill per leg instead of one card per leg, unioning the venues that offer each leg. A bucket that cannot render as a decomposition splits back to one card per market, so no market is ever hidden.
    • Collapse whole-number line rungs onto the same ladder as half-step ones. The aggKey encoder writes 8.5 as 8p5 but 8 as a bare integer, so integer rungs used to strand themselves in their own card.
    • Parse the signed leg token in a spread aggKey. Spread keys now carry m/p on the line, so the previous pattern matched nothing and the signed ladder axis was inert, collapsing both halves of a symmetric spread onto one stop.
    • Read the favoured and underdog roles off that leg token rather than assuming outcome 0 is always the favourite, which mislabelled every underdog-leg rung.
    • Head each period section when a tab spans more than one period, so two cards that share a title are distinguishable.
  • ui: Render a sports line ladder as a ladder, never one pill per rung.

    Every rung of a line ladder is its own Yes/No market with its own question, which by shape alone is indistinguishable from a decomposed N-way leg. A seven-rung team total therefore rendered seven pills on top of the rail that already selects between them. A leg is an outcome of one question and a rung is a line of one market, so a group carrying any lineValue now renders as a ladder and its pills are the active rung's two outcomes.

2026-09-01 · v4.7.2

  • Fix limit-order sells on matched (multi-venue) markets.
    • The sell venue picker no longer offers venues the user holds no shares on. The automatic pick sorts on price alone, so on a matched cluster it could choose a venue with no position — the API then rejected the order with "Insufficient available position size".
    • The "max shares" figure now matches positions across the whole matched cluster instead of only the selected venue market. Limit mode has no smart-route quote to fall back on, so a position held on another venue of the cluster read as 0 shares.

2026-09-01 · v4.7.1

  • Hide default-disabled venues (Novig today) in the event list tabs while /app/config is loading, so a partner-opt-in venue no longer flashes in and out for apps that disable it.

2026-08-31 · v4.7.0

  • Market lists for an event now cover its whole matched cluster by default.
    • getVenueMarkets({ venueEventId }) and useVenueMarkets({ venueEventId }) return every venue's markets for that real-world event, not just the markets belonging to the one event you named. Matched siblings are folded into a single anchor row rather than repeated, and each row still carries its cross-venue counterparts in matchedVenueMarkets[]. Naming any member of a cluster returns the same page in the same order, so you no longer need to know which event id is the anchor.

      This is the only way to reach markets that trade on a single venue and therefore have no cross-venue counterpart — previously they were unreachable unless you happened to ask for the exact event that carried them.

    • New cluster option on both, plus getVenueEventById and useVenueEvent. Pass cluster: false for the previous behaviour: only the named event's own markets. If you render a market list from an event's id and rely on it matching that one venue's markets exactly, pass cluster: false to keep today's output.

    • marketCount and the rolled-up volume on getVenueEventById / useVenueEvent now cover the whole cluster to match. marketCount is the number of rows the market list returns for that event (open markets, matched siblings folded into their anchor), so it can be paged to exactly — pass status: ["open"] to the market list to see the same set, since an unfiltered list also returns closed and resolved markets. volume sums every open and closed market in the cluster, including the siblings folded out of the list, so it is deliberately not a total over those rows. The embedded (deprecated) venueMarkets array is unchanged — always the one event's own markets. Pass the same cluster value to the event and market calls so the count agrees with the list you render.

2026-08-29 · v4.6.0

  • Self-custody fills take a new optional approveMode on fillSelfCustody(), for wallets that cannot sign a raw-digest EIP-7702 authorization.

    Omit it (the default, "sponsored") and nothing changes: the wallet is not prompted for gas for the ERC-20 approve, and your signer may be asked for the eip7702_authorization request as before — an EOA that is already delegated skips that step.

    Pass "user_broadcast" when you know your wallet cannot produce that signature. The approve comes back as a transaction request instead — the user sends it themselves and pays its gas. Only your client can make this call, because whether the signature is possible is a property of the wallet software, not of the address or the chain, so the API cannot detect it.

    Worth surfacing in your UI: "user_broadcast" never establishes the delegation, so the wallet is asked to broadcast an approve and pay its gas on every bridge, while the default asks for one authorization signature per chain. Reach for "user_broadcast" as a fallback, not a default.

  • Self-custody users can now fund a trade from their own wallet, not just from money already sitting in their Polymarket deposit wallet.

    SignerFn may now return { txHash: string } as well as a signature string. Returning a string keeps working exactly as before — this is additive, and existing signers need no change. The object form is for the new transaction requests, where the wallet broadcasts and you hand back the hash. Return it immediately, without awaiting the receipt; the server watches for the mined receipt itself.

    SignatureRequestType gains "eip7702_authorization". Its payload is { contractAddress, chainId, nonce } and it is signed over a raw EIP-7702 digest — not EIP-191, not EIP-712. Many wallets do not expose this. Throwing from your signer is not a fallback — nothing reads it as a rejection, so the request expires and the fill dies. If your wallet cannot sign it, send approveMode: "user_broadcast" on the fill and the ERC-20 approve is requested as a transaction the user broadcasts themselves. With the authorization signed, the wallet is not prompted for gas on the funding step.

  • Add a signer option and fillSelfCustody() for self-custodial fills. Supply a signer function and the wallet that will sign, and the SDK drives the sign-and-resume round trip; the key never leaves your process.

2026-08-29 · v4.5.0

  • Expose wallet account linking (SIWE/SIWS), matching the API that shipped in feat(auth): link an external wallet to an existing account.

    • AggLinkAccountBody gains { provider: "siwe" | "siws"; address: string }, and AggLinkAccountResult gains { type: "wallet_challenge"; message: string }.
    • linkAccountConfirm() / useLinkAccount().confirmLink() now accept either the existing link_confirm_token string (OAuth/email) or { kind: "wallet_signature"; message; signature } (wallet). Pass the message returned by /start verbatim — the server compares it byte-for-byte, so any client-side reconstruction fails.

    Both are additive and backward-compatible; existing callers are unaffected.

2026-08-28 · v4.4.0

  • hooks: Add useSportsLive and useRecurringCryptoMarkets, and harden execution progress reporting.

    • useSportsLive polls live sports scores and exposes SportsLiveState with participant scores and game status. Ships sportsLiveQueryKey, sportsLiveRefetchInterval and the SPORTS_LIVE_POLL_INTERVAL_MS / SPORTS_SCHEDULED_POLL_INTERVAL_MS constants so callers can align their own cache with the poll cadence.
    • useRecurringCryptoMarkets resolves the rolling window of a recurring crypto market — current instance, timeline and identity — alongside fetchRecurringCryptoMarkets, mergeRecurringCryptoWindows, resolveRecurringCryptoIdentity, resolveRecurringCryptoTimeline and recurringCryptoTransitionPollInterval for callers driving their own polling.
    • Execution progress recovers from failed execution steps more predictably, and now exports the ExecutionStepProgress and ExecutionRawStepProgress types.
    • Limit orders accept fractional sizes, and venue quotes surface a slippage warning.
    • Novig added to the exhaustive frontend venue maps.
    • New sportsLive and recurring-market label keys, all defaulted. AggUiLabelsInput remains a deep partial, so existing label overrides are unaffected.
  • ui: Add live sports scores and recurring-market navigation to event views, and fix a batch of order-flow issues.

    • New sports live-score display, exported from @agg-build/ui/events, showing participant scores and game status on event items and market details.
    • Event item details gain recurring-market navigation — window selector, countdown and a "go to live" affordance — driven by the new optional recurringMarkets prop.
    • Order-placement fixes from QA, covering state clearing between orders and slippage presentation in the place-order flow.
    • Balance management fixes, including raising the default refill maximum to 1000 USDC.
    • Live scores removed from the event list; they remain on detail views.

    No exports were removed and no existing prop became required.

2026-08-28 · v4.3.0

  • Add placeOrder() for direct single-venue order entry via POST /execution/orders, and surface the partner externalId on order reads, webhooks and order events.

  • Kalshi is now a fully active venue. isVenueActive("kalshi") returns true, INACTIVE_VENUES is empty, and getEffectiveDisabledVenues() no longer adds Kalshi to the disabled set by default. Apps that relied on Kalshi being filtered out automatically should add it to their own disabledVenues if they still want it hidden.

    isVenueExecutable has been removed. Every active venue is executable, so isVenueActive is the single predicate — replace any isVenueExecutable(v, override) call with isVenueActive(v). It was never exported from the partner-facing entry point, so partner code is unaffected.

  • getEffectiveDisabledVenues now treats an app's venue list as authoritative instead of merging it with the platform's default-hidden set.

    Previously the two were unioned, so the result always included every not-yet-launched venue no matter what the app passed. Now:

    • getEffectiveDisabledVenues(undefined) — no app context — returns the platform defaults, as before.
    • getEffectiveDisabledVenues([]) returns []. An app that disables nothing excludes nothing.
    • getEffectiveDisabledVenues(["kalshi"]) returns ["kalshi"], not ["kalshi", ...defaults].

    The distinction between undefined and [] is new and load-bearing: undefined means "no app configuration exists", [] means "this app has opted into everything". Passing ?? [] where you mean "no context" will now reveal default-hidden venues, so pass the value through unchanged.

    This matters because venue visibility is moving from a compile-time constant to per-app configuration. Read your effective list from /app/config (disabledVenues), which already resolves this for you — that is unchanged and remains the recommended source.

    isVenueActive, isVenueInactive, INACTIVE_VENUES and ACTIVE_VENUES are unchanged.

  • Add deployerVenue to the orderbook batch types. MatchedOrderbookMarket.deployerVenue names which Hyperliquid HIP-4 deployer a cluster member belongs to, and VenueOrderbookEntry.deployerVenue names which deployer's book the published entry carries — venueOrderbooks is keyed by bare venue name, so it holds at most one Hyperliquid book even when a cluster has several deployer members. Both are null for every other venue. Mirrors fields already present on the API contract and the published OpenAPI spec.

2026-08-18 · v4.2.0

  • Add Novig to the canonical event-list venue tabs, and stop inactive-venue tabs flashing while /app/config loads.

    getDefaultEventListTabs now emits a Novig tab. It renders only where Novig is data-visible — the venue sits in INACTIVE_VENUES, so /app/config reports it under disabledVenues until Novig is enabled, and useEventListTabs drops disabled venues before render. Until then partners see no change.

    useEventListTabs previously special-cased Kalshi when deciding whether to treat an unresolved app config as hidden. Any compile-time-inactive venue has the same problem — its tab is "available" until the config resolves, so it appears and then vanishes on first paint. The check now uses the shared isVenueInactive helper and covers Novig too.

2026-08-17 · v4.1.0

  • Export the QuoteErrorCode / QuoteError types, and add a quote_min_order_size code.

    /execution/fill previously rejected a quote minted with status: "min_order_size_violated" as the generic quote_unfillable, which named neither the cause nor the fix. It now returns quote_min_order_size, and the accompanying message names the venue minimum. Both the paper and live fill paths derive the code from the cached quote's status.

    QuoteErrorCode was previously unexported, so AggApiError.code could only be compared as a bare string. Branch on it with autocomplete instead:

    if (isAggApiError(err) && err.code === "quote_min_order_size") {
      // re-quote with a larger amount
    }

    Checking status === "ok" on the smart-route response before calling /execution/fill avoids the round-trip entirely.

2026-08-16 · v4.0.1

  • Fix READMEs left stale by v4.

    Both packages still documented quoteManaged / useQuoteManaged, which were removed in v4 — the npm pages described methods that no longer exist. Replaced with the getSmartRoute() → executeManaged() flow (useSmartRoute → useExecuteManaged in React) and a note explaining the removal.

    The SDK's "Peer dependencies" section also claimed the package "bundles its own clients for viem, ethers@5, @polymarket/clob-client and @polymarket/builder-signing-sdk". That was never accurate — those were declared as runtime dependencies but no code path imported them, and none were bundled. It now states the truth: zero runtime dependencies, node:crypto the only imported module, bring your own signer.

    Also documents AggApiError / isAggApiError, added in v4 but missing from the exports table.

2026-08-16 · v4.0.0

  • sdk, hooks: Remove quoteManaged and useQuoteManaged, along with the QuoteManagedParams, QuoteManagedResponse, QuoteManagedStep, QuoteManagedSplit and UseQuoteManagedOptions types.

    Their endpoint, POST /execution/quote, does not exist on the API — the call has only ever returned 404, so no working integration can depend on it. Nothing in the SDK, hooks, UI library or demo app used them.

    To quote and fill a managed trade:

    const route = await client.getSmartRoute({
      venueMarketOutcomeId,
      tradeSide: "buy",
      maxSpend: 50,
    });
    await client.executeManaged({ quoteId: route.quoteId });

    The React equivalents are useSmartRoute and useExecuteManaged.

  • sdk: Export AggApiError and isAggApiError. Every non-2xx API response has always been thrown with status, code, retryable and errors attached, but the thrown value was an untyped Error, so consumers had no way to reach those fields without matching on message text. The class and type guard are now exported; runtime behaviour is unchanged.

  • sdk: Remove four unused runtime dependencies (ethers, viem, @polymarket/clob-client, @polymarket/builder-signing-sdk). None were imported by any code path — the SDK's only external import is node:crypto. This cuts roughly 65 MB from a fresh install and removes the corresponding bundler traversal, which was the single largest install-size contributor for React Native consumers.

2026-08-12 · v3.0.0

  • sdk, hooks: Remove yesPrice from sortBy on listVenueMarkets / useVenueMarkets. The cross-event browse sort by price had no consumer and its dedicated index was one of the largest on the table. Markets listed under an event still order by price where the event's structure calls for it, and the minYesPrice/maxYesPrice filters are unchanged. Callers passing sortBy: "yesPrice" should drop the option.

2026-08-11 · v2.9.0

  • Add display metadata to execution status steps: orderIds, kind, groupId, groupStatus, label, and bridge details, so clients can render per-leg execution progress without classifying raw step types.
  • Add getVenueGeoPolicy() — returns the per-venue blocked-country lists (ISO 3166-1 alpha-2) from GET /app/venue-geo-policy. This is the raw venue policy, not per-user eligibility; keep using the quote's warnings array to gate a fill.

2026-08-08 · v2.8.2

  • Add the missing slippage field to SmartRouteResponse.

    The API has always returned route slippage metrics on the smart-route response (SmartRouteResponseTB.slippage in the contract: vwap, refMidpoint, slippage, slippageBps), but the SDK's hand-mirrored interface never declared it. @agg-build/hooks reads slippage.slippageBps to flag thin liquidity, so that code — and its test — could not typecheck against the published type.

    Purely additive: the field is optional, so no existing consumer changes behaviour. The new SmartRouteSlippage interface is exported alongside it.

2026-08-07 · v2.8.1

  • Widen WithdrawalSourceTokenSymbol to include USD1. BNB USD1 (Myriad settlement inventory) can now fund a withdrawal, so it can appear in a withdrawal's sources[]. It is a source-only token — WithdrawTokenSymbol (the request/destination union) is unchanged, same as pUSD.

2026-08-06 · v2.8.0

  • Add config.formatting.formatPrice so partners can override how prices render.

    formatPercent already governed probability rendering on discovery surfaces, but every cents surface formatted prices with a hardcoded formatter. Partners using a non-percentage odds convention (decimal, fractional, moneyline) ended up with converted probabilities on discovery pages and unconverted cents everywhere else.

    formatPrice receives the same 0..1 probability as formatPercent and now governs the order book, event page outcome chips, place-order, and positions. Set both to the same function to apply one odds convention app-wide.

    The positions view model also honors formatCurrency, which it previously ignored.

    formatPrice is optional and unset by default, so every surface keeps its current rendering until a partner opts in. The order book spread and position PnL percentages stay on the built-in formatters by design — neither is a price.

2026-08-03 · v2.7.3

  • Scope the order-book view for a selected outcome to that market's own matched set. The label fallback in collectEligibleVenueOutcomes no longer merges same-label outcomes from other markets that are on the opposite semantic side: a second same-venue market with a different question (a decomposed per-team sibling leg or a same-family total) and markets of a different framing shape (bare Yes/No leg vs team-titled single) are excluded when the user's exact outcome is known. Fixes the unmatched kalshi moneyline leg's No view showing polymarket rows the router can never fill on, and the crossed book / negative spread produced by merging opposite-side books.

2026-08-02 · v2.7.2

  • Event detail page: load every market instead of only the first page of 20. useVenueMarkets gains a fetchAll option that eagerly fetches remaining cursor pages until exhausted; MarketDetailsList and useEnrichedVenueEvent now use it with a 100-row page size, so events with many markets (e.g. 80+ open markets) render their full market list instead of a silently truncated one.
  • ui: Sports moneyline card: when a bare Yes/No leg and a cross-venue titled single collide on one aggKey (venue mis-keys a leg with the event base), keep the titled single — it renders one pill per team and carries the cluster — instead of first-wins evicting it.

2026-08-02 · v2.7.1

  • Sports game-detail page: fix period tab ordering and add a sport-aware gate so a market never surfaces a nonsense tab for its sport. first_N_innings tabs now sort together, numerically by N (5 Innings no longer sorts before 3 Innings). A period family implausible for the events sport (e.g. a Sets period on a baseball event) demotes to the Other tab instead of rendering its own tab, closing the gap left by an upstream Rust classification fix. The gate fails open: unknown or unlisted sports keep every existing tab.

2026-08-01 · v2.7.0

  • Sports game-detail tabs are now derived from the game's own markets instead of a fixed, soccer-shaped list. A baseball game renders Game Lines / 5 Innings / Home Runs / Strikeouts rather than Game Lines / Halves / Other.

    Three tab kinds are derived per game: Game Lines (full-game moneyline, spreads and totals, plus team props and specials such as the first-inning-run and extra-innings markets), one tab per period family that has lines (1h and 2h collapse into a single Halves tab as before, while first_5_innings reads as 5 Innings), and one tab per player-prop stat, labelled from the market subtype.

    Soccer Corners / Goals / Assists / Shots tabs are unchanged and still resolve their labels through the existing cornersTab / goalsTab / assistsTab / shotsTab ui-config keys, so partner overrides keep working. Three new overridable label keys are added: quartersTab, setsTab and playerPropsTab.

    The Game Lines tab no longer filters to regular time. Period-scoped markets now have their own tabs, and that filter had been hiding the period-scoped game props which belong on Game Lines.

  • ui: Sports moneyline card: drop decomposed per-team legs as a set once their merged single is present, instead of only the legs carrying a match edge. The matcher routinely pairs just some of a game's legs, which left an orphan leg beside the anchor and still rendered the whole matchup question as a pill.

  • ui: Trade panel: stop presenting an unaffordable primary route as a dead end when the same quote carries an executable solo. Solo route cards with their own quoteId no longer inherit the primary's insufficient_balance status/error, and the default card selection prefers the executable solo when the primary is blocked on funding — a Kalshi-funded user with no on-chain balance now lands on a submittable Kalshi route instead of a blocking "Insufficient balance" alert. Quotes where nothing is executable still block exactly as before.

2026-08-01 · v2.6.0

  • Sports game-detail tabs are now derived from the game's own markets instead of a fixed, soccer-shaped list. A baseball game renders Game Lines / 5 Innings / Home Runs / Strikeouts rather than Game Lines / Halves / Other.

    Three tab kinds are derived per game: Game Lines (full-game moneyline, spreads and totals, plus team props and specials such as the first-inning-run and extra-innings markets), one tab per period family that has lines (1h and 2h collapse into a single Halves tab as before, while first_5_innings reads as 5 Innings), and one tab per player-prop stat, labelled from the market subtype.

    Soccer Corners / Goals / Assists / Shots tabs are unchanged and still resolve their labels through the existing cornersTab / goalsTab / assistsTab / shotsTab ui-config keys, so partner overrides keep working. Three new overridable label keys are added: quartersTab, setsTab and playerPropsTab.

    The Game Lines tab no longer filters to regular time. Period-scoped markets now have their own tabs, and that filter had been hiding the period-scoped game props which belong on Game Lines.

  • ui: Sports moneyline card: drop decomposed per-team legs as a set once their merged single is present, instead of only the legs carrying a match edge. The matcher routinely pairs just some of a game's legs, which left an orphan leg beside the anchor and still rendered the whole matchup question as a pill.

2026-07-31 · v2.5.3

  • Sports moneyline card: render one pill per team when a venue's decomposed per-team legs and their merged cross-venue single are both in scope. Opening the leg venue's own event (e.g. kalshi's side of an MLB game) previously showed the merged anchor as an extra pill labelled with the whole matchup question.

2026-07-30 · v2.5.2

  • Show every quoted venue in Smart Routing, not just the fundable ones. A solo venue whose own funding source was short came back without an executable quote id and was dropped from the comparison list — so when an off-chain venue won the route (which keeps the overall quote ok), the alternative venue vanished from the panel entirely. The card now renders with its payout for comparison; the panel's existing Deposit / Add funds CTA remains the funding signal.

2026-07-28 · v2.5.1

  • Add isVenueExecutable venue predicate (@agg/common) for gating per-venue order-execution eligibility, distinct from isVenueActive (compile-time) and isVenueDataVisible (data display).

2026-07-27

Changed

  • Markets listed under an event are ordered more sensibly. Sets of competing candidates now sort by odds, and events whose legs are labelled by month sort chronologically instead of falling back to a shared settlement date.
  • Limitless recurring short-window crypto markets now appear as soon as the venue publishes the next slot, ahead of the window opening, instead of showing up part-way through it. They arrive with status of unopened and only become tradable once the window starts.

Fixed

  • Orders and transfers no longer fail with invalid- or insufficient-balance errors in two cases: Limitless positions reported more shares than the wallet could actually sell, and BNB transfers were quoted against a larger amount than the one that landed in the wallet.
  • Polymarket deposit wallets now resolve to the address the venue actually deployed. Funding and orders were being pointed at a predicted address that no longer exists on chain.
  • Activity now lists only deposits a user actually made. Settlement and bridge transfers made during trading that arrived in a managed wallet were being reported as incoming deposits.
  • Polymarket events that were missing some of their markets, and quiet markets whose prices had stopped updating, now stay current.
  • Markets Polymarket has shelved no longer show as open and tradable, and events that have finished report a volume24hr of 0 rather than a frozen figure.
  • Full settlement rules are returned for every venue on a matched market instead of just one of them, and Limitless grouped markets now carry their own rules text.
  • Search and orderbook reads stay fast and reliable under heavy load. Both previously slowed to several seconds or failed outright when traffic was high.

2026-07-24 · v2.5.0

  • Add bookQuality to position responses (OutcomePosition, VenuePositionBalance).

    Positions are marked at the price the held size could actually be closed at, by walking the venue's resting book. When a venue's book is degenerate — a handful of contracts of real bid behind a stub ladder — that mark collapses, but priceSource still reports "orderbook" because the walk did fill. Nothing in the response distinguished that from a healthy fill.

    bookQuality carries the engine's existing BookQuality verdict (healthy / wide_spread / bid_only / ask_only / endpoint_stub / crossed / empty) for the worst book across the position's legs, so clients can label a low-confidence mark instead of rendering it as a market price. Values are unchanged — this adds a signal, it does not re-mark.

    Read priceSource first: bookQuality only qualifies book-derived marks, and a settled position reports empty because it consults no book.

2026-07-23 · v2.4.9

  • Fix Tailwind utilities resolving theme tokens at :root instead of at the element.

    The @theme block is now @theme inline, so .bg-agg-primary & friends emit var(--agg-color-primary) directly rather than a --color-agg-* alias bound at :root. Two consequences of the old form are fixed:

    • Dark mode. :root never matches .dark .agg-root:not(.light), so every utility resolved against the light defaults regardless of theme — .bg-agg-primary rendered #536dfe and .text-agg-foreground rendered #020205 on dark surfaces.
    • Consumer theming. Overrides scoped to .agg-root (including Partner Theme Studio's exported CSS) never reached any utility class.

    Apps that worked around this by re-declaring the palette at :root can drop that block.

2026-07-21 · v2.4.8

  • Surface auth-callback failures as a toast. Account linking (and sign-in) completes on the OAuth redirect-return page, detached from the UI that started it — so when linkAccountConfirm rejects (e.g. HTTP 409 already_linked_elsewhere when the provider identity is already linked to a different user) the error only landed silently in AggAuthProvider's error state and the user saw nothing. AggAuthProvider now toasts the callback error (when a toast provider is mounted) with a readable message — already_linked_elsewhere → "That account is already linked to a different user." — titled for linking vs sign-in based on whether a link_confirm_token is present.

2026-07-21 · v2.4.7

  • Fix client.linkAccountConfirm() 401'ing with "Missing Authorization header". Confirm runs on the OAuth redirect-return page, where the full-page reload has wiped the in-memory access token (tokens are never persisted). The request therefore went out with no Authorization header, and request()'s 401-refresh-retry is gated on an existing token so it couldn't recover — so account linking failed at the final step. linkAccountConfirm() now mints a fresh access token from the refresh cookie/token (deduped) before the request when the in-memory token is absent; if the session is genuinely gone the refresh throws and the caller surfaces it.

2026-07-21 · v2.4.6

  • Complete the ProfileModal account-linking flow. The initial fix (#2503) switched ProfileModal's connect buttons to client.linkAccount(), but the rest of the round-trip was missing — this finishes it:
    • @agg-build/sdk — client.linkAccount() now sends credentials: "include" in the default bearer delivery mode. Without it the same-origin link-proof cookie the server sets on /users/me/link-account/start was dropped on the cross-origin response, so the OAuth link callback rejected every link with link_proof_invalid.
    • @agg-build/hooks — handleAuthCallback (the shared callback handler auto-run by @agg-build/auth's AggAuthProvider and useAggAuthCallback) now detects the link_confirm_token the provider callback returns, calls client.linkAccountConfirm(), refreshes the user, and strips the token from the URL. Previously nothing consumed the token and the link silently never persisted. A collision (identity already linked to a different principal) surfaces as the callback error (HTTP 409); already_linked_same resolves idempotently.
    • @agg-build/ui — email linking (magic link, no redirect) now shows a "check your inbox" success toast so the click has visible feedback.

2026-07-21 · v2.4.5

  • Fix ProfileModal social connect/disconnect. Connect now links the provider to the already-signed-in principal via client.linkAccount() (POST /users/me/link-account/start) instead of starting a fresh sign-in via startAuth() (POST /auth/start), which could not attach to the current user and caused a competing double-redirect. Disconnect (and connect) calls are now guarded and surface the API error (e.g. "Cannot disconnect the only linked auth provider") through the modal's toast instead of escaping as an unhandled promise rejection with no user feedback.

2026-07-14 · v2.4.4

  • Poll the venue-event query while a market lingers "open" past its endDate, so short-cadence markets (e.g. Kalshi 15-min BTC) transition to their resolved state instead of rendering a stale open market with no orderbooks (SNA-3047). endDate only triggers refetches — server status stays authoritative, so venues with inaccurate endDates can't disable trading.

2026-07-13 · v2.4.3

  • Update settlement differences UI behavior

2026-07-11 · v2.4.2

  • Stop the combined-moneyline card from rendering unmerged cross-venue duplicates of one market as two identical pills ("Match Winner" ×2 showing the same Yes price twice). When per-leg labels aren't distinct, the card falls back to the standard single-market rendering with team-title pills (SNA-3028, esports).

2026-07-10 · v2.4.1

  • Render "Team to Advance" in the Game Lines tab, above the Moneyline (Polymarket parity). Knockout advance markets are primary game lines (multi-million volume) and were buried in Other; for a venue whose only market is the advance single (hyperliquid) this is what makes Game Lines exist at all. One card per game, anchored by the 2-outcome single with team-name pills (SNA-3028).

2026-07-10 · v2.4.0

  • The Search primitive now requests status: ["open"] so open-only filtering happens server-side: pages arrive full of tradeable results and pagination is honest, instead of fetching all statuses and emptying resolved-heavy pages client-side (which rendered "No markets found" for queries like "5m btc" whose top results are recurring-series history).

2026-07-10 · v2.3.0

  • sdk, hooks: Add an optional status filter to search: client.search({ status: ["open"] }), useSearch({ status: ["open"] }), and useMarketSearch({ status: ["open"] }) return only markets/events in the given statuses. Pass ["open"] for tradeable-only results — without it, recurring series (e.g. 5-minute up/down markets) can fill a page with resolved history, which a UI that hides non-open rows renders as an empty result.
  • ui: Give whole-match totals a clean unit-based title ("Total Goals" / "Total Corners" / "Total Rounds") derived from the group's stat, instead of a raw venue question that produced labels like "Reg Time: Over 0.5 goals scored Totals" when a kalshi market anchored the group (SNA-3028). Per-team totals keep their subject ("Argentina Totals").

2026-07-10 · v2.2.4

  • Show consistent team names on sports spread outcome pills ({Favored} −X / {Dog} +X) regardless of which venue anchors the ladder stop. A kalshi/limitless anchor stores Yes/No labels; the team names are now borrowed from the cross-venue twins (poly/predict) at the same signed line, falling back to Yes/No only when no venue carries a team title (SNA-3028).

2026-07-10 · v2.2.3

  • Raise the sports event per-tab market fetch page size from 20 to 100 (the API max), so a prop-heavy tab like "Other" loads in 1–3 requests instead of ~15.
  • Render sports spreads as a single symmetric ladder (home ← center → away) like Polymarket, instead of two same-titled "Spreads" cards. Both favored-side families (…_sp_arg / …_sp_che) now collapse into one spread group, and the ladder lays them on a signed magnitude axis with the home/first team anchoring the negative side (SNA-3028).

2026-07-10 · v2.2.2

  • Group sports spread/total ladders by the aggKey line-family instead of the market question, so the same total (e.g. match total O/U) no longer fragments into separate cards across venues that phrase it differently (SNA-3028).

2026-07-08 · v2.2.1

  • Clarify the id vocabulary across the WS surface and surface resolved-market status on /midpoints.
    • useMarketOrderbook now accepts venueMarketOutcomeId (the outcomes[].id from REST market responses); the old marketId option keeps working but is deprecated — it has always carried a venueMarketOutcomeId, not a venueMarketId.
    • SDK onSnapshot/onDelta callback parameters are renamed outcomeId (type-level only, no runtime change).
    • subscribed/unsubscribed confirmations on the arb channel now also echo the ids under marketIds (arb subscriptions are keyed by marketIds); the misleading outcomeIds mirror stays for backwards compatibility.
    • The gateway's Snapshot unavailable for <id> error now explains that orderbook subscriptions take venueMarketOutcomeIds, not venueMarketIds.
    • GET /midpoints entries now carry marketStatus, and resolved markets return null prices plus a structured error (code: "market_resolved") instead of a stale settled-market mark; unknown ids get code: "market_not_found".

2026-07-07 · v2.2.0

  • Discovery responses no longer carry outcome prices. GET /venue-events, GET /venue-events/:id, and GET /venue-markets (and /search, which shares the same shapes) stop returning venueMarketOutcomes[].price — including on matchedVenueMarkets[] siblings. The VenueMarketOutcome.price type is now optional and deprecated. Fetch live prices from GET /midpoints (or the useMidpoints / useViewportMidpoints / live WS hooks), keyed by venueMarketOutcomeId. UI components already prefer live midpoints; the static price was only a pre-live seed and is now ignored when absent.

2026-07-05 · v2.1.5

  • Sports detail page: honor the documented open period grammar (set_N, map_N, game_N, period_N, inning_N, first_N_innings) instead of collapsing those markets into the "Other" period bucket; treat match as full-game scope so whole-match markets stay in Game Lines; stop parsing the opaque aggKey for the league label (categories are the only label source).

2026-07-03 · v2.1.4

  • Fold cross-venue moneyline legs into the 3-way Moneyline card regardless of which venue anchors the merged representative: isBinarySportsMoneylineMarket now also recognizes a leg by its deterministic _ml_ aggKey (e.g. a kalshi-anchored "Reg Time: Brazil" with marketSubtype=null), so no leg drops out as a standalone row. Mis-tagged props stay excluded (they are never _ml_-keyed).

2026-07-02 · v2.1.3

  • Add a context: "list" | "detail" option to getVenueMarkets / useVenueMarkets and pass context: "list" from the event tile's market-scroll query.

    On list/tile surfaces, axis and dates events now sort their markets by lifetime volume (biggest market first) for both the embedded top-3 and the scroll-loaded tail, while the detail page keeps its structured order (threshold/date ascending). candidate/sport events are unchanged.

2026-06-23 · v2.1.2

  • Improve live trading and resolved-market handling across the sufwan-local-test React packages.
    • @agg-build/hooks
      • invalidate and optimistically refresh cached market, balance, position, and activity data when market_resolved WebSocket events arrive
      • export venue-attributed live best-price candidate helpers/hooks so UI surfaces can keep the displayed venue logo aligned with the freshest live price
    • @agg-build/ui
      • add shared claim-winnings and resolved-market claim hooks for resolved positions
      • show the new resolved-market earnings/claim state in the event market flow
      • add onBeforePrimaryAction so partners can run a pre-submit gate before normal order execution
      • keep market detail outcome prices and venue logos in sync with live best-price updates
    • @agg-build/sdk
      • no direct SDK source or API changes landed in this push
      • patch bump only to keep the linked public package versions aligned for release/publishing with the updated hooks and UI peers

2026-06-19 · v2.1.1

  • Add provider-level sufwan-local-test UI trading execution mode config.

    AggProvider now accepts config.trading.executionMode so hosted UI surfaces can share a global live or paper trading mode without threading executionMode through every page or order panel. PlaceOrder, HomePage, EventMarketPage, and UserProfilePage use the configured mode by default while preserving explicit component-level overrides.

    The shared balance provider now also uses the configured paper mode, so ConnectButton header totals and UserProfilePage balance/positions value cards read paper balances instead of falling back to live-mode zeroes.

    Paper balances are labeled as Paper in the user profile network breakdown, use a shared paper-mode icon, and show a tooltip warning when the app is running with simulated paper funds.

2026-06-17 · v2.1.0

  • Add safe midpoint batching for component-driven market pages.

    AggClient.getMidpoints now splits large /midpoints GET requests into smaller batches and merges the responses, defaulting to 75 venue market IDs per request to stay below common CDN URL limits. React hooks and UI components read the same default through config.market.maxMidpointIdsPerRequest, and direct hook/SDK callers can override the cap when needed.

2026-06-12 · v2.0.0

  • sdk, hooks: Narrow recurrence filters to the documented public cadence enum and expose the full supported venue-event cadence list through RECURRENCE_CADENCES, including PT10M, P1M, and P1Y.

    Migration guidance: replace dynamic recurrence-catalog calls with the SDK RECURRENCE_CADENCES export or the OpenAPI enum. Pass the literal "null" only when filtering one-off venue events. listRecurringCryptoMarkets remains limited to deterministic crypto durations: PT5M, PT15M, PT1H, and P1D.

2026-06-12 · v1.3.0

  • Frontend feature round-up since the last release: live arbitrage signals, nested category navigation, event-tile price gaps, paper-trading mode, and position/redeem fixes.
    • Live arbitrage (@agg-build/sdk, @agg-build/hooks, @agg-build/ui)
      • SDK added subscribeArb and subscribeArbFeed, with the supporting wire types (WsArbMarketUpdate, WsArbFeedBatch, WsArbFeedEntry), socket dispatch, reconnect, and refcount handling, and documented the new arb actions in the WsClientMessage union.
      • Hooks added useMarketArb (live per-market arb) and useArbFeed (event-level MAX rollup), exported with their UseMarketArbResult / UseArbFeedResult types.
      • UI brought back the arbitrage sort option and added event-list filters; event tiles and lists now surface arb-driven ordering.
    • Nested categories (@agg-build/hooks, @agg-build/ui)
      • Hooks added useCategoryChildren (with UseCategoryChildrenOptions) for fetching subcategories.
      • UI renders the category displayName on home tabs and event-list filters, supports nested subcategories, and EventListProps gained onCategoryRootChange. HomePageCategory now carries an optional displayName.
    • Event-tile price gaps (@agg-build/hooks, @agg-build/ui)
      • Hooks exported the computePriceGaps helper plus MAX_PRICE_GAP_PCT / MIN_PRICE_GAP_PCT and the ComputePriceGapsOptions / PriceGapValue types.
      • A new enablePriceGap feature flag and priceGap label gate compact price-gap signals on aggregated event tiles; tiles wait for midpoints before computing gaps to avoid flicker.
    • Paper-trading mode (@agg-build/sdk, @agg-build/hooks, @agg-build/ui)
      • SDK introduced the ExecutionMode ("live" | "paper") type and threaded an optional mode through ExecuteManagedParams, smart-route params, positions/orders queries, and ManagedBalancesParams.
      • UI's place-order panel accepts an executionMode prop and reports the chosen mode back through onPrimaryAction.
    • Positions, redeem, and claim fixes (@agg-build/hooks, @agg-build/ui)
      • Hooks exported invalidateUserClaimState and now walk the active bucket when counting redeem-eligible positions; auto-settled HL winners are reported as redeemed rather than ineligible.
      • UI user-profile tabs trust server-provided position buckets, redeem payout renders from the 6-decimal totalPayoutRaw (no longer derived from legs[0] decimals), and a new partner-overridable claim label group powers claim lifecycle notifications.
    • Withdrawals & misc (@agg-build/sdk, @agg-build/ui)
      • SDK added the WithdrawalSourceTokenSymbol type to support pUSD withdrawal sources; WithdrawManagedSourceItem.tokenSymbol now accepts pUSD.
      • UI event-list skeletons can render the discovery sidebar (withSidebar), with refreshed event-tile coloring, volume sort, and updated sort-volume-24hr / shield-trust icons.

2026-06-05 · v1.2.13

  • Add email: string | null to the typed accounts.created webhook event payload so partners can read email addresses supplied by magic-link or OAuth sign-in.

2026-05-26 · v1.2.12

  • Expand the trading labels surface with 9 new partner-overridable keys for execution failure messaging (sellNoOnchainPosition, venueQuoteUnfillable, venueMinOrderSize, sourceBalanceChanged, venueMarketResolved, dflowWalletUnverified, venueNoQuotePath, solanaBlockhashExpired, venueRateLimited, serviceBlockedOrder), and wire matching patterns into normalizePlaceOrderErrorMessage so the SDK renders friendly copy for every UserActionableStepError reason code the API now emits.

    Also rewrites the venue-terminated pattern coverage to handle Limitless's FAILED / UNMATCHED settlement statuses with dedicated friendly copy ("Order didn't settle on-chain" and "No liquidity matched your order at the quoted price"), and extends the min-order-size pattern to recognize predict.fun's existing predict_min_quantity user-actionable copy.

  • Centralize execution failure labels and clean up place-order error normalization for venue outages and expired submission deadlines.

2026-05-21 · v1.2.11

  • Deposit & withdraw UX overhaul:
    • New useWithdrawEstimate hook surfaces per-route fee and ETA estimates before submit.
    • Refreshed deposit and withdraw flows, including new amount/method/success screens, transfer fee summary, and friendly error states.
    • WithdrawManagedResponse now exposes completedAmountRaw, and the WS WithdrawalLifecycleEvent includes requestedAmountRaw and completedAmountRaw so consumers can render the terminal settled amount.
    • New balance/activity invalidation helpers (invalidateUserActivityQueries, invalidateUserMoneyState, userActivityQueryKeys) exported from @agg-build/hooks.
    • User profile activity rows and balance cards updated with refined states; new spinner and circle-xmark icons; tooltip primitive tweaks.

2026-05-07 · v1.2.10

  • Fix a bug in geo-blocking behavior for order splitting

2026-05-07 · v1.2.9

  • UI updates for Positions tab and removed hardcoded colors

2026-05-06 · v1.2.8

  • Added new prop in tooltip for gap and UI adjustments

2026-05-06 · v1.2.7

  • Fix a bug in geo-blocking behavior for order splitting

2026-05-06 · v1.2.6

  • UI updates in the withdraw modal and claim flow for design QA

2026-05-05 · v1.2.5

  • Bug fix for nullable venueMarketOutcomeId

2026-05-05 · v1.2.4

  • Bug fix for nullable venueMarketOutcomeId
  • Update network chain logos to prediction-markets CDN paths; fix Solana missing from deposit modal network list

2026-05-05 · v1.2.3

  • removed redundant div from venue selection

2026-05-04 · v1.2.2

  • Sorting user positions

2026-05-04 · v1.2.1

  • Market details UX updates

2026-05-04 · v1.2.0

  • Added smart-route fee breakdown support across sufwan-local-test packages.

    • @agg-build/sdk now includes optional feeBreakdown data on smart-route quote responses so integrators can display venue fees, bridge fees, execution gas, and total fees.
    • @agg-build/hooks adds features.showFeesBreakdown to UI config and new label keys for fees and updated positions table/status copy.
    • @agg-build/ui adds optional fee breakdown display in the place-order flow, updates route ordering to prioritize better executable value, improves mobile event-market trade opening from outcome selection, and upgrades positions UX (active/closed filtering, status/result badges, and claim-state handling).

    These changes are backward compatible and require no migration for existing integrations unless you want to enable the new fee breakdown UI (showFeesBreakdown).

2026-04-30 · v1.1.0

  • Added xs size for icons and geo block updates for order

2026-04-30 · v1.0.2

  • Refresh published package metadata and fixes for the sufwan-local-test SDK package stack.

    Updates:

    • @agg-build/hooks: fix midpoint price calculation in useViewportMidpoints; minor adjustments to UI-config labels and the useWithdrawFlow hook.
    • @agg-build/ui: settlement formatting refactor (new settlement-text-renderer + settlement-text.utils); PlaceOrder flow updates including default amount, sell-tab default, partially-filled state, best-price wiring, max label, and a sign-in-to-trade flow; refreshed typography variants; new apps and circle-overlap icons; custom Select migration for TBAs; connect-button view rewrite; event-list search/filter improvements.
    • @agg-build/auth: updates to the SIWE adapter and shared auth utils.

2026-04-28 · v1.0.1

  • sdk: Add withdrawal lifecycle WebSocket support — new WsWithdrawalLifecycleEvent message type, onWithdrawalLifecycle SDK callback, and associated types (WsWithdrawalLifecycleStatus, WsWithdrawalLegStatus, WsWithdrawalLifecycleLeg).

    hooks: Add useWithdrawFlow, useWithdrawalLifecycle, and useOnWithdrawalLifecycle hooks with full withdrawal lifecycle tracking via WebSocket. Fix live outcome price fallback so REST midpoints surface when no WS midpoint exists. Improve useMidpoints to prefer the venue with the lower YES midpoint for better buyer pricing. Fix hooks package exports to use format-specific type declarations (.d.mts for ESM consumers).

    ui: Add wallet withdrawal flow — recipient address input, amount/token/network selection, confirmation step, and an order-style execution timeline showing per-leg bridge/transfer progress with live lifecycle status. Add CopyButton primitive. Show wallet address on card-payment pending step in the deposit flow. Surface best-price venue indicators in the event list, market details, and orderbook. Fix cluster-aware market deduplication to collapse cross-page duplicates.

2026-04-27 · v1.0.0

  • Initial v1.0.0 release of the sufwan-local-test client libraries on npm under the @agg-build scope.

On this page