create_connection_controller.go raw
1 package controllers
2
3 import (
4 "context"
5 "slices"
6 "time"
7
8 "github.com/getAlby/go-nostr"
9 "github.com/getAlby/hub/alby"
10 "github.com/getAlby/hub/constants"
11 "github.com/getAlby/hub/logger"
12 "github.com/getAlby/hub/nip47/models"
13 "github.com/getAlby/hub/nip47/permissions"
14 "github.com/sirupsen/logrus"
15 )
16
17 type createConnectionParams struct {
18 Pubkey string `json:"pubkey"` // pubkey of the app connection
19 Name string `json:"name"`
20 RequestMethods []string `json:"request_methods"`
21 NotificationTypes []string `json:"notification_types"`
22 MaxAmount uint64 `json:"max_amount"`
23 BudgetRenewal string `json:"budget_renewal"`
24 ExpiresAt *uint64 `json:"expires_at"` // unix timestamp
25 Isolated bool `json:"isolated"`
26 Metadata map[string]interface{} `json:"metadata,omitempty"`
27 }
28
29 type createConnectionResponse struct {
30 // pubkey is given, user requesting already knows relay.
31 WalletPubkey string `json:"wallet_pubkey"`
32 }
33
34 func (controller *nip47Controller) HandleCreateConnectionEvent(ctx context.Context, nip47Request *models.Request, requestEventId uint, publishResponse publishFunc) {
35 params := &createConnectionParams{}
36 resp := decodeRequest(nip47Request, params)
37 if resp != nil {
38 publishResponse(resp, nostr.Tags{})
39 return
40 }
41
42 logger.Logger.WithFields(logrus.Fields{
43 "request_event_id": requestEventId,
44 "params": params,
45 }).Info("creating app")
46
47 var expiresAt *time.Time
48 if params.ExpiresAt != nil {
49 expiresAtUnsigned := *params.ExpiresAt
50 expiresAtValue := time.Unix(int64(expiresAtUnsigned), 0)
51 expiresAt = &expiresAtValue
52 }
53
54 maxAmountSat := params.MaxAmount / 1000
55
56 if params.Name == alby.ALBY_ACCOUNT_APP_NAME {
57 publishResponse(&models.Response{
58 ResultType: nip47Request.Method,
59 Error: &models.Error{
60 Code: constants.ERROR_BAD_REQUEST,
61 Message: "cannot create a new app that has reserved name: " + alby.ALBY_ACCOUNT_APP_NAME,
62 },
63 }, nostr.Tags{})
64 return
65 }
66
67 // explicitly do not allow creating an app with create_connection permission
68 if slices.Contains(params.RequestMethods, models.CREATE_CONNECTION_METHOD) {
69 publishResponse(&models.Response{
70 ResultType: nip47Request.Method,
71 Error: &models.Error{
72 Code: constants.ERROR_BAD_REQUEST,
73 Message: "cannot create a new app that has create_connection permission via NWC",
74 },
75 }, nostr.Tags{})
76 return
77 }
78
79 // ensure there is at least one request method
80 if len(params.RequestMethods) == 0 {
81 publishResponse(&models.Response{
82 ResultType: nip47Request.Method,
83 Error: &models.Error{
84 Code: constants.ERROR_BAD_REQUEST,
85 Message: "No request methods provided",
86 },
87 }, nostr.Tags{})
88 return
89 }
90
91 supportedMethods := controller.lnClient.GetSupportedNIP47Methods()
92 if slices.ContainsFunc(params.RequestMethods, func(method string) bool {
93 return !slices.Contains(supportedMethods, method)
94 }) {
95 publishResponse(&models.Response{
96 ResultType: nip47Request.Method,
97 Error: &models.Error{
98 Code: constants.ERROR_BAD_REQUEST,
99 Message: "One or more methods are not supported by the current LNClient",
100 },
101 }, nostr.Tags{})
102 return
103 }
104
105 scopes, err := permissions.RequestMethodsToScopes(params.RequestMethods)
106
107 if err != nil {
108 logger.Logger.WithFields(logrus.Fields{
109 "request_event_id": requestEventId,
110 }).WithError(err).Error("Failed to convert request methods to scopes")
111 publishResponse(&models.Response{
112 ResultType: nip47Request.Method,
113 Error: mapNip47Error(err),
114 }, nostr.Tags{})
115 return
116 }
117
118 supportedNotificationTypes := controller.lnClient.GetSupportedNIP47NotificationTypes()
119 if len(params.NotificationTypes) > 0 {
120 if slices.ContainsFunc(params.NotificationTypes, func(method string) bool {
121 return !slices.Contains(supportedNotificationTypes, method)
122 }) {
123 publishResponse(&models.Response{
124 ResultType: nip47Request.Method,
125 Error: &models.Error{
126 Code: constants.ERROR_BAD_REQUEST,
127 Message: "One or more notification types are not supported by the current LNClient",
128 },
129 }, nostr.Tags{})
130 return
131 }
132 scopes = append(scopes, constants.NOTIFICATIONS_SCOPE)
133 }
134
135 app, _, err := controller.appsService.CreateApp(params.Name, params.Pubkey, maxAmountSat, params.BudgetRenewal, expiresAt, scopes, params.Isolated, params.Metadata)
136 if err != nil {
137 logger.Logger.WithFields(logrus.Fields{
138 "request_event_id": requestEventId,
139 }).WithError(err).Error("Failed to create app")
140 publishResponse(&models.Response{
141 ResultType: nip47Request.Method,
142 Error: mapNip47Error(err),
143 }, nostr.Tags{})
144 return
145 }
146
147 responsePayload := createConnectionResponse{
148 WalletPubkey: *app.WalletPubkey,
149 }
150
151 publishResponse(&models.Response{
152 ResultType: nip47Request.Method,
153 Result: responsePayload,
154 }, nostr.Tags{})
155 }
156