mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
feat: rebalance into channel (#1364)
* feat: rebalance into channel (WIP) * feat: rebalancing - add mainnet service URL - add loading state - add validation to avoid bad rebalancing - add rebalance warnings * chore: improve rebalance dialog copy * chore: add rebalance endpoint to wails handler * chore: change fee to 0.2% * fix: local spendable balance check * fix: local spendable balance check * fix: local spendable balance check, close dialog on successful swap * chore: use min/max for rebalancing amounts rather than errors * docs: change rebalancing fee copy * fix: only show rebalance option for channels that are less than half full * fix: rebalance fee copy * fix: reload balances after swapping
This commit is contained in:
parent
ec590faa49
commit
b1484228ad
9 changed files with 410 additions and 2 deletions
|
|
@ -14,6 +14,8 @@ AUTO_LINK_ALBY_ACCOUNT=false
|
|||
# Development settings (yarn dev:http)
|
||||
FRONTEND_URL=http://localhost:5173
|
||||
|
||||
#REBALANCE_SERVICE_URL=https://lsp1.mutiny.megalith-node.com
|
||||
|
||||
#AUTO_UNLOCK_PASSWORD=123
|
||||
#WORK_DIR=.data
|
||||
#DATABASE_URI=nwc.db
|
||||
|
|
|
|||
|
|
@ -172,7 +172,8 @@ The following configuration options can be set as environment variables or in a
|
|||
- `LOG_LEVEL`: Log level for the application. Higher is more verbose. Default: 4 (info)
|
||||
- `AUTO_UNLOCK_PASSWORD`: Provide unlock password to auto-unlock Alby Hub on startup (e.g. after a machine restart). Unlock password still be required to access the interface.
|
||||
- `BOLTZ_API`: The api which provides auto swaps functionality. Default: "https://api.boltz.exchange"
|
||||
- `NETWORK`: On-chain network used for auto swaps. Should match the backend network. Default: "bitcoin"
|
||||
- `NETWORK`: On-chain network used for the node. Default: "bitcoin"
|
||||
- `REBALANCE_SERVICE_URL`: service url for rebalancing existing channels.
|
||||
|
||||
### Migrating the database (Sqlite <-> Postgres)
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ type API interface {
|
|||
ConnectPeer(ctx context.Context, connectPeerRequest *ConnectPeerRequest) error
|
||||
DisconnectPeer(ctx context.Context, peerId string) error
|
||||
OpenChannel(ctx context.Context, openChannelRequest *OpenChannelRequest) (*OpenChannelResponse, error)
|
||||
RebalanceChannel(ctx context.Context, rebalanceChannelRequest *RebalanceChannelRequest) (*RebalanceChannelResponse, error)
|
||||
CloseChannel(ctx context.Context, peerId, channelId string, force bool) (*CloseChannelResponse, error)
|
||||
UpdateChannel(ctx context.Context, updateChannelRequest *UpdateChannelRequest) error
|
||||
MakeOffer(ctx context.Context, description string) (string, error)
|
||||
|
|
@ -254,6 +255,14 @@ type OpenChannelResponse = lnclient.OpenChannelResponse
|
|||
type CloseChannelResponse = lnclient.CloseChannelResponse
|
||||
type UpdateChannelRequest = lnclient.UpdateChannelRequest
|
||||
|
||||
type RebalanceChannelRequest struct {
|
||||
ReceiveThroughNodePubkey string `json:"receiveThroughNodePubkey"`
|
||||
AmountSat uint64 `json:"amountSat"`
|
||||
}
|
||||
type RebalanceChannelResponse struct {
|
||||
TotalFeeSat uint64 `json:"totalFeeSat"`
|
||||
}
|
||||
|
||||
type RedeemOnchainFundsRequest struct {
|
||||
ToAddress string `json:"toAddress"`
|
||||
Amount uint64 `json:"amount"`
|
||||
|
|
|
|||
133
api/rebalance.go
Normal file
133
api/rebalance.go
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/getAlby/hub/logger"
|
||||
decodepay "github.com/nbd-wtf/ln-decodepay"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (api *api) RebalanceChannel(ctx context.Context, rebalanceChannelRequest *RebalanceChannelRequest) (*RebalanceChannelResponse, error) {
|
||||
if api.svc.GetLNClient() == nil {
|
||||
return nil, errors.New("LNClient not started")
|
||||
}
|
||||
|
||||
receiveMetadata := map[string]interface{}{
|
||||
"receive_through": rebalanceChannelRequest.ReceiveThroughNodePubkey,
|
||||
"amount_sat": rebalanceChannelRequest.AmountSat,
|
||||
}
|
||||
|
||||
receiveInvoice, err := api.svc.GetTransactionsService().MakeInvoice(ctx, rebalanceChannelRequest.AmountSat*1000, "Alby Hub Rebalance through "+rebalanceChannelRequest.ReceiveThroughNodePubkey, "", 0, receiveMetadata, api.svc.GetLNClient(), nil, nil)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("failed to generate rebalance receive invoice")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
type rspCreateOrderRequest struct {
|
||||
Token string `json:"token"`
|
||||
PayRequest string `json:"pay_request"`
|
||||
PayThroughThisPublicKey string `json:"pay_through_this_public_key"`
|
||||
}
|
||||
|
||||
newRspCreateOrderRequest := rspCreateOrderRequest{
|
||||
Token: "alby-hub",
|
||||
PayRequest: receiveInvoice.PaymentRequest,
|
||||
PayThroughThisPublicKey: rebalanceChannelRequest.ReceiveThroughNodePubkey,
|
||||
}
|
||||
|
||||
payloadBytes, err := json.Marshal(newRspCreateOrderRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyReader := bytes.NewReader(payloadBytes)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, api.cfg.GetEnv().RebalanceServiceUrl+"/api/rebalance/v1/create_order", bodyReader)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
||||
"request": newRspCreateOrderRequest,
|
||||
}).Error("Failed to create new rebalance request")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
setDefaultRequestHeaders(req)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := http.Client{
|
||||
Timeout: time.Second * 60,
|
||||
}
|
||||
|
||||
res, err := client.Do(req)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
||||
"request": newRspCreateOrderRequest,
|
||||
}).Error("Failed to request new rebalance order")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
||||
"request": newRspCreateOrderRequest,
|
||||
}).Error("Failed to read response body")
|
||||
return nil, errors.New("failed to read response body")
|
||||
}
|
||||
|
||||
if res.StatusCode >= 300 {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"request": newRspCreateOrderRequest,
|
||||
"body": string(body),
|
||||
"statusCode": res.StatusCode,
|
||||
}).Error("rebalance create_order endpoint returned non-success code")
|
||||
return nil, fmt.Errorf("rebalance create_order endpoint returned non-success code: %s", string(body))
|
||||
}
|
||||
|
||||
type rspRebalanceCreateOrderResponse struct {
|
||||
OrderId string `json:"order_id"`
|
||||
PayRequest string `json:"pay_request"`
|
||||
}
|
||||
|
||||
var rebalanceCreateOrderResponse rspRebalanceCreateOrderResponse
|
||||
|
||||
err = json.Unmarshal(body, &rebalanceCreateOrderResponse)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
||||
"request": newRspCreateOrderRequest,
|
||||
}).Error("Failed to deserialize json")
|
||||
return nil, fmt.Errorf("failed to deserialize json from rebalance create order response: %s", string(body))
|
||||
}
|
||||
|
||||
logger.Logger.WithField("response", rebalanceCreateOrderResponse).Info("New rebalance order created")
|
||||
|
||||
paymentRequest, err := decodepay.Decodepay(rebalanceCreateOrderResponse.PayRequest)
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("Failed to decode bolt11 invoice")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
payMetadata := map[string]interface{}{
|
||||
"receive_through": rebalanceChannelRequest.ReceiveThroughNodePubkey,
|
||||
"amount_sat": rebalanceChannelRequest.AmountSat,
|
||||
"order_id": rebalanceCreateOrderResponse.OrderId,
|
||||
}
|
||||
|
||||
payRebalanceInvoiceResponse, err := api.svc.GetTransactionsService().SendPaymentSync(ctx, rebalanceCreateOrderResponse.PayRequest, nil, payMetadata, api.svc.GetLNClient(), nil, nil, nil)
|
||||
|
||||
if err != nil {
|
||||
logger.Logger.WithError(err).Error("failed to pay rebalance invoice")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &RebalanceChannelResponse{
|
||||
TotalFeeSat: uint64(paymentRequest.MSatoshi)/1000 + payRebalanceInvoiceResponse.FeeMsat/1000 - rebalanceChannelRequest.AmountSat,
|
||||
}, nil
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ type AppConfig struct {
|
|||
LDKVssUrl string `envconfig:"LDK_VSS_URL" default:"https://vss.getalbypro.com/vss"`
|
||||
LDKListeningAddresses string `envconfig:"LDK_LISTENING_ADDRESSES" default:"0.0.0.0:9735,[::]:9735"`
|
||||
LDKTransientNetworkGraph bool `envconfig:"LDK_TRANSIENT_NETWORK_GRAPH" default:"false"`
|
||||
RebalanceServiceUrl string `envconfig:"REBALANCE_SERVICE_URL" default:"https://megalithic.me"`
|
||||
LDKBitcoindRpcHost string `envconfig:"LDK_BITCOIND_RPC_HOST"`
|
||||
LDKBitcoindRpcPort string `envconfig:"LDK_BITCOIND_RPC_PORT"`
|
||||
LDKBitcoindRpcUser string `envconfig:"LDK_BITCOIND_RPC_USER"`
|
||||
|
|
|
|||
202
frontend/src/components/RebalanceChannelDialogContent.tsx
Normal file
202
frontend/src/components/RebalanceChannelDialogContent.tsx
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
import { AlertTriangleIcon, ExternalLinkIcon } from "lucide-react";
|
||||
import React from "react";
|
||||
import ExternalLink from "src/components/ExternalLink";
|
||||
import Loading from "src/components/Loading";
|
||||
import { Alert, AlertDescription, AlertTitle } from "src/components/ui/alert";
|
||||
import { Input } from "src/components/ui/input";
|
||||
import { Label } from "src/components/ui/label";
|
||||
import { LoadingButton } from "src/components/ui/loading-button";
|
||||
import { useToast } from "src/components/ui/use-toast";
|
||||
import { useBalances } from "src/hooks/useBalances";
|
||||
import { useChannels } from "src/hooks/useChannels";
|
||||
import { request } from "src/utils/request";
|
||||
import {
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "./ui/alert-dialog";
|
||||
|
||||
type Props = {
|
||||
receiveThroughNodePubkey: string;
|
||||
closeDialog(): void;
|
||||
};
|
||||
|
||||
export function RebalanceChannelDialogContent({
|
||||
receiveThroughNodePubkey,
|
||||
closeDialog,
|
||||
}: Props) {
|
||||
const [amount, setAmount] = React.useState("");
|
||||
const { toast } = useToast();
|
||||
const { data: channels, mutate: reloadChannels } = useChannels();
|
||||
const { mutate: reloadBalances } = useBalances();
|
||||
const [isRebalancing, setRebalancing] = React.useState(false);
|
||||
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimeout(() => {
|
||||
// for some reason `autoFocus` is not working on this input
|
||||
inputRef.current?.focus();
|
||||
}, 100);
|
||||
}, [inputRef]);
|
||||
|
||||
if (!channels) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
setRebalancing(true);
|
||||
try {
|
||||
if (!channels) {
|
||||
throw new Error("channels not loaded");
|
||||
}
|
||||
|
||||
const response = await request<{ totalFeeSat: number }>(
|
||||
`/api/channels/rebalance`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
receiveThroughNodePubkey,
|
||||
amountSat: parseInt(amount),
|
||||
}),
|
||||
}
|
||||
);
|
||||
if (!response) {
|
||||
throw new Error("No rebalance response received");
|
||||
}
|
||||
|
||||
await Promise.all([reloadChannels(), reloadBalances()]);
|
||||
toast({
|
||||
title:
|
||||
"Successfully rebalanced channels. Total fee: " +
|
||||
response.totalFeeSat +
|
||||
" sats",
|
||||
});
|
||||
closeDialog();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
description: "" + error,
|
||||
});
|
||||
}
|
||||
setRebalancing(false);
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialogContent>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Rebalance In</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
<p className="mb-4">
|
||||
Rebalance funds from other channels into this channel.
|
||||
</p>
|
||||
<Label htmlFor="fee" className="block mb-2">
|
||||
Rebalance amount (sats)
|
||||
</Label>
|
||||
<Input
|
||||
ref={inputRef}
|
||||
id="amount"
|
||||
name="amount"
|
||||
type="number"
|
||||
required
|
||||
autoFocus
|
||||
min={Math.max(
|
||||
10000,
|
||||
Math.floor(
|
||||
Math.min(
|
||||
...channels
|
||||
.filter(
|
||||
(channel) =>
|
||||
channel.remotePubkey === receiveThroughNodePubkey
|
||||
)
|
||||
.map((channel) => channel.localSpendableBalance / 1000)
|
||||
) + 1
|
||||
)
|
||||
)}
|
||||
max={Math.floor(
|
||||
Math.max(
|
||||
...channels
|
||||
.filter(
|
||||
(channel) =>
|
||||
channel.remotePubkey === receiveThroughNodePubkey
|
||||
)
|
||||
.map((channel) => channel.remoteBalance / 1000)
|
||||
)
|
||||
)}
|
||||
value={amount}
|
||||
onChange={(e) => {
|
||||
setAmount(e.target.value.trim());
|
||||
}}
|
||||
/>
|
||||
<p className="mt-2 text-xs text-muted-foreground">
|
||||
Fee: ~0.2%
|
||||
{!!amount && (
|
||||
<> ({Math.floor(parseInt(amount || "0") * 0.01)} sats)</>
|
||||
)}{" "}
|
||||
+ routing fees
|
||||
</p>
|
||||
<ExternalLink
|
||||
to="https://guides.getalby.com/user-guide/alby-hub/faq/can-i-rebalance-funds-from-one-of-my-channels-to-another"
|
||||
className="underline flex items-center mt-4"
|
||||
>
|
||||
Learn more about rebalancing between channels
|
||||
<ExternalLinkIcon className="w-4 h-4 ml-2" />
|
||||
</ExternalLink>
|
||||
<Alert className="mt-2">
|
||||
<AlertTriangleIcon className="h-4 w-4" />
|
||||
<AlertTitle>Rebalancing is in beta</AlertTitle>
|
||||
<AlertDescription>
|
||||
Funds may be rebalanced out of unexpected channels.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
{channels.filter(
|
||||
(channel) => channel.remotePubkey === receiveThroughNodePubkey
|
||||
).length > 1 && (
|
||||
<Alert className="mt-2">
|
||||
<AlertTriangleIcon className="h-4 w-4" />
|
||||
<AlertTitle>
|
||||
Multiple channels with same counterparty
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
Funds may be rebalanced to an unexpected channel.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{channels.some(
|
||||
(channel) =>
|
||||
channel.remotePubkey !== receiveThroughNodePubkey &&
|
||||
channel.localSpendableBalance <
|
||||
(channels.find(
|
||||
(other) => other.remotePubkey === receiveThroughNodePubkey
|
||||
)?.localSpendableBalance || 0)
|
||||
) && (
|
||||
<Alert className="mt-2">
|
||||
<AlertTriangleIcon className="h-4 w-4" />
|
||||
<AlertTitle>
|
||||
You have another channel with less funds
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
Consider choosing a channel with less spending balance to
|
||||
rebalance into.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter className="mt-4">
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<LoadingButton loading={isRebalancing}>Confirm</LoadingButton>
|
||||
</AlertDialogFooter>
|
||||
</form>
|
||||
</AlertDialogContent>
|
||||
);
|
||||
}
|
||||
|
|
@ -2,12 +2,14 @@ import {
|
|||
ExternalLinkIcon,
|
||||
HandCoinsIcon,
|
||||
MoreHorizontalIcon,
|
||||
ScaleIcon,
|
||||
Trash2Icon,
|
||||
} from "lucide-react";
|
||||
import React from "react";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
import { CloseChannelDialogContent } from "src/components/CloseChannelDialogContent";
|
||||
import ExternalLink from "src/components/ExternalLink";
|
||||
import { RebalanceChannelDialogContent } from "src/components/RebalanceChannelDialogContent";
|
||||
import { RoutingFeeDialogContent } from "src/components/RoutingFeeDialogContent";
|
||||
import {
|
||||
AlertDialog,
|
||||
|
|
@ -34,7 +36,9 @@ export function ChannelDropdownMenu({
|
|||
}: ChannelDropdownMenuProps) {
|
||||
const { data: info } = useInfo();
|
||||
const [searchParams] = useSearchParams();
|
||||
const [dialog, setDialog] = React.useState<"closeChannel" | "routingFee">();
|
||||
const [dialog, setDialog] = React.useState<
|
||||
"closeChannel" | "routingFee" | "rebalance"
|
||||
>();
|
||||
|
||||
React.useEffect(() => {
|
||||
// when opening the swap dialog, close existing dialog
|
||||
|
|
@ -58,6 +62,18 @@ export function ChannelDropdownMenu({
|
|||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
{channel.status == "online" &&
|
||||
channel.remoteBalance > channel.localSpendableBalance && (
|
||||
<AlertDialogTrigger asChild>
|
||||
<DropdownMenuItem
|
||||
className="flex flex-row items-center gap-2 cursor-pointer"
|
||||
onClick={() => setDialog("rebalance")}
|
||||
>
|
||||
<ScaleIcon className="h-4 w-4" />
|
||||
Rebalance In
|
||||
</DropdownMenuItem>
|
||||
</AlertDialogTrigger>
|
||||
)}
|
||||
<DropdownMenuItem className="flex flex-row items-center gap-2 cursor-pointer">
|
||||
<ExternalLink
|
||||
to={`${info?.mempoolUrl}/tx/${channel.fundingTxId}#flow=&vout=${channel.fundingTxVout}`}
|
||||
|
|
@ -102,6 +118,12 @@ export function ChannelDropdownMenu({
|
|||
<CloseChannelDialogContent alias={alias} channel={channel} />
|
||||
)}
|
||||
{dialog === "routingFee" && <RoutingFeeDialogContent channel={channel} />}
|
||||
{dialog === "rebalance" && (
|
||||
<RebalanceChannelDialogContent
|
||||
receiveThroughNodePubkey={channel.remotePubkey}
|
||||
closeDialog={() => setDialog(undefined)}
|
||||
/>
|
||||
)}
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,6 +130,7 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
|
|||
restrictedApiGroup.PATCH("/backup-reminder", httpSvc.backupReminderHandler)
|
||||
restrictedApiGroup.GET("/channels", httpSvc.channelsListHandler)
|
||||
restrictedApiGroup.POST("/channels", httpSvc.openChannelHandler)
|
||||
restrictedApiGroup.POST("/channels/rebalance", httpSvc.rebalanceChannelHandler)
|
||||
restrictedApiGroup.GET("/channels/suggestions", httpSvc.channelPeerSuggestionsHandler)
|
||||
restrictedApiGroup.POST("/lsp-orders", httpSvc.newInstantChannelInvoiceHandler)
|
||||
restrictedApiGroup.GET("/node/connection-info", httpSvc.nodeConnectionInfoHandler)
|
||||
|
|
@ -742,6 +743,27 @@ func (httpSvc *HttpService) openChannelHandler(c echo.Context) error {
|
|||
return c.JSON(http.StatusOK, openChannelResponse)
|
||||
}
|
||||
|
||||
func (httpSvc *HttpService) rebalanceChannelHandler(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
|
||||
var rebalanceChannelRequest api.RebalanceChannelRequest
|
||||
if err := c.Bind(&rebalanceChannelRequest); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, ErrorResponse{
|
||||
Message: fmt.Sprintf("Bad request: %s", err.Error()),
|
||||
})
|
||||
}
|
||||
|
||||
rebalanceChannelResponse, err := httpSvc.api.RebalanceChannel(ctx, &rebalanceChannelRequest)
|
||||
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, ErrorResponse{
|
||||
Message: fmt.Sprintf("Failed to rebalance channel: %s", err.Error()),
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, rebalanceChannelResponse)
|
||||
}
|
||||
|
||||
func (httpSvc *HttpService) disconnectPeerHandler(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
|
||||
|
|
|
|||
|
|
@ -458,6 +458,22 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
|
|||
}
|
||||
res := WailsRequestRouterResponse{Body: suggestions, Error: ""}
|
||||
return res
|
||||
case "/api/channels/rebalance":
|
||||
rebalanceChannelRequest := &api.RebalanceChannelRequest{}
|
||||
err := json.Unmarshal([]byte(body), rebalanceChannelRequest)
|
||||
if err != nil {
|
||||
logger.Logger.WithFields(logrus.Fields{
|
||||
"route": route,
|
||||
"method": method,
|
||||
"body": body,
|
||||
}).WithError(err).Error("Failed to decode request to wails router")
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
rebalanceChannelResponse, err := app.api.RebalanceChannel(ctx, rebalanceChannelRequest)
|
||||
if err != nil {
|
||||
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
|
||||
}
|
||||
return WailsRequestRouterResponse{Body: rebalanceChannelResponse, Error: ""}
|
||||
case "/api/balances":
|
||||
balancesResponse, err := app.api.GetBalances(ctx)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue