| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Advanced
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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
|
| |
|
|
|
|
|
|
|
|
|
|
| |
The app already presents as Coinos; this finishes the job in the repo:
package name, README, GitHub link, code comments, and the stale locale
lines that still explained the old "Hal Wallet" name (they fall back to
the English tribute line). Kept: the hal-nwc-pouch IndexedDB name (an
upgrade-compat identifier), the halwallet.app domain in the BIP-353
names feature (live infrastructure), and the Hal Finney tribute itself.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
An address only worked for wallets that speak DNS payment instructions
or BOLT 12; everything else looks for /.well-known/lnurlp/<name>. The
registrar now serves LUD-06/16 (with NIP-57 zap support), preferring
the path that touches no funds: if the recipient's wallet published a
CLINK offer key, we ask IT for the invoice over nostr and hand the
result straight back — the payment never passes through us. Only when
nobody answers does our node mint the invoice, and the existing settle
forwarder pushes the money on to their address, signing a zap receipt
when the payment was a zap.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
| |
Icons, favicon, and PWA assets come from the coinos brand set; the
accent goes monochrome (black in light mode, white in dark, with a
proper on-accent text variable so buttons stay legible both ways) and
the inline bitcoin mark inherits the text color.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The wallet is the next version of coinos, deployed at v3.coinos.io
(same build as halwallet.app) ahead of an eventual migration of
coinos.io itself. App name, manifest, notifications, and the NWC alias
all say Coinos; the Hal Finney tribute stays.
The registrar is multi-domain (per-domain Cloudflare zones; existing
halwallet.app names keep working) and new claims default to @coinos.io.
Names belonging to existing coinos users are reserved for them — the
registrar checks the live coinos API before granting a claim — until
the migration gives those users a way to claim their own.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
CLINK offers (noffer): every wallet gets a static offer keypair (synced
across devices, first-minted wins) answering kind-21001 invoice
requests — from open tabs and from the service worker while closed,
which mints against the mirrored state (the record now carries chain-4
and chain-5 key windows) and raises an open-to-receive notification.
Zap requests ride along and their kind-9735 receipts publish once the
payment is claimed. The code is shown in the payment-address card for
pasting into a Nostr profile; the notifier watches and relays kind
21001 alongside NWC.
Payment names also gain a Lightning offer: the registrar creates a
BOLT 12 offer per name on the node and appends lno= to the DNS record;
a settle-forwarder pushes each received payment on to the name's ark
destination from a small float wallet (custody window: seconds between
Lightning settle and the ark send).
Name resolution now runs full client-side DNSSEC validation
(src/dnssec.js): binary DoH, root-anchor chain walk, RSA + ECDSA via
WebCrypto — the resolver is untrusted transport, verified against live
signed zones including the canonical BIP-353 test name.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Dropped: silent payments (feature, wallet chains, scan worker, indexer
config), Boltz submarine/reverse swaps (feature + swap.js + tools), the
third-party ark swap bridge (config, client flows, settings card — the
ASP's own Lightning send is the one path now), and the Consolidate
coins settings action.
The NWC pouch is gone with them: background answering now spends the
WHOLE ark balance. The IndexedDB record mirrors the real manager state
plus the chain-3 keys for its live coins and a change-index window; the
worker pays through the ASP exactly like the open app, and the app
folds the worker's spends, change, and key allocations back in on
connect (additive merge + reconcile against the server). A leftover
chain-6 pouch from the short-lived pouch era is swept home
automatically using the keys stored in its own record. The stated
tradeoff: same-origin script access now reaches the ark balance, not a
carve-out — the ark balance is walking-around money, the on-chain
wallet's keys are never mirrored, and per-connection NWC budgets remain
the enforced ceiling.
Bundle: 726KB -> 602KB.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The no-tap half of background wallet-connect. The push payload carries
the (encrypted, client-signed) request event; a bundled service-worker
responder (src/sw-nwc.js -> nwc-respond.js) finds the matching pouch in
IndexedDB, decrypts with the connection's service key, and answers
get_info/get_balance/list_transactions from pouch state. pay_invoice
pays through the swap bridge with an ArkManager running over the
pouch's chain-6 key window (one HTLC lock, one HTTP swap), enforcing
per-payment and daily budgets from the pouch's own spend log, checking
the notifier's /answered endpoint so it never double-pays or shouts
over an open device, and persisting the swap before handing it off so
the app can refund a crashed flight. Anything it can't handle falls
back to the wake-the-user notification.
The notifier grows /publish (verified 23195s only, rate-limited) as the
worker's reply pipe and /answered for race avoidance. The app gains
pouch funding (mailbox-less arkoor self-send whose recipient bytes we
keep), emptying, spend/budget reconciliation on open, and refund
recovery with the pouch's own keys.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
The notifier only wakes a device for service pubkeys it was told about
at registration time, so recreating a connection silently killed
background wake-ups — the registration kept watching the revoked key
forever. Re-register (debounced; the server replaces by endpoint) on
init, create, revoke, and sync-merge whenever background alerts are on.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
Loose leader election for pay_invoice across a user's open tabs: before
handling, defer a beat and stay silent if any reply for the request
already exists on the relays; and before sending an ERROR reply,
re-check — a device that lost the race (mid-init, stale state) was
replying 'superseded' after another device's success, and clients
trusted the later error, marking paid zaps as failed.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
The pool's reconnect doesn't heal every socket death (an errored socket
sets skipReconnection and gives up), and a wallet service with a dead
subscription looks exactly like one that's closed — the client spins
with no error, which is what happened to tonight's zaps. Re-subscribe
every 90s and on online/visibility transitions; the since window plus
the replay guard make the churn and any duplicate deliveries harmless.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
onRequest handled every request against the connection object captured
at listen() time, so each spend was recorded against a stale snapshot —
spentToday restarted from the old value on every payment and the daily
budget never actually bound. The connection is now re-resolved from
stored state per request, which also stops serving a connection revoked
on another device mid-session. recordSpend now publishes the sync
snapshot so the spend (and the payment's movement + vtxo change) reaches
the user's other devices. Test: two payments must accumulate to 52.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
relay.coinos.io's write policy was rejecting every 23195/13194 hal sent
(kinds were whitelisted to coinos server pubkeys — incompatible with
hal's per-connection service keys) and the rejections temp-banned the
IP. publishOn swallowed all of it, which disguised a relay-side policy
problem as a client bug. The relay policy is fixed separately; here,
refused publishes now log the relay and reason, and the 13194 info
event goes out once per connection per session instead of on every
listen() so replies keep their share of the relay's per-IP rate budget.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
| |
Every guard in onRequest now names itself when it drops an event, and
listen()/stop() log subscription generations so a later listen() killing
the live sub becomes visible. subscribeOn passes extra params through so
relay-initiated CLOSED reasons surface too.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A request that fails to decrypt was returning without a reply, on the theory
that undecryptable traffic wasn't ours. But the event is addressed to this
connection's service key and signed by its client key — it IS ours, so a
failure there is our bug, and dropping it looks exactly like the wallet being
offline. That is why Amethyst spun with no way to tell why.
Now it logs the reason, records it for the Settings card, and replies with an
INTERNAL error (in both schemes, in case scheme detection was itself wrong) so
the client fails fast. The card also shows the last request accepted, so the
listener can be seen working without devtools.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Step 3, first half. Android Chrome freezes backgrounded PWAs and closes their
relay sockets, so a phone with hal "open" answers nothing — which is what made
Amethyst spin. Push is the only way to reach a frozen app.
- service worker gains a `push` handler. It deliberately does NOT spend: if a
window is open it nudges it to re-listen, otherwise it raises a notification
that opens hal on tap. Putting a spend path in the worker is the remaining
half and wants its own testing.
- hal-side: request permission, subscribe with the notifier's VAPID key, and
register its service pubkeys with nwcpush.coinos.io. Turning it off
deregisters, unsubscribes, and clears the pouch — no reason to leave
spendable keys where a worker can read them if nothing will.
- a Settings toggle, and the ark feature exposes the ASP context the pouch
record needs.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
A connection made on the phone existed only there, so only that device could
answer it — and if that device was asleep, a client just spun. Connections now
ride the encrypted device-sync snapshot, so whichever device is awake can
serve them. The snapshot is nip44-encrypted to a key derived from the seed,
the same channel that already carries ark vtxo bytes and gift secrets.
The merge is a union by id with two rules that matter:
- REVOCATION WINS, so a stale snapshot can never resurrect a revoked
connection
- the higher same-day spend wins, so a stale device cannot hand a connection
back its daily budget
Creating or revoking a connection publishes the snapshot immediately rather
than waiting for the next state change.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The connection URI advertised up to four relays (sync relays + profile
relays) while the subscription only ever listened on the sync relay, so a
client could publish its request somewhere hal wasn't and spin forever
waiting for a reply. Amethyst did exactly that.
NWC now has its own relay list used for all three things that must agree:
the URI, the subscription, and the replies. It no longer depends on device
sync being enabled, which would otherwise have made the whole wallet service
a silent no-op.
Also republish the kind 13194 capability event on every start — a client that
can't find it just spins, and a single publish at creation time is easy for a
relay to miss.
The relay transport is now injectable so the protocol can be exercised
without a live relay (tools/nwc-test.js).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
|
|
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 (>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 <[email protected]>
|