feat(connect): manual-connect LSP modal (#119) - #53
Merged
Conversation
Closes the final gap in the join-by-strangers flow. Today:
- Coordinator-advertised LSPs show up in the Open Factories list
→ click row → Join Factory button → JoinFactoryModal browses
the LSP and lets you pick a factory.
- Someone with a full superscalar://join?... URL pastes it into
"Join via invite link" → AcceptInviteModal parses + fires the
join request.
The missing case: a friend DMs you "my LSP is <pubkey>@<host:port>,
come browse my factories." Nothing to paste into invite link (no iid
yet — you haven't picked which factory), and not in the coordinator
list. You're stuck with manual lightning-cli, or copying the pubkey
and creating a fake invite URL by hand.
New ManualConnectModal lets you type a pubkey + address and pops
the existing JoinFactoryModal to browse what factories the LSP
hosts. No new plugin RPC: factory-browse-host already takes a
peer + address hint, and the plugin's auto-connect helper
(PR #118) does the BOLT-8 hop.
What ships:
- apps/frontend/src/components/connect/ManualConnectModal/
ManualConnectModal.tsx — modal with pubkey + address + optional
alias fields, validation, and a paste-helper that splits
<pubkey>@<host:port> if you paste the full CLN connect format.
- apps/frontend/src/components/connect/ConnectList/ConnectList.tsx —
new "Connect to LSP manually ›" button next to the existing
"Join via invite link ›" button. On submit, constructs a
synthetic FactoryRow with the user's pubkey + alias + lnAddresses
and routes it to the existing JoinFactoryModal flow.
- The two trigger buttons share a flex row instead of stacking,
cleaning up the rough </Card>/<div> formatting that was there.
testids: manual-connect-modal, manual-connect-pubkey,
manual-connect-address, manual-connect-alias, manual-connect-submit,
manual-connect-error, open-manual-connect.
Validation:
- pubkey: 66 hex chars (33 bytes secp256k1 compressed)
- address: host:port format (host shape NOT strict — IPv4 / IPv6 /
.onion / DNS all valid, CLN's connectd does real check)
- address optional if already a peer (peer is reused)
Closes the wallet-side of task #119. With this + the existing
InviteModal + AcceptInviteModal + browse-from-known-LSP path, every
practical out-of-band onboarding flow is supported.
2 tasks
8144225309
added a commit
that referenced
this pull request
May 29, 2026
Four invite-flow gaps surfaced during PR #53 visual review: 1. inviteUrl.ts — add optional expires=<unix-ts> param + parseInviteUrlDetailed returning {invite, error: 'malformed' | 'expired' | null} so callers can distinguish bad URL from stale invite for UX messaging. 2. InviteModal (LSP host) — - Expiry dropdown (Never / 1d / 1w / 30d / 90d); embeds `expires` only when set so existing permanent-invite behavior is preserved by default. - Tor-preferred toggle: if the node advertises both .onion and ipv4, default to broadcasting the onion so casual invite-sharing doesn't leak the operator's IP. Operator can flip back. - Privacy warning Alert when the chosen address is a public-routable IP (RFC1918/loopback/.onion suppressed); shows the leaked address and points the operator at the tor toggle. 3. AcceptInviteModal (client side) — - Uses parseInviteUrlDetailed so expired invites surface a clear "ask the LSP operator for a fresh one" message instead of generic "malformed URL". - Trust gate: if the parsed address is public-routable, render a warning Alert with an "I trust the source of this invite" checkbox; the Send-join-request button stays disabled until ticked. - Tor info note when the invite uses an .onion address. 4. ConnectList — when the active node's network has no coordinator binding (common on regtest/sandbox profiles), the empty-state row now lists the user's *other* profiles that ARE covered and offers one-click switch buttons that reuse the same NodesService.switchNode flow as NodePicker (clear stores → switch → refetch). testids added (sweep-friendly): invite-expiry, invite-prefer-tor, invite-privacy-warning, accept-invite-trust-gate, accept-invite-trust-ack, accept-invite-tor-note, connect-no-coord-empty, connect-switch-<network>.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
New
ManualConnectModallets a user type an LSP's pubkey + address to browse that LSP's factory inventory, closing the final gap in the join-by-strangers flow. Closes the wallet-side of task #119.Why
Three onboarding paths a user might want today:
superscalar://join?…invite URL<pubkey>@<host:port>, come browse"The third row was the gap. This PR fills it: a "Connect to LSP manually ›" button next to "Join via invite link", opening a modal where you enter the pubkey + address and the wallet calls
factory-browse-hoston it (which the plugin's auto-connect helper turns into a BOLT-8 hop if needed, via PR #118).What ships
ManualConnectModal.tsx— pubkey + address + optional alias inputs with validation:host:portformat, optional if already a peer<pubkey>@<host:port>into the pubkey field (the format CLN'sconnectcmd uses), the wallet splits it automaticallyConnectList.tsxwiring:FactoryRowwith the user's pubkey + alias + lnAddresses and routes to the existingJoinFactoryModalflowbrowseHost+ the full join-request pathNo plugin changes
Reuses RPCs already shipped:
factory-browse-host(viaFactoriesService.browseHost)factory-join-request(viaFactoriesService.joinRequest)testids for the e2e suite
open-manual-connect(trigger button on Connect page)manual-connect-modal(modal wrapper)manual-connect-pubkey/manual-connect-address/manual-connect-alias(inputs)manual-connect-submit(submit button)manual-connect-error(validation error alert)Test plan
0375…ae45@127.0.0.1:41110into pubkey field → splits into pubkey + addressCloses the loop on #119
With this + the already-shipped
InviteModal(host generates QR + URL),AcceptInviteModal(client pastes URL), andJoinFactoryModal(browse from a known LSP), every practical out-of-band onboarding flow is supported. Discoverability polish (sidebar nav for /factories sub-pages, etc.) is tracked inproject-polish-backlog.mdand will fold into the next iteration.