mirror of
https://github.com/getAlby/hub.git
synced 2026-08-13 12:33:39 +02:00
* chore: align internal models and methods to use sat/msat suffixes * chore: update request structs to use sat/msat suffixes * fix: default max amount to 0 during app creation * fix: simplify helper functions * chore: remove unused lnclient probing methods * chore: remove unused json tags on swap struct * chore: update frontend to use sat/msat suffixes everywhere * chore: use request types in frontend * fix: linting * fix: remove deprecated sat and msat fields in frontend * chore: further changes * fix: avoid sat/msat resolver variable shadowing * fix: validate Support Alby amounts
183 lines
5.7 KiB
Go
183 lines
5.7 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/getAlby/hub/alby"
|
|
"github.com/getAlby/hub/config"
|
|
"github.com/getAlby/hub/lnclient"
|
|
"github.com/getAlby/hub/logger"
|
|
"github.com/getAlby/hub/lsp"
|
|
"github.com/getAlby/hub/version"
|
|
decodepay "github.com/nbd-wtf/ln-decodepay"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func (api *api) RequestLSPOrder(ctx context.Context, request *LSPOrderRequest) (*LSPOrderResponse, error) {
|
|
lnClient := api.svc.GetLNClient()
|
|
if lnClient == nil {
|
|
return nil, ErrLNClientNotStarted
|
|
}
|
|
|
|
if request.LSPType != lsp.LSP_TYPE_LSPS1 {
|
|
return nil, fmt.Errorf("unsupported LSP type: %v", request.LSPType)
|
|
}
|
|
|
|
logger.Logger.Info("Requesting own node info")
|
|
|
|
nodeInfo, err := lnClient.GetInfo(ctx)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
|
"lspIdentifier": request.LSPIdentifier,
|
|
}).Error("Failed to request own node info", err)
|
|
return nil, err
|
|
}
|
|
|
|
logger.Logger.Info("Requesting LSP info")
|
|
lspInfo, err := api.albyOAuthSvc.GetLSPInfo(ctx, request.LSPIdentifier, nodeInfo.Network)
|
|
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Failed to request LSP info")
|
|
return nil, err
|
|
}
|
|
|
|
logger.Logger.WithField("lspInfo", lspInfo).Info("Connecting to LSP node as a peer")
|
|
|
|
err = lnClient.ConnectPeer(ctx, &lnclient.ConnectPeerRequest{
|
|
Pubkey: lspInfo.Pubkey,
|
|
Address: lspInfo.Address,
|
|
Port: lspInfo.Port,
|
|
})
|
|
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Failed to connect to peer")
|
|
return nil, err
|
|
}
|
|
|
|
invoice, feeSat, err := api.requestLSPS1Invoice(ctx, lnClient, request, nodeInfo.Network, nodeInfo.Pubkey, lspInfo.MaxChannelExpiryBlocks, lspInfo.MinRequiredChannelConfirmations, lspInfo.MinFundingConfirmsWithinBlocks)
|
|
invoiceAmountSat := uint64(0)
|
|
incomingLiquiditySat := uint64(0)
|
|
resolvedAmountSat := ResolveToSat(request.AmountSat, nil, request.Amount, nil)
|
|
if resolvedAmountSat != nil {
|
|
incomingLiquiditySat = *resolvedAmountSat
|
|
}
|
|
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Failed to request invoice")
|
|
return nil, err
|
|
}
|
|
|
|
if invoice != "" {
|
|
paymentRequest, err := decodepay.Decodepay(invoice)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Failed to decode bolt11 invoice")
|
|
return nil, err
|
|
}
|
|
|
|
invoiceAmountSat = uint64(paymentRequest.MSatoshi / 1000)
|
|
}
|
|
|
|
newChannelResponse := &LSPOrderResponse{
|
|
Invoice: invoice,
|
|
Fee: feeSat,
|
|
FeeSat: feeSat,
|
|
InvoiceAmount: invoiceAmountSat,
|
|
InvoiceAmountSat: invoiceAmountSat,
|
|
IncomingLiquidity: incomingLiquiditySat,
|
|
IncomingLiquiditySat: incomingLiquiditySat,
|
|
OutgoingLiquidity: uint64(0),
|
|
OutgoingLiquiditySat: uint64(0),
|
|
}
|
|
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
"newChannelResponse": newChannelResponse,
|
|
}).Debug("New Channel response")
|
|
|
|
return newChannelResponse, nil
|
|
}
|
|
|
|
func (api *api) requestLSPS1Invoice(ctx context.Context, lnClient lnclient.LNClient, request *LSPOrderRequest, network, pubkey string, channelExpiryBlocks uint64, minRequiredChannelConfirmations uint64, minFundingConfirmsWithinBlocks uint64) (invoice string, feeSat uint64, err error) {
|
|
refundAddress, err := lnClient.GetNewOnchainAddress(ctx)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).Error("Failed to request onchain address")
|
|
return "", 0, err
|
|
}
|
|
|
|
var requiredChannelConfirmations uint64 = 0
|
|
|
|
backendType, err := api.cfg.Get("LNBackendType", "")
|
|
if err != nil {
|
|
return "", 0, errors.New("failed to get LN backend type")
|
|
}
|
|
|
|
if backendType != config.LDKBackendType {
|
|
// LND does not support 0-conf by default
|
|
requiredChannelConfirmations = 1
|
|
}
|
|
|
|
// Some LSPs (e.g. Olympus) require more min confirmations, as per the spec ours must be at least as many blocks
|
|
requiredChannelConfirmations = max(requiredChannelConfirmations, minRequiredChannelConfirmations)
|
|
|
|
if request.Public {
|
|
// as per BOLT-7 6 confirmations are required for the channel to be gossiped
|
|
// https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#requirements
|
|
requiredChannelConfirmations = 6
|
|
}
|
|
|
|
token := ""
|
|
if request.LSPIdentifier == "olympus" {
|
|
token = "AlbyHub/" + version.Tag
|
|
}
|
|
|
|
// set a non-empty token to notify LNServer that we support 0-conf
|
|
// (Pre-v1.17.2 does not support 0-conf)
|
|
if request.LSPIdentifier == "lnserver" {
|
|
token = "AlbyHub/" + version.Tag
|
|
}
|
|
|
|
// set a non-empty token to notify Flashsats that we support 0-conf
|
|
// (Pre-v1.21.0 does not support 0-conf)
|
|
if request.LSPIdentifier == "flashsats" {
|
|
token = "AlbyHub/" + version.Tag
|
|
}
|
|
|
|
amountSat := uint64(0)
|
|
resolvedAmountSat := ResolveToSat(request.AmountSat, nil, request.Amount, nil)
|
|
if resolvedAmountSat != nil {
|
|
amountSat = *resolvedAmountSat
|
|
}
|
|
lspBalanceSat := strconv.FormatUint(amountSat, 10)
|
|
|
|
lsps1ChannelRequest := &alby.LSPChannelRequest{
|
|
PublicKey: pubkey,
|
|
LSPBalanceSat: lspBalanceSat,
|
|
ClientBalanceSat: "0",
|
|
RequiredChannelConfirmations: requiredChannelConfirmations,
|
|
FundingConfirmsWithinBlocks: minFundingConfirmsWithinBlocks,
|
|
ChannelExpiryBlocks: channelExpiryBlocks,
|
|
Token: token,
|
|
RefundOnchainAddress: refundAddress,
|
|
AnnounceChannel: request.Public,
|
|
}
|
|
|
|
channelResponse, err := api.albyOAuthSvc.CreateLSPOrder(ctx, request.LSPIdentifier, network, lsps1ChannelRequest)
|
|
if err != nil {
|
|
return "", 0, err
|
|
}
|
|
|
|
if channelResponse.Payment != nil {
|
|
invoice = channelResponse.Payment.Bolt11.Invoice
|
|
feeSat, err = strconv.ParseUint(channelResponse.Payment.Bolt11.FeeTotalSat, 10, 64)
|
|
if err != nil {
|
|
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
|
"lspIdentifier": request.LSPIdentifier,
|
|
}).Error("Failed to parse fee")
|
|
return "", 0, fmt.Errorf("failed to parse fee %v", err)
|
|
}
|
|
}
|
|
|
|
return invoice, feeSat, nil
|
|
}
|