diff --git a/alby.go b/alby.go index 0248e13a..1446298f 100644 --- a/alby.go +++ b/alby.go @@ -269,6 +269,67 @@ func (svc *AlbyOAuthService) LookupInvoice(ctx context.Context, senderPubkey str return "", false, errors.New(errorPayload.Message) } +func (svc *AlbyOAuthService) GetInfo(ctx context.Context, senderPubkey string) (alias, color, pubkey, network string, block_height uint32, block_hash string, err error) { + alias = "getalby.com" + app := App{} + err = svc.db.Preload("User").First(&app, &App{ + NostrPubkey: senderPubkey, + }).Error + if err != nil { + svc.Logger.WithFields(logrus.Fields{ + "senderPubkey": senderPubkey, + }).Errorf("App not found: %v", err) + return "", "", "", "", 0, "", err + } + tok, err := svc.FetchUserToken(ctx, app) + if err != nil { + return "", "", "", "", 0, "", err + } + client := svc.oauthConf.Client(ctx, tok) + + req, err := http.NewRequest("GET", fmt.Sprintf("%s/getinfo", svc.cfg.AlbyAPIURL), nil) + if err != nil { + svc.Logger.WithError(err).Error("Error creating request /getinfo") + return "", "", "", "", 0, "", err + } + + req.Header.Set("User-Agent", "NWC") + + resp, err := client.Do(req) + if err != nil { + svc.Logger.WithFields(logrus.Fields{ + "senderPubkey": senderPubkey, + "appId": app.ID, + "userId": app.User.ID, + }).Errorf("Failed to fetch node info: %v", err) + return "", "", "", "", 0, "", err + } + + if resp.StatusCode < 300 { + // TODO: decode fetched node info + // err = json.NewDecoder(resp.Body).Decode(&nodeInfo) + if err != nil { + return "", "", "", "", 0, "", err + } + svc.Logger.WithFields(logrus.Fields{ + "senderPubkey": senderPubkey, + "appId": app.ID, + "userId": app.User.ID, + }).Info("Info fetch successful") + return alias, "color", "pubkey", "mainnet", 0, "blockhash", err + } + + errorPayload := &ErrorResponse{} + err = json.NewDecoder(resp.Body).Decode(errorPayload) + svc.Logger.WithFields(logrus.Fields{ + "senderPubkey": senderPubkey, + "appId": app.ID, + "userId": app.User.ID, + "APIHttpStatus": resp.StatusCode, + }).Errorf("Invoices listing failed %s", string(errorPayload.Message)) + return "", "", "", "", 0, "", errors.New(errorPayload.Message) +} + func (svc *AlbyOAuthService) GetBalance(ctx context.Context, senderPubkey string) (balance int64, err error) { app := App{} err = svc.db.Preload("User").First(&app, &App{ diff --git a/handle_info_request.go b/handle_info_request.go new file mode 100644 index 00000000..74fbfa92 --- /dev/null +++ b/handle_info_request.go @@ -0,0 +1,81 @@ +package main + +import ( + "context" + "fmt" + + "github.com/nbd-wtf/go-nostr" + "github.com/sirupsen/logrus" +) + +func (svc *Service) HandleGetInfoEvent(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 + } + + hasPermission, code, message := svc.hasPermission(&app, event, request.Method, nil) + + if !hasPermission { + svc.Logger.WithFields(logrus.Fields{ + "eventId": event.ID, + "eventKind": event.Kind, + "appId": app.ID, + }).Errorf("App does not have permission: %s %s", code, message) + + return svc.createResponse(event, Nip47Response{ + ResultType: NIP_47_GET_INFO_METHOD, + Error: &Nip47Error{ + Code: code, + Message: message, + }}, ss) + } + + svc.Logger.WithFields(logrus.Fields{ + "eventId": event.ID, + "eventKind": event.Kind, + "appId": app.ID, + }).Info("Fetching node info") + + alias, color, pubkey, network, block_height, block_hash, err := svc.lnClient.GetInfo(ctx, event.PubKey) + if err != nil { + svc.Logger.WithFields(logrus.Fields{ + "eventId": event.ID, + "eventKind": event.Kind, + "appId": app.ID, + }).Infof("Failed to fetch node info: %v", err) + nostrEvent.State = NOSTR_EVENT_STATE_HANDLER_ERROR + svc.db.Save(&nostrEvent) + return svc.createResponse(event, Nip47Response{ + ResultType: NIP_47_GET_BALANCE_METHOD, + Error: &Nip47Error{ + Code: NIP_47_ERROR_INTERNAL, + Message: fmt.Sprintf("Something went wrong while fetching node info: %s", err.Error()), + }, + }, ss) + } + + responsePayload := &Nip47GetInfoResponse{ + Alias: alias, + Color: color, + Pubkey: pubkey, + Network: network, + BlockHeight: block_height, + BlockHash: block_hash, + } + + nostrEvent.State = NOSTR_EVENT_STATE_HANDLER_EXECUTED + svc.db.Save(&nostrEvent) + return svc.createResponse(event, Nip47Response{ + ResultType: NIP_47_GET_BALANCE_METHOD, + Result: responsePayload, + }, + ss) +} diff --git a/lnd.go b/lnd.go index 4993c5e9..ad911257 100644 --- a/lnd.go +++ b/lnd.go @@ -18,6 +18,7 @@ import ( type LNClient interface { SendPaymentSync(ctx context.Context, senderPubkey string, payReq string) (preimage string, err error) GetBalance(ctx context.Context, senderPubkey string) (balance int64, err error) + GetInfo(ctx context.Context, senderPubkey string) (alias, color, pubkey, network string, block_height uint32, block_hash string, err error) MakeInvoice(ctx context.Context, senderPubkey string, amount int64, description string, descriptionHash string, expiry int64) (invoice string, paymentHash string, err error) LookupInvoice(ctx context.Context, senderPubkey string, paymentHash string) (invoice string, paid bool, err error) } @@ -51,6 +52,14 @@ func (svc *LNDService) GetBalance(ctx context.Context, senderPubkey string) (bal return int64(resp.LocalBalance.Sat), nil } +func (svc *LNDService) GetInfo(ctx context.Context, senderPubkey string) (alias, color, pubkey, network string, block_height uint32, block_hash string, err error) { + resp, err := svc.client.GetInfo(ctx, &lnrpc.GetInfoRequest{}) + if err != nil { + return "", "", "", "", 0, "", err + } + return resp.Alias, resp.Color, resp.IdentityPubkey, "mainnet", resp.BlockHeight, resp.BlockHash, nil +} + func (svc *LNDService) MakeInvoice(ctx context.Context, senderPubkey string, amount int64, description string, descriptionHash string, expiry int64) (invoice string, paymentHash string, err error) { var descriptionHashBytes []byte diff --git a/models.go b/models.go index aa5d3352..03abd398 100644 --- a/models.go +++ b/models.go @@ -13,6 +13,7 @@ const ( NIP_47_RESPONSE_KIND = 23195 NIP_47_PAY_INVOICE_METHOD = "pay_invoice" NIP_47_GET_BALANCE_METHOD = "get_balance" + NIP_47_GET_INFO_METHOD = "get_info" NIP_47_MAKE_INVOICE_METHOD = "make_invoice" NIP_47_LOOKUP_INVOICE_METHOD = "lookup_invoice" NIP_47_ERROR_INTERNAL = "INTERNAL" @@ -36,6 +37,7 @@ const ( var nip47MethodDescriptions = map[string]string{ NIP_47_GET_BALANCE_METHOD: "Read your balance", + NIP_47_GET_INFO_METHOD: "Read your node info", NIP_47_PAY_INVOICE_METHOD: "Send payments", NIP_47_MAKE_INVOICE_METHOD: "Create invoices", NIP_47_LOOKUP_INVOICE_METHOD: "Lookup status of invoices", @@ -43,6 +45,7 @@ var nip47MethodDescriptions = map[string]string{ var nip47MethodIcons = map[string]string{ NIP_47_GET_BALANCE_METHOD: "wallet", + NIP_47_GET_INFO_METHOD: "wallet", NIP_47_PAY_INVOICE_METHOD: "lightning", NIP_47_MAKE_INVOICE_METHOD: "invoice", NIP_47_LOOKUP_INVOICE_METHOD: "search", @@ -187,6 +190,15 @@ type Nip47BalanceResponse struct { BudgetRenewal string `json:"budget_renewal"` } +type Nip47GetInfoResponse 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"` +} + type Nip47MakeInvoiceParams struct { Amount int64 `json:"amount"` Description string `json:"description"` diff --git a/service_test.go b/service_test.go index 40b819fd..3cd99e67 100644 --- a/service_test.go +++ b/service_test.go @@ -444,6 +444,10 @@ func (mln *MockLn) GetBalance(ctx context.Context, senderPubkey string) (balance return 21, nil } +func (mln *MockLn) GetInfo(ctx context.Context, senderPubkey string) (alias, color, pubkey, network string, block_height uint32, block_hash string, err error) { + return "bob", "#3399FF", "123pubkey", "testnet", 12, "123blockhash", nil +} + func (mln *MockLn) MakeInvoice(ctx context.Context, senderPubkey string, amount int64, description string, descriptionHash string, expiry int64) (invoice string, paymentHash string, err error) { return mockInvoice, mockPaymentHash, nil }