import { AlertTriangleIcon, ExternalLinkIcon, InfoIcon, PencilIcon, XIcon, } from "lucide-react"; import React from "react"; import { Link, useLocation, useNavigate } from "react-router"; import { toast } from "sonner"; import { AnchorReserveAlert } from "src/components/AnchorReserveAlert"; import AppHeader from "src/components/AppHeader"; import { CurrencyInputField } from "src/components/CurrencyInputField"; import ExternalLink from "src/components/ExternalLink"; import { InsufficientLightningBalanceAlert } from "src/components/InsufficientLightningBalanceAlert"; import Loading from "src/components/Loading"; import { MempoolAlert } from "src/components/MempoolAlert"; import { Alert, AlertDescription, AlertTitle } from "src/components/ui/alert"; import { Button } from "src/components/ui/button"; import { LinkButton } from "src/components/ui/custom/link-button"; import { LoadingButton } from "src/components/ui/custom/loading-button"; import { Input } from "src/components/ui/input"; import { Label } from "src/components/ui/label"; import { Switch } from "src/components/ui/switch"; 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 { useSwapInfo } from "src/hooks/useSwaps"; import { InitiateSwapRequest, RedeemOnchainFundsRequest, RedeemOnchainFundsResponse, SwapResponse, } from "src/types"; import { request } from "src/utils/request"; export default function Onchain() { const { state } = useLocation(); const navigate = useNavigate(); const [isSwap, setSwap] = React.useState(false); const address = state?.args?.address as string; const initialAmountSat = (state?.args?.amountSat as string | undefined) ?? ""; const [amountSat, setAmountSat] = React.useState(initialAmountSat); React.useEffect(() => { if (!address) { navigate("/wallet/send"); } }, [navigate, address]); if (!address) { return ; } return (
Recipient
{address.match(/.{1,4}/g)?.map((word, index) => { if (index % 2 === 0) { return ( {word} ); } else { return ( {word} ); } })}
{isSwap ? ( ) : ( )}
); } function OnchainForm({ address, setSwap, amountSat, setAmountSat, }: { address: string; amountSat: string; setAmountSat: React.Dispatch>; setSwap: React.Dispatch>; }) { const navigate = useNavigate(); 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 [feeRate, setFeeRate] = React.useState(""); const [isLoading, setLoading] = React.useState(false); const [editFee, setEditFee] = React.useState(false); React.useEffect(() => { if (recommendedFees?.fastestFee) { setFeeRate(recommendedFees.fastestFee.toString()); } }, [recommendedFees]); const onSubmit = async (event: React.FormEvent) => { event.preventDefault(); try { if (!balances) { return; } if (balances.onchain.spendableSat <= ONCHAIN_DUST_SATS) { throw new Error( "You currently don't have enough sats to pay for an on-chain transaction. Consider swapping from Lightning Balance." ); } setLoading(true); const payload: RedeemOnchainFundsRequest = { toAddress: address, amountSat: +amountSat, feeRate: +feeRate, }; const response = await request( "/api/wallet/redeem-onchain-funds", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(payload), } ); if (!response?.txId) { throw new Error("No address in response"); } navigate(`/wallet/send/onchain-success`, { state: { amountSat: +amountSat, txId: response.txId, }, replace: true, }); toast("Successfully broadcasted transaction"); } catch (e) { toast.error("Failed to send payment", { description: "" + e, }); console.error(e); } finally { setLoading(false); } }; if (!info || !balances || (!recommendedFees && !mempoolError)) { return ; } return (
{!editFee ? (

On-chain Fee Rate

setEditFee(true)} > {feeRate ? (

{feeRate} sat/vB

) : ( )}
) : (
{mempoolError && (
Failed to fetch fee estimates. Try refreshing the page.
)} { setFeeRate(e.target.value); }} /> {recommendedFees && (
{" "} {" "} View on Mempool
)}
)}
{amountSat && +amountSat < 10_000 && ( Amount not ideal for On-chain transaction Small amounts can become unspendable when mempool fees increase. Consider using Lightning instead. )}
Back Send
); } function SwapForm({ address, setSwap, amountSat, setAmountSat, }: { address: string; amountSat: string; setAmountSat: React.Dispatch>; setSwap: React.Dispatch>; }) { const navigate = useNavigate(); const { data: balances } = useBalances(); const { data: swapInfo } = useSwapInfo("out"); const [isLoading, setLoading] = React.useState(false); const onSubmit = async (event: React.FormEvent) => { event.preventDefault(); try { setLoading(true); const payload: InitiateSwapRequest = { swapAmountSat: +amountSat, destination: address, }; const swapOutResponse = await request("/api/swaps/out", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(payload), }); if (!swapOutResponse) { throw new Error("Error swapping out"); } navigate(`/wallet/swap/out/status/${swapOutResponse.swapId}`); toast("Initiated swap"); } catch (e) { console.error(e); toast.error("Failed to send payment", { description: "" + e, }); } finally { setLoading(false); } }; const { data: recommendedFees } = useMempoolApi<{ fastestFee: number; }>("/v1/fees/recommended"); if (!balances || !swapInfo) { return ; } return (

On-chain Fee Rate

{recommendedFees?.fastestFee ? (

{recommendedFees?.fastestFee} sat/vB

) : ( )}

Swap Fee

{swapInfo.albyServiceFee + swapInfo.boltzServiceFee}%

Back Send
); }