From aa32bcdb3e64125f6cb3f2a4e9261572728abf7e Mon Sep 17 00:00:00 2001 From: im-adithya Date: Wed, 29 Jan 2025 15:23:18 +0530 Subject: [PATCH] feat: add option to edit fee while withdrawing --- api/api.go | 4 +- api/models.go | 9 +-- .../screens/wallet/WithdrawOnchainFunds.tsx | 59 ++++++++++++++++++- http/http_service.go | 2 +- lnclient/breez/breez.go | 2 +- lnclient/cashu/cashu.go | 2 +- lnclient/greenlight/greenlight.go | 2 +- lnclient/ldk/ldk.go | 2 +- lnclient/lnd/lnd.go | 9 +-- lnclient/models.go | 2 +- lnclient/phoenixd/phoenixd.go | 2 +- tests/mock_ln_client.go | 2 +- wails/wails_handlers.go | 2 +- 13 files changed, 77 insertions(+), 22 deletions(-) diff --git a/api/api.go b/api/api.go index c81d0cb9..77529843 100644 --- a/api/api.go +++ b/api/api.go @@ -637,11 +637,11 @@ func (api *api) SignMessage(ctx context.Context, message string) (*SignMessageRe }, nil } -func (api *api) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (*RedeemOnchainFundsResponse, error) { +func (api *api) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, feeRate float64, sendAll bool) (*RedeemOnchainFundsResponse, error) { if api.svc.GetLNClient() == nil { return nil, errors.New("LNClient not started") } - txId, err := api.svc.GetLNClient().RedeemOnchainFunds(ctx, toAddress, amount, sendAll) + txId, err := api.svc.GetLNClient().RedeemOnchainFunds(ctx, toAddress, amount, feeRate, sendAll) if err != nil { return nil, err } diff --git a/api/models.go b/api/models.go index 73593f90..46b89cc3 100644 --- a/api/models.go +++ b/api/models.go @@ -34,7 +34,7 @@ type API interface { GetNewOnchainAddress(ctx context.Context) (string, error) GetUnusedOnchainAddress(ctx context.Context) (string, error) SignMessage(ctx context.Context, message string) (*SignMessageResponse, error) - RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (*RedeemOnchainFundsResponse, error) + RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, feeRate float64, sendAll bool) (*RedeemOnchainFundsResponse, error) GetBalances(ctx context.Context) (*BalancesResponse, error) ListTransactions(ctx context.Context, appId *uint, limit uint64, offset uint64) (*ListTransactionsResponse, error) SendPayment(ctx context.Context, invoice string, amountMsat *uint64) (*SendPaymentResponse, error) @@ -204,9 +204,10 @@ type CloseChannelResponse = lnclient.CloseChannelResponse type UpdateChannelRequest = lnclient.UpdateChannelRequest type RedeemOnchainFundsRequest struct { - ToAddress string `json:"toAddress"` - Amount uint64 `json:"amount"` - SendAll bool `json:"sendAll"` + ToAddress string `json:"toAddress"` + Amount uint64 `json:"amount"` + FeeRate float64 `json:"feeRate"` + SendAll bool `json:"sendAll"` } type RedeemOnchainFundsResponse struct { diff --git a/frontend/src/screens/wallet/WithdrawOnchainFunds.tsx b/frontend/src/screens/wallet/WithdrawOnchainFunds.tsx index 186104f1..0e09385e 100644 --- a/frontend/src/screens/wallet/WithdrawOnchainFunds.tsx +++ b/frontend/src/screens/wallet/WithdrawOnchainFunds.tsx @@ -1,4 +1,9 @@ -import { AlertTriangleIcon, CopyIcon, ExternalLinkIcon } from "lucide-react"; +import { + AlertTriangleIcon, + ChevronDown, + CopyIcon, + ExternalLinkIcon, +} from "lucide-react"; import React from "react"; import AppHeader from "src/components/AppHeader"; import ExternalLink from "src/components/ExternalLink"; @@ -21,6 +26,8 @@ import { LoadingButton } from "src/components/ui/loading-button"; import { useToast } from "src/components/ui/use-toast"; 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 { RedeemOnchainFundsResponse } from "src/types"; @@ -29,13 +36,28 @@ import { request } from "src/utils/request"; export default function WithdrawOnchainFunds() { const [isLoading, setLoading] = React.useState(false); const { toast } = useToast(); + const { data: info } = useInfo(); const { data: balances } = useBalances(); + const { data: recommendedFees } = useMempoolApi<{ + fastestFee: number; + halfHourFee: number; + economyFee: number; + minimumFee: number; + }>("/v1/fees/recommended"); const [onchainAddress, setOnchainAddress] = React.useState(""); const [amount, setAmount] = 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 (recommendedFees) { + setFeeRate(recommendedFees.fastestFee.toString()); + } + }, [recommendedFees]); + const copy = (text: string) => { copyToClipboard(text, toast); }; @@ -69,6 +91,7 @@ export default function WithdrawOnchainFunds() { body: JSON.stringify({ toAddress: onchainAddress, amount: +amount, + feeRate: +feeRate, sendAll, }), } @@ -87,7 +110,7 @@ export default function WithdrawOnchainFunds() { }); } setLoading(false); - }, [amount, onchainAddress, sendAll, toast]); + }, [amount, feeRate, onchainAddress, sendAll, toast]); if (transactionId) { return ( @@ -120,7 +143,7 @@ export default function WithdrawOnchainFunds() { ); } - if (!balances) { + if (!info || !balances || !recommendedFees) { return ; } @@ -239,6 +262,36 @@ export default function WithdrawOnchainFunds() { }} /> + {info?.backendType === "LND" && ( + <> + {showAdvanced && ( +
+ + { + setFeeRate(e.target.value); + }} + /> +
+ )} + {!showAdvanced && ( + + )} + + )}

Please double-check the destination address. This transaction cannot diff --git a/http/http_service.go b/http/http_service.go index 7b72af8f..fd6ec1db 100644 --- a/http/http_service.go +++ b/http/http_service.go @@ -768,7 +768,7 @@ func (httpSvc *HttpService) redeemOnchainFundsHandler(c echo.Context) error { }) } - redeemOnchainFundsResponse, err := httpSvc.api.RedeemOnchainFunds(ctx, redeemOnchainFundsRequest.ToAddress, redeemOnchainFundsRequest.Amount, redeemOnchainFundsRequest.SendAll) + redeemOnchainFundsResponse, err := httpSvc.api.RedeemOnchainFunds(ctx, redeemOnchainFundsRequest.ToAddress, redeemOnchainFundsRequest.Amount, redeemOnchainFundsRequest.FeeRate, redeemOnchainFundsRequest.SendAll) if err != nil { return c.JSON(http.StatusInternalServerError, ErrorResponse{ diff --git a/lnclient/breez/breez.go b/lnclient/breez/breez.go index 37a04b5a..ae870f55 100644 --- a/lnclient/breez/breez.go +++ b/lnclient/breez/breez.go @@ -368,7 +368,7 @@ func (bs *BreezService) GetOnchainBalance(ctx context.Context) (*lnclient.Onchai }, nil } -func (bs *BreezService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (txId string, err error) { +func (bs *BreezService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, feeRate float64, sendAll bool) (txId string, err error) { if !sendAll { return "", errors.New("only send all is supported") } diff --git a/lnclient/cashu/cashu.go b/lnclient/cashu/cashu.go index 4febb000..ed752f25 100644 --- a/lnclient/cashu/cashu.go +++ b/lnclient/cashu/cashu.go @@ -226,7 +226,7 @@ func (cs *CashuService) GetOnchainBalance(ctx context.Context) (*lnclient.Onchai }, nil } -func (cs *CashuService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (string, error) { +func (cs *CashuService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, feeRate float64, sendAll bool) (string, error) { return "", nil } diff --git a/lnclient/greenlight/greenlight.go b/lnclient/greenlight/greenlight.go index 5f76b6a0..dd250197 100644 --- a/lnclient/greenlight/greenlight.go +++ b/lnclient/greenlight/greenlight.go @@ -498,7 +498,7 @@ func (gs *GreenlightService) GetOnchainBalance(ctx context.Context) (*lnclient.O }, nil } -func (gs *GreenlightService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (string, error) { +func (gs *GreenlightService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, feeRate float64, sendAll bool) (string, error) { if !sendAll { return "", errors.New("only send all is supported") } diff --git a/lnclient/ldk/ldk.go b/lnclient/ldk/ldk.go index 438050bf..6909ba99 100644 --- a/lnclient/ldk/ldk.go +++ b/lnclient/ldk/ldk.go @@ -1087,7 +1087,7 @@ func (ls *LDKService) GetOnchainBalance(ctx context.Context) (*lnclient.OnchainB }, nil } -func (ls *LDKService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (string, error) { +func (ls *LDKService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, feeRate float64, sendAll bool) (string, error) { if !sendAll { // NOTE: this may fail if user does not reserve enough for the onchain transaction // and can also drain the anchor reserves if the user provides a too high amount. diff --git a/lnclient/lnd/lnd.go b/lnclient/lnd/lnd.go index 649176fe..26ddf44b 100644 --- a/lnclient/lnd/lnd.go +++ b/lnclient/lnd/lnd.go @@ -913,11 +913,12 @@ func (svc *LNDService) GetOnchainBalance(ctx context.Context) (*lnclient.Onchain }, nil } -func (svc *LNDService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (txId string, err error) { +func (svc *LNDService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, feeRate float64, sendAll bool) (txId string, err error) { resp, err := svc.client.SendCoins(ctx, &lnrpc.SendCoinsRequest{ - Addr: toAddress, - SendAll: sendAll, - Amount: int64(amount), + Addr: toAddress, + SendAll: sendAll, + Amount: int64(amount), + SatPerVbyte: uint64(feeRate), }) if err != nil { return "", err diff --git a/lnclient/models.go b/lnclient/models.go index 25f1cf0c..8c9000cb 100644 --- a/lnclient/models.go +++ b/lnclient/models.go @@ -66,7 +66,7 @@ type LNClient interface { ResetRouter(key string) error GetOnchainBalance(ctx context.Context) (*OnchainBalanceResponse, error) GetBalances(ctx context.Context) (*BalancesResponse, error) - RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (txId string, err error) + RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, feeRate float64, sendAll bool) (txId string, err error) SendPaymentProbes(ctx context.Context, invoice string) error SendSpontaneousPaymentProbes(ctx context.Context, amountMsat uint64, nodeId string) error ListPeers(ctx context.Context) ([]PeerDetails, error) diff --git a/lnclient/phoenixd/phoenixd.go b/lnclient/phoenixd/phoenixd.go index 7f40cfe8..56c1273e 100644 --- a/lnclient/phoenixd/phoenixd.go +++ b/lnclient/phoenixd/phoenixd.go @@ -433,7 +433,7 @@ func (svc *PhoenixService) SendKeysend(ctx context.Context, amount uint64, desti return nil, errors.New("not implemented") } -func (svc *PhoenixService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (txId string, err error) { +func (svc *PhoenixService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, feeRate float64, sendAll bool) (txId string, err error) { return "", errors.New("not implemented") } diff --git a/tests/mock_ln_client.go b/tests/mock_ln_client.go index e6e30af6..822df50f 100644 --- a/tests/mock_ln_client.go +++ b/tests/mock_ln_client.go @@ -143,7 +143,7 @@ func (mln *MockLn) GetBalances(ctx context.Context) (*lnclient.BalancesResponse, func (mln *MockLn) GetOnchainBalance(ctx context.Context) (*lnclient.OnchainBalanceResponse, error) { return nil, nil } -func (mln *MockLn) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (txId string, err error) { +func (mln *MockLn) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, feeRate float64, sendAll bool) (txId string, err error) { return "", nil } func (mln *MockLn) ResetRouter(key string) error { diff --git a/wails/wails_handlers.go b/wails/wails_handlers.go index 5826ff0c..994b8347 100644 --- a/wails/wails_handlers.go +++ b/wails/wails_handlers.go @@ -514,7 +514,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string return WailsRequestRouterResponse{Body: nil, Error: err.Error()} } - redeemOnchainFundsResponse, err := app.api.RedeemOnchainFunds(ctx, redeemOnchainFundsRequest.ToAddress, redeemOnchainFundsRequest.Amount, redeemOnchainFundsRequest.SendAll) + redeemOnchainFundsResponse, err := app.api.RedeemOnchainFunds(ctx, redeemOnchainFundsRequest.ToAddress, redeemOnchainFundsRequest.Amount, redeemOnchainFundsRequest.FeeRate, redeemOnchainFundsRequest.SendAll) if err != nil { return WailsRequestRouterResponse{Body: nil, Error: err.Error()} }