AutoChannel.tsx raw
1 import { ChevronDownIcon } from "lucide-react";
2 import React from "react";
3 import { useNavigate } from "react-router";
4 import { toast } from "sonner";
5 import AppHeader from "src/components/AppHeader";
6 import ExternalLink from "src/components/ExternalLink";
7 import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount";
8 import Loading from "src/components/Loading";
9 import { Button } from "src/components/ui/button";
10 import { Checkbox } from "src/components/ui/checkbox";
11 import { LoadingButton } from "src/components/ui/custom/loading-button";
12 import { Label } from "src/components/ui/label";
13 import { Separator } from "src/components/ui/separator";
14 import { useChannels } from "src/hooks/useChannels";
15
16 import { useInfo } from "src/hooks/useInfo";
17 import { AutoChannelRequest, AutoChannelResponse } from "src/types";
18 import { request } from "src/utils/request";
19
20 import { MempoolAlert } from "src/components/MempoolAlert";
21 import { PayLightningInvoice } from "src/components/PayLightningInvoice";
22 import { ChannelPublicPrivateAlert } from "src/components/channels/ChannelPublicPrivateAlert";
23
24 import LightningNetworkDarkSVG from "public/images/illustrations/lightning-network-dark.svg";
25 import LightningNetworkLightSVG from "public/images/illustrations/lightning-network-light.svg";
26 import { ExternalLinkButton } from "src/components/ui/custom/external-link-button";
27 import { LinkButton } from "src/components/ui/custom/link-button";
28
29 export function AutoChannel() {
30 const { data: info } = useInfo();
31 const { data: channels } = useChannels(true);
32 const [isLoading, setLoading] = React.useState(false);
33 const [showAdvanced, setShowAdvanced] = React.useState(false);
34 const [isPublic, setPublic] = React.useState(false);
35
36 const navigate = useNavigate();
37 const [invoice, setInvoice] = React.useState<string>();
38 const [channelSizeSat, setChannelSizeSat] = React.useState<number>();
39 const [, setPrevChannelIds] = React.useState<string[]>();
40
41 React.useEffect(() => {
42 if (channels) {
43 setPrevChannelIds((current) => {
44 if (current) {
45 const newChannelId = channels.find(
46 (channel) => !current?.includes(channel.id) && channel.fundingTxId
47 )?.id;
48
49 if (newChannelId) {
50 console.info("Found new channel", newChannelId);
51 navigate("/channels/auto/opening", {
52 state: {
53 newChannelId,
54 },
55 });
56 }
57
58 return current;
59 }
60
61 return channels.map((channel) => channel.id);
62 });
63 }
64 }, [channels, navigate]);
65
66 if (!info || !channels) {
67 return <Loading />;
68 }
69
70 async function openChannel() {
71 if (!info || !channels) {
72 return;
73 }
74 setLoading(true);
75 try {
76 const newInstantChannelInvoiceRequest: AutoChannelRequest = {
77 isPublic,
78 };
79 const autoChannelResponse = await request<AutoChannelResponse>(
80 "/api/alby/auto-channel",
81 {
82 method: "POST",
83 headers: {
84 "Content-Type": "application/json",
85 },
86 body: JSON.stringify(newInstantChannelInvoiceRequest),
87 }
88 );
89 if (!autoChannelResponse) {
90 throw new Error("unexpected auto channel response");
91 }
92
93 setInvoice(autoChannelResponse.invoice);
94 setChannelSizeSat(autoChannelResponse.channelSizeSat);
95 } catch (error) {
96 setLoading(false);
97 console.error(error);
98 toast.error("Something went wrong. Please try again");
99 }
100 }
101
102 return (
103 <>
104 <AppHeader
105 pageTitle="Open a lightning channel"
106 title="Open a lightning channel"
107 description="Open a channel to another node on the lightning network"
108 />
109 <MempoolAlert />
110 {invoice && channelSizeSat && (
111 <div className="flex flex-col gap-4 items-center justify-center max-w-md">
112 <p className="text-muted-foreground slashed-zero">
113 Please pay the lightning invoice below which will cover the costs of
114 opening your channel. You will receive a channel with{" "}
115 <FormattedBitcoinAmount amountMsat={channelSizeSat * 1000} /> of
116 receiving capacity.
117 </p>
118 <PayLightningInvoice invoice={invoice} />
119
120 <Separator className="mt-8" />
121 <p className="mt-8 text-sm mb-2 text-muted-foreground">
122 Other options
123 </p>
124 <LinkButton
125 to="/channels/outgoing"
126 variant="secondary"
127 className="w-full"
128 >
129 Open Channel with On-Chain Bitcoin
130 </LinkButton>
131 <ExternalLinkButton
132 to="https://www.getalby.com/topup"
133 variant="secondary"
134 className="w-full"
135 >
136 Buy Bitcoin
137 </ExternalLinkButton>
138 </div>
139 )}
140 {!invoice && (
141 <>
142 <div className="flex flex-col gap-6 max-w-md text-muted-foreground">
143 <img
144 src={LightningNetworkDarkSVG}
145 className="w-full hidden dark:block"
146 />
147 <img
148 src={LightningNetworkLightSVG}
149 className="w-full dark:hidden"
150 />
151
152 <>
153 <p>
154 You're now going to open a new lightning channel that you can
155 use to send and receive payments using your Hub in the booming
156 bitcoin economy! To make things easy, Alby has picked a channel
157 partner for you from one of our recommended channel partners.
158 </p>
159 <p>
160 After paying a lightning invoice to cover on-chain fees, you'll
161 immediately be able to receive and send bitcoin through this
162 channel with your Hub.
163 </p>
164 <p className="text-muted-foreground">
165 Alby Hub works with selected service providers (LSPs) which
166 provide the best network connectivity and liquidity to receive
167 payments.{" "}
168 <ExternalLink
169 className="underline"
170 to="https://guides.getalby.com/user-guide/alby-hub/faq/how-to-open-a-payment-channel"
171 >
172 Learn more
173 </ExternalLink>
174 </p>
175 </>
176 {showAdvanced && (
177 <>
178 <div className="mt-2 flex items-top space-x-2">
179 <Checkbox
180 id="public-channel"
181 onCheckedChange={() => setPublic(!isPublic)}
182 className="mr-2"
183 />
184 <div className="grid gap-1.5 leading-none">
185 <Label
186 htmlFor="public-channel"
187 className="cursor-pointer text-foreground"
188 >
189 Public Channel
190 </Label>
191 <p className="text-xs text-muted-foreground">
192 Not recommended for most users.{" "}
193 <ExternalLink
194 className="underline"
195 to="https://guides.getalby.com/user-guide/alby-hub/faq/should-i-open-a-private-or-public-channel"
196 >
197 Learn more
198 </ExternalLink>
199 </p>
200 </div>
201 </div>
202 </>
203 )}
204 {!showAdvanced && (
205 <div>
206 <Button
207 type="button"
208 variant="link"
209 className="text-muted-foreground text-xs px-0"
210 onClick={() => setShowAdvanced((current) => !current)}
211 >
212 Advanced Options
213 <ChevronDownIcon className="size-4 ml-1" />
214 </Button>
215 </div>
216 )}
217 {channels?.some((channel) => channel.public !== !!isPublic) && (
218 <ChannelPublicPrivateAlert />
219 )}
220 <LoadingButton loading={isLoading} onClick={openChannel}>
221 Open Channel
222 </LoadingButton>
223 </div>
224 </>
225 )}
226 </>
227 );
228 }
229