<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinosv3.git/src/features/sync.js, branch fdroid-v1.7</title>
<subtitle>coinos v3 — bitcoin, Ark, Lightning and nostr wallet (web + Android)</subtitle>
<id>https://git.coinos.io/coinosv3.git/atom?h=fdroid-v1.7</id>
<link rel='self' href='https://git.coinos.io/coinosv3.git/atom?h=fdroid-v1.7'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/'/>
<updated>2026-09-10T14:28:54Z</updated>
<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>
<entry>
<title>Split device sync into per-domain slots with bounded, dirty-only publishes</title>
<updated>2026-08-02T01:08:58Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-02T01:08:58Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=cbc3a5907c55c20c248fa80fa14528cb483688ad'/>
<id>urn:sha1:cbc3a5907c55c20c248fa80fa14528cb483688ad</id>
<content type='text'>
One monolithic snapshot per device outgrew a vanilla relay's 64KB event
cap the moment real payment traffic arrived, and sync silently died.
Snapshots now fragment into per-domain replaceable events (core wallet,
ark, nwc — an extension claims a domain by naming it), published under a
new ':x:' d-tag marker that pre-split clients don't match, so they can
never mistake a fragment for a full snapshot. Only domains whose content
changed republish, a refused publish retries on the next save instead of
being marked done, and readers apply full-snapshot semantics only to
core-carrying events. The ark projection also caps its history: spent
stubs at 200, done actions at 50 — the event size is bounded for life,
no special relay rules required.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>Add Nostr Wallet Connect (NIP-47) — hal as a wallet service</title>
<updated>2026-08-01T16:25:23Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-01T16:25:23Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=e09b76ea0ed13840426a56f031769f8d4154f34b'/>
<id>urn:sha1:e09b76ea0ed13840426a56f031769f8d4154f34b</id>
<content type='text'>
Other apps can connect over nostr and pay from this wallet without touching
its keys, within a per-payment and daily budget the user sets, revocable at
any time. hal holds no native Lightning balance, so every operation runs over
Ark: pay_invoice takes the swap bridge when configured and falls back to the
ASP's lightning-send, make_invoice mints on the ASP. NWC availability
therefore tracks Ark availability.

- kinds 13194/23194/23195, nip44_v2 and nip04, replying in whichever scheme
  the request used; every request gets a reply so clients never hang
- each connection gets its own service keypair, so revoking one can't affect
  the others and none is tied to the user's nostr identity
- budgets are checked BEFORE spending, not reconciled after
- requests are ignored unless signed by that connection's client key, and
  replayed/stale events (&gt;120s) can't re-trigger a payment
- Settings card to create, show (URI + QR) and revoke connections, and it
  says plainly that this only works while a tab is open

tools/nwc-test.js drives it as a real client: both encryption schemes, budget
refusal with a genuine over-limit invoice, unsupported-method errors, and a
request from an unauthorized key being ignored.

Co-Authored-By: Claude Fable 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
