| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
| |
the green say it. History rows, transaction details, the "payment received" screens (wallet, Spending, gifts, boards/onboards) and the till's paid bills. The till's tip choices keep theirs: "+$2" there is what a tip adds to the bill.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Vps2H27rhbG9ZqseGfMrc3
|
| |
|
|
|
|
| |
floored to the cent (so it never parses back above the maximum), not a BTC figure under a CAD label. One ctx.amountField helper for the ark send/LN Max, the gift Max and fixed-amount zap/BOLT 12 fills
Co-Authored-By: Claude Opus 5.5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
door to the previous one.
Word grids flow down a column then the next, with the row count from the
word count — on a phone the first column is 1–6, the way a hand copies a
list. pomegranate.js takes the central as an option (CENTRALS.coinos is
auth.coinos.io, now deployed; the default stays auth.njump.me until its
Google client is live), and the sign-in card grows a "previous server"
line once the default moves, so accounts made under njump keep working.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Tmfg8k5f3TbNJS34qiqHAp
|
| |
|
|
|
|
|
|
|
|
|
| |
An unclaimed on-chain gift has no transaction of its own yet — its
funding transaction is presigned and only broadcast when someone claims.
The coin it's locked against is on-chain from the start, so the detail
page now links that outpoint's transaction on the block explorer, and
the claim transaction once it exists.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01JuwQqNWtDJcxPWtEm9134z
|
| |
|
|
|
|
|
|
|
| |
Every feature balance line landed on the Spending face, but a gift's
locked reservation is on-chain money still sitting in Savings. Lines now
say which side they belong to and draw on that card: gifts on Savings
beside pending incoming, ark's Moving… on Spending as before.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
ephemeral-key (v2) gifts.
The old on-chain gift presigned a SIGHASH_NONE/SINGLE spend of the sender's own coin — a blank cheque whose claimer output isn't committed, so anyone (from the link, or a mempool watcher once a claim broadcast) could redirect the presigned input to their own address via a higher-fee tx. A sweeper bot had been front-running coinos gift links for ~a month; it drained a real 5000-sat gift ("claimed! → empty wallet"). Root-caused via chain + code forensics: theft tx input0 sighash = SINGLE.
New design (v2, lazy ephemeral key): the sender presigns a FULLY COMMITTED (SIGHASH_ALL) funding tx paying the gift to a throwaway key E, and puts E's private key in the link. The claimer broadcasts the funding tx, then a fresh SIGHASH_ALL sweep of E into their own wallet. Both txs commit to every output, so neither is redirectable in the mempool — redirecting needs E's key, which lives only in the link (the intended bearer property: holding the link is the only way to claim). Lazy: the funding tx isn't broadcast until claim, so an unclaimed gift moves no coins and is revoked for free by spending the still-reserved funding coin. Sender pays the funding fee (from change / the swept total); claimer pays the sweep fee (from the gift).
- gifts-wallet.js: _buildGiftPsbt now builds+signs the ephemeral funding tx (coin -> E + change, SIGHASH_ALL); new GIFT_MAGIC_V2 codec (parseLazyGift/_encodeLazyGift: [magic][E priv:32][le32 len][funding tx]); giftClaimTxs returns the {funding, sweep} pair; giftOutpoints/previewGift read E's output; isRetiredGift flags the old v1 (compact + legacy PSBT) links. Fee threaded through _selectGiftCoins/createGift/createGiftAll/createGiftFromSplit.
- gifts.js doClaim: broadcast funding (tolerating "already in mempool" from a co-holder/retry) then sweep; refuse retired v1 links with a clear message; verifyClaimWon now watches E's output.
- i18n: giftChainWarn reworded (safe bearer link funding a fresh on-chain wallet); giftRetired added.
Verified: focused crypto test confirms codec round-trip + both inputs signed SIGHASH_ALL (committed). Full regtest end-to-end: funding + sweep both relayed (child accepted spending an unconfirmed parent), mined, claimer address credited the gift. UI morph smoke passes.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
|
| |
|
|
|
|
|
| |
(Savings) gift creation now shows a hard bearer-risk warning: the presigned input is redirectable, a sweeper bot is actively front-running shared links, so share privately and claim immediately (Spending/ark gifts are unaffected). (2) The false "success" is fixed: a broadcast returning OK was never proof WE won — a higher-fee sweep can evict our claim (the "claimed! → empty wallet" the bot produced). verifyClaimWon now polls the funding coin after broadcast and, if it settles spent by a txid that isn't ours, retracts the celebration and shows the already-taken screen. Full architectural fix (ephemeral-key gifts, non-malleable SIGHASH_ALL claim) is next
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
|
| |
|
|
|
|
|
| |
backed-up seed doesn't control. A claim into a fresh wallet reportedly paid a DIFFERENT wallet's address than the seed shown for backup (5000-sat gift claimed in incognito → funds landed at an accumulator address the given seed couldn't derive), because the destination was wallet.receive[0] — which can be a stale address a previous account in the session left behind. Now the claim derives THIS wallet's own address fresh from the active seed (never receive[0]), and a hard guard refuses to broadcast unless that address is provably derivable from the wallet whose recovery phrase is about to be handed over — any future seed/wallet divergence becomes a safe error, not lost money
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MGzU12QrAA4G6BYGzGVdXz
|
| |
|
|
|
|
|
| |
one puts it away), every landing — login, account creation, an account snapped into the carousel — is the account's own timeline, and that timeline is now SCOPED: Savings shows the on-chain ledger and gift reservations, Spending shows the ark side. The carousel meanwhile learns mouse and mind-reading: desktop gets a real grab-and-pull drag (snap lifted mid-drag, restored on the glide), the peeking neighbor switches on a single click, and the strip wears grab cursors. And a thread's avatars and author names finally go somewhere: profiles stack on top of the conversation and back returns to it
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
|
| |
|
|
|
|
|
| |
the last tab: the ark move/exit and gift detail views join NAV_FIELDS (an on-chain txDetail always had an entry; the ark rows — most Spending payments — minted none, so Back skipped the history list entirely), and their in-app Back buttons pop that entry rather than pushing another
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01MrkLsNScXe9b6BviUSdmMr
|
| |
|
|
|
|
|
| |
the Spending side and selects it on every path into the wallet — backup, verify, skip, or straight in — instead of opening onto an empty Savings face
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_019eA8JRq9Wr7z2fRQk99HLi
|
| |
|
|
|
|
| |
address to land on becomes a gift only their key can open
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
| |
keeps its pending line to itself
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01M5kFodm9azZJVTQxnkDdKe
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
Regtest-proven (tools/ark/dust-gift-test.js): the ASP cosigns all-dust
arkoor sends, so a sub-dust bearer vtxo sweeps to its claimer fine, and
dust folds into rounds alongside bigger siblings. The 330-sat gift
floor guarded a failure that can't happen — dropped to 1 sat.
The experiment also caught a live bug: refresh() of a balance smaller
than the round fee computed a negative output amount, and pbWriter's
varint sign-extends negatives into an infinite loop — auto-refresh
would OOM the tab of any wallet too small to renew. varint now throws
on negatives and refresh() refuses cleanly, before marking coins
pending, when balance minus fee is under the server's 330-sat output
minimum.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015QwAtBSpKvECqp1rHLHjJk
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The balance card's label becomes a Spending/Savings toggle and the
selection is app-wide state (persisted per device): the headline shows
the selected balance with the other quiet below, Receive shows the
payment address in Spending and a fresh on-chain address in Savings
(the per-page dropdown and its iconSelect widget are gone), and a
gift's source follows the account instead of its own toggle. Wallets
without Ark see the old single-balance card. Send stays
destination-driven — an invoice pays from Spending, an address from
Savings, as before.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Wvj4aoeMZwA7VUGdJP9Swi
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
Gifts are plain bearer links again: the recipient-npub field, the
one-time claim code, the DM delivery, the /lg/ claim route and the
NIP-07 in-place decrypt are all gone, along with the crypto that only
they used. Unclaimed gifts remain reclaimable by the sender, so nothing
can be stranded by the removal.
The unlock screen shows a single 'Sign in with Nostr' button that
reveals the login options when tapped, instead of the full card
competing with create/import.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
layout
- Gifts now sync across devices: gift records + reserved/reclaimed coin
sets ride the encrypted snapshot as a merge-safe extension (like ark),
and every gift state change (create/reclaim/revoke) triggers a
saveCache so it actually publishes. A gift made on one device now
shows on the others.
- Cap the published snapshot at ~40KB JSON (relay rejects >64KB events,
nip44 expands ~1.5x): trim oldest on-chain txs when oversized; another
device backfills via rescan. Typical wallets untouched.
- Service worker: register with updateViaCache:'none' so a new build is
detected immediately (not held by the 4h HTTP cache), and reload once
on controllerchange so the fresh app runs without a manual second
refresh.
- Gift detail: View/Reclaim share a row, Revoke on its own full-width
line so 'Revoke now' stops wrapping.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
| |
Choosing a wallet used to just load it and show a second screen with a
Claim button. Now tapping a wallet claims immediately (the pre-claim
check auto-fires doClaim), and the claim fee — network fee estimate,
or 'instant, no fee' for ark gifts — shows on the choose screen so
it's still visible before you pick. Fresh-wallet claims keep the
welcome+backup flow.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
| |
A gift-claimed wallet is a brand-new seed but the claim flow never
passed generated:true, so every gift wallet ground through the
indexer's full history (~336k tweaks) hunting silent payments it could
not have received — the CPU burn seen with a fresh gift wallet open.
Fresh claim wallets now fast-forward their scan watermark to the tip
like any other generated wallet. And two tabs on the same wallet no
longer duplicate a catch-up: a localStorage lease, refreshed per chunk,
lets the second tab yield.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
The network is now a property of each wallet, not a global mode. Every
new wallet is born on the currently-selected network; activating a
wallet activates its network (features re-init through the same
networkChanged hooks the Settings switch uses); the Settings network
card moves the ACTIVE wallet and sets the default for new ones. The
stamp persists everywhere accounts do — session, durable directory,
and vault — and legacy accounts adopt the current global once.
Gift claims compose: an ark gift lists only wallets on its network as
claim targets, and when none exist the fresh claim wallet is simply
born on the gift's network — existing wallets keep theirs, so opening
a foreign-network gift never disturbs anything. The wallet switcher
tags non-mainnet wallets with their network.
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]>
|
| |
|
|
|
|
|
|
|
| |
The Cancel-then-confirm step is gone: a reserved gift offers View /
Reclaim / Revoke immediately, a reclaimed one just Revoke. The summary
box only renders when it has content — a reclaimed gift with no
recorded date drew an empty gray box.
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]>
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
wallet.js drops from 2,374 to ~1,970 lines, holding only BIP84 on-chain
logic plus generic feature seams. New seams added this pass:
registerBalanceExtra extra confirmed/pending buckets
registerInputProvider extra spendable inputs for buildTx
registerSendPreparer destinations needing post-selection derivation
registerInputSigner non-BIP84 input signing
registerHistoryProvider / registerScanHook / registerRealtimeHook /
registerLoadHook / registerCacheExtension / registerCacheSavedHook
src/features/sp-wallet.js: the whole BIP-352 engine — key derivation,
worker-accelerated indexer scanning, the live indexer WebSocket, SP
outputs as spendable inputs, sp1 send preparation, and per-output-key
signing — installs via those seams (installSpWallet).
src/features/sync.js: nostr identity + encrypted cross-device state sync
plus its Settings card, installed via load/cache-saved/realtime hooks.
profileChip/nostrProfile move here-adjacent (into gifts, the only other
nostr consumer), so core app.js no longer imports nostr at all.
buildTx/sign are now feature-agnostic (input providers + preparers +
signers); _recomputeBalance* use the balance-extra registry, so a core
build with no SP no longer calls a missing spBalance() (that regression
dropped the minimal build into offline mode — caught by the smoke test).
New coverage: tools/sp-test.js drives two browser "devices" through an
SP send, indexer discovery, and an SP-input spend on the ark regtest
chain (needs tools/sp-indexer.js pointed at bitcoind:18543). Minimal
smoke now also asserts no SP/nostr code ships.
Minimal profile: 314KB, down from 412KB (full: 580KB). All suites green:
minimal smoke, sp-test, ark UI, gift lifecycle.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |
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
|
|
|
The whole gift surface moves to src/features/gifts.js (~800 lines):
create/split/lock flows, link viewing, revoke/reclaim, history rows +
detail page, claim-tx row decoration and tx-detail section, the claim
flow (fresh or existing wallet, nostr-locked gifts, claim codes, NIP-07
extension), the boot URL handling (/g/, /lg/, #gift=), the post-claim
celebration, and the locked-in-gifts balance line.
New seams this required: screenView (full-screen feature views),
bootUrl (URL takeover at startup), giftOpened (claim pre-check on
wallet activation), sendFormExtras, decorateTxRow, and multi-section
txDetailSection. Account access goes through explicit ctx accessors
(getAccounts/setAccounts) — the moved code briefly relied on an
accidental cross-module binding that broke activeAccount() during
claims; caught by the gift e2e.
app.js: 4,206 → 3,000 lines. Ark + gift browser suites pass.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QFAcD4V284DvYZZHP7Rxxy
|