<feed xmlns='http://www.w3.org/2005/Atom'>
<title>coinosv3-server.git/nwcpush, branch master</title>
<subtitle>coinos v3 server — payment-name registrar, NWC push, Blossom sync storage, feed</subtitle>
<id>https://git.coinos.io/coinosv3-server.git/atom?h=master</id>
<link rel='self' href='https://git.coinos.io/coinosv3-server.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3-server.git/'/>
<updated>2026-10-23T14:39:21Z</updated>
<entry>
<title>nwcpush: NWC wake pushes live 120 s at APNs, the request window, so a phone in deep sleep gets the wake late rather than never</title>
<updated>2026-10-23T14:39:21Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-23T14:39:21Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3-server.git/commit/?id=8ea5ae33f14ade28a0809a6dc267b44570c8e74f'/>
<id>urn:sha1:8ea5ae33f14ade28a0809a6dc267b44570c8e74f</id>
<content type='text'>
Co-Authored-By: Claude Fable 5.1 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>nwcpush: calls push only to an app that says it rings for them (reasons.call), not to an older one whose worker would show a call as chat.</title>
<updated>2026-10-18T16:01:17Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-18T16:01:17Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3-server.git/commit/?id=56cad7ef0da868e7c7c2b3c902143acd611b4c48'/>
<id>urn:sha1:56cad7ef0da868e7c7c2b3c902143acd611b4c48</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
</content>
</entry>
<entry>
<title>nwcpush: calls wake a closed app. Kind 21059 wraps to a registered pubkey are watched; one big enough to be a call offer (an SDP) pushes 'call' at high urgency, a minute's TTL, naming the wrap, which the device fetches from GET /wrap/&lt;id&gt; (kept two minutes; a push can't carry it) and opens with its own key. ICE candidates and hang-ups are too small to wake anyone. A 'call' opt-out like the other reasons.</title>
<updated>2026-10-18T16:00:22Z</updated>
<author>
<name>Adam Soltys</name>
<email>adam@coinos.io</email>
</author>
<published>2026-10-18T16:00:22Z</published>
<link rel='alternate' type='text/html' href='https://git.coinos.io/coinosv3-server.git/commit/?id=2e897103836a6814a39dbb905d6a7ed1417344d7'/>
<id>urn:sha1:2e897103836a6814a39dbb905d6a7ed1417344d7</id>
<content type='text'>
Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_013ZDf1cyCQLkfjftYhymZAg
</content>
</entry>
<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-server.git/commit/?id=4edc04f95fc41c402897303f5c6c0eed01c6a45b'/>
<id>urn:sha1:4edc04f95fc41c402897303f5c6c0eed01c6a45b</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-server.git/commit/?id=ff6786509c639c33babc979b8cf28b6338af00ba'/>
<id>urn:sha1:ff6786509c639c33babc979b8cf28b6338af00ba</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-server.git/commit/?id=772e7c4499c0252f01521620c885c0dcaab84adb'/>
<id>urn:sha1:772e7c4499c0252f01521620c885c0dcaab84adb</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-server.git/commit/?id=ac5ba5c425e2762bc24d33f0cf8521483f68bc8c'/>
<id>urn:sha1:ac5ba5c425e2762bc24d33f0cf8521483f68bc8c</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-server.git/commit/?id=4bf68601fa66d56ff485fde4e096e3fa6dc274bf'/>
<id>urn:sha1:4bf68601fa66d56ff485fde4e096e3fa6dc274bf</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-server.git/commit/?id=1da2663aff784e9061aef1f30b842a1ca216b905'/>
<id>urn:sha1:1da2663aff784e9061aef1f30b842a1ca216b905</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-server.git/commit/?id=5de0ef4232270b8b0d67b2c001b62fa316c939a2'/>
<id>urn:sha1:5de0ef4232270b8b0d67b2c001b62fa316c939a2</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>
</feed>
