Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add multi pay invoice method #218

Closed
wants to merge 13 commits into from
201 changes: 201 additions & 0 deletions handle_multi_pay_invoice_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package main

import (
"context"
"encoding/json"
"fmt"
"strings"
"sync"

"github.com/nbd-wtf/go-nostr"
decodepay "github.com/nbd-wtf/ln-decodepay"
"github.com/sirupsen/logrus"
)

func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, sub *nostr.Subscription, request *Nip47Request, event *nostr.Event, app App, ss []byte) {

nostrEvent := NostrEvent{App: app, NostrId: event.ID, Content: event.Content, State: "received"}
err := svc.db.Create(&nostrEvent).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
"appId": app.ID,
}).Errorf("Failed to save nostr event: %v", err)
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
}).Errorf("Failed to process event: %v", err)
return
}

multiPayParams := &Nip47MultiPayParams{}
err = json.Unmarshal(request.Params, multiPayParams)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
"appId": app.ID,
}).Errorf("Failed to decode nostr event: %v", err)
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
}).Errorf("Failed to process event: %v", err)
return
}

var wg sync.WaitGroup
for _, invoiceInfo := range multiPayParams.Invoices {
wg.Add(1)
go func(invoiceInfo Nip47MultiPayInvoiceElement) {
defer wg.Done()
bolt11 := invoiceInfo.Invoice
// Convert invoice to lowercase string
bolt11 = strings.ToLower(bolt11)
paymentRequest, err := decodepay.Decodepay(bolt11)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
"appId": app.ID,
"bolt11": bolt11,
}).Errorf("Failed to decode bolt11 invoice: %v", err)

resp, err := svc.createResponse(event, Nip47Response{
ResultType: NIP_47_MULTI_PAY_INVOICE_METHOD,
Error: &Nip47Error{
Code: NIP_47_ERROR_INTERNAL,
Message: fmt.Sprintf("Failed to decode bolt11 invoice: %s", err.Error()),
},
}, ss)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
"paymentRequest": invoiceInfo.Invoice,
"invoiceId": invoiceInfo.Id,
}).Errorf("Failed to process event: %v", err)
return
}
// TODO: Decide what to do if id is empty
dTag := []string{"a", fmt.Sprintf("%d:%s:%s", NIP_47_RESPONSE_KIND, event.PubKey, invoiceInfo.Id)}
resp.Tags = append(resp.Tags, dTag)
svc.PublishEvent(ctx, sub, event, resp)
return
}

id := invoiceInfo.Id
if id == "" {
id = paymentRequest.PaymentHash
}
dTag := []string{"a", fmt.Sprintf("%d:%s:%s", NIP_47_RESPONSE_KIND, event.PubKey, id)}

hasPermission, code, message := svc.hasPermission(&app, event, request.Method, paymentRequest.MSatoshi)
rolznz marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using request.Method does not work here. We use the NIP_47_PAY_INVOICE_METHOD permission for any payment request


if !hasPermission {
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
"appId": app.ID,
}).Errorf("App does not have permission: %s %s", code, message)

resp, err := svc.createResponse(event, Nip47Response{
ResultType: NIP_47_MULTI_PAY_INVOICE_METHOD,
Error: &Nip47Error{
Code: code,
Message: message,
},
}, ss)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
"paymentRequest": invoiceInfo.Invoice,
"invoiceId": invoiceInfo.Id,
}).Errorf("Failed to process event: %v", err)
return
}
resp.Tags = append(resp.Tags, dTag)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cannot change the response after it has been signed. Setting tags like this does not work.

I am fixing it.

svc.PublishEvent(ctx, sub, event, resp)
return
}

payment := Payment{App: app, NostrEvent: nostrEvent, PaymentRequest: bolt11, Amount: uint(paymentRequest.MSatoshi / 1000)}
insertPaymentResult := svc.db.Create(&payment)
if insertPaymentResult.Error != nil {
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
"paymentRequest": bolt11,
"invoiceId": invoiceInfo.Id,
}).Errorf("Failed to process event: %v", insertPaymentResult.Error)
return
}

svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
"appId": app.ID,
"bolt11": bolt11,
}).Info("Sending payment")

preimage, err := svc.lnClient.SendPaymentSync(ctx, event.PubKey, bolt11)
rolznz marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
"appId": app.ID,
"bolt11": bolt11,
}).Infof("Failed to send payment: %v", err)
// TODO: What to do here?
nostrEvent.State = NOSTR_EVENT_STATE_HANDLER_ERROR
svc.db.Save(&nostrEvent)

resp, err := svc.createResponse(event, Nip47Response{
ResultType: NIP_47_MULTI_PAY_INVOICE_METHOD,
Error: &Nip47Error{
Code: NIP_47_ERROR_INTERNAL,
Message: fmt.Sprintf("Something went wrong while paying invoice: %s", err.Error()),
},
}, ss)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
"paymentRequest": invoiceInfo.Invoice,
"invoiceId": invoiceInfo.Id,
}).Errorf("Failed to process event: %v", err)
return
}
resp.Tags = append(resp.Tags, dTag)
svc.PublishEvent(ctx, sub, event, resp)
return
}
payment.Preimage = &preimage
// TODO: What to do here?
nostrEvent.State = NOSTR_EVENT_STATE_HANDLER_EXECUTED
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, we need to review these. They do not apply well to the multi methods.

I would probably set it to executed if at least one payment succeeds, otherwise error. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CC @bumi

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably set it to executed if at least one payment succeeds, otherwise error.

One thought I had was to set a counter to see how many were successful and update this according to that

svc.db.Save(&nostrEvent)
svc.db.Save(&payment)
resp, err := svc.createResponse(event, Nip47Response{
ResultType: NIP_47_MULTI_PAY_INVOICE_METHOD,
Result: Nip47PayResponse{
Preimage: preimage,
},
}, ss)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
"paymentRequest": invoiceInfo.Invoice,
"invoiceId": invoiceInfo.Id,
}).Errorf("Failed to process event: %v", err)
return
}
resp.Tags = append(resp.Tags, dTag)
svc.PublishEvent(ctx, sub, event, resp)
}(invoiceInfo)
}

wg.Wait()
return
}
3 changes: 1 addition & 2 deletions handle_payment_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ func (svc *Service) HandlePayInvoiceEvent(ctx context.Context, request *Nip47Req
return nil, err
}

var bolt11 string
payParams := &Nip47PayParams{}
err = json.Unmarshal(request.Params, payParams)
if err != nil {
Expand All @@ -36,7 +35,7 @@ func (svc *Service) HandlePayInvoiceEvent(ctx context.Context, request *Nip47Req
return nil, err
}

bolt11 = payParams.Invoice
bolt11 := payParams.Invoice
// Convert invoice to lowercase string
bolt11 = strings.ToLower(bolt11)
paymentRequest, err := decodepay.Decodepay(bolt11)
Expand Down
14 changes: 14 additions & 0 deletions models.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
NIP_47_LOOKUP_INVOICE_METHOD = "lookup_invoice"
NIP_47_LIST_TRANSACTIONS_METHOD = "list_transactions"
NIP_47_PAY_KEYSEND_METHOD = "pay_keysend"
NIP_47_MULTI_PAY_INVOICE_METHOD = "multi_pay_invoice"
NIP_47_ERROR_INTERNAL = "INTERNAL"
NIP_47_ERROR_NOT_IMPLEMENTED = "NOT_IMPLEMENTED"
NIP_47_ERROR_QUOTA_EXCEEDED = "QUOTA_EXCEEDED"
Expand Down Expand Up @@ -251,6 +252,19 @@ type Nip47PayResponse struct {
Preimage string `json:"preimage"`
}

type Nip47MultiPayParams struct {
Invoices []Nip47MultiPayInvoiceElement `json:"invoices"`
}

type Nip47MultiPayInvoiceElement struct {
Nip47PayParams
Id string `json:"id"`
}

type Nip47MultiPayResponse struct {
Invoice string `json:"invoice"`
}

type Nip47KeysendParams struct {
Amount int64 `json:"amount"`
Pubkey string `json:"pubkey"`
Expand Down
Loading
Loading