Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Commit

Permalink
Chore: payment sent notification improvements (#472)
Browse files Browse the repository at this point in the history
  • Loading branch information
rolznz authored Jun 20, 2024
1 parent 2680257 commit 9f46bb2
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 59 deletions.
5 changes: 0 additions & 5 deletions events/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,10 @@ type Event struct {

type PaymentReceivedEventProperties struct {
PaymentHash string `json:"payment_hash"`
Amount uint64 `json:"amount"`
NodeType string `json:"node_type"`
}

type PaymentSentEventProperties struct {
PaymentId string `json:"payment_id"`
PaymentHash string `json:"payment_hash"`
Amount uint64 `json:"amount"`
NodeType string `json:"node_type"`
}

type ChannelBackupEvent struct {
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/EmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const EmptyState: React.FC<Props> = ({
buttonLink,
}) => {
return (
<div className="flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm">
<div className="flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm py-8">
<div className="flex flex-col items-center gap-1 text-center max-w-sm">
<Icon className="w-10 h-10 text-muted-foreground" />
<h3 className="mt-4 text-lg font-semibold">{message}</h3>
Expand Down
9 changes: 0 additions & 9 deletions lnclient/ldk/ldk.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,10 +370,6 @@ func (ls *LDKService) resetRouterInternal() {
logger.Logger.WithFields(logrus.Fields{
"rowsAffected": rowsAffected,
}).Info("Reset router")

if err != nil {
logger.Logger.WithField("key", key).WithError(err).Error("ResetRouter failed")
}
}
}

Expand Down Expand Up @@ -1225,18 +1221,13 @@ func (ls *LDKService) handleLdkEvent(event *ldk_node.Event) {
Event: "nwc_payment_received",
Properties: &events.PaymentReceivedEventProperties{
PaymentHash: eventType.PaymentHash,
Amount: eventType.AmountMsat / 1000,
NodeType: config.LDKBackendType,
},
})
case ldk_node.EventPaymentSuccessful:
ls.eventPublisher.Publish(&events.Event{
Event: "nwc_payment_sent",
Properties: &events.PaymentSentEventProperties{
PaymentId: *eventType.PaymentId,
PaymentHash: eventType.PaymentHash,
Amount: *eventType.FeePaidMsat / 1000,
NodeType: config.LDKBackendType,
},
})
}
Expand Down
38 changes: 24 additions & 14 deletions nip47/controllers/get_info_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,27 @@ package controllers

import (
"context"
"strings"

"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/lnclient"
"github.com/getAlby/nostr-wallet-connect/logger"
"github.com/getAlby/nostr-wallet-connect/nip47/models"
"github.com/getAlby/nostr-wallet-connect/nip47/notifications"
permissions "github.com/getAlby/nostr-wallet-connect/nip47/permissions"
"github.com/nbd-wtf/go-nostr"
"github.com/sirupsen/logrus"
)

type getInfoResponse struct {
Alias string `json:"alias"`
Color string `json:"color"`
Pubkey string `json:"pubkey"`
Network string `json:"network"`
BlockHeight uint32 `json:"block_height"`
BlockHash string `json:"block_hash"`
Methods []string `json:"methods"`
Alias string `json:"alias"`
Color string `json:"color"`
Pubkey string `json:"pubkey"`
Network string `json:"network"`
BlockHeight uint32 `json:"block_height"`
BlockHash string `json:"block_hash"`
Methods []string `json:"methods"`
Notifications []string `json:"notifications"`
}

type getInfoController struct {
Expand Down Expand Up @@ -68,14 +71,21 @@ func (controller *getInfoController) HandleGetInfoEvent(ctx context.Context, nip
network = "mainnet"
}

supportedNotifications := []string{}
if controller.permissionsService.PermitsNotifications(app) {
// TODO: this needs to be LNClient-specific
supportedNotifications = strings.Split(notifications.NOTIFICATION_TYPES, " ")
}

responsePayload := &getInfoResponse{
Alias: info.Alias,
Color: info.Color,
Pubkey: info.Pubkey,
Network: network,
BlockHeight: info.BlockHeight,
BlockHash: info.BlockHash,
Methods: controller.permissionsService.GetPermittedMethods(app),
Alias: info.Alias,
Color: info.Color,
Pubkey: info.Pubkey,
Network: network,
BlockHeight: info.BlockHeight,
BlockHash: info.BlockHash,
Methods: controller.permissionsService.GetPermittedMethods(app),
Notifications: supportedNotifications,
}

publishResponse(&models.Response{
Expand Down
62 changes: 62 additions & 0 deletions nip47/controllers/get_info_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,66 @@ func TestHandleGetInfoEvent_WithPermission(t *testing.T) {
assert.Equal(t, tests.MockNodeInfo.BlockHeight, nodeInfo.BlockHeight)
assert.Equal(t, tests.MockNodeInfo.BlockHash, nodeInfo.BlockHash)
assert.Equal(t, []string{"get_info"}, nodeInfo.Methods)
assert.Equal(t, []string{}, nodeInfo.Notifications)
}

func TestHandleGetInfoEvent_WithNotifications(t *testing.T) {
ctx := context.TODO()
defer tests.RemoveTestService()
svc, err := tests.CreateTestService()
assert.NoError(t, err)

app, _, err := tests.CreateApp(svc)
assert.NoError(t, err)

nip47Request := &models.Request{}
err = json.Unmarshal([]byte(nip47GetInfoJson), nip47Request)
assert.NoError(t, err)

dbRequestEvent := &db.RequestEvent{}
err = svc.DB.Create(&dbRequestEvent).Error
assert.NoError(t, err)

appPermission := &db.AppPermission{
AppId: app.ID,
RequestMethod: models.GET_INFO_METHOD,
ExpiresAt: nil,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)

// TODO: AppPermission RequestMethod needs to change to scope
appPermission = &db.AppPermission{
AppId: app.ID,
RequestMethod: "notifications",
ExpiresAt: nil,
}
err = svc.DB.Create(appPermission).Error
assert.NoError(t, err)

checkPermission := func(amountMsat uint64) *models.Response {
return nil
}

var publishedResponse *models.Response

publishResponse := func(response *models.Response, tags nostr.Tags) {
publishedResponse = response
}

permissionsSvc := permissions.NewPermissionsService(svc.DB, svc.EventPublisher)

NewGetInfoController(permissionsSvc, svc.LNClient).
HandleGetInfoEvent(ctx, nip47Request, dbRequestEvent.ID, app, checkPermission, publishResponse)

assert.Nil(t, publishedResponse.Error)
nodeInfo := publishedResponse.Result.(*getInfoResponse)
assert.Equal(t, tests.MockNodeInfo.Alias, nodeInfo.Alias)
assert.Equal(t, tests.MockNodeInfo.Color, nodeInfo.Color)
assert.Equal(t, tests.MockNodeInfo.Pubkey, nodeInfo.Pubkey)
assert.Equal(t, tests.MockNodeInfo.Network, nodeInfo.Network)
assert.Equal(t, tests.MockNodeInfo.BlockHeight, nodeInfo.BlockHeight)
assert.Equal(t, tests.MockNodeInfo.BlockHash, nodeInfo.BlockHash)
assert.Equal(t, []string{"get_info"}, nodeInfo.Methods)
assert.Equal(t, []string{"payment_received", "payment_sent"}, nodeInfo.Notifications)
}
6 changes: 5 additions & 1 deletion nip47/notifications/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ type Notification struct {
}

const (
NOTIFICATION_TYPES = "payment_received payment_sent" // e.g. "payment_received payment_sent balance_updated payment_sent channel_opened channel_closed ..."
NOTIFICATION_TYPES = "payment_received payment_sent"
PAYMENT_RECEIVED_NOTIFICATION = "payment_received"
PAYMENT_SENT_NOTIFICATION = "payment_sent"
)

type PaymentSentNotification struct {
models.Transaction
}

type PaymentReceivedNotification struct {
models.Transaction
}
37 changes: 19 additions & 18 deletions nip47/notifications/nip47_notifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,6 @@ func NewNip47Notifier(relay Relay, db *gorm.DB, cfg config.Config, keys keys.Key
}

func (notifier *Nip47Notifier) ConsumeEvent(ctx context.Context, event *events.Event) error {
if event.Event != "nwc_payment_received" && event.Event != "nwc_payment_sent" {
return nil
}

var transaction interface{}
var notificationType string
switch event.Event {
case "nwc_payment_received":
paymentReceivedEventProperties, ok := event.Properties.(*events.PaymentReceivedEventProperties)
Expand All @@ -58,41 +52,48 @@ func (notifier *Nip47Notifier) ConsumeEvent(ctx context.Context, event *events.E
return errors.New("failed to cast event")
}

var err error
transaction, err = notifier.lnClient.LookupInvoice(ctx, paymentReceivedEventProperties.PaymentHash)
transaction, err := notifier.lnClient.LookupInvoice(ctx, paymentReceivedEventProperties.PaymentHash)
if err != nil {
logger.Logger.
WithField("paymentHash", paymentReceivedEventProperties.PaymentHash).
WithError(err).
Error("Failed to lookup invoice by payment hash")
return err
}
notificationType = PAYMENT_RECEIVED_NOTIFICATION
notification := PaymentReceivedNotification{
Transaction: *transaction,
}

notifier.notifySubscribers(ctx, &Notification{
Notification: notification,
NotificationType: PAYMENT_RECEIVED_NOTIFICATION,
}, nostr.Tags{})

case "nwc_payment_sent":
paymentSentEventProperties, ok := event.Properties.(*events.PaymentSentEventProperties)
if !ok {
logger.Logger.WithField("event", event).Error("Failed to cast event")
return errors.New("failed to cast event")
}

var err error
transaction, err = notifier.lnClient.LookupInvoice(ctx, paymentSentEventProperties.PaymentHash)
transaction, err := notifier.lnClient.LookupInvoice(ctx, paymentSentEventProperties.PaymentHash)
if err != nil {
logger.Logger.
WithField("paymentHash", paymentSentEventProperties.PaymentHash).
WithError(err).
Error("Failed to lookup invoice by payment hash")
return err
}
notificationType = PAYMENT_SENT_NOTIFICATION
default:
logger.Logger.Fatalf("Unsupported notification type: %v", event.Event)
notification := PaymentSentNotification{
Transaction: *transaction,
}

notifier.notifySubscribers(ctx, &Notification{
Notification: notification,
NotificationType: PAYMENT_SENT_NOTIFICATION,
}, nostr.Tags{})
}

notifier.notifySubscribers(ctx, &Notification{
Notification: transaction,
NotificationType: notificationType,
}, nostr.Tags{})
return nil
}

Expand Down
7 changes: 0 additions & 7 deletions nip47/notifications/nip47_notifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ func TestSendNotification(t *testing.T) {
Event: "nwc_payment_received",
Properties: &events.PaymentReceivedEventProperties{
PaymentHash: tests.MockPaymentHash,
Amount: uint64(tests.MockTransaction.Amount),
NodeType: "LDK",
},
}

Expand Down Expand Up @@ -88,10 +86,7 @@ func TestSendNotification(t *testing.T) {
testEvent = &events.Event{
Event: "nwc_payment_sent",
Properties: &events.PaymentSentEventProperties{
PaymentId: tests.MockPaymentId,
PaymentHash: tests.MockPaymentHash,
Amount: uint64(tests.MockTransaction.Amount),
NodeType: "LDK",
},
}

Expand Down Expand Up @@ -121,8 +116,6 @@ func TestSendNotificationNoPermission(t *testing.T) {
Event: "nwc_payment_received",
Properties: &events.PaymentReceivedEventProperties{
PaymentHash: tests.MockPaymentHash,
Amount: uint64(tests.MockTransaction.Amount),
NodeType: "LDK",
},
}

Expand Down
20 changes: 17 additions & 3 deletions nip47/permissions/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type PermissionsService interface {
HasPermission(app *db.App, requestMethod string, amount uint64) (result bool, code string, message string)
GetBudgetUsage(appPermission *db.AppPermission) uint64
GetPermittedMethods(app *db.App) []string
PermitsNotifications(app *db.App) bool
}

func NewPermissionsService(db *gorm.DB, eventPublisher events.EventPublisher) *permissionsService {
Expand Down Expand Up @@ -88,9 +89,9 @@ func (svc *permissionsService) GetBudgetUsage(appPermission *db.AppPermission) u

func (svc *permissionsService) GetPermittedMethods(app *db.App) []string {
appPermissions := []db.AppPermission{}
svc.db.Find(&appPermissions, &db.AppPermission{
AppId: app.ID,
})
// TODO: request_method needs to be renamed to scopes or capabilities
// see https://github.com/getAlby/nostr-wallet-connect-next/issues/219
svc.db.Where("app_id = ? and request_method <> ?", app.ID, "notifications").Find(&appPermissions)
requestMethods := make([]string, 0, len(appPermissions))
for _, appPermission := range appPermissions {
requestMethods = append(requestMethods, appPermission.RequestMethod)
Expand All @@ -103,6 +104,19 @@ func (svc *permissionsService) GetPermittedMethods(app *db.App) []string {
return requestMethods
}

func (svc *permissionsService) PermitsNotifications(app *db.App) bool {
notificationPermission := db.AppPermission{}
err := svc.db.First(&notificationPermission, &db.AppPermission{
AppId: app.ID,
RequestMethod: "notifications",
}).Error
if err != nil {
return false
}

return true
}

func getStartOfBudget(budget_type string) time.Time {
now := time.Now()
switch budget_type {
Expand Down
1 change: 0 additions & 1 deletion tests/mock_ln_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ const MockPaymentHash500 = "be8ad5d0b82071d538dcd160e3a3af444bd890de68388a4d771b

const MockInvoice = "lntb1230n1pjypux0pp5xgxzcks5jtx06k784f9dndjh664wc08ucrganpqn52d0ftrh9n8sdqyw3jscqzpgxqyz5vqsp5rkx7cq252p3frx8ytjpzc55rkgyx2mfkzzraa272dqvr2j6leurs9qyyssqhutxa24r5hqxstchz5fxlslawprqjnarjujp5sm3xj7ex73s32sn54fthv2aqlhp76qmvrlvxppx9skd3r5ut5xutgrup8zuc6ay73gqmra29m"
const MockPaymentHash = "320c2c5a1492ccfd5bc7aa4ad9b657d6aaec3cfcc0d1d98413a29af4ac772ccf" // for the above invoice
const MockPaymentId = "paymentId123"

var MockNodeInfo = lnclient.NodeInfo{
Alias: "bob",
Expand Down

0 comments on commit 9f46bb2

Please sign in to comment.