Skip to content

Sovran 0.1.3

Latest

Choose a tag to compare

@github-actions github-actions released this 12 Sep 18:44
d9ad12c

Sovran 0.1.3

The Android release. Sovran now posts to Nostr, zaps, and sends bitcoin onchain — three things 0.1.0 could only read, display, or stub out. Underneath, the screens you touch most — Receive, the balance, the feed, and your history — were rebuilt rather than patched.

Ordered by what's most interesting — flagship features first, then design, security, and engine work. 22 areas, 195 changes.

Sovran runs on Android

0.1.0 shipped on iOS only. Android is now a first-class build with native sheets, working NFC, the Bluetooth mesh, and an app that is 21 MB smaller than it would otherwise be.

  • Every flow — send, receive, mint management, settings, and six others — presents as a native Android bottom sheet, matching iOS on top gap, dim, and rounded corners.
  • Sheets behave: content scrolls instead of dismissing the sheet out from under you, drag-to-dismiss works from the Balance split, sheets reach full height on open, and a closed sheet no longer peeks as a sliver at the bottom of the screen.
  • Standalone sheets carry the same faded header band as iOS, and the header scrim no longer composites over the title.
  • Continuous rounded corners render natively, so the component kit reads the same on both platforms.
  • The Bluetooth mesh and geohash Nostr module are ported, so Nut Drop works on Android.
  • NFC works end to end — preflight, initialization, and popups — and malformed tags are rejected instead of parsed.
  • The photo lightbox opens and dismisses in the same window, landing exactly back on the thumbnail you tapped.
  • Going offline now takes a confirmation rather than one failed probe, so the connection indicator stops flapping.
  • Headers are readable again: one header-button primitive, an eased scrim with a fade tail, and no doubled gradient.
  • Every system icon renders, including the Nut Drop shuffle button and the Advanced payment-request controls that used to appear blank.
  • Full-width search bars, neutral text-input colors, rounded transaction cards, a revived category picker, grayscale map styling, and slider haptics.
  • Toasts are opaque where blur isn't real and frosted only where it is.
  • Bottom-sheet menus route through one host, so a closed menu no longer renders behind the screen.
  • Code and resource shrinking are on, JavaScript is minified, and bundled wallpapers and unused font weights are gone — around 21 MB off the download.
  • Releasing to the stores is automated end to end, with reproducible artwork, signed artifacts, and a pipeline that names the stage that failed instead of swallowing the error.
  • 0.1.2 was withdrawn: Google Play's health declaration blocked the bundle over an activity-recognition permission Sovran doesn't use. 0.1.3 is the same app rebuilt without it.

Post, reply, and delete on Nostr

0.1.0 could read the feed. Now you can write to it — notes, replies, quotes, and polls, with media you can caption and posts you can take back.

  • A full composer for text notes, replies, quotes, and polls, with quote context carried onto poll posts.
  • When you reply, the post you're answering renders in full above your draft.
  • Attach photos, write alt text before posting, and watch upload progress; a failed image is tappable to retry.
  • Posts publish optimistically — your note appears immediately and a toast offers an instant View, while the relay fan-out finishes in the background.
  • Your own likes, reposts, replies, and follows sync straight from relays into Sovran's own stores, so they stay correct even when the app-view is behind.
  • A "you replied" highlight now appears on the engagement bar of every post you've answered, across the feed, threads, and profiles.
  • Delete a post and its media together: a NIP-09 deletion to your relays plus a Blossom delete for each image.
  • A deleted post renders as a tombstone row with a trash-can avatar instead of vanishing silently.
  • Deletion is capped at about eight seconds per relay, so one dead relay can't hang the confirmation, and slow media purges are confirmed with a follow-up probe.
  • If a deletion can't start, Sovran says so; when it finishes, the toast closes with a green ring and tick.
  • A My media page in settings lists everything you've uploaded, split into Still online and Deleted, with per-blob status and inline deletion.
  • Home-feed text notes cap at 280 characters so the feed stays skimmable, with the full post a tap away.
  • A shared Repost-or-Quote menu and a share-and-more menu sit on every post in the feed.
  • Thread links embed inline, and relay addresses render as relay cards rather than raw text.
  • The composer slides up from the bottom with a delayed, bottom-rising keyboard, and the compose button is anchored to match the AI tab's.

Zap any post in sats

0.1.0 showed you how many sats a post had received. Now you can add to the total.

  • Tap the lightning icon on a post to zap it; Sovran signs a NIP-57 zap request and attaches it to the invoice, so the zap is publicly attributed to you.
  • A zap menu handles amount selection and routes the payment through your wallet like any other send.
  • Zaps land in your history with their own annotation, get a dedicated filter in Transactions, and show the post you zapped on the transaction detail.

Onchain bitcoin, sent and received

0.1.0 split onchain onto its own route and showed an "Onchain send is not supported yet" screen. That screen is gone.

  • Send real onchain payments from your ecash balance, choosing a fee tier; when a mint offers only one option it's selected for you instead of showing a one-button sheet.
  • Receive to onchain addresses that rotate when a deposit lands and on demand.
  • Accelerate a stuck payment from its transaction detail when the fee you picked isn't clearing.
  • The transaction outpoint and fee are saved, so the fee line survives a restart, and payments made before this shipped have their outpoint discovered after the fact.
  • Every onchain transaction links to a block explorer, with an honest not-found while the network hasn't seen it yet.
  • Quote expiry shows on the timeline, so you know when a payment has to go now.
  • Settlement waits for two agreeing reads before declaring success, and a rollback that happens in the background is surfaced instead of swallowed.
  • Confirmation polling stops once a payment is deep enough and never runs outside mainnet.
  • A partial fixed-amount payment is refused outright rather than half-sent.

Receive, rebuilt around four rails

Receive is now four rails — Lightning, Unified, Onchain, and Cashu — with Unified first and default: one BIP-321 QR that composes whatever rails you have standing, so a Lightning wallet, an onchain wallet, or a Cashu wallet can each read the same code.

  • Each rail keeps its own mint selection, defaulted for you, and routes "Receiving with" through the normal mint picker.
  • The Cashu rail is a NUT-18 payment request delivered over Nostr: a standing request, a fresh one per visit, a fixed-amount single-use request, and Advanced per-mint toggles controlling which mints you'll accept.
  • BOLT 12 appears as a rail whenever your mint advertises support for it.
  • A receive hub sits in front of the QR, with every tab preloaded so switching never flashes a skeleton.
  • View all pushes full lists of your payment requests, onchain addresses, and BOLT 12 offers.
  • The standing rail rotates itself when a payment lands, so you're never handed a code someone has already paid.
  • A retired request is never displayed, and a request that's been settled can't be shown again by mistake.
  • A dedicated payment-request receive screen redeems incoming ecash live as it arrives, and pending requests can be customized per request.
  • Ecash sent to you but not yet redeemed shows as an in-flight receive in your wallet instead of being invisible until it lands.

Your currencies are now accounts you swipe between

The unit dropdown is gone. Each currency you hold is an account with its own page, its own live data, its own wallpaper, and amount entry that speaks its own denomination.

  • Swipe between accounts in a carousel above the action row, each page showing its own balance and activity in consistent slots so nothing jumps as you move.
  • Branded currency discs identify each account at a glance.
  • Amount entry is native to the account's unit, with a typed-input envelope and a clamp notice rather than silently rewriting the number you typed.
  • Unit tabs on Select Mint are backed by each mint's real keysets, with per-row unit badges, and the unit switcher follows what a mint actually supports instead of assuming.
  • Mint minimums and maximums are read from the mint and enforced before you commit, with mints that can't serve your amount sorted below those that can.
  • The wallpaper follows the active account, with a drag-driven transition that commits when you let go.
  • The fiat display-currency pill moved below the balance, onto a native menu that scrolls.
  • Your transaction list stays unfiltered across all accounts and doesn't reload when you switch.
  • Conversion is correct in both directions: fiat balances convert to sats for Lightning and onchain, scanned invoices and addresses re-denominate into the active account, and sub-satoshi invoices round up to whole sats.
  • A self-overflowing action row keeps every wallet action reachable at any screen width.

Sign in to any Nostr app with your Sovran identity

A full NIP-46 remote signer turns your Sovran profile into a login for other Nostr apps, approving each request on-device so your key never leaves it.

  • Pair by scanning a QR, pasting a connection string, following a deep link, or switching profiles.
  • Every signing request shows what's being signed and waits for your approval.
  • Live signer state is legible: cooldown, session access, and an explicit Everyone scope instead of a silent default.
  • Signer settings mirror into the approval sheet, and a signer reset clears every pairing at once.
  • Subtitles wrap rather than truncate, so a long app name stays readable.
  • The whole surface was hardened against an adversarial review before it shipped.

Nut Drop, locked to one person

0.1.0 broadcast every drop to the whole public mesh and said locking to a person would come once bitchat could carry longer encrypted messages. It can, and it does.

  • Drops are P2PK-locked to a specific person and delivered as a private message, carrying a NUT-18 request with the mint and capability signal.
  • Identity is exchanged through bitchat's own favorite notification, with a single-initiator handshake so eagerly favoriting someone still completes the exchange, and re-exchange after a restart.
  • Sends ride a proper NUT-18 mesh transport, while an open ecash capability extension keeps bearer drops working to peers running stock bitchat.
  • Incoming drops redeem themselves in the background, with guards for switching profiles mid-redeem and recovery from a crash while redeeming.
  • A full-screen gold celebration marks an arriving drop, with adaptive hero avatars and a lightning strike that scales to the frame.
  • Bluetooth state shows on Nut Drop, the network sheet, and BLE chats, so an empty peer field explains itself.
  • Announce verification works across platforms, and your profile is no longer disclosed to peers you haven't engaged with.
  • Structured logs cover the whole mesh send and receive path for support.

An AI tab that prices itself honestly

The AI tab keeps the streaming chat and pay-per-message ecash from 0.1.0, and replaces the hardcoded model list with a live one that tells you what you can afford before you send.

  • The model lineup is served live rather than compiled in, so new models appear without an app update.
  • Models sort into price-ordered tiers, with vision-capable models marked by an icon.
  • Attach images to a message on models that accept them, with a graceful downgrade on models that don't.
  • An admission check tells you up front whether your balance covers a model, instead of failing mid-request.
  • Your balance heals itself: it syncs from the provider's own payment-required response and refreshes when the balance chip mounts, so it can't sit stale.
  • Stale attachments clear between messages, and a dead conversation branch advances instead of dead-ending.
  • The conversation store no longer wipes itself when it reaches its own storage ceiling.

A feed that survives its own backend

0.1.0's feed came from Nagg's app-view with a quiet fallback. It's now three tiers — Nagg, the Primal cache, and raw Nostr relays — with ranking, threads, notifications, and search all working with Nagg completely offline.

  • If a tier is empty, errors, or is unreachable, the next one takes over, and an empty page falls through rather than rendering blank.
  • Every social surface runs on it: the feed, threads, notifications, profiles, profile search, direct messages, the social graph, profile stats, your own history, and mint reviews.
  • For You ranking runs on the raw-relay floor too, so a personalized feed survives with no app-view at all, and engagement is weighted toward accounts with real reach rather than raw counts.
  • Feed sessions are paginated and live-aware, with a page buffer that stops the feed reshuffling under your thumb while you read.
  • A shared cache means a profile or post you've already seen renders instantly next time, and names and avatars fill in reactively as they resolve.
  • A fifteen-second timeout and a consecutive-failure breaker put a struggling backend into cooldown instead of hanging the screen.
  • Engagement counts roll in smoothly rather than snapping to a new number.
  • Settings, Relays is now Settings, Network, with a section per tier and live health for each.
  • Home feeds request fewer posts per page, so the first screenful paints sooner.
  • Profile posts survive a fallback failure instead of emptying the page.

Threads that hold their place

Opening a thread used to move the post you tapped. Now it stays anchored while replies and parents load in around it, and the thread tells you the truth about how much of it you're seeing.

  • The post you tapped holds its position as content arrives above and below it.
  • Paging is truthful: counts reflect what's actually there, with the original post partitioned first.
  • One fetch fills one in-memory stack, so paging within a thread is instant and never refetches.
  • A Might-be-spam section collects low-quality replies below a full-width divider instead of mixing them in.
  • Only genuine direct replies render as replies, and Mentions is a proper classifier over replies, quotes, and mentions of you.
  • Skeletons are measured against the rows they stand in for — the author row, the footer, reshaping media — so real content landing doesn't shift the page.
  • The chain of parent posts above a reply renders from cache on the first frame.
  • Replies resolve off-screen and cross-fade in rather than reflowing in place.

Notifications from three sources at once

The inbox now pulls from Nagg, the Primal cache, and relays concurrently, so notifications arrive from whichever answers first — and your mints can notify you too.

  • One unified session merges all three sources, deduplicating as it goes.
  • Mint changes appear in Notifications: Sovran checks each of your mints daily and describes in plain language what changed about its capabilities or terms.
  • Profile resolution behaves identically on every tier, and grouping applies across the whole page rather than per batch.
  • Relay-sourced notifications are ownership-gated, so you only ever see what's genuinely about you.

Mints you can discover, vet, and rebalance faster

Mint data moved into one store with a cache-first read, so the mint screens stop shifting as data trickles in — and discovery no longer fires a request per mint.

  • One unified metadata store backs every mint surface, ending the content shifts on the mint screens.
  • Discovery comes from Nagg's app-view, removing a per-mint request storm and making Add Mints markedly faster.
  • Reviews arrive through NIP-87 with the reviewer's identity attached and an authoritative review count.
  • Auditor uptime is correct: errors are counted separately from successes rather than folded together.
  • Lightning payments auto-pick your preferred mint instead of forcing you through the selector every time.
  • Balance split is rebuilt as swap-routing-style cards — one minimal layout with slim sliders, a canonical footer, and theme-aware Min and Max buttons.
  • Capitalized URL schemes are accepted, the search field stops auto-capitalizing what you type, and the Add-mint skeleton holds until results settle.
  • A mint that won't respond can no longer block startup.
  • The mint selector refreshes when you return to a flow screen, so a mint you just added is there.

A single transaction list that explains itself

History is one continuous list with month navigation instead of paged tabs, and every entry carries where it came from, who it was with, and whether it was locked.

  • One list, navigated by month, that merges new entries at the top on refresh without losing your place.
  • Each transaction records its source — QR, NFC, clipboard, deep link, or Bluetooth — plus a P2PK lock badge and a counterparty.
  • The counterparty's Nostr avatar appears in both the row and the detail.
  • Filter by source, lock, counterparty, and zaps, on flattened sections rather than boxed cards.
  • Per-person history collects everything you've exchanged with someone in one place.
  • Swap legs group under a single entry instead of cluttering the list.
  • Classification is correct for onchain and in-flight payments, credited mint deposits count toward Received this month, and only redeemed receives count as settled.
  • Payment-request transactions are marked in their detail, kept out of pending, and hidden once they're over a day old.
  • The mint shows read-only on a history detail, so it can't be changed after the fact.
  • The timeline animates its own shape changes: a rollback resolves visually like a success, pending steps take the rail color, segments that complete in one frame cascade, and the confirmation ring stops pulsing before the payment has actually been observed.

Photos that carry nothing you didn't intend

Every photo you upload is stripped and converted before it leaves the device, and everything that renders one handles a missing or broken image gracefully.

  • Location, device, and timestamp metadata is stripped from every uploaded photo.
  • Video uploads are blocked.
  • HEIC and HEIF photos convert to JPEG before upload, so they open everywhere.
  • Uploads carry a size cap, a retry, a timeout, and cancel-on-remove.
  • Media renders by its declared type, with blurhash placeholders and AVIF support, and the post's own dimensions drive video aspect ratio so nothing reflows.
  • Alt text written by the poster is surfaced to readers.
  • An image that can't load shows a placeholder instead of a gap, and the media overlay only mounts while media is actually on screen.

Messages, contacts, and one search overlay

Direct messages moved onto the tiered facade with a live subscription, and search stopped replacing the screen you were looking at.

  • New messages arrive over a live subscription instead of waiting for a refresh.
  • Your own message appears in the thread the moment you send it.
  • A persistent decryption cache makes reopening a conversation instant.
  • Search is an overlay now, layering over the screen rather than swapping its body out.
  • Profile search runs through all three tiers, falling back from Nagg to the Primal cache to a relay query.
  • People search in Send shares one implementation with the rest of the app, so it behaves identically everywhere.
  • A quick-pay contact flow lets you pick a person and pay them without assembling a destination.
  • Send describes what you've pasted or scanned before you commit, and paste, submit, and tapping a detected row all take the same path.

Bitcoin near you, with a reason for the permission

The merchant map keeps what 0.1.0 shipped and fixes the parts that got in the way.

  • Location permission is requested with a plain reason: Sovran uses your location to show nearby Bitcoin-accepting places.
  • The map stats button matches the wallet's fiat pill.
  • Zoom controls stay above the merchant filter rather than hiding behind it.
  • Per-place counts read from precomputed statistics, so panning doesn't recompute the map.

Liquid glass on iOS 26, and a design system with real tokens

The visual language from 0.1.0 is now enforced by tokens rather than convention, and iOS 26's glass is adopted properly instead of approximated.

  • App-owned glass circles match the mint selector's geometry, glyphs sit bare inside the system capsule, the profile avatar joins the same treatment, and the search bar and mint selector are glass throughout — with a full regression sweep for tap delivery after the SDK upgrade.
  • Typography and control heights are token scales, and the flat tier is normalized to the same contract.
  • Skeletons cross-fade into content app-wide, with a wave highlight that always matches the surface behind it, and profile pictures fade in over their placeholder.
  • The wallpaper carousel is a persistent layer stack decoded at a fraction of screen size, so switching accounts doesn't stutter, and a truncated wallpaper download is rejected rather than rendered broken.
  • Generated avatars are a seeded clay silhouette, held back until a real profile resolves so they don't flash first.
  • Profile actions group into the banner as a stable primary-and-secondary pair, with no pop-in.
  • An overflowing tab bar collapses behind a menu instead of squashing its labels.
  • An update-available sheet now carries a Download action rather than just telling you.
  • Background gradients no longer dither on solid-color themes.
  • Settings opens faster, with its content mounted in stages.
  • Toast tints blend into the slab on platforms without real blur.
  • Every account's theme resets once to dark on upgrade, giving the new wallpaper system a clean baseline.

Your keys, your logs, and your stored data

0.1.0 moved your seed into the secure enclave and redacted secrets from logs. 0.1.3 rebuilds restore, closes the remaining log paths, and makes your stored settings impossible to wipe by accident.

  • The seed-restore screen is rebuilt on current Cashu libraries with native cryptography, and a restore that recovered nothing no longer reports as complete.
  • Mnemonics are redacted from logs, and base58, base64, and long-hex key material is filtered out.
  • The logger and the storage dump share a single list of sensitive fields, so the two can't drift apart.
  • Export your log file on-device for support; wallet database backups are listed separately and never included in a dump or share.
  • Switching profiles persists then restarts cleanly, cages stale background work from the profile you left, and bounds how long it will wait for teardown.
  • The sixty-fifth profile is refused with a message rather than corrupting your profile list.
  • Your settings can't be lost to a schema change: a rejected field is contained instead of discarding the whole store, oversized annotation keys are hashed so they can't overflow it, and a migration registry with a drift guard sits behind it — with a test proving every store accepts its own writes.
  • Denying camera access surfaces a toast rather than a dead screen.
  • The price feed no longer dies, or leaks, on a bad network.
  • Service errors are centralized, so failures read as sentences instead of codes.
  • Your Lightning address migrated to npub.cash v2, with the username claim re-enabled and the lock-state check bounded.

The payment machine, audited

A dedicated review pass on the payment state machine found fourteen issues before users did. All of them are fixed.

  • Melt previews match live updates by quote, not by mint and amount, so two similar payments can't be confused for each other.
  • The mint's own exchange-rate drift shows on a fiat payment preview before you pay.
  • The quote is created before you tap Pay, not after.
  • A payment request in a different currency than your account is refused outright, and the raw request amount is never re-read after conversion.
  • A prepared send whose execution fails has its reserved funds released instead of stranded.
  • A confirmation arriving with no mint selected recovers rather than being dropped, and full constraints survive a currency-mismatch recovery.
  • Back-navigating into a payment flow and re-entering it works instead of leaving a dead screen, and dead buttons are hidden on flows entered by scanning.
  • Send preselects a funded mint, constrains which mint the funds come from, and labels Lightning-address payment targets so you can tell them apart.
  • Fixed-amount payments that can only be partly covered are rejected.

A faster app on Coco v2, Expo 56, and a monorepo

The engine underneath moved forward on every axis: a new wallet core, a new SDK, one repository, and a code base with a quarter of its duplication removed.

  • The wallet runs on Coco v2, with quote-first minting and melting, live settlement watchers, and history projected from operations rather than reassembled.
  • Your wallet database migrates forward once on first launch, after Sovran takes a one-time backup of the previous version so the upgrade is recoverable.
  • Expo SDK 56 and React Native 0.85, with a security update to the crypto library, a newer list implementation that fixed a blank feed after leaving a thread, and Xcode 26.1 support.
  • The seven-hundred-line payment status switch is replaced by a declarative flow engine, with one owner for state vocabulary and one for the confirmation ring's geometry.
  • Sovran is one repository again: the wallet engine and the Nostr client live in-tree as workspace packages.
  • Twenty-five code-hygiene passes removed duplicated blocks, dead code, and every import cycle, and turned on stricter compiler checks now that the floor is clear.
  • More than twenty dependencies are gone, among them the patching layer, two color libraries, two class-name libraries, a screenshot library, and a bundled wallet nothing used.
  • The client talks to the app-view over REST only; the GraphQL layer is deleted.
  • The legacy Redux stack is removed entirely.
  • An end-to-end test harness drives both a simulator and an Android emulator — including real airplane mode — with mint-fault injection, per-frame state capture, and video for every scenario; fifty-four wallet scenarios run at release.
  • Continuous integration gates the whole workspace with a check per gate and a maintenance scorecard.

The finishing pass: what changed under you

The smaller decisions and removals that come with a release this size — the things worth knowing before you open the app.

  • Split Bill is gone: the flow, its module, its transaction rows, and its stored data. Shared payments are going a different direction.
  • Balance split's three experimental layouts, briefly available behind a developer setting, are reduced to the single layout that shipped.
  • Generated avatars change appearance on upgrade; real profile pictures are untouched.
  • Developer settings reset once on upgrade.
  • Android and iOS are separate installs, with no migration between them.
  • BOLT 12 appears only when your mint advertises support for it, which most don't yet.
  • A drop to someone running stock bitchat rather than Sovran is still a bearer token, since only Sovran peers can exchange the identity needed to lock one.
  • Accelerating a stuck onchain payment depends on your mint supporting it.
  • Plus a batch of stability, formatting, and copy fixes across the app.

Source: d9ad12c

Store availability is tracked separately at https://sovran.money/download.

APK SHA-256: 2fca91015f0669685d808cae3a6caa274c4f15d1a2acad3893188b1ca758a941
Android signing certificate SHA-256: b598befca67352eff6a0333e9a963c76db42bd3d1c64920ebd8b5b84751e59af