Skip to content

feat(connect): manual-connect LSP modal (#119) - #53

Merged
8144225309 merged 1 commit into
mainfrom
feat/manual-connect-119
May 29, 2026
Merged

feat(connect): manual-connect LSP modal (#119)#53
8144225309 merged 1 commit into
mainfrom
feat/manual-connect-119

Conversation

@8144225309

Copy link
Copy Markdown
Owner

Summary

New ManualConnectModal lets 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:

Path Status before this PR
LSP is advertised by a Nostr rendezvous coordinator ✅ shown in Open Factories list, click row → Join Factory → JoinFactoryModal
LSP sends a full superscalar://join?… invite URL ✅ Connect → "Join via invite link ›" → AcceptInviteModal
A friend DMs you "my LSP is <pubkey>@<host:port>, come browse" ❌ no path — had to drop to lightning-cli

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-host on 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:
    • Pubkey: 66 hex chars (33 bytes secp256k1 compressed)
    • Address: host:port format, optional if already a peer
    • Paste-helper: if you paste <pubkey>@<host:port> into the pubkey field (the format CLN's connect cmd uses), the wallet splits it automatically
  • ConnectList.tsx wiring:
    • New "Connect to LSP manually ›" button next to "Join via invite link ›", sharing a flex row instead of stacking
    • On submit: constructs a synthetic FactoryRow with the user's pubkey + alias + lnAddresses and routes to the existing JoinFactoryModal flow
    • No code duplication — reuses browseHost + the full join-request path

No plugin changes

Reuses RPCs already shipped:

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

  • eslint + build + test in CI
  • Demo regtest LSP profile, click "Connect to LSP manually ›":
    • Paste 0375…ae45@127.0.0.1:41110 into pubkey field → splits into pubkey + address
    • Submit → JoinFactoryModal opens, browses the LSP, shows existing factories (a823, bad40d, e2eb)
    • Pick one, fire join-request → confirmation
  • Validation: invalid pubkey shows error, invalid address shows error
  • Cancel button + X both close modal cleanly without firing browse

Closes the loop on #119

With this + the already-shipped InviteModal (host generates QR + URL), AcceptInviteModal (client pastes URL), and JoinFactoryModal (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 in project-polish-backlog.md and will fold into the next iteration.

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.
@8144225309
8144225309 merged commit d7ec3c8 into main May 29, 2026
3 checks passed
@8144225309
8144225309 deleted the feat/manual-connect-119 branch May 29, 2026 10:26
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>.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant