diff --git a/api/api.go b/api/api.go index da75b8180..82906c5e8 100644 --- a/api/api.go +++ b/api/api.go @@ -997,7 +997,7 @@ func (api *api) GetSwapOutInfo() (*SwapInfoResponse, error) { }, nil } -func (api *api) InitiateSwapOut(ctx context.Context, initiateSwapOutRequest *InitiateSwapRequest) (*swaps.SwapResponse, error) { +func (api *api) InitiateSwapOut(ctx context.Context, initiateSwapOutRequest *InitiateSwapOutRequest) (*swaps.SwapResponse, error) { lnClient := api.svc.GetLNClient() if lnClient == nil { return nil, ErrLNClientNotStarted @@ -1030,7 +1030,7 @@ func (api *api) InitiateSwapOut(ctx context.Context, initiateSwapOutRequest *Ini return swapOutResponse, nil } -func (api *api) InitiateSwapIn(ctx context.Context, initiateSwapInRequest *InitiateSwapRequest) (*swaps.SwapResponse, error) { +func (api *api) InitiateSwapIn(ctx context.Context, initiateSwapInRequest *InitiateSwapInRequest) (*swaps.SwapResponse, error) { lnClient := api.svc.GetLNClient() if lnClient == nil { return nil, ErrLNClientNotStarted @@ -1050,10 +1050,11 @@ func (api *api) InitiateSwapIn(ctx context.Context, initiateSwapInRequest *Initi return nil, errors.New("invalid swap amount") } - swapInResponse, err := api.svc.GetSwapsService().SwapIn(amountSat, false) + swapInResponse, err := api.svc.GetSwapsService().SwapIn(amountSat, false, initiateSwapInRequest.InternalPayment, initiateSwapInRequest.FeeRate) if err != nil { logger.Logger.WithFields(logrus.Fields{ - "amount_sat": amountSat, + "amount_sat": amountSat, + "internal_payment": initiateSwapInRequest.InternalPayment, }).WithError(err).Error("Failed to initiate swap in") return nil, err } diff --git a/api/models.go b/api/models.go index 5448465dc..023ecfb92 100644 --- a/api/models.go +++ b/api/models.go @@ -69,8 +69,8 @@ type API interface { ListSwaps() (*ListSwapsResponse, error) GetSwapInInfo() (*SwapInfoResponse, error) GetSwapOutInfo() (*SwapInfoResponse, error) - InitiateSwapIn(ctx context.Context, initiateSwapInRequest *InitiateSwapRequest) (*swaps.SwapResponse, error) - InitiateSwapOut(ctx context.Context, initiateSwapOutRequest *InitiateSwapRequest) (*swaps.SwapResponse, error) + InitiateSwapIn(ctx context.Context, initiateSwapInRequest *InitiateSwapInRequest) (*swaps.SwapResponse, error) + InitiateSwapOut(ctx context.Context, initiateSwapOutRequest *InitiateSwapOutRequest) (*swaps.SwapResponse, error) RefundSwap(refundSwapRequest *RefundSwapRequest) error GetSwapMnemonic() string GetAutoSwapConfig() (*GetAutoSwapConfigResponse, error) @@ -168,7 +168,14 @@ type CreateLightningAddressRequest struct { AppId uint `json:"appId"` } -type InitiateSwapRequest struct { +type InitiateSwapInRequest struct { + SwapAmount *uint64 `json:"swapAmount"` // deprecated + SwapAmountSat *uint64 `json:"swapAmountSat"` + InternalPayment bool `json:"internalPayment"` + FeeRate *uint64 `json:"feeRate"` +} + +type InitiateSwapOutRequest struct { SwapAmount *uint64 `json:"swapAmount"` // deprecated SwapAmountSat *uint64 `json:"swapAmountSat"` Destination string `json:"destination"` @@ -304,38 +311,38 @@ type InfoResponseRelay struct { } type InfoResponse struct { - BackendType string `json:"backendType"` - SetupCompleted bool `json:"setupCompleted"` - OAuthRedirect bool `json:"oauthRedirect"` - Running bool `json:"running"` - Unlocked bool `json:"unlocked"` - AlbyAuthUrl string `json:"albyAuthUrl"` - NextBackupReminder string `json:"nextBackupReminder"` - AlbyUserIdentifier string `json:"albyUserIdentifier"` - AlbyAccountConnected bool `json:"albyAccountConnected"` - Version string `json:"version"` - Network string `json:"network"` - EnableAdvancedSetup bool `json:"enableAdvancedSetup"` - LdkVssEnabled bool `json:"ldkVssEnabled"` - VssSupported bool `json:"vssSupported"` - StartupState string `json:"startupState"` - StartupError string `json:"startupError"` - StartupErrorTime time.Time `json:"startupErrorTime"` - AutoUnlockPasswordSupported bool `json:"autoUnlockPasswordSupported"` - AutoUnlockPasswordEnabled bool `json:"autoUnlockPasswordEnabled"` - Currency string `json:"currency"` - BitcoinDisplayFormat string `json:"bitcoinDisplayFormat"` - Relays []InfoResponseRelay `json:"relays"` - NodeAlias string `json:"nodeAlias"` - MempoolUrl string `json:"mempoolUrl"` - ChainDataSourceType string `json:"chainDataSourceType,omitempty"` - ChainDataSourceAddress string `json:"chainDataSourceAddress,omitempty"` - JitChannelsLiquiditySource string `json:"jitChannelsLiquiditySource,omitempty"` - JitChannelsMinPaymentSizeMsat *uint64 `json:"jitChannelsMinPaymentSizeMsat,omitempty"` - JitChannelsMaxPaymentSizeMsat *uint64 `json:"jitChannelsMaxPaymentSizeMsat,omitempty"` - JitChannelsEnabled bool `json:"jitChannelsEnabled"` - HideUpdateBanner bool `json:"hideUpdateBanner"` - SupportsBolt12 bool `json:"supportsBolt12"` + BackendType string `json:"backendType"` + SetupCompleted bool `json:"setupCompleted"` + OAuthRedirect bool `json:"oauthRedirect"` + Running bool `json:"running"` + Unlocked bool `json:"unlocked"` + AlbyAuthUrl string `json:"albyAuthUrl"` + NextBackupReminder string `json:"nextBackupReminder"` + AlbyUserIdentifier string `json:"albyUserIdentifier"` + AlbyAccountConnected bool `json:"albyAccountConnected"` + Version string `json:"version"` + Network string `json:"network"` + EnableAdvancedSetup bool `json:"enableAdvancedSetup"` + LdkVssEnabled bool `json:"ldkVssEnabled"` + VssSupported bool `json:"vssSupported"` + StartupState string `json:"startupState"` + StartupError string `json:"startupError"` + StartupErrorTime time.Time `json:"startupErrorTime"` + AutoUnlockPasswordSupported bool `json:"autoUnlockPasswordSupported"` + AutoUnlockPasswordEnabled bool `json:"autoUnlockPasswordEnabled"` + Currency string `json:"currency"` + BitcoinDisplayFormat string `json:"bitcoinDisplayFormat"` + Relays []InfoResponseRelay `json:"relays"` + NodeAlias string `json:"nodeAlias"` + MempoolUrl string `json:"mempoolUrl"` + ChainDataSourceType string `json:"chainDataSourceType,omitempty"` + ChainDataSourceAddress string `json:"chainDataSourceAddress,omitempty"` + JitChannelsLiquiditySource string `json:"jitChannelsLiquiditySource,omitempty"` + JitChannelsMinPaymentSizeMsat *uint64 `json:"jitChannelsMinPaymentSizeMsat,omitempty"` + JitChannelsMaxPaymentSizeMsat *uint64 `json:"jitChannelsMaxPaymentSizeMsat,omitempty"` + JitChannelsEnabled bool `json:"jitChannelsEnabled"` + HideUpdateBanner bool `json:"hideUpdateBanner"` + SupportsBolt12 bool `json:"supportsBolt12"` } type UpdateSettingsRequest struct { diff --git a/frontend/src/components/CloseChannelDialogContent.tsx b/frontend/src/components/CloseChannelDialogContent.tsx index 91145b140..5e3c0668a 100644 --- a/frontend/src/components/CloseChannelDialogContent.tsx +++ b/frontend/src/components/CloseChannelDialogContent.tsx @@ -94,7 +94,7 @@ export function CloseChannelDialogContent({ alias, channel }: Props) { Are you sure you want to close the channel with {alias}? - This channel is inactive. Some channels require up to 6 onchain + This channel is inactive. Some channels require up to 6 on-chain confirmations before they are usable. diff --git a/frontend/src/components/FeeRateField.tsx b/frontend/src/components/FeeRateField.tsx new file mode 100644 index 000000000..0240416bf --- /dev/null +++ b/frontend/src/components/FeeRateField.tsx @@ -0,0 +1,131 @@ +import { AlertTriangleIcon, ExternalLinkIcon, PencilIcon } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; +import ExternalLink from "src/components/ExternalLink"; +import Loading from "src/components/Loading"; +import { Button } from "src/components/ui/button"; +import { Input } from "src/components/ui/input"; +import { Label } from "src/components/ui/label"; +import { useInfo } from "src/hooks/useInfo"; +import { useMempoolApi } from "src/hooks/useMempoolApi"; + +type RecommendedFees = { + fastestFee: number; + halfHourFee: number; + economyFee: number; + minimumFee: number; +}; + +type FeeRateFieldProps = { + feeRate: string; + onFeeRateChange: (value: string) => void; +}; + +export function FeeRateField({ feeRate, onFeeRateChange }: FeeRateFieldProps) { + const { data: info } = useInfo(); + const { data: recommendedFees, error: mempoolError } = + useMempoolApi("/v1/fees/recommended"); + const [isEditing, setIsEditing] = useState(false); + const hasInitializedDefaultFee = useRef(false); + + useEffect(() => { + if ( + recommendedFees?.fastestFee && + !hasInitializedDefaultFee.current && + !feeRate + ) { + hasInitializedDefaultFee.current = true; + onFeeRateChange(recommendedFees.fastestFee.toString()); + } + }, [feeRate, onFeeRateChange, recommendedFees]); + + useEffect(() => { + if (mempoolError) { + setIsEditing(true); + } + }, [mempoolError]); + + if (!info || (!recommendedFees && !mempoolError)) { + return ( +
+ + +
+ ); + } + + if (!isEditing) { + return ( +
+ + {feeRate ? ( + + ) : ( + + )} +
+ ); + } + + return ( +
+ + {mempoolError && ( +
+ + Failed to fetch fee estimates. Try refreshing the page. +
+ )} + { + onFeeRateChange(e.target.value); + }} + /> + {recommendedFees && ( +
+ + + {info.mempoolUrl && ( + + View on Mempool + + + )} +
+ )} +
+ ); +} diff --git a/frontend/src/screens/channels/CurrentChannelOrder.tsx b/frontend/src/screens/channels/CurrentChannelOrder.tsx index fbffb3f7d..b59b45721 100644 --- a/frontend/src/screens/channels/CurrentChannelOrder.tsx +++ b/frontend/src/screens/channels/CurrentChannelOrder.tsx @@ -302,7 +302,7 @@ function PayBitcoinChannelOrderTopup({ order }: { order: NewChannelOrder }) {

This amount includes cost for the channel opening and potential - channel onchain reserves. + channel on-chain reserves.

("/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); }; @@ -80,7 +55,7 @@ export default function WithdrawOnchainFunds() { setLoading(true); try { if (!onchainAddress) { - throw new Error("No onchain address"); + throw new Error("No on-chain address"); } if (!feeRate) { throw new Error("No fee rate set"); @@ -111,14 +86,14 @@ export default function WithdrawOnchainFunds() { body: JSON.stringify(payload), } ); - console.info("Redeemed onchain funds", response); + console.info("Redeemed on-chain 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", { + toast.error("Failed to redeem on-chain funds", { description: "" + error, }); } @@ -157,14 +132,14 @@ export default function WithdrawOnchainFunds() { ); } - if (!info || !balances || (!recommendedFees && !mempoolError)) { + if (!info || !balances) { return ; } if (balances.onchain.spendableSat <= ONCHAIN_DUST_SATS) { return (

- You currently don't have enough sats to pay for an onchain transaction. + You currently don't have enough sats to pay for an on-chain transaction.

); } @@ -174,12 +149,12 @@ export default function WithdrawOnchainFunds() {

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

Amount

- Current onchain balance:{" "} + Current on-chain balance:{" "} @@ -227,7 +202,7 @@ export default function WithdrawOnchainFunds() { Entire wallet balance will be sent - Your entire wallet balance will be sent minus onchain + Your entire wallet balance will be sent minus on-chain transaction fees. The exact amount cannot be determined until the payment is made. @@ -239,7 +214,7 @@ export default function WithdrawOnchainFunds() { />

- +
- {(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 + Confirm On-chain Transaction
diff --git a/frontend/src/screens/wallet/receive/ReceiveOnchain.tsx b/frontend/src/screens/wallet/receive/ReceiveOnchain.tsx index 0044c33e9..7e6e20858 100644 --- a/frontend/src/screens/wallet/receive/ReceiveOnchain.tsx +++ b/frontend/src/screens/wallet/receive/ReceiveOnchain.tsx @@ -16,7 +16,7 @@ import { useMempoolApi } from "src/hooks/useMempoolApi"; import { useSwapInfo } from "src/hooks/useSwaps"; import { CreateInvoiceRequest, - InitiateSwapRequest, + InitiateSwapInRequest, SwapResponse, Transaction, } from "src/types"; @@ -75,7 +75,7 @@ export default function ReceiveOnchain() { return; } - const payload: InitiateSwapRequest = { + const payload: InitiateSwapInRequest = { swapAmountSat: parseInt(swapAmountSat), }; const swapInResponse = await request("/api/swaps/in", { diff --git a/frontend/src/screens/wallet/send/Onchain.tsx b/frontend/src/screens/wallet/send/Onchain.tsx index 3b9661374..0447583b8 100644 --- a/frontend/src/screens/wallet/send/Onchain.tsx +++ b/frontend/src/screens/wallet/send/Onchain.tsx @@ -1,34 +1,25 @@ -import { - AlertTriangleIcon, - ExternalLinkIcon, - InfoIcon, - PencilIcon, - XIcon, -} from "lucide-react"; +import { InfoIcon, 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 { FeeRateField } from "src/components/FeeRateField"; 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, + InitiateSwapOutRequest, RedeemOnchainFundsRequest, RedeemOnchainFundsResponse, SwapResponse, @@ -115,24 +106,10 @@ function OnchainForm({ 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(); @@ -182,7 +159,7 @@ function OnchainForm({ } }; - if (!info || !balances || (!recommendedFees && !mempoolError)) { + if (!balances) { return ; } @@ -210,74 +187,7 @@ function OnchainForm({
- {!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 && ( @@ -323,7 +233,7 @@ function SwapForm({ event.preventDefault(); try { setLoading(true); - const payload: InitiateSwapRequest = { + const payload: InitiateSwapOutRequest = { swapAmountSat: +amountSat, destination: address, }; @@ -388,7 +298,7 @@ function SwapForm({
-

On-chain Fee Rate

+

{recommendedFees?.fastestFee ? (

{recommendedFees?.fastestFee} sat/vB

diff --git a/frontend/src/screens/wallet/swap/SwapInStatus.tsx b/frontend/src/screens/wallet/swap/SwapInStatus.tsx index dbb302b18..8ae5799aa 100644 --- a/frontend/src/screens/wallet/swap/SwapInStatus.tsx +++ b/frontend/src/screens/wallet/swap/SwapInStatus.tsx @@ -6,9 +6,7 @@ import { CopyIcon, ExternalLinkIcon, } from "lucide-react"; -import React, { useEffect, useState } from "react"; import { useParams, useSearchParams } from "react-router"; -import { toast } from "sonner"; import AppHeader from "src/components/AppHeader"; import ExternalLink from "src/components/ExternalLink"; import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount"; @@ -35,12 +33,7 @@ import { useInfo } from "src/hooks/useInfo"; import { useSwap } from "src/hooks/useSwaps"; import { useSyncWallet } from "src/hooks/useSyncWallet"; import { copyToClipboard } from "src/lib/clipboard"; -import { - RedeemOnchainFundsRequest, - RedeemOnchainFundsResponse, - SwapIn, -} from "src/types"; -import { request } from "src/utils/request"; +import { SwapIn } from "src/types"; export default function SwapInStatus() { const { data: info } = useInfo(); @@ -48,74 +41,9 @@ export default function SwapInStatus() { const { swapId } = useParams() as { swapId: string }; const { data: swap } = useSwap(swapId, true); - const [isPaying, setPaying] = useState(false); const [searchParams] = useSearchParams(); const isInternalSwap = searchParams.has("internal", "true"); - const [, setPaidWithAlbyHub] = React.useState(false); - - useEffect(() => { - if (isPaying && swap?.lockupTxId) { - setPaying(false); - } - }, [isPaying, swap?.lockupTxId]); - - const payWithAlbyHub = React.useCallback(() => { - (async () => { - setPaying(true); - try { - if (!swap) { - throw new Error("swap not loaded"); - } - const payload: RedeemOnchainFundsRequest = { - toAddress: swap.lockupAddress, - amountSat: swap.sendAmountSat, - }; - 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"); - } - console.info("Redeemed onchain funds", response); - } catch (error) { - console.error(error); - toast.error("Failed to redeem onchain funds", { - description: "" + error, - }); - setPaying(false); - } - })(); - }, [swap]); - - React.useEffect(() => { - // only auto-redeem while the swap is still awaiting its on-chain deposit, - // otherwise a refresh/revisit of ?internal=true would submit a second - // redeem request for an already-funded swap - if ( - isInternalSwap && - swap && - swap.state === "PENDING" && - !swap.lockupTxId - ) { - setPaidWithAlbyHub((current) => { - if (current) { - return current; - } - setTimeout(() => { - payWithAlbyHub(); - }, 1); - return true; - }); - } - }, [isInternalSwap, payWithAlbyHub, swap]); if (!swap) { return ; @@ -247,7 +175,7 @@ export default function SwapInStatus() {
-

Onchain deposit confirmed

+

On-chain deposit confirmed

-

Onchain deposit failed

+

On-chain deposit failed

-

Onchain deposit failed

+

On-chain deposit failed

-

Confirmed onchain

+

Confirmed on-chain

(null); @@ -128,8 +131,14 @@ function SwapInForm() { return; } - const payload: InitiateSwapRequest = { + const payload: InitiateSwapInRequest = { swapAmountSat: parseInt(swapAmountSat), + ...(swapFrom === "internal" + ? { + internalPayment: true, + ...(feeRate ? { feeRate: +feeRate } : {}), + } + : {}), }; const swapInResponse = await request("/api/swaps/in", { method: "POST", @@ -142,7 +151,9 @@ function SwapInForm() { throw new Error("Error swapping in"); } navigate( - `/wallet/swap/in/status/${swapInResponse.swapId}${swapFrom === "internal" ? "?internal=true" : ""}` + `/wallet/swap/in/status/${swapInResponse.swapId}${ + swapFrom === "internal" ? "?internal=true" : "" + }` ); toast("Initiated swap"); } catch (error) { @@ -284,12 +295,17 @@ function SwapInForm() { /> ) : ( <> -
- -

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

+
+ {isInternalSwap && ( + + )} +
+ +

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

+
@@ -323,7 +339,7 @@ function SwapOutForm() { try { setLoading(true); - const payload: InitiateSwapRequest = { + const payload: InitiateSwapOutRequest = { swapAmountSat: parseInt(swapAmountSat), destination, }; diff --git a/frontend/src/types.ts b/frontend/src/types.ts index d05867904..08207e641 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -594,9 +594,15 @@ export type AutoSwapRequest = { unlockPassword?: string; }; -export type InitiateSwapRequest = { - swapAmountSat?: number; - destination?: string; +export type InitiateSwapInRequest = { + swapAmountSat: number; + internalPayment?: boolean; + feeRate?: number; +}; + +export type InitiateSwapOutRequest = { + swapAmountSat: number; + destination: string; }; export type LSPOrderResponse = { diff --git a/http/http_service.go b/http/http_service.go index b36091a62..04c4e3437 100644 --- a/http/http_service.go +++ b/http/http_service.go @@ -1479,7 +1479,7 @@ func (httpSvc *HttpService) getSwapInInfoHandler(c echo.Context) error { } func (httpSvc *HttpService) initiateSwapOutHandler(c echo.Context) error { - var initiateSwapOutRequest api.InitiateSwapRequest + var initiateSwapOutRequest api.InitiateSwapOutRequest if err := c.Bind(&initiateSwapOutRequest); err != nil { return c.JSON(http.StatusBadRequest, ErrorResponse{ Message: fmt.Sprintf("Bad request: %s", err.Error()), @@ -1497,7 +1497,7 @@ func (httpSvc *HttpService) initiateSwapOutHandler(c echo.Context) error { } func (httpSvc *HttpService) initiateSwapInHandler(c echo.Context) error { - var initiateSwapInRequest api.InitiateSwapRequest + var initiateSwapInRequest api.InitiateSwapInRequest if err := c.Bind(&initiateSwapInRequest); err != nil { return c.JSON(http.StatusBadRequest, ErrorResponse{ Message: fmt.Sprintf("Bad request: %s", err.Error()), diff --git a/swaps/swaps_service.go b/swaps/swaps_service.go index 7a8c2f310..e45f3bb9c 100644 --- a/swaps/swaps_service.go +++ b/swaps/swaps_service.go @@ -56,7 +56,7 @@ type SwapsService interface { StopAutoSwapOut() EnableAutoSwapOut(encryptionKey string) error SwapOut(amountSat uint64, destination string, autoSwap, usedXpubDerivation bool) (*SwapResponse, error) - SwapIn(amountSat uint64, autoSwap bool) (*SwapResponse, error) + SwapIn(amountSat uint64, autoSwap bool, internalPayment bool, feeRate *uint64) (*SwapResponse, error) GetSwapOutInfo() (*SwapInfo, error) GetSwapInInfo() (*SwapInfo, error) RefundSwap(swapId, address string, enableRetries bool) error @@ -379,7 +379,7 @@ func (svc *swapsService) SwapOut(amountSat uint64, destination string, autoSwap, }, nil } -func (svc *swapsService) SwapIn(amountSat uint64, autoSwap bool) (*SwapResponse, error) { +func (svc *swapsService) SwapIn(amountSat uint64, autoSwap bool, internalPayment bool, feeRate *uint64) (*SwapResponse, error) { amountMsat := amountSat * 1000 invoice, err := svc.transactionsService.MakeInvoice(svc.ctx, amountMsat, "On-chain to lightning swap", "", 0, nil, svc.lnClient, nil, nil, nil) if err != nil { @@ -498,6 +498,17 @@ func (svc *swapsService) SwapIn(amountSat uint64, autoSwap bool) (*SwapResponse, logger.Logger.WithField("swapId", swap.Id).Info("Swap created") + if internalPayment { + _, err = svc.lnClient.RedeemOnchainFunds(svc.ctx, swap.Address, swap.ExpectedAmount, feeRate, false) + if err != nil { + logger.Logger.WithError(err).WithFields(logrus.Fields{ + "swapId": swap.Id, + "amountSat": swap.ExpectedAmount, + }).Error("Failed to fund internal swap in") + return nil, err + } + } + go svc.startSwapInListener(&dbSwap) return &SwapResponse{ diff --git a/wails/wails_handlers.go b/wails/wails_handlers.go index 193f3a532..a4f7d2ee5 100644 --- a/wails/wails_handlers.go +++ b/wails/wails_handlers.go @@ -1154,7 +1154,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string } return WailsRequestRouterResponse{Body: swapInInfo, Error: ""} case "/api/swaps/out": - initiateSwapOutRequest := &api.InitiateSwapRequest{} + initiateSwapOutRequest := &api.InitiateSwapOutRequest{} err := json.Unmarshal([]byte(body), initiateSwapOutRequest) if err != nil { logger.Logger.WithFields(logrus.Fields{ @@ -1175,7 +1175,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string } return WailsRequestRouterResponse{Body: swapOutResponse, Error: ""} case "/api/swaps/in": - initiateSwapInRequest := &api.InitiateSwapRequest{} + initiateSwapInRequest := &api.InitiateSwapInRequest{} err := json.Unmarshal([]byte(body), initiateSwapInRequest) if err != nil { logger.Logger.WithFields(logrus.Fields{