import { ClipboardPasteIcon, ExternalLinkIcon, MoveRightIcon, RefreshCwIcon, } from "lucide-react"; import { useEffect, useState } from "react"; import { useNavigate, useSearchParams } 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 { FixedFloatButton } from "src/components/FixedFloatButton"; import { FixedFloatSwapInFlow } from "src/components/FixedFloatSwapInFlow"; import Loading from "src/components/Loading"; import LowReceivingCapacityAlert from "src/components/LowReceivingCapacityAlert"; import ResponsiveLinkButton from "src/components/ResponsiveLinkButton"; import { Button } from "src/components/ui/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 { RadioGroup, RadioGroupItem } from "src/components/ui/radio-group"; import { Separator } from "src/components/ui/separator"; import { Tabs, TabsContent, TabsList, TabsTrigger, } from "src/components/ui/tabs"; import { useBalances } from "src/hooks/useBalances"; import { useInfo } from "src/hooks/useInfo"; import { useSwapInfo } from "src/hooks/useSwaps"; import { CreateInvoiceRequest, InitiateSwapRequest, SwapResponse, Transaction, } from "src/types"; import { openLink } from "src/utils/openLink"; import { request } from "src/utils/request"; export default function Swap() { const [searchParams, setSearchParams] = useSearchParams(); const [tab, setTab] = useState(searchParams.get("type") || "in"); useEffect(() => { const newTabValue = searchParams.get("type"); if (newTabValue) { setTab(newTabValue); setSearchParams({}, { replace: true }); } }, [searchParams, setSearchParams]); return (
) } /> Swap In Swap Out
); } function SwapInForm() { const [swapFrom, setSwapFrom] = useState<"internal" | "external" | "crypto">( "external" ); const { data: info, hasChannelManagement } = useInfo(); const { data: balances } = useBalances(); const { data: swapInfo } = useSwapInfo("in"); const navigate = useNavigate(); const [swapAmountSat, setSwapAmountSat] = useState(""); const [loading, setLoading] = useState(false); const [cryptoTransaction, setCryptoTransaction] = useState(null); const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { setLoading(true); if (swapFrom === "crypto") { const tx = await request("/api/invoices", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ amountMsat: (parseInt(swapAmountSat) || 0) * 1000, description: "Fixed Float swap", } as CreateInvoiceRequest), }); if (!tx?.invoice) { throw new Error("Failed to create invoice"); } setCryptoTransaction(tx); openLink( `https://ff.io/?to=BTCLN&address=${encodeURIComponent(tx.invoice)}&ref=qnnjvywb` ); toast("Initiated swap"); return; } const payload: InitiateSwapRequest = { swapAmountSat: parseInt(swapAmountSat), }; const swapInResponse = await request("/api/swaps/in", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(payload), }); if (!swapInResponse) { throw new Error("Error swapping in"); } navigate( `/wallet/swap/in/status/${swapInResponse.swapId}${swapFrom === "internal" ? "?internal=true" : ""}` ); toast("Initiated swap"); } catch (error) { toast.error("Failed to initiate swap", { description: (error as Error).message, }); } finally { setLoading(false); } }; if (!info || !balances || !swapInfo) { return ; } const spendableOnchainBalance = balances.onchain.spendableSat; const isInternalSwap = swapFrom === "internal"; const isCryptoSwappingState = swapFrom === "crypto" && cryptoTransaction !== null; return (
{!isCryptoSwappingState && ( <>

On-chain Lightning

Swap on-chain funds into your lightning balance.

{hasChannelManagement && parseInt(swapAmountSat || "0") * 1000 >= 0.8 * balances.lightning.totalReceivableMsat && (
)} {isInternalSwap && ( )}
{ setSwapFrom(value); }} className="flex gap-4 flex-wrap" >
)} {swapFrom === "crypto" ? ( { setCryptoTransaction(null); setSwapAmountSat(""); }} /> ) : ( <>

{swapInfo.albyServiceFee + swapInfo.boltzServiceFee}% + on-chain fees

Swap In

powered by{" "} boltz.exchange

)} ); } function SwapOutForm() { const { data: swapInfo } = useSwapInfo("out"); const navigate = useNavigate(); const { data: balances } = useBalances(); const [isInternalSwap, setInternalSwap] = useState(true); const [swapAmountSat, setSwapAmountSat] = useState(""); const [destination, setDestination] = useState(""); const [loading, setLoading] = useState(false); const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { setLoading(true); const payload: InitiateSwapRequest = { swapAmountSat: parseInt(swapAmountSat), destination, }; 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 (error) { toast.error("Failed to initiate swap", { description: (error as Error).message, }); } finally { setLoading(false); } }; const paste = async () => { const text = await navigator.clipboard.readText(); setDestination(text.trim()); }; if (!balances || !swapInfo) { return ; } return (

Lightning On-chain

Swap bitcoin lightning into your on-chain balance.

{ setDestination(""); setInternalSwap(!isInternalSwap); }} className="flex gap-4 flex-row" >
{!isInternalSwap && (
setDestination(e.target.value)} required />
)}

{swapInfo.albyServiceFee + swapInfo.boltzServiceFee}% + on-chain fees

Swap Out

powered by{" "} boltz.exchange

Swap out to other Cryptocurrency ); }