Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Implement RedeemOnchainFunds() for LDK and Greenlight #131

Merged
merged 6 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
55 changes: 6 additions & 49 deletions frontend/src/components/BreezRedeem.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import React from "react";
import Alert from "src/components/Alert";
import Loading from "src/components/Loading";
import { useCSRF } from "src/hooks/useCSRF";
import { useInfo } from "src/hooks/useInfo";
import { useOnchainBalance } from "src/hooks/useOnchainBalance";
import { RedeemOnchainFundsResponse } from "src/types";
import { request } from "src/utils/request";
import { useRedeemOnchainFunds } from "src/hooks/useRedeemOnchainFunds";

export default function BreezRedeem() {
const { data: info } = useInfo();
Expand All @@ -16,49 +13,9 @@ export default function BreezRedeem() {
}

function BreezRedeemInternal() {
const { data: onchainBalance, mutate: reloadOnchainBalance } =
useOnchainBalance();
const { data: onchainBalance } = useOnchainBalance();

const { data: csrf } = useCSRF();
const [isLoading, setLoading] = React.useState(false);

const redeemFunds = React.useCallback(async () => {
if (!csrf) {
return;
}
const toAddress = prompt(
"Please enter an onchain bitcoin address (bc1...)"
);
if (!toAddress) {
return;
}
setLoading(true);

try {
const response = await request<RedeemOnchainFundsResponse>(
"/api/wallet/redeem-onchain-funds",
{
method: "POST",
headers: {
"X-CSRF-Token": csrf,
"Content-Type": "application/json",
},
body: JSON.stringify({ toAddress }),
}
);
console.log("Redeemed onchain funds", response);
if (!response?.txId) {
throw new Error("No address in response");
}
prompt("Funds redeemed. Copy TX to view in mempool", response.txId);
} catch (error) {
alert("Failed to request a new address: " + error);
} finally {
setLoading(false);
}

await reloadOnchainBalance();
}, [csrf, reloadOnchainBalance]);
const redeemOnchainFunds = useRedeemOnchainFunds();

if (!onchainBalance || onchainBalance.spendable <= 0) {
return null;
Expand All @@ -72,10 +29,10 @@ function BreezRedeemInternal() {
{onchainBalance.spendable} sats to redeem.{" "}
<button
className="flex justify-center items-center gap-2 bg-purple-100 p-2 text-purple-500 rounded-md"
onClick={redeemFunds}
disabled={isLoading}
onClick={redeemOnchainFunds.redeemFunds}
disabled={redeemOnchainFunds.isLoading}
>
Redeem onchain funds {isLoading && <Loading />}
Redeem onchain funds {redeemOnchainFunds.isLoading && <Loading />}
</button>
</div>
</Alert>
Expand Down
56 changes: 56 additions & 0 deletions frontend/src/hooks/useRedeemOnchainFunds.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from "react";
import { useCSRF } from "src/hooks/useCSRF";
import { useOnchainBalance } from "src/hooks/useOnchainBalance";
import { RedeemOnchainFundsResponse } from "src/types";
import { request } from "src/utils/request";

export function useRedeemOnchainFunds() {
const { data: csrf } = useCSRF();
const { mutate: reloadOnchainBalance } = useOnchainBalance();
const [isLoading, setLoading] = React.useState(false);

const redeemFunds = React.useCallback(async () => {
if (!csrf) {
return;
}
setLoading(true);
await new Promise((resolve) => setTimeout(resolve, 100));
const toAddress = prompt(
"Please enter an onchain bitcoin address (bc1...)"
);
if (!toAddress) {
setLoading(false);
return;
}

try {
const response = await request<RedeemOnchainFundsResponse>(
"/api/wallet/redeem-onchain-funds",
{
method: "POST",
headers: {
"X-CSRF-Token": csrf,
"Content-Type": "application/json",
},
body: JSON.stringify({ toAddress }),
}
);
console.log("Redeemed onchain funds", response);
if (!response?.txId) {
throw new Error("No address in response");
}
prompt("Funds redeemed. Copy TX to view in mempool", response.txId);
} catch (error) {
alert("Failed to request a new address: " + error);
} finally {
setLoading(false);
}

await reloadOnchainBalance();
}, [csrf, reloadOnchainBalance]);

return React.useMemo(
() => ({ redeemFunds, isLoading }),
[isLoading, redeemFunds]
);
}
13 changes: 13 additions & 0 deletions frontend/src/screens/channels/Channels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { useOnchainBalance } from "src/hooks/useOnchainBalance";
import { CloseChannelRequest, CloseChannelResponse, Node } from "src/types";
import { request } from "src/utils/request";
import { useCSRF } from "../../hooks/useCSRF.ts";
import { useRedeemOnchainFunds } from "src/hooks/useRedeemOnchainFunds.ts";
import Loading from "src/components/Loading.tsx";

export default function Channels() {
const { data: channels, mutate: reloadChannels } = useChannels();
Expand All @@ -14,6 +16,7 @@ export default function Channels() {
const { data: info, mutate: reloadInfo } = useInfo();
const { data: csrf } = useCSRF();
const navigate = useNavigate();
const redeemOnchainFunds = useRedeemOnchainFunds();

React.useEffect(() => {
if (!info || info.running) {
Expand Down Expand Up @@ -244,6 +247,16 @@ export default function Channels() {
Reset Router
</button>
)}
{(info?.backendType === "LDK" || info?.backendType === "GREENLIGHT") &&
(onchainBalance?.spendable || 0) > 0 && (
<button
onClick={redeemOnchainFunds.redeemFunds}
disabled={redeemOnchainFunds.isLoading}
className="text-white bg-orange-700 hover:bg-orange-800 focus:ring-4 focus:ring-orange-300 font-medium rounded-lg text-sm px-5 py-2.5 mr-2 dark:bg-orange-600 dark:hover:bg-orange-700 focus:outline-none dark:focus:ring-orange-800 inline-flex gap-2 justify-center items-center"
>
Redeem Onchain Funds {redeemOnchainFunds.isLoading && <Loading />}
</button>
)}

<div className="flex flex-col mt-5">
<div className="overflow-x-auto shadow-md sm:rounded-lg">
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ toolchain go1.21.1
require (
github.com/breez/breez-sdk-go v0.3.4
github.com/davrux/echo-logrus/v4 v4.0.3
github.com/getAlby/glalby-go v0.0.0-20240307093106-aab6d05591b4
github.com/getAlby/glalby-go v0.0.0-20240319195439-4ec5332e5493
github.com/getAlby/ldk-node-go v0.0.0-20240318131343-7ddfe35d7ce9
github.com/go-gormigrate/gormigrate/v2 v2.1.1
github.com/gorilla/sessions v1.2.1
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,10 @@ github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/getAlby/glalby-go v0.0.0-20240307093106-aab6d05591b4 h1:Kzx3NNzSGtMwo9+YdvzG6ISwFq7TmqGBhz3pXnBZ4X0=
github.com/getAlby/glalby-go v0.0.0-20240307093106-aab6d05591b4/go.mod h1:ViyJvjlvv0GCesTJ7mb3fBo4G+/qsujDAFN90xZ7a9U=
github.com/getAlby/glalby-go v0.0.0-20240319102223-4c6dcbf77c68 h1:kpR1EP3gwhaieKKMDz1mDxxlHzH3BEgsFS8196BzFKQ=
github.com/getAlby/glalby-go v0.0.0-20240319102223-4c6dcbf77c68/go.mod h1:ViyJvjlvv0GCesTJ7mb3fBo4G+/qsujDAFN90xZ7a9U=
github.com/getAlby/glalby-go v0.0.0-20240319195439-4ec5332e5493 h1:izgFrd4y3XhNM24AQXPPjhVBCUjxN0GD2xVt90KdAKU=
github.com/getAlby/glalby-go v0.0.0-20240319195439-4ec5332e5493/go.mod h1:ViyJvjlvv0GCesTJ7mb3fBo4G+/qsujDAFN90xZ7a9U=
github.com/getAlby/ldk-node-go v0.0.0-20240318131343-7ddfe35d7ce9 h1:yjGywbDFuaba0V2qporii04/JkP/mgYS4qDeOj3pnH4=
github.com/getAlby/ldk-node-go v0.0.0-20240318131343-7ddfe35d7ce9/go.mod h1:8BRjtKcz8E0RyYTPEbMS8VIdgredcGSLne8vHDtcRLg=
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
Expand Down
16 changes: 14 additions & 2 deletions greenlight.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,8 +483,20 @@ func (gs *GreenlightService) GetOnchainBalance(ctx context.Context) (*lnclient.O
Total: (spendableBalance + pendingBalance) / 1000,
}, nil
}
func (gs *GreenlightService) RedeemOnchainFunds(ctx context.Context, toAddress string) (txId string, err error) {
return "", nil

func (gs *GreenlightService) RedeemOnchainFunds(ctx context.Context, toAddress string) (string, error) {
amountAll := glalby.AmountOrAll(glalby.AmountOrAllAll{})
txId, err := gs.client.Withdraw(glalby.WithdrawRequest{
Destination: toAddress,
Amount: &amountAll,
})
if err != nil {
gs.svc.Logger.WithError(err).Error("Withdraw failed")
return "", err
}
gs.svc.Logger.WithField("txId", txId).Info("Redeeming on chain funds")

return txId.Txid, nil
}

func (gs *GreenlightService) greenlightInvoiceToTransaction(invoice *glalby.ListInvoicesInvoice) (*Nip47Transaction, error) {
Expand Down
2 changes: 1 addition & 1 deletion http_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ func (httpSvc *HttpService) redeemOnchainFundsHandler(c echo.Context) error {

if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to request new onchain address: %s", err.Error()),
Message: fmt.Sprintf("Failed to redeem onchain funds: %s", err.Error()),
})
}

Expand Down
23 changes: 14 additions & 9 deletions ldk.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ func (gs *LDKService) SendPaymentSync(ctx context.Context, payReq string) (preim

paymentHash, err := gs.node.Bolt11Payment().Send(payReq)
if err != nil {
gs.svc.Logger.Errorf("SendPayment failed: %v", err)
gs.svc.Logger.WithError(err).Error("SendPayment failed")
return "", err
}
fee := uint64(0)
Expand Down Expand Up @@ -352,7 +352,7 @@ func (gs *LDKService) MakeInvoice(ctx context.Context, amount int64, description
uint32(expiry))

if err != nil {
gs.svc.Logger.Errorf("MakeInvoice failed: %v", err)
gs.svc.Logger.WithError(err).Error("MakeInvoice failed")
return nil, err
}

Expand Down Expand Up @@ -481,7 +481,7 @@ func (gs *LDKService) GetNodeConnectionInfo(ctx context.Context) (nodeConnection
}
port, err := strconv.Atoi(parts[1])
if err != nil {
gs.svc.Logger.Errorf("ConnectPeer failed: %v", err)
gs.svc.Logger.WithError(err).Error("ConnectPeer failed")
return nil, err
}*/

Expand All @@ -495,7 +495,7 @@ func (gs *LDKService) GetNodeConnectionInfo(ctx context.Context) (nodeConnection
func (gs *LDKService) ConnectPeer(ctx context.Context, connectPeerRequest *lnclient.ConnectPeerRequest) error {
err := gs.node.Connect(connectPeerRequest.Pubkey, connectPeerRequest.Address+":"+strconv.Itoa(int(connectPeerRequest.Port)), true)
if err != nil {
gs.svc.Logger.Errorf("ConnectPeer failed: %v", err)
gs.svc.Logger.WithError(err).Error("ConnectPeer failed")
return err
}

Expand Down Expand Up @@ -523,7 +523,7 @@ func (gs *LDKService) OpenChannel(ctx context.Context, openChannelRequest *lncli
gs.svc.Logger.Infof("Opening channel with: %v", foundPeer.NodeId)
userChannelId, err := gs.node.ConnectOpenChannel(foundPeer.NodeId, foundPeer.Address, uint64(openChannelRequest.Amount), nil, nil, openChannelRequest.Public)
if err != nil {
gs.svc.Logger.Errorf("OpenChannel failed: %v", err)
gs.svc.Logger.WithError(err).Error("OpenChannel failed")
return nil, err
}

Expand Down Expand Up @@ -562,7 +562,7 @@ func (gs *LDKService) CloseChannel(ctx context.Context, closeChannelRequest *lnc
// TODO: support passing force option
err := gs.node.CloseChannel(closeChannelRequest.ChannelId, closeChannelRequest.NodeId, false)
if err != nil {
gs.svc.Logger.Errorf("CloseChannel failed: %v", err)
gs.svc.Logger.WithError(err).Error("CloseChannel failed")
return nil, err
}
return &lnclient.CloseChannelResponse{}, nil
Expand All @@ -571,7 +571,7 @@ func (gs *LDKService) CloseChannel(ctx context.Context, closeChannelRequest *lnc
func (gs *LDKService) GetNewOnchainAddress(ctx context.Context) (string, error) {
address, err := gs.node.OnchainPayment().NewAddress()
if err != nil {
gs.svc.Logger.Errorf("NewOnchainAddress failed: %v", err)
gs.svc.Logger.WithError(err).Error("NewOnchainAddress failed")
return "", err
}
return address, nil
Expand All @@ -588,8 +588,13 @@ func (gs *LDKService) GetOnchainBalance(ctx context.Context) (*lnclient.OnchainB
}, nil
}

func (gs *LDKService) RedeemOnchainFunds(ctx context.Context, toAddress string) (txId string, err error) {
return "", nil
func (gs *LDKService) RedeemOnchainFunds(ctx context.Context, toAddress string) (string, error) {
txId, err := gs.node.OnchainPayment().SendAllToAddress(toAddress)
if err != nil {
gs.svc.Logger.WithError(err).Error("SendAllToOnchainAddress failed")
return "", err
}
return txId, nil
}

func (ls *LDKService) ResetRouter(ctx context.Context) error {
Expand Down
Loading