<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinosv3.git/src/sync-outbox.js, branch fdroid-v1.6</title>
<subtitle>coinos v3 — bitcoin, Ark, Lightning and nostr wallet (web + Android)</subtitle>
<id>https://git.coinos.io/coinosv3.git/atom?h=fdroid-v1.6</id>
<link rel='self' href='https://git.coinos.io/coinosv3.git/atom?h=fdroid-v1.6'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/'/>
<updated>2026-09-10T06:12:13Z</updated>
<entry>
<title>Phone boot: remember opened DM wraps across boots; supersede stuck relay-only sync records.</title>
<updated>2026-09-10T06:12:13Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-10T06:12:13Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=57289fd3f87554e9981ddc2c63c3bba805d599bd'/>
<id>urn:sha1:57289fd3f87554e9981ddc2c63c3bba805d599bd</id>
<content type='text'>
A remote-signer (bunker) wallet re-opened the relay's last ~400 gift wraps
on EVERY boot — the seen-set lived in memory only — and each wrap costs two
NIP-46 round trips with a schnorr sign+verify apiece on the client. On the
Pixel that was ~4s of a 15s pegged boot. Opened (and given-up) wrap ids are
now kept in the messages feature state, bounded to 1500 id prefixes, and
loaded before the inbox subscription.

The outbox also replaces a record whose route changed even when its digest
matches: the 874KB relay-only ark record that could never publish would
otherwise have stayed in front of its Blossom successor forever.

Verified live: the wallet's 633KB ark domain uploaded to blossom.coinos.io
and its pointer was acknowledged by relay.coinos.io on the next boot.

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>
</feed>
