lsp.go raw

   1  package api
   2  
   3  import (
   4  	"context"
   5  	"errors"
   6  	"fmt"
   7  	"strconv"
   8  
   9  	"github.com/getAlby/hub/alby"
  10  	"github.com/getAlby/hub/config"
  11  	"github.com/getAlby/hub/lnclient"
  12  	"github.com/getAlby/hub/logger"
  13  	"github.com/getAlby/hub/lsp"
  14  	"github.com/getAlby/hub/version"
  15  	decodepay "github.com/nbd-wtf/ln-decodepay"
  16  	"github.com/sirupsen/logrus"
  17  )
  18  
  19  func (api *api) RequestLSPOrder(ctx context.Context, request *LSPOrderRequest) (*LSPOrderResponse, error) {
  20  	lnClient := api.svc.GetLNClient()
  21  	if lnClient == nil {
  22  		return nil, ErrLNClientNotStarted
  23  	}
  24  
  25  	if request.LSPType != lsp.LSP_TYPE_LSPS1 {
  26  		return nil, fmt.Errorf("unsupported LSP type: %v", request.LSPType)
  27  	}
  28  
  29  	logger.Logger.Info("Requesting own node info")
  30  
  31  	nodeInfo, err := lnClient.GetInfo(ctx)
  32  	if err != nil {
  33  		logger.Logger.WithError(err).WithFields(logrus.Fields{
  34  			"lspIdentifier": request.LSPIdentifier,
  35  		}).Error("Failed to request own node info", err)
  36  		return nil, err
  37  	}
  38  
  39  	logger.Logger.Info("Requesting LSP info")
  40  	lspInfo, err := api.albyOAuthSvc.GetLSPInfo(ctx, request.LSPIdentifier, nodeInfo.Network)
  41  
  42  	if err != nil {
  43  		logger.Logger.WithError(err).Error("Failed to request LSP info")
  44  		return nil, err
  45  	}
  46  
  47  	logger.Logger.WithField("lspInfo", lspInfo).Info("Connecting to LSP node as a peer")
  48  
  49  	err = lnClient.ConnectPeer(ctx, &lnclient.ConnectPeerRequest{
  50  		Pubkey:  lspInfo.Pubkey,
  51  		Address: lspInfo.Address,
  52  		Port:    lspInfo.Port,
  53  	})
  54  
  55  	if err != nil {
  56  		logger.Logger.WithError(err).Error("Failed to connect to peer")
  57  		return nil, err
  58  	}
  59  
  60  	invoice, feeSat, err := api.requestLSPS1Invoice(ctx, lnClient, request, nodeInfo.Network, nodeInfo.Pubkey, lspInfo.MaxChannelExpiryBlocks, lspInfo.MinRequiredChannelConfirmations, lspInfo.MinFundingConfirmsWithinBlocks)
  61  	invoiceAmountSat := uint64(0)
  62  	incomingLiquiditySat := uint64(0)
  63  	resolvedAmountSat := ResolveToSat(request.AmountSat, nil, request.Amount, nil)
  64  	if resolvedAmountSat != nil {
  65  		incomingLiquiditySat = *resolvedAmountSat
  66  	}
  67  
  68  	if err != nil {
  69  		logger.Logger.WithError(err).Error("Failed to request invoice")
  70  		return nil, err
  71  	}
  72  
  73  	if invoice != "" {
  74  		paymentRequest, err := decodepay.Decodepay(invoice)
  75  		if err != nil {
  76  			logger.Logger.WithError(err).Error("Failed to decode bolt11 invoice")
  77  			return nil, err
  78  		}
  79  
  80  		invoiceAmountSat = uint64(paymentRequest.MSatoshi / 1000)
  81  	}
  82  
  83  	newChannelResponse := &LSPOrderResponse{
  84  		Invoice:              invoice,
  85  		Fee:                  feeSat,
  86  		FeeSat:               feeSat,
  87  		InvoiceAmount:        invoiceAmountSat,
  88  		InvoiceAmountSat:     invoiceAmountSat,
  89  		IncomingLiquidity:    incomingLiquiditySat,
  90  		IncomingLiquiditySat: incomingLiquiditySat,
  91  		OutgoingLiquidity:    uint64(0),
  92  		OutgoingLiquiditySat: uint64(0),
  93  	}
  94  
  95  	logger.Logger.WithFields(logrus.Fields{
  96  		"newChannelResponse": newChannelResponse,
  97  	}).Debug("New Channel response")
  98  
  99  	return newChannelResponse, nil
 100  }
 101  
 102  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) {
 103  	refundAddress, err := lnClient.GetNewOnchainAddress(ctx)
 104  	if err != nil {
 105  		logger.Logger.WithError(err).Error("Failed to request onchain address")
 106  		return "", 0, err
 107  	}
 108  
 109  	var requiredChannelConfirmations uint64 = 0
 110  
 111  	backendType, err := api.cfg.Get("LNBackendType", "")
 112  	if err != nil {
 113  		return "", 0, errors.New("failed to get LN backend type")
 114  	}
 115  
 116  	if backendType != config.LDKBackendType {
 117  		// LND does not support 0-conf by default
 118  		requiredChannelConfirmations = 1
 119  	}
 120  
 121  	// Some LSPs (e.g. Olympus) require more min confirmations, as per the spec ours must be at least as many blocks
 122  	requiredChannelConfirmations = max(requiredChannelConfirmations, minRequiredChannelConfirmations)
 123  
 124  	if request.Public {
 125  		// as per BOLT-7 6 confirmations are required for the channel to be gossiped
 126  		// https://github.com/lightning/bolts/blob/master/07-routing-gossip.md#requirements
 127  		requiredChannelConfirmations = 6
 128  	}
 129  
 130  	token := ""
 131  	if request.LSPIdentifier == "olympus" {
 132  		token = "AlbyHub/" + version.Tag
 133  	}
 134  
 135  	// set a non-empty token to notify LNServer that we support 0-conf
 136  	// (Pre-v1.17.2 does not support 0-conf)
 137  	if request.LSPIdentifier == "lnserver" {
 138  		token = "AlbyHub/" + version.Tag
 139  	}
 140  
 141  	// set a non-empty token to notify Flashsats that we support 0-conf
 142  	// (Pre-v1.21.0 does not support 0-conf)
 143  	if request.LSPIdentifier == "flashsats" {
 144  		token = "AlbyHub/" + version.Tag
 145  	}
 146  
 147  	amountSat := uint64(0)
 148  	resolvedAmountSat := ResolveToSat(request.AmountSat, nil, request.Amount, nil)
 149  	if resolvedAmountSat != nil {
 150  		amountSat = *resolvedAmountSat
 151  	}
 152  	lspBalanceSat := strconv.FormatUint(amountSat, 10)
 153  
 154  	lsps1ChannelRequest := &alby.LSPChannelRequest{
 155  		PublicKey:                    pubkey,
 156  		LSPBalanceSat:                lspBalanceSat,
 157  		ClientBalanceSat:             "0",
 158  		RequiredChannelConfirmations: requiredChannelConfirmations,
 159  		FundingConfirmsWithinBlocks:  minFundingConfirmsWithinBlocks,
 160  		ChannelExpiryBlocks:          channelExpiryBlocks,
 161  		Token:                        token,
 162  		RefundOnchainAddress:         refundAddress,
 163  		AnnounceChannel:              request.Public,
 164  	}
 165  
 166  	channelResponse, err := api.albyOAuthSvc.CreateLSPOrder(ctx, request.LSPIdentifier, network, lsps1ChannelRequest)
 167  	if err != nil {
 168  		return "", 0, err
 169  	}
 170  
 171  	if channelResponse.Payment != nil {
 172  		invoice = channelResponse.Payment.Bolt11.Invoice
 173  		feeSat, err = strconv.ParseUint(channelResponse.Payment.Bolt11.FeeTotalSat, 10, 64)
 174  		if err != nil {
 175  			logger.Logger.WithError(err).WithFields(logrus.Fields{
 176  				"lspIdentifier": request.LSPIdentifier,
 177  			}).Error("Failed to parse fee")
 178  			return "", 0, fmt.Errorf("failed to parse fee %v", err)
 179  		}
 180  	}
 181  
 182  	return invoice, feeSat, nil
 183  }
 184