<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinosv3.git/src/features/sync.js, branch master</title>
<subtitle>coinos v3 — bitcoin, Ark, Lightning and nostr wallet (web + Android)</subtitle>
<id>https://git.coinos.io/coinosv3.git/atom?h=master</id>
<link rel='self' href='https://git.coinos.io/coinosv3.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/'/>
<updated>2026-10-03T06:20:42Z</updated>
<entry>
<title>Zaps find lightning addresses our relays have pruned</title>
<updated>2026-10-03T06:20:42Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-03T06:20:42Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=d4d5a8b65dde249bc6eb27b7c120b7f5c9b238fe'/>
<id>urn:sha1:d4d5a8b65dde249bc6eb27b7c120b7f5c9b238fe</id>
<content type='text'>
Zapping a post looked the author's lightning address up on our four home
relays only. Those prune old events, so anyone whose kind 0 hadn't changed
in a while (1776, Brad Mills) came back as "no Lightning address or Ark
address" — while their profile page, which also asks the index relays and
the author's own relays, showed the address right there. The zap lookup
now asks both ways at once and takes whichever carries a lightning address.

And when someone does have one but Lightning can't be paid right now, the
toast says that instead of claiming they have no address.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01VKv2uEFRR42Gp6NyqcN3A9
</content>
</entry>
<entry>
<title>Accounts from the header, snapshot-first Spending restore, name ownership grants.</title>
<updated>2026-09-19T06:12:18Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-19T06:12:18Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=703ee6e1281e72ec00f65768d7de7a015c242abb'/>
<id>urn:sha1:703ee6e1281e72ec00f65768d7de7a015c242abb</id>
<content type='text'>
Header buttons always leave the current feature screen; an account with
no remembered nostr identity opens Accounts instead of nothing. Logout
confirms in place. A scan finishing after logout or an account switch no
longer drops the new session into offline mode (stateGeneration guards in
sync + ark). Spending restores from the snapshot first. A migration grant
names the identity that owns an address; its manager may register it
without becoming that identity (names/ownership.js). Browser tests for
account access, profile retry, spending restore, thread reply focus,
custom-emoji reactions and registry ownership.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Sync: stop resurrecting stale pending txs from other devices' snapshots.</title>
<updated>2026-09-10T14:28:54Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-10T14:28:54Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=f2de9b1330dfad5990f8e0ea6130b91096a60e54'/>
<id>urn:sha1:f2de9b1330dfad5990f8e0ea6130b91096a60e54</id>
<content type='text'>
A 5,000-sat send built on 2026-09-06 that the network never took sat in the
history as a pending withdrawal for four days on every device. The local
scan prunes such rows (explorer 404 / absent from address history), but
the boot-time cross-device history union re-adopted it from a stale device
slot's core snapshot, and the device then republished it — a phantom that
no explorer knows, immortal across devices.

The union now adopts a remote PENDING row only while it is under a day old
(it has to prove itself to the local scan); confirmed rows still union as
before, and the device's own rows are untouched.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Nw1Dijik1tLVKRM1WkU7xE
</content>
</entry>
<entry>
<title>Sync: oversized domains ride Blossom blobs — plus a coinos-hosted server.</title>
<updated>2026-09-10T06:06:35Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-10T06:06:35Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=839ab937afbd0c68d1a4366e10145274b6f62d31'/>
<id>urn:sha1:839ab937afbd0c68d1a4366e10145274b6f62d31</id>
<content type='text'>
A wallet with deep Ark coins had a 633KB ark sync domain against the relay's
40KB budget: the publish silently failed forever, the slot stayed stale,
and every boot retried. Relays can't carry it (64KB event cap) and neither
can NIP-44 (64KB plaintext), so a domain over budget now goes to Blossom
(nostr blob storage, BUD-01/02/06/11/12): sealed into an envelope
("CSB1" + xchacha20poly1305 under an HKDF of the wallet's NIP-44 self-key),
uploaded to the configured servers, and the kind-30078 event carries only
{ blob: { sha256, size, servers } }. Readers resolve pointers from their own
outbox copy first, else from the pointer's servers plus the configured
list, hash-verified. Small domains are unchanged.

The outbox uploads before it publishes (upload/delete authorizations are
kind-24242 events signed at enqueue time, so delivery after logout still
holds no key), keeps a local copy for recovery, and deletes superseded
blobs once the replacement is acknowledged.

Servers: Settings → Nostr → Sync storage (any Blossom server; a typed list
is kept), else the user's BUD-03 list (kind 10063) with the coinos server
as mirror, else blossom.coinos.io.

blossom/server.js is that default: a Bun Blossom server that stores ONLY
these envelopes (magic check → 415 for anything else), serves every blob as
an octet-stream attachment with nosniff, requires signed auth for
upload/list/delete, and enforces per-blob size, per-key quota (oldest
evicted), rate limits, a disk cap and unused-blob expiry — it cannot serve
as a file host. tools/sync-blob-test.js spawns it and covers the policy,
the outbox flow and a fresh device resolving a pointer.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Nw1Dijik1tLVKRM1WkU7xE
</content>
</entry>
<entry>
<title>Durable sync outbox: fix the fresh-device phantom "Sent" that showed a zero balance (authored by codex).</title>
<updated>2026-09-07T06:52:21Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-07T06:52:21Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=989e29302e479277fb18ee105874c9a1eeb00be5'/>
<id>urn:sha1:989e29302e479277fb18ee105874c9a1eeb00be5</id>
<content type='text'>
Root cause of the "signed in and my balance is 0 with a huge Sent I never made": a device's snapshot carrying its change/new coins never reached the relay. The publish was a setTimeout debounce that got canceled on page-hide / logout / feature stop, so coins created by another session were never synced; a fresh device then loaded only the old (now-spent) coins, and reconcile dressed the spent inputs as a definitive "Sent" row and zeroed the balance. No funds ever moved — verified on the ASP that no such payment existed and the change coin was still spendable.

Fix (codex):
- src/sync-outbox.js (new): a durable outbox. Snapshots are signed synchronously at save time under the current identity (no cross-account mixing) and persisted already-encrypted to localStorage (no keys/plaintext in storage). Delivery retries with backoff and wakes on online/pageshow/visibilitychange/storage, surviving suspension, logout and relay outages. The last copy doubles as a local recovery source if an account cache is cleared.
- sync.js: replace the cancel-on-stop debounced publish with the outbox; fetchAllStates also merges the outbox's own (possibly-unpublished) events so a rejected publish is still recoverable locally; forgetAll clears it.
- nostr.js: factor out stateEvent() (synchronous signing) and decodeStateEvents(), shared by fetch and the outbox.
- ark.js: adoptArkSnapshot() applies a synced snapshot to a LIVE connected manager without restarting payment ceremonies (a snapshot arriving while connected previously stayed invisible until reload); the pre-handshake hold-key merges instead of overwrites; and the reconcile row is reworded from a red "Sent -N" to a neutral "History incomplete / Payment details not synced" (↻) with an explainer, since a spent input proves neither an amount nor a date.
- app.js: clearAll also clears the outbox (forgetAll).

Tests (codex): sync-outbox-test, sync-browser-test (real Chrome: relay rejects a snapshot, browser closes, reopens and retries; a second browser recovers the balance + history without reload), sync-split-test, plus build and morph smoke — all green.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
</content>
</entry>
<entry>
<title>On-chain tx history now UNIONS across every device's core snapshot (+ local state) at sync time, newest-first per txid, instead of adopting the newest core's list wholesale. The wholesale apply lost history: a device whose history fetch keeps failing (mobile 429 storms — the balance scan is cheap per-address calls, history is the heavy /txs pulls) publishes a THIN core with txs:[] and a fine balance, and every fresh device then adopted the newest=thin core and showed a balance with no transactions — adam's phone today, with three ~2KB thin cores on the relay shadowing the fat 7KB core from Aug 18 that still holds the history. Union is safe because history is append-mostly and every SUCCESSFUL local scan still prunes rows its addresses no longer report; a wiped device also self-heals the fleet, since its republished core carries the union'd txs back to the relay</title>
<updated>2026-09-04T15:41:47Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-04T15:41:47Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=174ce1027327230154b37b80e44c6b2d45929101'/>
<id>urn:sha1:174ce1027327230154b37b80e44c6b2d45929101</id>
<content type='text'>
Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
</content>
</entry>
<entry>
<title>Wallets go flat: every Spending and Savings is a top-level wallet in the switcher, named for its purpose, renamable apart — and a new wallet can carry its own seed or live at the next BIP84 account of one you already back up</title>
<updated>2026-08-15T21:32:28Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-15T21:32:28Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=643046319fa9a393374fc2348381d9505fe143eb'/>
<id>urn:sha1:643046319fa9a393374fc2348381d9505fe143eb</id>
<content type='text'>
Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_014tE4U5nEjGTQy8PwYqamfv
</content>
</entry>
<entry>
<title>Profiles wear their latest notes, each one zappable; a zap with no ark address to land on becomes a gift only their key can open</title>
<updated>2026-08-08T15:21:11Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-08T15:21:11Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=19c14131ce3fc69a06d3c0af8d445b056d703bf9'/>
<id>urn:sha1:19c14131ce3fc69a06d3c0af8d445b056d703bf9</id>
<content type='text'>
Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Sync just works: NIP-65 relays, no card; gift-locking leftovers gone</title>
<updated>2026-08-03T05:03:50Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-03T05:03:50Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=5a16f87c3ba44c9c5231b37cf626db06b63ce351'/>
<id>urn:sha1:5a16f87c3ba44c9c5231b37cf626db06b63ce351</id>
<content type='text'>
The Device sync card and toggle are gone — sync is always on, riding
the user's own NIP-65 (kind 10002) write relays: the login npub's
first, else the wallet key's, coinos relay as the fallback and always
included. A fresh wallet identity with no NIP-65 publishes one
(default relay) so other clients can find its events.

The npub-locked-gift remnants go with it: the per-wallet nostr address
card that pitched gift-locking, the how-it-works sentence, the dead
NostrSync.sendDM/sendNostrDM path, and the orphaned claim-code field.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
</content>
</entry>
<entry>
<title>Settings: surface wallet settings, add Nostr section, tuck rarities into Advanced</title>
<updated>2026-08-02T23:18:38Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-02T23:18:38Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=6308a2669388db05943f234b260f7d93f760f9a0'/>
<id>urn:sha1:6308a2669388db05943f234b260f7d93f760f9a0</id>
<content type='text'>
The main Settings tab now inlines the active wallet's own cards
(recovery phrase, name, public key) instead of linking to a sub-page,
followed by payment address, Ark status, network, and data source. All
nostr cards — address, account link, wallet connect, device sync — sit
under one Nostr heading via a new nostrSettingsCards seam. Offline
transfer, address rescan, and auto-logout move to an Advanced sub-page.
The Ark provider picker is gone: mainnet always rides coinos (a stale
stored choice no longer wins); dev networks keep their per-network
defaults. The per-account screen (Accounts → ⚙) reuses the same card
builders unchanged.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
</content>
</entry>
</feed>
