Skip to content

v0.2.4

Choose a tag to compare

@ouziel-slama ouziel-slama released this 30 Jul 16:53
cd9d292

[0.2.4] - 2026-07-30

Added

  • React: buy / delist funnel observer callbacks on useSwapList / <SwapList/> (web + native) — onLoginRequired, onBuyStarted, onDelistStarted on useSwapList, and onBuySuccess, onBuyError, onDelistSuccess, onDelistError on <SwapList/>. Previously a host could only observe the raw onSwapSelect tap (fires for both buy and sell) with no visibility into the login/confirm/result funnel — buy/delist results were consumed entirely inside the component. These are observers, not overrides: the built-in login modal, confirmation modal, and result screens still drive the UX unchanged — the callbacks only notify, each passed the full AtomicSwap (not just the asset-poor PendingSale) so a host can build rich analytics and branch kontor-vs-multisig via swap.listingType.
    • onLoginRequired(swap) fires from onItemAction when acting while logged out, before the login modal opens.
    • onBuyStarted(swap) / onDelistStarted(swap) fire when the buy/delist confirmation modal opens — immediately, or after a successful login.
    • onBuySuccess(swap, sales) / onBuyError(swap, error) and onDelistSuccess(swap) / onDelistError(swap, error) fire from SwapList's existing confirmation wiring.

Fixed

  • A failed balance read no longer masquerades as an empty wallet. WalletBalances (web + native) rendered "No {group} holdings yet." whenever a tab had no options — including when that source's fetch had failed, since useAssets resolves a rejected source to an empty list and only records the reason in errors, which the component never read. An unreachable indexer, a down API, or a wallet the SDK can't identify all produced a confident, wrong "you own nothing" with no way to tell. A failure is only one of three ways an empty group can lie, so useAssets now publishes the whole picture as sources: Record<SourceKey, SourceState>loading (the read hasn't settled; previously every tab claimed "no holdings" for the length of the first fetch), ok, error, or unread (this app never asks for that source: no ordApiBaseUrl, Kontor off this network). Each OtherGroup carries its source's state, and the shared emptyGroupNotice() decides what both renderers show — only ok licenses the "No {group} holdings yet." claim and its deposit affordance. A failure also marks the tab (!) so it's visible from a non-active tab; unread and loading don't, being nothing to alarm the user about. unread deliberately stays out of errors: nothing went wrong and there is nothing to retry.
  • isEmpty no longer counts a failed read as an empty wallet. It was loadedOnce && !allAssets.length — true even when every source had failed, which is exactly the conflation the rest of this fix removes. It now also requires that no source failed, so the empty states driven by it are only ever shown for a wallet that was actually read. SellOrderForm's asset picker gets a distinct "Couldn't load your assets" placeholder for that case (the per-source reasons were already listed under it).
  • The headline balances stop reporting a failed read as 0. The same lie sat above the tabs: XCP / KOR / ZELD fell back to "0" whenever their source yielded no holdings, failure included. TokenLine now carries an error (XCP ← counterparty, ZELD ← zeld, KOR ← kontor, BTC ← the BTC read) and a null amount when set; all four render sites (web + native × full list + dropdown summary) print instead of a balance they could not read, with the reason in the tooltip / accessibility label — and spelled out under the BTC line, which has room for it. This also gives errors.zeld a consumer: ZELD has no other-holdings tab, so its failure was previously unreportable.
  • getKontorHoldings() now reports why it read nothing. It deliberately degrades to empty holdings rather than throwing (a Kontor outage must not break the whole balances read), so its silent bail-outs — no @kontor/sdk backend, a non-signet network, or a wallet exposing no taproot x-only key — were indistinguishable from an empty wallet and never reached errors.kontor at all. The result gains unavailable: KontorUnavailableReason | null ("runtime" | "network" | "wallet-key"), which useAssets turns into a user-facing errors.kontor. Null both on success and when Kontor is simply not configured. Note: consumers that construct a KontorHoldings (test doubles) must add the field.
  • CLI: horizon balances stops printing 0 for a source it couldn't read. Same bug, same shape — every read was folded into an empty list on rejection. XCP / ZELD / KOR now print when their read failed (KOR also when getKontorHoldings() reports unavailable), each failure is named under the table it explains, and --json gains an errors object (null per source on success) so scripts can tell "holds none" from "couldn't look". The BTC price feed is tracked as its own source (errors.price) — without it a down feed just dropped the (…) USD suffix, reading as "no price for you" — and an ord API that didn't answer is reported under its own Ordinals heading rather than printing nothing at all, which is what an inscription-free wallet also does.
  • The balances cache no longer persists a failed read — or re-fetches everything because of one. It stored holdings only, so a later mount seeding from it reported no errors and replayed the silent, wrong "you hold nothing" for the rest of the one-hour TTL. The stored payload is now { assets, stale } (cache key bumped v1v2, old entries read as a clean miss): the sources that answered are cached, the ones that failed are named, and the next mount paints the cached part immediately while re-fetching only the stale sources. Dropping the whole snapshot instead would have made one flaky source — the ord API, typically — cost a full re-read of the other three on every mount until it recovered. A topped-up entry keeps its original timestamp, so a source that keeps failing can't extend the others' TTL indefinitely.