mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
153 lines
4.9 KiB
Go
153 lines
4.9 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/getAlby/hub/events"
|
|
"github.com/getAlby/hub/logger"
|
|
"github.com/getAlby/hub/version"
|
|
decodepay "github.com/nbd-wtf/ln-decodepay"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func (api *api) RebalanceChannel(ctx context.Context, rebalanceChannelRequest *RebalanceChannelRequest) (*RebalanceChannelResponse, error) {
|
|
lnClient := api.svc.GetLNClient()
|
|
if lnClient == nil {
|
|
return nil, ErrLNClientNotStarted
|
|
}
|
|
|
|
amountSat := uint64(0)
|
|
resolvedAmountSat := ResolveToSat(rebalanceChannelRequest.AmountSat, rebalanceChannelRequest.AmountMsat, nil, nil)
|
|
if resolvedAmountSat != nil {
|
|
amountSat = *resolvedAmountSat
|
|
}
|
|
|
|
receiveMetadata := map[string]interface{}{
|
|
"receive_through": rebalanceChannelRequest.ReceiveThroughNodePubkey,
|
|
}
|
|
|
|
receiveInvoice, err := api.svc.GetTransactionsService().MakeInvoice(ctx, amountSat*1000, "Alby Hub Rebalance through "+rebalanceChannelRequest.ReceiveThroughNodePubkey, "", 0, receiveMetadata, lnClient, nil, nil, &rebalanceChannelRequest.ReceiveThroughNodePubkey)
|
|
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.NewRequestWithContext(ctx, 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
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
req.Header.Set("User-Agent", "AlbyHub/"+version.Tag)
|
|
|
|
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 != http.StatusOK {
|
|
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
|
|
}
|
|
|
|
if paymentRequest.MSatoshi > int64(float64(amountSat)*float64(1000)*float64(1.005)+1 /*0.5% fees*/) {
|
|
return nil, errors.New("rebalance payment is more expensive than expected")
|
|
}
|
|
|
|
payMetadata := map[string]interface{}{
|
|
"receive_through": rebalanceChannelRequest.ReceiveThroughNodePubkey,
|
|
"amount_sat": amountSat,
|
|
"order_id": rebalanceCreateOrderResponse.OrderId,
|
|
}
|
|
|
|
payRebalanceInvoiceResponse, err := api.svc.GetTransactionsService().SendPaymentSync(rebalanceCreateOrderResponse.PayRequest, nil, payMetadata, lnClient, nil, nil)
|
|
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("failed to pay rebalance invoice")
|
|
return nil, err
|
|
}
|
|
|
|
api.eventPublisher.Publish(&events.Event{
|
|
Event: "nwc_rebalance_succeeded",
|
|
Properties: map[string]interface{}{},
|
|
})
|
|
|
|
totalFeeMsat := uint64(paymentRequest.MSatoshi) + payRebalanceInvoiceResponse.FeeMsat - amountSat*1000
|
|
|
|
return &RebalanceChannelResponse{
|
|
TotalFeeSat: totalFeeMsat / 1000,
|
|
TotalFeeMsat: totalFeeMsat,
|
|
}, nil
|
|
}
|