<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinosv3.git/nwcpush, 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-04T16:25:23Z</updated>
<entry>
<title>nwcpush: zaps, payments, DMs and mentions push high-urgency, kept a day</title>
<updated>2026-10-04T16:25:23Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-04T16:25:23Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=b0fdec8eef365805f7a3a57bd5ec5563472785f9'/>
<id>urn:sha1:b0fdec8eef365805f7a3a57bd5ec5563472785f9</id>
<content type='text'>
Android holds 'normal' pushes while the phone dozes and drops them once
the 1 h TTL runs out; a pocketed phone missed most zaps and mentions
that the laptop showed. Reactions and community chat stay normal.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_011bvzKaZosXGHkigcGcSzAR
</content>
</entry>
<entry>
<title>Zaps and reactions get their own notification switches.</title>
<updated>2026-10-01T19:35:15Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-01T19:35:15Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=1da40523fc00b9963012abe067a6c4910c7578b6'/>
<id>urn:sha1:1da40523fc00b9963012abe067a6c4910c7578b6</id>
<content type='text'>
The notifier now watches kind-7 reactions p-tagged at the user (opt-in
by an explicit flag, so older apps that can't word them stay quiet) and
sends zap receipts as 'zap' to apps that report the category, 'payment'
to older ones. Coinos zap receipts carry their amount into the push. The
service worker names the reactor when it knows them and stays quiet for
a muted one.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01VP5Ljqk7EYcm5QQAbzDwog
</content>
</entry>
<entry>
<title>A reply or a mention on nostr can buzz your phone.</title>
<updated>2026-09-15T00:42:04Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-15T00:42:04Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=5a0f70fcedd7a02a159de1819082981a6fd6244f'/>
<id>urn:sha1:5a0f70fcedd7a02a159de1819082981a6fd6244f</id>
<content type='text'>
The push notifier now watches kind 1 for the user's pubkey beside DMs and
zap receipts, and pushes who, what, and an excerpt. The worker shows
"New reply to your post" or "You were mentioned" with the text, and a tap
lands in the Notifications list — as ?open=notifs when a window has to
be opened, or as a worker message when one already is. It has its own
switch, "Replies and mentions on nostr", in Settings → Notifications,
on by default like payments and DMs.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
</content>
</entry>
<entry>
<title>One device pays a zap, whichever of them heard it first.</title>
<updated>2026-09-14T16:06:02Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-14T16:06:02Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=671d2a96474f59ca9234c876ce6604d03de95727'/>
<id>urn:sha1:671d2a96474f59ca9234c876ce6604d03de95727</id>
<content type='text'>
Every awake device of a wallet hears a wallet-connect pay request within
the same second, and the per-tab lock only reaches one browser profile:
two profiles on one laptop each locked an HTLC for the same 21-sat zap.
The push notifier now hands each pay request to the first device that
claims it; a device that loses the claim stands down silently, and a
device that cannot reach the notifier answers as before so nobody goes
quiet over a hiccup. The worker path claims the same way.

Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01QYJfZ4A1TDCEY4Foq3CApR
</content>
</entry>
<entry>
<title>UnifiedPush: reach a phone that has no Google in it.</title>
<updated>2026-09-12T17:05:10Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-12T17:05:10Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=a846211262f37238c001e5210938749294f8db6a'/>
<id>urn:sha1:a846211262f37238c001e5210938749294f8db6a</id>
<content type='text'>
Web push on Android is Firebase's, so a phone without Google Play Services —
GrapheneOS, CalyxOS, any de-Googled Android — cannot be woken by a web app at
all. UnifiedPush is the way that doesn't involve Google: the user runs a
distributor (ntfy and friends), apps ask it for an endpoint, and whoever
wants to reach the device POSTs to that endpoint.

THE WRAPPER speaks the protocol directly — four broadcasts each way, so it
pulls in no library; an APK this small has no business growing a Kotlin
runtime for eight intents. It asks the distributor on every launch (the call
is idempotent, and it's how a distributor installed later gets noticed),
raises a notification when a message arrives, and acknowledges it so the
distributor stops resending.

THE ENDPOINT reaches the web app on the launch URL as ?up=, the same road
payment intents already travel — because the distributor talks to the app and
only the web app knows which pubkeys are worth watching. It's registered with
the notifier in place of a browser subscription, for notifications and for
NWC alike, and needs neither permission nor push service.

THE NOTIFIER sends to either kind: one deliver() that POSTs the payload to a
UnifiedPush endpoint, or hands it to web-push as before. Same payloads, same
404/410 pruning. The endpoint is the only secret there, so it's never logged
in full.

Verified: a POST to a live ntfy endpoint delivers the payload byte for byte,
so the app parses exactly what the notifier sent; the APK builds, carries the
receiver, and is signed with the certificate assetlinks already pins (v1.6).
tools/unifiedpush-test.js drives a browser with no push service and checks
the endpoint is taken off the URL, kept, and registered.

Honest about the limit: a UnifiedPush message wakes the ANDROID app, not
Chrome's service worker, and the wallet's keys live in the browser's storage
where nothing here can reach them. So a zap request arrives as a notification
you tap, and the wallet answers when it opens — one tap instead of none.
Automatic answering while closed still needs a browser with a push service.

Co-Authored-By: Claude Opus 5 (1M context) &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
</content>
</entry>
<entry>
<title>Heal dead push subscriptions from a VAPID key rotation — the reason notifications went silent. registerPush reused whatever getSubscription() returned and only ever subscribed fresh when there was none; a subscription made against an OLD VAPID key can never receive a push (the notifier's send fails with a permanent 403, which was never pruned, and the client kept re-registering the same dead endpoint). Now the client fetches the current VAPID key first and, when an existing subscription's applicationServerKey doesn't match, unsubscribes and re-subscribes against the current key — so a device self-heals on next open. And the notifier prunes 403 endpoints alongside 404/410. Deployed nwcpush on cs</title>
<updated>2026-09-06T04:47:30Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-06T04:47:30Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=7e96dc4d4292b29156ec6b9b9c7009f8c60106f1'/>
<id>urn:sha1:7e96dc4d4292b29156ec6b9b9c7009f8c60106f1</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>Payment push notifications are scoped to their network, so a mainnet payment stops opening the staging PWA. The same seed derives the same nostr pubkey on mainnet and staging, so both PWAs registered push under one pubkey and the notifier — matching only by pubkey — fanned every mainnet payment out to the staging subscription too; tapping it opened staging.coinos.io. Now the client stamps its network on the push registration (pushWatch net: getNetwork()), the registrar declares its own network on the /notify call (CFG.forwarder.network), and nwcpush delivers a payment only to registrations whose net matches — with a rollout-safe fallback: a device that hasn't re-registered yet (net=null) still gets the push, and a caller that omits net still reaches everyone, so nobody goes dark mid-deploy. Deployed names + nwcpush on cs</title>
<updated>2026-09-06T04:38:09Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-09-06T04:38:09Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=ec1d6b8c6da0f9e4e169f4e8f95b88133207b741'/>
<id>urn:sha1:ec1d6b8c6da0f9e4e169f4e8f95b88133207b741</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>Settings grows a Notifications page — per-category switches the notifier honours server-side — and the status bar gets a real coinos badge instead of a black blob</title>
<updated>2026-08-14T08:19:44Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-14T08:19:44Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=7a7b2c61fe8394844fa306debb84917454ade7b0'/>
<id>urn:sha1:7a7b2c61fe8394844fa306debb84917454ade7b0</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>Only people you know get to buzz your phone</title>
<updated>2026-08-04T05:07:13Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-04T05:07:13Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=1f96f513060d65c3a4e7c7cda100cde4ef8ee2c3'/>
<id>urn:sha1:1f96f513060d65c3a4e7c7cda100cde4ef8ee2c3</id>
<content type='text'>
A NIP-17 wrap is signed by a throwaway key with the sender sealed under
two layers of nip44, so no server can filter DMs by who sent them. The
notifier now ships the wrap inside the push payload and the service
worker opens it locally: a message from someone you follow arrives with
their name on it, a stranger's is dropped, and your own sent-copy — every
DM is wrapped to the sender too — no longer notifies you about what you
just typed.

That needs the nostr identity key in the worker's IndexedDB, which
dm-inbox.js states plainly: for a seed-derived identity it is a second
copy of a key localStorage's seed already yields, and it exists only to
keep strangers out. A remote signer never hands its key over, so those
wallets store no secret and keep getting unfiltered notifications rather
than silence.

Silence is always the riskier failure here, so the rule leans the other
way whenever it isn't sure: a wrap we can't open notifies, an account
with no contact list published notifies (otherwise a new user's welcome
message would vanish), and anyone you already have a thread with counts
as known whether or not you follow them. The server-side DM cooldown
drops to a second — with the device discarding pushes, a throttle there
would let a discarded one swallow the message behind it.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
</content>
</entry>
<entry>
<title>Push notifications for payments and messages; offline Spending notice</title>
<updated>2026-08-03T05:41:39Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-08-03T05:41:39Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3.git/commit/?id=d60697c8d1cc0b862d7488be4cf8ee21cafdfd1d'/>
<id>urn:sha1:d60697c8d1cc0b862d7488be4cf8ee21cafdfd1d</id>
<content type='text'>
The nwcpush notifier grows a second registration kind alongside NWC:
a notify watch of what it can see without keys — kind-1059 wraps
p-tagged at the user (DMs, direct invites), zap receipts (9735/9737),
and wraps authored by the user's community channel keys (chat). It
pushes a typed nudge, throttled per device (chat bursts collapse); the
service worker shows a generic notification unless a window is visible,
and tapping opens the app. The names registrar reports settled
lightning-address receives server-to-server (token-gated /notify), so
the common payment path notifies even with no nostr event in sight.

The app registers silently whenever permission is already granted and
keeps the watch current as communities change; a dismissible card on
the chat home offers to enable it otherwise. Ark zap receipts (9737)
join the relay whitelist so the payment watch can see them.

Also: the offline Spending receive pane now says you're offline and
offers a one-tap switch to Savings instead of spinning forever.

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