Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into task-list-invoices
Browse files Browse the repository at this point in the history
  • Loading branch information
rolznz committed Dec 14, 2023
2 parents c1e80b5 + f1824b1 commit 0c8d5f3
Show file tree
Hide file tree
Showing 13 changed files with 431 additions and 38 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ You can also contribute to our [bounty program](https://github.com/getAlby/light

`pay_invoice`

`pay_keysend`

⚠️ `make_invoice`
- ⚠️ invoice in response missing (TODO)

Expand All @@ -141,8 +143,6 @@ You can also contribute to our [bounty program](https://github.com/getAlby/light
`list_transactions`
- ⚠️ from and until in request not supported

`pay_keysend`

`multi_pay_invoice (TBC)`

`multi_pay_keysend (TBC)`
Expand All @@ -160,6 +160,9 @@ You can also contribute to our [bounty program](https://github.com/getAlby/light

`pay_invoice`

`pay_keysend`
- ⚠️ preimage in request not supported

⚠️ `make_invoice`
- ⚠️ expiry in request not supported
- ⚠️ invoice in response missing (TODO)
Expand All @@ -171,8 +174,6 @@ You can also contribute to our [bounty program](https://github.com/getAlby/light
`list_transactions`
- ⚠️ offset and unpaid in request not supported

`pay_keysend`

`multi_pay_invoice (TBC)`

`multi_pay_keysend (TBC)`
95 changes: 91 additions & 4 deletions alby.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,10 +392,10 @@ func (svc *AlbyOAuthService) ListTransactions(ctx context.Context, senderPubkey
endpoint := "/invoices"

switch invoiceType {
case "incoming":
endpoint += "/incoming"
case "outgoing":
endpoint += "/outgoing"
case "incoming":
endpoint += "/incoming"
case "outgoing":
endpoint += "/outgoing"
}

req, err := http.NewRequest("GET", fmt.Sprintf("%s%s?%s", svc.cfg.AlbyAPIURL, endpoint, urlParams.Encode()), nil)
Expand Down Expand Up @@ -518,6 +518,93 @@ func (svc *AlbyOAuthService) SendPaymentSync(ctx context.Context, senderPubkey,
return "", errors.New(errorPayload.Message)
}

func (svc *AlbyOAuthService) SendKeysend(ctx context.Context, senderPubkey string, amount int64, destination, preimage string, custom_records []TLVRecord) (preImage string, err error) {
app := App{}
err = svc.db.Preload("User").First(&app, &App{
NostrPubkey: senderPubkey,
}).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
"senderPubkey": senderPubkey,
"payeePubkey": destination,
}).Errorf("App not found: %v", err)
return "", err
}
svc.Logger.WithFields(logrus.Fields{
"senderPubkey": senderPubkey,
"payeePubkey": destination,
"appId": app.ID,
"userId": app.User.ID,
}).Info("Processing keysend request")
tok, err := svc.FetchUserToken(ctx, app)
if err != nil {
return "", err
}
client := svc.oauthConf.Client(ctx, tok)

customRecordsMap := make(map[string]string)
for _, record := range custom_records {
customRecordsMap[strconv.FormatUint(record.Type, 10)] = record.Value
}

body := bytes.NewBuffer([]byte{})
payload := &KeysendRequest{
Amount: amount,
Destination: destination,
CustomRecords: customRecordsMap,
}
err = json.NewEncoder(body).Encode(payload)

// here we don't use the preimage from params
req, err := http.NewRequest("POST", fmt.Sprintf("%s/payments/keysend", svc.cfg.AlbyAPIURL), body)
if err != nil {
svc.Logger.WithError(err).Error("Error creating request /payments/keysend")
return "", err
}

req.Header.Set("User-Agent", "NWC")
req.Header.Set("Content-Type", "application/json")

resp, err := client.Do(req)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
"senderPubkey": senderPubkey,
"payeePubkey": destination,
"appId": app.ID,
"userId": app.User.ID,
}).Errorf("Failed to pay keysend: %v", err)
return "", err
}

if resp.StatusCode < 300 {
responsePayload := &PayResponse{}
err = json.NewDecoder(resp.Body).Decode(responsePayload)
if err != nil {
return "", err
}
svc.Logger.WithFields(logrus.Fields{
"senderPubkey": senderPubkey,
"payeePubkey": destination,
"appId": app.ID,
"userId": app.User.ID,
"preimage": responsePayload.Preimage,
"paymentHash": responsePayload.PaymentHash,
}).Info("Keysend payment successful")
return responsePayload.Preimage, nil
}

errorPayload := &ErrorResponse{}
err = json.NewDecoder(resp.Body).Decode(errorPayload)
svc.Logger.WithFields(logrus.Fields{
"senderPubkey": senderPubkey,
"payeePubkey": destination,
"appId": app.ID,
"userId": app.User.ID,
"APIHttpStatus": resp.StatusCode,
}).Errorf("Payment failed %s", string(errorPayload.Message))
return "", errors.New(errorPayload.Message)
}

func (svc *AlbyOAuthService) AuthHandler(c echo.Context) error {
appName := c.QueryParam("c") // c - for client
// clear current session
Expand Down
2 changes: 1 addition & 1 deletion handle_balance_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (svc *Service) HandleGetBalanceEvent(ctx context.Context, request *Nip47Req
return nil, err
}

hasPermission, code, message := svc.hasPermission(&app, event, request.Method, nil)
hasPermission, code, message := svc.hasPermission(&app, event, request.Method, 0)

if !hasPermission {
svc.Logger.WithFields(logrus.Fields{
Expand Down
2 changes: 1 addition & 1 deletion handle_info_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (svc *Service) HandleGetInfoEvent(ctx context.Context, request *Nip47Reques
return nil, err
}

hasPermission, code, message := svc.hasPermission(&app, event, request.Method, nil)
hasPermission, code, message := svc.hasPermission(&app, event, request.Method, 0)

if !hasPermission {
svc.Logger.WithFields(logrus.Fields{
Expand Down
8 changes: 4 additions & 4 deletions handle_list_transactions_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (svc *Service) HandleListTransactionsEvent(ctx context.Context, request *Ni
return nil, err
}

hasPermission, code, message := svc.hasPermission(&app, event, request.Method, nil)
hasPermission, code, message := svc.hasPermission(&app, event, request.Method, 0)

if !hasPermission {
svc.Logger.WithFields(logrus.Fields{
Expand All @@ -45,9 +45,9 @@ func (svc *Service) HandleListTransactionsEvent(ctx context.Context, request *Ni
return svc.createResponse(event, Nip47Response{
ResultType: request.Method,
Error: &Nip47Error{
Code: code,
Message: message,
}}, ss)
Code: code,
Message: message,
}}, ss)
}

svc.Logger.WithFields(logrus.Fields{
Expand Down
2 changes: 1 addition & 1 deletion handle_lookup_invoice_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func (svc *Service) HandleLookupInvoiceEvent(ctx context.Context, request *Nip47
}

// TODO: move to a shared function
hasPermission, code, message := svc.hasPermission(&app, event, request.Method, nil)
hasPermission, code, message := svc.hasPermission(&app, event, request.Method, 0)

if !hasPermission {
svc.Logger.WithFields(logrus.Fields{
Expand Down
2 changes: 1 addition & 1 deletion handle_make_invoice_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (svc *Service) HandleMakeInvoiceEvent(ctx context.Context, request *Nip47Re
}

// TODO: move to a shared function
hasPermission, code, message := svc.hasPermission(&app, event, request.Method, nil)
hasPermission, code, message := svc.hasPermission(&app, event, request.Method, 0)

if !hasPermission {
svc.Logger.WithFields(logrus.Fields{
Expand Down
96 changes: 96 additions & 0 deletions handle_pay_keysend_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package main

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

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

func (svc *Service) HandlePayKeysendEvent(ctx context.Context, request *Nip47Request, event *nostr.Event, app App, ss []byte) (result *nostr.Event, err error) {

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)
return nil, err
}

payParams := &Nip47KeysendParams{}
err = json.Unmarshal(request.Params, payParams)
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)
return nil, err
}

// We use pay_invoice permissions for budget and max amount
hasPermission, code, message := svc.hasPermission(&app, event, NIP_47_PAY_INVOICE_METHOD, payParams.Amount)

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

return svc.createResponse(event, Nip47Response{
ResultType: request.Method,
Error: &Nip47Error{
Code: code,
Message: message,
}}, ss)
}

payment := Payment{App: app, NostrEvent: nostrEvent, Amount: uint(payParams.Amount / 1000)}
insertPaymentResult := svc.db.Create(&payment)
if insertPaymentResult.Error != nil {
return nil, insertPaymentResult.Error
}

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

preimage, err := svc.lnClient.SendKeysend(ctx, event.PubKey, payParams.Amount/1000, payParams.Pubkey, payParams.Preimage, payParams.TLVRecords)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
"eventId": event.ID,
"eventKind": event.Kind,
"appId": app.ID,
"senderPubkey": payParams.Pubkey,
}).Infof("Failed to send payment: %v", err)
nostrEvent.State = NOSTR_EVENT_STATE_HANDLER_ERROR
svc.db.Save(&nostrEvent)
return 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()),
},
}, ss)
}
payment.Preimage = &preimage
nostrEvent.State = NOSTR_EVENT_STATE_HANDLER_EXECUTED
svc.db.Save(&nostrEvent)
svc.db.Save(&payment)
return svc.createResponse(event, Nip47Response{
ResultType: request.Method,
Result: Nip47PayResponse{
Preimage: preimage,
},
}, ss)
}
2 changes: 1 addition & 1 deletion handle_payment_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (svc *Service) HandlePayInvoiceEvent(ctx context.Context, request *Nip47Req
}, ss)
}

hasPermission, code, message := svc.hasPermission(&app, event, request.Method, &paymentRequest)
hasPermission, code, message := svc.hasPermission(&app, event, request.Method, paymentRequest.MSatoshi)

if !hasPermission {
svc.Logger.WithFields(logrus.Fields{
Expand Down
Loading

0 comments on commit 0c8d5f3

Please sign in to comment.