import { InfoIcon } from "lucide-react"; import React, { FormEvent } from "react"; import { Link, useNavigate } from "react-router"; import { toast } from "sonner"; import AppHeader from "src/components/AppHeader"; import { ChannelPeerNote } from "src/components/channels/ChannelPeerNote"; import { ChannelPublicPrivateAlert } from "src/components/channels/ChannelPublicPrivateAlert"; import { DuplicateChannelAlert } from "src/components/channels/DuplicateChannelAlert"; import { SwapAlert } from "src/components/channels/SwapAlert"; import ExternalLink from "src/components/ExternalLink"; import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount"; import Loading from "src/components/Loading"; import { MempoolAlert } from "src/components/MempoolAlert"; import { Alert, AlertDescription } from "src/components/ui/alert"; import { Button } from "src/components/ui/button"; import { Checkbox } from "src/components/ui/checkbox"; import { ExternalLinkButton } from "src/components/ui/custom/external-link-button"; import { LinkButton } from "src/components/ui/custom/link-button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "src/components/ui/dialog"; import { Input } from "src/components/ui/input"; import { Label } from "src/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "src/components/ui/select"; import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from "src/components/ui/tooltip"; import { useBalances } from "src/hooks/useBalances"; import { useChannelPeerSuggestions } from "src/hooks/useChannelPeerSuggestions"; import { useChannels } from "src/hooks/useChannels"; import { useInfo } from "src/hooks/useInfo"; import { usePeers } from "src/hooks/usePeers"; import { cn, formatAmount } from "src/lib/utils"; import useChannelOrderStore from "src/state/ChannelOrderStore"; import { Channel, Network, NewChannelOrder, OnchainOrder, RecommendedChannelPeer, } from "src/types"; import LightningNetworkDarkSVG from "public/images/illustrations/lightning-network-dark.svg"; import LightningNetworkLightSVG from "public/images/illustrations/lightning-network-light.svg"; import { useNodeDetails } from "src/hooks/useNodeDetails"; function getPeerKey(peer: RecommendedChannelPeer) { return JSON.stringify(peer); } export default function IncreaseOutgoingCapacity() { const { data: info } = useInfo(); const { data: channels } = useChannels(); if (!info?.network || !channels) { return ; } return ; } function NewChannelInternal({ network, channels, }: { network: Network; channels: Channel[]; }) { const { data: _channelPeerSuggestions } = useChannelPeerSuggestions(); const { data: balances } = useBalances(); const navigate = useNavigate(); const presetAmounts = [250_000, 500_000, 1_000_000]; const [order, setOrder] = React.useState>({ paymentMethod: "onchain", status: "pay", amountSat: presetAmounts[0].toString(), isPublic: !!channels.length && channels.every((channel) => channel.public), }); const [selectedPeer, setSelectedPeer] = React.useState< RecommendedChannelPeer | undefined >(); const [showConfirmModal, setShowConfirmModal] = React.useState(false); const channelPeerSuggestions = React.useMemo(() => { const customOption: RecommendedChannelPeer = { name: "Custom", network, paymentMethod: "onchain", minimumChannelSizeSat: 0, maximumChannelSizeSat: 0, description: "", pubkey: "", host: "", image: "", note: "", publicChannelsAllowed: true, }; return _channelPeerSuggestions ? [ ..._channelPeerSuggestions.filter( (peer) => peer.paymentMethod !== "lightning" ), customOption, ] : [customOption]; }, [_channelPeerSuggestions, network]); function setPublic(isPublic: boolean) { setOrder((current) => ({ ...current, isPublic, })); } const setAmountSat = React.useCallback((amountSat: string) => { setOrder((current) => ({ ...current, amountSat, })); }, []); React.useEffect(() => { if (!channelPeerSuggestions) { return; } const recommendedPeer = channelPeerSuggestions.find( (peer) => peer.network === network && peer.paymentMethod === order.paymentMethod ); setSelectedPeer(recommendedPeer); }, [network, order.paymentMethod, channelPeerSuggestions]); React.useEffect(() => { if (selectedPeer) { if ( selectedPeer.paymentMethod === "onchain" && order.paymentMethod === "onchain" ) { setOrder((current) => ({ ...current, pubkey: selectedPeer.pubkey, host: selectedPeer.host, ...(!selectedPeer.publicChannelsAllowed && { isPublic: false }), })); } } }, [order.paymentMethod, selectedPeer]); function onSubmit(e: FormEvent) { e.preventDefault(); setShowConfirmModal(true); } function handleConfirmSubmit() { try { if (!channels) { throw new Error("Channels not loaded"); } if ( channels.some( (channel) => channel.status === "opening" && channel.isOutbound && !channel.confirmations ) ) { throw new Error( "You already are opening a channel which has not been confirmed yet. Please wait for one block confirmation." ); } useChannelOrderStore.getState().setOrder(order as NewChannelOrder); setShowConfirmModal(false); navigate("/channels/order"); } catch (error) { toast.error("Something went wrong", { description: `${error}`, }); setShowConfirmModal(false); } } if (!channelPeerSuggestions || !balances) { return ; } const openImmediately = order.amountSat && order.paymentMethod === "onchain" && +order.amountSat < balances.onchain.spendableSat; return ( <> Open Channel with Lightning } />

Open a channel with on-chain funds. Both parties are free to close the channel at any time. However, by keeping more funds on your side of the channel and using it regularly, there is more chance the channel will stay open.{" "} Learn more .

Configure the amount of spending balance you need in your new lightning channel. You will need to deposit on-chain bitcoin to cover the entire channel size, plus on-chain fees.
{order.amountSat && +order.amountSat < 200_000 && (

For a smooth experience consider a opening a channel of{" "} in size or more.{" "} Learn more

)} { setAmountSat(e.target.value.trim()); }} />
Current on-chain balance:{" "}
{presetAmounts.map((presetAmountSat) => (
setAmountSat(presetAmountSat.toString())} > {formatAmount(presetAmountSat * 1000, 0)}
))}
<>
{selectedPeer && order.paymentMethod === "onchain" && selectedPeer.pubkey === order.pubkey && (
{selectedPeer.name === "Custom" && ( <>
)}
)}
{order.paymentMethod === "onchain" && ( )}
setPublic(!order.isPublic)} className="mr-2" disabled={selectedPeer && !selectedPeer.publicChannelsAllowed} title={ selectedPeer && !selectedPeer.publicChannelsAllowed ? "This channel partner does not support public channels." : undefined } />

Not recommended for most users.{" "} Learn more

{channels?.some((channel) => channel.public !== !!order.isPublic) && ( )} {selectedPeer?.note && }

Other options

Increase Receiving Capacity Buy Bitcoin
{/* Confirmation Modal */} Confirm Channel Opening Are you sure you want to open a Lightning channel with the following details?
Peer
{selectedPeer?.name || "Custom"}
Amount
Channel Type
{order.isPublic ? "Public" : "Private"}
Payment Method
On-chain
{selectedPeer?.name === "Custom" && order.pubkey && (
Node Public Key
{order.pubkey}
)} Important: Opening a channel requires an on-chain transaction and network fees. This action cannot be undone. Please verify all details before proceeding.
); } type NewChannelOnchainProps = { order: Partial; setOrder: React.Dispatch>>; showCustomOptions: boolean; }; function NewChannelOnchain(props: NewChannelOnchainProps) { const { data: peers } = usePeers(); if (props.order.paymentMethod !== "onchain") { throw new Error("unexpected payment method"); } const { pubkey, host } = props.order; const { setOrder } = props; const isAlreadyPeered = pubkey && peers?.some((peer) => peer.nodeId === pubkey); function setPubkey(pubkey: string) { props.setOrder((current) => ({ ...current, paymentMethod: "onchain", pubkey, })); } const setHost = React.useCallback( (host: string) => { setOrder((current) => ({ ...current, paymentMethod: "onchain", host, })); }, [setOrder] ); const { data: nodeDetails } = useNodeDetails(pubkey); React.useEffect(() => { const socketAddress = nodeDetails?.sockets?.split(",")?.[0]; if (socketAddress) { setHost(socketAddress); } }, [nodeDetails, setHost]); return ( <>
{props.showCustomOptions && ( <>
{ const parts = e.target.value.trim().split("@"); setPubkey(parts[0]); if (parts.length > 1) { setHost(parts[1]); } }} /> {nodeDetails && (
{nodeDetails.alias && ( <> {nodeDetails.alias} ({nodeDetails.active_channel_count}{" "} channels) )}
)}
{!isAlreadyPeered && /*!nodeDetails && */ pubkey && (
{ setHost(e.target.value.trim()); }} />
)} )}
); }