The Dependabot bump updated github.com/wailsapp/wails/v2 to v2.14.0 in
go.mod but the wails workflow still installed the CLI at v2.12.0. Align
the CI install and document in AGENTS.md that both must be updated
together.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Vite 8 changed CJS default-import interop: with "type": "module" set,
a default import of a CJS dependency now resolves to the whole
module.exports object instead of its .default export. react-lottie is
CJS-only, so <Lottie> received an object as the element type and
crashed LottieLoading/LottieSuccess with "Element type is invalid".
Swap to the maintained, ESM-built lottie-react, aliasing it to its ES
build since its browser field points at a UMD build with the same
interop hazard. No other dependency is affected by the interop change.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: update encryption scheme for node migration files
Migration files are now encrypted with AES-CTR using a key derived via
Argon2 with a 32-byte salt, the same derivation used for encrypted
configuration values. Files created by earlier versions can still be
restored: the restore path detects the scheme by trial-decrypting the
archive header and checking for the ZIP file signature, which also
rejects an incorrect unlock password up front instead of extracting
garbage.
The migration screen now also tells users to never share their
migration file with anyone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: reword migration file warning
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: read full migration file header before detecting cipher scheme
io.ReadAtLeast can return once the smallest scheme's header is read,
which truncates the larger current-scheme header when the reader
delivers short reads (e.g. a network request body). Read the full
header and only tolerate a short read that still covers the smallest
scheme.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: extract migration files to a staging directory during restore
If extraction failed partway through, the partially populated restore
directory was left in the working directory, and the next startup would
apply the incomplete restore. Extract to a staging directory and only
move it into place after every entry has been extracted successfully.
Also reject archives that contain no files.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: assert traversal-specific error in restore backup test
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* chore: add Bark terms link and exit disclosure to security page
Link Second's Terms of Service from the Bark setup security screen, note
that the hub must stay online so automatically refreshed funds do not
expire, and clarify (via tooltip) that unilateral exit is not built into
Alby Hub yet and must be executed manually with the wallet data.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: improve copy
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Replace __dirname with import.meta.dirname and use Vite's native
resolve.tsconfigPaths option instead of the vite-tsconfig-paths plugin.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The unlock endpoints were rate limited per client IP, which is derived
from request headers and so is chosen by the caller. Switch to a single
global rate limiter (one bucket for all callers) and apply it to every
endpoint that verifies the unlock password: start, unlock, backup,
mnemonic, apps, autoswap, unlock-password and auto-unlock. A small burst
keeps unlocking and immediately performing an action working.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: remove legacy acceptance of empty unlock password check
CheckUnlockPassword previously treated a missing or empty
UnlockPasswordCheck value as a match — a legacy compatibility path from
before the canary was always written. It now requires the stored value
to be present and to equal the expected string.
StartApp checks for the canary up front and, if it is missing, stops
with a message asking the user to restore from a backup rather than
continuing. A new IsUnlockPasswordCheckSet helper reports whether the
value is present.
keys.Init now returns the error from reading NostrSecretKey instead of
ignoring it, so a read failure aborts instead of generating and saving a
new key.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: add operation context to unlock password check errors
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: limit LSP opening fees for JIT channel invoices
JIT channel invoices are now created with a maximum LSP opening fee
instead of no limit: the fee the LSP advertises in its LSPS2 opening fee
menu for the payment size, bounded by an absolute ceiling of 5000 sats
or 10% of the payment, whichever is greater. Invoice creation fails if
the LSP quotes a fee above this limit.
The minimum JIT payment size calculation now uses the same ceiling so
the advertised receivable range matches what invoice creation accepts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: validate invoice expiry range and guard LSPS2 cache reads
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Verify the invoice returned when creating a swap out before storing and
paying it:
- the invoice payment hash must match the payment hash of the locally
generated preimage
- the invoice amount must not exceed the requested amount plus the
quoted service and miner fees (with a small rounding tolerance)
- the lockup address is checked against the swap tree, matching the
checks already performed for swap in and refunds
- the invoice is verified again directly before it is paid
Also renames AlbySwapServiceFee to AlbySwapServiceFeePercentage for
clarity.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Move GET /api/log/:type from the read-only API group to the
full-access group, matching /api/swaps/mnemonic. Add tests asserting
a readonly token receives 403 from the log endpoint and a full-access
token can still read it.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The Wails request router included the full request body in its error
log entries, and the HTTP app creation handler logged the whole request
struct on failure. Log only the route, method and error instead,
matching the existing behavior of the /api/mnemonic handler, and log
only the route and method for requests in the desktop frontend.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The setup API accepts file paths for the LND certificate and macaroon
and for the CLN lightning directory. Previously the raw file contents
were read and stored without any validation.
Validate these inputs before persisting them:
- LND cert: parse the PEM and store only the re-encoded certificate(s),
discarding any other PEM blocks (e.g. a bundled private key).
- LND macaroon: unmarshal and store the re-marshalled macaroon.
- CLN lightning directory: verify it contains the TLS credentials
(ca.pem, client.pem, client-key.pem) that CLN loads at connect time,
including the hold subdirectory when configured.
On failure, return a generic error to the client and log the detail
server-side. File paths remain supported for Umbrel-style installs.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
return_to query parameters are now parsed and only http and https URLs
are used for redirects, both in the frontend and when the createApp API
adds the connection parameters to the URL.
The production frontend build now also includes the same
Content-Security-Policy meta tag that is served as a header in http
mode, so the policy also applies where no HTTP headers are set, e.g. in
the desktop app.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The mock event consumer waited a fixed 10ms before returning consumed
events, which was not always enough on slow CI runners and caused flaky
failures (e.g. TestMarkSettled_App_BudgetWarning missing its
nwc_budget_warning event). It also appended to the events slice from
concurrent goroutines without synchronization, a data race that could
drop events.
- guard the consumed events slice with a mutex and return copies
- add WaitForConsumedEvents which polls until the expected number of
events arrived (up to 5s) instead of relying on a fixed sleep
- use it in tests that assert on consumed events; tests asserting that
no event was published keep the short grace period
- normalize event order in the keysend self-payment test, matching the
existing approach in the self-payment test, since async publishing
does not guarantee ordering
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Archive entry names come from the uploaded backup and were joined to the
restore directory without validation, so an entry name containing ".."
segments could resolve to a path outside it. Reject entries whose name is
absolute or escapes the restore directory, and confirm the cleaned
destination path stays within it before writing.
Add a test covering rejection of an entry that points outside the restore
directory.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: keep showing migration success page after creating migration file
After creating a node migration file the hub is halted and the Alby
OAuth token is intentionally removed, so visiting the homepage sent the
user through /start into the Alby OAuth flow. Track the halted state
in memory and redirect back to the migration success page instead.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: synchronize migration flag access and propagate zip close error
Make nodeMigrationFileCreated an atomic.Bool since it is written by
CreateBackup and read by GetInfo on concurrent HTTP handler goroutines,
and finalize the migration archive explicitly so a failed zip close
returns an error instead of reporting a corrupt backup as success.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: return minimal info response after migration file is created
Once a migration file is created the hub is halted and the database is
closed. GetInfo previously only worked because every config key it reads
happened to be served from the config cache; any cache miss on an
error-propagating read would fail /api/info. Return early with a minimal
response instead so the migration success page does not depend on cache
state.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Allows users running Alby Hub on postgres (e.g. Alby Cloud) to create a
migration file from Settings -> Migrate Alby Hub. The contents of the
postgres database are copied into a temporary local sqlite database
which is included in the migration file, so it can be imported into a
fresh sqlite-based hub.
- extract the db_migrate CLI copy logic into a shared db.MigrateDB
- also copy the swaps and forwards tables (previously silently dropped)
- only require VSS in the source when migrating to postgres
- show a hint on the migrate page when running on postgres
- show database storage type and VSS status on the about page
- don't log an error when removing non-existent db files before restore
Closes#2500
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: update bark onboarding and backup messaging for seed-based recovery
Since bark 0.6.0, offchain funds are recoverable from the mnemonic
alone via the seed-derived recovery mailbox. Remove the outdated
warnings that the recovery phrase is not sufficient, show the standard
recovery guidance for bark during onboarding, and expose the
seed-recovery scan result as a 'recoveryreport' custom node command so
users migrating to a new device can verify their funds were restored.
Closes#2512
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: settle bark lightning receives in the new delivering state
bark 0.6.0 added a 'delivering' receive state between preimage reveal
and settlement. The receive claim handler only treated
'preimage-revealed' and 'settled' as paid, so claimed receives were
published without a preimage and the transactions service rejected the
settlement ('no preimage in payment'), leaving paid invoices pending
forever.
Recognize all states at or past preimage reveal via a receiveIsPaid
helper (a positive allowlist, so an unknown future state degrades to
pending rather than falsely settled), only mark the transaction settled
when the preimage is present, and prefer bark's own settled_at
timestamp when available.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: replace import channels checkbox with LDK-specific warning
The 'I don't have another Alby Hub to migrate or open channels'
checkbox on the import recovery phrase screen only applied to LDK but
was required for every backend, and its claim that channel funds are
always lost is wrong when dynamic channel backups (VSS) are enabled.
Remove the checkbox and the channels bullet from the import screen and
show the caveat on the Security & Recovery page instead, only when a
mnemonic was imported and the LDK backend was chosen.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: enable typing card name when choosing other card
Closes#2457
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: reset connect-card dialog form on open and show empty name validation error
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: use shadcn Button for other-card option in connect dialog
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: publish transaction events only after the database transaction commits
markTransactionSettled and markPaymentFailed published nwc_payment_sent /
nwc_payment_received / nwc_payment_failed (and checkBudgetUsage published
nwc_budget_warning) while still inside the caller's database transaction, so
connected apps and the Alby API could be notified of a payment whose row was
never committed, and subscribers reading the database in response to an event
could race with the commit.
Every function that writes transaction state now owns its own database
transaction and publishes its events only after the commit succeeds:
- markTransactionSettled and markPaymentFailed open their own transaction;
callers no longer wrap them in db.Transaction
- new createSettledTransactionFromNotification inserts transactions reported
by LNClient notifications for payments the hub has no record of (external
payments, received keysends) directly in their settled state, removing the
transient PENDING row and the zombie row left behind on duplicate events
- markPaymentFailed now refuses to mark a settled transaction as failed,
replacing CancelHoldInvoice's in-transaction ACCEPTED re-check and also
protecting the SendPaymentSync error path from a racing settle
- checkBudgetUsage returns the budget warning event instead of publishing it
Closes#2506
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: serialize payment failure with settlement and propagate lock errors
Address review findings on the previous commit:
- markPaymentFailed now takes the same payment-hash row lock as settlement
(postgres), so the settled-state guard cannot be bypassed by a concurrent
settle between the state check and the update; it also returns not-found
instead of publishing an event when the transaction row no longer exists,
and reports whether this call transitioned the row so CancelHoldInvoice
only publishes nwc_hold_invoice_canceled when it performed the cancellation
- findSettledTransaction propagates errors from the lock query and the
settled-transaction lookup instead of treating a failed lookup as
"no settled transaction exists", which could defeat the dedup guard
- TestMarkSettled_Twice no longer shares one transaction struct between
concurrent goroutines and collects errors instead of asserting inside them
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: mark failed keysend payments via markPaymentFailed
The SendKeysend failure path updated the transaction directly, which never
zeroed the fee reserve, recorded no failure reason, published no
nwc_payment_failed event, and had no guard against overwriting a
concurrently settled payment. Route it through markPaymentFailed like
SendPaymentSync, and allow MockLn keysends to fail so the path is testable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: use scope constant in get_budget permission query
The get_budget controller filtered the app_permissions scope column with
models.PAY_INVOICE_METHOD, which only matched because the method and
scope constants share the same string value. Use
constants.PAY_INVOICE_SCOPE like every other scope lookup, and document
why the unchecked First result is safe.
Fixes#2503
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: return error from get_budget on unexpected permission query failure
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The avatar overlay made the QR code hard to scan, especially for
short lightning addresses. Without center content the QR also drops
back to a lower error correction level, improving scannability.
Fixes#2507
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: filter transactions
* fix: harden transaction filters
* refactor: use explicit nullable transaction filters with HideFailed polarity
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: set transaction filters in a dialog from wallet actions menu
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat: filter transactions by search term and type
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: reject invalid transaction filters and reset page synchronously
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: parse complete minimum amount value in transactions filter dialog
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix: fallback to outgoing payments in Phoenixd LookupInvoice
LookupInvoice only queried /payments/incoming/{hash}, returning 404 for
outgoing payments. This caused all outgoing Lightning payments to remain
permanently stuck as PENDING in Alby Hub.
The fix tries incoming first (preserving existing behavior), then falls
back to listing outgoing payments and matching by paymentHash.
Fixes#2442
* fix: amount and fees in phoenix payment to transaction
---------
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
chore: update bark bindings to v0.15.0
Moves from bark 0.4.0 to 0.6.0. The Ark server now requires protocol version 5
(hashlock clauses) to start a lightning receive, which only bark 0.6.0 sends, so
older clients are refused outright and cannot generate invoices.
No client changes needed: v0.15.0 only adds to the surface we use.
Creating a developer token in the Wails build failed with a confusing
"Unhandled route: POST /api/unlock" error, because the desktop app does
not expose an HTTP API for the token to be used against. Hide the token
creation form in the desktop build and show an explanatory message
instead.
Fixes#2471
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Picks up getAlby/go-nostr#6, which shares relay connections in
SimplePool when dials fail, closes relay websockets on pool close,
and closes previous subscriptions before re-subscribing on CLOSED.
The shared per-relay-URL connect backoff is now enabled by default
in the fork, so no hub-side pool option is needed
(nostr.WithPenaltyBox is deprecated).
Fixes#2481
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Switch from polling node.NextEvent() every second to node.NextEventAsync(),
which parks the goroutine until an event arrives without blocking an OS
thread or an LDK thread, as LDK is migrating to async event handling.
Guard event handling with a mutex held by Shutdown() so in-flight handlers
finish before the node is stopped and destroyed, and drop events that
arrive after shutdown starts (LDK redelivers unhandled events on startup).
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: refresh payment QR and status components
* fix: align payment success button spacing
* fix: invert payment QR colors in dark mode
* fix: address payment QR review feedback
* fix: flatten nested cards in payment review FixedFloat tiles
* chore: remove internal payment component review screen
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: keep QR codes dark-on-light in dark mode
Inverted QR codes are unreadable by many scanner apps (e.g. Phoenix).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: René Aaron <rene@getalby.com>
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Moves from bark 0.2.3 to 0.4.0, which changed the FFI surface:
- WalletOpen takes the network and a WalletOpenArgs, replacing WalletCreate
and the separate RunDaemon call.
- Bolt11Invoice takes an optional anti-DoS token, unused here.
- LightningReceiveStatus is now LightningReceiveState, reporting progress
via State rather than a PreimageRevealed bool.
Movements expose PaymentHash and sends expose a typed terminal state, so both
are read from those instead of the movement metadata JSON. A send movement that
is neither pending nor successful now resolves the SendPaymentSync waiter
instead of being ignored.
the mnemonic could be considered a non read-only route because the
mnemonic could be used.
This moves this route to the full access group to require a full access
api key.
* fix: avoid mempool api failures during swaps by using boltz for fee and tx lookup
* fix: ensure swap payment isn't made twice on refresh
---------
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
* feat: just in time channels with lsps2
* fix: clarify JIT receive channel fee
* fix: fees
* fix: fees 2
* fix: don't show low inbound when LSPS2 is active
* fix: remove the receive limit below the input if LSPS2 is being used
* fix: simplify
* fix: bring back fee % for outgoing
* fix: remove unneeded changes
* fix: typo
* fix: unneeded
* fix: don't show open first channel is LSPS2
* feat: clearer JIT channel fee copy on receive screen
* fix: add LSPS2 var info
* fix: don't duplicate JIT fee hint on create invoice form
* fix: make paymentDone a standard boolean
* fix: update to golang:1.26 in Dockerfile
* feat: read LSPS2 sources from channel suggestions, set minimum receive amount, update guide link
* docs: update LDK_LSPS2_ADDRESSES to be used as an override
* fix: only show minimum jit receive amount on validation error
* fix: add more detail to receive error when receiving low amounts with jit
* fix: do not use JIT when user has public channels
* feat: add option to disable JIT
* fix: isTrusted check, add jit property to event
* fix: do not require node restart for toggling JIT
* chore: simplify JIT alert
* chore: add guide link on node settings JIT description
* feat: fetch the lsp2info to have access to params like minimum/maximum payment size
* refactor: share single learn-more link across JIT fee hint branches
* fix: remove variable amount invoice support
* fix: use lsps2info for min payment size and remove channelPeerSuggestion usage of minimumChannelSize
* fix: only do amount validation according to lsps2Info values if jit is enabled in settings
* feat: add jit first payment fee alert on receive via lightning address
* fix: remove unnecessary conditional
* fix: ensure at least one sat is left over when opening JIT channel
* chore: remove hardcoded suggestions
* chore: rename JIT enabled config variable
* fix: ui checks when JIT is disabled
* fix: amount input validation message
* fix: formatting
---------
Co-authored-by: anon <anon@anon.com>
Co-authored-by: saunter <68239231+stackingsaunter@users.noreply.github.com>
Co-authored-by: fmar <fmar@fmar>
Co-authored-by: René Aaron <rene@twentyuno.net>
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
* feat: pass selected provider to card topup app
The Bitcoin Card Topup app (card.albylabs.com) now supports configuration
presets selected via a `provider` query param. Pass the provider chosen on
the Cards page through to the topup app's install link so its preset is
pre-applied, simplifying setup.
Closes#2384
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: move bitcoin card topup install guide component to a new file
* chore: remove accidentally committed worktree gitlinks
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: use more general copy for card topup app install guide
* fix: remove subtree commits
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: tighten inline empty state spacing on wallet pages
* chore: refine wallet empty states
- Replace placeholder icons (drum, link) with channel-specific icons (ZapIcon for lightning, BitcoinIcon for on-chain)
- Rewrite empty-state copy with warmer, less technical phrasing
- Drop redundant CTA (Receive button already sits above)
- Add subtle bg-accent surface to anchor the transactions section
* chore(empty-state): add variant prop, drop unused button props on wallet pages
* chore(empty-state): swap muted surface from bg-accent/40 to bg-muted
* chore(transactions): allow callers to override empty-state copy and icon
App transaction lists now show app-context messaging ('No transactions yet'
+ 'Payments made through this app will appear here.' with a ReceiptIcon)
instead of the wallet-specific lightning copy.
* chore(empty-state): drop unused 'none' variant
* chore(empty-state): default showButton to false
* chore(empty-state): drop showButton prop, derive from buttonText+buttonLink
* fix(empty-state): drop nested surface in app transactions card
Add 'none' variant and use it from AppTransactionList so the empty state
no longer renders a bg-muted box inside the already-bordered Card.
* fix(app-transactions): swap ReceiptIcon for ArrowDownUpIcon
ReceiptIcon renders a dollar sign — wrong for a bitcoin app.
* chore(empty-state): default variant to 'muted', CTA placeholders opt into 'dashed'
---------
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
* fix: reduce wallet balance/transaction polling interval to 10s
The wallet dashboard polls /api/balances and /api/transactions every 3s
via SWR refreshInterval. For hubs left open in a browser tab, this produces
a high, continuous volume of identical requests around the clock with little
UX benefit, since SWR already revalidates on window focus.
Raise the interval for the balances and transactions-list hooks to 10s. The
single-transaction hook (used while waiting for a specific invoice to settle)
is intentionally left at 3s, where fast updates matter and polling is
short-lived.
* refactor: drop poll-interval comments, rationale moved to PR
* feat: reframe AI agent inspiration tab around managing the hub
Change the Node inspiration tab on the AI & Agents page to a Hub tab focused on managing Alby Hub itself (app connections, sub-wallets, budgets) rather than lightning channels. Channel/node prompts only show when the backend supports channel management.
Closes#2401
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix: icon
* chore: name
* chore: remove stray .claude/worktrees gitlinks
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: René Aaron <rene@twentyuno.net>
* chore: remove Bitrefill custom app in favor of standard NWC connection
Bitrefill now supports Nostr Wallet Connect directly, so the custom
embedded iframe app is no longer needed. Remove the internal Bitrefill
screen and route, convert the app store entry to a standard NWC
connectable app, and drop the embed.bitrefill.com frame-src CSP
exceptions from both the backend header and the dev Vite config.
Closes#2283
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: update bitrefill instructions and add mobile links
* chore: remove accidentally committed .claude/worktrees gitlinks
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: show confirmation progress when opening public channel from LSP
Public channels require 6 confirmations before they can be gossiped and
become usable (BOLT-7), but LDK accepts channels from trusted LSP peers
as 0-conf and reports ConfirmationsRequired as nil/0. As a result the
channel-opening screen rendered an indefinite blank loading spinner
instead of confirmation progress.
Override ConfirmationsRequired to 6 for announced channels so the UI
shows the "X/6 confirmations" progress card while the channel opens.
Fixes#2294
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: remove unnecessary link in comment
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fix: ignore 404 when deleting an already-deleted lightning address (#2323)
When a sub-wallet's lightning address was already removed on getalby.com, the DELETE call returns 404, which surfaced as a "Failed to delete lightning address" error toast. Treat 404 as success so deletion is idempotent.
Renames the onboarding checklist item from "Send or receive your
first payment" to "Receive your first payment" and marks it complete
when the wallet has a spendable balance (e.g. after opening an
outbound channel) in addition to having a transaction.
Exporting a wallet with thousands of transactions previously appeared to
do nothing — the handler silently paginated through every page with no
feedback. It now shows a loading toast that updates with the running
transaction count, and fetches 1000 transactions per page instead of 20
to cut round-trips.
Closes#2386
* fix: link Windows CNG libs for bark FFI
The bark FFI static library is built for the GNU/mingw target and embeds
Rust's getrandom/ring code, which references Windows CNG symbols such as
BCryptGenRandom. The upstream bark bindings only link -lbark_ffi_go, so the
mingw linker fails with "undefined reference to BCryptGenRandom".
cgo merges LDFLAGS across packages, so supply the missing Windows system
libraries from our own bark package without modifying the vendored module.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: move cgo directive out of Go doc comment for bark windows
The descriptive comment block was contiguous with the import "C" line, so
the entire block became the cgo C preamble and the C compiler tried to parse
the prose (unknown type name 'The', stray quotes/backticks). Separate the Go
documentation from the cgo preamble with a blank line and keep only the #cgo
directive in a /* */ block immediately preceding import "C".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: link Windows CNG libs for bark via extldflags
cgo #cgo LDFLAGS directives from our package are ordered before the bark
module on the link line, so the single-pass mingw linker discards -lbcrypt
before it sees the undefined BCryptGenRandom reference from libbark_ffi_go.a.
Append the Windows system libraries via -extldflags instead, which places
them after -lbark_ffi_go so the linker can resolve the symbols.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: link bark Windows CNG libs via CGO_LDFLAGS start-group
wails drops -ldflags=-extldflags, so the system libraries never reached the
external linker. Set them through CGO_LDFLAGS instead (read directly by cgo)
and wrap them with bark in a --start-group, so the linker re-scans the group
and resolves BCryptGenRandom regardless of library order.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: gate bark backend to platforms with prebuilt FFI libs
The bark FFI bindings ship no native library for 32-bit ARM Linux, so the
armv6 build failed to link bark's own FFI symbols. Constrain the real bark
implementation to bark's supported platforms (darwin/linux amd64+arm64,
windows amd64) and add a stub for everything else that returns an
"unsupported" error if the bark backend is selected at runtime.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
These promo cards are now covered by the Stories widget (Alby account,
Alby Go and Alby Extension stories are live in the stories feed), so the
standalone home widgets are redundant.
Supersedes #2175, which patched the older inline cards that were since
refactored into widgets.
The NIP47 info publish queue re-enqueued every failed publish with an
incrementing backoff and no terminal condition. When an app connection
was deleted, PublishNip47Info fails the `db.First(&app, appId)` lookup
with gorm.ErrRecordNotFound on every attempt, so the item was retried
forever (observed as a steady stream of "Failed to publish NIP47 info
from queue" errors from affected instances).
Drop the queue item when the app no longer exists instead of requeuing.
All other errors (offline relay, timeouts) still retry with backoff.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: integrate Stories widget with backend endpoint
Add stories endpoint plumbing for HTTP and Wails, wire the Home Stories card
to fetch from /api/alby/stories, and keep it first in the right column.
Made-with: Cursor
* feat(home): story modal CTAs and preview fallback
- Add contextual actions in the story dialog (update hub with version,
open Alby Go in-app, install extension) keyed by kind or title
- Use preview stories when the stories API request fails
- Pass hub version from useInfo into the update link
Made-with: Cursor
* feat(stories): polish modal, drop preview fallback
- Widen modal and put video edge-to-edge with overlay close button
- Drop verbose header and 'Watch on YouTube' button
- Remove previewStories fallback so widget hides until upstream API ships
- Tighten title line-height
* feat(stories): render cta from API instead of mapping by kind
Move CTA copy and URLs into the API response. Hub renders story.cta
directly, so adding new story types no longer requires a hub release.
* chore(csp): allow cdn.getalby-assets.com in img-src
* feat(stories): bump avatar size and add ring gap
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): post-review cleanups
- Use react-router Link for in-tab CTA instead of plain <a>.
- Drop redundant www.youtube.com from frame-src (embeds always go through nocookie).
- Tighten stories endpoint status check from >= 300 to >= 400.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): address CodeRabbit feedback
- Switch StoriesWidget to useSWR + swrFetcher (project convention).
- Guard story iframe with isYouTubeUrl so non-YouTube urls never embed.
- Wrap GetStories errors with fmt.Errorf("...: %w", err).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): drop isYouTubeUrl guard
Stories are curated and always YouTube; the runtime check was
redundant. CSP frame-src still constrains the iframe source.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): drop getYouTubeEmbedUrl, embed videoUrl as-is
The Alby API now sends canonical youtube-nocookie embed URLs with
autoplay/rel query strings (getAlby/getalby.com#2568), so the
runtime normalization is no longer needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): use w-16 instead of arbitrary w-[73px]
Match the avatar's size token; no magic numbers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): take videoId from API and assemble embed url locally
Pairs with getAlby/getalby.com#2568. The API now sends just the
YouTube videoId; the hub composes the canonical embed URL so the
domain/query-string format stays in one place.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): treat 3xx as non-success, matching file convention
The other status checks in alby_oauth_service.go all use >= 300;
align GetStories so redirects don't slip through.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): move viewed-storage key to constants, widen story button
Address review feedback:
- Centralize the localStorage key for viewed stories in localStorageKeys
alongside the other keys.
- Widen the story button from w-16 to w-20 so "Alby Extension" fits on
one line and matches the other titles.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(stories): bump story button to w-24 so titles fit one line
w-20 still wrapped "Alby Extension"; w-24 fits all current titles
without truncation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Revert "chore(stories): bump story button to w-24 so titles fit one line"
This reverts commit 0b47438f50.
* chore(stories): split title words onto separate lines
Reserve two lines for every story title so avatars align regardless of
title length.
* chore(stories): align homeStoriesViewed key with sibling pattern
* chore(stories): fit titles on one line
* chore(stories): widen story button to w-21 for one-line titles
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: stop prompting for bitcoin: protocol handler on every load
Browsers re-show the registerProtocolHandler prompt every time it is
called if the user dismissed (X'd) the previous one without explicitly
accepting or denying. Gate the call with sessionStorage so we ask at
most once per browser session.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix: guard sessionStorage access against restricted/private modes
sessionStorage.getItem and setItem can throw in private browsing or
restricted storage modes. Move both inside the existing try/catch so an
exception doesn't break the hook.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore: simplify protocol handler session flag to a boolean
sessionStorage is tab-scoped and ephemeral, so comparing the stored
handler URL gains nothing over a plain truthy check.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* chore: remove json tags from lnclient models
these should not be passed through the API directly
* fix: properly return not implemented errors
* fix: json tags on TLVRecord
* chore: remove unnecessary regions
* chore: improve cards mobile UI, remove extra regions from RedotPay
* fix: add visit button for mobile provider cards rather than the whole card opening the provider url
* chore(cards): use Select for the region filter everywhere
Drop the mobile-only Select / desktop-only ToggleGroup split. Using a
single Select for the region filter across both viewports removes the
duplicated component, keeps the filter bar a single row at all widths,
and lets the feature toggles stay as pills (those carry icons and read
as a row of binary on/off filters).
* chore(cards): cluster Apple/Google Pay icons next to region badges
Drop the ml-auto on the mobile ProviderCard's pay icons so they sit
right after the region badges instead of floating at the far-right
edge with a large gap. Reads as a single group of card properties.
* chore(cards): rework mobile card spacing + use w-40 for region select
- Bump card padding to p-5 for more breathing room.
- Add a subtle border-t before the facts grid so KYC/Time/Cost/Fees
read as a separate block from the header + regions row.
- Widen the facts grid's vertical gap (gap-y-4) so labels don't sit
right against the value of the previous row.
- More space before the Visit CTA (mt-6) so it reads as a primary
action, not a fifth fact.
- Swap w-[160px] on the region Select for w-40 per project Tailwind
conventions (CodeRabbit nit on PR #2377).
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
* feat: add cards directory page with provider listings
Adds a dedicated Cards screen that surfaces crypto debit card
providers users can top up from their Alby Hub balance, with region
and feature filters and a fees comparison table.
Co-Authored-By: Claude <noreply@anthropic.com>
* feat(cards): connect-card flow with NWC top-up link
Iterates on the cards directory page (#ae087d0b) to wire up the full
connect-card flow described with the bitcoin-card-topup PWA at
card.albylabs.com.
- AI-style hero with three steps: Get a card → Connect it → 1-click top-ups
- Trimmed provider list to RedotPay / 2fiat / Freedomia (the providers
we've validated end-to-end)
- New "Time to get" column so users see physical vs virtual at a glance
- Renamed "Add card" → "Connect card" everywhere; submit mints a real
NWC connection via createApp (same pattern as the AI page) with
scopes for the topup app (get_info / get_balance / list_transactions /
lookup_invoice / make_invoice / pay_invoice / notifications)
- CardCreatedDialog shown once at creation with QR code + bookmarkable
top-up link of the form
https://card.albylabs.com/#label=...&address=0x...&chainId=42161¤cy=USDC&nwc=<pairing-uri>
Includes prominent "save this link — you won't see it again" warning
(Alert with warning variant) and "scan with your phone's camera app
(this is a URL, not a Lightning invoice)" caption under the QR
- Saved card tiles link to /apps/:appId so users get back to the NWC
connection detail; no separate top-up affordance from the hub
- useUserCards hook persists cards in localStorage with appId pointing
at the NWC connection; ready to migrate to deriving the list from
/api/apps filtered by metadata.app_store_app_id = "bitcoin-card-topup"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* feat(cards): derive cards from /api/apps, polish
Replaces the localStorage card store with a derivation from /api/apps
filtered by metadata.app_store_app_id = "bitcoin-card-topup". Card
provider/destination/chain/currency now ride on the NWC app's metadata,
so cards survive across devices, reloads and DB backups, and "forget
card" flows through the existing /apps/:id delete affordance.
- Gate /cards through DefaultRedirect so a locked hub redirects to
/unlock (matches /wallet, /apps, etc.)
- Hub-generated top-up link now uses #?... prefix so bitcoin-connect's
parser branches to the URLSearchParams path (works in all cases vs.
the bare #... which mis-parsed for some users)
- Remove EmptyCards empty state (the Connect card button in the page
header is the sole entry point)
- Drop the "Experimental" filter and badge — too small a catalog for
it to be useful, and the toggle behavior confused users
- Replace "Top up via" column with "Card cost" (more decision-relevant)
- Verified all provider data from each provider's site; updated
- RedotPay: KYC Full (not Light), regions add US/UK, fees ~2.2% + FX
- 2fiat: Mastercard (not Visa), Apple Pay + Google Pay supported, KYC
None (not Light), card cost $50, fees ~6.8%
- Freedomia: Google Pay supported, card cost $5–30/mo subscription,
fees 1.3–4.3%
- Add a hoverable info-icon tooltip on the "None" KYC badge so privacy-
focused users keep the signal they want while cautious users see the
merchant-of-record-fragility caveat
- Field label "Destination address" → "Top-up address" (matches what
the user pastes from their provider)
- Connect dialog copy de-emdashed
- Various smaller copy/layout tweaks
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* chore(cards): tidy provider tiles, filter, and column header
- Switch Freedomia tile bg to bg-orange-500 to match the brand's
orange F logo
- Drop the upscaled watermark logo from card-tile backgrounds for a
cleaner surface
- Hide filter toggles whose criterion no provider satisfies, so the
filter bar only offers actionable options
- Rename "Mobile pay" column header to "Mobile" — the icons already
identify Apple/Google Pay
Co-Authored-By: Claude <noreply@anthropic.com>
* chore(cards): drop unused provider logos
We trimmed the catalog to RedotPay/2fiat/Freedomia in an earlier
commit; the other 8 PNGs were left behind. Remove them to keep the
asset directory tight.
Co-Authored-By: Claude <noreply@anthropic.com>
* chore(cards): downgrade RedotPay KYC from Full to Light
RedotPay only requires ID verification — no proof of address, employer
details, or source-of-funds questions. "Full" overstates it and may
deter users from even trying.
Adds a Light KYC tooltip alongside the existing None one so the
distinction is visible at a glance.
* chore(cards): tighten copy and mobile layout
- CardCreatedDialog: drop the "regular URL, not a Lightning invoice"
caveat and replace with actionable "save it as an app to your
homescreen" guidance.
- Hide the QR + scan instruction on mobile — the user is already on
their phone, so scanning their own screen is nonsense.
- Provider table: switch wrapper from overflow-hidden to overflow-x-auto
and set a 720px min-width so 9 columns scroll horizontally on narrow
viewports instead of cramming/clipping.
* feat(cards): list Bringin and wavecard
Both already ship as suggested apps with their own NWC pairing flows, so
the table row's action arrow links to /appstore/<id> rather than the
stablecoin connect-card dialog. Marked via a new optional appStoreId
flag on Provider; the Connect-card dropdown filters these out so the
top-up form never offers a chain/currency for cards that don't need one.
Reuses the existing suggested-apps logos to avoid duplicating assets.
* chore(cards): retitle 3rd hero step to highlight top-up speed
"Top up in seconds — your card is funded in under a minute, ready to
spend on the go" lands the speed promise harder than "1-click top-ups"
did, and frames the value as on-the-go spending rather than mechanics.
* feat(cards): route app-store cards through the Connect dialog
Bringin and wavecard are now in the provider dropdown. Picking either
hides the address/network/currency fields and replaces the primary
button with an "Open setup guide" link to /appstore/<id>, where their
own NWC pairing flow lives. Keeps a single entry point for "connect a
card" without forcing the stablecoin form onto cards that don't use it.
Also tightens the hero subtitle to lead with the speed promise.
* chore(cards): link app-store cards straight to /apps/new
Skip the app-store detail page — /apps/new?app=<id> drops users into
the connection flow with the right app preselected, which is what they
actually wanted.
* chore(cards): correct Bringin and wavecard table data
Verified against bringin.app/bitcoin-debit-cards and wave.space/card:
- Both offer physical *and* virtual cards (added "Both" to cardType).
- wave.space is EEA-only for issuance (card itself is accepted globally)
— regions changed from Global to EU.
- Direct Apple Pay isn't live for either; both currently work via Curve,
which is too indirect to claim native Apple Pay support.
- Issuance is not free: Bringin charges a €3.49/mo subscription that
bundles both cards; wave.space charges €2.99 virtual / €29.99 physical
one-time.
- Conversion fees are 1% + ~0.5% LP spread for both, not flat ~1%.
- Bringin URL fixed to bringin.app (they migrated from bringin.xyz);
wave.space URL points at the card landing page.
Doesn't change the dialog routing — picking Bringin/wavecard still drops
the user into /apps/new?app=<id> for the NWC pairing.
* chore(cards): tighten created-dialog copy
- Title and subtitle now describe the link instead of warning.
- Alert highlights the device-specific action ("save it on the phone
you'll top up from") and drops the redundant secret-recovery prose.
- QR caption mentions bookmark as an alternative to home-screen install.
* refactor(cards): route every provider through the standard app flow
- Add a bitcoin-card-topup app store entry pointing to card.albylabs.com
with a "visit + add to home screen + enter card details" install guide.
- Wire RedotPay and Freedomia to bitcoin-card-topup, and 2fiat to its
existing app store entry. Bringin and wavespace already had theirs.
- Drop the custom Connect card dialog, address/network/currency form,
and the one-shot CardCreatedDialog with embedded NWC link. Card config
is now collected inside the topup app itself.
- Drop the "Your card connections" section and the useUserCards hook —
connected cards show up in the standard /apps list like any other app.
* feat(cards): add Connect card picker dialog + clickable rows
- Bring back the Connect card button in the header; opens a lightweight
provider-picker dialog where each tile routes straight to
/apps/new?app=<appStoreId>.
- Drop the table's action column entirely; the whole provider row is now
clickable and opens the provider's website in a new tab. This separates
discovery (row click → learn more) from action (Connect card → setup).
* feat(cards): add Other card option + broaden install wording
- Add an "Other card" tile to the Connect card picker (dashed border,
generic credit-card icon). Routes to /apps/new?app=bitcoin-card-topup
so anyone holding a USDC/USDT card not in the listed providers can
still set up the topup flow.
- Reword the install guide from "phone you'll top up from" to "device" —
the topup app works equally well on a tablet or any browser.
* chore(cards): fix bitcoin-card-topup logo + broaden copy to "any crypto"
- Swap the placeholder 2fiat logo for the Alby logo (alby.png).
- Drop USDC/USDT specifics from the app description, extended
description, and the picker dialog's "Other card" tile copy. From the
user's perspective the topup app just takes a crypto card.
* chore(cards): use bitcoin-card-topup's own PWA icon as the app logo
Copy bitcoin-card-topup/public/shortcut-icon.png (the topup PWA's home-
screen icon) into suggested-apps/ and point the bitcoin-card-topup app
store entry at it, replacing the Alby-logo placeholder.
* fix(apps): align Connect-to-app header logo size with the appstore page
NewApp.tsx rendered the app logo at w-12 h-12 (48px) while
AppStoreDetailHeader uses w-14 h-14 (56px) — the logo visually jumps
between /apps/new?app=<id> and /appstore/<id>. Unify on w-14 h-14.
* fix(appstore): use AppHeader's standard icon/description slots
AppStoreDetailHeader was rendering the logo + title + description inside
a custom flex container nested into AppHeader's title prop, which gave
the icon a different vertical alignment than every other AppHeader use.
Pass them via the icon and description props instead so the header
layout stays consistent with /apps/new and the rest of the app.
* chore(cards): use Freedomia affiliate URL
* chore: add affiliate links
* fix: always show global cards
* fix: make 2fiat lightning native
* chore: update card instructions
* chore: optimize bitcoin card topup app image
* chore: improve bitcoin card topup copy
* chore: add card events, fix http service event endpoint url
* chore(cards): drop redundant 'Get a card' section heading
* chore(cards): tighten hero subtext + 3-step copy, hide filter footer when inactive
- Hero subtext drops the "one click" claim that's no longer accurate
post-refactor and the em-dash.
- 3-step descriptions reworded for length and fix "Setup" -> "Set up";
bottom step matches the "in seconds" heading instead of saying
"under a minute".
- "Showing N of M providers" footer is hidden unless a filter is
active — silent when there's nothing to clarify.
* chore(cards): apply Alby button gradient + brand stroke to hero card
Use the same vertical white→cream highlight and 1px #ffdf6f stroke that
alby.css applies to default primary buttons, so the hero card visual
reads as part of the same theme family instead of an isolated panel.
* chore(cards): reframe step 2 around the top-up link + drop hero gradient
- Step 2 title shifts from "Connect it" to "Get a top-up link" so the
deliverable (a saveable link) is what users see, matching what
actually happens at that step post-refactor.
- Drop the white→cream gradient overlay from the hero card; the flat
yellow with the existing shadow is the look we want.
* chore: change wave.space kyc level
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
* fix: show BOLT-12 offer button for CLN backend
The CLN backend implements MakeOffer and the /api/offers endpoint is
backend-agnostic, but the UI only surfaced the "Lightning Offer" button
when backendType === "LDK", leaving CLN users without a way to reach
the flow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: gate BOLT-12 offer UI on supportsBolt12 from useInfo
Surface a supportsBolt12 capability on the info response (true for LDK
and CLN) so the frontend stops hardcoding backend-type checks for the
BOLT-12 offer button.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(appstore): rebrand LendaSwap as Satora
* chore: also show the previous name on the app card
---------
Co-authored-by: Lucas Soriano del Pino <lucas_soriano@fastmail.com>
* fix: replace payment flow intermediate screens in browser history
* fix: add break all to ln address container
---------
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
* feat: prototype balance switcher variants on wallet screen
Adds two switcher styles (icon-only segmented control and tab-style
control) alongside the original "Spending Balance ⇅" toggle, with a
floating dev-only variant toggle for side-by-side comparison.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: replace balance toggle with tab switcher
Drops the icon-only and original variants plus the dev preview toggle,
keeping just the Lightning / On-chain tab switcher and a wider gap to
the balance underneath.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: use shadcn Tabs primitive for balance switcher
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: type balance switcher icons with LucideIcon
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: only render balance switcher when on-chain wallet exists
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: note lucide-react *Icon suffix convention in AGENTS.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
* chore: rename spending balance to lightning balance
* chore: align AutoSwap threshold label capitalization
Match sentence case used by the form label and sibling rows in
ActiveSwapOutConfig.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: more renaming and copy fixes
* chore: more renaming
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fix(alby-theme): scope default-variant styling to buttons only
The Alby theme's `[data-variant="default"]` selector was element-broad
(`:is(button, a, span)`) and matched any Radix component that exposes
`data-variant`. Wrapping a `DropdownMenuItem` around `<Link asChild>` made
the `<a>` inherit `data-variant="default"` and pick up the gold gradient +
1px border — the bug fixed symptomatically in #2286 and #2295.
Narrow the selector to `[data-slot~="button"][data-variant="default"]`.
Radix's Slot forwards `data-slot="button"` onto `<Button asChild>` children,
so DialogTrigger asChild and friends keep their styling, while
DropdownMenuItem (`data-slot="dropdown-menu-item"`) and Badge
(`data-slot="badge"`) no longer match.
Restore the idiomatic `<DropdownMenuItem asChild><Link/>` pattern that #2286
removed so users keep middle-click / cmd-click / "open in new tab" on menu
navigation items.
Transaction IDs in the wallet on-chain tab list are noisy and not very
useful at a glance. The full TX ID is still shown in the transaction
detail modal with a copy button and a link to mempool.
Note: showing the bitcoin address in the detail modal (also suggested
in the issue) would require extending lnclient.OnchainTransaction with
an Address field. LDK's PaymentKindOnchain doesn't expose the address,
so this would only be populatable for LND/CLN — leaving it out for now.
Closes#2299
* feat: add CLN as a lnclient backend
* feat: add hold invoice support for CLN backend
* fix: reduce CLN form to just addresses and lightning dir
* feat: add README for CLN grpc go code generation
* fix: cln backend does not support keysend with given preimages
* fix: hold invoice notifications in CLN backend
* fix: remove dead code in CLN backend from ListTransactions
* fix: env example CLN_ADDRESS_HOLD with different port to show it's a different service
* fix: cleanup of CLN ressources in all cases
* fix: cln backend's GetNetworkGraph only fetches specified nodeId's
* fix: cln backend: only advertise hold methods for nip47 if hold plugin enabled
* fix: cln's Shutdown should not stop CLN itself
* fix: relax the LND README line regarding env configuration
* fix: more nil checks in clnInvoiceToTransaction
* fix: prevent feerate overflow in CLN's RedeemOnchainFunds
* fix: don't access nil errors for empty reponses of certain CLN methods
* fix: nil instead of empty string in cln's GetNetworkGraph return types
* fix: nil checks for created_at in cln's clnInvoiceToTransaction
* fix: set minimum tls version to 1.2 for cln backend grpc connections
* fix: cln's subscribeOpenHoldInvoices doesn't give up as fast
* fix: deduplicate graph edges in cln's GetNetworkGraph
* fix: print the error string, not pointer address, in cln's ListChannels
* fix: remove cln's ListTransactions completely
* fix: use ListPeers instead of ListPeerChannels in cln's ListPeers
* feat: cln's MakeHoldInvoice supports minCltvExpiryDelta
* fix: use named return err in NewCLNService
* fix: cln listpeers log message
* fix: incorrect import
* fix: compile errors after rename
---------
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
refactor(frontend): extract TransactionDetailRow for both detail dialogs
Pulls the repeated `<div><label/><value+copy/></div>` shape out of
TransactionItem and OnchainTransactionItem into a shared component with
an optional `copyable` prop. Replaces ~16 hand-written rows in the
lightning dialog and 4 in the on-chain dialog. Lightning also gets
parent-driven `gap-6` spacing in place of per-row `mt-6` / `mt-8`.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): auto-balance home dashboard columns
Replace the fixed two-column grid with a CSS multi-column layout so
widgets flow and auto-balance regardless of which ones render (several
widgets can return null based on user state). Group the Alby ecosystem
cross-promo cards so they stay together within a column.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: extract Alby home widgets
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
* feat(frontend): add wallet on-chain balance mode
Show on-chain balances and transactions in Wallet.
Keep receive and send flows aligned with the active balance mode.
Made-with: Cursor
* fix(frontend): gate on-chain toggle on channel management and fix isLast naming
Hide the balance mode toggle for backends without on-chain support
(PHOENIX, CASHU). Rename misleading `isLast` prop to `showSeparator`
in PendingClosedChannelsAlert.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(frontend): inline on-chain balance display per page
Drop the shared OnchainBalanceSummary component. The two call sites
(compact card on Channels, large hero on Wallet) diverged enough that
sharing required threading className overrides for every internal node.
Inline each shape next to its surrounding context.
Also drop the pending-closed-channels alert from Wallet — it's
channel-management context and belongs on the Channels page only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): a11y and small cleanups on wallet on-chain mode
- Add aria-label to the balance-mode toggle and aria-hidden on its
icon so screen readers announce it as a mode switch.
- Drop the unnecessary :index suffix on the pending-closed-channels
item key — (fundingTxId, fundingTxVout) is unique.
- Replace trailing mb-4 on each on-chain transaction row with
space-y-4 on the list container so the last row has no dangling
margin.
- Use named useState import in Wallet to match repo convention.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): move OnchainTransactionsTable to components/wallet and drop node page copy
Wallet is now the single place to review on-chain transaction history,
so drop the duplicate <OnchainTransactionsTable /> from the Node page
and move the component to components/wallet/ to reflect its new home.
Pure move — no content changes to the component itself.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): simplify OnchainTransactionsTable to its one use case
Wallet is now the sole consumer of this component, so remove the
reusability props that were only there for the now-dropped Channels
page render: wrapInCard, title, className, contentClassName,
showEmptyState, and the four emptyState* props. The card-wrapping
branch and its CardHeader/CardTitle dependencies are gone with it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): add on-chain transaction details dialog
Open a lightweight details dialog from an on-chain transaction row
showing amount, status, confirmations, date/time, and full transaction
id. Mempool becomes a secondary action in the dialog footer (hidden
when no mempool URL is configured).
Diverges slightly from saunter's original implementation: body content
lives in a sibling div of DialogDescription instead of inside it, so
the block elements don't nest inside a <p> (invalid HTML / React
hydration warning).
Folds in https://github.com/getAlby/hub/pull/2218 (original author: saunter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(frontend): polish on-chain pending transaction state
Treat unconfirmed on-chain transactions as pending in the Wallet UI.
Keep the backend "unconfirmed" state value unchanged — this only
affects UI labels and the variable name (isUnconfirmed → isPending).
Folds in https://github.com/getAlby/hub/pull/2221 (original author: saunter).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): use "transactions" instead of "payments" in on-chain empty state
Aligns terminology with bitcoin.design convention: "transaction" for
on-chain, "payment" for lightning.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): reset receive-onchain flow on "Receive Another Payment"
The success screen's "Receive Another Payment" button linked to
/wallet/send (typo) and later to /wallet/receive/onchain (still broken
since the route is unchanged, so React Router wouldn't remount and the
local state kept showing the success card).
Replace the LinkButton with a Button that resets the local state
(txId, confirmed/pending amount, start timestamp) and requests a fresh
on-chain address, taking the user back to "Waiting for Payment…" with
a new QR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): address review feedback on wallet on-chain mode
- OnchainTransactionsTable: don't flash the empty state while SWR is
still loading; only render it once transactions is defined AND empty.
- OnchainTransactionsTable: add DialogDescription to the details dialog
so DialogContent has an accessible description (fixes Radix's dev
warning and improves screen-reader flow).
- OnchainTransactionsTable: swap the copy-tx-id native button for the
shadcn Button in ghost/icon-xs — kept the row-level button native
because the shadcn variants (justify-center, h-9, bg) fight the
custom-shaped row surface.
- Wallet: dynamic aria-label on the balance-mode toggle so the current
state is announced (previously "Switch balance mode" hid the visible
label).
- Wallet: align on "On-chain Balance" (repo convention — ~18 other uses)
instead of "On-Chain Balance".
- Wallet: gate the "Open Your First Channel" alert on channels being
loaded so it doesn't flash while useChannels() is resolving.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): unify copy buttons in transaction details dialogs
Both the Lightning (TransactionItem) and on-chain (OnchainTransactionsTable)
transaction details dialogs now use the shadcn Button component for
inline copy-to-clipboard affordances — variant=ghost, size=icon-sm,
muted foreground to match the surrounding value text. Previously Lightning
used a bare CopyIcon with onClick and on-chain used a mix; now both
dialogs look and behave the same.
Also drops the now-redundant "Copy Transaction ID" footer button from
the on-chain dialog (the inline copy icon next to the tx id does the
same thing). Footer hosts only "View on Mempool" and renders only when
a mempool URL is configured.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): second review pass — pluralization and null-guard fixes
- OnchainTransactionsTable: pluralize the confirmations tooltip so a
single-confirmation tx reads "1 confirmation" instead of
"1 confirmations".
- PendingClosedChannelsAlert: guard the mempool funding-tx link on
info?.mempoolUrl being resolved — previously the URL interpolated to
"undefined/tx/..." during the brief window before useInfo settles.
Falls back to omitting the funding-tx link rather than rendering a
broken one.
- PendingClosedChannelsAlert: only render "with {details}" when the
details arrays are non-empty. Avoids the dangling "with." sentence
in the unlikely case that pendingBalancesFromChannelClosures > 0
while both pendingBalancesDetails and pendingSweepBalancesDetails
are empty.
Other items in the review were already addressed in earlier commits:
capitalization (6a8f47b3), row mb-4 → space-y-4 on parent (c51826bb),
and the title/emptyState* props (gone with the component simplification).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): drop redundant mempoolUrl guards
Backend always populates info.mempoolUrl — MEMPOOL_API env defaults to
https://mempool.space/api in config/models.go and GetMempoolUrl
(config/config.go:206) returns it with /api trimmed. There's no code
path that leaves the URL empty, so guarding the "View on Mempool"
button and the "funding tx" link against it being falsy was defensive
against a state the backend won't produce. Matches the convention
used elsewhere (ReceiveOnchain etc.).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): guard against negative incoming and drop dead mb-1
In the Channels-page inline on-chain balance block:
- Switch the "+X incoming" guard from spendable !== total to
total > spendable, matching the wallet hero. Prevents a negative
displayed amount in the unlikely event spendable briefly exceeds total.
- Drop mb-1 from the inline <span> wrapping FormattedBitcoinAmount;
margin-bottom is a no-op on inline elements. Parent <div className="mb-1">
already provides the intended spacing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): drop unreachable short-txid branch in subtitle
Bitcoin transaction ids are always 64 hex chars, so the
`tx.txId.length > 22` ternary's false branch is dead code.
Always render the truncated head…tail form in the row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): group subtitle with other derived vars
Move the subtitle declaration up alongside Icon, isPending,
typeStateText, statusText, createdAt at the top of OnchainTransactionRow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): use ExternalLinkButton for "View on Mempool"
Replace the Button + onClick(openLink(...)) pattern with the repo's
ExternalLinkButton component. In http mode it renders as a real
<a target="_blank" rel="noreferer noopener"> so right-click-to-open,
middle-click, keyboard nav, and ctrl-click all work; in wails mode it
falls back to openLink. Previously the button always used the wails
path regardless of mode.
Also drops the now-unused openLink import.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: redirect to send screen in onchain mode
* chore: align components
* feat(frontend): drive wallet balance mode from URL
Lift the spending/onchain toggle state into a `mode` query param so the
browser back button restores the previous mode after going through
send/receive flows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): split wallet dashboard into Lightning and Onchain routes
Replaces the single conditional Wallet component with two sibling route
components mounted under a shared WalletLayout that hosts the page header
and Outlet. The Lightning view (default `/wallet`) is the renamed and
trimmed-down former index.tsx; on-chain (`/wallet/onchain`) is a new
component with its own balance, action buttons, and transactions list.
The toggle UI stays — clicking it now navigates between the two real
routes instead of flipping a `?mode=` query param, so the browser back
button restores the previous tab for free.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(frontend): drop on-chain incoming indicator from balance header
Pending receives are already visible in the on-chain transactions list
below, so the duplicated +X incoming summary just adds noise.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
* feat: user labels poc
* feat: backend for transaction user labels
Add PATCH /api/transactions/:paymentHash/label that merges a
user-supplied {key:value} map into the existing transaction metadata
under the user_label key, preserving NIP-47 fields. Empty map clears
the labels. Trims whitespace, drops blank rows, caps key/value length.
Wire the frontend editor to call the endpoint and revalidate the
transactions SWR cache on success.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: include user labels as columns in CSV export
Collect the union of user_label keys across all exported transactions
and emit each one as its own label_<key> column. The existing metadata
JSON column is preserved so importers like Raccoin keep working.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: drop hardcoded label suggestions, autocomplete from prior keys
Open the editor with a single blank row instead of four pre-seeded
fields. Suggest label keys via a datalist populated from any
user_label keys already present in the SWR transactions cache, so
suggestions reflect the user's own taxonomy.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: inline label editor and indicator-only list row
Replace the nested label dialog with an inline editor that toggles
within the existing transaction detail dialog, removing dialog
stacking. In the transactions list, replace per-label badges with a
single tag icon next to the timestamp so row height stays uniform; the
full labels remain visible in the detail view.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: don't bump updated_at on metadata-only edits
GORM's Update auto-touches updated_at, which made the transactions
list reorder labeled transactions to the top. Switch
SetTransactionMetadata to UpdateColumn so only the metadata column
changes. Add regression test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat: route PATCH transaction label requests in wails
The desktop app routes API calls through WailsRequestRouter rather
than HTTP. Add a handler for PATCH /api/transactions/:hash/label
before the existing transaction lookup so labels work in Wails too.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: finalize backend
* chore: rename to labels
* chore: finalize frontend
* chore: use tx id to add user labels
* chore: address minor nits
* fix: linting
* fix: use explicit primary key lookups
* chore: simplify transaction csv label export
* fix(frontend): drop label count from transaction list badge
The count adds visual weight without informing any decision from the
list view. The icon-as-badge already signals labels exist; the actual
values are in the details dialog.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): revert transaction list label indicator to bare icon
The Badge wrapper made the icon-only indicator wider than tall, which
looked off. Restores the pre-PR look — a small TagIcon next to the
timestamp — since the in-dialog editor is the place to see actual labels.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
* feat: make Alby the default theme and rename default to classic
* refactor(frontend): extract neutral base.css for theme defaults
Splits the global :root/.dark fallbacks out of alby.css into a dedicated
base.css so no single theme is special. Alby and Classic both become
pure override layers; Classic is now an empty layer that falls through
to base, eliminating the :root/.theme-classic dual-selector pattern.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(frontend): improve Earn page readability and polish
Use theme-safe link styling instead of text-primary (unreadable yellow
on white in the Alby theme), bolder reward amounts, and trophy/heart
icons for non-sat rewards.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: simplify reward icon handling
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
refactor(frontend): replace time-based greeting with static title
The previous time-of-day greeting used the browser's local hour but had
range bugs (e.g. midnight showed "Good Morning"). Rather than iterate on
the ranges, drop the greeting entirely and use "Home" to match the
sidebar nav.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(frontend): redesign Pro upgrade dialog and fix theme bleed
- Drop the `premium` button variant; default buttons now use the alby
theme gradient via theme tokens instead of hard-coded amber
- Fix alby button gradient: target `data-variant` only (so Radix
asChild wrappers like DialogTrigger don't break the selector) and
unlayer the override so it's not overridden by Tailwind utilities
- ExternalLink now forwards arbitrary props so ExternalLinkButton's
data-variant attribute reaches the rendered <a>
- Recompose UpgradeDialog: Pro badge -> headline -> stacked $3 / mo
price -> 6-feature grid -> CTA with inline price -> guarantee
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): apply V1 upgrade modal design
Restructure the Pro upgrade dialog to match the V1 "Refined default"
design from the Claude Design handoff:
- Pro pill: primary-tinted Badge with sparkles glyph
- New headline ("Do more with your Hub.") + open-source subhead
- Price block: $3 / month inline with "Billed $36 yearly" caption
- Single-column feature list with primary-tinted check circles
- CTA drops the inline price (already prominent above) and the
guarantee tightens to "30-day money-back guarantee"
All color treatments use theme tokens (bg-primary/10, border-primary/40,
text-foreground) so the look adapts per hub theme.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(frontend): inline Pro badge in upgrade dialog heading
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: copy
* fix(frontend): compose onClick in ExternalLink non-http branch
Spreading props after setting onClick silently dropped caller-provided
handlers. Compose them instead so callers' onClick runs, with
openLink skipped if the event is prevented.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore: add sr-only to dialog header
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
* feat: redesign settings pages with improved UI
- Add icons and group nav into sections
- Visual theme picker with color previews
- Segmented light/dark/system toggle
- Card-based layout for settings sections
- Alby Account: profile info with ProBadge
- Locked themes trigger UpgradeDialog
- Sidebar: startsWith for active states
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback
- Sidebar: boundary-aware route matching
- Theme cards: use button for keyboard a11y
- Appearance toggle: add aria-pressed
- Alby Account: show email when name exists
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use CSS variables for theme previews
Use actual theme classes with CSS variables instead
of hardcoded hex colors for theme preview cards.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract ThemePreview component
Use CSS theme classes directly on preview elements
with a theme-default wrapper for proper fallback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: mobile view
* revert: restore AlbyAccount page to master
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: align general settings with other settings pages
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: drop unused optional description change in SettingsHeader
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: improve theme picker accessibility and loading state
- Use role=radiogroup/radio semantics for theme grid and dark mode toggle
- Make locked theme cards keyboard-accessible via controlled UpgradeDialog
- Gate render on albyMe load when account connected to prevent lock flash
- Use cursor-not-allowed on disabled theme cards
- Mark ThemePreview aria-hidden (decorative)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: extract isPathActive helper in AppSidebar
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor: move ThemePreview to components directory
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: align settings sidebar with main sidebar
* chore: polish appearance section in settings
* chore: add spacing for scroll in backup page
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
* feat: improve Alby Account settings page
Redesigns the settings page with a unified profile card, groups
destructive actions into a "Danger Zone", and simplifies the copy
for the Switch/Disconnect flows.
Closes#2247
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: handle loading and failed state
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
* feat: add Sats4AI to app store
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: pad Sats4AI logo to square
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(frontend): use npx skills add for agent skill install prompts
Agents WebFetch the GitHub URL and summarize the skill content, losing
important details. Switch to `npx -y skills add` commands which install
the skill directly, matching the pattern already used for payments-skill.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(frontend): use npx skills add for Bitrefill skill prompt too
Bitrefill is also on the skills registry as bitrefill/agents.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add both Sat and Msat companion fields for all ambiguous balance/amount properties
* feat: add sat and msat companion fields to backend API responses
* chore: align frontend types and other missing fields
* chore: keep old formula for calculating total fee sat
* chore: remove unnecessary balance assignments in cashu and phoenix
* chore: add deprecated comment to non unit fields
* chore: rename callers and variable to specify units
* chore: remove msat fields for channel size and liquidity fields
* chore: drop msat fields from onchain channel size and liquidity responses
* chore: further changes
* chore: remove amount msat field onchain tx
* chore: remove msats from onchain balance response
* chore: remove msat fields for swaps
* chore: remove msat fields for punishment reserves
* chore: simplify rebalancing fee calculation
* chore: remove unnecessary fields
* chore: mark deprecated fields in frontend types
---------
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
* feat(frontend): improve wallet page balance layout
Center the wallet balance area and tune spacing for a cleaner visual rhythm.
Move secondary actions to the header as ghost buttons and use a vertical more icon.
Made-with: Cursor
* feat(frontend): refine wallet header actions on mobile
Move wallet secondary actions into the mobile overflow menu and add Recurring.
Also remove the ZapPlanner card from Home to avoid duplicate entry points.
Made-with: Cursor
* refactor(frontend): separate wallet actions, add ProDropdownMenuItem and AlertAction
- Extract wallet navigation (Swap, Recurring, Buy) into dedicated WalletActionsMenu component
- Revert TransactionsListMenu to single-purpose (export transactions only)
- Move CSV export logic to shared transactions-utils
- Add ProDropdownMenuItem for reusable pro-gated dropdown items with consistent Pro badge
- Add AlertAction component to alert system for proper action button placement
- Use controlled mode in UpgradeDialog to avoid DialogTrigger data-slot conflicts
- Replace inline upgrade gating in Channels (Set Node Alias) and Settings (themes)
- Theme select now opens UpgradeDialog instead of disabling paid items
- Use AlertAction in SubwalletList for upgrade prompt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(frontend): consolidate export into WalletActionsMenu
Show wallet actions (Swap, Recurring, Buy) only on mobile,
Export Transactions on all breakpoints — removes the separate
TransactionsListMenu from the wallet page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(frontend): sanitize CSV export and fix subwallet upgrade copy
Prevent CSV formula injection by prepending a single quote to values
starting with =, +, -, or @. Fix grammar in sub-wallet upgrade prompt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(frontend): center fiat amount skeleton on wallet page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(frontend): improve wallet page balance layout
Center the wallet balance area and tune spacing for a cleaner visual rhythm.
Move secondary actions to the header as ghost buttons and use a vertical more icon.
Made-with: Cursor
* fix: no important modifiers
* fix: remove important modifiers
* fix: use ghost for both occurrences
* fix: screenreader
* fix: center fiat amount skeleton on wallet page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: ai
* fix: review feedback
* fix: new tabs, install via prompt
* fix: cleanup
* fix: visual improvements
* fix: connection names
* fix: cursor
* fix: copy
* fix: optimize images
* fix: agent card
* fix: cursor logo
* fix: avatar rounding
* fix: class
* fix: link with tab handling
* fix: example prompts, claude code using skill
* fix: capabilities
* feat: share connection instructions
* fix: mange + explore ai apps
* fix: always create a new connection
* fix: use externallink, cleanup
* fix: improved hero copy
* fix: copy
* fix: apply CodeRabbit auto-fixes
Fixed 1 file(s) based on 2 unresolved review comments.
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
* fix: formatting
* fix: openclaw description
* fix: dim card classes
* fix: use tailwind divide classes
* fix: stop cursor blinking in header
* feat: use CLI auth flow for agent wallet onboarding
Replace connection secret embedding with @getalby/cli auth command
for CLI-based agents. The auth flow generates keys locally so the
secret never leaves the device or gets sent to the AI model.
- Generic agents skip connection creation, show auth prompt immediately
- Claude Code and Goose CLI tabs use auth instead of secret/MCP config
- Claude Web/Desktop and Goose Desktop still use MCP URLs as before
- "Waiting for agent to connect" only shown for MCP-based agents
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: simplify AI agent onboarding with CLI auth flow
- Use @getalby/cli auth for all CLI agents instead of embedding
connection secrets in prompts — keys stay local, never sent to AI
- Split Claude dropdown into "Claude Code" (auth prompt) and
"Claude Web/Desktop" (MCP setup via /apps/new)
- Goose uses generic auth prompt (no dedicated setup page needed)
- Remove Goose internal-app page/route (no longer linked)
- Remove standalone ClaudeConnectionInstructions and
GooseConnectionInstructions components (inlined into consumers)
- Add agent logo map in AppAvatar for connections without full
app store entries (goose, openclaw, cursor, codex, cline, opencode)
- Remove Goose from app store (prevents 404 on internal redirect)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add gemini, update mcp link
* fix: clarify Claude connector instructions (#2216)
fix: update Claude connector setup wording
Align Claude Web/Desktop steps with current Connectors labels and MCP URL wording.
Made-with: Cursor
Co-authored-by: René Aaron <rene@twentyuno.net>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: inline MCP setup on AI page, remove Claude internal-app
- Add generic McpSetup component for inline MCP URL connection
creation (reusable for future agents with MCP support)
- Claude entry gets mcpInstructions for Web/Desktop connector setup
- Remove /internal-apps/claude page and route
- Remove Claude from app store, add to agentLogos map
- Support subfolder installs in auth prompt hub URL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: update inspiration prompts and copy
- Add service names to prompts (bitrefill.com, ppq.ai, unhuman.store)
- Add "Creative" category with image generation + print-on-demand
- Replace weak automation example with spending analysis
- Rename heading to "What can your agent do?"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: improve AI page prompt cards UX
Make inspiration prompts clickable to copy (matching connect card),
use ChevronRightIcon consistently, and add discovery prompt to Services.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: update Services prompt to podcast search example
Replace untestable "bitcoin price data" prompt with a working
discover→fetch example using Pull That Up Jamie podcast search.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: use npx skills add for agent onboarding prompts
WebFetch summarizes SKILL.md content, losing critical details. Switch
to `npx -y skills add getAlby/bitcoin-payments-skill` which downloads
the full skill file via GitHub without summarization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update prompts
* fix: update skill install command to use correct repo name
The repo was renamed from getAlby/bitcoin-payments-skill to
getAlby/payments-skill.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add -y flag to skill install prompts for non-interactive install
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: redirect to app detail page after connection is established
After approving a new app connection, redirect to /apps/:id instead of
the apps list so the user sees the app they just created.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: match agent logos by app name fallback
When app_store_app_id metadata is not set, fall back to matching the
app name against the agent logos map so agent apps created via auth
show proper icons without needing app store entries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: saunter <68239231+stackingsaunter@users.noreply.github.com>
* fix: common problems in ui
* fix: add layout, spacing, and copy guidance to AGENTS.md
Address recurring agent mistakes: nested card layouts, inconsistent
spacing, and copy that doesn't match the user's context inside the wallet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore(frontend): upgrade shadcn/ui components for Tailwind v4
- Refresh vendored UI primitives from shadcn registry (radix-ui, lucide, etc.)
- Fix components.json tailwind.config for Tailwind v4 CLI
- Preserve Hub splits: buttonVariants, badgeVariants, hybrid tooltip + TouchProvider
- Keep custom alert/badge variants; sonner uses document dark class
- Remove duplicate use-mobile.ts (use use-mobile.tsx only)
- Card keeps shadow-sm to match pre-change product default
Made-with: Cursor
* feat(frontend): alby theme polish and theme-aware hub logo
- Alby: lighter primary gradient, 1px stroke, stacked focus ring
- Alby: neutral gray palette; dark primary button label matches light
- Hub logo: default/alby light & dark fills; mono for other themes
- Figtree for alby; map tailwind font-sans via --app-font-sans
- Link buttons: data-slot/data-variant for themed primary styles
Made-with: Cursor
* ci: fix fork pr macos builds in http and wails workflows
Select Xcode.app for CGO on macOS so Wails WebKit compiles.
Skip Apple signing/notarize/DMG when the PR head is a fork (no secrets);
upload an unsigned zip for Wails fork PRs.
Set fail-fast false on the HTTP build matrix so Linux jobs finish if macOS
fails.
Made-with: Cursor
* ci: gate macos signing on workflow_call build-release input
For workflow_call runs, require inputs.build-release for macOS signing.
Fork PR logic unchanged.
Made-with: Cursor
* ci: revert macos workflow signing fixes
Revert workflow_call gating and fork unsigned logic.
Remove Xcode selection step added for CGO builds.
Made-with: Cursor
* fix: fallback font
* fix: theme detection for sonner
* fix: accent color for alby theme
* fix: cleanup alby hub logo implementation
* fix: revert external link change
* fix: unify font loading
* fix: hub logo coloring / inversion
* fix: disable card shadows
* fix: remove old radix deps
* fix: stepper asChild
* fix: not selector
* fix: restore max-h-full on sheet to ensure full viewport height
The shadcn upgrade dropped max-h-full from the right/left sheet
variants, causing the sheet to not cover the full viewport height.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
Co-authored-by: René Aaron <100827540+reneaaron@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: polish recently used apps widget
Match Home widget styles for the See All CTA, card shell, and chevron interaction.
Made-with: Cursor
* fix: move recently used apps widget to left column
Place Recently Used Apps in the left Home column above the Alby Go widget.
Made-with: Cursor
* fix: remove obsolete classes
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
* feat: add New Arrivals widget to Home
Show up to three curated latest apps in a New Arrivals card.
Place it in the left Home column above Alby Go.
Made-with: Cursor
* feat: make New Arrivals widget data-driven with addedDate
- Add addedDate field to AppStoreApp type
- Add addedDate to latest 3 apps (nadanada, LendaSwap, Bitrequest)
- Sort appStoreApps by addedDate (newest first), then alphabetically
- Remove hardcoded latestAppStoreAppIds list
- Remove duplicate getAppDestination function
- Align widget styling with other Home widgets
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: sort by addedDate in NewArrivalsWidget, keep app store alphabetical
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract getAppStoreUrl utility and deduplicate app link logic
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: replace bitrequest with castamatic in new arrivals
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use descriptive alt text for app logos in SuggestedApps
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: align App of the Day widget UI
Use a chevron row layout like Recently Used Apps and place the widget above
Alby Go in the left Home column without changing selection logic.
Made-with: Cursor
* fix: remove obsolete classes
* fix: use theme classes
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
* fix: align Pro badge styling with theme
Use theme-aware colors (primary with opacity) instead of hardcoded
amber gradient, so the badge fits the dark theme while still standing out.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: increase Pro badge background opacity
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use solid primary colors for Pro badge visibility
Use bg-primary/text-primary-foreground instead of low-opacity tint
to ensure the badge is visible across all themes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add nadanada to app store suggestions
Add nadanada with app metadata, onboarding guide text, and a compliant 200x200 logo asset so it appears in the Alby Hub app store list.
Made-with: Cursor
* fix: update connection instructions
---------
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
Non-custodial Bitcoin ↔ Stablecoin atomic swaps via NWC.
Uses pay_invoice for Lightning→EVM swaps and make_invoice for
EVM→Lightning swaps.
Categorized under payment-tools with install/finalize guides.
* feat: track last settled transaction time for apps
* fix: use default subwallet ordering and app_id logging in event handler
* chore: rename to last_settled_transaction_at and remove last tx migration
* chore: split app settlement update and budget check
* chore: extract app display name helper into utils function
* fix: one-click connection flow shows waiting screen instead of hanging
When creating a connection via /apps/new?pubkey=..., the flow previously
hung after clicking "Connect" because the finalize step was excluded and
handleCreateApp returned early. Now the finalize step is included for the
pubkey flow with a simplified waiting screen that polls for the first
NWC request and redirects on success.
Closes#2197
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: success handling
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add chain data source and address to about page
* fix: only show chainsource for ldk
* fix: redact chain-source address secrets and return complete bitcoind endpoint
* chore: make sanitize function readable and add tests
* fix: align ldk chain data source labels
---------
Co-authored-by: anon <anon@anon.com>
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
* feat: claude.md as symlink to agents.md
* fix: use actual symlink for CLAUDE.md instead of include directive
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fixes#2065
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Improvements**
* Hold invoices now support an optional minimum CLTV expiry delta with
validation to ensure values stay within protocol limits, allowing finer
control over confirmation timeout behavior.
* **Chores**
* Updated a direct library dependency to a newer version.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
* fix: checks for ok status code in responses
* refactor: replacing native fetch with useSWR
* chore: remove url from logs
* chore: add phoenixd error logs for non-success responses
* chore: add use currencies hook
* chore: use loading from currencies hook and filter out btc
---------
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
* fix: return app metadata and lud16 in get_info without scope
* chore: remove unnecessary no error check
* chore: rename nodeInfo to infoResponse in get info tests
* feat: refresh support widget on Home
Rename Support Alby to Support Open Source, apply the updated card UI,
and place it in the right column under Recently Used Apps.
Made-with: Cursor
* fix: remove obsolete classes
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
* style(frontend): remove card shadows and set card grid gap to 3
- Use shadow-none on shadcn Card and drop shadow-xs on UpgradeCard
- Standardize spacing between card groups to gap-3 (12px) across grids
Made-with: Cursor
* ci: fix macos gha builds for wails and fork prs
Select Xcode.app before CGO so Wails WebKit uses the full SDK (avoids
Foundation parsed as C on CLT-only toolchains).
Skip Apple signing/notarize/DMG when the PR head is a fork; upload an
unsigned zip instead. Add fail-fast: false to the HTTP build matrix.
Made-with: Cursor
* ci: require build-release for workflow_call macos signing
Gate cert import, codesign, DMG, notarize, and signed DMG upload on
inputs.build-release when the event is workflow_call. Keeps fork PR
unsigned zip path; push and internal PR behavior unchanged.
Made-with: Cursor
* ci: revert macos workflow signing fixes
Revert CI changes that attempted to fix macOS build failures;
keep PRs focused on frontend changes.
Made-with: Cursor
* fix: cleanup
---------
Co-authored-by: René Aaron <rene@twentyuno.net>
* feat: make installation scripts agent-friendly with CLI args
Add command-line argument support to install.sh and update.sh for both
x86_64 and aarch64 architectures:
- -d, --install-dir DIR Set installation directory
- -s, --systemd Auto-setup systemd service (install only)
- --no-systemd Skip systemd setup (install only)
- -y, --yes Non-interactive mode (auto-confirm prompts)
- -h, --help Show usage information
This allows agents and automation tools to run the scripts without
interactive prompts, making them suitable for CI/CD pipelines and
automated deployments.
* fix: add argument validation for --install-dir flag
Add validation to ensure -d/--install-dir is provided with a valid value:
- Checks that argument exists (not empty)
- Checks that argument doesn't start with '-' (not another flag)
- Exits with error message if validation fails
Fixes CodeRabbit review feedback on PR #2144.
* feat: add --skip-verify flag to skip verification step
Add --skip-verify flag to install and update scripts:
- linux-x86_64/install.sh
- linux-x86_64/update.sh
- linux-aarch64/install.sh
- linux-aarch64/update.sh
When --skip-verify is passed, the scripts skip downloading and
calling verify.sh entirely.
Usage: ./install.sh --skip-verify
* fix: ensure install dir does not contain whitespace
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Apply suggestion from @rolznz
* Apply suggestion from @coderabbitai[bot]
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* Apply suggestion from @rolznz
* Update scripts/linux-aarch64/install.sh
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
---------
Co-authored-by: molty21 <moltbot21@agentmail.to>
Co-authored-by: Roland <33993199+rolznz@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* feat: add custom message support for subwallet transfers
- Add optional message field to TransferRequest model
- Update Transfer function to use custom message with 'transfer' as fallback
- Update HTTP and Wails handlers to extract and pass message parameter
- Add message input field to IsolatedAppTopupDialog and IsolatedAppDrawDownDialog
- Maintains backward compatibility with empty message defaulting to 'transfer'
Closes#2066
* chore: rename
* chore: rename and simplify
---------
Co-authored-by: Roland Bewick <roland.bewick@gmail.com>
* fix: intercept self hold payments based on invoice rather than payment hash
* fix: generate test invoices with long expiry
* fix: add timeout seconds for standard lnd payments
* fix: return optional total balance in list apps response for subwallets
* chore: add error handling to subwallet balance query
* chore: add METADATA_APPSTORE_APP_ID_KEY constant
* chore: add MAX_FREE_SUBWALLETS constant
* chore: use subwallet query and total count for limit check
* feat: add Alby CLI to the app store
Closes#2047
Adds Alby CLI (NWC CLI with lightning tools) to the Hub app store
under wallet-interfaces category. Includes install guide (npm/npx)
and connection guide (NWC_URL env var or -c flag).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: hide connection QR code for CLI app
CLI users will copy-paste the connection secret, so the QR code adds no value.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: modify finalize guide
---------
Co-authored-by: Joel Klabo <max@klabo.world>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
* feat: add HIDE_UPDATE_BANNER environment variable
Add a new HIDE_UPDATE_BANNER env var that allows platform operators
(e.g. Start9) to suppress the built-in version update banner when
they provide their own update notification mechanism.
When set to true, the "What's New" widget is hidden and the header
banner only shows for VSS migration notices. The version comparison
against the Alby API is skipped entirely.
Closes#2048
* chore: simplify
* chore: undo changes
---------
Co-authored-by: Joel Klabo <max@klabo.world>
Co-authored-by: im-adithya <imadithyavardhan@gmail.com>
Transactions are listed from the database via transactionsService, not
from the LN backend. The LNClient.ListTransactions method was never
called and each backend's implementation was dead code.
Closes#2045
Co-authored-by: Joel Klabo <max@klabo.world>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This file provides guidance for AI coding agents working on the Alby Hub repository.
## Project Overview
Alby Hub is a self-custodial **Nostr Wallet Connect (NWC)** service that bridges Lightning Network wallets with applications supporting the NIP-47 protocol. It supports multiple Lightning backends (LDK, LND, Phoenixd, Cashu) and runs as either a web server or a desktop app (via Wails).
├── http/ # Web-specific utilities (copied at build time)
└── wails/ # Desktop-specific utilities (copied at build time)
```
## Development Setup
### Prerequisites
- Node.js 20+
- Yarn
### Running in HTTP Mode (Primary)
```bash
# Terminal 1 – Frontend (port 5173)
cd frontend
yarn install
yarn dev:http
# Terminal 2 – Backend (port 8080)
cp .env.example .env # configure as needed
go run cmd/http/main.go
```
### Running in Desktop Mode (Wails)
```bash
wails dev -tags "wails"
```
**Wails versions must stay in sync:** the `github.com/wailsapp/wails/v2` version in `go.mod` and the Wails CLI version installed in `.github/workflows/wails.yml` (`go install ...cmd/wails@vX.Y.Z`) must match. When bumping one, always update the other — this is a common source of drift (e.g. via Dependabot updates to `go.mod` only).
Mocks are generated with `mockery` (config in `.mockery.yaml`); run it after changing any interface.
### Frontend
```bash
cd frontend
yarn lint # ESLint + TypeScript type check + Prettier
yarn tsc:compile # TypeScript only
yarn format # Prettier only
```
No Jest/Vitest tests exist; frontend quality is enforced via linting.
## Building
```bash
# HTTP production build
cd frontend && yarn build:http
go build -o main cmd/http/main.go
# Docker
docker build . -t albyhub:latest
```
## Key Architecture Patterns
### Request Flow
```text
HTTP Request / NIP-47 Nostr Event
→ HTTP Handler / NIP-47 Event Handler
→ api/ package (business logic)
→ LNClient interface
→ Backend implementation (LDK/LND/Phoenixd/Cashu)
```
### Event System
Services communicate via `events/` pub/sub. Prefer publishing events over direct inter-service calls. Key events use the `nwc_*` prefix (e.g., `nwc_payment_sent`, `nwc_payment_received`).
### Platform-Specific Frontend Code
Code under `frontend/platform_specific/http/` and `frontend/platform_specific/wails/` is swapped at build time. Any platform-specific frontend logic must have both variants.
### LNClient Interface
`lnclient/models.go` defines the interface all backends must implement. Changes to this interface require updates to all four implementations (LDK, LND, Phoenixd, Cashu) and their mocks.
## Database
- **Migrations:**`db/migrations/` — always add new migrations here; never modify existing ones.
- **SQLite:** WAL mode, 5s busy timeout, 20MB cache. Default for development and most deployments.
- **PostgreSQL:** Supported for production. If touching DB code, test with both.
- **ORM:** GORM with `go-gormigrate`. Use GORM conventions for new models.
## Coding Conventions
### Go
- Idiomatic Go; `gofmt` formatting expected.
- Structured logging via `logrus` with contextual fields — no `fmt.Print`.
- Error wrapping with `fmt.Errorf("context: %w", err)` for debugging.
- Use the event publisher for cross-service communication.
- New API endpoints belong in `api/api.go` with corresponding HTTP routes in `http/http_service.go`.
### TypeScript / React
- **Avoid using useNavigate** — Use <Link/> component where possible to ensure good browser UX.
- **Use shadcn/ui components** for all UI — do not create custom components unless no shadcn equivalent exists.
- **Do not modify core shadcn/ui components** — customize behavior by composing or wrapping them, not by editing the source files directly.
- **Prefer Tailwind utility classes** over custom `px` definitions or inline styles. Use Tailwind's spacing, sizing, and layout utilities instead of hardcoded pixel values.
- **Never use `!important` Tailwind modifiers** (e.g., `!px-12`, `!text-sm`). If a component's default styles need overriding, use a proper variant, compose with a wrapper, or extend the component — don't force specificity with `!`.
- **Use the theme system** for colors, border-radius, shadows, and other design tokens. Reference CSS variables / Tailwind theme tokens (e.g., `bg-primary`, `rounded-lg`, `shadow-sm`) rather than hardcoding hex values or arbitrary values. See `frontend/src/index.css` for available theme variables.
- **Keep layouts flat** — avoid nesting cards inside cards or wrapping elements in unnecessary bordered containers. Prefer clear, flat visual hierarchy.
- **Match existing spacing patterns** — before adding new components, check sibling components for consistent padding, margins, and gaps. Ensure sibling elements have equal dimensions where appropriate.
- **Write copy from the user's perspective** — Alby Hub IS the wallet; don't explain what a lightning wallet is or tell the user to "connect to a wallet" when they're already inside one. Keep UI copy concise and use the product's own vocabulary (sats, connections, apps).
- **Use the `*Icon` suffix when importing lucide-react icons** (e.g., `ZapIcon`, `BitcoinIcon`, `ArrowDownIcon`) — both forms are valid lucide exports, but this codebase consistently uses the suffixed alias. Don't mix styles.
- Strict TypeScript — no `any` types.
- Functional components with hooks only.
- SWR for server state; Zustand for client state (stores in `frontend/src/state/`).
- HTTP requests use the typed `request()` helper in `frontend/src/utils/request.ts`.
- New screens added to `frontend/src/routes.tsx`.
- ESLint + Prettier enforced via pre-commit hooks (husky).
### Branches
Use a type prefix followed by a short, dash-separated summary: `feat/`, `chore/`, or `fix/`. For example:
```text
feat/add-cashu-backend
fix/payment-timeout-crash
chore/bump-go-1.25
```
### Commits
Follow **Conventional Commits** format (`feat:`, `fix:`, `chore:`, etc.) — enforced by commitlint.
## Critical Files
| File | Purpose |
|------|---------|
| `cmd/http/main.go` | HTTP server entry point |
| `main_wails.go` | Desktop entry point |
| `api/api.go` | Primary API endpoint handlers |
| `service/service.go` | Core service initialization |
- Never log sensitive data (seeds, macaroons, tokens).
- Validate all user input at system boundaries; trust internal service calls.
## CI/CD
CI runs Go tests (including PostgreSQL), frontend lint/type checks, and binary builds for Linux and macOS. All checks must pass before merging to `master`.
@ -14,7 +14,7 @@ The application can run in two modes:
Ideally the app runs 24/7 (on a node, VPS or always-online desktop/laptop machine) so it can be connected to a lightning address and receive online payments.
## Run on Alby Cloud
## Learn more about Alby Hub
Visit [albyhub.com](https://albyhub.com) to learn more and get started and get Alby Hub running in minutes.
@ -29,6 +29,7 @@ By default Alby Hub uses the embedded LDK based lightning node. Optionally it ca
- LND
- Phoenixd
- Cashu
- CLN
- want more? please open an issue.
## Development
@ -59,7 +60,7 @@ By default Alby Hub uses the embedded LDK based lightning node. Optionally it ca
Go to `/frontend`
1. `yarn install`
2. `yarn dev`
2. `yarn dev:http`
### HTTP Production build
@ -157,7 +158,7 @@ For more information on the Go pprof library, see the [official documentation](h
The following configuration options can be set as environment variables or in a .env file
- `RELAY`: default: "wss://relay.getalby.com/v1" (can support multiple separated by commas)
- `RELAY`: default: "wss://relay.getalby.com,wss://relay2.getalby.com" (supports multiple separated by commas)
- `DATABASE_URI`: A sqlite filename or postgres URL. Default is SQLite DB `nwc.db` without a path, which will be put in the user home directory: $XDG_DATA_HOME/albyhub/nwc.db
- `PORT`: The port on which the app should listen on (default: 8080)
- `WORK_DIR`: Directory to store NWC data files. Default: $XDG_DATA_HOME/albyhub
@ -206,13 +207,33 @@ Migration of the database is currently experimental. Please make a backup before
go run cmd/db_migrate/main.go -from .data/nwc.db -to postgresql://myuser:mypass@localhost:5432/nwc
#### Migration from Postgres to Sqlite
No manual steps are needed: create a migration file from Settings -> Migrate Alby Hub. The contents of the Postgres database will automatically be copied into a Sqlite database which is included in the migration file. Alternatively, run the migration tool manually:
go run cmd/db_migrate/main.go -from postgresql://myuser:mypass@localhost:5432/nwc -to .data/nwc.db
## Node-specific backend parameters
- `ENABLE_ADVANCED_SETUP`: set to `false` to force a specific backend type (combined with backend parameters below)
### CLN Backend parameters
Can be configured via env or the UI
- `LN_BACKEND_TYPE`: CLN
- `CLN_ADDRESS`: the CLN grpc address (grpc-host and grpc-port), e.g. `127.0.0.1:9737`
- `CLN_LIGHTNING_DIR`: CLN's lightning directory containing the grpc certificates, usually `~/.lightning/<network>`
Optional for hold invoice methods support:
- `CLN_ADDRESS_HOLD`: the CLN hold plugin grpc address (grpc-host and grpc-port), e.g. `127.0.0.1:9738`
If you are copying the certificates to another machine make sure you get the `ca.pem`, `client.pem` and `client-key.pem` from the lightning directory and optionally from the `hold` directory inside the lightning directory and keep the sub-directory structure of the hold directory.
### LND Backend parameters
Currently only LND can be configured via env. Other node types must be configured via the UI.
LND can be configured via env. Other node types may need to be configured via the UI.
_To configure via env, the following parameters must be provided:_
@ -230,6 +251,8 @@ _To configure via env, the following parameters must be provided:_
- `LDK_MAX_CHANNEL_SATURATION`: Sets the maximum portion of a channel's total capacity that may be used for sending a payment, expressed as a power of 1/2. See `max_channel_saturation_power_of_half` in [LDK docs](https://docs.rs/lightning/latest/lightning/routing/router/struct.PaymentParameters.html#structfield.max_channel_saturation_power_of_half).
- `LDK_MAX_PATH_COUNT`: Maximum number of paths that may be used by MPP payments.
- `LDK_LOG_LEVEL`: Log level for the LDK node. Higher is more verbose. Default: 3. This is separate from the main application log level, allowing you to enable more verbose LDK logging (e.g., level 4, 5 or 6) without enabling verbose logging for the entire application.
- `LDK_CHANNEL_MONITOR_WARNING_SIZE_BYTES`: If a channel monitor is larger than this value, a performance warning will be shown on the node page.
- `LDK_LSPS2_ADDRESSES`: Override the LSPS2 just-in-time (JIT) LSP provider for receiving. When set, Alby Hub can receive payments even without inbound liquidity: the configured LSP opens a channel on the fly and the fee is deducted from the incoming payment. Expected format is a single `<pubkey>@<host>:<port>`. When set, the "Open Your First Channel" prompts are hidden since the first channel is created automatically on the first receive.
#### LDK Network Configuration
@ -244,6 +267,12 @@ _To configure via env, the following parameters must be provided:_
@ -262,6 +291,16 @@ _To configure via env, the following parameters must be provided:_
See [Phoenixd](scripts/linux-x86_64/phoenixd/README.md)
### Bark
Bark connects to an [Ark](https://second.tech/) server. It can be configured via env.
- `LN_BACKEND_TYPE`: BARK
- `BARK_SERVER`: the Ark server URL. For signet use `https://ark.signet.2nd.dev`
- `BARK_ESPLORA_SERVER`: the Esplora server URL used for chain data. For signet use `https://esplora.signet.2nd.dev`.
- `BARK_SERVER_ACCESS_TOKEN`: an optional access token, only required if using a private Ark server.
- `BARK_LOG_LEVEL`: Log level for Bark. Higher is more verbose. Default: 3. This is separate from the main application log level, allowing you to enable more verbose Bark logging (e.g., level 4 or 5) without enabling verbose logging for the entire application.
### Alby OAuth
Create an OAuth client at the [Alby Developer Portal](https://getalby.com/developer) and set your `ALBY_OAUTH_CLIENT_ID` and `ALBY_OAUTH_CLIENT_SECRET` in your .env. If not running locally, you'll also need to change your `BASE_URL`.
@ -363,6 +402,8 @@ Once the user has authorized the app connection a `nwc:success` message is sent
If you need help contact support@getalby.com or reach out on Nostr: npub1getal6ykt05fsz5nqu4uld09nfj3y3qxmv8crys4aeut53unfvlqr80nfm
You can also visit the chat of our Community on [Telegram](https://t.me/getalby).
For security vulnerabilities, please follow our [security policy](SECURITY.md).
Please report suspected security vulnerabilities privately by emailing [security@getalby.com](mailto:security@getalby.com). Do not open a public issue or disclose the vulnerability publicly until we have coordinated a fix.
Please include the affected version or component, the potential impact, and clear steps to reproduce the issue. We will acknowledge your report and keep you informed as we investigate and address it.
receiveInvoice,err:=api.svc.GetTransactionsService().MakeInvoice(ctx,rebalanceChannelRequest.AmountSat*1000,"Alby Hub Rebalance through "+rebalanceChannelRequest.ReceiveThroughNodePubkey,"",0,receiveMetadata,api.svc.GetLNClient(),nil,nil,&rebalanceChannelRequest.ReceiveThroughNodePubkey)
receiveInvoice,err:=api.svc.GetTransactionsService().MakeInvoice(ctx,amountSat*1000,"Alby Hub Rebalance through "+rebalanceChannelRequest.ReceiveThroughNodePubkey,"",0,receiveMetadata,lnClient,nil,nil,&rebalanceChannelRequest.ReceiveThroughNodePubkey)
iferr!=nil{
logger.Logger.WithError(err).Error("failed to generate rebalance receive invoice")
Select("SUM(amount_msat + fee_msat + fee_reserve_msat) as sum").
Where("app_id = ? AND type = ? AND (state = ? OR state = ?) AND created_at > ?",appPermission.AppId,constants.TRANSACTION_TYPE_OUTGOING,constants.TRANSACTION_STATE_SETTLED,constants.TRANSACTION_STATE_PENDING,getStartOfBudget(appPermission.BudgetRenewal)).Scan(&result)
returnresult.Sum/1000
Where("app_id = ? AND type = ? AND (state = ? OR state = ?) AND created_at > ?",appPermission.AppId,constants.TRANSACTION_TYPE_OUTGOING,constants.TRANSACTION_STATE_SETTLED,constants.TRANSACTION_STATE_PENDING,getStartOfBudget(appPermission.BudgetRenewal)).Scan(&result).Error
Where("app_id = ? AND type = ? AND state = ?",appId,constants.TRANSACTION_TYPE_INCOMING,constants.TRANSACTION_STATE_SETTLED).Scan(&received)
Where("app_id = ? AND type = ? AND state = ?",appId,constants.TRANSACTION_TYPE_INCOMING,constants.TRANSACTION_STATE_SETTLED).Scan(&received).Error
iferr!=nil{
return0,err
}
varspentstruct{
Sumint64
}
tx.
err =tx.
Table("transactions").
Select("SUM(amount_msat + fee_msat + fee_reserve_msat) as sum").
Where("app_id = ? AND type = ? AND (state = ? OR state = ?)",appId,constants.TRANSACTION_TYPE_OUTGOING,constants.TRANSACTION_STATE_SETTLED,constants.TRANSACTION_STATE_PENDING).Scan(&spent)
Where("app_id = ? AND type = ? AND (state = ? OR state = ?)",appId,constants.TRANSACTION_TYPE_OUTGOING,constants.TRANSACTION_STATE_SETTLED,constants.TRANSACTION_STATE_PENDING).Scan(&spent).Error
Where("app_id IN (?) AND type = ? AND state = ?",subwalletAppIDsQuery,constants.TRANSACTION_TYPE_INCOMING,constants.TRANSACTION_STATE_SETTLED).
Scan(&received)
ifres.Error!=nil{
return0,res.Error
}
varspentstruct{
Sumint64
}
res=tx.
Table("transactions").
Select("SUM(amount_msat + fee_msat + fee_reserve_msat) as sum").
Where("app_id IN (?) AND type = ? AND (state = ? OR state = ?)",subwalletAppIDsQuery,constants.TRANSACTION_TYPE_OUTGOING,constants.TRANSACTION_STATE_SETTLED,constants.TRANSACTION_STATE_PENDING).