import { AlertTriangleIcon, ChevronDownIcon, CopyIcon, ExternalLinkIcon, InfoIcon, } from "lucide-react"; import React from "react"; import { toast } from "sonner"; import { AnchorReserveAlert } from "src/components/AnchorReserveAlert"; import AppHeader from "src/components/AppHeader"; import ExternalLink from "src/components/ExternalLink"; import { FixedFloatButton } from "src/components/FixedFloatButton"; import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount"; import Loading from "src/components/Loading"; import { MempoolAlert } from "src/components/MempoolAlert"; import { Alert, AlertDescription, AlertTitle } from "src/components/ui/alert"; import { AlertDialog, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "src/components/ui/alert-dialog"; import { Button } from "src/components/ui/button"; import { Checkbox } from "src/components/ui/checkbox"; import { LoadingButton } from "src/components/ui/custom/loading-button"; import { Input } from "src/components/ui/input"; import { Label } from "src/components/ui/label"; import { Separator } from "src/components/ui/separator"; import { ONCHAIN_DUST_SATS } from "src/constants"; import { useBalances } from "src/hooks/useBalances"; import { useInfo } from "src/hooks/useInfo"; import { useMempoolApi } from "src/hooks/useMempoolApi"; import { copyToClipboard } from "src/lib/clipboard"; import { RedeemOnchainFundsRequest, RedeemOnchainFundsResponse, } from "src/types"; import { request } from "src/utils/request"; export default function WithdrawOnchainFunds() { const { data: info } = useInfo(); const { data: balances } = useBalances(); const { data: recommendedFees, error: mempoolError } = useMempoolApi<{ fastestFee: number; halfHourFee: number; economyFee: number; minimumFee: number; }>("/v1/fees/recommended"); const [isLoading, setLoading] = React.useState(false); const [onchainAddress, setOnchainAddress] = React.useState(""); const [amountSat, setAmountSat] = React.useState(""); const [feeRate, setFeeRate] = React.useState(""); const [sendAll, setSendAll] = React.useState(false); const [showAdvanced, setShowAdvanced] = React.useState(false); const [transactionId, setTransactionId] = React.useState(""); const [confirmDialogOpen, setConfirmDialogOpen] = React.useState(false); React.useEffect(() => { if (mempoolError) { setShowAdvanced(true); } }, [mempoolError]); React.useEffect(() => { if (recommendedFees?.fastestFee) { setFeeRate(recommendedFees.fastestFee.toString()); } }, [recommendedFees]); const copy = (text: string) => { copyToClipboard(text); }; const redeemFunds = React.useCallback(async () => { setLoading(true); try { if (!onchainAddress) { throw new Error("No onchain address"); } if (!feeRate) { throw new Error("No fee rate set"); } } catch (error) { console.error(error); toast.error("Something went wrong", { description: "" + error, }); setLoading(false); return; } try { const payload: RedeemOnchainFundsRequest = { toAddress: onchainAddress, amountSat: +amountSat, sendAll, feeRate: +feeRate, }; const response = await request( "/api/wallet/redeem-onchain-funds", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(payload), } ); console.info("Redeemed onchain funds", response); if (!response?.txId) { throw new Error("No address in response"); } setTransactionId(response.txId); } catch (error) { console.error(error); toast.error("Failed to redeem onchain funds", { description: "" + error, }); } setLoading(false); }, [amountSat, feeRate, onchainAddress, sendAll]); if (transactionId) { return (

Withdrawal Transaction Id

{transactionId}

{ copy(transactionId); }} />
View on Mempool

Your on-chain balance in Alby Hub may take some time to update.

); } if (!info || !balances || (!recommendedFees && !mempoolError)) { return ; } if (balances.onchain.spendableSat <= ONCHAIN_DUST_SATS) { return (

You currently don't have enough sats to pay for an onchain transaction.

); } return (

Your on-chain balance will be withdrawn to the onchain bitcoin wallet address you specify below.

{ e.preventDefault(); setConfirmDialogOpen(true); }} className="grid gap-5 mt-4" >

Current onchain balance:{" "}

setSendAll(!sendAll)} />
{!sendAll && ( { setAmountSat(e.target.value); }} /> )}
{sendAll && ( Entire wallet balance will be sent Your entire wallet balance will be sent minus onchain transaction fees. The exact amount cannot be determined until the payment is made. )}
{ setOnchainAddress(e.target.value); }} />

Please double-check the destination address. This transaction cannot be reversed.

{(info?.backendType === "LDK" || info?.backendType === "LND") && ( <> {showAdvanced && (
{mempoolError && (
Failed to fetch fee estimates. Try refreshing the page.
)} { setFeeRate(e.target.value); }} /> {recommendedFees && (
{" "} {" "} View on Mempool
)}
)} {!showAdvanced && ( )} )}
{feeRate && (
On-chain payment will be made with{" "} {feeRate} sat/vB fee
)} Confirm Onchain Transaction

Please confirm your payment to

{onchainAddress}

Amount:{" "} {sendAll ? ( "entire on-chain balance" ) : ( <> )}

{feeRate && (

Fee Rate:{" "} {feeRate} {" "} sat/vB

)}
Cancel redeemFunds()} > Confirm
Withdraw to other Cryptocurrency
); }