publish_nip47_info.go raw
1 package nip47
2
3 import (
4 "context"
5 "errors"
6 "strconv"
7 "strings"
8
9 "github.com/getAlby/go-nostr"
10 "github.com/getAlby/hub/db"
11 "github.com/getAlby/hub/lnclient"
12 "github.com/getAlby/hub/logger"
13 "github.com/getAlby/hub/nip47/cipher"
14 "github.com/getAlby/hub/nip47/models"
15 nostrmodels "github.com/getAlby/hub/nostr/models"
16 "github.com/sirupsen/logrus"
17 )
18
19 type Nip47InfoPublishRequest struct {
20 AppId uint
21 AppWalletPubKey string
22 AppWalletPrivKey string
23 RelayUrl string
24 Attempt uint32
25 }
26
27 type nip47InfoPublishQueue struct {
28 channel chan *Nip47InfoPublishRequest
29 }
30
31 func NewNip47InfoPublishQueue() *nip47InfoPublishQueue {
32 return &nip47InfoPublishQueue{
33 channel: make(chan *Nip47InfoPublishRequest),
34 }
35 }
36
37 func (q *nip47InfoPublishQueue) AddToQueue(req *Nip47InfoPublishRequest) {
38 // thread will be blocked if the channel is full, so execute in a separate goroutine
39 go func() {
40 q.channel <- req
41 }()
42 }
43
44 func (q *nip47InfoPublishQueue) Channel() <-chan *Nip47InfoPublishRequest {
45 return q.channel
46 }
47
48 func (svc *nip47Service) GetNip47Info(ctx context.Context, pool nostrmodels.SimplePool, appWalletPubKey string) (*nostr.Event, error) {
49 filter := nostr.Filter{
50 Kinds: []int{models.INFO_EVENT_KIND},
51 Authors: []string{appWalletPubKey},
52 Limit: 1,
53 }
54
55 relayEvent := pool.QuerySingle(ctx, svc.cfg.GetRelayUrls(), filter)
56 if relayEvent == nil {
57 return nil, nil
58 }
59
60 return relayEvent.Event, nil
61 }
62
63 func (svc *nip47Service) PublishNip47Info(ctx context.Context, pool nostrmodels.SimplePool, appId uint, appWalletPubKey string, appWalletPrivKey string, relayUrl string, lnClient lnclient.LNClient) (*nostr.Event, error) {
64 var capabilities []string
65 var permitsNotifications bool
66 tags := nostr.Tags{[]string{"encryption", cipher.SUPPORTED_ENCRYPTIONS}}
67
68 if svc.keys.GetNostrPublicKey() == appWalletPubKey {
69 // legacy app, so return lnClient.GetSupportedNIP47Methods()
70 capabilities = lnClient.GetSupportedNIP47Methods()
71 permitsNotifications = true
72 } else {
73 app := db.App{}
74 err := svc.db.First(&app, appId).Error
75 if err != nil {
76 logger.Logger.WithFields(logrus.Fields{
77 "walletPubKey": appWalletPubKey,
78 }).WithError(err).Error("Failed to find app for wallet pubkey")
79 return nil, err
80 }
81 capabilities = svc.permissionsService.GetPermittedMethods(&app, lnClient)
82 permitsNotifications = svc.permissionsService.PermitsNotifications(&app)
83
84 // NWA: associate the info event with the app so that the app can receive the wallet pubkey
85 tags = append(tags, []string{"p", app.AppPubkey})
86 }
87 if permitsNotifications && len(lnClient.GetSupportedNIP47NotificationTypes()) > 0 {
88 capabilities = append(capabilities, "notifications")
89 tags = append(tags, []string{"notifications", strings.Join(lnClient.GetSupportedNIP47NotificationTypes(), " ")})
90 }
91
92 ev := &nostr.Event{}
93 ev.Kind = models.INFO_EVENT_KIND
94 ev.Content = strings.Join(capabilities, " ")
95 ev.CreatedAt = nostr.Now()
96 ev.PubKey = appWalletPubKey
97 ev.Tags = tags
98 err := ev.Sign(appWalletPrivKey)
99 if err != nil {
100 return nil, err
101 }
102
103 // publish to a single relay so that we can requeue failed publishes on a relay level
104 publishResultChannel := pool.PublishMany(ctx, []string{relayUrl}, *ev)
105
106 publishSuccessful := false
107 for result := range publishResultChannel {
108 if result.Error == nil {
109 publishSuccessful = true
110 } else {
111 logger.Logger.WithFields(logrus.Fields{
112 "appId": appId,
113 "relay": result.RelayURL,
114 }).WithError(result.Error).Error("failed to publish nip47 info to relay")
115 }
116 }
117 if !publishSuccessful {
118 return nil, errors.New("failed to publish nostr info event to all relays")
119 }
120 logger.Logger.WithField("wallet_pubkey", appWalletPubKey).Debug("published info event")
121 return ev, nil
122 }
123
124 func (svc *nip47Service) PublishNip47InfoDeletion(ctx context.Context, pool nostrmodels.SimplePool, appWalletPubKey string, appWalletPrivKey string, infoEventId string) error {
125 ev := &nostr.Event{}
126 ev.Kind = nostr.KindDeletion
127 ev.Content = "deleting nip47 info since app connection for this key was deleted"
128 ev.Tags = nostr.Tags{[]string{"e", infoEventId}, []string{"k", strconv.Itoa(models.INFO_EVENT_KIND)}}
129 ev.CreatedAt = nostr.Now()
130 ev.PubKey = appWalletPubKey
131 err := ev.Sign(appWalletPrivKey)
132 if err != nil {
133 return err
134 }
135 publishResultChannel := pool.PublishMany(ctx, svc.cfg.GetRelayUrls(), *ev)
136
137 publishSuccessful := false
138 for result := range publishResultChannel {
139 if result.Error == nil {
140 publishSuccessful = true
141 } else {
142 logger.Logger.WithFields(logrus.Fields{
143 "wallet_pubkey": appWalletPubKey,
144 "relay": result.RelayURL,
145 }).WithError(result.Error).Error("failed to publish info event deletion to relay")
146 }
147 }
148
149 if !publishSuccessful {
150 return errors.New("failed to publish info event deletion to all relays")
151 }
152 return nil
153 }
154