Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: withdraw custom amount from savings #597

Merged
merged 3 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: withdraw custom amount from savings
  • Loading branch information
rolznz committed Sep 3, 2024
commit 629bb61f74973827935b5cc0431c57b373eff465
4 changes: 2 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -560,11 +560,11 @@ func (api *api) SignMessage(ctx context.Context, message string) (*SignMessageRe
}, nil
}

func (api *api) RedeemOnchainFunds(ctx context.Context, toAddress string) (*RedeemOnchainFundsResponse, error) {
func (api *api) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (*RedeemOnchainFundsResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
txId, err := api.svc.GetLNClient().RedeemOnchainFunds(ctx, toAddress)
txId, err := api.svc.GetLNClient().RedeemOnchainFunds(ctx, toAddress, amount, sendAll)
if err != nil {
return nil, err
}
Expand Down
4 changes: 3 additions & 1 deletion api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,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) (*RedeemOnchainFundsResponse, error)
RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (*RedeemOnchainFundsResponse, error)
GetBalances(ctx context.Context) (*BalancesResponse, error)
ListTransactions(ctx context.Context, limit uint64, offset uint64) (*ListTransactionsResponse, error)
SendPayment(ctx context.Context, invoice string) (*SendPaymentResponse, error)
Expand Down Expand Up @@ -186,6 +186,8 @@ type UpdateChannelRequest = lnclient.UpdateChannelRequest

type RedeemOnchainFundsRequest struct {
ToAddress string `json:"toAddress"`
Amount uint64 `json:"amount"`
SendAll bool `json:"sendAll"`
}

type RedeemOnchainFundsResponse struct {
Expand Down
70 changes: 56 additions & 14 deletions frontend/src/screens/wallet/WithdrawOnchainFunds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export default function WithdrawOnchainFunds() {
const [onchainAddress, setOnchainAddress] = React.useState("");
const [confirmOnchainAddress, setConfirmOnchainAddress] = React.useState("");
const [checkedConfirmation, setCheckedConfirmation] = React.useState(false);
const [amount, setAmount] = React.useState("");
const [sendAll, setSendAll] = React.useState(false);
const [transactionId, setTransactionId] = React.useState("");

const copy = (text: string) => {
Expand Down Expand Up @@ -67,7 +69,11 @@ export default function WithdrawOnchainFunds() {
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ toAddress: onchainAddress }),
body: JSON.stringify({
toAddress: onchainAddress,
amount: +amount,
sendAll,
}),
}
);
console.info("Redeemed onchain funds", response);
Expand All @@ -85,7 +91,14 @@ export default function WithdrawOnchainFunds() {
}
setLoading(false);
},
[checkedConfirmation, confirmOnchainAddress, onchainAddress, toast]
[
amount,
checkedConfirmation,
confirmOnchainAddress,
onchainAddress,
sendAll,
toast,
]
);

if (transactionId) {
Expand Down Expand Up @@ -139,18 +152,19 @@ export default function WithdrawOnchainFunds() {
/>

<div className="max-w-lg">
{!!balances?.onchain.reserved && (
<Alert className="mb-4">
<AlertTriangleIcon className="h-4 w-4" />
<AlertTitle>Channel Anchor Reserves will be depleted</AlertTitle>
<AlertDescription>
You have channels open and this withdrawal will use some or all of
your anchor reserves to publish the transaction, which may make it
harder to close channels without depositing additional onchain
funds to your savings balance.
</AlertDescription>
</Alert>
)}
{!!balances?.onchain.reserved &&
(sendAll || +amount > balances.onchain.total * 0.9) && (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use spendable instead of total

<Alert className="mb-4">
<AlertTriangleIcon className="h-4 w-4" />
<AlertTitle>Channel Anchor Reserves may be depleted</AlertTitle>
<AlertDescription>
You have channels open and this withdrawal may deplete your
anchor reserves, which may make it harder to close channels
without depositing additional onchain funds to your savings
balance.
</AlertDescription>
</Alert>
)}
<p>
Your savings balance will be withdrawn to the onchain bitcoin wallet
address you specify below. Please make sure you are the owner of this
Expand All @@ -159,6 +173,34 @@ export default function WithdrawOnchainFunds() {
and have the seed phrase for.
</p>
<form onSubmit={redeemFunds} className="grid gap-5 mt-4">
<div className="">
<Label htmlFor="amount">Amount</Label>
<div className="flex justify-between items-center mb-1">
<p className="text-sm text-muted-foreground">
Current onchain balance:{" "}
{new Intl.NumberFormat().format(balances.onchain.total)} sats
</p>
<div className="flex items-center gap-1">
<Checkbox
id="send-all"
onCheckedChange={() => setSendAll(!sendAll)}
/>
<Label htmlFor="send-all" className="text-xs">
Send All
</Label>
</div>
</div>
<Input
id="amount"
type="number"
value={sendAll ? balances.onchain.total : amount}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we sure "send all" will be that amount?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, good catch!

disabled={sendAll}
required
onChange={(e) => {
setAmount(e.target.value);
}}
/>
</div>
<div className="">
<Label htmlFor="onchain-address">Onchain Address</Label>
<Input
Expand Down
2 changes: 1 addition & 1 deletion http/http_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ func (httpSvc *HttpService) redeemOnchainFundsHandler(c echo.Context) error {
})
}

redeemOnchainFundsResponse, err := httpSvc.api.RedeemOnchainFunds(ctx, redeemOnchainFundsRequest.ToAddress)
redeemOnchainFundsResponse, err := httpSvc.api.RedeemOnchainFunds(ctx, redeemOnchainFundsRequest.ToAddress, redeemOnchainFundsRequest.Amount, redeemOnchainFundsRequest.SendAll)

if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Expand Down
6 changes: 5 additions & 1 deletion lnclient/breez/breez.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,11 @@ func (bs *BreezService) GetOnchainBalance(ctx context.Context) (*lnclient.Onchai
}, nil
}

func (bs *BreezService) RedeemOnchainFunds(ctx context.Context, toAddress string) (txId string, err error) {
func (bs *BreezService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (txId string, err error) {
if !sendAll {
return "", errors.New("only send all is supported")
}

if toAddress == "" {
return "", errors.New("No address provided")
}
Expand Down
2 changes: 1 addition & 1 deletion lnclient/cashu/cashu.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func (cs *CashuService) GetOnchainBalance(ctx context.Context) (*lnclient.Onchai
}, nil
}

func (cs *CashuService) RedeemOnchainFunds(ctx context.Context, toAddress string) (string, error) {
func (cs *CashuService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (string, error) {
return "", nil
}

Expand Down
5 changes: 4 additions & 1 deletion lnclient/greenlight/greenlight.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,10 @@ func (gs *GreenlightService) GetOnchainBalance(ctx context.Context) (*lnclient.O
}, nil
}

func (gs *GreenlightService) RedeemOnchainFunds(ctx context.Context, toAddress string) (string, error) {
func (gs *GreenlightService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (string, error) {
if !sendAll {
return "", errors.New("only send all is supported")
}
amountAll := glalby.AmountOrAll(glalby.AmountOrAllAll{})
txId, err := gs.client.Withdraw(glalby.WithdrawRequest{
Destination: toAddress,
Expand Down
17 changes: 6 additions & 11 deletions lnclient/ldk/ldk.go
Original file line number Diff line number Diff line change
Expand Up @@ -999,24 +999,19 @@ func (ls *LDKService) GetOnchainBalance(ctx context.Context) (*lnclient.OnchainB
}, nil
}

func (ls *LDKService) RedeemOnchainFunds(ctx context.Context, toAddress string) (string, error) {

// TODO: LDK will improve SendAllToAddress to preserve anchor funds, then this
// code below can be removed.
///////////////////////////////////////////////////////////////////////////////////
balances := ls.node.ListBalances()
if balances.TotalAnchorChannelsReserveSats > 0 {
// NOTE: this is not good because it uses anchor reserves for the onchain transaction fee
spendableBalance := balances.SpendableOnchainBalanceSats
txId, err := ls.node.OnchainPayment().SendToAddress(toAddress, spendableBalance)
func (ls *LDKService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, 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.
txId, err := ls.node.OnchainPayment().SendToAddress(toAddress, amount)
if err != nil {
logger.Logger.WithError(err).Error("SendToAddress failed")
return "", err
}
return txId, nil
}
///////////////////////////////////////////////////////////////////////////////////

// TODO: this could be improved to preserve anchor reserves once LDK supports this
txId, err := ls.node.OnchainPayment().SendAllToAddress(toAddress)
if err != nil {
logger.Logger.WithError(err).Error("SendAllToAddress failed")
Expand Down
5 changes: 3 additions & 2 deletions lnclient/lnd/lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,10 +762,11 @@ func (svc *LNDService) GetOnchainBalance(ctx context.Context) (*lnclient.Onchain
}, nil
}

func (svc *LNDService) RedeemOnchainFunds(ctx context.Context, toAddress string) (txId string, err error) {
func (svc *LNDService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (txId string, err error) {
resp, err := svc.client.SendCoins(ctx, &lnrpc.SendCoinsRequest{
Addr: toAddress,
SendAll: true,
SendAll: sendAll,
Amount: int64(amount),
})
if err != nil {
return "", err
Expand Down
2 changes: 1 addition & 1 deletion lnclient/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,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) (txId string, err error)
RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, 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)
Expand Down
2 changes: 1 addition & 1 deletion lnclient/phoenixd/phoenixd.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,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) (txId string, err error) {
func (svc *PhoenixService) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (txId string, err error) {
return "", errors.New("not implemented")
}

Expand Down
2 changes: 1 addition & 1 deletion tests/mock_ln_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,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) (txId string, err error) {
func (mln *MockLn) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (txId string, err error) {
return "", nil
}
func (mln *MockLn) ResetRouter(key string) error {
Expand Down
2 changes: 1 addition & 1 deletion wails/wails_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,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)
redeemOnchainFundsResponse, err := app.api.RedeemOnchainFunds(ctx, redeemOnchainFundsRequest.ToAddress, redeemOnchainFundsRequest.Amount, redeemOnchainFundsRequest.SendAll)
if err != nil {
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
Expand Down
Loading