From 6c2fe349e7212085548f9c31b922d602d620ba58 Mon Sep 17 00:00:00 2001 From: im-adithya Date: Thu, 25 Jan 2024 17:31:20 +0530 Subject: [PATCH] feat: add multi keysend method --- handle_multi_pay_invoice_request.go | 14 +-- handle_multi_pay_keysend_request.go | 163 ++++++++++++++++++++++++++++ models.go | 18 ++- service.go | 3 + 4 files changed, 185 insertions(+), 13 deletions(-) create mode 100644 handle_multi_pay_keysend_request.go diff --git a/handle_multi_pay_invoice_request.go b/handle_multi_pay_invoice_request.go index 865af37a..04c94933 100644 --- a/handle_multi_pay_invoice_request.go +++ b/handle_multi_pay_invoice_request.go @@ -29,7 +29,7 @@ func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, sub *nostr.S return } - multiPayParams := &Nip47MultiPayParams{} + multiPayParams := &Nip47MultiPayInvoiceParams{} err = json.Unmarshal(request.Params, multiPayParams) if err != nil { svc.Logger.WithFields(logrus.Fields{ @@ -64,7 +64,7 @@ func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, sub *nostr.S // TODO: Decide what to do if id is empty dTag := []string{"d", invoiceInfo.Id} resp, err := svc.createResponse(event, Nip47Response{ - ResultType: NIP_47_MULTI_PAY_INVOICE_METHOD, + ResultType: request.Method, Error: &Nip47Error{ Code: NIP_47_ERROR_INTERNAL, Message: fmt.Sprintf("Failed to decode bolt11 invoice: %s", err.Error()), @@ -100,7 +100,7 @@ func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, sub *nostr.S }).Errorf("App does not have permission: %s %s", code, message) resp, err := svc.createResponse(event, Nip47Response{ - ResultType: NIP_47_MULTI_PAY_INVOICE_METHOD, + ResultType: request.Method, Error: &Nip47Error{ Code: code, Message: message, @@ -146,12 +146,12 @@ func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, sub *nostr.S "appId": app.ID, "bolt11": bolt11, }).Infof("Failed to send payment: %v", err) - // TODO: What to do here? + // TODO: https://github.com/getAlby/nostr-wallet-connect/issues/231 nostrEvent.State = NOSTR_EVENT_STATE_HANDLER_ERROR svc.db.Save(&nostrEvent) resp, err := svc.createResponse(event, Nip47Response{ - ResultType: NIP_47_MULTI_PAY_INVOICE_METHOD, + ResultType: request.Method, Error: &Nip47Error{ Code: NIP_47_ERROR_INTERNAL, Message: fmt.Sprintf("Something went wrong while paying invoice: %s", err.Error()), @@ -170,12 +170,12 @@ func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, sub *nostr.S return } payment.Preimage = &preimage - // TODO: What to do here? + // TODO: https://github.com/getAlby/nostr-wallet-connect/issues/231 nostrEvent.State = NOSTR_EVENT_STATE_HANDLER_EXECUTED svc.db.Save(&nostrEvent) svc.db.Save(&payment) resp, err := svc.createResponse(event, Nip47Response{ - ResultType: NIP_47_MULTI_PAY_INVOICE_METHOD, + ResultType: request.Method, Result: Nip47PayResponse{ Preimage: preimage, }, diff --git a/handle_multi_pay_keysend_request.go b/handle_multi_pay_keysend_request.go new file mode 100644 index 00000000..82860442 --- /dev/null +++ b/handle_multi_pay_keysend_request.go @@ -0,0 +1,163 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "sync" + + "github.com/nbd-wtf/go-nostr" + "github.com/sirupsen/logrus" +) + +func (svc *Service) HandleMultiPayKeysendEvent(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 := &Nip47MultiPayKeysendParams{} + 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 _, keysendInfo := range multiPayParams.Invoices { + wg.Add(1) + go func(keysendInfo Nip47MultiPayKeysendElement) { + defer wg.Done() + + keysendDTagValue := keysendInfo.Id + if keysendDTagValue == "" { + keysendDTagValue = keysendInfo.Pubkey + } + dTag := []string{"d", keysendDTagValue} + + hasPermission, code, message := svc.hasPermission(&app, event, NIP_47_PAY_INVOICE_METHOD, keysendInfo.Amount) + + if !hasPermission { + svc.Logger.WithFields(logrus.Fields{ + "eventId": event.ID, + "eventKind": event.Kind, + "appId": app.ID, + "senderPubkey": keysendInfo.Pubkey, + }).Errorf("App does not have permission: %s %s", code, message) + + resp, err := svc.createResponse(event, Nip47Response{ + ResultType: request.Method, + Error: &Nip47Error{ + Code: code, + Message: message, + }, + }, nostr.Tags{dTag}, ss) + if err != nil { + svc.Logger.WithFields(logrus.Fields{ + "eventId": event.ID, + "eventKind": event.Kind, + "senderPubkey": keysendInfo.Pubkey, + "keysendId": keysendInfo.Id, + }).Errorf("Failed to process event: %v", err) + return + } + svc.PublishEvent(ctx, sub, event, resp) + return + } + + payment := Payment{App: app, NostrEvent: nostrEvent, Amount: uint(keysendInfo.Amount / 1000)} + insertPaymentResult := svc.db.Create(&payment) + if insertPaymentResult.Error != nil { + svc.Logger.WithFields(logrus.Fields{ + "eventId": event.ID, + "eventKind": event.Kind, + "senderPubkey": keysendInfo.Pubkey, + "keysendId": keysendInfo.Id, + }).Errorf("Failed to process event: %v", insertPaymentResult.Error) + return + } + + svc.Logger.WithFields(logrus.Fields{ + "eventId": event.ID, + "eventKind": event.Kind, + "appId": app.ID, + "senderPubkey": keysendInfo.Pubkey, + }).Info("Sending payment") + + preimage, err := svc.lnClient.SendKeysend(ctx, event.PubKey, keysendInfo.Amount/1000, keysendInfo.Pubkey, keysendInfo.Preimage, keysendInfo.TLVRecords) + if err != nil { + svc.Logger.WithFields(logrus.Fields{ + "eventId": event.ID, + "eventKind": event.Kind, + "appId": app.ID, + "senderPubkey": keysendInfo.Pubkey, + }).Infof("Failed to send payment: %v", err) + // TODO: https://github.com/getAlby/nostr-wallet-connect/issues/231 + nostrEvent.State = NOSTR_EVENT_STATE_HANDLER_ERROR + svc.db.Save(&nostrEvent) + + resp, err := svc.createResponse(event, Nip47Response{ + ResultType: request.Method, + Error: &Nip47Error{ + Code: NIP_47_ERROR_INTERNAL, + Message: fmt.Sprintf("Something went wrong while paying invoice: %s", err.Error()), + }, + }, nostr.Tags{dTag}, ss) + if err != nil { + svc.Logger.WithFields(logrus.Fields{ + "eventId": event.ID, + "eventKind": event.Kind, + "senderPubkey": keysendInfo.Pubkey, + "keysendId": keysendInfo.Id, + }).Errorf("Failed to process event: %v", err) + return + } + svc.PublishEvent(ctx, sub, event, resp) + return + } + payment.Preimage = &preimage + // TODO: https://github.com/getAlby/nostr-wallet-connect/issues/231 + nostrEvent.State = NOSTR_EVENT_STATE_HANDLER_EXECUTED + svc.db.Save(&nostrEvent) + svc.db.Save(&payment) + resp, err := svc.createResponse(event, Nip47Response{ + ResultType: request.Method, + Result: Nip47PayResponse{ + Preimage: preimage, + }, + }, nostr.Tags{dTag}, ss) + if err != nil { + svc.Logger.WithFields(logrus.Fields{ + "eventId": event.ID, + "eventKind": event.Kind, + "senderPubkey": keysendInfo.Pubkey, + "keysendId": keysendInfo.Id, + }).Errorf("Failed to process event: %v", err) + return + } + svc.PublishEvent(ctx, sub, event, resp) + }(keysendInfo) + } + + wg.Wait() + return +} diff --git a/models.go b/models.go index cf23aa5e..501d7e60 100644 --- a/models.go +++ b/models.go @@ -17,6 +17,7 @@ const ( 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_MULTI_PAY_KEYSEND_METHOD = "multi_pay_keysend" NIP_47_ERROR_INTERNAL = "INTERNAL" NIP_47_ERROR_NOT_IMPLEMENTED = "NOT_IMPLEMENTED" NIP_47_ERROR_QUOTA_EXCEEDED = "QUOTA_EXCEEDED" @@ -25,7 +26,7 @@ const ( NIP_47_ERROR_EXPIRED = "EXPIRED" NIP_47_ERROR_RESTRICTED = "RESTRICTED" NIP_47_OTHER = "OTHER" - NIP_47_CAPABILITIES = "pay_invoice,pay_keysend,get_balance,get_info,make_invoice,lookup_invoice,list_transactions,multi_pay_invoice" + NIP_47_CAPABILITIES = "pay_invoice,pay_keysend,get_balance,get_info,make_invoice,lookup_invoice,list_transactions,multi_pay_invoice,multi_pay_keysend" ) const ( @@ -173,7 +174,16 @@ type Nip47PayResponse struct { Preimage string `json:"preimage"` } -type Nip47MultiPayParams struct { +type Nip47MultiPayKeysendParams struct { + Invoices []Nip47MultiPayKeysendElement `json:"invoices"` +} + +type Nip47MultiPayKeysendElement struct { + Nip47KeysendParams + Id string `json:"id"` +} + +type Nip47MultiPayInvoiceParams struct { Invoices []Nip47MultiPayInvoiceElement `json:"invoices"` } @@ -182,10 +192,6 @@ type Nip47MultiPayInvoiceElement struct { Id string `json:"id"` } -type Nip47MultiPayResponse struct { - Invoice string `json:"invoice"` -} - type Nip47KeysendParams struct { Amount int64 `json:"amount"` Pubkey string `json:"pubkey"` diff --git a/service.go b/service.go index 48dc7a2d..d7fc573f 100644 --- a/service.go +++ b/service.go @@ -334,6 +334,9 @@ func (svc *Service) HandleEvent(ctx context.Context, sub *nostr.Subscription, ev case NIP_47_MULTI_PAY_INVOICE_METHOD: svc.HandleMultiPayInvoiceEvent(ctx, sub, nip47Request, event, app, ss) return + case NIP_47_MULTI_PAY_KEYSEND_METHOD: + svc.HandleMultiPayKeysendEvent(ctx, sub, nip47Request, event, app, ss) + return // TODO: for the below handlers consider returning // Nip47Response instead of *nostr.Event case NIP_47_PAY_INVOICE_METHOD: