From 60db237749a1189d57b8783cd318c60d6df0cbcc Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Mon, 15 Jun 2026 23:15:44 -0700 Subject: Sync wallet state across devices via encrypted Nostr events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Persist wallet state (addresses, utxos, history, indices, fees) as a NIP-44 encrypted, parameterized-replaceable event (kind 30078) addressed to self on relay.coinos.io. The Nostr identity is derived from the same seed (NIP-06), so no extra secret. On load we pull the latest state from Nostr — a device with no local cache gets full state without re-scanning, and another device's updates propagate — using savedAt to keep the newest copy. localStorage stays as the instant/offline cache; state is published (debounced) on every change. (Relay note: added kind 30078 to the strfry write-policy allowlist on prod.) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'src/app.js') diff --git a/src/app.js b/src/app.js index c64c650..e59b1bd 100644 --- a/src/app.js +++ b/src/app.js @@ -391,9 +391,12 @@ async function enterWallet(mnemonic, passphrase) { return; } try { - // With a cache, only check the live frontier (cheap) instead of a full - // re-scan; a full scan still runs for a brand-new wallet and periodically. - if (hadCache) await wallet.refreshLive(); + // Cross-device state: pull the latest from Nostr (may supply state on a + // device with no local cache, or a newer copy from another device). + const hadNostr = await wallet.syncFromNostr(); + // With existing state (local or remote), only check the live frontier; + // a brand-new wallet does one full scan. + if (hadCache || hadNostr) await wallet.refreshLive(); else await wallet.scan(); wallet.startRealtime(); } catch { -- cgit v1.2.3