| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
| |
Every synced snapshot re-ran initArk() while Ark was unconnected, and
initArk() calls stopArk() first — so each arriving snapshot tore down
and restarted the connection, flashing 'Connecting to the Ark server…'
and jittering the page. Automatic re-inits are now skipped while a
connection exists or is in flight, and rate-limited to one per 20s;
user-driven reconnects (switching servers, the connect button) are
unaffected.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
A seed imported on a new device (or a new origin — v3.coinos.io) has no
local ark state and no live connection, so the anti-poisoning guard
can't prove the incoming snapshot's owner and parks it in the hold.
Nothing then triggered the connect that would adopt it — ark is lazy on
fresh wallets — so the balance never appeared. Parking with no local
identity now kicks the init; connecting learns the server pubkey and
the held state is adopted iff it matches.
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Receiving: a Settings card claims a username, signed with the wallet's
nostr key; the new names registrar (names/server.js, deployed at
names.halwallet.app) enforces first-come ownership by that key and
mirrors the claim into a DNSSEC-signed TXT record at
<name>.user._bitcoin-payment.halwallet.app whose BIP-21 URI carries the
wallet's reusable ark address. The record re-registers itself if the
ark address ever changes, and a collapsible hint shows the single CNAME
a user adds to serve the same name from their own domain (BIP-353
supports delegation when both zones are DNSSEC-signed).
Sending: a pasted name@domain resolves over DNS first — two independent
DoH resolvers must agree and both report the DNSSEC AD flag (full
client-side chain validation is the stated follow-up) — an ark=
instruction pays natively, an on-chain path fills the send form, and a
name with no DNS record falls back to the Lightning-address zap flow.
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
Tonight's payments pushed the encrypted snapshot past strfry's 64KB
maxEventSize and every publish after the first zap was silently
refused — the other device just showed no history, with nothing logged
anywhere. The relay's limit is raised to 256KB; client-side, movements
older than the last 50 now drop their bolt11/preimage (~half a KB per
ln-send) and the cap tightens to 200, and a refused snapshot publish
logs the relay's reason.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
The sync merge is additive by design, so a vtxo spent on one device can
survive in another device's state looking spendable. The interactive
send path reconciles on intent; the NWC path connected and spent blind,
so a wallet answering from stale state picked an already-spent coin and
the ASP refused with 'vtxo … is not spendable (state: spent)'.
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The phantom balance came back through the sync path. Snapshots published by
older builds carry no ASP tag, so the tag check passed them, and with the
local namespace empty the merge returned the snapshot wholesale — writing
second.tech's vtxos straight into the coinos namespace, where the manager
then stamped them as coinos'.
Two fixes, both grounded in the fact that a vtxo names its own cosigner:
- the sync path derives the snapshot's true owner from its vtxos and parks it
under that owner instead of merging when it isn't provably ours
- ArkManager.init drops any state whose coins were cosigned by a different
server, whatever the stamp claims. Stamps are something we wrote; the vtxo
bytes are evidence.
Found by tracing localStorage reads in the live tab: the namespaced key was
being *created* during load, which pointed at the merge rather than at
storage keying.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The first namespacing attempt could brand one ASP's coins with whichever
server happened to be connected, filing them under the wrong namespace — a
phantom balance that survives both later fixes because the damage is already
written to localStorage.
loadArkState now checks the stamp against the vtxos themselves. A vtxo names
its cosigner in its own bytes, which is fact; the stamp is just something we
wrote. When they disagree the state is misfiled: it moves to a holding key
under its true owner and this namespace shows nothing. adoptArkState claims
from that holding area when the matching server connects.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The previous fix still let a phantom balance through: loadArkState fell back
to the pre-namespacing key when a namespace was empty, and the UI renders
from that before any server is contacted — so the guard, which runs on
connect, never got a say.
loadArkState now returns only the namespaced state. Legacy state is claimed
through adoptArkState(serverPubkey), which proves ownership first: from the
state's own stamp, or failing that by decoding a vtxo, whose wire format
names its cosigner. No network call, and it cannot guess wrong. On a match
the state moves into the namespace and the legacy key is retired; on a
mismatch it is left untouched for its real owner.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Switching providers showed the previous server's balance and tried to resume
its in-flight board ("1 deposit confirming" against a server that had never
heard of it). A vtxo is only meaningful against the ASP that cosigned it, so
state must be scoped to both the wallet and the server.
- ark state keys gain a namespace derived from the ASP endpoint, so switching
providers switches state and switching back restores it
- state is stamped with the server pubkey; on connect a mismatch is discarded
rather than displayed. This also self-corrects the one-time adoption of
pre-namespacing state, which can only guess which ASP it belonged to
- mergeArkStates refuses to fuse two servers' states, and the nostr sync
snapshot carries (and filters on) the ASP it came from, so a device on a
different provider can't inject foreign vtxos
Nothing is deleted: each ASP's state lives under its own key, and the legacy
key is left in place for whichever server rightfully claims it.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- ark presets gain coinos entries (ark.coinos.io on mainnet,
ark-staging.coinos.io on mutinynet) alongside Second's
- new per-network swap-bridge setting (preset/custom URL + access token)
with its own Settings card
When a bridge is configured, a bolt11 quote asks it first and shows its fee;
paying locks an HTLC only the bridge can claim, and only by revealing the
invoice preimage. Any failure — unreachable bridge, refused swap, unpayable
invoice — refunds and falls back to the ASP's own lightning-send, so the
payment never dead-ends. Hanging swaps are refunded on reconnect.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
- a rejected cosign (gRPC INVALID_ARGUMENT) returns the input vtxo to
spendable and fails the action instead of leaving it pending forever —
the stuck-pending state that pushed small zaps onto Boltz's 25k minimum
- ark ln-pay now takes over when funds are settling (retrying quote) rather
than falling through to Boltz
- typing a destination clears stale send errors (also cleared on tab
switch); paste path likewise
- typed lightning addresses no longer auto-advance mid-keystroke (the
matcher fired at `[email protected]` before `.com`, yanking the form away): the send
form offers an explicit "Zap <addr>" button; pasted/scanned addresses
still advance immediately
- ln-recv: HTLC vtxos anchored to a not-yet-confirmed pool round park and
retry instead of throwing; the requested HTLC expiry is anchored on first
prepare so retries can't inflate it past the server's grant
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The server rejects an arkoor mixing dust and non-dust outputs, which broke
small Lightning payments (a 45-sat HTLC next to change) and tiny plain
sends. Mirror bark's isolation: dust outputs route through a combined
isolation output spent by a fanout tx, splitting a non-dust output to pad
the dust below-threshold. Outputs can therefore split across two vtxos, so
the manager now classifies results by policy instead of index (legacy
single-field actions still resume).
Regtest now runs the full stack: dedicated cln-ark node (secondark/cln-hold,
hold plugin + xpay) wired into captaind with a channel to the coinos dev clc
node. tools/ark/ln-e2e-test.js proves pay (5000 sat and a 25-sat dust zap),
hold-invoice receive with claim, and spending the claimed vtxos.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The ASP is the HTLC counterparty (second.tech's protocol), so no external
swap provider is needed: paying a bolt11 converts pubkey vtxos into
ServerHtlcSend vtxos the server can only claim by revealing the invoice
preimage; receiving grants ServerHtlcRecv vtxos claimed by revealing ours.
Failure paths cosign the HTLC back (revocation) — proven end-to-end against
regtest captaind, which independently derives the HTLC taproots.
- ark/lightning.js: bolt11 decode, lightning fee schedules, all pay/receive
gRPC-web wrappers (cosign, initiate, status, revocation, hold-invoice
receive flow, anti-DoS attestation)
- ark/send.js: arkoor builder generalized to policy outputs (pubkey /
serverHtlcSend / serverHtlcRecv), two-leaf taproot trees, HTLC clause
scripts, reusable per-part cosign helpers for package RPCs
- ark/manager.js: ln-pay and ln-recv crash-safe action state machines
(deterministic receive preimages from the seed, chain 5), mailbox
lightning notifications, unpayable-invoice -> instant revocation
- proto.js: lightning ArkInfo fields + mailbox lightning message decode
- validate/refresh: policy-aware vtxo validation and reconstruction
- features: bolt11 routes via ark when the balance covers it (Boltz stays
the fallback), lightning-invoice receive on the Ark pane
- tools/ark/ln-test.js: harness proving HTLC cosign + revocation against
captaind; manager-test.js updated for the consolidated bc-container chain
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Send a Lightning zap to a nostr npub, a lightning address (name@domain),
or an lnurl. New src/lnurl.js does the resolution half — target → LNURL-pay
params → signed kind-9734 zap request → callback → bolt11 — and the new
zaps feature (src/features/zaps.js) drives the amount/comment UI, then hands
the invoice to the existing Boltz submarine-swap flow via a new
swaps.startLnPay hook, so there's one tested pay/review/confirm/success path.
Routing: a bolt11 is still caught by swaps; an npub still prefers an instant
free Ark zap when Ark can serve it, and only falls through to a Lightning
zap otherwise — ark's "no Ark address" screen also offers an explicit
"Zap over Lightning" fallback (gated on the zaps feature's canLnZap). A real
zap receipt is attributed only when the server advertises allowsNostr and we
know the recipient's pubkey; otherwise it degrades to a plain LNURL-pay.
Nostr seam gains nostrSign (finalize without publishing), nostrProfile
(kind-0 with lud16/lud06), and nostrRelays. Lightning addresses are
debounced in matchSendText since their regex matches mid-typing.
Verified: unit tests for parse/encode/zap-request; a live resolve→signed
9734→payable mainnet bolt11 against coinos; and an in-browser DOM smoke
(zap form + LNURL params over CORS + allowsNostr hint + handoff).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Cross-device ark sync silently failed: the full snapshot (deep-genesis
vtxo bytes + done-action signing hex) was ~153KB and relay.coinos.io
rejects events over 64KB, so no publish ever landed and other devices
never saw the balance. The synced ark state is now slimmed — spent
vtxos drop their signed bytes (kept only for spendable/pending, which a
device might spend/exit), done board/offboard/exit actions keep just
the fields history labels need (hex stripped), in-flight actions and
movements beyond the last 300 are dropped. Local storage keeps the full
state; only the published copy is slim. ~150KB -> well under 64KB.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| | |
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
Cross-device ark sync only re-published on a state change (send/board),
so a device holding ark funds but making no transaction never wrote its
state to the relay — another device could never see those coins (and a
legacy shared-tag snapshot that got clobbered to zero-ark stayed that
way). connectArk now calls saveCache once on connect when there are
vtxos, publishing this device's balance to its per-device slot so every
device's ark funds reach the relay and get merged, no transaction
needed.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
After a refresh a wallet reverts to watch-only unless the seed was
saved to device — and ark balance/history were gated behind
arkAvailable() (which excludes watch-only), so the on-chain balance
showed but the ark balance vanished. The ark state is persisted
locally and should display read-only exactly like the on-chain
balance. arkStateNow() now reads it whenever Ark is configured for the
network, regardless of watch-only; the Exit link (which needs keys to
act) is hidden for watch-only. Verified: full wallet shows balance +
Exit link, a locked/watch-only wallet shows the balance alone.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
The cooperative offboard was buried under the receive-tab board form
and the unilateral exit lived in Settings — both unintuitive. Now a
small 'Exit' link next to the Ark balance opens a dedicated page that
explains and offers both: cooperative (server co-signs, fast, no
on-chain funds needed, small server fee) and unilateral (trustless,
timelocked, pays mining fees). In-flight exit progress (hop counter,
timelock countdown, cancel) moved here from Settings; the receive pane
loses the offboard button and Settings loses the exit button.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The balance line and history-row icons read the LIVE ark manager,
which is null until connectArk() finishes its async handshake — so on
refresh the ark balance briefly vanished and ark rows flashed the
Bitcoin icon before flipping to the ark mark. Read-only hooks
(balance, decorateTxRow, historyEntries, detail views) now fall back
to the persisted ark state from storage via arkStateNow(), so they
paint correct on first render; the manager connecting later just keeps
it fresh with identical data (no visible change). Verified: balance
present and board row shows the ark icon from the first 50ms sample
after reload.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
Off-chain Ark rows (arkHistoryItem) showed an arrow + a redundant 'Ark'
text chip; now the Ark mark sits in the direction-colored circle and
the chip is gone — the label + signed amount carry direction. The
detail header gets the badge instead of the chip. And the receive
selector's Bitcoin option uses coinos's real bitcoin.svg (orange disc +
tilted white B) instead of a bare ₿ character.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
The receive address-type picker was a native <select>, which can't
render SVG in <option>s. Replace it with a lightweight custom dropdown
(icon-select) that shows a glyph per rail — ₿ Bitcoin, ⚡ Lightning,
the Second mark for Ark, 🕶️ Silent payment — closing on select or an
outside tap. Receive modes gained an icon field; the address option is
built in app.js. Silent-payment receipts (which merge into history as
on-chain-looking rows) now get a rail glyph + SP tag via a
decorateTxRow so they're distinguishable, matching how ark/gift rows
already decorate.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
| |
Use ARK_MARK (bare currentColor glyph) instead of the app-badge in the
settings card header, so it sits like the sibling feature glyphs
(lightning/SP/gifts) rather than a heavy dark square.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
The ⚔ was a U+2694 emoji stand-in. Swap in Second's actual mark
(second.tech logo: a triangle with an evenodd inner cutout) as two
format.js helpers — ARK_ICON (dark rounded-square app badge + white
mark, for card/section headers) and ARK_MARK (bare currentColor glyph,
for the history-row circle). Wired into the Ark settings card, the
boarding/withdrawal/exit history rows + detail section, and the
How-it-works Ark section; the gift source toggle drops the emoji for
plain 'Ark'. Verified the badge renders (18px, #00090C square, white
evenodd mark).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
All devices share one replaceable-event slot (same seed -> same nostr
key), so a device that published its snapshot before merging in
another device's ark state wiped those coins from the slot — exactly
what stranded a phone showing no ark balance. Fix: on receiving any
snapshot, union it with local and, if we hold vtxos the snapshot
lacked, re-publish the superset. The slot converges to the union
regardless of publish order; the guard (mergedN>remoteN) stops the
push-back once every device agrees, so no loop.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Ark vtxos lived only in per-device storage, so a second device with the
same seed saw the right on-chain balance (that rides the snapshot) but
none of its ark funds — board/change vtxos exist only where created.
Ark state now rides the encrypted nostr snapshot as a merge-safe cache
extension: additive union (bring in vtxos a device is missing) with
each device's own server reconcile() as the authority for spent-ness,
so a stale or wrong 'spent' on one device can never propagate and
poison the others. A connect-time reconcile catches coins spent
elsewhere while a device was away, and a live subscription to our own
replaceable event merges another device's new coins within seconds.
New seam: registerCacheExtension({mergeAlways:true}) extensions get
fed snapshots that aren't newer than local state too (last-writer-wins
is right for rescannable chain data, wrong for merge-style vtxo state),
and nostr.subscribeStates for the live path.
Verified two same-seed devices over a mini relay: B sees A's boarded
balance with zero mailbox activity, spends a synced vtxo, A converges
to a stable balance with the spent vtxo correctly marked.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The compatibility play for ark zaps: an NWC wallet service (coinos,
eventually) receives pay_invoice from any nostr client, asks the hal
wallet to cover the amount over ark (kind 23196 funding request), hal
auto-pays within a user-set allowance and acks (23197), the bridge
receives the vtxo in its mailbox and settles the bolt11 over
lightning. Custody window is the seconds the sats are in flight.
hal side: a live nostr subscription primitive (nostrSubscribe through
the sync seam) and an allowance-gated funding handler that arms when
ark connects. PoC config via localStorage 'btc-wallet-arknwc'
({bridgePk, budgetSat}); proper UI when the protocol settles.
Proven end-to-end on regtest (tools/.arknwc-e2e.tmp.js): mini relay +
bridge process (ArkManager in bun + CLN) + NIP-47 client — invoice on
clb paid via cl, preimage returned over NWC, hal debited 1,000 sats
over ark, zero page errors.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Lightning zaps are welded to LNURL+bolt11; nutzaps (NIP-61) proved the
alternative shape, and ark fits it better than Cashu because delivery
already exists (the mailbox). Draft protocol, kinds provisional:
kind 10037 (replaceable) announces the wallet's ark address+network;
the zap itself is a plain instant arkoor send; kind 9737 is the
sender-signed receipt tagging recipient, amount, comment, and the
delivered vtxo id — so the receipt is checkable against the actual
payment, unlike a bare attestation.
UX: paste an npub into Send and hal offers the zap (needs both the ark
and sync features; npub decoding is inline bech32 so ark-only builds
stay lean). The nostr layer gains generic publishEvent/fetchEvents,
exposed through the sync seam as wallet.nostrPublish/nostrFetch.
Proven on regtest against a mini NIP-01 relay: announce, resolve,
zap 1,000 sats with a comment, instant receipt+delivery, vtxo match.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
submitpackage answers HTTP 200 even when it rejects the package — the
verdict hides in package_msg/tx-results — so a mainnet no-fee-coin exit
sat at 'publishing on-chain…' forever believing its bare hop had
relayed. Rejections now surface (and become the actionable needs-a-fee-
coin message on the bumpless path). The status line shows hop progress
(step 2 of 9), the timelock wait counts down in blocks with a rough
wall-clock estimate, and a stuck exit that never published anything
gets a 'Cancel this exit' link that returns the vtxo to spendable.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
A fresh gift-only wallet has no on-chain coin for the CPFP fee child,
so its exit stalled behind 'hit a snag — retrying automatically' — a
precondition dressed up as a transient. When no fee coin exists the
hop is now tried bare (a zero-minrelay regtest relays it; a gift
wallet can exit with literally nothing but the gift), and if the node
refuses, the real message shows: exiting needs an on-chain coin for
fees. Errors now carry an actionable flag so the status line knows
when to tell the truth verbatim.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
100,000 in, 99,670 out was a surprise: the ASP's board fee (min/base/
ppm from its published schedule; it funds the exit tx's P2A fee anchor)
was only visible after the fact. The board form now shows the fee and
the net amount live as you type, and the funding tx's detail view gets
an 'Ark server fee' line.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
| |
submitPackage falls back to sequential /tx posts when the esplora lacks
POST /txs/package (regtest electrs) — works wherever the node relays
zero-fee v3 parents. The ark card stops speaking developer: in-flight
operations summarize as '{n} deposit(s) confirming' instead of
'board:funded, exit:chain, …', exit lines read 'Exiting N sats —
publishing on-chain…', and transient errors show 'hit a snag — retrying
automatically' instead of raw gRPC/HTTP text.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
| |
Same trigger as pasting an ark address into Send: the gift form is a
send-intent signal, so stale vtxos (spent server-side while this state
was idle) drop before coin selection instead of erroring at cosign.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The trustless backstop behind the Ark card's promise. The vtxo's
genesis chain reconstructs to fully-signed txs (keyspend sigs for
cosigned/arkoor transitions, preimage script-path for hark leaves);
they are zero-fee v3 with P2A anchors, so each hop broadcasts as a
[parent, CPFP child] package via esplora POST /txs/package, one
confirmed hop at a time (TRUC allows a single unconfirmed parent).
After the vtxo tx confirms and the CSV exit delta passes, the claim
spends the delayed_sign leaf with only the user key into the wallet's
own receive address.
Settings gains 'Exit on-chain without the server' with live per-exit
status lines; the claim tx is labeled 'Ark exit' in History. Probing
uses /tx/:txid (definitive 404), never /tx/:txid/status, which answers
confirmed:false for unknown txids.
Also: a fresh visitor opening an ark gift link now adopts the gift's
network automatically instead of being sent to Settings.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Ark can't presign a bearer spend, so an ark gift is a vtxo sent to an
ephemeral ark identity whose seed IS the link (/ag/<code>: net, amount,
32-byte secret). The claimer rebuilds the identity, reads its mailbox,
and sweeps to their own ark address — instant, free, and it works for a
brand-new empty wallet with zero on-chain footprint. The sender keeps
the secret, which is what makes revoke possible (first cosign wins).
The gift form gains an On-chain/Ark source toggle when ark balance
exists; ark gifts skip the lock/split logic entirely. Claim, choose-
wallet, already-claimed, and nostr-locked (/lg/) flows all branch by
code kind; unclaimed ark gifts list with one-tap revoke and lazily
mark themselves claimed via GetVtxoStatus. Features stay decoupled:
gifts asks for ark support via the new ctx.hook seam, so either
feature builds alone.
Also restores goHome in the feature ctx — lost in the plugin-seam
extraction, it made the already-claimed screen throw on render.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
PrepareOffboard/FinishOffboard against captaind — per-input ownership
attestations, connector-based musig forfeits (with the deterministic
fanout tx when there is more than one input), and fee math that mirrors
the server bit-for-bit (weight fee rounds up, ppm-by-expiry rounds
down) since it rejects any other net amount. The receive pane gains a
'Move back on-chain' button that withdraws everything to the wallet's
own fresh address; the incoming tx is labeled 'Ark withdrawal' in
History and the ark side records the movement. Crash-safe: the txid
persists before FinishOffboard and resume asks GetVtxoStatus whether
the forfeits landed (the server broadcasts the tx itself).
Also fixes boarding to applySentTx after broadcasting the funding tx —
without it the spent coin lingered in the utxo set (a second board
double-spent it), and the post-board scan() could resurrect it by
racing the explorer's indexing.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
The board's on-chain send showed as a generic 'Sent' and the sats
vanished from the balance until the vtxo registered. The funding tx now
reads 'Ark board' in History (with a detail section showing the net
credit), and an 'Ark (boarding…)' line carries the pending amount from
broadcast until the board completes.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
| |
Match captaind's actual 'not spendable' error so a crashed or foreign
spend fails the action instead of retrying forever, and reconcile
locally-spendable vtxos against the server (GetVtxoStatus + ownership
attestation) when an ark address lands in the send form. boardFee now
treats absent proto3 fee fields as zero (Second's signet boards free).
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
wallet.js drops from 2,762 to 2,374 lines and now exposes two generic
seams instead of feature code:
- registerCoinLock()/lockedCoinIds(): features contribute coin ids that
spending and the balance treat as unavailable (gift reservations).
- loadFeatureState()/saveFeatureState(): namespaced per-wallet persisted
state.
src/features/gifts-wallet.js takes the whole gift engine — reservation
sets, gift records, coin selection, the SIGHASH_SINGLE/NONE PSBT
builder, the compact codec, claim building, nostr-locked gifts — as an
installGiftWallet(wallet) instance install plus standalone exports.
Swap storage/key helpers (loadSwaps/saveSwaps/swapNode/nextSwapIndex)
install from swap.js; ark state storage installs from the ark feature.
The silent-payment scan worker only embeds when the sp feature ships
(wallet.js already tolerated its absence).
Minimal profile: 412KB, down from 476KB (full: 578KB). All suites pass:
ark UI e2e, ArkManager crash-resume, gift lifecycle, minimal smoke.
SP's engine (scanning, BIP-352 signing) stays in wallet.js — extracting
it safely wants an SP e2e harness on this stack first.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
|
|
|
First step toward optional build profiles. app.js gains a small feature
registry (ctx + FEATURES + hook helpers); swaps/Ark/silent-payments UI
moves wholesale to src/features/{swaps,ark,sp}.js, each exporting a
factory returning integration hooks:
init/stop/networkChanged wallet lifecycle
receiveModes/receiveTakeover receive tab
isSendDest/matchSendText/
interceptReview/sendView/
hideSendControls/sendFormNote send flow
historyEntries/historyDetail/
txDetailSection history
balanceLines/settingsCards balance card + settings
No behavior change: ark + gift browser e2e suites pass, and a probe
confirms LN receive, all receive modes, and the three settings cards
render as before. Gifts extraction and build flags come next.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
|