Skip to content

Commit

Permalink
Merge pull request #172 from getAlby/task-get-info
Browse files Browse the repository at this point in the history
feat: add get info method
  • Loading branch information
rolznz authored Dec 13, 2023
2 parents cdfb14d + d751b03 commit 0b8c1a6
Show file tree
Hide file tree
Showing 9 changed files with 315 additions and 18 deletions.
16 changes: 12 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
DATABASE_URI=file:nwc.db
NOSTR_PRIVKEY=
LN_BACKEND_TYPE=ALBY
ALBY_CLIENT_SECRET=
ALBY_CLIENT_ID=
OAUTH_REDIRECT_URL=http://localhost:8080/alby/callback
COOKIE_SECRET=secretsecret
RELAY=wss://relay.getalby.com/v1
PUBLIC_RELAY=
PORT=8080

# Polar LND Client
#LN_BACKEND_TYPE=LND
#LND_CERT_FILE=/home/YOUR_USERNAME/.polar/networks/1/volumes/lnd/alice/tls.cert
#LND_ADDRESS=127.0.0.1:10001
#LND_MACAROON_FILE=/home/YOUR_USERNAME/.polar/networks/1/volumes/lnd/alice/data/chain/bitcoin/regtest/admin.macaroon

# Alby Wallet API Client
#LN_BACKEND_TYPE=ALBY
#ALBY_CLIENT_SECRET=
#ALBY_CLIENT_ID=
#OAUTH_REDIRECT_URL=http://localhost:8080/alby/callback
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.defaultFormatter": "golang.go"
}
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,60 @@ Want to support the work on Alby?

Support the Alby team ⚡️hello@getalby.com
You can also contribute to our [bounty program](https://github.com/getAlby/lightning-browser-extension/wiki/Bounties): ⚡️[email protected]


## NIP-47 Supported Methods

NIP-47 info event

### LND

`get_info`

`get_balance`

`pay_invoice`

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

⚠️ `lookup_invoice`
- ⚠️ invoice in response missing (TODO)
- ⚠️ response does not match spec, missing fields

`pay_keysend`

`list_transactions`

`multi_pay_invoice (TBC)`

`multi_pay_keysend (TBC)`

### Alby OAuth API

`get_info`
- ⚠️ block_hash not supported
- ⚠️ block_height not supported
- ⚠️ pubkey not supported
- ⚠️ color not supported
- ⚠️ network is always `mainnet`

`get_balance`

`pay_invoice`

⚠️ `make_invoice`
- ⚠️ expiry in request not supported
- ⚠️ invoice in response missing (TODO)

⚠️ `lookup_invoice`
- ⚠️ invoice in response missing (TODO)
- ⚠️ response does not match spec, missing fields (TODO)

`pay_keysend`

`list_transactions`

`multi_pay_invoice (TBC)`

`multi_pay_keysend (TBC)`
28 changes: 28 additions & 0 deletions alby.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ func (svc *AlbyOAuthService) MakeInvoice(ctx context.Context, senderPubkey strin
return "", "", err
}

// TODO: move to creation of HTTP client
req.Header.Set("User-Agent", "NWC")
req.Header.Set("Content-Type", "application/json")

Expand Down Expand Up @@ -269,6 +270,33 @@ func (svc *AlbyOAuthService) LookupInvoice(ctx context.Context, senderPubkey str
return "", false, errors.New(errorPayload.Message)
}

func (svc *AlbyOAuthService) GetInfo(ctx context.Context, senderPubkey string) (info *NodeInfo, 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,
}).Errorf("App not found: %v", err)
return nil, err
}

svc.Logger.WithFields(logrus.Fields{
"senderPubkey": senderPubkey,
"appId": app.ID,
"userId": app.User.ID,
}).Info("Info fetch successful")
return &NodeInfo{
Alias: "getalby.com",
Color: "",
Pubkey: "",
Network: "mainnet",
BlockHeight: 0,
BlockHash: "",
}, err
}

func (svc *AlbyOAuthService) GetBalance(ctx context.Context, senderPubkey string) (balance int64, err error) {
app := App{}
err = svc.db.Preload("User").First(&app, &App{
Expand Down
81 changes: 81 additions & 0 deletions handle_info_request.go
Original file line number Diff line number Diff line change
@@ -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: request.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")

info, 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: request.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: info.Alias,
Color: info.Color,
Pubkey: info.Pubkey,
Network: info.Network,
BlockHeight: info.BlockHeight,
BlockHash: info.BlockHash,
Methods: svc.GetMethods(&app),
}

nostrEvent.State = NOSTR_EVENT_STATE_HANDLER_EXECUTED
svc.db.Save(&nostrEvent)
return svc.createResponse(event, Nip47Response{
ResultType: request.Method,
Result: responsePayload,
}, ss)
}
16 changes: 16 additions & 0 deletions lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) (info *NodeInfo, 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)
}
Expand Down Expand Up @@ -51,6 +52,21 @@ 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) (info *NodeInfo, err error) {
resp, err := svc.client.GetInfo(ctx, &lnrpc.GetInfoRequest{})
if err != nil {
return nil, err
}
return &NodeInfo{
Alias: resp.Alias,
Color: resp.Color,
Pubkey: resp.IdentityPubkey,
Network: resp.Chains[0].Network,
BlockHeight: resp.BlockHeight,
BlockHash: 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

Expand Down
28 changes: 27 additions & 1 deletion models.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -23,7 +24,7 @@ const (
NIP_47_ERROR_EXPIRED = "EXPIRED"
NIP_47_ERROR_RESTRICTED = "RESTRICTED"
NIP_47_OTHER = "OTHER"
NIP_47_CAPABILITIES = "pay_invoice,get_balance"
NIP_47_CAPABILITIES = "pay_invoice,get_balance,get_info,make_invoice,lookup_invoice"
)

const (
Expand All @@ -36,18 +37,21 @@ 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",
}

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",
}

// TODO: move to models/Alby
type AlbyMe struct {
Identifier string `json:"identifier"`
NPub string `json:"nostr_pubkey"`
Expand Down Expand Up @@ -121,6 +125,7 @@ type PayRequest struct {
Invoice string `json:"invoice"`
}

// TODO: move to models/Alby
type BalanceResponse struct {
Balance int64 `json:"balance"`
Currency string `json:"currency"`
Expand Down Expand Up @@ -154,6 +159,16 @@ type ErrorResponse struct {
Message string `json:"message"`
}

// TODO: move to models/LNClient
type NodeInfo struct {
Alias string
Color string
Pubkey string
Network string
BlockHeight uint32
BlockHash string
}

type Identity struct {
gorm.Model
Privkey string
Expand Down Expand Up @@ -187,6 +202,17 @@ type Nip47BalanceResponse struct {
BudgetRenewal string `json:"budget_renewal"`
}

// TODO: move to models/Nip47
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"`
Methods []string `json:"methods"`
}

type Nip47MakeInvoiceParams struct {
Amount int64 `json:"amount"`
Description string `json:"description"`
Expand Down
20 changes: 20 additions & 0 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"strings"
"time"

"github.com/labstack/echo-contrib/session"
Expand All @@ -26,6 +27,7 @@ type Service struct {
var supportedMethods = map[string]bool{
NIP_47_PAY_INVOICE_METHOD: true,
NIP_47_GET_BALANCE_METHOD: true,
NIP_47_GET_INFO_METHOD: true,
NIP_47_MAKE_INVOICE_METHOD: true,
NIP_47_LOOKUP_INVOICE_METHOD: true,
}
Expand Down Expand Up @@ -207,6 +209,8 @@ func (svc *Service) HandleEvent(ctx context.Context, event *nostr.Event) (result
return svc.HandleMakeInvoiceEvent(ctx, nip47Request, event, app, ss)
case NIP_47_LOOKUP_INVOICE_METHOD:
return svc.HandleLookupInvoiceEvent(ctx, nip47Request, event, app, ss)
case NIP_47_GET_INFO_METHOD:
return svc.HandleGetInfoEvent(ctx, nip47Request, event, app, ss)
default:
return svc.createResponse(event, Nip47Response{
ResultType: nip47Request.Method,
Expand Down Expand Up @@ -240,6 +244,22 @@ func (svc *Service) createResponse(initialEvent *nostr.Event, content interface{
return resp, nil
}

func (svc *Service) GetMethods(app *App) []string {
appPermissions := []AppPermission{}
findPermissionsResult := svc.db.Find(&appPermissions, &AppPermission{
AppId: app.ID,
})
if findPermissionsResult.RowsAffected == 0 {
// No permissions created for this app. It can do anything
return strings.Split(NIP_47_CAPABILITIES, ",")
}
requestMethods := make([]string, 0, len(appPermissions))
for _, appPermission := range appPermissions {
requestMethods = append(requestMethods, appPermission.RequestMethod)
}
return requestMethods
}

func (svc *Service) hasPermission(app *App, event *nostr.Event, requestMethod string, paymentRequest *decodepay.Bolt11) (result bool, code string, message string) {
// find all permissions for the app
appPermissions := []AppPermission{}
Expand Down
Loading

0 comments on commit 0b8c1a6

Please sign in to comment.