Refactor: API cleanup (#338)

This commit is contained in:
Roland 2024-05-30 00:06:06 +07:00 committed by GitHub
parent a05aafb22b
commit 073d669e03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
62 changed files with 3106 additions and 2808 deletions

View file

@ -4,21 +4,22 @@ import (
"fmt"
"net/http"
"github.com/getAlby/nostr-wallet-connect/models/api"
models "github.com/getAlby/nostr-wallet-connect/models/http"
"github.com/getAlby/nostr-wallet-connect/config"
"github.com/labstack/echo/v4"
"github.com/sirupsen/logrus"
)
type AlbyHttpService struct {
albyOAuthSvc *AlbyOAuthService
albyOAuthSvc AlbyOAuthService
logger *logrus.Logger
appConfig *config.AppConfig
}
func NewAlbyHttpService(albyOAuthSvc *AlbyOAuthService, logger *logrus.Logger) *AlbyHttpService {
func NewAlbyHttpService(albyOAuthSvc AlbyOAuthService, logger *logrus.Logger, appConfig *config.AppConfig) *AlbyHttpService {
return &AlbyHttpService{
albyOAuthSvc: albyOAuthSvc,
logger: logger,
appConfig: appConfig,
}
}
@ -36,20 +37,20 @@ func (albyHttpSvc *AlbyHttpService) albyCallbackHandler(c echo.Context) error {
err := albyHttpSvc.albyOAuthSvc.CallbackHandler(c.Request().Context(), code)
if err != nil {
albyHttpSvc.logger.WithError(err).Error("Failed to handle Alby OAuth callback")
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to handle Alby OAuth callback: %s", err.Error()),
})
}
if albyHttpSvc.albyOAuthSvc.appConfig.IsDefaultClientId() {
if albyHttpSvc.appConfig.IsDefaultClientId() {
// do not redirect if using default OAuth client
// redirect will be handled by the frontend instead
return c.NoContent(http.StatusNoContent)
}
redirectUrl := albyHttpSvc.albyOAuthSvc.appConfig.FrontendUrl
redirectUrl := albyHttpSvc.appConfig.FrontendUrl
if redirectUrl == "" {
redirectUrl = albyHttpSvc.albyOAuthSvc.appConfig.BaseUrl
redirectUrl = albyHttpSvc.appConfig.BaseUrl
}
return c.Redirect(http.StatusFound, redirectUrl)
@ -59,7 +60,7 @@ func (albyHttpSvc *AlbyHttpService) albyMeHandler(c echo.Context) error {
me, err := albyHttpSvc.albyOAuthSvc.GetMe(c.Request().Context())
if err != nil {
albyHttpSvc.logger.WithError(err).Error("Failed to request alby me endpoint")
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to request alby me endpoint: %s", err.Error()),
})
}
@ -71,20 +72,20 @@ func (albyHttpSvc *AlbyHttpService) albyBalanceHandler(c echo.Context) error {
balance, err := albyHttpSvc.albyOAuthSvc.GetBalance(c.Request().Context())
if err != nil {
albyHttpSvc.logger.WithError(err).Error("Failed to request alby balance endpoint")
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to request alby balance endpoint: %s", err.Error()),
})
}
return c.JSON(http.StatusOK, &api.AlbyBalanceResponse{
return c.JSON(http.StatusOK, &AlbyBalanceResponse{
Sats: balance.Balance,
})
}
func (albyHttpSvc *AlbyHttpService) albyPayHandler(c echo.Context) error {
var payRequest api.AlbyPayRequest
var payRequest AlbyPayRequest
if err := c.Bind(&payRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
@ -92,7 +93,7 @@ func (albyHttpSvc *AlbyHttpService) albyPayHandler(c echo.Context) error {
err := albyHttpSvc.albyOAuthSvc.SendPayment(c.Request().Context(), payRequest.Invoice)
if err != nil {
albyHttpSvc.logger.WithError(err).Error("Failed to request alby pay endpoint")
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to request alby pay endpoint: %s", err.Error()),
})
}

View file

@ -8,52 +8,24 @@ import (
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"time"
"github.com/getAlby/nostr-wallet-connect/config"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/events"
"github.com/getAlby/nostr-wallet-connect/models/api"
"github.com/getAlby/nostr-wallet-connect/models/config"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/sirupsen/logrus"
"golang.org/x/oauth2"
)
type AlbyOAuthService struct {
type albyOAuthService struct {
appConfig *config.AppConfig
config config.Config
oauthConf *oauth2.Config
logger *logrus.Logger
api api.API
}
// TODO: move to models/alby
type AlbyMe struct {
Identifier string `json:"identifier"`
NPub string `json:"nostr_pubkey"`
LightningAddress string `json:"lightning_address"`
Email string `json:"email"`
Name string `json:"name"`
Avatar string `json:"avatar"`
KeysendPubkey string `json:"keysend_pubkey"`
SharedNode bool `json:"shared_node"`
}
type AlbyBalance struct {
Balance int64 `json:"balance"`
Unit string `json:"unit"`
Currency string `json:"currency"`
}
type ChannelPeerSuggestion struct {
Network string `json:"network"`
PaymentMethod string `json:"paymentMethod"`
Pubkey string `json:"pubkey"`
Host string `json:"host"`
MinimumChannelSize uint64 `json:"minimumChannelSize"`
Name string `json:"name"`
Image string `json:"image"`
Lsp string `json:"lsp"`
dbSvc db.DBService
}
const (
@ -63,7 +35,7 @@ const (
userIdentifierKey = "AlbyUserIdentifier"
)
func NewAlbyOAuthService(logger *logrus.Logger, kvStore config.Config, appConfig *config.AppConfig, api api.API) *AlbyOAuthService {
func NewAlbyOAuthService(logger *logrus.Logger, config config.Config, appConfig *config.AppConfig, dbSvc db.DBService) *albyOAuthService {
conf := &oauth2.Config{
ClientID: appConfig.AlbyClientId,
ClientSecret: appConfig.AlbyClientSecret,
@ -81,17 +53,17 @@ func NewAlbyOAuthService(logger *logrus.Logger, kvStore config.Config, appConfig
conf.RedirectURL = appConfig.BaseUrl + "/api/alby/callback"
}
albyOAuthSvc := &AlbyOAuthService{
albyOAuthSvc := &albyOAuthService{
appConfig: appConfig,
oauthConf: conf,
config: kvStore,
config: config,
logger: logger,
api: api,
dbSvc: dbSvc,
}
return albyOAuthSvc
}
func (svc *AlbyOAuthService) CallbackHandler(ctx context.Context, code string) error {
func (svc *albyOAuthService) CallbackHandler(ctx context.Context, code string) error {
token, err := svc.oauthConf.Exchange(ctx, code)
if err != nil {
svc.logger.WithError(err).Error("Failed to exchange token")
@ -122,7 +94,7 @@ func (svc *AlbyOAuthService) CallbackHandler(ctx context.Context, code string) e
return nil
}
func (svc *AlbyOAuthService) GetUserIdentifier() (string, error) {
func (svc *albyOAuthService) GetUserIdentifier() (string, error) {
userIdentifier, err := svc.config.Get(userIdentifierKey, "")
if err != nil {
svc.logger.WithError(err).Error("Failed to fetch user identifier from user configs")
@ -131,7 +103,7 @@ func (svc *AlbyOAuthService) GetUserIdentifier() (string, error) {
return userIdentifier, nil
}
func (svc *AlbyOAuthService) IsConnected(ctx context.Context) bool {
func (svc *albyOAuthService) IsConnected(ctx context.Context) bool {
token, err := svc.fetchUserToken(ctx)
if err != nil {
svc.logger.WithError(err).Error("Failed to check fetch token")
@ -139,7 +111,7 @@ func (svc *AlbyOAuthService) IsConnected(ctx context.Context) bool {
return token != nil
}
func (svc *AlbyOAuthService) saveToken(token *oauth2.Token) {
func (svc *albyOAuthService) saveToken(token *oauth2.Token) {
svc.config.SetUpdate(accessTokenExpiryKey, strconv.FormatInt(token.Expiry.Unix(), 10), "")
svc.config.SetUpdate(accessTokenKey, token.AccessToken, "")
svc.config.SetUpdate(refreshTokenKey, token.RefreshToken, "")
@ -147,7 +119,7 @@ func (svc *AlbyOAuthService) saveToken(token *oauth2.Token) {
var tokenMutex sync.Mutex
func (svc *AlbyOAuthService) fetchUserToken(ctx context.Context) (*oauth2.Token, error) {
func (svc *albyOAuthService) fetchUserToken(ctx context.Context) (*oauth2.Token, error) {
tokenMutex.Lock()
defer tokenMutex.Unlock()
accessToken, err := svc.config.Get(accessTokenKey, "")
@ -202,8 +174,7 @@ func (svc *AlbyOAuthService) fetchUserToken(ctx context.Context) (*oauth2.Token,
return newToken, nil
}
func (svc *AlbyOAuthService) GetMe(ctx context.Context) (*AlbyMe, error) {
func (svc *albyOAuthService) GetMe(ctx context.Context) (*AlbyMe, error) {
token, err := svc.fetchUserToken(ctx)
if err != nil {
svc.logger.WithError(err).Error("Failed to fetch user token")
@ -237,7 +208,7 @@ func (svc *AlbyOAuthService) GetMe(ctx context.Context) (*AlbyMe, error) {
return me, nil
}
func (svc *AlbyOAuthService) GetBalance(ctx context.Context) (*AlbyBalance, error) {
func (svc *albyOAuthService) GetBalance(ctx context.Context) (*AlbyBalance, error) {
token, err := svc.fetchUserToken(ctx)
if err != nil {
@ -271,7 +242,7 @@ func (svc *AlbyOAuthService) GetBalance(ctx context.Context) (*AlbyBalance, erro
return balance, nil
}
func (svc *AlbyOAuthService) SendPayment(ctx context.Context, invoice string) error {
func (svc *albyOAuthService) SendPayment(ctx context.Context, invoice string) error {
token, err := svc.fetchUserToken(ctx)
if err != nil {
svc.logger.WithError(err).Error("Failed to fetch user token")
@ -356,27 +327,28 @@ func (svc *AlbyOAuthService) SendPayment(ctx context.Context, invoice string) er
return nil
}
func (svc *AlbyOAuthService) GetAuthUrl() string {
func (svc *albyOAuthService) GetAuthUrl() string {
if svc.appConfig.AlbyClientId == "" || svc.appConfig.AlbyClientSecret == "" {
svc.logger.Fatalf("No ALBY_OAUTH_CLIENT_ID or ALBY_OAUTH_CLIENT_SECRET set")
}
return svc.oauthConf.AuthCodeURL("unused")
}
func (svc *AlbyOAuthService) LinkAccount(ctx context.Context) error {
func (svc *albyOAuthService) LinkAccount(ctx context.Context) error {
connectionPubkey, err := svc.createAlbyAccountNWCNode(ctx)
if err != nil {
svc.logger.WithError(err).Error("Failed to create alby account nwc node")
return err
}
app, err := svc.api.CreateApp(&api.CreateAppRequest{
Name: "getalby.com",
Pubkey: connectionPubkey,
MaxAmount: 1_000_000,
BudgetRenewal: nip47.BUDGET_RENEWAL_MONTHLY,
RequestMethods: nip47.CAPABILITIES,
})
app, _, err := svc.dbSvc.CreateApp(
"getalby.com",
connectionPubkey,
1_000_000,
nip47.BUDGET_RENEWAL_MONTHLY,
nil,
strings.Split(nip47.CAPABILITIES, " "),
)
if err != nil {
svc.logger.WithError(err).Error("Failed to create app connection")
@ -396,7 +368,7 @@ func (svc *AlbyOAuthService) LinkAccount(ctx context.Context) error {
return nil
}
func (svc *AlbyOAuthService) ConsumeEvent(ctx context.Context, event *events.Event, globalProperties map[string]interface{}) error {
func (svc *albyOAuthService) ConsumeEvent(ctx context.Context, event *events.Event, globalProperties map[string]interface{}) error {
// TODO: rename this config option to be specific to the alby API
if !svc.appConfig.LogEvents {
svc.logger.WithField("event", event).Debug("Skipped sending to alby events API")
@ -481,7 +453,7 @@ func (svc *AlbyOAuthService) ConsumeEvent(ctx context.Context, event *events.Eve
return nil
}
func (svc *AlbyOAuthService) createAlbyAccountNWCNode(ctx context.Context) (string, error) {
func (svc *albyOAuthService) createAlbyAccountNWCNode(ctx context.Context) (string, error) {
token, err := svc.fetchUserToken(ctx)
if err != nil {
svc.logger.WithError(err).Error("Failed to fetch user token")
@ -548,7 +520,7 @@ func (svc *AlbyOAuthService) createAlbyAccountNWCNode(ctx context.Context) (stri
return responsePayload.Pubkey, nil
}
func (svc *AlbyOAuthService) activateAlbyAccountNWCNode(ctx context.Context) error {
func (svc *albyOAuthService) activateAlbyAccountNWCNode(ctx context.Context) error {
token, err := svc.fetchUserToken(ctx)
if err != nil {
svc.logger.WithError(err).Error("Failed to fetch user token")
@ -583,7 +555,7 @@ func (svc *AlbyOAuthService) activateAlbyAccountNWCNode(ctx context.Context) err
return nil
}
func (svc *AlbyOAuthService) GetChannelPeerSuggestions(ctx context.Context) ([]ChannelPeerSuggestion, error) {
func (svc *albyOAuthService) GetChannelPeerSuggestions(ctx context.Context) ([]ChannelPeerSuggestion, error) {
token, err := svc.fetchUserToken(ctx)
if err != nil {

60
alby/models.go Normal file
View file

@ -0,0 +1,60 @@
package alby
import (
"context"
"github.com/getAlby/nostr-wallet-connect/events"
)
type AlbyOAuthService interface {
events.EventSubscriber
GetChannelPeerSuggestions(ctx context.Context) ([]ChannelPeerSuggestion, error)
GetAuthUrl() string
GetUserIdentifier() (string, error)
IsConnected(ctx context.Context) bool
LinkAccount(ctx context.Context) error
CallbackHandler(ctx context.Context, code string) error
GetBalance(ctx context.Context) (*AlbyBalance, error)
GetMe(ctx context.Context) (*AlbyMe, error)
SendPayment(ctx context.Context, invoice string) error
}
type AlbyBalanceResponse struct {
Sats int64 `json:"sats"`
}
type AlbyPayRequest struct {
Invoice string `json:"invoice"`
}
type AlbyMe struct {
Identifier string `json:"identifier"`
NPub string `json:"nostr_pubkey"`
LightningAddress string `json:"lightning_address"`
Email string `json:"email"`
Name string `json:"name"`
Avatar string `json:"avatar"`
KeysendPubkey string `json:"keysend_pubkey"`
SharedNode bool `json:"shared_node"`
}
type AlbyBalance struct {
Balance int64 `json:"balance"`
Unit string `json:"unit"`
Currency string `json:"currency"`
}
type ChannelPeerSuggestion struct {
Network string `json:"network"`
PaymentMethod string `json:"paymentMethod"`
Pubkey string `json:"pubkey"`
Host string `json:"host"`
MinimumChannelSize uint64 `json:"minimumChannelSize"`
Name string `json:"name"`
Image string `json:"image"`
Lsp string `json:"lsp"`
}
type ErrorResponse struct {
Message string `json:"message"`
}

1552
api.go

File diff suppressed because it is too large Load diff

643
api/api.go Normal file
View file

@ -0,0 +1,643 @@
package api
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
"github.com/getAlby/nostr-wallet-connect/alby"
"github.com/getAlby/nostr-wallet-connect/backup"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/lnclient"
"github.com/getAlby/nostr-wallet-connect/lsp"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/getAlby/nostr-wallet-connect/service"
"github.com/getAlby/nostr-wallet-connect/utils"
)
type api struct {
logger *logrus.Logger
svc service.Service
lspSvc lsp.LSPService
backupSvc backup.BackupService
db *gorm.DB
dbSvc db.DBService
}
func NewAPI(svc service.Service, logger *logrus.Logger, gormDb *gorm.DB) *api {
return &api{
svc: svc,
logger: logger,
db: gormDb,
dbSvc: db.NewDBService(gormDb, logger),
lspSvc: lsp.NewLSPService(svc, logger),
backupSvc: backup.NewBackupService(svc, logger),
}
}
func (api *api) CreateApp(createAppRequest *CreateAppRequest) (*CreateAppResponse, error) {
expiresAt, err := api.parseExpiresAt(createAppRequest.ExpiresAt)
if err != nil {
return nil, fmt.Errorf("invalid expiresAt: %v", err)
}
// request methods are a space separated list of known request kinds TODO: it should be a string array in the API
requestMethods := strings.Split(createAppRequest.RequestMethods, " ")
if len(requestMethods) == 0 {
return nil, fmt.Errorf("won't create an app without request methods")
}
app, pairingSecretKey, err := api.dbSvc.CreateApp(createAppRequest.Name, createAppRequest.Pubkey, createAppRequest.MaxAmount, createAppRequest.BudgetRenewal, expiresAt, requestMethods)
if err != nil {
return nil, err
}
relayUrl := api.svc.GetConfig().GetRelayUrl()
responseBody := &CreateAppResponse{}
responseBody.Name = createAppRequest.Name
responseBody.Pubkey = app.NostrPubkey
responseBody.PairingSecret = pairingSecretKey
if createAppRequest.ReturnTo != "" {
returnToUrl, err := url.Parse(createAppRequest.ReturnTo)
if err == nil {
query := returnToUrl.Query()
query.Add("relay", relayUrl)
query.Add("pubkey", api.svc.GetConfig().GetNostrPublicKey())
// if user.LightningAddress != "" {
// query.Add("lud16", user.LightningAddress)
// }
returnToUrl.RawQuery = query.Encode()
responseBody.ReturnTo = returnToUrl.String()
}
}
var lud16 string
// if user.LightningAddress != "" {
// lud16 = fmt.Sprintf("&lud16=%s", user.LightningAddress)
// }
responseBody.PairingUri = fmt.Sprintf("nostr+walletconnect://%s?relay=%s&secret=%s%s", api.svc.GetConfig().GetNostrPublicKey(), relayUrl, pairingSecretKey, lud16)
return responseBody, nil
}
func (api *api) UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) error {
maxAmount := updateAppRequest.MaxAmount
budgetRenewal := updateAppRequest.BudgetRenewal
requestMethods := updateAppRequest.RequestMethods
if requestMethods == "" {
return fmt.Errorf("won't update an app to have no request methods")
}
newRequestMethods := strings.Split(requestMethods, " ")
expiresAt, err := api.parseExpiresAt(updateAppRequest.ExpiresAt)
if err != nil {
return fmt.Errorf("invalid expiresAt: %v", err)
}
err = api.db.Transaction(func(tx *gorm.DB) error {
// Update existing permissions with new budget and expiry
err := tx.Model(&db.AppPermission{}).Where("app_id", userApp.ID).Updates(map[string]interface{}{
"ExpiresAt": expiresAt,
"MaxAmount": maxAmount,
"BudgetRenewal": budgetRenewal,
}).Error
if err != nil {
return err
}
var existingPermissions []db.AppPermission
if err := tx.Where("app_id = ?", userApp.ID).Find(&existingPermissions).Error; err != nil {
return err
}
existingMethodMap := make(map[string]bool)
for _, perm := range existingPermissions {
existingMethodMap[perm.RequestMethod] = true
}
// Add new permissions
for _, method := range newRequestMethods {
if !existingMethodMap[method] {
perm := db.AppPermission{
App: *userApp,
RequestMethod: method,
ExpiresAt: expiresAt,
MaxAmount: maxAmount,
BudgetRenewal: budgetRenewal,
}
if err := tx.Create(&perm).Error; err != nil {
return err
}
}
delete(existingMethodMap, method)
}
// Remove old permissions
for method := range existingMethodMap {
if err := tx.Where("app_id = ? AND request_method = ?", userApp.ID, method).Delete(&db.AppPermission{}).Error; err != nil {
return err
}
}
// commit transaction
return nil
})
return err
}
func (api *api) DeleteApp(userApp *db.App) error {
return api.db.Delete(userApp).Error
}
func (api *api) GetApp(userApp *db.App) *App {
var lastEvent db.RequestEvent
lastEventResult := api.db.Where("app_id = ?", userApp.ID).Order("id desc").Limit(1).Find(&lastEvent)
paySpecificPermission := db.AppPermission{}
appPermissions := []db.AppPermission{}
var expiresAt *time.Time
api.db.Where("app_id = ?", userApp.ID).Find(&appPermissions)
requestMethods := []string{}
for _, appPerm := range appPermissions {
expiresAt = appPerm.ExpiresAt
if appPerm.RequestMethod == nip47.PAY_INVOICE_METHOD {
//find the pay_invoice-specific permissions
paySpecificPermission = appPerm
}
requestMethods = append(requestMethods, appPerm.RequestMethod)
}
//renewsIn := ""
budgetUsage := int64(0)
maxAmount := paySpecificPermission.MaxAmount
if maxAmount > 0 {
budgetUsage = api.svc.GetBudgetUsage(&paySpecificPermission)
}
response := App{
Name: userApp.Name,
Description: userApp.Description,
CreatedAt: userApp.CreatedAt,
UpdatedAt: userApp.UpdatedAt,
NostrPubkey: userApp.NostrPubkey,
ExpiresAt: expiresAt,
MaxAmount: maxAmount,
RequestMethods: requestMethods,
BudgetUsage: budgetUsage,
BudgetRenewal: paySpecificPermission.BudgetRenewal,
}
if lastEventResult.RowsAffected > 0 {
response.LastEventAt = &lastEvent.CreatedAt
}
return &response
}
func (api *api) ListApps() ([]App, error) {
// TODO: join dbApps and permissions
dbApps := []db.App{}
api.db.Find(&dbApps)
permissions := []db.AppPermission{}
api.db.Find(&permissions)
permissionsMap := make(map[uint][]db.AppPermission)
for _, perm := range permissions {
permissionsMap[perm.AppId] = append(permissionsMap[perm.AppId], perm)
}
apiApps := []App{}
for _, userApp := range dbApps {
apiApp := App{
// ID: app.ID,
Name: userApp.Name,
Description: userApp.Description,
CreatedAt: userApp.CreatedAt,
UpdatedAt: userApp.UpdatedAt,
NostrPubkey: userApp.NostrPubkey,
}
for _, permission := range permissionsMap[userApp.ID] {
apiApp.RequestMethods = append(apiApp.RequestMethods, permission.RequestMethod)
apiApp.ExpiresAt = permission.ExpiresAt
if permission.RequestMethod == nip47.PAY_INVOICE_METHOD {
apiApp.BudgetRenewal = permission.BudgetRenewal
apiApp.MaxAmount = permission.MaxAmount
if apiApp.MaxAmount > 0 {
apiApp.BudgetUsage = api.svc.GetBudgetUsage(&permission)
}
}
}
var lastEvent db.RequestEvent
lastEventResult := api.db.Where("app_id = ?", userApp.ID).Order("id desc").Limit(1).Find(&lastEvent)
if lastEventResult.RowsAffected > 0 {
apiApp.LastEventAt = &lastEvent.CreatedAt
}
apiApps = append(apiApps, apiApp)
}
return apiApps, nil
}
func (api *api) ListChannels(ctx context.Context) ([]lnclient.Channel, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
return api.svc.GetLNClient().ListChannels(ctx)
}
func (api *api) GetChannelPeerSuggestions(ctx context.Context) ([]alby.ChannelPeerSuggestion, error) {
return api.svc.GetAlbyOAuthSvc().GetChannelPeerSuggestions(ctx)
}
func (api *api) ResetRouter(key string) error {
if api.svc.GetLNClient() == nil {
return errors.New("LNClient not started")
}
err := api.svc.GetLNClient().ResetRouter(key)
if err != nil {
return err
}
// Because the above method has to stop the node to reset the router,
// We also need to stop the lnclient and ask the user to start it again
return api.Stop()
}
func (api *api) ChangeUnlockPassword(changeUnlockPasswordRequest *ChangeUnlockPasswordRequest) error {
if api.svc.GetLNClient() == nil {
return errors.New("LNClient not started")
}
err := api.svc.GetConfig().ChangeUnlockPassword(changeUnlockPasswordRequest.CurrentUnlockPassword, changeUnlockPasswordRequest.NewUnlockPassword)
if err != nil {
api.logger.WithError(err).Error("failed to change unlock password")
return err
}
// Because all the encrypted fields have changed
// we also need to stop the lnclient and ask the user to start it again
return api.Stop()
}
func (api *api) Stop() error {
api.logger.Info("Running Stop command")
if api.svc.GetLNClient() == nil {
return errors.New("LNClient not started")
}
// stop the lnclient
// The user will be forced to re-enter their unlock password to restart the node
err := api.svc.StopLNClient()
if err != nil {
api.logger.WithError(err).Error("Failed to stop LNClient")
}
return err
}
func (api *api) GetNodeConnectionInfo(ctx context.Context) (*lnclient.NodeConnectionInfo, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
return api.svc.GetLNClient().GetNodeConnectionInfo(ctx)
}
func (api *api) GetNodeStatus(ctx context.Context) (*lnclient.NodeStatus, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
return api.svc.GetLNClient().GetNodeStatus(ctx)
}
func (api *api) ListPeers(ctx context.Context) ([]lnclient.PeerDetails, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
return api.svc.GetLNClient().ListPeers(ctx)
}
func (api *api) ConnectPeer(ctx context.Context, connectPeerRequest *ConnectPeerRequest) error {
if api.svc.GetLNClient() == nil {
return errors.New("LNClient not started")
}
return api.svc.GetLNClient().ConnectPeer(ctx, connectPeerRequest)
}
func (api *api) OpenChannel(ctx context.Context, openChannelRequest *OpenChannelRequest) (*OpenChannelResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
return api.svc.GetLNClient().OpenChannel(ctx, openChannelRequest)
}
func (api *api) CloseChannel(ctx context.Context, peerId, channelId string, force bool) (*CloseChannelResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
api.logger.WithFields(logrus.Fields{
"peer_id": peerId,
"channel_id": channelId,
"force": force,
}).Info("Closing channel")
return api.svc.GetLNClient().CloseChannel(ctx, &lnclient.CloseChannelRequest{
NodeId: peerId,
ChannelId: channelId,
Force: force,
})
}
func (api *api) GetNewOnchainAddress(ctx context.Context) (*NewOnchainAddressResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
address, err := api.svc.GetLNClient().GetNewOnchainAddress(ctx)
if err != nil {
return nil, err
}
return &NewOnchainAddressResponse{
Address: address,
}, nil
}
func (api *api) SignMessage(ctx context.Context, message string) (*SignMessageResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
signature, err := api.svc.GetLNClient().SignMessage(ctx, message)
if err != nil {
return nil, err
}
return &SignMessageResponse{
Message: message,
Signature: signature,
}, nil
}
func (api *api) RedeemOnchainFunds(ctx context.Context, toAddress string) (*RedeemOnchainFundsResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
txId, err := api.svc.GetLNClient().RedeemOnchainFunds(ctx, toAddress)
if err != nil {
return nil, err
}
return &RedeemOnchainFundsResponse{
TxId: txId,
}, nil
}
func (api *api) GetBalances(ctx context.Context) (*BalancesResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
balances, err := api.svc.GetLNClient().GetBalances(ctx)
if err != nil {
return nil, err
}
return balances, nil
}
func (api *api) RequestMempoolApi(endpoint string) (interface{}, error) {
url := api.svc.GetConfig().GetEnv().MempoolApi + endpoint
client := http.Client{
Timeout: time.Second * 10,
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
api.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to create http request")
return nil, err
}
res, err := client.Do(req)
if err != nil {
api.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to send request")
return nil, err
}
defer res.Body.Close()
body, readErr := io.ReadAll(res.Body)
if readErr != nil {
api.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to read response body")
return nil, errors.New("failed to read response body")
}
var jsonContent interface{}
jsonErr := json.Unmarshal(body, &jsonContent)
if jsonErr != nil {
api.logger.WithError(jsonErr).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to deserialize json")
return nil, fmt.Errorf("failed to deserialize json %s %s", url, string(body))
}
return jsonContent, nil
}
func (api *api) GetInfo(ctx context.Context) (*InfoResponse, error) {
info := InfoResponse{}
backendType, _ := api.svc.GetConfig().Get("LNBackendType", "")
unlockPasswordCheck, _ := api.svc.GetConfig().Get("UnlockPasswordCheck", "")
info.SetupCompleted = unlockPasswordCheck != ""
info.Running = api.svc.GetLNClient() != nil
info.BackendType = backendType
info.AlbyAuthUrl = api.svc.GetAlbyOAuthSvc().GetAuthUrl()
info.OAuthRedirect = !api.svc.GetConfig().GetEnv().IsDefaultClientId()
albyUserIdentifier, err := api.svc.GetAlbyOAuthSvc().GetUserIdentifier()
if err != nil {
api.logger.WithError(err).Error("Failed to get alby user identifier")
return nil, err
}
info.AlbyUserIdentifier = albyUserIdentifier
info.AlbyAccountConnected = api.svc.GetAlbyOAuthSvc().IsConnected(ctx)
if api.svc.GetLNClient() != nil {
nodeInfo, err := api.svc.GetLNClient().GetInfo(ctx)
if err != nil {
api.logger.WithError(err).Error("Failed to get nodeInfo")
return nil, err
}
info.Network = nodeInfo.Network
}
info.NextBackupReminder, _ = api.svc.GetConfig().Get("NextBackupReminder", "")
return &info, nil
}
func (api *api) GetEncryptedMnemonic() *EncryptedMnemonicResponse {
resp := EncryptedMnemonicResponse{}
mnemonic, _ := api.svc.GetConfig().Get("Mnemonic", "")
resp.Mnemonic = mnemonic
return &resp
}
func (api *api) SetNextBackupReminder(backupReminderRequest *BackupReminderRequest) error {
api.svc.GetConfig().SetUpdate("NextBackupReminder", backupReminderRequest.NextBackupReminder, "")
return nil
}
func (api *api) Start(startRequest *StartRequest) error {
return api.svc.StartApp(startRequest.UnlockPassword)
}
func (api *api) Setup(ctx context.Context, setupRequest *SetupRequest) error {
info, err := api.GetInfo(ctx)
if err != nil {
api.logger.WithError(err).Error("Failed to get info")
return err
}
if info.SetupCompleted {
api.logger.Error("Cannot re-setup node")
return errors.New("setup already completed")
}
api.svc.GetConfig().Setup(setupRequest.UnlockPassword)
// TODO: move all below code to cfg.Setup()
// update next backup reminder
api.svc.GetConfig().SetUpdate("NextBackupReminder", setupRequest.NextBackupReminder, "")
// only update non-empty values
if setupRequest.LNBackendType != "" {
api.svc.GetConfig().SetUpdate("LNBackendType", setupRequest.LNBackendType, "")
}
if setupRequest.BreezAPIKey != "" {
api.svc.GetConfig().SetUpdate("BreezAPIKey", setupRequest.BreezAPIKey, setupRequest.UnlockPassword)
}
if setupRequest.Mnemonic != "" {
api.svc.GetConfig().SetUpdate("Mnemonic", setupRequest.Mnemonic, setupRequest.UnlockPassword)
}
if setupRequest.GreenlightInviteCode != "" {
api.svc.GetConfig().SetUpdate("GreenlightInviteCode", setupRequest.GreenlightInviteCode, setupRequest.UnlockPassword)
}
if setupRequest.LNDAddress != "" {
api.svc.GetConfig().SetUpdate("LNDAddress", setupRequest.LNDAddress, setupRequest.UnlockPassword)
}
if setupRequest.LNDCertHex != "" {
api.svc.GetConfig().SetUpdate("LNDCertHex", setupRequest.LNDCertHex, setupRequest.UnlockPassword)
}
if setupRequest.LNDMacaroonHex != "" {
api.svc.GetConfig().SetUpdate("LNDMacaroonHex", setupRequest.LNDMacaroonHex, setupRequest.UnlockPassword)
}
return nil
}
func (api *api) SendPaymentProbes(ctx context.Context, sendPaymentProbesRequest *SendPaymentProbesRequest) (*SendPaymentProbesResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
var errMessage string
err := api.svc.GetLNClient().SendPaymentProbes(ctx, sendPaymentProbesRequest.Invoice)
if err != nil {
errMessage = err.Error()
}
return &SendPaymentProbesResponse{Error: errMessage}, nil
}
func (api *api) SendSpontaneousPaymentProbes(ctx context.Context, sendSpontaneousPaymentProbesRequest *SendSpontaneousPaymentProbesRequest) (*SendSpontaneousPaymentProbesResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
var errMessage string
err := api.svc.GetLNClient().SendSpontaneousPaymentProbes(ctx, sendSpontaneousPaymentProbesRequest.Amount, sendSpontaneousPaymentProbesRequest.NodeId)
if err != nil {
errMessage = err.Error()
}
return &SendSpontaneousPaymentProbesResponse{Error: errMessage}, nil
}
func (api *api) GetNetworkGraph(nodeIds []string) (NetworkGraphResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
return api.svc.GetLNClient().GetNetworkGraph(nodeIds)
}
func (api *api) SyncWallet() error {
if api.svc.GetLNClient() == nil {
return errors.New("LNClient not started")
}
api.svc.GetLNClient().UpdateLastWalletSyncRequest()
return nil
}
func (api *api) GetLogOutput(ctx context.Context, logType string, getLogRequest *GetLogOutputRequest) (*GetLogOutputResponse, error) {
var err error
var logData []byte
if logType == LogTypeNode {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
logData, err = api.svc.GetLNClient().GetLogOutput(ctx, getLogRequest.MaxLen)
if err != nil {
return nil, err
}
} else if logType == LogTypeApp {
logFileName := api.svc.GetLogFilePath()
logData, err = utils.ReadFileTail(logFileName, getLogRequest.MaxLen)
if err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("invalid log type: '%s'", logType)
}
return &GetLogOutputResponse{Log: string(logData)}, nil
}
func (api *api) parseExpiresAt(expiresAtString string) (*time.Time, error) {
var expiresAt *time.Time
if expiresAtString != "" {
var err error
expiresAtValue, err := time.Parse(time.RFC3339, expiresAtString)
if err != nil {
api.logger.WithField("expiresAt", expiresAtString).Error("Invalid expiresAt")
return nil, fmt.Errorf("invalid expiresAt: %v", err)
}
expiresAtValue = time.Date(expiresAtValue.Year(), expiresAtValue.Month(), expiresAtValue.Day(), 23, 59, 59, 0, expiresAtValue.Location())
expiresAt = &expiresAtValue
}
return expiresAt, nil
}
func (api *api) GetLSPService() lsp.LSPService {
return api.lspSvc
}
func (api *api) GetBackupService() backup.BackupService {
return api.backupSvc
}

View file

@ -1,14 +1,50 @@
// TODO: move to api/models.go
package api
import (
"context"
"time"
"github.com/getAlby/nostr-wallet-connect/models/lnclient"
"github.com/getAlby/nostr-wallet-connect/alby"
"github.com/getAlby/nostr-wallet-connect/backup"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/lnclient"
"github.com/getAlby/nostr-wallet-connect/lsp"
)
type API interface {
CreateApp(createAppRequest *CreateAppRequest) (*CreateAppResponse, error)
UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) error
DeleteApp(userApp *db.App) error
GetApp(userApp *db.App) *App
ListApps() ([]App, error)
ListChannels(ctx context.Context) ([]lnclient.Channel, error)
GetChannelPeerSuggestions(ctx context.Context) ([]alby.ChannelPeerSuggestion, error)
ResetRouter(key string) error
ChangeUnlockPassword(changeUnlockPasswordRequest *ChangeUnlockPasswordRequest) error
Stop() error
GetNodeConnectionInfo(ctx context.Context) (*lnclient.NodeConnectionInfo, error)
GetNodeStatus(ctx context.Context) (*lnclient.NodeStatus, error)
ListPeers(ctx context.Context) ([]lnclient.PeerDetails, error)
ConnectPeer(ctx context.Context, connectPeerRequest *ConnectPeerRequest) error
OpenChannel(ctx context.Context, openChannelRequest *OpenChannelRequest) (*OpenChannelResponse, error)
CloseChannel(ctx context.Context, peerId, channelId string, force bool) (*CloseChannelResponse, error)
GetNewOnchainAddress(ctx context.Context) (*NewOnchainAddressResponse, error)
SignMessage(ctx context.Context, message string) (*SignMessageResponse, error)
RedeemOnchainFunds(ctx context.Context, toAddress string) (*RedeemOnchainFundsResponse, error)
GetBalances(ctx context.Context) (*BalancesResponse, error)
RequestMempoolApi(endpoint string) (interface{}, error)
GetInfo(ctx context.Context) (*InfoResponse, error)
GetEncryptedMnemonic() *EncryptedMnemonicResponse
SetNextBackupReminder(backupReminderRequest *BackupReminderRequest) error
Start(startRequest *StartRequest) error
Setup(ctx context.Context, setupRequest *SetupRequest) error
SendPaymentProbes(ctx context.Context, sendPaymentProbesRequest *SendPaymentProbesRequest) (*SendPaymentProbesResponse, error)
SendSpontaneousPaymentProbes(ctx context.Context, sendSpontaneousPaymentProbesRequest *SendSpontaneousPaymentProbesRequest) (*SendSpontaneousPaymentProbesResponse, error)
GetNetworkGraph(nodeIds []string) (NetworkGraphResponse, error)
SyncWallet() error
GetLogOutput(ctx context.Context, logType string, getLogRequest *GetLogOutputRequest) (*GetLogOutputResponse, error)
GetLSPService() lsp.LSPService
GetBackupService() backup.BackupService
}
type App struct {
@ -96,11 +132,10 @@ type InfoResponse struct {
BackendType string `json:"backendType"`
SetupCompleted bool `json:"setupCompleted"`
OAuthRedirect bool `json:"oauthRedirect"`
OnboardingCompleted bool `json:"onboardingCompleted"` // TODO: rename - HasChannel?
Running bool `json:"running"`
Unlocked bool `json:"unlocked"`
AlbyAuthUrl string `json:"albyAuthUrl"`
ShowBackupReminder bool `json:"showBackupReminder"`
NextBackupReminder string `json:"nextBackupReminder"`
AlbyUserIdentifier string `json:"albyUserIdentifier"`
AlbyAccountConnected bool `json:"albyAccountConnected"`
Network string `json:"network"`
@ -120,16 +155,6 @@ type OpenChannelRequest = lnclient.OpenChannelRequest
type OpenChannelResponse = lnclient.OpenChannelResponse
type CloseChannelResponse = lnclient.CloseChannelResponse
type NewInstantChannelInvoiceRequest struct {
Amount uint64 `json:"amount"`
LSP string `json:"lsp"`
}
type NewInstantChannelInvoiceResponse struct {
Invoice string `json:"invoice"`
Fee uint64 `json:"fee"`
}
type RedeemOnchainFundsRequest struct {
ToAddress string `json:"toAddress"`
}
@ -185,15 +210,6 @@ type SignMessageResponse struct {
Signature string `json:"signature"`
}
// TODO: move to different file
type AlbyBalanceResponse struct {
Sats int64 `json:"sats"`
}
type AlbyPayRequest struct {
Invoice string `json:"invoice"`
}
type ResetRouterRequest struct {
Key string `json:"key"`
}

295
backup/backup_service.go Normal file
View file

@ -0,0 +1,295 @@
package backup
import (
"errors"
"fmt"
"io"
"strings"
"time"
"archive/zip"
"os"
"path/filepath"
"slices"
"github.com/getAlby/nostr-wallet-connect/service"
"github.com/sirupsen/logrus"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"golang.org/x/crypto/pbkdf2"
)
type backupService struct {
svc service.Service
logger *logrus.Logger
}
func NewBackupService(svc service.Service, logger *logrus.Logger) *backupService {
return &backupService{
svc: svc,
logger: logger,
}
}
func (bs *backupService) CreateBackup(unlockPassword string, w io.Writer) error {
var err error
if !bs.svc.GetConfig().CheckUnlockPassword(unlockPassword) {
return errors.New("invalid unlock password")
}
workDir, err := filepath.Abs(bs.svc.GetConfig().GetEnv().Workdir)
if err != nil {
return fmt.Errorf("failed to get absolute workdir: %w", err)
}
lnStorageDir := ""
if bs.svc.GetLNClient() == nil {
return fmt.Errorf("node not running")
}
lnStorageDir, err = bs.svc.GetLNClient().GetStorageDir()
if err != nil {
return fmt.Errorf("failed to get storage dir: %w", err)
}
bs.logger.WithField("path", lnStorageDir).Info("Found node storage dir")
// Reset the routing data to decrease the LDK DB size
err = bs.svc.GetLNClient().ResetRouter("ALL")
if err != nil {
bs.logger.WithError(err).Error("Failed to reset router")
return fmt.Errorf("failed to reset router: %w", err)
}
// Stop the app to ensure no new requests are processed.
bs.svc.StopApp()
// Closing the database leaves the service in an inconsistent state,
// but that should not be a problem since the app is not expected
// to be used after its data is exported.
err = bs.svc.StopDb()
if err != nil {
bs.logger.WithError(err).Error("Failed to stop database")
return fmt.Errorf("failed to close database: %w", err)
}
var filesToArchive []string
if lnStorageDir != "" {
lnFiles, err := filepath.Glob(filepath.Join(workDir, lnStorageDir, "*"))
if err != nil {
return fmt.Errorf("failed to list files in the LNClient storage directory: %w", err)
}
bs.logger.WithField("lnFiles", lnFiles).Info("Listed node storage dir")
// Avoid backing up log files.
slices.DeleteFunc(lnFiles, func(s string) bool {
return filepath.Ext(s) == ".log"
})
filesToArchive = append(filesToArchive, lnFiles...)
}
cw, err := encryptingWriter(w, unlockPassword)
if err != nil {
return fmt.Errorf("failed to create encrypted writer: %w", err)
}
zw := zip.NewWriter(cw)
defer zw.Close()
addFileToZip := func(fsPath, zipPath string) error {
inF, err := os.Open(fsPath)
if err != nil {
return fmt.Errorf("failed to open source file for reading: %w", err)
}
defer inF.Close()
outW, err := zw.Create(zipPath)
if err != nil {
return fmt.Errorf("failed to create zip entry: %w", err)
}
_, err = io.Copy(outW, inF)
return err
}
// Locate the main database file.
dbFilePath := bs.svc.GetConfig().GetEnv().DatabaseUri
// Add the database file to the archive.
bs.logger.WithField("nwc.db", dbFilePath).Info("adding nwc db to zip")
err = addFileToZip(dbFilePath, "nwc.db")
if err != nil {
bs.logger.WithError(err).Error("Failed to zip nwc db")
return fmt.Errorf("failed to write nwc db file to zip: %w", err)
}
for _, fileToArchive := range filesToArchive {
bs.logger.WithField("fileToArchive", fileToArchive).Info("adding file to zip")
relPath, err := filepath.Rel(workDir, fileToArchive)
if err != nil {
bs.logger.WithError(err).Error("Failed to get relative path of input file")
return fmt.Errorf("failed to get relative path of input file: %w", err)
}
// Ensure forward slashes for zip format compatibility.
err = addFileToZip(fileToArchive, filepath.ToSlash(relPath))
if err != nil {
bs.logger.WithError(err).Error("Failed to write file to zip")
return fmt.Errorf("failed to write input file to zip: %w", err)
}
}
return nil
}
func (bs *backupService) RestoreBackup(unlockPassword string, r io.Reader) error {
workDir, err := filepath.Abs(bs.svc.GetConfig().GetEnv().Workdir)
if err != nil {
return fmt.Errorf("failed to get absolute workdir: %w", err)
}
if strings.HasPrefix(bs.svc.GetConfig().GetEnv().DatabaseUri, "file:") {
return errors.New("cannot restore backup when database path is a file URI")
}
cr, err := decryptingReader(r, unlockPassword)
if err != nil {
return fmt.Errorf("failed to create decrypted reader: %w", err)
}
tmpF, err := os.CreateTemp("", "nwc-*.bkp")
if err != nil {
return fmt.Errorf("failed to create temporary output file: %w", err)
}
tmpName := tmpF.Name()
defer os.Remove(tmpName)
defer tmpF.Close()
zipSize, err := io.Copy(tmpF, cr)
if err != nil {
return fmt.Errorf("failed to decrypt backup data into temporary file: %w", err)
}
if err = tmpF.Sync(); err != nil {
return fmt.Errorf("failed to flush temporary file: %w", err)
}
if _, err = tmpF.Seek(0, 0); err != nil {
return fmt.Errorf("failed to seek to beginning of temporary file: %w", err)
}
zr, err := zip.NewReader(tmpF, zipSize)
if err != nil {
return fmt.Errorf("failed to create zip reader: %w", err)
}
extractZipEntry := func(zipFile *zip.File) error {
fsFilePath := filepath.Join(workDir, "restore", filepath.FromSlash(zipFile.Name))
if err = os.MkdirAll(filepath.Dir(fsFilePath), 0700); err != nil {
return fmt.Errorf("failed to create directory for zip entry: %w", err)
}
inF, err := zipFile.Open()
if err != nil {
return fmt.Errorf("failed to open zip entry for reading: %w", err)
}
defer inF.Close()
outF, err := os.OpenFile(fsFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("failed to create destination file: %w", err)
}
defer outF.Close()
if _, err = io.Copy(outF, inF); err != nil {
return fmt.Errorf("failed to write zip entry to destination file: %w", err)
}
return nil
}
bs.logger.WithField("count", len(zr.File)).Info("Extracting files")
for _, f := range zr.File {
bs.logger.WithField("file", f.Name).Info("Extracting file")
if err = extractZipEntry(f); err != nil {
return fmt.Errorf("failed to extract zip entry: %w", err)
}
}
bs.logger.WithField("count", len(zr.File)).Info("Extracted files")
go func() {
bs.logger.Info("Backup restored. Shutting down Alby Hub...")
// schedule node shutdown after a few seconds to ensure frontend updates
time.Sleep(5 * time.Second)
os.Exit(0)
}()
return nil
}
func encryptingWriter(w io.Writer, password string) (io.Writer, error) {
salt := make([]byte, 8)
if _, err := rand.Read(salt); err != nil {
return nil, fmt.Errorf("failed to generate salt: %w", err)
}
encKey := pbkdf2.Key([]byte(password), salt, 4096, 32, sha256.New)
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, fmt.Errorf("failed to create AES cipher: %w", err)
}
iv := make([]byte, aes.BlockSize)
if _, err = rand.Read(iv); err != nil {
return nil, fmt.Errorf("failed to generate IV: %w", err)
}
_, err = w.Write(salt)
if err != nil {
return nil, fmt.Errorf("failed to write salt: %w", err)
}
_, err = w.Write(iv)
if err != nil {
return nil, fmt.Errorf("failed to write IV: %w", err)
}
stream := cipher.NewOFB(block, iv)
cw := &cipher.StreamWriter{
S: stream,
W: w,
}
return cw, nil
}
func decryptingReader(r io.Reader, password string) (io.Reader, error) {
salt := make([]byte, 8)
if _, err := io.ReadFull(r, salt); err != nil {
return nil, fmt.Errorf("failed to read salt: %w", err)
}
iv := make([]byte, aes.BlockSize)
if _, err := io.ReadFull(r, iv); err != nil {
return nil, fmt.Errorf("failed to read IV: %w", err)
}
encKey := pbkdf2.Key([]byte(password), salt, 4096, 32, sha256.New)
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, fmt.Errorf("failed to create AES cipher: %w", err)
}
stream := cipher.NewOFB(block, iv)
cr := &cipher.StreamReader{
S: stream,
R: r,
}
return cr, nil
}

10
backup/models.go Normal file
View file

@ -0,0 +1,10 @@
package backup
import (
"io"
)
type BackupService interface {
CreateBackup(unlockPassword string, w io.Writer) error
RestoreBackup(unlockPassword string, r io.Reader) error
}

View file

@ -1,4 +1,4 @@
package main
package config
import (
"crypto/aes"

View file

@ -1,4 +1,4 @@
package main
package config
import (
"crypto/rand"
@ -7,16 +7,16 @@ import (
"fmt"
"os"
"github.com/nbd-wtf/go-nostr"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"github.com/getAlby/nostr-wallet-connect/models/config"
dbModels "github.com/getAlby/nostr-wallet-connect/models/db"
dbModels "github.com/getAlby/nostr-wallet-connect/db"
)
type Config struct {
Env *config.AppConfig
type config struct {
Env *AppConfig
CookieSecret string
NostrSecretKey string
NostrPublicKey string
@ -28,7 +28,13 @@ const (
unlockPasswordCheck = "THIS STRING SHOULD MATCH IF PASSWORD IS CORRECT"
)
func (cfg *Config) Init(db *gorm.DB, env *config.AppConfig, logger *logrus.Logger) {
func NewConfig(db *gorm.DB, env *AppConfig, logger *logrus.Logger) *config {
cfg := &config{}
cfg.init(db, env, logger)
return cfg
}
func (cfg *config) init(db *gorm.DB, env *AppConfig, logger *logrus.Logger) {
cfg.db = db
cfg.Env = env
cfg.logger = logger
@ -70,15 +76,28 @@ func (cfg *Config) Init(db *gorm.DB, env *config.AppConfig, logger *logrus.Logge
}
}
func (cfg *Config) GetNostrPublicKey() string {
func (cfg *config) GetNostrPublicKey() string {
return cfg.NostrPublicKey
}
func (cfg *Config) Get(key string, encryptionKey string) (string, error) {
func (cfg *config) GetNostrSecretKey() string {
return cfg.NostrSecretKey
}
func (cfg *config) GetCookieSecret() string {
return cfg.CookieSecret
}
func (cfg *config) GetRelayUrl() string {
relayUrl, _ := cfg.Get("Relay", "")
return relayUrl
}
func (cfg *config) Get(key string, encryptionKey string) (string, error) {
return cfg.get(key, encryptionKey, cfg.db)
}
func (cfg *Config) get(key string, encryptionKey string, db *gorm.DB) (string, error) {
func (cfg *config) get(key string, encryptionKey string, db *gorm.DB) (string, error) {
var userConfig dbModels.UserConfig
err := db.Where(&dbModels.UserConfig{Key: key}).Limit(1).Find(&userConfig).Error
if err != nil {
@ -96,7 +115,7 @@ func (cfg *Config) get(key string, encryptionKey string, db *gorm.DB) (string, e
return value, nil
}
func (cfg *Config) set(key string, value string, clauses clause.OnConflict, encryptionKey string, db *gorm.DB) error {
func (cfg *config) set(key string, value string, clauses clause.OnConflict, encryptionKey string, db *gorm.DB) error {
if encryptionKey != "" {
encrypted, err := AesGcmEncrypt(value, encryptionKey)
if err != nil {
@ -113,7 +132,7 @@ func (cfg *Config) set(key string, value string, clauses clause.OnConflict, encr
return nil
}
func (cfg *Config) SetIgnore(key string, value string, encryptionKey string) {
func (cfg *config) SetIgnore(key string, value string, encryptionKey string) {
clauses := clause.OnConflict{
Columns: []clause.Column{{Name: "key"}},
DoNothing: true,
@ -124,7 +143,7 @@ func (cfg *Config) SetIgnore(key string, value string, encryptionKey string) {
}
}
func (cfg *Config) SetUpdate(key string, value string, encryptionKey string) {
func (cfg *config) SetUpdate(key string, value string, encryptionKey string) {
clauses := clause.OnConflict{
Columns: []clause.Column{{Name: "key"}},
DoUpdates: clause.AssignmentColumns([]string{"value"}),
@ -135,7 +154,7 @@ func (cfg *Config) SetUpdate(key string, value string, encryptionKey string) {
}
}
func (cfg *Config) ChangeUnlockPassword(currentUnlockPassword string, newUnlockPassword string) error {
func (cfg *config) ChangeUnlockPassword(currentUnlockPassword string, newUnlockPassword string) error {
if !cfg.CheckUnlockPassword(currentUnlockPassword) {
return errors.New("incorrect password")
}
@ -179,16 +198,37 @@ func (cfg *Config) ChangeUnlockPassword(currentUnlockPassword string, newUnlockP
return nil
}
func (cfg *Config) CheckUnlockPassword(encryptionKey string) bool {
func (cfg *config) CheckUnlockPassword(encryptionKey string) bool {
decryptedValue, err := cfg.Get("UnlockPasswordCheck", encryptionKey)
return err == nil && (decryptedValue == "" || decryptedValue == unlockPasswordCheck)
}
func (cfg *Config) SavePasswordCheck(encryptionKey string) {
func (cfg *config) Setup(encryptionKey string) {
cfg.SetUpdate("UnlockPasswordCheck", unlockPasswordCheck, encryptionKey)
}
func (cfg *config) Start(encryptionKey string) error {
nostrSecretKey, _ := cfg.Get("NostrSecretKey", encryptionKey)
if nostrSecretKey == "" {
nostrSecretKey = nostr.GeneratePrivateKey()
cfg.SetUpdate("NostrSecretKey", nostrSecretKey, encryptionKey)
}
nostrPublicKey, err := nostr.GetPublicKey(nostrSecretKey)
if err != nil {
cfg.logger.WithError(err).Error("Error converting nostr privkey to pubkey")
return err
}
cfg.NostrSecretKey = nostrSecretKey
cfg.NostrPublicKey = nostrPublicKey
return nil
}
func (cfg *config) GetEnv() *AppConfig {
return cfg.Env
}
func randomHex(n int) (string, error) {
bytes := make([]byte, n)
if _, err := rand.Read(bytes); err != nil {

View file

@ -1,4 +1,3 @@
// TODO: move to config/models.go
package config
const (
@ -45,4 +44,12 @@ type Config interface {
SetIgnore(key string, value string, encryptionKey string)
SetUpdate(key string, value string, encryptionKey string)
GetNostrPublicKey() string
GetNostrSecretKey() string
GetCookieSecret() string
GetRelayUrl() string
GetEnv() *AppConfig
CheckUnlockPassword(password string) bool
ChangeUnlockPassword(currentUnlockPassword string, newUnlockPassword string) error
Setup(encryptionKey string)
Start(encryptionKey string) error
}

79
db/db_service.go Normal file
View file

@ -0,0 +1,79 @@
package db
import (
"encoding/hex"
"fmt"
"strings"
"time"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
type dbService struct {
db *gorm.DB
logger *logrus.Logger
}
func NewDBService(db *gorm.DB, logger *logrus.Logger) *dbService {
return &dbService{
db: db,
logger: logger,
}
}
func (dbSvc *dbService) CreateApp(name string, pubkey string, maxAmount int, budgetRenewal string, expiresAt *time.Time, requestMethods []string) (*App, string, error) {
var pairingPublicKey string
var pairingSecretKey string
if pubkey == "" {
pairingSecretKey = nostr.GeneratePrivateKey()
pairingPublicKey, _ = nostr.GetPublicKey(pairingSecretKey)
} else {
pairingPublicKey = pubkey
//validate public key
decoded, err := hex.DecodeString(pairingPublicKey)
if err != nil || len(decoded) != 32 {
dbSvc.logger.WithField("pairingPublicKey", pairingPublicKey).Error("Invalid public key format")
return nil, "", fmt.Errorf("invalid public key format: %s", pairingPublicKey)
}
}
app := App{Name: name, NostrPubkey: pairingPublicKey}
err := dbSvc.db.Transaction(func(tx *gorm.DB) error {
err := tx.Save(&app).Error
if err != nil {
return err
}
for _, m := range requestMethods {
//if we don't know this method, we return an error
if !strings.Contains(nip47.CAPABILITIES, m) {
return fmt.Errorf("did not recognize request method: %s", m)
}
appPermission := AppPermission{
App: app,
RequestMethod: m,
ExpiresAt: expiresAt,
//these fields are only relevant for pay_invoice
MaxAmount: maxAmount,
BudgetRenewal: budgetRenewal,
}
err = tx.Create(&appPermission).Error
if err != nil {
return err
}
}
// commit transaction
return nil
})
if err != nil {
dbSvc.logger.WithError(err).Error("Failed to save app")
return nil, "", err
}
return &app, pairingSecretKey, nil
}

83
db/models.go Normal file
View file

@ -0,0 +1,83 @@
package db
import "time"
type UserConfig struct {
ID uint
Key string
Value string
Encrypted bool
CreatedAt time.Time
UpdatedAt time.Time
}
type App struct {
ID uint
Name string `validate:"required"`
Description string
NostrPubkey string `validate:"required"`
CreatedAt time.Time
UpdatedAt time.Time
}
type AppPermission struct {
ID uint
AppId uint `validate:"required"`
App App
RequestMethod string `validate:"required"`
MaxAmount int
BudgetRenewal string
ExpiresAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type RequestEvent struct {
ID uint
AppId *uint
App App
NostrId string `validate:"required"`
Content string
State string
CreatedAt time.Time
UpdatedAt time.Time
}
type ResponseEvent struct {
ID uint
NostrId string `validate:"required"`
RequestId uint `validate:"required"`
Content string
State string
RepliedAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
type Payment struct {
ID uint
AppId uint `validate:"required"`
App App
RequestEventId uint `validate:"required"`
RequestEvent RequestEvent
Amount uint // in sats
PaymentRequest string
Preimage *string
CreatedAt time.Time
UpdatedAt time.Time
}
type DBService interface {
CreateApp(name string, pubkey string, maxAmount int, budgetRenewal string, expiresAt *time.Time, requestMethods []string) (*App, string, error)
}
const (
REQUEST_EVENT_STATE_HANDLER_EXECUTING = "executing"
REQUEST_EVENT_STATE_HANDLER_EXECUTED = "executed"
REQUEST_EVENT_STATE_HANDLER_ERROR = "error"
)
const (
RESPONSE_EVENT_STATE_PUBLISH_CONFIRMED = "confirmed"
RESPONSE_EVENT_STATE_PUBLISH_FAILED = "failed"
RESPONSE_EVENT_STATE_PUBLISH_UNCONFIRMED = "unconfirmed"
)

View file

@ -8,21 +8,6 @@ import (
"github.com/sirupsen/logrus"
)
type EventSubscriber interface {
ConsumeEvent(ctx context.Context, event *Event, globalProperties map[string]interface{}) error
}
type Event struct {
Event string `json:"event"`
Properties interface{} `json:"properties,omitempty"`
}
type PaymentReceivedEventProperties struct {
PaymentHash string `json:"payment_hash"`
Amount uint64 `json:"amount"`
NodeType string `json:"node_type"`
}
type eventPublisher struct {
logger *logrus.Logger
listeners []EventSubscriber
@ -30,13 +15,6 @@ type eventPublisher struct {
globalProperties map[string]interface{}
}
type EventPublisher interface {
RegisterSubscriber(eventListener EventSubscriber)
RemoveSubscriber(eventListener EventSubscriber)
Publish(event *Event)
SetGlobalProperty(key string, value interface{})
}
func NewEventPublisher(logger *logrus.Logger) *eventPublisher {
eventPublisher := &eventPublisher{
logger: logger,

25
events/models.go Normal file
View file

@ -0,0 +1,25 @@
package events
import "context"
type EventSubscriber interface {
ConsumeEvent(ctx context.Context, event *Event, globalProperties map[string]interface{}) error
}
type EventPublisher interface {
RegisterSubscriber(eventListener EventSubscriber)
RemoveSubscriber(eventListener EventSubscriber)
Publish(event *Event)
SetGlobalProperty(key string, value interface{})
}
type Event struct {
Event string `json:"event"`
Properties interface{} `json:"properties,omitempty"`
}
type PaymentReceivedEventProperties struct {
PaymentHash string `json:"payment_hash"`
Amount uint64 `json:"amount"`
NodeType string `json:"node_type"`
}

View file

@ -14,6 +14,7 @@ import { useAlbyMe } from "src/hooks/useAlbyMe";
import { useChannels } from "src/hooks/useChannels";
import { useInfo } from "src/hooks/useInfo";
import { useNodeConnectionInfo } from "src/hooks/useNodeConnectionInfo";
import { backendTypeHasMnemonic } from "src/lib/utils";
import useChannelOrderStore from "src/state/ChannelOrderStore";
function SidebarHint() {
@ -34,7 +35,7 @@ function SidebarHint() {
}
// User has a channel order
if (order) {
if (order && order.status !== "pay") {
return (
<SidebarHintCard
icon={Zap}
@ -64,7 +65,10 @@ function SidebarHint() {
}
// User has no channels yet
if ((info?.backendType === "LDK" || info?.backendType === "GREENLIGHT") && channels?.length === 0) {
if (
(info?.backendType === "LDK" || info?.backendType === "GREENLIGHT") &&
channels?.length === 0
) {
return (
<SidebarHintCard
icon={Zap}
@ -77,7 +81,11 @@ function SidebarHint() {
}
// User has not linked their hub to their Alby Account
if (albyMe && nodeConnectionInfo && albyMe?.keysend_pubkey !== nodeConnectionInfo?.pubkey) {
if (
albyMe &&
nodeConnectionInfo &&
albyMe?.keysend_pubkey !== nodeConnectionInfo?.pubkey
) {
return (
<SidebarHintCard
icon={Link2}
@ -89,7 +97,12 @@ function SidebarHint() {
);
}
if (info?.backendType === "LDK" && info?.showBackupReminder) {
if (
info &&
backendTypeHasMnemonic(info.backendType) &&
(!info.nextBackupReminder ||
new Date(info.nextBackupReminder).getTime() < new Date().getTime())
) {
return (
<SidebarHintCard
icon={ShieldAlert}

View file

@ -1,4 +1,5 @@
import { clsx, type ClassValue } from "clsx";
import { BackendType } from "src/types";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
@ -20,3 +21,7 @@ export function splitSocketAddress(socketAddress: string) {
const port = socketAddress.slice(lastColonIndex + 1);
return { address, port };
}
export function backendTypeHasMnemonic(backendType: BackendType) {
return ["LND", "PHOENIX"].indexOf(backendType) === -1;
}

View file

@ -119,8 +119,9 @@ export default function Channels() {
if (
!confirm(
`Are you sure you want to close the channel with ${nodes.find((node) => node.public_key === nodeId)?.alias ||
"Unknown Node"
`Are you sure you want to close the channel with ${
nodes.find((node) => node.public_key === nodeId)?.alias ||
"Unknown Node"
}?\n\nNode ID: ${nodeId}\n\nChannel ID: ${channelId}`
)
) {
@ -139,7 +140,8 @@ export default function Channels() {
console.log(`🎬 Closing channel with ${nodeId}`);
const closeChannelResponse = await request<CloseChannelResponse>(
`/api/peers/${nodeId}/channels/${channelId}?force=${closeType === "force close"
`/api/peers/${nodeId}/channels/${channelId}?force=${
closeType === "force close"
}`,
{
method: "DELETE",
@ -203,36 +205,6 @@ export default function Channels() {
}
}
async function stopNode() {
try {
if (!csrf) {
throw new Error("csrf not loaded");
}
if (
!confirm(
"After restarting, you'll need to re-enter your unlock password."
)
) {
console.error("User cancelled reset");
return;
}
await request("/api/stop", {
method: "POST",
headers: {
"X-CSRF-Token": csrf,
"Content-Type": "application/json",
},
});
await reloadInfo();
alert(`🎉 Node stopped`);
} catch (error) {
console.error(error);
alert("Something went wrong: " + error);
}
}
return (
<>
<AppHeader
@ -299,9 +271,6 @@ export default function Channels() {
<DropdownMenuItem onClick={resetRouter}>
Clear Routing Data
</DropdownMenuItem>
<DropdownMenuItem onClick={stopNode}>
Restart
</DropdownMenuItem>
</DropdownMenuGroup>
</>
)}

View file

@ -530,7 +530,8 @@ function PayLightningChannelOrder({ order }: { order: NewChannelOrder }) {
channels && prevChannels
? channels.find(
(newChannel) =>
!prevChannels.some((current) => current.id === newChannel.id)
!prevChannels.some((current) => current.id === newChannel.id) &&
newChannel.fundingTxId
)
: undefined;

View file

@ -11,6 +11,7 @@ import { Input } from "src/components/ui/input";
import { Label } from "src/components/ui/label";
import { useToast } from "src/components/ui/use-toast";
import { useInfo } from "src/hooks/useInfo";
import { backendTypeHasMnemonic } from "src/lib/utils";
export function SetupPassword() {
const { toast } = useToast();
@ -23,6 +24,9 @@ export function SetupPassword() {
function onSubmit(e: React.FormEvent) {
e.preventDefault();
if (!info) {
return;
}
if (store.unlockPassword !== confirmPassword) {
toast({
title: "Passwords don't match",
@ -31,8 +35,7 @@ export function SetupPassword() {
return;
}
// Pre-configured nodes that do not need a mnemonic
if (info?.backendType === "LND" || info?.backendType === "PHOENIX") {
if (!backendTypeHasMnemonic(info.backendType)) {
// NOTE: LND flow does not setup a mnemonic
navigate(`/setup/finish`);
return;

View file

@ -133,12 +133,11 @@ export interface InfoResponse {
backendType: BackendType;
setupCompleted: boolean;
oauthRedirect: boolean;
onboardingCompleted: boolean;
albyAccountConnected: boolean;
running: boolean;
unlocked: boolean;
albyAuthUrl: string;
showBackupReminder: boolean;
nextBackupReminder: string;
albyUserIdentifier: string;
network?: Network;
}

View file

@ -3,6 +3,7 @@ package main
import (
"context"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
"github.com/sirupsen/logrus"
@ -12,7 +13,7 @@ const (
MSAT_PER_SAT = 1000
)
func (svc *Service) HandleGetBalanceEvent(ctx context.Context, nip47Request *Nip47Request, requestEvent *RequestEvent, app *App, publishResponse func(*Nip47Response, nostr.Tags)) {
func (svc *Service) HandleGetBalanceEvent(ctx context.Context, nip47Request *nip47.Request, requestEvent *db.RequestEvent, app *db.App, publishResponse func(*nip47.Response, nostr.Tags)) {
resp := svc.checkPermission(nip47Request, requestEvent.NostrId, app, 0)
if resp != nil {
@ -20,20 +21,20 @@ func (svc *Service) HandleGetBalanceEvent(ctx context.Context, nip47Request *Nip
return
}
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
}).Info("Fetching balance")
balance, err := svc.lnClient.GetBalance(ctx)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
}).Infof("Failed to fetch balance: %v", err)
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: err.Error(),
},
@ -41,11 +42,11 @@ func (svc *Service) HandleGetBalanceEvent(ctx context.Context, nip47Request *Nip
return
}
responsePayload := &Nip47BalanceResponse{
responsePayload := &nip47.BalanceResponse{
Balance: balance,
}
appPermission := AppPermission{}
appPermission := db.AppPermission{}
svc.db.Where("app_id = ? AND request_method = ?", app.ID, nip47.PAY_INVOICE_METHOD).First(&appPermission)
maxAmount := appPermission.MaxAmount
@ -54,7 +55,7 @@ func (svc *Service) HandleGetBalanceEvent(ctx context.Context, nip47Request *Nip
responsePayload.BudgetRenewal = appPermission.BudgetRenewal
}
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Result: responsePayload,
}, nostr.Tags{})

View file

@ -3,12 +3,13 @@ package main
import (
"context"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
"github.com/sirupsen/logrus"
)
func (svc *Service) HandleGetInfoEvent(ctx context.Context, nip47Request *Nip47Request, requestEvent *RequestEvent, app *App, publishResponse func(*Nip47Response, nostr.Tags)) {
func (svc *Service) HandleGetInfoEvent(ctx context.Context, nip47Request *nip47.Request, requestEvent *db.RequestEvent, app *db.App, publishResponse func(*nip47.Response, nostr.Tags)) {
resp := svc.checkPermission(nip47Request, requestEvent.NostrId, app, 0)
if resp != nil {
@ -16,21 +17,21 @@ func (svc *Service) HandleGetInfoEvent(ctx context.Context, nip47Request *Nip47R
return
}
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
}).Info("Fetching node info")
info, err := svc.lnClient.GetInfo(ctx)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
}).Infof("Failed to fetch node info: %v", err)
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: err.Error(),
},
@ -44,7 +45,7 @@ func (svc *Service) HandleGetInfoEvent(ctx context.Context, nip47Request *Nip47R
network = "mainnet"
}
responsePayload := &Nip47GetInfoResponse{
responsePayload := &nip47.GetInfoResponse{
Alias: info.Alias,
Color: info.Color,
Pubkey: info.Pubkey,
@ -54,7 +55,7 @@ func (svc *Service) HandleGetInfoEvent(ctx context.Context, nip47Request *Nip47R
Methods: svc.GetMethods(app),
}
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Result: responsePayload,
}, nostr.Tags{})

View file

@ -3,14 +3,15 @@ package main
import (
"context"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
"github.com/sirupsen/logrus"
)
func (svc *Service) HandleListTransactionsEvent(ctx context.Context, nip47Request *Nip47Request, requestEvent *RequestEvent, app *App, publishResponse func(*Nip47Response, nostr.Tags)) {
func (svc *Service) HandleListTransactionsEvent(ctx context.Context, nip47Request *nip47.Request, requestEvent *db.RequestEvent, app *db.App, publishResponse func(*nip47.Response, nostr.Tags)) {
listParams := &Nip47ListTransactionsParams{}
listParams := &nip47.ListTransactionsParams{}
resp := svc.decodeNip47Request(nip47Request, requestEvent, app, listParams)
if resp != nil {
publishResponse(resp, nostr.Tags{})
@ -23,7 +24,7 @@ func (svc *Service) HandleListTransactionsEvent(ctx context.Context, nip47Reques
return
}
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
// TODO: log request fields from listParams
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
@ -37,15 +38,15 @@ func (svc *Service) HandleListTransactionsEvent(ctx context.Context, nip47Reques
}
transactions, err := svc.lnClient.ListTransactions(ctx, listParams.From, listParams.Until, limit, listParams.Offset, listParams.Unpaid, listParams.Type)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
// TODO: log request fields from listParams
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
}).Infof("Failed to fetch transactions: %v", err)
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: err.Error(),
},
@ -53,11 +54,11 @@ func (svc *Service) HandleListTransactionsEvent(ctx context.Context, nip47Reques
return
}
responsePayload := &Nip47ListTransactionsResponse{
responsePayload := &nip47.ListTransactionsResponse{
Transactions: transactions,
}
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Result: responsePayload,
}, nostr.Tags{})

View file

@ -5,15 +5,16 @@ import (
"fmt"
"strings"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
decodepay "github.com/nbd-wtf/ln-decodepay"
"github.com/sirupsen/logrus"
)
func (svc *Service) HandleLookupInvoiceEvent(ctx context.Context, nip47Request *Nip47Request, requestEvent *RequestEvent, app *App, publishResponse func(*Nip47Response, nostr.Tags)) {
func (svc *Service) HandleLookupInvoiceEvent(ctx context.Context, nip47Request *nip47.Request, requestEvent *db.RequestEvent, app *db.App, publishResponse func(*nip47.Response, nostr.Tags)) {
lookupInvoiceParams := &Nip47LookupInvoiceParams{}
lookupInvoiceParams := &nip47.LookupInvoiceParams{}
resp := svc.decodeNip47Request(nip47Request, requestEvent, app, lookupInvoiceParams)
if resp != nil {
publishResponse(resp, nostr.Tags{})
@ -26,7 +27,7 @@ func (svc *Service) HandleLookupInvoiceEvent(ctx context.Context, nip47Request *
return
}
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"invoice": lookupInvoiceParams.Invoice,
@ -38,15 +39,15 @@ func (svc *Service) HandleLookupInvoiceEvent(ctx context.Context, nip47Request *
if paymentHash == "" {
paymentRequest, err := decodepay.Decodepay(strings.ToLower(lookupInvoiceParams.Invoice))
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"invoice": lookupInvoiceParams.Invoice,
}).Errorf("Failed to decode bolt11 invoice: %v", err)
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: fmt.Sprintf("Failed to decode bolt11 invoice: %s", err.Error()),
},
@ -58,16 +59,16 @@ func (svc *Service) HandleLookupInvoiceEvent(ctx context.Context, nip47Request *
transaction, err := svc.lnClient.LookupInvoice(ctx, paymentHash)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"invoice": lookupInvoiceParams.Invoice,
"paymentHash": lookupInvoiceParams.PaymentHash,
}).Infof("Failed to lookup invoice: %v", err)
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: err.Error(),
},
@ -75,11 +76,11 @@ func (svc *Service) HandleLookupInvoiceEvent(ctx context.Context, nip47Request *
return
}
responsePayload := &Nip47LookupInvoiceResponse{
Nip47Transaction: *transaction,
responsePayload := &nip47.LookupInvoiceResponse{
Transaction: *transaction,
}
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Result: responsePayload,
}, nostr.Tags{})

View file

@ -3,14 +3,15 @@ package main
import (
"context"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
"github.com/sirupsen/logrus"
)
func (svc *Service) HandleMakeInvoiceEvent(ctx context.Context, nip47Request *Nip47Request, requestEvent *RequestEvent, app *App, publishResponse func(*Nip47Response, nostr.Tags)) {
func (svc *Service) HandleMakeInvoiceEvent(ctx context.Context, nip47Request *nip47.Request, requestEvent *db.RequestEvent, app *db.App, publishResponse func(*nip47.Response, nostr.Tags)) {
makeInvoiceParams := &Nip47MakeInvoiceParams{}
makeInvoiceParams := &nip47.MakeInvoiceParams{}
resp := svc.decodeNip47Request(nip47Request, requestEvent, app, makeInvoiceParams)
if resp != nil {
publishResponse(resp, nostr.Tags{})
@ -23,7 +24,7 @@ func (svc *Service) HandleMakeInvoiceEvent(ctx context.Context, nip47Request *Ni
return
}
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"amount": makeInvoiceParams.Amount,
@ -39,7 +40,7 @@ func (svc *Service) HandleMakeInvoiceEvent(ctx context.Context, nip47Request *Ni
transaction, err := svc.lnClient.MakeInvoice(ctx, makeInvoiceParams.Amount, makeInvoiceParams.Description, makeInvoiceParams.DescriptionHash, expiry)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"amount": makeInvoiceParams.Amount,
@ -48,9 +49,9 @@ func (svc *Service) HandleMakeInvoiceEvent(ctx context.Context, nip47Request *Ni
"expiry": makeInvoiceParams.Expiry,
}).Infof("Failed to make invoice: %v", err)
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: err.Error(),
},
@ -58,11 +59,11 @@ func (svc *Service) HandleMakeInvoiceEvent(ctx context.Context, nip47Request *Ni
return
}
responsePayload := &Nip47MakeInvoiceResponse{
Nip47Transaction: *transaction,
responsePayload := &nip47.MakeInvoiceResponse{
Transaction: *transaction,
}
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Result: responsePayload,
}, nostr.Tags{})

View file

@ -6,6 +6,7 @@ import (
"strings"
"sync"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/events"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
@ -14,9 +15,9 @@ import (
)
// TODO: pass a channel instead of publishResponse function
func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, nip47Request *Nip47Request, requestEvent *RequestEvent, app *App, publishResponse func(*Nip47Response, nostr.Tags)) {
func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, nip47Request *nip47.Request, requestEvent *db.RequestEvent, app *db.App, publishResponse func(*nip47.Response, nostr.Tags)) {
multiPayParams := &Nip47MultiPayInvoiceParams{}
multiPayParams := &nip47.MultiPayInvoiceParams{}
resp := svc.decodeNip47Request(nip47Request, requestEvent, app, multiPayParams)
if resp != nil {
publishResponse(resp, nostr.Tags{})
@ -28,14 +29,14 @@ func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, nip47Request
for _, invoiceInfo := range multiPayParams.Invoices {
wg.Add(1)
// TODO: we should call the handle_payment_request (most of this code is duplicated)
go func(invoiceInfo Nip47MultiPayInvoiceElement) {
go func(invoiceInfo nip47.MultiPayInvoiceElement) {
defer wg.Done()
bolt11 := invoiceInfo.Invoice
// Convert invoice to lowercase string
bolt11 = strings.ToLower(bolt11)
paymentRequest, err := decodepay.Decodepay(bolt11)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"bolt11": bolt11,
@ -43,9 +44,9 @@ func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, nip47Request
// TODO: Decide what to do if id is empty
dTag := []string{"d", invoiceInfo.Id}
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: fmt.Sprintf("Failed to decode bolt11 invoice: %s", err.Error()),
},
@ -65,12 +66,12 @@ func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, nip47Request
return
}
payment := Payment{App: *app, RequestEventId: requestEvent.ID, PaymentRequest: bolt11, Amount: uint(paymentRequest.MSatoshi / 1000)}
payment := db.Payment{App: *app, RequestEventId: requestEvent.ID, PaymentRequest: bolt11, Amount: uint(paymentRequest.MSatoshi / 1000)}
mu.Lock()
insertPaymentResult := svc.db.Create(&payment)
mu.Unlock()
if insertPaymentResult.Error != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"paymentRequest": bolt11,
"invoiceId": invoiceInfo.Id,
@ -78,7 +79,7 @@ func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, nip47Request
return
}
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"bolt11": bolt11,
@ -86,13 +87,13 @@ func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, nip47Request
response, err := svc.lnClient.SendPaymentSync(ctx, bolt11)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"bolt11": bolt11,
}).Infof("Failed to send payment: %v", err)
svc.EventPublisher.Publish(&events.Event{
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_payment_failed",
Properties: map[string]interface{}{
// "error": fmt.Sprintf("%v", err),
@ -102,9 +103,9 @@ func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, nip47Request
},
})
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: err.Error(),
},
@ -117,16 +118,16 @@ func (svc *Service) HandleMultiPayInvoiceEvent(ctx context.Context, nip47Request
mu.Lock()
svc.db.Save(&payment)
mu.Unlock()
svc.EventPublisher.Publish(&events.Event{
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_payment_succeeded",
Properties: map[string]interface{}{
"multi": true,
"amount": paymentRequest.MSatoshi / 1000,
},
})
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Result: Nip47PayResponse{
Result: nip47.PayResponse{
Preimage: response.Preimage,
FeesPaid: response.Fee,
},

View file

@ -4,15 +4,16 @@ import (
"context"
"sync"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/events"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
"github.com/sirupsen/logrus"
)
func (svc *Service) HandleMultiPayKeysendEvent(ctx context.Context, nip47Request *Nip47Request, requestEvent *RequestEvent, app *App, publishResponse func(*Nip47Response, nostr.Tags)) {
func (svc *Service) HandleMultiPayKeysendEvent(ctx context.Context, nip47Request *nip47.Request, requestEvent *db.RequestEvent, app *db.App, publishResponse func(*nip47.Response, nostr.Tags)) {
multiPayParams := &Nip47MultiPayKeysendParams{}
multiPayParams := &nip47.MultiPayKeysendParams{}
resp := svc.decodeNip47Request(nip47Request, requestEvent, app, multiPayParams)
if resp != nil {
publishResponse(resp, nostr.Tags{})
@ -23,7 +24,7 @@ func (svc *Service) HandleMultiPayKeysendEvent(ctx context.Context, nip47Request
var mu sync.Mutex
for _, keysendInfo := range multiPayParams.Keysends {
wg.Add(1)
go func(keysendInfo Nip47MultiPayKeysendElement) {
go func(keysendInfo nip47.MultiPayKeysendElement) {
defer wg.Done()
keysendDTagValue := keysendInfo.Id
@ -38,12 +39,12 @@ func (svc *Service) HandleMultiPayKeysendEvent(ctx context.Context, nip47Request
return
}
payment := Payment{App: *app, RequestEvent: *requestEvent, Amount: uint(keysendInfo.Amount / 1000)}
payment := db.Payment{App: *app, RequestEvent: *requestEvent, Amount: uint(keysendInfo.Amount / 1000)}
mu.Lock()
insertPaymentResult := svc.db.Create(&payment)
mu.Unlock()
if insertPaymentResult.Error != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"recipientPubkey": keysendInfo.Pubkey,
"keysendId": keysendInfo.Id,
@ -51,7 +52,7 @@ func (svc *Service) HandleMultiPayKeysendEvent(ctx context.Context, nip47Request
return
}
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"recipientPubkey": keysendInfo.Pubkey,
@ -59,12 +60,12 @@ func (svc *Service) HandleMultiPayKeysendEvent(ctx context.Context, nip47Request
preimage, err := svc.lnClient.SendKeysend(ctx, keysendInfo.Amount, keysendInfo.Pubkey, keysendInfo.Preimage, keysendInfo.TLVRecords)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"recipientPubkey": keysendInfo.Pubkey,
}).Infof("Failed to send payment: %v", err)
svc.EventPublisher.Publish(&events.Event{
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_payment_failed",
Properties: map[string]interface{}{
// "error": fmt.Sprintf("%v", err),
@ -74,9 +75,9 @@ func (svc *Service) HandleMultiPayKeysendEvent(ctx context.Context, nip47Request
},
})
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: err.Error(),
},
@ -87,7 +88,7 @@ func (svc *Service) HandleMultiPayKeysendEvent(ctx context.Context, nip47Request
mu.Lock()
svc.db.Save(&payment)
mu.Unlock()
svc.EventPublisher.Publish(&events.Event{
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_payment_succeeded",
Properties: map[string]interface{}{
"keysend": true,
@ -95,9 +96,9 @@ func (svc *Service) HandleMultiPayKeysendEvent(ctx context.Context, nip47Request
"amount": keysendInfo.Amount / 1000,
},
})
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Result: Nip47PayResponse{
Result: nip47.PayResponse{
Preimage: preimage,
},
}, nostr.Tags{dTag})

View file

@ -3,15 +3,16 @@ package main
import (
"context"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/events"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
"github.com/sirupsen/logrus"
)
func (svc *Service) HandlePayKeysendEvent(ctx context.Context, nip47Request *Nip47Request, requestEvent *RequestEvent, app *App, publishResponse func(*Nip47Response, nostr.Tags)) {
func (svc *Service) HandlePayKeysendEvent(ctx context.Context, nip47Request *nip47.Request, requestEvent *db.RequestEvent, app *db.App, publishResponse func(*nip47.Response, nostr.Tags)) {
payParams := &Nip47KeysendParams{}
payParams := &nip47.KeysendParams{}
resp := svc.decodeNip47Request(nip47Request, requestEvent, app, payParams)
if resp != nil {
publishResponse(resp, nostr.Tags{})
@ -24,12 +25,12 @@ func (svc *Service) HandlePayKeysendEvent(ctx context.Context, nip47Request *Nip
return
}
payment := Payment{App: *app, RequestEvent: *requestEvent, Amount: uint(payParams.Amount / 1000)}
payment := db.Payment{App: *app, RequestEvent: *requestEvent, Amount: uint(payParams.Amount / 1000)}
err := svc.db.Create(&payment).Error
if err != nil {
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: err.Error(),
},
@ -37,7 +38,7 @@ func (svc *Service) HandlePayKeysendEvent(ctx context.Context, nip47Request *Nip
return
}
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"senderPubkey": payParams.Pubkey,
@ -45,12 +46,12 @@ func (svc *Service) HandlePayKeysendEvent(ctx context.Context, nip47Request *Nip
preimage, err := svc.lnClient.SendKeysend(ctx, payParams.Amount, payParams.Pubkey, payParams.Preimage, payParams.TLVRecords)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"recipientPubkey": payParams.Pubkey,
}).Infof("Failed to send payment: %v", err)
svc.EventPublisher.Publish(&events.Event{
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_payment_failed",
Properties: map[string]interface{}{
// "error": fmt.Sprintf("%v", err),
@ -58,9 +59,9 @@ func (svc *Service) HandlePayKeysendEvent(ctx context.Context, nip47Request *Nip
"amount": payParams.Amount / 1000,
},
})
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: err.Error(),
},
@ -69,16 +70,16 @@ func (svc *Service) HandlePayKeysendEvent(ctx context.Context, nip47Request *Nip
}
payment.Preimage = &preimage
svc.db.Save(&payment)
svc.EventPublisher.Publish(&events.Event{
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_payment_succeeded",
Properties: map[string]interface{}{
"keysend": true,
"amount": payParams.Amount / 1000,
},
})
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Result: Nip47PayResponse{
Result: nip47.PayResponse{
Preimage: preimage,
},
}, nostr.Tags{})

View file

@ -5,6 +5,7 @@ import (
"fmt"
"strings"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/events"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
@ -12,9 +13,9 @@ import (
"github.com/sirupsen/logrus"
)
func (svc *Service) HandlePayInvoiceEvent(ctx context.Context, nip47Request *Nip47Request, requestEvent *RequestEvent, app *App, publishResponse func(*Nip47Response, nostr.Tags)) {
func (svc *Service) HandlePayInvoiceEvent(ctx context.Context, nip47Request *nip47.Request, requestEvent *db.RequestEvent, app *db.App, publishResponse func(*nip47.Response, nostr.Tags)) {
payParams := &Nip47PayParams{}
payParams := &nip47.PayParams{}
resp := svc.decodeNip47Request(nip47Request, requestEvent, app, payParams)
if resp != nil {
publishResponse(resp, nostr.Tags{})
@ -26,15 +27,15 @@ func (svc *Service) HandlePayInvoiceEvent(ctx context.Context, nip47Request *Nip
bolt11 = strings.ToLower(bolt11)
paymentRequest, err := decodepay.Decodepay(bolt11)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"bolt11": bolt11,
}).Errorf("Failed to decode bolt11 invoice: %v", err)
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: fmt.Sprintf("Failed to decode bolt11 invoice: %s", err.Error()),
},
@ -48,12 +49,12 @@ func (svc *Service) HandlePayInvoiceEvent(ctx context.Context, nip47Request *Nip
return
}
payment := Payment{App: *app, RequestEvent: *requestEvent, PaymentRequest: bolt11, Amount: uint(paymentRequest.MSatoshi / 1000)}
payment := db.Payment{App: *app, RequestEvent: *requestEvent, PaymentRequest: bolt11, Amount: uint(paymentRequest.MSatoshi / 1000)}
err = svc.db.Create(&payment).Error
if err != nil {
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: err.Error(),
},
@ -61,7 +62,7 @@ func (svc *Service) HandlePayInvoiceEvent(ctx context.Context, nip47Request *Nip
return
}
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"bolt11": bolt11,
@ -69,12 +70,12 @@ func (svc *Service) HandlePayInvoiceEvent(ctx context.Context, nip47Request *Nip
response, err := svc.lnClient.SendPaymentSync(ctx, bolt11)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
"bolt11": bolt11,
}).Infof("Failed to send payment: %v", err)
svc.EventPublisher.Publish(&events.Event{
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_payment_failed",
Properties: map[string]interface{}{
// "error": fmt.Sprintf("%v", err),
@ -82,9 +83,9 @@ func (svc *Service) HandlePayInvoiceEvent(ctx context.Context, nip47Request *Nip
"amount": paymentRequest.MSatoshi / 1000,
},
})
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: err.Error(),
},
@ -95,7 +96,7 @@ func (svc *Service) HandlePayInvoiceEvent(ctx context.Context, nip47Request *Nip
// TODO: save payment fee
svc.db.Save(&payment)
svc.EventPublisher.Publish(&events.Event{
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_payment_succeeded",
Properties: map[string]interface{}{
"bolt11": bolt11,
@ -103,9 +104,9 @@ func (svc *Service) HandlePayInvoiceEvent(ctx context.Context, nip47Request *Nip
},
})
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Result: Nip47PayResponse{
Result: nip47.PayResponse{
Preimage: response.Preimage,
FeesPaid: response.Fee,
},

View file

@ -3,13 +3,14 @@ package main
import (
"context"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
"github.com/sirupsen/logrus"
)
func (svc *Service) HandleSignMessageEvent(ctx context.Context, nip47Request *Nip47Request, requestEvent *RequestEvent, app *App, publishResponse func(*Nip47Response, nostr.Tags)) {
signParams := &Nip47SignMessageParams{}
func (svc *Service) HandleSignMessageEvent(ctx context.Context, nip47Request *nip47.Request, requestEvent *db.RequestEvent, app *db.App, publishResponse func(*nip47.Response, nostr.Tags)) {
signParams := &nip47.SignMessageParams{}
resp := svc.decodeNip47Request(nip47Request, requestEvent, app, signParams)
if resp != nil {
publishResponse(resp, nostr.Tags{})
@ -22,20 +23,20 @@ func (svc *Service) HandleSignMessageEvent(ctx context.Context, nip47Request *Ni
return
}
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
}).Info("Signing message")
signature, err := svc.lnClient.SignMessage(ctx, signParams.Message)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
}).Infof("Failed to sign message: %v", err)
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: err.Error(),
},
@ -43,12 +44,12 @@ func (svc *Service) HandleSignMessageEvent(ctx context.Context, nip47Request *Ni
return
}
responsePayload := Nip47SignMessageResponse{
responsePayload := nip47.SignMessageResponse{
Message: signParams.Message,
Signature: signature,
}
publishResponse(&Nip47Response{
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Result: responsePayload,
}, nostr.Tags{})

View file

@ -1,4 +1,4 @@
package main
package http
import (
"bytes"
@ -12,20 +12,27 @@ import (
"github.com/labstack/echo-contrib/session"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
"github.com/getAlby/nostr-wallet-connect/alby"
"github.com/getAlby/nostr-wallet-connect/config"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/events"
models "github.com/getAlby/nostr-wallet-connect/models/http"
"github.com/getAlby/nostr-wallet-connect/lsp"
"github.com/getAlby/nostr-wallet-connect/service"
"github.com/getAlby/nostr-wallet-connect/api"
"github.com/getAlby/nostr-wallet-connect/frontend"
"github.com/getAlby/nostr-wallet-connect/models/api"
)
type HttpService struct {
svc *Service
api *API
albyHttpSvc *alby.AlbyHttpService
api api.API
albyHttpSvc *alby.AlbyHttpService
cfg config.Config
db *gorm.DB
logger *logrus.Logger
eventPublisher events.EventPublisher
}
const (
@ -33,11 +40,15 @@ const (
sessionCookieAuthKey = "authenticated"
)
func NewHttpService(svc *Service) *HttpService {
func NewHttpService(svc service.Service, logger *logrus.Logger, db *gorm.DB, eventPublisher events.EventPublisher) *HttpService {
return &HttpService{
svc: svc,
api: NewAPI(svc),
albyHttpSvc: alby.NewAlbyHttpService(svc.AlbyOAuthSvc, svc.Logger),
api: api.NewAPI(svc, logger, db),
albyHttpSvc: alby.NewAlbyHttpService(svc.GetAlbyOAuthSvc(), logger, svc.GetConfig().GetEnv()),
cfg: svc.GetConfig(),
db: db,
logger: logger,
eventPublisher: eventPublisher,
}
}
@ -58,7 +69,7 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "header:X-CSRF-Token",
}))
e.Use(session.Middleware(sessions.NewCookieStore([]byte(httpSvc.svc.cfg.CookieSecret))))
e.Use(session.Middleware(sessions.NewCookieStore([]byte(httpSvc.cfg.GetCookieSecret()))))
authMiddleware := httpSvc.validateUserMiddleware
e.GET("/api/apps", httpSvc.appsListHandler, authMiddleware)
@ -117,7 +128,7 @@ func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
func (httpSvc *HttpService) csrfHandler(c echo.Context) error {
csrf, _ := c.Get(middleware.DefaultCSRFConfig.ContextKey).(string)
if csrf == "" {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: "CSRF token not available",
})
}
@ -127,7 +138,7 @@ func (httpSvc *HttpService) csrfHandler(c echo.Context) error {
func (httpSvc *HttpService) infoHandler(c echo.Context) error {
responseBody, err := httpSvc.api.GetInfo(c.Request().Context())
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}
@ -143,14 +154,14 @@ func (httpSvc *HttpService) encryptedMnemonicHandler(c echo.Context) error {
func (httpSvc *HttpService) backupReminderHandler(c echo.Context) error {
var backupReminderRequest api.BackupReminderRequest
if err := c.Bind(&backupReminderRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
err := httpSvc.api.SetNextBackupReminder(&backupReminderRequest)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to store backup reminder: %s", err.Error()),
})
}
@ -161,14 +172,14 @@ func (httpSvc *HttpService) backupReminderHandler(c echo.Context) error {
func (httpSvc *HttpService) startHandler(c echo.Context) error {
var startRequest api.StartRequest
if err := c.Bind(&startRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
err := httpSvc.api.Start(&startRequest)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to start node: %s", err.Error()),
})
}
@ -176,7 +187,7 @@ func (httpSvc *HttpService) startHandler(c echo.Context) error {
err = httpSvc.saveSessionCookie(c)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to save session: %s", err.Error()),
})
}
@ -187,13 +198,13 @@ func (httpSvc *HttpService) startHandler(c echo.Context) error {
func (httpSvc *HttpService) unlockHandler(c echo.Context) error {
var unlockRequest api.UnlockRequest
if err := c.Bind(&unlockRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
if !httpSvc.svc.cfg.CheckUnlockPassword(unlockRequest.UnlockPassword) {
return c.JSON(http.StatusUnauthorized, models.ErrorResponse{
if !httpSvc.cfg.CheckUnlockPassword(unlockRequest.UnlockPassword) {
return c.JSON(http.StatusUnauthorized, ErrorResponse{
Message: "Invalid password",
})
}
@ -201,12 +212,12 @@ func (httpSvc *HttpService) unlockHandler(c echo.Context) error {
err := httpSvc.saveSessionCookie(c)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to save session: %s", err.Error()),
})
}
httpSvc.svc.EventPublisher.Publish(&events.Event{
httpSvc.eventPublisher.Publish(&events.Event{
Event: "nwc_unlocked",
})
@ -216,14 +227,14 @@ func (httpSvc *HttpService) unlockHandler(c echo.Context) error {
func (httpSvc *HttpService) changeUnlockPasswordHandler(c echo.Context) error {
var changeUnlockPasswordRequest api.ChangeUnlockPasswordRequest
if err := c.Bind(&changeUnlockPasswordRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
err := httpSvc.api.ChangeUnlockPassword(&changeUnlockPasswordRequest)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to change unlock password: %s", err.Error()),
})
}
@ -246,7 +257,7 @@ func (httpSvc *HttpService) saveSessionCookie(c echo.Context) error {
sess.Values[sessionCookieAuthKey] = true
err := sess.Save(c.Request(), c.Response())
if err != nil {
httpSvc.svc.Logger.WithError(err).Error("Failed to save session")
httpSvc.logger.WithError(err).Error("Failed to save session")
}
return err
}
@ -254,13 +265,13 @@ func (httpSvc *HttpService) saveSessionCookie(c echo.Context) error {
func (httpSvc *HttpService) logoutHandler(c echo.Context) error {
sess, err := session.Get(sessionCookieName, c)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: "Failed to get session",
})
}
sess.Options.MaxAge = -1
if err := sess.Save(c.Request(), c.Response()); err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: "Failed to save session",
})
}
@ -273,7 +284,7 @@ func (httpSvc *HttpService) channelsListHandler(c echo.Context) error {
channels, err := httpSvc.api.ListChannels(ctx)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}
@ -287,7 +298,7 @@ func (httpSvc *HttpService) channelPeerSuggestionsHandler(c echo.Context) error
suggestions, err := httpSvc.api.GetChannelPeerSuggestions(ctx)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}
@ -298,15 +309,15 @@ func (httpSvc *HttpService) channelPeerSuggestionsHandler(c echo.Context) error
func (httpSvc *HttpService) resetRouterHandler(c echo.Context) error {
var resetRouterRequest api.ResetRouterRequest
if err := c.Bind(&resetRouterRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
err := httpSvc.api.ResetRouter(resetRouterRequest.Key, true)
err := httpSvc.api.ResetRouter(resetRouterRequest.Key)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}
@ -319,7 +330,7 @@ func (httpSvc *HttpService) stopHandler(c echo.Context) error {
err := httpSvc.api.Stop()
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}
@ -333,7 +344,7 @@ func (httpSvc *HttpService) nodeConnectionInfoHandler(c echo.Context) error {
info, err := httpSvc.api.GetNodeConnectionInfo(ctx)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}
@ -347,7 +358,7 @@ func (httpSvc *HttpService) nodeStatusHandler(c echo.Context) error {
info, err := httpSvc.api.GetNodeStatus(ctx)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}
@ -361,7 +372,7 @@ func (httpSvc *HttpService) nodeNetworkGraphHandler(c echo.Context) error {
info, err := httpSvc.api.GetNetworkGraph(nodeIds)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}
@ -375,7 +386,7 @@ func (httpSvc *HttpService) balancesHandler(c echo.Context) error {
balances, err := httpSvc.api.GetBalances(ctx)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}
@ -392,15 +403,15 @@ func (httpSvc *HttpService) walletSyncHandler(c echo.Context) error {
func (httpSvc *HttpService) mempoolApiHandler(c echo.Context) error {
endpoint := c.QueryParam("endpoint")
if endpoint == "" {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: "Invalid pubkey parameter",
})
}
response, err := httpSvc.api.RequestMempoolApi(endpoint)
if err != nil {
httpSvc.svc.Logger.WithField("endpoint", endpoint).WithError(err).Error("Failed to request mempool API")
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
httpSvc.logger.WithField("endpoint", endpoint).WithError(err).Error("Failed to request mempool API")
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to request mempool API: %s", err.Error()),
})
}
@ -409,9 +420,9 @@ func (httpSvc *HttpService) mempoolApiHandler(c echo.Context) error {
}
func (httpSvc *HttpService) listPeers(c echo.Context) error {
peers, err := httpSvc.api.ListPeers(httpSvc.svc.ctx)
peers, err := httpSvc.api.ListPeers(c.Request().Context())
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to list peers: %s", err.Error()),
})
}
@ -424,7 +435,7 @@ func (httpSvc *HttpService) connectPeerHandler(c echo.Context) error {
var connectPeerRequest api.ConnectPeerRequest
if err := c.Bind(&connectPeerRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
@ -432,7 +443,7 @@ func (httpSvc *HttpService) connectPeerHandler(c echo.Context) error {
err := httpSvc.api.ConnectPeer(ctx, &connectPeerRequest)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to connect peer: %s", err.Error()),
})
}
@ -445,7 +456,7 @@ func (httpSvc *HttpService) openChannelHandler(c echo.Context) error {
var openChannelRequest api.OpenChannelRequest
if err := c.Bind(&openChannelRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
@ -453,7 +464,7 @@ func (httpSvc *HttpService) openChannelHandler(c echo.Context) error {
openChannelResponse, err := httpSvc.api.OpenChannel(ctx, &openChannelRequest)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to open channel: %s", err.Error()),
})
}
@ -467,7 +478,7 @@ func (httpSvc *HttpService) closeChannelHandler(c echo.Context) error {
closeChannelResponse, err := httpSvc.api.CloseChannel(ctx, c.Param("peerId"), c.Param("channelId"), c.QueryParam("force") == "true")
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to close channel: %s", err.Error()),
})
}
@ -478,17 +489,17 @@ func (httpSvc *HttpService) closeChannelHandler(c echo.Context) error {
func (httpSvc *HttpService) newInstantChannelInvoiceHandler(c echo.Context) error {
ctx := c.Request().Context()
var newWrappedInvoiceRequest api.NewInstantChannelInvoiceRequest
var newWrappedInvoiceRequest lsp.NewInstantChannelInvoiceRequest
if err := c.Bind(&newWrappedInvoiceRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
newWrappedInvoiceResponse, err := httpSvc.api.NewInstantChannelInvoice(ctx, &newWrappedInvoiceRequest)
newWrappedInvoiceResponse, err := httpSvc.api.GetLSPService().NewInstantChannelInvoice(ctx, &newWrappedInvoiceRequest)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to request wrapped invoice: %s", err.Error()),
})
}
@ -502,7 +513,7 @@ func (httpSvc *HttpService) newOnchainAddressHandler(c echo.Context) error {
newAddressResponse, err := httpSvc.api.GetNewOnchainAddress(ctx)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to request new onchain address: %s", err.Error()),
})
}
@ -515,7 +526,7 @@ func (httpSvc *HttpService) redeemOnchainFundsHandler(c echo.Context) error {
var redeemOnchainFundsRequest api.RedeemOnchainFundsRequest
if err := c.Bind(&redeemOnchainFundsRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
@ -523,7 +534,7 @@ func (httpSvc *HttpService) redeemOnchainFundsHandler(c echo.Context) error {
redeemOnchainFundsResponse, err := httpSvc.api.RedeemOnchainFunds(ctx, redeemOnchainFundsRequest.ToAddress)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to redeem onchain funds: %s", err.Error()),
})
}
@ -536,7 +547,7 @@ func (httpSvc *HttpService) signMessageHandler(c echo.Context) error {
var signMessageRequest api.SignMessageRequest
if err := c.Bind(&signMessageRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
@ -544,7 +555,7 @@ func (httpSvc *HttpService) signMessageHandler(c echo.Context) error {
signMessageResponse, err := httpSvc.api.SignMessage(ctx, signMessageRequest.Message)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to sign messae: %s", err.Error()),
})
}
@ -555,7 +566,7 @@ func (httpSvc *HttpService) appsListHandler(c echo.Context) error {
apps, err := httpSvc.api.ListApps()
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: err.Error(),
})
}
@ -564,11 +575,11 @@ func (httpSvc *HttpService) appsListHandler(c echo.Context) error {
}
func (httpSvc *HttpService) appsShowHandler(c echo.Context) error {
app := App{}
findResult := httpSvc.svc.db.Where("nostr_pubkey = ?", c.Param("pubkey")).First(&app)
app := db.App{}
findResult := httpSvc.db.Where("nostr_pubkey = ?", c.Param("pubkey")).First(&app)
if findResult.RowsAffected == 0 {
return c.JSON(http.StatusNotFound, models.ErrorResponse{
return c.JSON(http.StatusNotFound, ErrorResponse{
Message: "App does not exist",
})
}
@ -581,16 +592,16 @@ func (httpSvc *HttpService) appsShowHandler(c echo.Context) error {
func (httpSvc *HttpService) appsUpdateHandler(c echo.Context) error {
var requestData api.UpdateAppRequest
if err := c.Bind(&requestData); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
app := App{}
findResult := httpSvc.svc.db.Where("nostr_pubkey = ?", c.Param("pubkey")).First(&app)
app := db.App{}
findResult := httpSvc.db.Where("nostr_pubkey = ?", c.Param("pubkey")).First(&app)
if findResult.RowsAffected == 0 {
return c.JSON(http.StatusNotFound, models.ErrorResponse{
return c.JSON(http.StatusNotFound, ErrorResponse{
Message: "App does not exist",
})
}
@ -598,8 +609,8 @@ func (httpSvc *HttpService) appsUpdateHandler(c echo.Context) error {
err := httpSvc.api.UpdateApp(&app, &requestData)
if err != nil {
httpSvc.svc.Logger.WithError(err).Error("Failed to update app")
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
httpSvc.logger.WithError(err).Error("Failed to update app")
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to update app: %v", err),
})
}
@ -610,25 +621,25 @@ func (httpSvc *HttpService) appsUpdateHandler(c echo.Context) error {
func (httpSvc *HttpService) appsDeleteHandler(c echo.Context) error {
pubkey := c.Param("pubkey")
if pubkey == "" {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: "Invalid pubkey parameter",
})
}
app := App{}
result := httpSvc.svc.db.Where("nostr_pubkey = ?", pubkey).First(&app)
app := db.App{}
result := httpSvc.db.Where("nostr_pubkey = ?", pubkey).First(&app)
if result.Error != nil {
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return c.JSON(http.StatusNotFound, models.ErrorResponse{
return c.JSON(http.StatusNotFound, ErrorResponse{
Message: "App not found",
})
}
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: "Failed to fetch app",
})
}
if err := httpSvc.api.DeleteApp(&app); err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: "Failed to delete app",
})
}
@ -638,7 +649,7 @@ func (httpSvc *HttpService) appsDeleteHandler(c echo.Context) error {
func (httpSvc *HttpService) appsCreateHandler(c echo.Context) error {
var requestData api.CreateAppRequest
if err := c.Bind(&requestData); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
@ -646,8 +657,8 @@ func (httpSvc *HttpService) appsCreateHandler(c echo.Context) error {
responseBody, err := httpSvc.api.CreateApp(&requestData)
if err != nil {
httpSvc.svc.Logger.WithField("requestData", requestData).WithError(err).Error("Failed to save app")
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
httpSvc.logger.WithField("requestData", requestData).WithError(err).Error("Failed to save app")
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to save app: %v", err),
})
}
@ -658,14 +669,14 @@ func (httpSvc *HttpService) appsCreateHandler(c echo.Context) error {
func (httpSvc *HttpService) setupHandler(c echo.Context) error {
var setupRequest api.SetupRequest
if err := c.Bind(&setupRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
err := httpSvc.api.Setup(c.Request().Context(), &setupRequest)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to setup node: %s", err.Error()),
})
}
@ -676,14 +687,14 @@ func (httpSvc *HttpService) setupHandler(c echo.Context) error {
func (httpSvc *HttpService) sendPaymentProbesHandler(c echo.Context) error {
var sendPaymentProbesRequest api.SendPaymentProbesRequest
if err := c.Bind(&sendPaymentProbesRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
sendPaymentProbesResponse, err := httpSvc.api.SendPaymentProbes(c.Request().Context(), &sendPaymentProbesRequest)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to send payment probes: %v", err),
})
}
@ -694,14 +705,14 @@ func (httpSvc *HttpService) sendPaymentProbesHandler(c echo.Context) error {
func (httpSvc *HttpService) sendSpontaneousPaymentProbesHandler(c echo.Context) error {
var sendSpontaneousPaymentProbesRequest api.SendSpontaneousPaymentProbesRequest
if err := c.Bind(&sendSpontaneousPaymentProbesRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
sendSpontaneousPaymentProbesResponse, err := httpSvc.api.SendSpontaneousPaymentProbes(c.Request().Context(), &sendSpontaneousPaymentProbesRequest)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to send spontaneous payment probes: %v", err),
})
}
@ -712,21 +723,21 @@ func (httpSvc *HttpService) sendSpontaneousPaymentProbesHandler(c echo.Context)
func (httpSvc *HttpService) getLogOutputHandler(c echo.Context) error {
var getLogRequest api.GetLogOutputRequest
if err := c.Bind(&getLogRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
logType := c.Param("type")
if logType != api.LogTypeNode && logType != api.LogTypeApp {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Invalid log type parameter: '%s'", logType),
})
}
getLogResponse, err := httpSvc.api.GetLogOutput(c.Request().Context(), logType, &getLogRequest)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to get log output: %v", err),
})
}
@ -737,19 +748,19 @@ func (httpSvc *HttpService) getLogOutputHandler(c echo.Context) error {
func (httpSvc *HttpService) createBackupHandler(c echo.Context) error {
var backupRequest api.BasicBackupRequest
if err := c.Bind(&backupRequest); err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Bad request: %s", err.Error()),
})
}
if !httpSvc.svc.cfg.CheckUnlockPassword(backupRequest.UnlockPassword) {
return c.JSON(http.StatusUnauthorized, models.ErrorResponse{
if !httpSvc.cfg.CheckUnlockPassword(backupRequest.UnlockPassword) {
return c.JSON(http.StatusUnauthorized, ErrorResponse{
Message: "Invalid password",
})
}
var buffer bytes.Buffer
err := httpSvc.api.CreateBackup(&backupRequest, &buffer)
err := httpSvc.api.GetBackupService().CreateBackup(backupRequest.UnlockPassword, &buffer)
if err != nil {
return c.String(500, fmt.Sprintf("Failed to create backup: %v", err))
}
@ -774,22 +785,22 @@ func (httpSvc *HttpService) restoreBackupHandler(c echo.Context) error {
fileHeader, err := c.FormFile("backup")
if err != nil {
return c.JSON(http.StatusBadRequest, models.ErrorResponse{
return c.JSON(http.StatusBadRequest, ErrorResponse{
Message: fmt.Sprintf("Failed to get backup file header: %v", err),
})
}
file, err := fileHeader.Open()
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to open backup file: %v", err),
})
}
defer file.Close()
err = httpSvc.api.RestoreBackup(password, file)
err = httpSvc.api.GetBackupService().RestoreBackup(password, file)
if err != nil {
return c.JSON(http.StatusInternalServerError, models.ErrorResponse{
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Message: fmt.Sprintf("Failed to restore backup: %v", err),
})
}

View file

@ -1,4 +1,3 @@
// TODO: move to http/models.go
package http
type ErrorResponse struct {

View file

@ -1,6 +1,6 @@
//go:build !skip_breez
package main
package breez
import (
"context"
@ -16,7 +16,8 @@ import (
decodepay "github.com/nbd-wtf/ln-decodepay"
"github.com/sirupsen/logrus"
"github.com/getAlby/nostr-wallet-connect/models/lnclient"
"github.com/getAlby/nostr-wallet-connect/lnclient"
"github.com/getAlby/nostr-wallet-connect/nip47"
)
type BreezService struct {
@ -102,7 +103,7 @@ func (bs *BreezService) Shutdown() error {
return bs.svc.Disconnect()
}
func (bs *BreezService) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.Nip47PayInvoiceResponse, error) {
func (bs *BreezService) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.PayInvoiceResponse, error) {
sendPaymentRequest := breez_sdk.SendPaymentRequest{
Bolt11: payReq,
}
@ -114,7 +115,7 @@ func (bs *BreezService) SendPaymentSync(ctx context.Context, payReq string) (*ln
if resp.Payment.Details != nil {
lnDetails, _ = resp.Payment.Details.(breez_sdk.PaymentDetailsLn)
}
return &lnclient.Nip47PayInvoiceResponse{
return &lnclient.PayInvoiceResponse{
Preimage: lnDetails.Data.PaymentPreimage,
}, nil
@ -153,7 +154,7 @@ func (bs *BreezService) GetBalance(ctx context.Context) (balance int64, err erro
return int64(info.MaxPayableMsat), nil
}
func (bs *BreezService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *Nip47Transaction, err error) {
func (bs *BreezService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *nip47.Transaction, err error) {
expiry32 := uint32(expiry)
receivePaymentRequest := breez_sdk.ReceivePaymentRequest{
// amount provided in msat
@ -166,7 +167,7 @@ func (bs *BreezService) MakeInvoice(ctx context.Context, amount int64, descripti
return nil, err
}
tx := &Nip47Transaction{
tx := &nip47.Transaction{
Type: "incoming",
Invoice: resp.LnInvoice.Bolt11,
Preimage: hex.EncodeToString(resp.LnInvoice.PaymentSecret),
@ -191,7 +192,7 @@ func (bs *BreezService) MakeInvoice(ctx context.Context, amount int64, descripti
return tx, nil
}
func (bs *BreezService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *Nip47Transaction, err error) {
func (bs *BreezService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *nip47.Transaction, err error) {
log.Printf("p: %v", paymentHash)
payment, err := bs.svc.PaymentByHash(paymentHash)
if err != nil {
@ -209,7 +210,7 @@ func (bs *BreezService) LookupInvoice(ctx context.Context, paymentHash string) (
}
}
func (bs *BreezService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []Nip47Transaction, err error) {
func (bs *BreezService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []nip47.Transaction, err error) {
request := breez_sdk.ListPaymentsRequest{}
if limit > 0 {
@ -234,7 +235,7 @@ func (bs *BreezService) ListTransactions(ctx context.Context, from, until, limit
return nil, err
}
transactions = []Nip47Transaction{}
transactions = []nip47.Transaction{}
for _, payment := range payments {
if payment.PaymentType != breez_sdk.PaymentTypeReceived && payment.PaymentType != breez_sdk.PaymentTypeSent {
// skip other types of payments for now
@ -280,7 +281,7 @@ func (bs *BreezService) CloseChannel(ctx context.Context, closeChannelRequest *l
return nil, nil
}
func breezPaymentToTransaction(payment *breez_sdk.Payment) (*Nip47Transaction, error) {
func breezPaymentToTransaction(payment *breez_sdk.Payment) (*nip47.Transaction, error) {
var lnDetails breez_sdk.PaymentDetailsLn
if payment.Details != nil {
lnDetails, _ = payment.Details.(breez_sdk.PaymentDetailsLn)
@ -312,7 +313,7 @@ func breezPaymentToTransaction(payment *breez_sdk.Payment) (*Nip47Transaction, e
descriptionHash = paymentRequest.DescriptionHash
}
tx := &Nip47Transaction{
tx := &nip47.Transaction{
Type: txType,
Invoice: lnDetails.Data.Bolt11,
Preimage: lnDetails.Data.PaymentPreimage,
@ -463,3 +464,5 @@ func (bs *BreezService) GetStorageDir() (string, error) {
func (bs *BreezService) GetNetworkGraph(nodeIds []string) (lnclient.NetworkGraphResponse, error) {
return nil, nil
}
func (bs *BreezService) UpdateLastWalletSyncRequest() {}

View file

@ -1,11 +1,11 @@
//go:build skip_breez
package main
package breez
import (
"github.com/sirupsen/logrus"
"github.com/getAlby/nostr-wallet-connect/models/lnclient"
"github.com/getAlby/nostr-wallet-connect/lnclient"
)
func NewBreezService(logger *logrus.Logger, mnemonic, apiKey, inviteCode, workDir string) (result lnclient.LNClient, err error) {

View file

@ -1,4 +1,4 @@
package main
package greenlight
import (
"context"
@ -19,18 +19,20 @@ import (
decodepay "github.com/nbd-wtf/ln-decodepay"
"github.com/sirupsen/logrus"
"github.com/getAlby/nostr-wallet-connect/models/lnclient"
"github.com/getAlby/nostr-wallet-connect/config"
"github.com/getAlby/nostr-wallet-connect/lnclient"
"github.com/getAlby/nostr-wallet-connect/nip47"
)
type GreenlightService struct {
workdir string
client *glalby.BlockingGreenlightAlbyClient
svc *Service
logger *logrus.Logger
}
const DEVICE_CREDENTIALS_KEY = "GreenlightCreds"
func NewGreenlightService(svc *Service, mnemonic, inviteCode, workDir, encryptionKey string) (result lnclient.LNClient, err error) {
func NewGreenlightService(cfg config.Config, logger *logrus.Logger, mnemonic, inviteCode, workDir, encryptionKey string) (result lnclient.LNClient, err error) {
if mnemonic == "" || inviteCode == "" || workDir == "" {
return nil, errors.New("one or more required greenlight configuration are missing")
}
@ -44,35 +46,35 @@ func NewGreenlightService(svc *Service, mnemonic, inviteCode, workDir, encryptio
}
var credentials *glalby.GreenlightCredentials
existingDeviceCreds, _ := svc.cfg.Get(DEVICE_CREDENTIALS_KEY, encryptionKey)
existingDeviceCreds, _ := cfg.Get(DEVICE_CREDENTIALS_KEY, encryptionKey)
if existingDeviceCreds != "" {
credentials = &glalby.GreenlightCredentials{
GlCreds: existingDeviceCreds,
}
svc.Logger.Info("Using saved greenlight credentials")
logger.Info("Using saved greenlight credentials")
}
if credentials == nil {
svc.Logger.Info("No greenlight credentials found, attempting to recover existing node")
logger.Info("No greenlight credentials found, attempting to recover existing node")
recoveredCredentials, err := glalby.Recover(mnemonic)
credentials = &recoveredCredentials
if err != nil {
svc.Logger.Errorf("Failed to recover node: %v", err)
svc.Logger.Infof("Trying to register instead...")
logger.Errorf("Failed to recover node: %v", err)
logger.Infof("Trying to register instead...")
recoveredCredentials, err := glalby.Register(mnemonic, inviteCode)
credentials = &recoveredCredentials
if err != nil {
svc.Logger.Fatalf("Failed to register new node")
logger.Fatalf("Failed to register new node")
}
}
if credentials == nil || credentials.GlCreds == "" {
return nil, errors.New("unexpected response from Recover")
}
svc.cfg.SetUpdate(DEVICE_CREDENTIALS_KEY, credentials.GlCreds, encryptionKey)
cfg.SetUpdate(DEVICE_CREDENTIALS_KEY, credentials.GlCreds, encryptionKey)
}
client, err := glalby.NewBlockingGreenlightAlbyClient(mnemonic, *credentials)
@ -88,7 +90,7 @@ func NewGreenlightService(svc *Service, mnemonic, inviteCode, workDir, encryptio
gs := GreenlightService{
workdir: newpath,
client: client,
svc: svc,
logger: logger,
}
nodeInfo, err := client.GetInfo()
@ -105,23 +107,23 @@ func NewGreenlightService(svc *Service, mnemonic, inviteCode, workDir, encryptio
func (gs *GreenlightService) Shutdown() error {
_, err := gs.client.Shutdown()
if err != nil {
gs.svc.Logger.WithError(err).Error("Failed to shutdown greenlight node")
gs.logger.WithError(err).Error("Failed to shutdown greenlight node")
return err
}
return nil
}
func (gs *GreenlightService) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.Nip47PayInvoiceResponse, error) {
func (gs *GreenlightService) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.PayInvoiceResponse, error) {
response, err := gs.client.Pay(glalby.PayRequest{
Bolt11: payReq,
})
if err != nil {
gs.svc.Logger.Errorf("Failed to send payment: %v", err)
gs.logger.Errorf("Failed to send payment: %v", err)
return nil, err
}
log.Printf("SendPaymentSync succeeded: %v", response.Preimage)
return &lnclient.Nip47PayInvoiceResponse{
return &lnclient.PayInvoiceResponse{
Preimage: response.Preimage,
}, nil
}
@ -146,7 +148,7 @@ func (gs *GreenlightService) SendKeysend(ctx context.Context, amount int64, dest
})
if err != nil {
gs.svc.Logger.Errorf("Failed to send keysend payment: %v", err)
gs.logger.Errorf("Failed to send keysend payment: %v", err)
return "", err
}
@ -157,7 +159,7 @@ func (gs *GreenlightService) GetBalance(ctx context.Context) (balance int64, err
response, err := gs.client.ListFunds(glalby.ListFundsRequest{})
if err != nil {
gs.svc.Logger.Errorf("Failed to list funds: %v", err)
gs.logger.Errorf("Failed to list funds: %v", err)
return 0, err
}
@ -171,7 +173,7 @@ func (gs *GreenlightService) GetBalance(ctx context.Context) (balance int64, err
return balance, nil
}
func (gs *GreenlightService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *Nip47Transaction, err error) {
func (gs *GreenlightService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *nip47.Transaction, err error) {
uexpiry := uint64(expiry)
// TODO: it seems description hash cannot be passed to greenlight
invoice, err := gs.client.MakeInvoice(glalby.MakeInvoiceRequest{
@ -182,13 +184,13 @@ func (gs *GreenlightService) MakeInvoice(ctx context.Context, amount int64, desc
})
if err != nil {
gs.svc.Logger.Errorf("MakeInvoice failed: %v", err)
gs.logger.Errorf("MakeInvoice failed: %v", err)
return nil, err
}
paymentRequest, err := decodepay.Decodepay(strings.ToLower(invoice.Bolt11))
if err != nil {
gs.svc.Logger.WithFields(logrus.Fields{
gs.logger.WithFields(logrus.Fields{
"invoice": invoice.Bolt11,
}).Errorf("Failed to decode bolt11 invoice: %v", invoice.Bolt11)
return nil, err
@ -197,7 +199,7 @@ func (gs *GreenlightService) MakeInvoice(ctx context.Context, amount int64, desc
description = paymentRequest.Description
descriptionHash = paymentRequest.DescriptionHash
expiresAt := int64(invoice.ExpiresAt)
transaction = &Nip47Transaction{
transaction = &nip47.Transaction{
Type: "incoming",
Invoice: invoice.Bolt11,
Description: description,
@ -211,13 +213,13 @@ func (gs *GreenlightService) MakeInvoice(ctx context.Context, amount int64, desc
return transaction, nil
}
func (gs *GreenlightService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *Nip47Transaction, err error) {
func (gs *GreenlightService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *nip47.Transaction, err error) {
response, err := gs.client.ListInvoices(glalby.ListInvoicesRequest{
PaymentHash: &paymentHash,
})
if err != nil {
gs.svc.Logger.Errorf("ListInvoices failed: %v", err)
gs.logger.Errorf("ListInvoices failed: %v", err)
return nil, err
}
@ -233,22 +235,22 @@ func (gs *GreenlightService) LookupInvoice(ctx context.Context, paymentHash stri
transaction, err = gs.greenlightInvoiceToTransaction(&invoice)
if err != nil {
gs.svc.Logger.Errorf("Failed to map invoice: %v", err)
gs.logger.Errorf("Failed to map invoice: %v", err)
return nil, err
}
return transaction, nil
}
func (gs *GreenlightService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []Nip47Transaction, err error) {
func (gs *GreenlightService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []nip47.Transaction, err error) {
listInvoicesResponse, err := gs.client.ListInvoices(glalby.ListInvoicesRequest{})
if err != nil {
gs.svc.Logger.Errorf("ListInvoices failed: %v", err)
gs.logger.Errorf("ListInvoices failed: %v", err)
return nil, err
}
transactions = []Nip47Transaction{}
transactions = []nip47.Transaction{}
if err != nil {
log.Printf("ListInvoices failed: %v", err)
@ -272,7 +274,7 @@ func (gs *GreenlightService) ListTransactions(ctx context.Context, from, until,
listPaymentsResponse, err := gs.client.ListPayments(glalby.ListPaymentsRequest{})
if err != nil {
gs.svc.Logger.Errorf("ListPayments failed: %v", err)
gs.logger.Errorf("ListPayments failed: %v", err)
return nil, err
}
@ -341,7 +343,7 @@ func (gs *GreenlightService) GetInfo(ctx context.Context) (info *lnclient.NodeIn
nodeInfo, err := gs.client.GetInfo()
if err != nil {
gs.svc.Logger.Errorf("GetInfo failed: %v", err)
gs.logger.Errorf("GetInfo failed: %v", err)
return nil, err
}
@ -359,7 +361,7 @@ func (gs *GreenlightService) ListChannels(ctx context.Context) ([]lnclient.Chann
response, err := gs.client.ListFunds(glalby.ListFundsRequest{})
if err != nil {
gs.svc.Logger.Errorf("Failed to list funds: %v", err)
gs.logger.Errorf("Failed to list funds: %v", err)
return nil, err
}
@ -396,7 +398,7 @@ func (gs *GreenlightService) ListChannels(ctx context.Context) ([]lnclient.Chann
func (gs *GreenlightService) GetNodeConnectionInfo(ctx context.Context) (nodeConnectionInfo *lnclient.NodeConnectionInfo, err error) {
info, err := gs.GetInfo(ctx)
if err != nil {
gs.svc.Logger.Errorf("GetInfo failed: %v", err)
gs.logger.Errorf("GetInfo failed: %v", err)
return nil, err
}
return &lnclient.NodeConnectionInfo{
@ -419,7 +421,7 @@ func (gs *GreenlightService) ConnectPeer(ctx context.Context, connectPeerRequest
Port: port,
})
if err != nil {
gs.svc.Logger.Errorf("ConnectPeer failed: %v", err)
gs.logger.Errorf("ConnectPeer failed: %v", err)
return err
}
return nil
@ -436,7 +438,7 @@ func (gs *GreenlightService) OpenChannel(ctx context.Context, openChannelRequest
// Minconf: &minConf,
})
if err != nil {
gs.svc.Logger.Errorf("OpenChannel failed: %v", err)
gs.logger.Errorf("OpenChannel failed: %v", err)
return nil, err
}
@ -450,7 +452,7 @@ func (gs *GreenlightService) CloseChannel(ctx context.Context, closeChannelReque
Id: closeChannelRequest.ChannelId,
})
if err != nil {
gs.svc.Logger.WithError(err).Error("CloseChannel failed")
gs.logger.WithError(err).Error("CloseChannel failed")
return nil, err
}
@ -461,7 +463,7 @@ func (gs *GreenlightService) GetNewOnchainAddress(ctx context.Context) (string,
newAddressResponse, err := gs.client.NewAddress(glalby.NewAddressRequest{})
if err != nil {
gs.svc.Logger.Errorf("NewAddress failed: %v", err)
gs.logger.Errorf("NewAddress failed: %v", err)
return "", err
}
if newAddressResponse.Bech32 == nil {
@ -473,10 +475,10 @@ func (gs *GreenlightService) GetNewOnchainAddress(ctx context.Context) (string,
func (gs *GreenlightService) GetOnchainBalance(ctx context.Context) (*lnclient.OnchainBalanceResponse, error) {
response, err := gs.client.ListFunds(glalby.ListFundsRequest{})
gs.svc.Logger.WithField("response", response).Info("Listed funds")
gs.logger.WithField("response", response).Info("Listed funds")
if err != nil {
gs.svc.Logger.Errorf("Failed to list funds: %v", err)
gs.logger.Errorf("Failed to list funds: %v", err)
return nil, err
}
@ -505,10 +507,10 @@ func (gs *GreenlightService) RedeemOnchainFunds(ctx context.Context, toAddress s
Amount: &amountAll,
})
if err != nil {
gs.svc.Logger.WithError(err).Error("Withdraw failed")
gs.logger.WithError(err).Error("Withdraw failed")
return "", err
}
gs.svc.Logger.WithField("txId", txId).Info("Redeeming On-Chain funds")
gs.logger.WithField("txId", txId).Info("Redeeming On-Chain funds")
return txId.Txid, nil
}
@ -535,14 +537,14 @@ func (gs *GreenlightService) SignMessage(ctx context.Context, message string) (s
})
if err != nil {
gs.svc.Logger.Errorf("SignMessage failed: %v", err)
gs.logger.Errorf("SignMessage failed: %v", err)
return "", err
}
return response.Zbase, nil
}
func (gs *GreenlightService) greenlightInvoiceToTransaction(invoice *glalby.ListInvoicesInvoice) (*Nip47Transaction, error) {
func (gs *GreenlightService) greenlightInvoiceToTransaction(invoice *glalby.ListInvoicesInvoice) (*nip47.Transaction, error) {
description := ""
descriptionHash := ""
if invoice.Description != nil {
@ -551,7 +553,7 @@ func (gs *GreenlightService) greenlightInvoiceToTransaction(invoice *glalby.List
bolt11 := *invoice.Bolt11
paymentRequest, err := decodepay.Decodepay(strings.ToLower(bolt11))
if err != nil {
gs.svc.Logger.WithFields(logrus.Fields{
gs.logger.WithFields(logrus.Fields{
"invoice": bolt11,
}).Errorf("Failed to decode bolt11 invoice: %v", bolt11)
return nil, err
@ -580,7 +582,7 @@ func (gs *GreenlightService) greenlightInvoiceToTransaction(invoice *glalby.List
settledAt = &paidAt
}
transaction := &Nip47Transaction{
transaction := &nip47.Transaction{
Type: "incoming",
Invoice: bolt11,
Description: description,
@ -603,14 +605,14 @@ func (gs *GreenlightService) ResetRouter(key string) error {
func (gs *GreenlightService) GetBalances(ctx context.Context) (*lnclient.BalancesResponse, error) {
onchainBalance, err := gs.GetOnchainBalance(ctx)
if err != nil {
gs.svc.Logger.WithError(err).Error("Failed to retrieve onchain balance")
gs.logger.WithError(err).Error("Failed to retrieve onchain balance")
return nil, err
}
response, err := gs.client.ListFunds(glalby.ListFundsRequest{})
if err != nil {
gs.svc.Logger.Errorf("Failed to list funds: %v", err)
gs.logger.Errorf("Failed to list funds: %v", err)
return nil, err
}
@ -662,3 +664,5 @@ func (gs *GreenlightService) GetNodeStatus(ctx context.Context) (nodeStatus *lnc
func (gs *GreenlightService) GetNetworkGraph(nodeIds []string) (lnclient.NetworkGraphResponse, error) {
return nil, nil
}
func (gs *GreenlightService) UpdateLastWalletSyncRequest() {}

View file

@ -1,7 +1,6 @@
// TODO: move to greenlight/models.go
package greenlight
import "github.com/getAlby/nostr-wallet-connect/models/lnclient"
import "github.com/getAlby/nostr-wallet-connect/lnclient"
type NodeInfo struct {
ID string `json:"id"`

View file

@ -1,4 +1,4 @@
package main
package ldk
import (
"context"
@ -20,27 +20,31 @@ import (
decodepay "github.com/nbd-wtf/ln-decodepay"
"github.com/sirupsen/logrus"
"github.com/getAlby/nostr-wallet-connect/config"
"github.com/getAlby/nostr-wallet-connect/events"
"github.com/getAlby/nostr-wallet-connect/models/config"
"github.com/getAlby/nostr-wallet-connect/models/lnclient"
"github.com/getAlby/nostr-wallet-connect/models/lsp"
"github.com/getAlby/nostr-wallet-connect/lnclient"
"github.com/getAlby/nostr-wallet-connect/lsp"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/getAlby/nostr-wallet-connect/utils"
)
type LDKService struct {
svc *Service
workdir string
node *ldk_node.Node
ldkEventBroadcaster LDKEventBroadcaster
cancel context.CancelFunc
network string
eventPublisher events.EventPublisher
syncing bool
lastSync time.Time
workdir string
node *ldk_node.Node
ldkEventBroadcaster LDKEventBroadcaster
cancel context.CancelFunc
network string
eventPublisher events.EventPublisher
syncing bool
lastSync time.Time
logger *logrus.Logger
cfg config.Config
lastWalletSyncRequest time.Time
}
const resetRouterKey = "ResetRouter"
func NewLDKService(ctx context.Context, svc *Service, mnemonic, workDir string, network string, esploraServer string, gossipSource string) (result lnclient.LNClient, err error) {
func NewLDKService(ctx context.Context, logger *logrus.Logger, cfg config.Config, eventPublisher events.EventPublisher, mnemonic, workDir string, network string, esploraServer string, gossipSource string) (result lnclient.LNClient, err error) {
if mnemonic == "" || workDir == "" {
return nil, errors.New("one or more required LDK configuration are missing")
}
@ -79,7 +83,7 @@ func NewLDKService(ctx context.Context, svc *Service, mnemonic, workDir string,
config.ListeningAddresses = &listeningAddresses
config.LogDirPath = &logDirPath
logLevel, err := strconv.Atoi(svc.cfg.Env.LDKLogLevel)
logLevel, err := strconv.Atoi(cfg.GetEnv().LDKLogLevel)
if err == nil {
config.LogLevel = ldk_node.LogLevel(logLevel)
}
@ -88,10 +92,10 @@ func NewLDKService(ctx context.Context, svc *Service, mnemonic, workDir string,
builder.SetNetwork(network)
builder.SetEsploraServer(esploraServer)
if gossipSource != "" {
svc.Logger.WithField("gossipSource", gossipSource).Warn("LDK RGS instance set")
logger.WithField("gossipSource", gossipSource).Warn("LDK RGS instance set")
builder.SetGossipSourceRgs(gossipSource)
} else {
svc.Logger.Warn("No LDK RGS instance set")
logger.Warn("No LDK RGS instance set")
}
builder.SetStorageDirPath(filepath.Join(newpath, "./storage"))
@ -103,33 +107,34 @@ func NewLDKService(ctx context.Context, svc *Service, mnemonic, workDir string,
node, err := builder.Build()
if err != nil {
svc.Logger.Errorf("Failed to create LDK node: %v", err)
logger.Errorf("Failed to create LDK node: %v", err)
return nil, err
}
ldkEventConsumer := make(chan *ldk_node.Event)
ldkCtx, cancel := context.WithCancel(ctx)
ldkEventBroadcaster := NewLDKEventBroadcaster(svc.Logger, ldkCtx, ldkEventConsumer)
ldkEventBroadcaster := NewLDKEventBroadcaster(logger, ldkCtx, ldkEventConsumer)
ls := LDKService{
workdir: newpath,
node: node,
svc: svc,
cancel: cancel,
ldkEventBroadcaster: ldkEventBroadcaster,
network: network,
eventPublisher: svc.EventPublisher,
eventPublisher: eventPublisher,
logger: logger,
cfg: cfg,
}
// TODO: remove when LDK supports this
deleteOldLDKLogs(svc.Logger, logDirPath)
deleteOldLDKLogs(logger, logDirPath)
go func() {
// delete old LDK logs every 24 hours
ticker := time.NewTicker(24 * time.Hour)
for {
select {
case <-ticker.C:
deleteOldLDKLogs(svc.Logger, logDirPath)
deleteOldLDKLogs(logger, logDirPath)
case <-ldkCtx.Done():
return
}
@ -152,7 +157,7 @@ func NewLDKService(ctx context.Context, svc *Service, mnemonic, workDir string,
continue
}
ls.logLdkEvent(ldkCtx, event)
ls.handleLdkEvent(ldkCtx, event)
ldkEventConsumer <- event
node.EventHandled()
@ -162,12 +167,12 @@ func NewLDKService(ctx context.Context, svc *Service, mnemonic, workDir string,
err = node.Start()
if err != nil {
svc.Logger.Errorf("Failed to start LDK node: %v", err)
logger.Errorf("Failed to start LDK node: %v", err)
return nil, err
}
nodeId := node.NodeId()
svc.Logger.WithFields(logrus.Fields{
logger.WithFields(logrus.Fields{
"nodeId": nodeId,
"status": node.Status(),
}).Info("Started LDK node. Syncing wallet...")
@ -175,17 +180,17 @@ func NewLDKService(ctx context.Context, svc *Service, mnemonic, workDir string,
syncStartTime := time.Now()
err = node.SyncWallets()
if err != nil {
svc.Logger.WithError(err).Error("Failed to sync LDK wallets")
logger.WithError(err).Error("Failed to sync LDK wallets")
shutdownErr := ls.Shutdown()
if shutdownErr != nil {
svc.Logger.WithError(shutdownErr).Error("Failed to shutdown LDK node")
logger.WithError(shutdownErr).Error("Failed to shutdown LDK node")
}
return nil, err
}
ls.lastSync = time.Now()
svc.Logger.WithFields(logrus.Fields{
logger.WithFields(logrus.Fields{
"nodeId": nodeId,
"status": node.Status(),
"duration": math.Ceil(time.Since(syncStartTime).Seconds()),
@ -193,18 +198,19 @@ func NewLDKService(ctx context.Context, svc *Service, mnemonic, workDir string,
if ls.network == "bitcoin" {
// try to connect to some peers to retrieve P2P gossip data. TODO: Remove once LDK can correctly do gossip with CLN and Eclair nodes
// see https://github.com/lightningdevkit/rust-lightning/issues/3075
peers := []string{
"031b301307574bbe9b9ac7b79cbe1700e31e544513eae0b5d7497483083f99e581@45.79.192.236:9735", // Olympus
"0364913d18a19c671bb36dd04d6ad5be0fe8f2894314c36a9db3f03c2d414907e1@192.243.215.102:9735", // LQwD
"035e4ff418fc8b5554c5d9eea66396c227bd429a3251c8cbc711002ba215bfc226@170.75.163.209:9735", // WoS
"02fcc5bfc48e83f06c04483a2985e1c390cb0f35058baa875ad2053858b8e80dbd@35.239.148.251:9735", // Blink
}
svc.Logger.Info("Connecting to some peers to retrieve P2P gossip data")
logger.Info("Connecting to some peers to retrieve P2P gossip data")
for _, peer := range peers {
parts := strings.FieldsFunc(peer, func(r rune) bool { return r == '@' || r == ':' })
port, err := strconv.ParseUint(parts[2], 10, 16)
if err != nil {
svc.Logger.WithError(err).Error("Failed to parse port number")
logger.WithError(err).Error("Failed to parse port number")
continue
}
err = ls.ConnectPeer(ctx, &lnclient.ConnectPeerRequest{
@ -213,7 +219,7 @@ func NewLDKService(ctx context.Context, svc *Service, mnemonic, workDir string,
Port: uint16(port),
})
if err != nil {
svc.Logger.WithField("peer", peer).WithError(err).Error("Failed to connect to peer")
logger.WithField("peer", peer).WithError(err).Error("Failed to connect to peer")
}
}
}
@ -228,26 +234,26 @@ func NewLDKService(ctx context.Context, svc *Service, mnemonic, workDir string,
return
case <-time.After(MIN_SYNC_INTERVAL):
if time.Since(ls.svc.lastWalletSyncRequest) > MIN_SYNC_INTERVAL && time.Since(ls.lastSync) < MAX_SYNC_INTERVAL {
// ls.svc.Logger.Debug("skipping background wallet sync")
if time.Since(ls.lastWalletSyncRequest) > MIN_SYNC_INTERVAL && time.Since(ls.lastSync) < MAX_SYNC_INTERVAL {
// ls.logger.Debug("skipping background wallet sync")
continue
}
ls.svc.Logger.Info("Starting background wallet sync")
ls.logger.Info("Starting background wallet sync")
syncStartTime := time.Now()
ls.syncing = true
err = node.SyncWallets()
ls.syncing = false
if err != nil {
svc.Logger.WithError(err).Error("Failed to sync LDK wallets")
logger.WithError(err).Error("Failed to sync LDK wallets")
// try again at next MIN_SYNC_INTERVAL
continue
}
ls.lastSync = time.Now()
svc.Logger.WithFields(logrus.Fields{
logger.WithFields(logrus.Fields{
"nodeId": nodeId,
"status": node.Status(),
"duration": math.Ceil(time.Since(syncStartTime).Seconds()),
@ -261,23 +267,23 @@ func NewLDKService(ctx context.Context, svc *Service, mnemonic, workDir string,
func (ls *LDKService) Shutdown() error {
if ls.node == nil {
ls.svc.Logger.Infof("LDK client already shut down")
ls.logger.Infof("LDK client already shut down")
return nil
}
// make sure nothing else can use it
node := ls.node
ls.node = nil
ls.svc.Logger.Infof("shutting down LDK client")
ls.svc.Logger.Infof("cancelling LDK context")
ls.logger.Infof("shutting down LDK client")
ls.logger.Infof("cancelling LDK context")
ls.cancel()
for ls.syncing {
ls.svc.Logger.Infof("Waiting for background sync to finish before stopping LDK node...")
ls.logger.Infof("Waiting for background sync to finish before stopping LDK node...")
time.Sleep(1 * time.Second)
}
ls.svc.Logger.Infof("stopping LDK node")
ls.logger.Infof("stopping LDK node")
shutdownChannel := make(chan error)
go func() {
shutdownChannel <- node.Stop()
@ -286,44 +292,44 @@ func (ls *LDKService) Shutdown() error {
select {
case err := <-shutdownChannel:
if err != nil {
ls.svc.Logger.WithError(err).Error("Failed to stop LDK node")
ls.logger.WithError(err).Error("Failed to stop LDK node")
// do not return error - we still need to destroy the node
} else {
ls.svc.Logger.Info("LDK stop node succeeded")
ls.logger.Info("LDK stop node succeeded")
}
case <-time.After(120 * time.Second):
ls.svc.Logger.Error("Timeout shutting down LDK node after 120 seconds")
ls.logger.Error("Timeout shutting down LDK node after 120 seconds")
}
ls.svc.Logger.Infof("Destroying node object")
ls.logger.Infof("Destroying node object")
node.Destroy()
ls.resetRouterInternal()
ls.svc.Logger.Infof("LDK shutdown complete")
ls.logger.Infof("LDK shutdown complete")
return nil
}
func (ls *LDKService) resetRouterInternal() {
key, err := ls.svc.cfg.Get(resetRouterKey, "")
key, err := ls.cfg.Get(resetRouterKey, "")
if err != nil {
ls.svc.Logger.Error("Failed to retrieve ResetRouter key")
ls.logger.Error("Failed to retrieve ResetRouter key")
}
if key != "" {
ls.svc.cfg.SetUpdate(resetRouterKey, "", "")
ls.svc.Logger.WithField("key", key).Info("Resetting router")
ls.cfg.SetUpdate(resetRouterKey, "", "")
ls.logger.WithField("key", key).Info("Resetting router")
ldkDbPath := filepath.Join(ls.workdir, "storage", "ldk_node_data.sqlite")
if _, err := os.Stat(ldkDbPath); errors.Is(err, os.ErrNotExist) {
ls.svc.Logger.Error("Could not find LDK database")
ls.logger.Error("Could not find LDK database")
return
}
ldkDb, err := sql.Open("sqlite", ldkDbPath)
if err != nil {
ls.svc.Logger.Error("Could not open LDK DB file")
ls.logger.Error("Could not open LDK DB file")
return
}
@ -339,43 +345,43 @@ func (ls *LDKService) resetRouterInternal() {
case "NetworkGraph":
command = "delete from ldk_node_data where key = 'network_graph';VACUUM;"
default:
ls.svc.Logger.WithField("key", key).Error("Unknown reset router key")
ls.logger.WithField("key", key).Error("Unknown reset router key")
return
}
result, err := ldkDb.Exec(command)
if err != nil {
ls.svc.Logger.WithError(err).Error("Failed execute reset command")
ls.logger.WithError(err).Error("Failed execute reset command")
return
}
rowsAffected, err := result.RowsAffected()
if err != nil {
ls.svc.Logger.WithError(err).Error("Failed to get rows affected")
ls.logger.WithError(err).Error("Failed to get rows affected")
return
}
ls.svc.Logger.WithFields(logrus.Fields{
ls.logger.WithFields(logrus.Fields{
"rowsAffected": rowsAffected,
}).Info("Reset router")
if err != nil {
ls.svc.Logger.WithField("key", key).WithError(err).Error("ResetRouter failed")
ls.logger.WithField("key", key).WithError(err).Error("ResetRouter failed")
}
}
}
func (gs *LDKService) SendPaymentSync(ctx context.Context, invoice string) (*lnclient.Nip47PayInvoiceResponse, error) {
func (ls *LDKService) SendPaymentSync(ctx context.Context, invoice string) (*lnclient.PayInvoiceResponse, error) {
paymentRequest, err := decodepay.Decodepay(invoice)
if err != nil {
gs.svc.Logger.WithFields(logrus.Fields{
ls.logger.WithFields(logrus.Fields{
"bolt11": invoice,
}).Errorf("Failed to decode bolt11 invoice: %v", err)
return nil, err
}
maxSpendable := gs.getMaxSpendable()
maxSpendable := ls.getMaxSpendable()
if paymentRequest.MSatoshi > maxSpendable {
gs.eventPublisher.Publish(&events.Event{
ls.eventPublisher.Publish(&events.Event{
Event: "nwc_outgoing_liquidity_required",
Properties: map[string]interface{}{
//"amount": amount / 1000,
@ -387,12 +393,12 @@ func (gs *LDKService) SendPaymentSync(ctx context.Context, invoice string) (*lnc
}
paymentStart := time.Now()
ldkEventSubscription := gs.ldkEventBroadcaster.Subscribe()
defer gs.ldkEventBroadcaster.CancelSubscription(ldkEventSubscription)
ldkEventSubscription := ls.ldkEventBroadcaster.Subscribe()
defer ls.ldkEventBroadcaster.CancelSubscription(ldkEventSubscription)
paymentHash, err := gs.node.Bolt11Payment().Send(invoice)
paymentHash, err := ls.node.Bolt11Payment().Send(invoice)
if err != nil {
gs.svc.Logger.WithError(err).Error("SendPayment failed")
ls.logger.WithError(err).Error("SendPayment failed")
return nil, err
}
fee := uint64(0)
@ -405,23 +411,23 @@ func (gs *LDKService) SendPaymentSync(ctx context.Context, invoice string) (*lnc
eventPaymentFailed, isEventPaymentFailedEvent := (*event).(ldk_node.EventPaymentFailed)
if isEventPaymentSuccessfulEvent && eventPaymentSuccessful.PaymentHash == paymentHash {
gs.svc.Logger.Info("Got payment success event")
payment := gs.node.Payment(paymentHash)
ls.logger.Info("Got payment success event")
payment := ls.node.Payment(paymentHash)
if payment == nil {
gs.svc.Logger.Errorf("Couldn't find payment by payment hash: %v", paymentHash)
ls.logger.Errorf("Couldn't find payment by payment hash: %v", paymentHash)
return nil, errors.New("Payment not found")
}
bolt11PaymentKind, ok := payment.Kind.(ldk_node.PaymentKindBolt11)
if !ok {
gs.svc.Logger.WithFields(logrus.Fields{
ls.logger.WithFields(logrus.Fields{
"payment": payment,
}).Error("Payment is not a bolt11 kind")
}
if bolt11PaymentKind.Preimage == nil {
gs.svc.Logger.Errorf("No payment preimage for payment hash: %v", paymentHash)
ls.logger.Errorf("No payment preimage for payment hash: %v", paymentHash)
return nil, errors.New("Payment preimage not found")
}
preimage = *bolt11PaymentKind.Preimage
@ -454,7 +460,7 @@ func (gs *LDKService) SendPaymentSync(ctx context.Context, invoice string) (*lnc
failureReasonMessage = "UnknownError"
}
gs.svc.Logger.WithFields(logrus.Fields{
ls.logger.WithFields(logrus.Fields{
"paymentHash": paymentHash,
"failureReason": failureReason,
"failureReasonMessage": failureReasonMessage,
@ -468,18 +474,18 @@ func (gs *LDKService) SendPaymentSync(ctx context.Context, invoice string) (*lnc
return nil, errors.New("Payment timed out")
}
gs.svc.Logger.WithFields(logrus.Fields{
ls.logger.WithFields(logrus.Fields{
"duration": time.Since(paymentStart).Milliseconds(),
"fee": fee,
}).Info("Successful payment")
return &lnclient.Nip47PayInvoiceResponse{
return &lnclient.PayInvoiceResponse{
Preimage: preimage,
Fee: &fee,
}, nil
}
func (gs *LDKService) SendKeysend(ctx context.Context, amount int64, destination, preimage string, custom_records []lnclient.TLVRecord) (preImage string, err error) {
func (ls *LDKService) SendKeysend(ctx context.Context, amount int64, destination, preimage string, custom_records []lnclient.TLVRecord) (preImage string, err error) {
paymentStart := time.Now()
customTlvs := []ldk_node.TlvEntry{}
@ -490,16 +496,16 @@ func (gs *LDKService) SendKeysend(ctx context.Context, amount int64, destination
})
}
ldkEventSubscription := gs.ldkEventBroadcaster.Subscribe()
defer gs.ldkEventBroadcaster.CancelSubscription(ldkEventSubscription)
ldkEventSubscription := ls.ldkEventBroadcaster.Subscribe()
defer ls.ldkEventBroadcaster.CancelSubscription(ldkEventSubscription)
paymentHash, err := gs.node.SpontaneousPayment().Send(uint64(amount), destination, customTlvs)
paymentHash, err := ls.node.SpontaneousPayment().Send(uint64(amount), destination, customTlvs)
if err != nil {
gs.svc.Logger.WithError(err).Error("Keysend failed")
ls.logger.WithError(err).Error("Keysend failed")
return "", err
}
gs.svc.Logger.Infof("TODO: listen for events %v", paymentHash)
ls.logger.Infof("TODO: listen for events %v", paymentHash)
fee := uint64(0)
@ -510,23 +516,23 @@ func (gs *LDKService) SendKeysend(ctx context.Context, amount int64, destination
eventPaymentFailed, isEventPaymentFailedEvent := (*event).(ldk_node.EventPaymentFailed)
if isEventPaymentSuccessfulEvent && eventPaymentSuccessful.PaymentHash == paymentHash {
gs.svc.Logger.Info("Got payment success event")
payment := gs.node.Payment(paymentHash)
ls.logger.Info("Got payment success event")
payment := ls.node.Payment(paymentHash)
if payment == nil {
gs.svc.Logger.Errorf("Couldn't find payment by payment hash: %v", paymentHash)
ls.logger.Errorf("Couldn't find payment by payment hash: %v", paymentHash)
return "", errors.New("Payment not found")
}
spontaneousPaymentKind, ok := payment.Kind.(ldk_node.PaymentKindSpontaneous)
if !ok {
gs.svc.Logger.WithFields(logrus.Fields{
ls.logger.WithFields(logrus.Fields{
"payment": payment,
}).Error("Payment is not a spontaneous kind")
}
if spontaneousPaymentKind.Preimage == nil {
gs.svc.Logger.Errorf("No payment preimage for payment hash: %v", paymentHash)
ls.logger.Errorf("No payment preimage for payment hash: %v", paymentHash)
return "", errors.New("Payment preimage not found")
}
preimage = *spontaneousPaymentKind.Preimage
@ -559,7 +565,7 @@ func (gs *LDKService) SendKeysend(ctx context.Context, amount int64, destination
failureReasonMessage = "UnknownError"
}
gs.svc.Logger.WithFields(logrus.Fields{
ls.logger.WithFields(logrus.Fields{
"paymentHash": paymentHash,
"failureReason": failureReason,
"failureReasonMessage": failureReasonMessage,
@ -573,15 +579,15 @@ func (gs *LDKService) SendKeysend(ctx context.Context, amount int64, destination
return "", errors.New("keysend payment timed out")
}
gs.svc.Logger.WithFields(logrus.Fields{
ls.logger.WithFields(logrus.Fields{
"duration": time.Since(paymentStart).Milliseconds(),
"fee": fee,
}).Info("Successful keysend payment")
return preimage, nil
}
func (gs *LDKService) GetBalance(ctx context.Context) (balance int64, err error) {
channels := gs.node.ListChannels()
func (ls *LDKService) GetBalance(ctx context.Context) (balance int64, err error) {
channels := ls.node.ListChannels()
balance = 0
for _, channel := range channels {
@ -593,9 +599,9 @@ func (gs *LDKService) GetBalance(ctx context.Context) (balance int64, err error)
return balance, nil
}
func (gs *LDKService) getMaxReceivable() int64 {
func (ls *LDKService) getMaxReceivable() int64 {
var receivable int64 = 0
channels := gs.node.ListChannels()
channels := ls.node.ListChannels()
for _, channel := range channels {
if channel.IsUsable {
receivable += min(int64(channel.InboundCapacityMsat), int64(*channel.InboundHtlcMaximumMsat))
@ -604,9 +610,9 @@ func (gs *LDKService) getMaxReceivable() int64 {
return int64(receivable)
}
func (gs *LDKService) getMaxSpendable() int64 {
func (ls *LDKService) getMaxSpendable() int64 {
var spendable int64 = 0
channels := gs.node.ListChannels()
channels := ls.node.ListChannels()
for _, channel := range channels {
if channel.IsUsable {
spendable += min(int64(channel.OutboundCapacityMsat), int64(*channel.CounterpartyOutboundHtlcMaximumMsat))
@ -615,12 +621,12 @@ func (gs *LDKService) getMaxSpendable() int64 {
return int64(spendable)
}
func (gs *LDKService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *Nip47Transaction, err error) {
func (ls *LDKService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *nip47.Transaction, err error) {
maxReceivable := gs.getMaxReceivable()
maxReceivable := ls.getMaxReceivable()
if amount > maxReceivable {
gs.eventPublisher.Publish(&events.Event{
ls.eventPublisher.Publish(&events.Event{
Event: "nwc_incoming_liquidity_required",
Properties: map[string]interface{}{
//"amount": amount / 1000,
@ -632,19 +638,19 @@ func (gs *LDKService) MakeInvoice(ctx context.Context, amount int64, description
}
// TODO: support passing description hash
invoice, err := gs.node.Bolt11Payment().Receive(uint64(amount),
invoice, err := ls.node.Bolt11Payment().Receive(uint64(amount),
description,
uint32(expiry))
if err != nil {
gs.svc.Logger.WithError(err).Error("MakeInvoice failed")
ls.logger.WithError(err).Error("MakeInvoice failed")
return nil, err
}
var expiresAt *int64
paymentRequest, err := decodepay.Decodepay(invoice)
if err != nil {
gs.svc.Logger.WithFields(logrus.Fields{
ls.logger.WithFields(logrus.Fields{
"bolt11": invoice,
}).Errorf("Failed to decode bolt11 invoice: %v", err)
@ -655,7 +661,7 @@ func (gs *LDKService) MakeInvoice(ctx context.Context, amount int64, description
description = paymentRequest.Description
descriptionHash = paymentRequest.DescriptionHash
transaction = &Nip47Transaction{
transaction = &nip47.Transaction{
Type: "incoming",
Invoice: invoice,
PaymentHash: paymentRequest.PaymentHash,
@ -669,26 +675,26 @@ func (gs *LDKService) MakeInvoice(ctx context.Context, amount int64, description
return transaction, nil
}
func (gs *LDKService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *Nip47Transaction, err error) {
func (ls *LDKService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *nip47.Transaction, err error) {
payment := gs.node.Payment(paymentHash)
payment := ls.node.Payment(paymentHash)
if payment == nil {
gs.svc.Logger.Errorf("Couldn't find payment by payment hash: %v", paymentHash)
ls.logger.Errorf("Couldn't find payment by payment hash: %v", paymentHash)
return nil, errors.New("Payment not found")
}
transaction, err = gs.ldkPaymentToTransaction(payment)
transaction, err = ls.ldkPaymentToTransaction(payment)
if err != nil {
gs.svc.Logger.Errorf("Failed to map transaction: %v", err)
ls.logger.Errorf("Failed to map transaction: %v", err)
return nil, err
}
return transaction, nil
}
func (ls *LDKService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []Nip47Transaction, err error) {
transactions = []Nip47Transaction{}
func (ls *LDKService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []nip47.Transaction, err error) {
transactions = []nip47.Transaction{}
// TODO: support pagination
payments := ls.node.ListPayments()
@ -698,7 +704,7 @@ func (ls *LDKService) ListTransactions(ctx context.Context, from, until, limit,
transaction, err := ls.ldkPaymentToTransaction(&payment)
if err != nil {
ls.svc.Logger.Errorf("Failed to map transaction: %v", err)
ls.logger.Errorf("Failed to map transaction: %v", err)
continue
}
@ -726,7 +732,7 @@ func (ls *LDKService) ListTransactions(ctx context.Context, from, until, limit,
if offset < uint64(len(transactions)) {
transactions = transactions[offset:]
} else {
transactions = []Nip47Transaction{}
transactions = []nip47.Transaction{}
}
}
@ -734,32 +740,32 @@ func (ls *LDKService) ListTransactions(ctx context.Context, from, until, limit,
transactions = transactions[:limit]
}
// ls.svc.Logger.WithField("transactions", transactions).Debug("Listed transactions")
// ls.logger.WithField("transactions", transactions).Debug("Listed transactions")
return transactions, nil
}
func (gs *LDKService) GetInfo(ctx context.Context) (info *lnclient.NodeInfo, err error) {
func (ls *LDKService) GetInfo(ctx context.Context) (info *lnclient.NodeInfo, err error) {
// TODO: should alias, color be configured in LDK-node? or can we manage them in NWC?
// an alias is only needed if the user has public channels and wants their node to be publicly visible?
status := gs.node.Status()
status := ls.node.Status()
return &lnclient.NodeInfo{
Alias: "NWC",
Color: "#897FFF",
Pubkey: gs.node.NodeId(),
Network: gs.network,
Pubkey: ls.node.NodeId(),
Network: ls.network,
BlockHeight: status.CurrentBestBlock.Height,
BlockHash: status.CurrentBestBlock.BlockHash,
}, nil
}
func (gs *LDKService) ListChannels(ctx context.Context) ([]lnclient.Channel, error) {
func (ls *LDKService) ListChannels(ctx context.Context) ([]lnclient.Channel, error) {
ldkChannels := gs.node.ListChannels()
ldkChannels := ls.node.ListChannels()
channels := []lnclient.Channel{}
// gs.svc.Logger.WithFields(logrus.Fields{
// gs.logger.WithFields(logrus.Fields{
// "channels": ldkChannels,
// }).Debug("Listed Channels")
@ -786,7 +792,7 @@ func (gs *LDKService) ListChannels(ctx context.Context) ([]lnclient.Channel, err
return channels, nil
}
func (gs *LDKService) GetNodeConnectionInfo(ctx context.Context) (nodeConnectionInfo *lnclient.NodeConnectionInfo, err error) {
func (ls *LDKService) GetNodeConnectionInfo(ctx context.Context) (nodeConnectionInfo *lnclient.NodeConnectionInfo, err error) {
/*addresses := gs.node.ListeningAddresses()
if addresses == nil || len(*addresses) < 1 {
return nil, errors.New("no available listening addresses")
@ -798,29 +804,29 @@ func (gs *LDKService) GetNodeConnectionInfo(ctx context.Context) (nodeConnection
}
port, err := strconv.Atoi(parts[1])
if err != nil {
gs.svc.Logger.WithError(err).Error("ConnectPeer failed")
gs.logger.WithError(err).Error("ConnectPeer failed")
return nil, err
}*/
return &lnclient.NodeConnectionInfo{
Pubkey: gs.node.NodeId(),
Pubkey: ls.node.NodeId(),
//Address: parts[0],
//Port: port,
}, nil
}
func (gs *LDKService) ConnectPeer(ctx context.Context, connectPeerRequest *lnclient.ConnectPeerRequest) error {
err := gs.node.Connect(connectPeerRequest.Pubkey, connectPeerRequest.Address+":"+strconv.Itoa(int(connectPeerRequest.Port)), true)
func (ls *LDKService) ConnectPeer(ctx context.Context, connectPeerRequest *lnclient.ConnectPeerRequest) error {
err := ls.node.Connect(connectPeerRequest.Pubkey, connectPeerRequest.Address+":"+strconv.Itoa(int(connectPeerRequest.Port)), true)
if err != nil {
gs.svc.Logger.WithField("request", connectPeerRequest).WithError(err).Error("ConnectPeer failed")
ls.logger.WithField("request", connectPeerRequest).WithError(err).Error("ConnectPeer failed")
return err
}
return nil
}
func (gs *LDKService) OpenChannel(ctx context.Context, openChannelRequest *lnclient.OpenChannelRequest) (*lnclient.OpenChannelResponse, error) {
peers := gs.node.ListPeers()
func (ls *LDKService) OpenChannel(ctx context.Context, openChannelRequest *lnclient.OpenChannelRequest) (*lnclient.OpenChannelResponse, error) {
peers := ls.node.ListPeers()
var foundPeer *ldk_node.PeerDetails
for _, peer := range peers {
if peer.NodeId == openChannelRequest.Pubkey {
@ -834,18 +840,18 @@ func (gs *LDKService) OpenChannel(ctx context.Context, openChannelRequest *lncli
return nil, errors.New("node is not peered yet")
}
ldkEventSubscription := gs.ldkEventBroadcaster.Subscribe()
defer gs.ldkEventBroadcaster.CancelSubscription(ldkEventSubscription)
ldkEventSubscription := ls.ldkEventBroadcaster.Subscribe()
defer ls.ldkEventBroadcaster.CancelSubscription(ldkEventSubscription)
gs.svc.Logger.Infof("Opening channel with: %v", foundPeer.NodeId)
userChannelId, err := gs.node.ConnectOpenChannel(foundPeer.NodeId, foundPeer.Address, uint64(openChannelRequest.Amount), nil, nil, openChannelRequest.Public)
ls.logger.Infof("Opening channel with: %v", foundPeer.NodeId)
userChannelId, err := ls.node.ConnectOpenChannel(foundPeer.NodeId, foundPeer.Address, uint64(openChannelRequest.Amount), nil, nil, openChannelRequest.Public)
if err != nil {
gs.svc.Logger.WithError(err).Error("OpenChannel failed")
ls.logger.WithError(err).Error("OpenChannel failed")
return nil, err
}
// userChannelId allows to locally keep track of the channel (and is also used to close the channel)
gs.svc.Logger.Infof("Funded channel: %v", userChannelId)
ls.logger.Infof("Funded channel: %v", userChannelId)
for start := time.Now(); time.Since(start) < time.Second*60; {
event := <-ldkEventSubscription
@ -854,7 +860,7 @@ func (gs *LDKService) OpenChannel(ctx context.Context, openChannelRequest *lncli
channelClosedEvent, isChannelClosedEvent := (*event).(ldk_node.EventChannelClosed)
if isChannelClosedEvent {
gs.svc.Logger.WithFields(logrus.Fields{
ls.logger.WithFields(logrus.Fields{
"event": channelClosedEvent,
})
return nil, fmt.Errorf("failed to open channel: %+v", *channelClosedEvent.Reason)
@ -872,31 +878,31 @@ func (gs *LDKService) OpenChannel(ctx context.Context, openChannelRequest *lncli
return nil, errors.New("open channel timeout")
}
func (gs *LDKService) CloseChannel(ctx context.Context, closeChannelRequest *lnclient.CloseChannelRequest) (*lnclient.CloseChannelResponse, error) {
gs.svc.Logger.WithFields(logrus.Fields{
func (ls *LDKService) CloseChannel(ctx context.Context, closeChannelRequest *lnclient.CloseChannelRequest) (*lnclient.CloseChannelResponse, error) {
ls.logger.WithFields(logrus.Fields{
"request": closeChannelRequest,
}).Info("Closing Channel")
// TODO: support passing force option
err := gs.node.CloseChannel(closeChannelRequest.ChannelId, closeChannelRequest.NodeId, closeChannelRequest.Force)
err := ls.node.CloseChannel(closeChannelRequest.ChannelId, closeChannelRequest.NodeId, closeChannelRequest.Force)
if err != nil {
gs.svc.Logger.WithError(err).Error("CloseChannel failed")
ls.logger.WithError(err).Error("CloseChannel failed")
return nil, err
}
return &lnclient.CloseChannelResponse{}, nil
}
func (gs *LDKService) GetNewOnchainAddress(ctx context.Context) (string, error) {
address, err := gs.node.OnchainPayment().NewAddress()
func (ls *LDKService) GetNewOnchainAddress(ctx context.Context) (string, error) {
address, err := ls.node.OnchainPayment().NewAddress()
if err != nil {
gs.svc.Logger.WithError(err).Error("NewOnchainAddress failed")
ls.logger.WithError(err).Error("NewOnchainAddress failed")
return "", err
}
return address, nil
}
func (gs *LDKService) GetOnchainBalance(ctx context.Context) (*lnclient.OnchainBalanceResponse, error) {
balances := gs.node.ListBalances()
gs.svc.Logger.WithFields(logrus.Fields{
func (ls *LDKService) GetOnchainBalance(ctx context.Context) (*lnclient.OnchainBalanceResponse, error) {
balances := ls.node.ListBalances()
ls.logger.WithFields(logrus.Fields{
"balances": balances,
}).Debug("Listed Balances")
return &lnclient.OnchainBalanceResponse{
@ -906,33 +912,33 @@ func (gs *LDKService) GetOnchainBalance(ctx context.Context) (*lnclient.OnchainB
}, nil
}
func (gs *LDKService) RedeemOnchainFunds(ctx context.Context, toAddress string) (string, error) {
txId, err := gs.node.OnchainPayment().SendAllToAddress(toAddress)
func (ls *LDKService) RedeemOnchainFunds(ctx context.Context, toAddress string) (string, error) {
txId, err := ls.node.OnchainPayment().SendAllToAddress(toAddress)
if err != nil {
gs.svc.Logger.WithError(err).Error("SendAllToOnchainAddress failed")
ls.logger.WithError(err).Error("SendAllToOnchainAddress failed")
return "", err
}
return txId, nil
}
func (ls *LDKService) ResetRouter(key string) error {
ls.svc.cfg.SetUpdate(resetRouterKey, key, "")
ls.cfg.SetUpdate(resetRouterKey, key, "")
return nil
}
func (gs *LDKService) SignMessage(ctx context.Context, message string) (string, error) {
sign, err := gs.node.SignMessage([]byte(message))
func (ls *LDKService) SignMessage(ctx context.Context, message string) (string, error) {
sign, err := ls.node.SignMessage([]byte(message))
if err != nil {
gs.svc.Logger.Errorf("SignMessage failed: %v", err)
ls.logger.Errorf("SignMessage failed: %v", err)
return "", err
}
return sign, nil
}
func (gs *LDKService) ldkPaymentToTransaction(payment *ldk_node.PaymentDetails) (*Nip47Transaction, error) {
// gs.svc.Logger.WithField("payment", payment).Debug("Mapping LDK payment to transaction")
func (ls *LDKService) ldkPaymentToTransaction(payment *ldk_node.PaymentDetails) (*nip47.Transaction, error) {
// gs.logger.WithField("payment", payment).Debug("Mapping LDK payment to transaction")
transactionType := "incoming"
if payment.Direction == ldk_node.PaymentDirectionOutbound {
@ -954,7 +960,7 @@ func (gs *LDKService) ldkPaymentToTransaction(payment *ldk_node.PaymentDetails)
bolt11Invoice = *bolt11PaymentKind.Bolt11Invoice
paymentRequest, err := decodepay.Decodepay(strings.ToLower(bolt11Invoice))
if err != nil {
gs.svc.Logger.WithFields(logrus.Fields{
ls.logger.WithFields(logrus.Fields{
"bolt11": bolt11Invoice,
}).Errorf("Failed to decode bolt11 invoice: %v", err)
@ -988,7 +994,7 @@ func (gs *LDKService) ldkPaymentToTransaction(payment *ldk_node.PaymentDetails)
fee = *payment.FeeMsat
}
return &Nip47Transaction{
return &nip47.Transaction{
Type: transactionType,
Preimage: preimage,
PaymentHash: paymentHash,
@ -1003,28 +1009,28 @@ func (gs *LDKService) ldkPaymentToTransaction(payment *ldk_node.PaymentDetails)
}, nil
}
func (gs *LDKService) SendPaymentProbes(ctx context.Context, invoice string) error {
err := gs.node.Bolt11Payment().SendProbes(invoice)
func (ls *LDKService) SendPaymentProbes(ctx context.Context, invoice string) error {
err := ls.node.Bolt11Payment().SendProbes(invoice)
if err != nil {
gs.svc.Logger.Errorf("Bolt11Payment.SendProbes failed: %v", err)
ls.logger.Errorf("Bolt11Payment.SendProbes failed: %v", err)
return err
}
return nil
}
func (gs *LDKService) SendSpontaneousPaymentProbes(ctx context.Context, amountMsat uint64, nodeId string) error {
err := gs.node.SpontaneousPayment().SendProbes(amountMsat, nodeId)
func (ls *LDKService) SendSpontaneousPaymentProbes(ctx context.Context, amountMsat uint64, nodeId string) error {
err := ls.node.SpontaneousPayment().SendProbes(amountMsat, nodeId)
if err != nil {
gs.svc.Logger.Errorf("SpontaneousPayment.SendProbes failed: %v", err)
ls.logger.Errorf("SpontaneousPayment.SendProbes failed: %v", err)
return err
}
return nil
}
func (gs *LDKService) ListPeers(ctx context.Context) ([]lnclient.PeerDetails, error) {
peers := gs.node.ListPeers()
func (ls *LDKService) ListPeers(ctx context.Context) ([]lnclient.PeerDetails, error) {
peers := ls.node.ListPeers()
ret := make([]lnclient.PeerDetails, 0, len(peers))
for _, peer := range peers {
ret = append(ret, lnclient.PeerDetails{
@ -1070,8 +1076,8 @@ func (ls *LDKService) GetNetworkGraph(nodeIds []string) (lnclient.NetworkGraphRe
return networkGraph, nil
}
func (gs *LDKService) GetLogOutput(ctx context.Context, maxLen int) ([]byte, error) {
config := gs.node.Config()
func (ls *LDKService) GetLogOutput(ctx context.Context, maxLen int) ([]byte, error) {
config := ls.node.Config()
logPath := ""
if config.LogDirPath != nil {
logPath = *config.LogDirPath
@ -1082,7 +1088,7 @@ func (gs *LDKService) GetLogOutput(ctx context.Context, maxLen int) ([]byte, err
allLogFiles, err := filepath.Glob(filepath.Join(logPath, "ldk_node_*.log"))
if err != nil {
gs.svc.Logger.WithError(err).Error("GetLogOutput failed to list log files")
ls.logger.WithError(err).Error("GetLogOutput failed to list log files")
return nil, err
}
@ -1094,17 +1100,17 @@ func (gs *LDKService) GetLogOutput(ctx context.Context, maxLen int) ([]byte, err
// naturally sort by date.
lastLogFileName := slices.Max(allLogFiles)
logData, err := ReadFileTail(lastLogFileName, maxLen)
logData, err := utils.ReadFileTail(lastLogFileName, maxLen)
if err != nil {
gs.svc.Logger.WithError(err).Error("GetLogOutput failed to read log file")
ls.logger.WithError(err).Error("GetLogOutput failed to read log file")
return nil, err
}
return logData, nil
}
func (ls *LDKService) logLdkEvent(ctx context.Context, event *ldk_node.Event) {
ls.svc.Logger.WithFields(logrus.Fields{
func (ls *LDKService) handleLdkEvent(ctx context.Context, event *ldk_node.Event) {
ls.logger.WithFields(logrus.Fields{
"event": event,
}).Info("Received LDK event")
@ -1141,7 +1147,7 @@ func (ls *LDKService) logLdkEvent(ctx context.Context, event *ldk_node.Event) {
func (ls *LDKService) GetBalances(ctx context.Context) (*lnclient.BalancesResponse, error) {
onchainBalance, err := ls.GetOnchainBalance(ctx)
if err != nil {
ls.svc.Logger.WithError(err).Error("Failed to retrieve onchain balance")
ls.logger.WithError(err).Error("Failed to retrieve onchain balance")
return nil, err
}
@ -1224,3 +1230,7 @@ func (ls *LDKService) GetNodeStatus(ctx context.Context) (nodeStatus *lnclient.N
InternalNodeStatus: ls.node.Status(),
}, nil
}
func (ls *LDKService) UpdateLastWalletSyncRequest() {
ls.lastWalletSyncRequest = time.Now()
}

View file

@ -1,4 +1,4 @@
package main
package ldk
import (
"context"

View file

@ -1,4 +1,4 @@
package main
package lnd
import (
"context"
@ -12,11 +12,12 @@ import (
decodepay "github.com/nbd-wtf/ln-decodepay"
"github.com/getAlby/nostr-wallet-connect/lnd"
"github.com/getAlby/nostr-wallet-connect/models/lnclient"
"github.com/getAlby/nostr-wallet-connect/lnclient"
"github.com/getAlby/nostr-wallet-connect/lnclient/lnd/wrapper"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
// "gorm.io/gorm"
"github.com/lightningnetwork/lnd/lnrpc"
)
@ -24,8 +25,8 @@ import (
// wrap it again :sweat_smile:
// todo: drop dependency on lndhub package
type LNDService struct {
client *lnd.LNDWrapper
db *gorm.DB
client *wrapper.LNDWrapper
// db *gorm.DB
Logger *logrus.Logger
}
@ -37,7 +38,7 @@ func (svc *LNDService) GetBalance(ctx context.Context) (balance int64, err error
return int64(resp.LocalBalance.Msat), nil
}
func (svc *LNDService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []Nip47Transaction, err error) {
func (svc *LNDService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []nip47.Transaction, err error) {
// Fetch invoices
var invoices []*lnrpc.Invoice
if invoiceType == "" || invoiceType == "incoming" {
@ -98,7 +99,7 @@ func (svc *LNDService) ListTransactions(ctx context.Context, from, until, limit,
settledAt = &settledAtUnix
}
transaction := Nip47Transaction{
transaction := nip47.Transaction{
Type: "outgoing",
Invoice: payment.PaymentRequest,
Preimage: payment.PaymentPreimage,
@ -143,7 +144,7 @@ func (svc *LNDService) ListChannels(ctx context.Context) ([]lnclient.Channel, er
return channels, nil
}
func (svc *LNDService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *Nip47Transaction, err error) {
func (svc *LNDService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *nip47.Transaction, err error) {
var descriptionHashBytes []byte
if descriptionHash != "" {
@ -174,7 +175,7 @@ func (svc *LNDService) MakeInvoice(ctx context.Context, amount int64, descriptio
return transaction, nil
}
func (svc *LNDService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *Nip47Transaction, err error) {
func (svc *LNDService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *nip47.Transaction, err error) {
paymentHashBytes, err := hex.DecodeString(paymentHash)
if err != nil || len(paymentHashBytes) != 32 {
@ -193,12 +194,12 @@ func (svc *LNDService) LookupInvoice(ctx context.Context, paymentHash string) (t
return transaction, nil
}
func (svc *LNDService) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.Nip47PayInvoiceResponse, error) {
func (svc *LNDService) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.PayInvoiceResponse, error) {
resp, err := svc.client.SendPaymentSync(ctx, &lnrpc.SendRequest{PaymentRequest: payReq})
if err != nil {
return nil, err
}
return &lnclient.Nip47PayInvoiceResponse{
return &lnclient.PayInvoiceResponse{
Preimage: hex.EncodeToString(resp.PaymentPreimage),
}, nil
}
@ -302,18 +303,18 @@ func makePreimageHex() ([]byte, error) {
return bytes, nil
}
func NewLNDService(ctx context.Context, svc *Service, lndAddress, lndCertHex, lndMacaroonHex string) (result lnclient.LNClient, err error) {
func NewLNDService(ctx context.Context, logger *logrus.Logger, lndAddress, lndCertHex, lndMacaroonHex string) (result lnclient.LNClient, err error) {
if lndAddress == "" || lndCertHex == "" || lndMacaroonHex == "" {
return nil, errors.New("one or more required LND configuration are missing")
}
lndClient, err := lnd.NewLNDclient(lnd.LNDoptions{
lndClient, err := wrapper.NewLNDclient(wrapper.LNDoptions{
Address: lndAddress,
CertHex: lndCertHex,
MacaroonHex: lndMacaroonHex,
})
if err != nil {
svc.Logger.Errorf("Failed to create new LND client %v", err)
logger.Errorf("Failed to create new LND client %v", err)
return nil, err
}
info, err := lndClient.GetInfo(ctx, &lnrpc.GetInfoRequest{})
@ -321,9 +322,9 @@ func NewLNDService(ctx context.Context, svc *Service, lndAddress, lndCertHex, ln
return nil, err
}
lndService := &LNDService{client: lndClient, Logger: svc.Logger, db: svc.db}
lndService := &LNDService{client: lndClient, Logger: logger}
svc.Logger.Infof("Connected to LND - alias %s", info.Alias)
logger.Infof("Connected to LND - alias %s", info.Alias)
return lndService, nil
}
@ -406,7 +407,7 @@ func (svc *LNDService) GetBalances(ctx context.Context) (*lnclient.BalancesRespo
}, nil
}
func lndInvoiceToTransaction(invoice *lnrpc.Invoice) *Nip47Transaction {
func lndInvoiceToTransaction(invoice *lnrpc.Invoice) *nip47.Transaction {
var settledAt *int64
var preimage string
if invoice.State == lnrpc.Invoice_SETTLED {
@ -420,7 +421,7 @@ func lndInvoiceToTransaction(invoice *lnrpc.Invoice) *Nip47Transaction {
expiresAt = &expiresAtUnix
}
return &Nip47Transaction{
return &nip47.Transaction{
Type: "incoming",
Invoice: invoice.PaymentRequest,
Description: invoice.Memo,
@ -451,3 +452,5 @@ func (svc *LNDService) GetNodeStatus(ctx context.Context) (nodeStatus *lnclient.
func (svc *LNDService) GetNetworkGraph(nodeIds []string) (lnclient.NetworkGraphResponse, error) {
return nil, nil
}
func (svc *LNDService) UpdateLastWalletSyncRequest() {}

View file

@ -1,4 +1,4 @@
package lnd
package wrapper
import (
"context"

View file

@ -1,4 +1,4 @@
package lnd
package wrapper
import (
"context"

View file

@ -42,7 +42,7 @@ type NodeConnectionInfo struct {
}
type LNClient interface {
SendPaymentSync(ctx context.Context, payReq string) (*Nip47PayInvoiceResponse, error)
SendPaymentSync(ctx context.Context, payReq string) (*PayInvoiceResponse, error)
SendKeysend(ctx context.Context, amount int64, destination, preimage string, customRecords []TLVRecord) (preImage string, err error)
GetBalance(ctx context.Context) (balance int64, err error)
GetInfo(ctx context.Context) (info *NodeInfo, err error)
@ -68,6 +68,7 @@ type LNClient interface {
SignMessage(ctx context.Context, message string) (string, error)
GetStorageDir() (string, error)
GetNetworkGraph(nodeIds []string) (NetworkGraphResponse, error)
UpdateLastWalletSyncRequest()
}
type Channel struct {
@ -133,7 +134,7 @@ type LightningBalanceResponse struct {
NextMaxReceivableMPP int64 `json:"nextMaxReceivableMPP"`
}
type Nip47PayInvoiceResponse struct {
type PayInvoiceResponse struct {
Preimage string `json:"preimage"`
Fee *uint64 `json:"fee"`
}

View file

@ -1,4 +1,4 @@
package main
package phoenixd
import (
"context"
@ -12,10 +12,10 @@ import (
"strings"
"time"
"github.com/getAlby/nostr-wallet-connect/models/lnclient"
"github.com/getAlby/nostr-wallet-connect/lnclient"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
type InvoiceResponse struct {
@ -67,13 +67,12 @@ type BalanceResponse struct {
type PhoenixService struct {
Address string
Authorization string
db *gorm.DB
Logger *logrus.Logger
}
func NewPhoenixService(svc *Service, address string, authorization string) (result lnclient.LNClient, err error) {
func NewPhoenixService(logger *logrus.Logger, address string, authorization string) (result lnclient.LNClient, err error) {
authorizationBase64 := b64.StdEncoding.EncodeToString([]byte(":" + authorization))
phoenixService := &PhoenixService{Logger: svc.Logger, db: svc.db, Address: address, Authorization: authorizationBase64}
phoenixService := &PhoenixService{Logger: logger, Address: address, Authorization: authorizationBase64}
return phoenixService, nil
}
@ -122,7 +121,7 @@ func (svc *PhoenixService) GetBalances(ctx context.Context) (*lnclient.BalancesR
}, nil
}
func (svc *PhoenixService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []Nip47Transaction, err error) {
func (svc *PhoenixService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (transactions []nip47.Transaction, err error) {
incomingQuery := url.Values{}
if from != 0 {
incomingQuery.Add("from", strconv.FormatUint(from*1000, 10))
@ -160,14 +159,14 @@ func (svc *PhoenixService) ListTransactions(ctx context.Context, from, until, li
if err := json.NewDecoder(incomingResp.Body).Decode(&incomingPayments); err != nil {
return nil, err
}
transactions = []Nip47Transaction{}
transactions = []nip47.Transaction{}
for _, invoice := range incomingPayments {
var settledAt *int64
if invoice.CompletedAt != 0 {
settledAtUnix := time.UnixMilli(invoice.CompletedAt).Unix()
settledAt = &settledAtUnix
}
transaction := Nip47Transaction{
transaction := nip47.Transaction{
Type: "incoming",
Invoice: invoice.Invoice,
Preimage: invoice.Preimage,
@ -223,7 +222,7 @@ func (svc *PhoenixService) ListTransactions(ctx context.Context, from, until, li
settledAtUnix := time.UnixMilli(invoice.CompletedAt).Unix()
settledAt = &settledAtUnix
}
transaction := Nip47Transaction{
transaction := nip47.Transaction{
Type: "outgoing",
Invoice: invoice.Invoice,
Preimage: invoice.Preimage,
@ -276,7 +275,7 @@ func (svc *PhoenixService) ListChannels(ctx context.Context) ([]lnclient.Channel
return channels, nil
}
func (svc *PhoenixService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *Nip47Transaction, err error) {
func (svc *PhoenixService) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *nip47.Transaction, err error) {
form := url.Values{}
amountSat := strconv.FormatInt(amount/1000, 10)
form.Add("amountSat", amountSat)
@ -313,7 +312,7 @@ func (svc *PhoenixService) MakeInvoice(ctx context.Context, amount int64, descri
}
expiresAt := time.Now().Add(1 * time.Hour).Unix()
tx := &Nip47Transaction{
tx := &nip47.Transaction{
Type: "incoming",
Invoice: invoiceRes.Serialized,
Preimage: "",
@ -327,7 +326,7 @@ func (svc *PhoenixService) MakeInvoice(ctx context.Context, amount int64, descri
return tx, nil
}
func (svc *PhoenixService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *Nip47Transaction, err error) {
func (svc *PhoenixService) LookupInvoice(ctx context.Context, paymentHash string) (transaction *nip47.Transaction, err error) {
req, err := http.NewRequest(http.MethodGet, svc.Address+"/payments/incoming/"+paymentHash, nil)
if err != nil {
return nil, err
@ -350,7 +349,7 @@ func (svc *PhoenixService) LookupInvoice(ctx context.Context, paymentHash string
settledAtUnix := time.UnixMilli(invoiceRes.CompletedAt).Unix()
settledAt = &settledAtUnix
}
transaction = &Nip47Transaction{
transaction = &nip47.Transaction{
Type: "incoming",
Invoice: invoiceRes.Invoice,
Preimage: invoiceRes.Preimage,
@ -364,7 +363,7 @@ func (svc *PhoenixService) LookupInvoice(ctx context.Context, paymentHash string
return transaction, nil
}
func (svc *PhoenixService) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.Nip47PayInvoiceResponse, error) {
func (svc *PhoenixService) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.PayInvoiceResponse, error) {
form := url.Values{}
form.Add("invoice", payReq)
req, err := http.NewRequest(http.MethodPost, svc.Address+"/payinvoice", strings.NewReader(form.Encode()))
@ -386,7 +385,7 @@ func (svc *PhoenixService) SendPaymentSync(ctx context.Context, payReq string) (
}
fee := uint64(payRes.RoutingFeeSat) * 1000
return &lnclient.Nip47PayInvoiceResponse{
return &lnclient.PayInvoiceResponse{
Preimage: payRes.PaymentPreimage,
Fee: &fee,
}, nil
@ -480,3 +479,5 @@ func (svc *PhoenixService) GetStorageDir() (string, error) {
func (svc *PhoenixService) GetNetworkGraph(nodeIds []string) (lnclient.NetworkGraphResponse, error) {
return nil, nil
}
func (svc *PhoenixService) UpdateLastWalletSyncRequest() {}

621
lsp/lsp_service.go Normal file
View file

@ -0,0 +1,621 @@
package lsp
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"regexp"
"strconv"
"strings"
"time"
"github.com/getAlby/nostr-wallet-connect/lnclient"
"github.com/getAlby/nostr-wallet-connect/service"
"github.com/sirupsen/logrus"
)
type lspService struct {
svc service.Service
logger *logrus.Logger
}
type lspConnectionInfo struct {
Pubkey string
Address string
Port uint16
}
func NewLSPService(svc service.Service, logger *logrus.Logger) *lspService {
return &lspService{
svc: svc,
logger: logger,
}
}
func (ls *lspService) NewInstantChannelInvoice(ctx context.Context, request *NewInstantChannelInvoiceRequest) (*NewInstantChannelInvoiceResponse, error) {
var selectedLsp LSP
switch request.LSP {
case "VOLTAGE":
selectedLsp = VoltageLSP()
case "OLYMPUS_FLOW_2_0":
selectedLsp = OlympusLSP()
case "OLYMPUS_MUTINYNET_FLOW_2_0":
selectedLsp = OlympusMutinynetFlowLSP()
case "OLYMPUS_MUTINYNET_LSPS1":
selectedLsp = OlympusMutinynetLSPS1LSP()
case "ALBY":
selectedLsp = AlbyPlebsLSP()
case "ALBY_MUTINYNET":
selectedLsp = AlbyMutinynetPlebsLSP()
default:
return nil, errors.New("unknown LSP")
}
if ls.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
ls.logger.Infoln("Requesting LSP info")
var lspInfo *lspConnectionInfo
var err error
switch selectedLsp.LspType {
case LSP_TYPE_FLOW_2_0:
fallthrough
case LSP_TYPE_PMLSP:
lspInfo, err = ls.getFlowLSPInfo(selectedLsp.Url + "/info")
case LSP_TYPE_LSPS1:
lspInfo, err = ls.getLSPS1LSPInfo(selectedLsp.Url + "/get_info")
default:
return nil, fmt.Errorf("unsupported LSP type: %v", selectedLsp.LspType)
}
if err != nil {
ls.logger.WithError(err).Error("Failed to request LSP info")
return nil, err
}
ls.logger.Infoln("Requesting own node info")
nodeInfo, err := ls.svc.GetLNClient().GetInfo(ctx)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to request own node info", err)
return nil, err
}
ls.logger.WithField("lspInfo", lspInfo).Info("Connecting to LSP node as a peer")
err = ls.svc.GetLNClient().ConnectPeer(ctx, &lnclient.ConnectPeerRequest{
Pubkey: lspInfo.Pubkey,
Address: lspInfo.Address,
Port: lspInfo.Port,
})
if err != nil {
ls.logger.WithError(err).Error("Failed to connect to peer")
return nil, err
}
invoice := ""
var fee uint64 = 0
switch selectedLsp.LspType {
case LSP_TYPE_FLOW_2_0:
invoice, fee, err = ls.requestFlow20WrappedInvoice(ctx, &selectedLsp, request.Amount, nodeInfo.Pubkey)
case LSP_TYPE_PMLSP:
invoice, fee, err = ls.requestPMLSPInvoice(&selectedLsp, request.Amount, nodeInfo.Pubkey)
case LSP_TYPE_LSPS1:
invoice, fee, err = ls.requestLSPS1Invoice(ctx, &selectedLsp, request.Amount, nodeInfo.Pubkey)
default:
return nil, fmt.Errorf("unsupported LSP type: %v", selectedLsp.LspType)
}
if err != nil {
ls.logger.WithError(err).Error("Failed to request invoice")
return nil, err
}
newChannelResponse := &NewInstantChannelInvoiceResponse{
Invoice: invoice,
Fee: fee,
}
ls.logger.WithFields(logrus.Fields{
"newChannelResponse": newChannelResponse,
}).Info("New Channel response")
return newChannelResponse, nil
}
func (ls *lspService) getLSPS1LSPInfo(url string) (*lspConnectionInfo, error) {
type LSPS1LSPInfo struct {
// TODO: implement options
Options interface{} `json:"options"`
URIs []string `json:"uris"`
}
var lsps1LspInfo LSPS1LSPInfo
client := http.Client{
Timeout: time.Second * 10,
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to create lsp info request")
return nil, err
}
res, err := client.Do(req)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to request lsp info")
return nil, err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to read response body")
return nil, errors.New("failed to read response body")
}
err = json.Unmarshal(body, &lsps1LspInfo)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to deserialize json")
return nil, fmt.Errorf("failed to deserialize json %s %s", url, string(body))
}
uri := lsps1LspInfo.URIs[0]
// make sure it's a valid IPv4 URI
regex := regexp.MustCompile(`^([0-9a-f]+)@([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+):([0-9]+)$`)
parts := regex.FindStringSubmatch(uri)
ls.logger.WithField("parts", parts).Info("Split URI")
if parts == nil || len(parts) != 4 {
ls.logger.WithField("parts", parts).Info("Unsupported URI")
return nil, errors.New("could not decode LSP URI")
}
port, err := strconv.Atoi(parts[3])
if err != nil {
ls.logger.WithField("port", parts[3]).WithError(err).Info("Failed to decode port number")
return nil, err
}
return &lspConnectionInfo{
Pubkey: parts[1],
Address: parts[2],
Port: uint16(port),
}, nil
}
func (ls *lspService) getFlowLSPInfo(url string) (*lspConnectionInfo, error) {
type FlowLSPConnectionMethod struct {
Address string `json:"address"`
Port uint16 `json:"port"`
Type string `json:"type"`
}
type FlowLSPInfo struct {
Pubkey string `json:"pubkey"`
ConnectionMethods []FlowLSPConnectionMethod `json:"connection_methods"`
}
var flowLspInfo FlowLSPInfo
client := http.Client{
Timeout: time.Second * 10,
}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to create lsp info request")
return nil, err
}
res, err := client.Do(req)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to request lsp info")
return nil, err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to read response body")
return nil, errors.New("failed to read response body")
}
err = json.Unmarshal(body, &flowLspInfo)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": url,
}).Error("Failed to deserialize json")
return nil, fmt.Errorf("failed to deserialize json %s %s", url, string(body))
}
ipIndex := -1
for i, cm := range flowLspInfo.ConnectionMethods {
if strings.HasPrefix(cm.Type, "ip") {
ipIndex = i
break
}
}
if ipIndex == -1 {
ls.logger.Error("No ipv4/ipv6 connection method found in LSP info")
return nil, errors.New("unexpected LSP connection method")
}
return &lspConnectionInfo{
Pubkey: flowLspInfo.Pubkey,
Address: flowLspInfo.ConnectionMethods[ipIndex].Address,
Port: flowLspInfo.ConnectionMethods[ipIndex].Port,
}, nil
}
func (ls *lspService) requestFlow20WrappedInvoice(ctx context.Context, selectedLsp *LSP, amount uint64, pubkey string) (invoice string, fee uint64, err error) {
ls.logger.Infoln("Requesting fee information")
type FeeRequest struct {
AmountMsat uint64 `json:"amount_msat"`
Pubkey string `json:"pubkey"`
}
type FeeResponse struct {
FeeAmountMsat uint64 `json:"fee_amount_msat"`
Id string `json:"id"`
}
var feeResponse FeeResponse
{
client := http.Client{
Timeout: time.Second * 10,
}
payloadBytes, err := json.Marshal(FeeRequest{
AmountMsat: amount * 1000,
Pubkey: pubkey,
})
if err != nil {
return "", 0, err
}
bodyReader := bytes.NewReader(payloadBytes)
req, err := http.NewRequest(http.MethodPost, selectedLsp.Url+"/fee", bodyReader)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to create lsp fee request")
return "", 0, err
}
req.Header.Set("Content-Type", "application/json")
res, err := client.Do(req)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to request lsp fee")
return "", 0, err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to read response body")
return "", 0, errors.New("failed to read response body")
}
if res.StatusCode >= 300 {
ls.logger.WithFields(logrus.Fields{
"body": string(body),
"statusCode": res.StatusCode,
}).Error("fee endpoint returned non-success code")
return "", 0, fmt.Errorf("fee endpoint returned non-success code: %s", string(body))
}
err = json.Unmarshal(body, &feeResponse)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to deserialize json")
return "", 0, fmt.Errorf("failed to deserialize json %s %s", selectedLsp.Url, string(body))
}
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
"feeResponse": feeResponse,
}).Info("Got fee response")
if feeResponse.Id == "" {
ls.logger.WithError(err).WithFields(logrus.Fields{
"feeResponse": feeResponse,
}).Error("No fee id in fee response")
return "", 0, fmt.Errorf("no fee id in fee response %v", feeResponse)
}
fee = feeResponse.FeeAmountMsat / 1000
}
// because we don't want the sender to pay the fee
// see: https://docs.voltage.cloud/voltage-lsp#gqBqV
makeInvoiceResponse, err := ls.svc.GetLNClient().MakeInvoice(ctx, int64(amount)*1000-int64(feeResponse.FeeAmountMsat), "", "", 60*60)
if err != nil {
ls.logger.WithError(err).Error("Failed to request own invoice")
return "", 0, fmt.Errorf("failed to request own invoice %v", err)
}
type ProposalRequest struct {
Bolt11 string `json:"bolt11"`
FeeId string `json:"fee_id"`
}
type ProposalResponse struct {
Bolt11 string `json:"jit_bolt11"`
}
ls.logger.Infoln("Proposing invoice")
var proposalResponse ProposalResponse
{
client := http.Client{
Timeout: time.Second * 10,
}
payloadBytes, err := json.Marshal(ProposalRequest{
Bolt11: makeInvoiceResponse.Invoice,
FeeId: feeResponse.Id,
})
if err != nil {
return "", 0, err
}
bodyReader := bytes.NewReader(payloadBytes)
req, err := http.NewRequest(http.MethodPost, selectedLsp.Url+"/proposal", bodyReader)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to create lsp fee request")
return "", 0, err
}
req.Header.Set("Content-Type", "application/json")
res, err := client.Do(req)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to request lsp fee")
return "", 0, err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to read response body")
return "", 0, errors.New("failed to read response body")
}
if res.StatusCode >= 300 {
ls.logger.WithFields(logrus.Fields{
"body": string(body),
"statusCode": res.StatusCode,
}).Error("proposal endpoint returned non-success code")
return "", 0, fmt.Errorf("proposal endpoint returned non-success code: %s", string(body))
}
err = json.Unmarshal(body, &proposalResponse)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to deserialize json")
return "", 0, fmt.Errorf("failed to deserialize json %s %s", selectedLsp.Url, string(body))
}
ls.logger.WithField("proposalResponse", proposalResponse).Info("Got proposal response")
if proposalResponse.Bolt11 == "" {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
"proposalResponse": proposalResponse,
}).Error("No bolt11 in proposal response")
return "", 0, fmt.Errorf("no bolt11 in proposal response %v", proposalResponse)
}
}
invoice = proposalResponse.Bolt11
return invoice, fee, nil
}
func (ls *lspService) requestPMLSPInvoice(selectedLsp *LSP, amount uint64, pubkey string) (invoice string, fee uint64, err error) {
type NewInstantChannelRequest struct {
Amount uint64 `json:"amount"`
Pubkey string `json:"pubkey"`
}
client := http.Client{
Timeout: time.Second * 10,
}
payloadBytes, err := json.Marshal(NewInstantChannelRequest{
Amount: amount,
Pubkey: pubkey,
})
if err != nil {
return "", 0, err
}
bodyReader := bytes.NewReader(payloadBytes)
req, err := http.NewRequest(http.MethodPost, selectedLsp.Url+"/new-channel", bodyReader)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to create new channel request")
return "", 0, err
}
req.Header.Set("Content-Type", "application/json")
res, err := client.Do(req)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to request new channel invoice")
return "", 0, err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to read response body")
return "", 0, errors.New("failed to read response body")
}
if res.StatusCode >= 300 {
ls.logger.WithFields(logrus.Fields{
"body": string(body),
"statusCode": res.StatusCode,
}).Error("new-channel endpoint returned non-success code")
return "", 0, fmt.Errorf("new-channel endpoint returned non-success code: %s", string(body))
}
var newChannelResponse NewInstantChannelResponse
err = json.Unmarshal(body, &newChannelResponse)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to deserialize json")
return "", 0, fmt.Errorf("failed to deserialize json %s %s", selectedLsp.Url, string(body))
}
invoice = newChannelResponse.Invoice
fee = newChannelResponse.FeeAmountMsat / 1000
return invoice, fee, nil
}
func (ls *lspService) requestLSPS1Invoice(ctx context.Context, selectedLsp *LSP, amount uint64, pubkey string) (invoice string, fee uint64, err error) {
client := http.Client{
Timeout: time.Second * 10,
}
type NewLSPS1ChannelRequest struct {
PublicKey string `json:"public_key"`
LSPBalanceSat string `json:"lsp_balance_sat"`
ClientBalanceSat string `json:"client_balance_sat"`
RequiredChannelConfirmations uint64 `json:"required_channel_confirmations"`
FundingConfirmsWithinBlocks uint64 `json:"funding_confirms_within_blocks"`
ChannelExpiryBlocks uint64 `json:"channel_expiry_blocks"`
Token string `json:"token"`
RefundOnchainAddress string `json:"refund_onchain_address"`
AnnounceChannel bool `json:"announce_channel"`
}
refundAddress, err := ls.svc.GetLNClient().GetNewOnchainAddress(ctx)
if err != nil {
ls.logger.WithError(err).Error("Failed to request onchain address")
return "", 0, err
}
newLSPS1ChannelRequest := NewLSPS1ChannelRequest{
PublicKey: pubkey,
LSPBalanceSat: strconv.FormatUint(amount, 10),
ClientBalanceSat: "0",
RequiredChannelConfirmations: 0,
FundingConfirmsWithinBlocks: 6,
ChannelExpiryBlocks: 13000, // TODO: this should be customizable
Token: "",
RefundOnchainAddress: refundAddress,
AnnounceChannel: false, // TODO: this should be customizable
}
payloadBytes, err := json.Marshal(newLSPS1ChannelRequest)
if err != nil {
return "", 0, err
}
bodyReader := bytes.NewReader(payloadBytes)
req, err := http.NewRequest(http.MethodPost, selectedLsp.Url+"/create_order", bodyReader)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to create new channel request")
return "", 0, err
}
req.Header.Set("Content-Type", "application/json")
res, err := client.Do(req)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to request new channel invoice")
return "", 0, err
}
defer res.Body.Close()
body, err := io.ReadAll(res.Body)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to read response body")
return "", 0, errors.New("failed to read response body")
}
if res.StatusCode >= 300 {
ls.logger.WithFields(logrus.Fields{
"newLSPS1ChannelRequest": newLSPS1ChannelRequest,
"body": string(body),
"statusCode": res.StatusCode,
}).Error("create_order endpoint returned non-success code")
return "", 0, fmt.Errorf("create_order endpoint returned non-success code: %s", string(body))
}
type NewLSPS1ChannelPayment struct {
LightningInvoice string `json:"lightning_invoice"`
FeeTotalSat string `json:"fee_total_sat"`
}
type NewLSPS1ChannelResponse struct {
Payment NewLSPS1ChannelPayment `json:"payment"`
}
var newChannelResponse NewLSPS1ChannelResponse
err = json.Unmarshal(body, &newChannelResponse)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to deserialize json")
return "", 0, fmt.Errorf("failed to deserialize json %s %s", selectedLsp.Url, string(body))
}
invoice = newChannelResponse.Payment.LightningInvoice
fee, err = strconv.ParseUint(newChannelResponse.Payment.FeeTotalSat, 10, 64)
if err != nil {
ls.logger.WithError(err).WithFields(logrus.Fields{
"url": selectedLsp.Url,
}).Error("Failed to parse fee")
return "", 0, fmt.Errorf("failed to parse fee %v", err)
}
return invoice, fee, nil
}

View file

@ -1,6 +1,9 @@
// TODO: move to lsp/models.go
package lsp
import (
"context"
)
type LSP struct {
Pubkey string
Url string
@ -70,3 +73,17 @@ func AlbyMutinynetPlebsLSP() LSP {
}
return lsp
}
type LSPService interface {
NewInstantChannelInvoice(ctx context.Context, request *NewInstantChannelInvoiceRequest) (*NewInstantChannelInvoiceResponse, error)
}
type NewInstantChannelInvoiceRequest struct {
Amount uint64 `json:"amount"`
LSP string `json:"lsp"`
}
type NewInstantChannelInvoiceResponse struct {
Invoice string `json:"invoice"`
Fee uint64 `json:"fee"`
}

View file

@ -8,13 +8,14 @@ package main
import (
"context"
"fmt"
"net/http"
nethttp "net/http"
"os"
"os/signal"
"syscall"
"time"
echologrus "github.com/davrux/echo-logrus/v4"
"github.com/getAlby/nostr-wallet-connect/http"
"github.com/labstack/echo/v4"
log "github.com/sirupsen/logrus"
)
@ -26,26 +27,26 @@ func main() {
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM, os.Kill)
svc, _ := NewService(ctx)
echologrus.Logger = svc.Logger
echologrus.Logger = svc.logger
e := echo.New()
//register shared routes
httpSvc := NewHttpService(svc)
httpSvc := http.NewHttpService(svc, svc.logger, svc.db, svc.eventPublisher)
httpSvc.RegisterSharedRoutes(e)
//start Echo server
go func() {
if err := e.Start(fmt.Sprintf(":%v", svc.cfg.Env.Port)); err != nil && err != http.ErrServerClosed {
svc.Logger.Fatalf("shutting down the server: %v", err)
if err := e.Start(fmt.Sprintf(":%v", svc.cfg.GetEnv().Port)); err != nil && err != nethttp.ErrServerClosed {
svc.logger.Fatalf("shutting down the server: %v", err)
}
}()
//handle graceful shutdown
<-ctx.Done()
svc.Logger.Infof("Shutting down echo server...")
svc.logger.Infof("Shutting down echo server...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
e.Shutdown(ctx)
svc.Logger.Info("Echo server exited")
svc.Logger.Info("Waiting for service to exit...")
svc.logger.Info("Echo server exited")
svc.logger.Info("Waiting for service to exit...")
svc.wg.Wait()
svc.Logger.Info("Service exited")
svc.logger.Info("Service exited")
}

View file

@ -18,12 +18,12 @@ func main() {
app := NewApp(svc)
LaunchWailsApp(app)
svc.Logger.Info("Wails app exited")
svc.logger.Info("Wails app exited")
svc.Logger.Info("Cancelling service context...")
svc.logger.Info("Cancelling service context...")
// cancel the service context
cancel()
svc.Logger.Info("Waiting for service to exit...")
svc.logger.Info("Waiting for service to exit...")
svc.wg.Wait()
svc.Logger.Info("Service exited")
svc.logger.Info("Service exited")
}

View file

@ -8,7 +8,7 @@ import (
"database/sql"
"github.com/getAlby/nostr-wallet-connect/models/config"
"github.com/getAlby/nostr-wallet-connect/config"
"github.com/go-gormigrate/gormigrate/v2"
"github.com/sirupsen/logrus"
"gorm.io/gorm"

View file

@ -1,7 +1,7 @@
package migrations
import (
"github.com/getAlby/nostr-wallet-connect/models/config"
"github.com/getAlby/nostr-wallet-connect/config"
"github.com/go-gormigrate/gormigrate/v2"
"github.com/sirupsen/logrus"
"gorm.io/gorm"

204
models.go
View file

@ -1,204 +0,0 @@
package main
import (
"encoding/json"
"time"
"github.com/getAlby/nostr-wallet-connect/models/lnclient"
)
const (
REQUEST_EVENT_STATE_HANDLER_EXECUTING = "executing"
REQUEST_EVENT_STATE_HANDLER_EXECUTED = "executed"
REQUEST_EVENT_STATE_HANDLER_ERROR = "error"
)
const (
RESPONSE_EVENT_STATE_PUBLISH_CONFIRMED = "confirmed"
RESPONSE_EVENT_STATE_PUBLISH_FAILED = "failed"
RESPONSE_EVENT_STATE_PUBLISH_UNCONFIRMED = "unconfirmed"
)
// TODO: move to models/db
type App struct {
ID uint
Name string `validate:"required"`
Description string
NostrPubkey string `validate:"required"`
CreatedAt time.Time
UpdatedAt time.Time
}
// TODO: move to models/db
type AppPermission struct {
ID uint
AppId uint `validate:"required"`
App App
RequestMethod string `validate:"required"`
MaxAmount int
BudgetRenewal string
ExpiresAt *time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
// TODO: move to models/db
type RequestEvent struct {
ID uint
AppId *uint
App App
NostrId string `validate:"required"`
Content string
State string
CreatedAt time.Time
UpdatedAt time.Time
}
// TODO: move to models/db
type ResponseEvent struct {
ID uint
NostrId string `validate:"required"`
RequestId uint `validate:"required"`
Content string
State string
RepliedAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
// TODO: move to models/db
type Payment struct {
ID uint
AppId uint `validate:"required"`
App App
RequestEventId uint `validate:"required"`
RequestEvent RequestEvent
Amount uint // in sats
PaymentRequest string
Preimage *string
CreatedAt time.Time
UpdatedAt time.Time
}
// TODO: move to models/Nip47
type Nip47Transaction = lnclient.Transaction
type PayRequest struct {
Invoice string `json:"invoice"`
}
// TODO: move to models/Nip47
type Nip47Request struct {
Method string `json:"method"`
Params json.RawMessage `json:"params"`
}
type Nip47Response struct {
Error *Nip47Error `json:"error,omitempty"`
Result interface{} `json:"result,omitempty"`
ResultType string `json:"result_type"`
}
type Nip47Notification struct {
Notification interface{} `json:"notification,omitempty"`
NotificationType string `json:"notification_type"`
}
type Nip47Error struct {
Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
type Nip47PaymentReceivedNotification struct {
Nip47Transaction
}
type Nip47PayParams struct {
Invoice string `json:"invoice"`
}
type Nip47PayResponse struct {
Preimage string `json:"preimage"`
FeesPaid *uint64 `json:"fees_paid"`
}
type Nip47MultiPayKeysendParams struct {
Keysends []Nip47MultiPayKeysendElement `json:"keysends"`
}
type Nip47MultiPayKeysendElement struct {
Nip47KeysendParams
Id string `json:"id"`
}
type Nip47MultiPayInvoiceParams struct {
Invoices []Nip47MultiPayInvoiceElement `json:"invoices"`
}
type Nip47MultiPayInvoiceElement struct {
Nip47PayParams
Id string `json:"id"`
}
type Nip47KeysendParams struct {
Amount int64 `json:"amount"`
Pubkey string `json:"pubkey"`
Preimage string `json:"preimage"`
TLVRecords []lnclient.TLVRecord `json:"tlv_records"`
}
type Nip47BalanceResponse struct {
Balance int64 `json:"balance"`
MaxAmount int `json:"max_amount"`
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"`
DescriptionHash string `json:"description_hash"`
Expiry int64 `json:"expiry"`
}
type Nip47MakeInvoiceResponse struct {
Nip47Transaction
}
type Nip47LookupInvoiceParams struct {
Invoice string `json:"invoice"`
PaymentHash string `json:"payment_hash"`
}
type Nip47LookupInvoiceResponse struct {
Nip47Transaction
}
type Nip47ListTransactionsParams struct {
From uint64 `json:"from,omitempty"`
Until uint64 `json:"until,omitempty"`
Limit uint64 `json:"limit,omitempty"`
Offset uint64 `json:"offset,omitempty"`
Unpaid bool `json:"unpaid,omitempty"`
Type string `json:"type,omitempty"`
}
type Nip47ListTransactionsResponse struct {
Transactions []Nip47Transaction `json:"transactions"`
}
type Nip47SignMessageParams struct {
Message string `json:"message"`
}
type Nip47SignMessageResponse struct {
Message string `json:"message"`
Signature string `json:"signature"`
}

View file

@ -1,13 +0,0 @@
// TODO: move to db/models.go
package db
import "time"
type UserConfig struct {
ID uint
Key string
Value string
Encrypted bool
CreatedAt time.Time
UpdatedAt time.Time
}

View file

@ -1,5 +1,11 @@
package nip47
import (
"encoding/json"
"github.com/getAlby/nostr-wallet-connect/lnclient"
)
const (
INFO_EVENT_KIND = 13194
REQUEST_KIND = 23194
@ -44,3 +50,124 @@ const (
BUDGET_RENEWAL_YEARLY = "yearly"
BUDGET_RENEWAL_NEVER = "never"
)
type Transaction = lnclient.Transaction
type PayRequest struct {
Invoice string `json:"invoice"`
}
type Request struct {
Method string `json:"method"`
Params json.RawMessage `json:"params"`
}
type Response struct {
Error *Error `json:"error,omitempty"`
Result interface{} `json:"result,omitempty"`
ResultType string `json:"result_type"`
}
type Notification struct {
Notification interface{} `json:"notification,omitempty"`
NotificationType string `json:"notification_type"`
}
type Error struct {
Code string `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
type PaymentReceivedNotification struct {
Transaction
}
type PayParams struct {
Invoice string `json:"invoice"`
}
type PayResponse struct {
Preimage string `json:"preimage"`
FeesPaid *uint64 `json:"fees_paid"`
}
type MultiPayKeysendParams struct {
Keysends []MultiPayKeysendElement `json:"keysends"`
}
type MultiPayKeysendElement struct {
KeysendParams
Id string `json:"id"`
}
type MultiPayInvoiceParams struct {
Invoices []MultiPayInvoiceElement `json:"invoices"`
}
type MultiPayInvoiceElement struct {
PayParams
Id string `json:"id"`
}
type KeysendParams struct {
Amount int64 `json:"amount"`
Pubkey string `json:"pubkey"`
Preimage string `json:"preimage"`
TLVRecords []lnclient.TLVRecord `json:"tlv_records"`
}
type BalanceResponse struct {
Balance int64 `json:"balance"`
MaxAmount int `json:"max_amount"`
BudgetRenewal string `json:"budget_renewal"`
}
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"`
}
type MakeInvoiceParams struct {
Amount int64 `json:"amount"`
Description string `json:"description"`
DescriptionHash string `json:"description_hash"`
Expiry int64 `json:"expiry"`
}
type MakeInvoiceResponse struct {
Transaction
}
type LookupInvoiceParams struct {
Invoice string `json:"invoice"`
PaymentHash string `json:"payment_hash"`
}
type LookupInvoiceResponse struct {
Transaction
}
type ListTransactionsParams struct {
From uint64 `json:"from,omitempty"`
Until uint64 `json:"until,omitempty"`
Limit uint64 `json:"limit,omitempty"`
Offset uint64 `json:"offset,omitempty"`
Unpaid bool `json:"unpaid,omitempty"`
Type string `json:"type,omitempty"`
}
type ListTransactionsResponse struct {
Transactions []Transaction `json:"transactions"`
}
type SignMessageParams struct {
Message string `json:"message"`
}
type SignMessageResponse struct {
Message string `json:"message"`
Signature string `json:"signature"`
}

View file

@ -1,3 +1,4 @@
// TODO: move to nip47
package main
import (
@ -5,6 +6,7 @@ import (
"encoding/json"
"errors"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/events"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
@ -39,28 +41,28 @@ func (notifier *Nip47Notifier) ConsumeEvent(ctx context.Context, event *events.E
paymentReceivedEventProperties, ok := event.Properties.(*events.PaymentReceivedEventProperties)
if !ok {
notifier.svc.Logger.WithField("event", event).Error("Failed to cast event")
notifier.svc.logger.WithField("event", event).Error("Failed to cast event")
return errors.New("failed to cast event")
}
transaction, err := notifier.svc.lnClient.LookupInvoice(ctx, paymentReceivedEventProperties.PaymentHash)
if err != nil {
notifier.svc.Logger.
notifier.svc.logger.
WithField("paymentHash", paymentReceivedEventProperties.PaymentHash).
WithError(err).
Error("Failed to lookup invoice by payment hash")
return err
}
notifier.notifySubscribers(ctx, &Nip47Notification{
notifier.notifySubscribers(ctx, &nip47.Notification{
Notification: transaction,
NotificationType: nip47.PAYMENT_RECEIVED_NOTIFICATION,
}, nostr.Tags{})
return nil
}
func (notifier *Nip47Notifier) notifySubscribers(ctx context.Context, notification *Nip47Notification, tags nostr.Tags) {
apps := []App{}
func (notifier *Nip47Notifier) notifySubscribers(ctx context.Context, notification *nip47.Notification, tags nostr.Tags) {
apps := []db.App{}
// TODO: join apps and permissions
notifier.svc.db.Find(&apps)
@ -74,15 +76,15 @@ func (notifier *Nip47Notifier) notifySubscribers(ctx context.Context, notificati
}
}
func (notifier *Nip47Notifier) notifySubscriber(ctx context.Context, app *App, notification *Nip47Notification, tags nostr.Tags) {
notifier.svc.Logger.WithFields(logrus.Fields{
func (notifier *Nip47Notifier) notifySubscriber(ctx context.Context, app *db.App, notification *nip47.Notification, tags nostr.Tags) {
notifier.svc.logger.WithFields(logrus.Fields{
"notification": notification,
"appId": app.ID,
}).Info("Notifying subscriber")
ss, err := nip04.ComputeSharedSecret(app.NostrPubkey, notifier.svc.cfg.NostrSecretKey)
ss, err := nip04.ComputeSharedSecret(app.NostrPubkey, notifier.svc.cfg.GetNostrSecretKey())
if err != nil {
notifier.svc.Logger.WithFields(logrus.Fields{
notifier.svc.logger.WithFields(logrus.Fields{
"notification": notification,
"appId": app.ID,
}).WithError(err).Error("Failed to compute shared secret")
@ -91,7 +93,7 @@ func (notifier *Nip47Notifier) notifySubscriber(ctx context.Context, app *App, n
payloadBytes, err := json.Marshal(notification)
if err != nil {
notifier.svc.Logger.WithFields(logrus.Fields{
notifier.svc.logger.WithFields(logrus.Fields{
"notification": notification,
"appId": app.ID,
}).WithError(err).Error("Failed to stringify notification")
@ -99,7 +101,7 @@ func (notifier *Nip47Notifier) notifySubscriber(ctx context.Context, app *App, n
}
msg, err := nip04.Encrypt(string(payloadBytes), ss)
if err != nil {
notifier.svc.Logger.WithFields(logrus.Fields{
notifier.svc.logger.WithFields(logrus.Fields{
"notification": notification,
"appId": app.ID,
}).WithError(err).Error("Failed to encrypt notification payload")
@ -110,15 +112,15 @@ func (notifier *Nip47Notifier) notifySubscriber(ctx context.Context, app *App, n
allTags = append(allTags, tags...)
event := &nostr.Event{
PubKey: notifier.svc.cfg.NostrPublicKey,
PubKey: notifier.svc.cfg.GetNostrPublicKey(),
CreatedAt: nostr.Now(),
Kind: nip47.NOTIFICATION_KIND,
Tags: allTags,
Content: msg,
}
err = event.Sign(notifier.svc.cfg.NostrSecretKey)
err = event.Sign(notifier.svc.cfg.GetNostrSecretKey())
if err != nil {
notifier.svc.Logger.WithFields(logrus.Fields{
notifier.svc.logger.WithFields(logrus.Fields{
"notification": notification,
"appId": app.ID,
}).WithError(err).Error("Failed to sign event")
@ -127,13 +129,13 @@ func (notifier *Nip47Notifier) notifySubscriber(ctx context.Context, app *App, n
err = notifier.relay.Publish(ctx, *event)
if err != nil {
notifier.svc.Logger.WithFields(logrus.Fields{
notifier.svc.logger.WithFields(logrus.Fields{
"notification": notification,
"appId": app.ID,
}).WithError(err).Error("Failed to publish notification")
return
}
notifier.svc.Logger.WithFields(logrus.Fields{
notifier.svc.logger.WithFields(logrus.Fields{
"notification": notification,
"appId": app.ID,
}).Info("Published notification event")

View file

@ -7,6 +7,7 @@ import (
"os"
"testing"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/events"
"github.com/getAlby/nostr-wallet-connect/nip47"
"github.com/nbd-wtf/go-nostr"
@ -24,7 +25,7 @@ func TestSendNotification(t *testing.T) {
app, ss, err := createApp(svc)
assert.NoError(t, err)
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.NOTIFICATIONS_PERMISSION,
@ -32,8 +33,8 @@ func TestSendNotification(t *testing.T) {
err = svc.db.Create(appPermission).Error
assert.NoError(t, err)
svc.nip47NotificationQueue = nip47.NewNip47NotificationQueue(svc.Logger)
svc.EventPublisher.RegisterSubscriber(svc.nip47NotificationQueue)
svc.nip47NotificationQueue = nip47.NewNip47NotificationQueue(svc.logger)
svc.eventPublisher.RegisterSubscriber(svc.nip47NotificationQueue)
testEvent := &events.Event{
Event: "nwc_payment_received",
@ -44,7 +45,7 @@ func TestSendNotification(t *testing.T) {
},
}
svc.EventPublisher.Publish(testEvent)
svc.eventPublisher.Publish(testEvent)
receivedEvent := <-svc.nip47NotificationQueue.Channel()
assert.Equal(t, testEvent, receivedEvent)
@ -59,15 +60,15 @@ func TestSendNotification(t *testing.T) {
decrypted, err := nip04.Decrypt(relay.publishedEvent.Content, ss)
assert.NoError(t, err)
unmarshalledResponse := Nip47Notification{
Notification: &Nip47PaymentReceivedNotification{},
unmarshalledResponse := nip47.Notification{
Notification: &nip47.PaymentReceivedNotification{},
}
err = json.Unmarshal([]byte(decrypted), &unmarshalledResponse)
assert.NoError(t, err)
assert.Equal(t, nip47.PAYMENT_RECEIVED_NOTIFICATION, unmarshalledResponse.NotificationType)
transaction := (unmarshalledResponse.Notification.(*Nip47PaymentReceivedNotification))
transaction := (unmarshalledResponse.Notification.(*nip47.PaymentReceivedNotification))
assert.Equal(t, mockTransaction.Type, transaction.Type)
assert.Equal(t, mockTransaction.Invoice, transaction.Invoice)
assert.Equal(t, mockTransaction.Description, transaction.Description)
@ -89,8 +90,8 @@ func TestSendNotificationNoPermission(t *testing.T) {
_, _, err = createApp(svc)
assert.NoError(t, err)
svc.nip47NotificationQueue = nip47.NewNip47NotificationQueue(svc.Logger)
svc.EventPublisher.RegisterSubscriber(svc.nip47NotificationQueue)
svc.nip47NotificationQueue = nip47.NewNip47NotificationQueue(svc.logger)
svc.eventPublisher.RegisterSubscriber(svc.nip47NotificationQueue)
testEvent := &events.Event{
Event: "nwc_payment_received",
@ -101,7 +102,7 @@ func TestSendNotificationNoPermission(t *testing.T) {
},
}
svc.EventPublisher.Publish(testEvent)
svc.eventPublisher.Publish(testEvent)
receivedEvent := <-svc.nip47NotificationQueue.Channel()
assert.Equal(t, testEvent, receivedEvent)

View file

@ -28,10 +28,17 @@ import (
alby "github.com/getAlby/nostr-wallet-connect/alby"
"github.com/getAlby/nostr-wallet-connect/events"
"github.com/getAlby/nostr-wallet-connect/utils"
"github.com/getAlby/nostr-wallet-connect/config"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/lnclient"
"github.com/getAlby/nostr-wallet-connect/lnclient/breez"
"github.com/getAlby/nostr-wallet-connect/lnclient/greenlight"
"github.com/getAlby/nostr-wallet-connect/lnclient/ldk"
"github.com/getAlby/nostr-wallet-connect/lnclient/lnd"
"github.com/getAlby/nostr-wallet-connect/lnclient/phoenixd"
"github.com/getAlby/nostr-wallet-connect/migrations"
"github.com/getAlby/nostr-wallet-connect/models/config"
"github.com/getAlby/nostr-wallet-connect/models/lnclient"
"github.com/getAlby/nostr-wallet-connect/nip47"
)
@ -40,24 +47,25 @@ const (
logFilename = "nwc.log"
)
// TODO: move to service/
// TODO: do not expose service struct
type Service struct {
// config from .env only. Fetch dynamic config from db
cfg *Config
// config from .GetEnv() only. Fetch dynamic config from db
cfg config.Config
db *gorm.DB
lnClient lnclient.LNClient
Logger *logrus.Logger
AlbyOAuthSvc *alby.AlbyOAuthService
EventPublisher events.EventPublisher
logger *logrus.Logger
albyOAuthSvc alby.AlbyOAuthService
eventPublisher events.EventPublisher
ctx context.Context
wg *sync.WaitGroup
nip47NotificationQueue nip47.Nip47NotificationQueue
appCancelFn context.CancelFunc
lastWalletSyncRequest time.Time
}
// TODO: move to service.go
func NewService(ctx context.Context) (*Service, error) {
// Load config from environment variables / .env file
// Load config from environment variables / .GetEnv() file
godotenv.Load(".env")
appConfig := &config.AppConfig{}
err := envconfig.Process("", appConfig)
@ -107,28 +115,27 @@ func NewService(ctx context.Context) (*Service, error) {
}
}
var db *gorm.DB
var gormDB *gorm.DB
var sqlDb *sql.DB
db, err = gorm.Open(sqlite.Open(appConfig.DatabaseUri), &gorm.Config{})
gormDB, err = gorm.Open(sqlite.Open(appConfig.DatabaseUri), &gorm.Config{})
if err != nil {
return nil, err
}
// Enable foreign keys for sqlite
db.Exec("PRAGMA foreign_keys=ON;")
sqlDb, err = db.DB()
gormDB.Exec("PRAGMA foreign_keys=ON;")
sqlDb, err = gormDB.DB()
if err != nil {
return nil, err
}
sqlDb.SetMaxOpenConns(1)
err = migrations.Migrate(db, appConfig, logger)
err = migrations.Migrate(gormDB, appConfig, logger)
if err != nil {
logger.WithError(err).Error("Failed to migrate")
return nil, err
}
cfg := &Config{}
cfg.Init(db, appConfig, logger)
cfg := config.NewConfig(gormDB, appConfig, logger)
eventPublisher := events.NewEventPublisher(logger)
@ -143,17 +150,16 @@ func NewService(ctx context.Context) (*Service, error) {
var wg sync.WaitGroup
svc := &Service{
cfg: cfg,
db: db,
db: gormDB,
ctx: ctx,
wg: &wg,
Logger: logger,
EventPublisher: eventPublisher,
logger: logger,
eventPublisher: eventPublisher,
nip47NotificationQueue: nip47NotificationQueue,
albyOAuthSvc: alby.NewAlbyOAuthService(logger, cfg, cfg.GetEnv(), db.NewDBService(gormDB, logger)),
}
// FIXME: tangled dependency
svc.AlbyOAuthSvc = alby.NewAlbyOAuthService(logger, cfg, cfg.Env, NewAPI(svc))
eventPublisher.RegisterSubscriber(svc.AlbyOAuthSvc)
eventPublisher.RegisterSubscriber(svc.albyOAuthSvc)
eventPublisher.Publish(&events.Event{
Event: "nwc_started",
@ -164,11 +170,11 @@ func NewService(ctx context.Context) (*Service, error) {
func (svc *Service) StopLNClient() error {
if svc.lnClient != nil {
svc.Logger.Info("Shutting down LDK client")
svc.logger.Info("Shutting down LDK client")
err := svc.lnClient.Shutdown()
if err != nil {
svc.Logger.WithError(err).Error("Failed to stop LN backend")
svc.EventPublisher.Publish(&events.Event{
svc.logger.WithError(err).Error("Failed to stop LN backend")
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_node_stop_failed",
Properties: map[string]interface{}{
"error": fmt.Sprintf("%v", err),
@ -176,13 +182,13 @@ func (svc *Service) StopLNClient() error {
})
return err
}
svc.Logger.Info("Publishing node shutdown event")
svc.logger.Info("Publishing node shutdown event")
svc.lnClient = nil
svc.EventPublisher.Publish(&events.Event{
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_node_stopped",
})
}
svc.Logger.Info("LNClient stopped successfully")
svc.logger.Info("LNClient stopped successfully")
return nil
}
@ -197,51 +203,51 @@ func (svc *Service) launchLNBackend(ctx context.Context, encryptionKey string) e
return errors.New("no LNBackendType specified")
}
svc.Logger.Infof("Launching LN Backend: %s", lnBackend)
svc.logger.Infof("Launching LN Backend: %s", lnBackend)
var lnClient lnclient.LNClient
switch lnBackend {
case config.LNDBackendType:
LNDAddress, _ := svc.cfg.Get("LNDAddress", encryptionKey)
LNDCertHex, _ := svc.cfg.Get("LNDCertHex", encryptionKey)
LNDMacaroonHex, _ := svc.cfg.Get("LNDMacaroonHex", encryptionKey)
lnClient, err = NewLNDService(ctx, svc, LNDAddress, LNDCertHex, LNDMacaroonHex)
lnClient, err = lnd.NewLNDService(ctx, svc.logger, LNDAddress, LNDCertHex, LNDMacaroonHex)
case config.LDKBackendType:
Mnemonic, _ := svc.cfg.Get("Mnemonic", encryptionKey)
LDKWorkdir := path.Join(svc.cfg.Env.Workdir, "ldk")
LDKWorkdir := path.Join(svc.cfg.GetEnv().Workdir, "ldk")
lnClient, err = NewLDKService(ctx, svc, Mnemonic, LDKWorkdir, svc.cfg.Env.LDKNetwork, svc.cfg.Env.LDKEsploraServer, svc.cfg.Env.LDKGossipSource)
lnClient, err = ldk.NewLDKService(ctx, svc.logger, svc.cfg, svc.eventPublisher, Mnemonic, LDKWorkdir, svc.cfg.GetEnv().LDKNetwork, svc.cfg.GetEnv().LDKEsploraServer, svc.cfg.GetEnv().LDKGossipSource)
case config.GreenlightBackendType:
Mnemonic, _ := svc.cfg.Get("Mnemonic", encryptionKey)
GreenlightInviteCode, _ := svc.cfg.Get("GreenlightInviteCode", encryptionKey)
GreenlightWorkdir := path.Join(svc.cfg.Env.Workdir, "greenlight")
GreenlightWorkdir := path.Join(svc.cfg.GetEnv().Workdir, "greenlight")
lnClient, err = NewGreenlightService(svc, Mnemonic, GreenlightInviteCode, GreenlightWorkdir, encryptionKey)
lnClient, err = greenlight.NewGreenlightService(svc.cfg, svc.logger, Mnemonic, GreenlightInviteCode, GreenlightWorkdir, encryptionKey)
case config.BreezBackendType:
Mnemonic, _ := svc.cfg.Get("Mnemonic", encryptionKey)
BreezAPIKey, _ := svc.cfg.Get("BreezAPIKey", encryptionKey)
GreenlightInviteCode, _ := svc.cfg.Get("GreenlightInviteCode", encryptionKey)
BreezWorkdir := path.Join(svc.cfg.Env.Workdir, "breez")
BreezWorkdir := path.Join(svc.cfg.GetEnv().Workdir, "breez")
lnClient, err = NewBreezService(svc.Logger, Mnemonic, BreezAPIKey, GreenlightInviteCode, BreezWorkdir)
lnClient, err = breez.NewBreezService(svc.logger, Mnemonic, BreezAPIKey, GreenlightInviteCode, BreezWorkdir)
case config.PhoenixBackendType:
lnClient, err = NewPhoenixService(svc, svc.cfg.Env.PhoenixdAddress, svc.cfg.Env.PhoenixdAuthorization)
lnClient, err = phoenixd.NewPhoenixService(svc.logger, svc.cfg.GetEnv().PhoenixdAddress, svc.cfg.GetEnv().PhoenixdAuthorization)
default:
svc.Logger.Fatalf("Unsupported LNBackendType: %v", lnBackend)
svc.logger.Fatalf("Unsupported LNBackendType: %v", lnBackend)
}
if err != nil {
svc.Logger.WithError(err).Error("Failed to launch LN backend")
svc.logger.WithError(err).Error("Failed to launch LN backend")
return err
}
info, err := lnClient.GetInfo(ctx)
if err != nil {
svc.Logger.WithError(err).Error("Failed to fetch node info")
svc.logger.WithError(err).Error("Failed to fetch node info")
}
if info != nil && info.Pubkey != "" {
svc.EventPublisher.SetGlobalProperty("node_id", info.Pubkey)
svc.eventPublisher.SetGlobalProperty("node_id", info.Pubkey)
}
svc.EventPublisher.Publish(&events.Event{
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_node_started",
Properties: map[string]interface{}{
"node_type": lnBackend,
@ -260,7 +266,11 @@ func (svc *Service) createFilters(identityPubkey string) nostr.Filters {
}
func (svc *Service) noticeHandler(notice string) {
svc.Logger.Infof("Received a notice %s", notice)
svc.logger.Infof("Received a notice %s", notice)
}
func (svc *Service) GetLNClient() lnclient.LNClient {
return svc.lnClient
}
func (svc *Service) StartSubscription(ctx context.Context, sub *nostr.Subscription) error {
@ -280,34 +290,34 @@ func (svc *Service) StartSubscription(ctx context.Context, sub *nostr.Subscripti
go func() {
// block till EOS is received
<-sub.EndOfStoredEvents
svc.Logger.Info("Received EOS")
svc.logger.Info("Received EOS")
// loop through incoming events
for event := range sub.Events {
go svc.HandleEvent(ctx, sub, event)
}
svc.Logger.Info("Relay subscription events channel ended")
svc.logger.Info("Relay subscription events channel ended")
}()
<-ctx.Done()
if sub.Relay.ConnectionError != nil {
svc.Logger.WithField("connectionError", sub.Relay.ConnectionError).Error("Relay error")
svc.logger.WithField("connectionError", sub.Relay.ConnectionError).Error("Relay error")
return sub.Relay.ConnectionError
}
svc.Logger.Info("Exiting subscription...")
svc.logger.Info("Exiting subscription...")
return nil
}
func (svc *Service) PublishEvent(ctx context.Context, sub *nostr.Subscription, requestEvent *RequestEvent, resp *nostr.Event, app *App) error {
func (svc *Service) PublishEvent(ctx context.Context, sub *nostr.Subscription, requestEvent *db.RequestEvent, resp *nostr.Event, app *db.App) error {
var appId *uint
if app != nil {
appId = &app.ID
}
responseEvent := ResponseEvent{NostrId: resp.ID, RequestId: requestEvent.ID, Content: resp.Content, State: "received"}
responseEvent := db.ResponseEvent{NostrId: resp.ID, RequestId: requestEvent.ID, Content: resp.Content, State: "received"}
err := svc.db.Create(&responseEvent).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": appId,
"replyEventId": resp.ID,
@ -317,8 +327,8 @@ func (svc *Service) PublishEvent(ctx context.Context, sub *nostr.Subscription, r
err = sub.Relay.Publish(ctx, *resp)
if err != nil {
responseEvent.State = RESPONSE_EVENT_STATE_PUBLISH_FAILED
svc.Logger.WithFields(logrus.Fields{
responseEvent.State = db.RESPONSE_EVENT_STATE_PUBLISH_FAILED
svc.logger.WithFields(logrus.Fields{
"requestEventId": requestEvent.ID,
"requestNostrEventId": requestEvent.NostrId,
"appId": appId,
@ -326,9 +336,9 @@ func (svc *Service) PublishEvent(ctx context.Context, sub *nostr.Subscription, r
"responseNostrEventId": resp.ID,
}).Errorf("Failed to publish reply: %v", err)
} else {
responseEvent.State = RESPONSE_EVENT_STATE_PUBLISH_CONFIRMED
responseEvent.State = db.RESPONSE_EVENT_STATE_PUBLISH_CONFIRMED
responseEvent.RepliedAt = time.Now()
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventId": requestEvent.ID,
"requestNostrEventId": requestEvent.NostrId,
"appId": appId,
@ -339,7 +349,7 @@ func (svc *Service) PublishEvent(ctx context.Context, sub *nostr.Subscription, r
err = svc.db.Save(&responseEvent).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventId": requestEvent.ID,
"requestNostrEventId": requestEvent.NostrId,
"appId": appId,
@ -353,25 +363,15 @@ func (svc *Service) PublishEvent(ctx context.Context, sub *nostr.Subscription, r
}
func (svc *Service) HandleEvent(ctx context.Context, sub *nostr.Subscription, event *nostr.Event) {
var nip47Response *Nip47Response
svc.Logger.WithFields(logrus.Fields{
var nip47Response *nip47.Response
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).Info("Processing Event")
// make sure we don't know the event, yet
requestEvent := RequestEvent{}
findEventResult := svc.db.Where("nostr_id = ?", event.ID).Find(&requestEvent)
if findEventResult.RowsAffected != 0 {
svc.Logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
}).Warn("Event already processed")
return
}
ss, err := nip04.ComputeSharedSecret(event.PubKey, svc.cfg.NostrSecretKey)
ss, err := nip04.ComputeSharedSecret(event.PubKey, svc.cfg.GetNostrSecretKey())
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).Errorf("Failed to compute shared secret: %v", err)
@ -379,22 +379,28 @@ func (svc *Service) HandleEvent(ctx context.Context, sub *nostr.Subscription, ev
}
// store request event
requestEvent = RequestEvent{AppId: nil, NostrId: event.ID, Content: event.Content, State: REQUEST_EVENT_STATE_HANDLER_EXECUTING}
requestEvent := db.RequestEvent{AppId: nil, NostrId: event.ID, Content: event.Content, State: db.REQUEST_EVENT_STATE_HANDLER_EXECUTING}
err = svc.db.Create(&requestEvent).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
if errors.Is(err, gorm.ErrDuplicatedKey) {
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
}).Warn("Event already processed")
return
}
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).Errorf("Failed to save nostr event: %v", err)
nip47Response = &Nip47Response{
Error: &Nip47Error{
nip47Response = &nip47.Response{
Error: &nip47.Error{
Code: nip47.ERROR_INTERNAL,
Message: fmt.Sprintf("Failed to save nostr event: %s", err.Error()),
},
}
resp, err := svc.createResponse(event, nip47Response, nostr.Tags{}, ss)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).Errorf("Failed to process event: %v", err)
@ -403,34 +409,34 @@ func (svc *Service) HandleEvent(ctx context.Context, sub *nostr.Subscription, ev
return
}
app := App{}
err = svc.db.First(&app, &App{
app := db.App{}
err = svc.db.First(&app, &db.App{
NostrPubkey: event.PubKey,
}).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"nostrPubkey": event.PubKey,
}).Errorf("Failed to find app for nostr pubkey: %v", err)
nip47Response = &Nip47Response{
Error: &Nip47Error{
nip47Response = &nip47.Response{
Error: &nip47.Error{
Code: nip47.ERROR_UNAUTHORIZED,
Message: "The public key does not have a wallet connected.",
},
}
resp, err := svc.createResponse(event, nip47Response, nostr.Tags{}, ss)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).Errorf("Failed to process event: %v", err)
}
svc.PublishEvent(ctx, sub, &requestEvent, resp, &app)
requestEvent.State = REQUEST_EVENT_STATE_HANDLER_ERROR
requestEvent.State = db.REQUEST_EVENT_STATE_HANDLER_ERROR
err = svc.db.Save(&requestEvent).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"nostrPubkey": event.PubKey,
}).Errorf("Failed to save state to nostr event: %v", err)
}
@ -440,29 +446,29 @@ func (svc *Service) HandleEvent(ctx context.Context, sub *nostr.Subscription, ev
requestEvent.AppId = &app.ID
err = svc.db.Save(&requestEvent).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"nostrPubkey": event.PubKey,
}).Errorf("Failed to save app to nostr event: %v", err)
nip47Response = &Nip47Response{
Error: &Nip47Error{
nip47Response = &nip47.Response{
Error: &nip47.Error{
Code: nip47.ERROR_UNAUTHORIZED,
Message: fmt.Sprintf("Failed to save app to nostr event: %s", err.Error()),
},
}
resp, err := svc.createResponse(event, nip47Response, nostr.Tags{}, ss)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).Errorf("Failed to process event: %v", err)
}
svc.PublishEvent(ctx, sub, &requestEvent, resp, &app)
requestEvent.State = REQUEST_EVENT_STATE_HANDLER_ERROR
requestEvent.State = db.REQUEST_EVENT_STATE_HANDLER_ERROR
err = svc.db.Save(&requestEvent).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"nostrPubkey": event.PubKey,
}).Errorf("Failed to save state to nostr event: %v", err)
}
@ -470,24 +476,24 @@ func (svc *Service) HandleEvent(ctx context.Context, sub *nostr.Subscription, ev
return
}
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
"appId": app.ID,
}).Info("App found for nostr event")
//to be extra safe, decrypt using the key found from the app
ss, err = nip04.ComputeSharedSecret(app.NostrPubkey, svc.cfg.NostrSecretKey)
ss, err = nip04.ComputeSharedSecret(app.NostrPubkey, svc.cfg.GetNostrSecretKey())
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).Errorf("Failed to process event: %v", err)
requestEvent.State = REQUEST_EVENT_STATE_HANDLER_ERROR
requestEvent.State = db.REQUEST_EVENT_STATE_HANDLER_ERROR
err = svc.db.Save(&requestEvent).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"nostrPubkey": event.PubKey,
}).Errorf("Failed to save state to nostr event: %v", err)
}
@ -496,38 +502,38 @@ func (svc *Service) HandleEvent(ctx context.Context, sub *nostr.Subscription, ev
}
payload, err := nip04.Decrypt(event.Content, ss)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
"appId": app.ID,
}).Errorf("Failed to decrypt content: %v", err)
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).Errorf("Failed to process event: %v", err)
requestEvent.State = REQUEST_EVENT_STATE_HANDLER_ERROR
requestEvent.State = db.REQUEST_EVENT_STATE_HANDLER_ERROR
err = svc.db.Save(&requestEvent).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"nostrPubkey": event.PubKey,
}).Errorf("Failed to save state to nostr event: %v", err)
}
return
}
nip47Request := &Nip47Request{}
nip47Request := &nip47.Request{}
err = json.Unmarshal([]byte(payload), nip47Request)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).Errorf("Failed to process event: %v", err)
requestEvent.State = REQUEST_EVENT_STATE_HANDLER_ERROR
requestEvent.State = db.REQUEST_EVENT_STATE_HANDLER_ERROR
err = svc.db.Save(&requestEvent).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"nostrPubkey": event.PubKey,
}).Errorf("Failed to save state to nostr event: %v", err)
}
@ -537,29 +543,29 @@ func (svc *Service) HandleEvent(ctx context.Context, sub *nostr.Subscription, ev
// TODO: replace with a channel
// TODO: update all previous occurences of svc.PublishEvent to also use the channel
publishResponse := func(nip47Response *Nip47Response, tags nostr.Tags) {
publishResponse := func(nip47Response *nip47.Response, tags nostr.Tags) {
resp, err := svc.createResponse(event, nip47Response, tags, ss)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).Errorf("Failed to create response: %v", err)
requestEvent.State = REQUEST_EVENT_STATE_HANDLER_ERROR
requestEvent.State = db.REQUEST_EVENT_STATE_HANDLER_ERROR
} else {
err = svc.PublishEvent(ctx, sub, &requestEvent, resp, &app)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": event.ID,
"eventKind": event.Kind,
}).Errorf("Failed to publish event: %v", err)
requestEvent.State = REQUEST_EVENT_STATE_HANDLER_ERROR
requestEvent.State = db.REQUEST_EVENT_STATE_HANDLER_ERROR
} else {
requestEvent.State = REQUEST_EVENT_STATE_HANDLER_EXECUTED
requestEvent.State = db.REQUEST_EVENT_STATE_HANDLER_EXECUTED
}
}
err = svc.db.Save(&requestEvent).Error
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"nostrPubkey": event.PubKey,
}).Errorf("Failed to save state to nostr event: %v", err)
}
@ -591,10 +597,10 @@ func (svc *Service) HandleEvent(ctx context.Context, sub *nostr.Subscription, ev
}
}
func (svc *Service) handleUnknownMethod(ctx context.Context, nip47Request *Nip47Request, publishResponse func(*Nip47Response, nostr.Tags)) {
publishResponse(&Nip47Response{
func (svc *Service) handleUnknownMethod(ctx context.Context, nip47Request *nip47.Request, publishResponse func(*nip47.Response, nostr.Tags)) {
publishResponse(&nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_NOT_IMPLEMENTED,
Message: fmt.Sprintf("Unknown method: %s", nip47Request.Method),
},
@ -615,22 +621,22 @@ func (svc *Service) createResponse(initialEvent *nostr.Event, content interface{
allTags = append(allTags, tags...)
resp := &nostr.Event{
PubKey: svc.cfg.NostrPublicKey,
PubKey: svc.cfg.GetNostrPublicKey(),
CreatedAt: nostr.Now(),
Kind: nip47.RESPONSE_KIND,
Tags: allTags,
Content: msg,
}
err = resp.Sign(svc.cfg.NostrSecretKey)
err = resp.Sign(svc.cfg.GetNostrSecretKey())
if err != nil {
return nil, err
}
return resp, nil
}
func (svc *Service) GetMethods(app *App) []string {
appPermissions := []AppPermission{}
svc.db.Find(&appPermissions, &AppPermission{
func (svc *Service) GetMethods(app *db.App) []string {
appPermissions := []db.AppPermission{}
svc.db.Find(&appPermissions, &db.AppPermission{
AppId: app.ID,
})
requestMethods := make([]string, 0, len(appPermissions))
@ -645,16 +651,16 @@ func (svc *Service) GetMethods(app *App) []string {
return requestMethods
}
func (svc *Service) decodeNip47Request(nip47Request *Nip47Request, requestEvent *RequestEvent, app *App, methodParams interface{}) *Nip47Response {
func (svc *Service) decodeNip47Request(nip47Request *nip47.Request, requestEvent *db.RequestEvent, app *db.App, methodParams interface{}) *nip47.Response {
err := json.Unmarshal(nip47Request.Params, methodParams)
if err != nil {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestEvent.NostrId,
"appId": app.ID,
}).Errorf("Failed to decode nostr event: %v", err)
return &Nip47Response{
return &nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: nip47.ERROR_BAD_REQUEST,
Message: err.Error(),
}}
@ -662,17 +668,17 @@ func (svc *Service) decodeNip47Request(nip47Request *Nip47Request, requestEvent
return nil
}
func (svc *Service) checkPermission(nip47Request *Nip47Request, requestNostrEventId string, app *App, amount int64) *Nip47Response {
func (svc *Service) checkPermission(nip47Request *nip47.Request, requestNostrEventId string, app *db.App, amount int64) *nip47.Response {
hasPermission, code, message := svc.hasPermission(app, nip47Request.Method, amount)
if !hasPermission {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestEventNostrId": requestNostrEventId,
"appId": app.ID,
"code": code,
"message": message,
}).Error("App does not have permission")
svc.EventPublisher.Publish(&events.Event{
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_permission_denied",
Properties: map[string]interface{}{
"request_method": nip47Request.Method,
@ -683,9 +689,9 @@ func (svc *Service) checkPermission(nip47Request *Nip47Request, requestNostrEven
},
})
return &Nip47Response{
return &nip47.Response{
ResultType: nip47Request.Method,
Error: &Nip47Error{
Error: &nip47.Error{
Code: code,
Message: message,
},
@ -694,14 +700,14 @@ func (svc *Service) checkPermission(nip47Request *Nip47Request, requestNostrEven
return nil
}
func (svc *Service) hasPermission(app *App, requestMethod string, amount int64) (result bool, code string, message string) {
func (svc *Service) hasPermission(app *db.App, requestMethod string, amount int64) (result bool, code string, message string) {
switch requestMethod {
case nip47.PAY_INVOICE_METHOD, nip47.PAY_KEYSEND_METHOD, nip47.MULTI_PAY_INVOICE_METHOD, nip47.MULTI_PAY_KEYSEND_METHOD:
requestMethod = nip47.PAY_INVOICE_METHOD
}
appPermission := AppPermission{}
findPermissionResult := svc.db.Find(&appPermission, &AppPermission{
appPermission := db.AppPermission{}
findPermissionResult := svc.db.Find(&appPermission, &db.AppPermission{
AppId: app.ID,
RequestMethod: requestMethod,
})
@ -711,7 +717,7 @@ func (svc *Service) hasPermission(app *App, requestMethod string, amount int64)
}
expiresAt := appPermission.ExpiresAt
if expiresAt != nil && expiresAt.Before(time.Now()) {
svc.Logger.WithFields(logrus.Fields{
svc.logger.WithFields(logrus.Fields{
"requestMethod": requestMethod,
"expiresAt": expiresAt.Unix(),
"appId": app.ID,
@ -734,12 +740,13 @@ func (svc *Service) hasPermission(app *App, requestMethod string, amount int64)
return true, "", ""
}
func (svc *Service) GetBudgetUsage(appPermission *AppPermission) int64 {
// TODO: move somewhere else
func (svc *Service) GetBudgetUsage(appPermission *db.AppPermission) int64 {
var result struct {
Sum uint
}
// TODO: discard failed payments from this check instead of checking payments that have a preimage
svc.db.Table("payments").Select("SUM(amount) as sum").Where("app_id = ? AND preimage IS NOT NULL AND created_at > ?", appPermission.AppId, GetStartOfBudget(appPermission.BudgetRenewal, appPermission.App.CreatedAt)).Scan(&result)
svc.db.Table("payments").Select("SUM(amount) as sum").Where("app_id = ? AND preimage IS NOT NULL AND created_at > ?", appPermission.AppId, utils.GetStartOfBudget(appPermission.BudgetRenewal, appPermission.App.CreatedAt)).Scan(&result)
return int64(result.Sum)
}
@ -748,9 +755,9 @@ func (svc *Service) PublishNip47Info(ctx context.Context, relay *nostr.Relay) er
ev.Kind = nip47.INFO_EVENT_KIND
ev.Content = nip47.CAPABILITIES
ev.CreatedAt = nostr.Now()
ev.PubKey = svc.cfg.NostrPublicKey
ev.PubKey = svc.cfg.GetNostrPublicKey()
ev.Tags = nostr.Tags{[]string{"notifications", nip47.NOTIFICATION_TYPES}}
err := ev.Sign(svc.cfg.NostrSecretKey)
err := ev.Sign(svc.cfg.GetNostrSecretKey())
if err != nil {
return err
}
@ -761,8 +768,8 @@ func (svc *Service) PublishNip47Info(ctx context.Context, relay *nostr.Relay) er
return nil
}
func (svc *Service) LogFilePath() string {
return filepath.Join(svc.cfg.Env.Workdir, logDir, logFilename)
func (svc *Service) GetLogFilePath() string {
return filepath.Join(svc.cfg.GetEnv().Workdir, logDir, logFilename)
}
func finishRestoreNode(logger *logrus.Logger, workDir string) {
@ -803,3 +810,24 @@ func finishRestoreNode(logger *logrus.Logger, workDir string) {
logger.WithField("restoreDir", restoreDir).Info("removed restore directory")
}
}
func (svc *Service) StopDb() error {
db, err := svc.db.DB()
if err != nil {
return fmt.Errorf("failed to get database connection: %w", err)
}
err = db.Close()
if err != nil {
return fmt.Errorf("failed to close database connection: %w", err)
}
return nil
}
func (svc *Service) GetConfig() config.Config {
return svc.cfg
}
func (svc *Service) GetAlbyOAuthSvc() alby.AlbyOAuthService {
return svc.albyOAuthSvc
}

20
service/models.go Normal file
View file

@ -0,0 +1,20 @@
package service
import (
"github.com/getAlby/nostr-wallet-connect/alby"
"github.com/getAlby/nostr-wallet-connect/config"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/lnclient"
)
type Service interface {
GetLNClient() lnclient.LNClient
GetConfig() config.Config
StartApp(encryptionKey string) error
StopApp()
StopLNClient() error
StopDb() error
GetBudgetUsage(appPermission *db.AppPermission) int64
GetLogFilePath() string
GetAlbyOAuthSvc() alby.AlbyOAuthService
}

View file

@ -14,10 +14,11 @@ import (
"github.com/stretchr/testify/assert"
"gorm.io/gorm"
"github.com/getAlby/nostr-wallet-connect/config"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/events"
"github.com/getAlby/nostr-wallet-connect/lnclient"
"github.com/getAlby/nostr-wallet-connect/migrations"
"github.com/getAlby/nostr-wallet-connect/models/config"
"github.com/getAlby/nostr-wallet-connect/models/lnclient"
"github.com/getAlby/nostr-wallet-connect/nip47"
)
@ -223,7 +224,7 @@ var mockNodeInfo = lnclient.NodeInfo{
var mockTime = time.Unix(1693876963, 0)
var mockTimeUnix = mockTime.Unix()
var mockTransactions = []Nip47Transaction{
var mockTransactions = []nip47.Transaction{
{
Type: "incoming",
Invoice: mockInvoice,
@ -287,7 +288,7 @@ func TestHasPermission_Expired(t *testing.T) {
budgetRenewal := "never"
expiresAt := time.Now().Add(-24 * time.Hour)
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.PAY_INVOICE_METHOD,
@ -316,7 +317,7 @@ func TestHasPermission_Exceeded(t *testing.T) {
budgetRenewal := "never"
expiresAt := time.Now().Add(24 * time.Hour)
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.PAY_INVOICE_METHOD,
@ -345,7 +346,7 @@ func TestHasPermission_OK(t *testing.T) {
budgetRenewal := "never"
expiresAt := time.Now().Add(24 * time.Hour)
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.PAY_INVOICE_METHOD,
@ -381,12 +382,12 @@ func TestCreateResponse(t *testing.T) {
reqEvent.ID = "12345"
ss, err := nip04.ComputeSharedSecret(reqPubkey, svc.cfg.NostrSecretKey)
ss, err := nip04.ComputeSharedSecret(reqPubkey, svc.cfg.GetNostrSecretKey())
assert.NoError(t, err)
nip47Response := &Nip47Response{
nip47Response := &nip47.Response{
ResultType: nip47.GET_BALANCE_METHOD,
Result: Nip47BalanceResponse{
Result: nip47.BalanceResponse{
Balance: 1000,
},
}
@ -394,18 +395,18 @@ func TestCreateResponse(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, reqPubkey, res.Tags.GetFirst([]string{"p"}).Value())
assert.Equal(t, reqEvent.ID, res.Tags.GetFirst([]string{"e"}).Value())
assert.Equal(t, svc.cfg.NostrPublicKey, res.PubKey)
assert.Equal(t, svc.cfg.GetNostrPublicKey(), res.PubKey)
decrypted, err := nip04.Decrypt(res.Content, ss)
assert.NoError(t, err)
unmarshalledResponse := Nip47Response{
Result: &Nip47BalanceResponse{},
unmarshalledResponse := nip47.Response{
Result: &nip47.BalanceResponse{},
}
err = json.Unmarshal([]byte(decrypted), &unmarshalledResponse)
assert.NoError(t, err)
assert.Equal(t, nip47Response.ResultType, unmarshalledResponse.ResultType)
assert.Equal(t, nip47Response.Result, *unmarshalledResponse.Result.(*Nip47BalanceResponse))
assert.Equal(t, nip47Response.Result, *unmarshalledResponse.Result.(*nip47.BalanceResponse))
}
func TestHandleEncryption(t *testing.T) {}
@ -420,7 +421,7 @@ func TestHandleMultiPayInvoiceEvent(t *testing.T) {
app, ss, err := createApp(svc)
assert.NoError(t, err)
request := &Nip47Request{}
request := &nip47.Request{}
err = json.Unmarshal([]byte(nip47MultiPayJson), request)
assert.NoError(t, err)
@ -432,17 +433,17 @@ func TestHandleMultiPayInvoiceEvent(t *testing.T) {
PubKey: app.NostrPubkey,
Content: payload,
}
requestEvent := &RequestEvent{
requestEvent := &db.RequestEvent{
Content: reqEvent.Content,
}
reqEvent.ID = "multi_pay_invoice_without_permission"
requestEvent.NostrId = reqEvent.ID
responses := []*Nip47Response{}
responses := []*nip47.Response{}
dTags := []nostr.Tags{}
publishResponse := func(response *Nip47Response, tags nostr.Tags) {
publishResponse := func(response *nip47.Response, tags nostr.Tags) {
responses = append(responses, response)
dTags = append(dTags, tags)
}
@ -460,7 +461,7 @@ func TestHandleMultiPayInvoiceEvent(t *testing.T) {
maxAmount := 1000
budgetRenewal := "never"
expiresAt := time.Now().Add(24 * time.Hour)
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.PAY_INVOICE_METHOD,
@ -473,13 +474,13 @@ func TestHandleMultiPayInvoiceEvent(t *testing.T) {
reqEvent.ID = "multi_pay_invoice_with_permission"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
dTags = []nostr.Tags{}
svc.HandleMultiPayInvoiceEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, 2, len(responses))
for i := 0; i < len(responses); i++ {
assert.Equal(t, responses[i].Result.(Nip47PayResponse).Preimage, "123preimage")
assert.Equal(t, responses[i].Result.(nip47.PayResponse).Preimage, "123preimage")
assert.Equal(t, mockPaymentHash, dTags[i].GetFirst([]string{"d"}).Value())
}
@ -493,7 +494,7 @@ func TestHandleMultiPayInvoiceEvent(t *testing.T) {
reqEvent.ID = "multi_pay_invoice_with_one_malformed_invoice"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
dTags = []nostr.Tags{}
svc.HandleMultiPayInvoiceEvent(ctx, request, requestEvent, app, publishResponse)
@ -502,13 +503,13 @@ func TestHandleMultiPayInvoiceEvent(t *testing.T) {
assert.Equal(t, responses[0].Error.Code, nip47.ERROR_INTERNAL)
assert.Equal(t, mockPaymentHash, dTags[1].GetFirst([]string{"d"}).Value())
assert.Equal(t, responses[1].Result.(Nip47PayResponse).Preimage, "123preimage")
assert.Equal(t, responses[1].Result.(nip47.PayResponse).Preimage, "123preimage")
// we've spent 369 till here in three payments
// budget overflow
newMaxAmount := 500
err = svc.db.Model(&AppPermission{}).Where("app_id = ?", app.ID).Update("max_amount", newMaxAmount).Error
err = svc.db.Model(&db.AppPermission{}).Where("app_id = ?", app.ID).Update("max_amount", newMaxAmount).Error
assert.NoError(t, err)
err = json.Unmarshal([]byte(nip47MultiPayOneOverflowingBudgetJson), request)
@ -520,7 +521,7 @@ func TestHandleMultiPayInvoiceEvent(t *testing.T) {
reqEvent.ID = "multi_pay_invoice_with_budget_overflow"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
dTags = []nostr.Tags{}
svc.HandleMultiPayInvoiceEvent(ctx, request, requestEvent, app, publishResponse)
@ -529,7 +530,7 @@ func TestHandleMultiPayInvoiceEvent(t *testing.T) {
// publishResponse as it's called earlier
assert.Equal(t, responses[0].Error.Code, nip47.ERROR_QUOTA_EXCEEDED)
assert.Equal(t, mockPaymentHash500, dTags[0].GetFirst([]string{"d"}).Value())
assert.Equal(t, responses[1].Result.(Nip47PayResponse).Preimage, "123preimage")
assert.Equal(t, responses[1].Result.(nip47.PayResponse).Preimage, "123preimage")
assert.Equal(t, mockPaymentHash, dTags[1].GetFirst([]string{"d"}).Value())
}
@ -544,7 +545,7 @@ func TestHandleMultiPayKeysendEvent(t *testing.T) {
app, ss, err := createApp(svc)
assert.NoError(t, err)
request := &Nip47Request{}
request := &nip47.Request{}
err = json.Unmarshal([]byte(nip47MultiPayKeysendJson), request)
assert.NoError(t, err)
@ -556,17 +557,17 @@ func TestHandleMultiPayKeysendEvent(t *testing.T) {
PubKey: app.NostrPubkey,
Content: payload,
}
requestEvent := &RequestEvent{
requestEvent := &db.RequestEvent{
Content: reqEvent.Content,
}
reqEvent.ID = "multi_pay_keysend_without_permission"
requestEvent.NostrId = reqEvent.ID
responses := []*Nip47Response{}
responses := []*nip47.Response{}
dTags := []nostr.Tags{}
publishResponse := func(response *Nip47Response, tags nostr.Tags) {
publishResponse := func(response *nip47.Response, tags nostr.Tags) {
responses = append(responses, response)
dTags = append(dTags, tags)
}
@ -585,7 +586,7 @@ func TestHandleMultiPayKeysendEvent(t *testing.T) {
// because we need the same permission for keysend although
// it works even with nip47.PAY_KEYSEND_METHOD, see
// https://github.com/getAlby/nostr-wallet-connect/issues/189
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.PAY_INVOICE_METHOD,
@ -598,13 +599,13 @@ func TestHandleMultiPayKeysendEvent(t *testing.T) {
reqEvent.ID = "multi_pay_keysend_with_permission"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
dTags = []nostr.Tags{}
svc.HandleMultiPayKeysendEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, 2, len(responses))
for i := 0; i < len(responses); i++ {
assert.Equal(t, responses[i].Result.(Nip47PayResponse).Preimage, "12345preimage")
assert.Equal(t, responses[i].Result.(nip47.PayResponse).Preimage, "12345preimage")
assert.Equal(t, "123pubkey", dTags[i].GetFirst([]string{"d"}).Value())
}
@ -612,7 +613,7 @@ func TestHandleMultiPayKeysendEvent(t *testing.T) {
// budget overflow
newMaxAmount := 500
err = svc.db.Model(&AppPermission{}).Where("app_id = ?", app.ID).Update("max_amount", newMaxAmount).Error
err = svc.db.Model(&db.AppPermission{}).Where("app_id = ?", app.ID).Update("max_amount", newMaxAmount).Error
assert.NoError(t, err)
err = json.Unmarshal([]byte(nip47MultiPayKeysendOneOverflowingBudgetJson), request)
@ -624,13 +625,13 @@ func TestHandleMultiPayKeysendEvent(t *testing.T) {
reqEvent.ID = "multi_pay_keysend_with_budget_overflow"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
dTags = []nostr.Tags{}
svc.HandleMultiPayKeysendEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, responses[0].Error.Code, nip47.ERROR_QUOTA_EXCEEDED)
assert.Equal(t, "500pubkey", dTags[0].GetFirst([]string{"d"}).Value())
assert.Equal(t, responses[1].Result.(Nip47PayResponse).Preimage, "12345preimage")
assert.Equal(t, responses[1].Result.(nip47.PayResponse).Preimage, "12345preimage")
assert.Equal(t, "customId", dTags[1].GetFirst([]string{"d"}).Value())
}
@ -644,7 +645,7 @@ func TestHandleGetBalanceEvent(t *testing.T) {
app, ss, err := createApp(svc)
assert.NoError(t, err)
request := &Nip47Request{}
request := &nip47.Request{}
err = json.Unmarshal([]byte(nip47GetBalanceJson), request)
assert.NoError(t, err)
@ -656,16 +657,16 @@ func TestHandleGetBalanceEvent(t *testing.T) {
PubKey: app.NostrPubkey,
Content: payload,
}
requestEvent := &RequestEvent{
requestEvent := &db.RequestEvent{
Content: reqEvent.Content,
}
reqEvent.ID = "test_get_balance_without_permission"
requestEvent.NostrId = reqEvent.ID
responses := []*Nip47Response{}
responses := []*nip47.Response{}
publishResponse := func(response *Nip47Response, tags nostr.Tags) {
publishResponse := func(response *nip47.Response, tags nostr.Tags) {
responses = append(responses, response)
}
@ -678,7 +679,7 @@ func TestHandleGetBalanceEvent(t *testing.T) {
// with permission
expiresAt := time.Now().Add(24 * time.Hour)
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.GET_BALANCE_METHOD,
@ -689,15 +690,15 @@ func TestHandleGetBalanceEvent(t *testing.T) {
reqEvent.ID = "test_get_balance_with_permission"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandleGetBalanceEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, responses[0].Result.(*Nip47BalanceResponse).Balance, int64(21000))
assert.Equal(t, responses[0].Result.(*nip47.BalanceResponse).Balance, int64(21000))
// create pay_invoice permission
maxAmount := 1000
budgetRenewal := "never"
appPermission = &AppPermission{
appPermission = &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.PAY_INVOICE_METHOD,
@ -709,12 +710,12 @@ func TestHandleGetBalanceEvent(t *testing.T) {
assert.NoError(t, err)
reqEvent.ID = "test_get_balance_with_budget"
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandleGetBalanceEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, int64(21000), responses[0].Result.(*Nip47BalanceResponse).Balance)
assert.Equal(t, 1000000, responses[0].Result.(*Nip47BalanceResponse).MaxAmount)
assert.Equal(t, "never", responses[0].Result.(*Nip47BalanceResponse).BudgetRenewal)
assert.Equal(t, int64(21000), responses[0].Result.(*nip47.BalanceResponse).Balance)
assert.Equal(t, 1000000, responses[0].Result.(*nip47.BalanceResponse).MaxAmount)
assert.Equal(t, "never", responses[0].Result.(*nip47.BalanceResponse).BudgetRenewal)
}
func TestHandlePayInvoiceEvent(t *testing.T) {
@ -727,7 +728,7 @@ func TestHandlePayInvoiceEvent(t *testing.T) {
app, ss, err := createApp(svc)
assert.NoError(t, err)
request := &Nip47Request{}
request := &nip47.Request{}
err = json.Unmarshal([]byte(nip47PayJson), request)
assert.NoError(t, err)
@ -739,16 +740,16 @@ func TestHandlePayInvoiceEvent(t *testing.T) {
PubKey: app.NostrPubkey,
Content: payload,
}
requestEvent := &RequestEvent{
requestEvent := &db.RequestEvent{
Content: reqEvent.Content,
}
reqEvent.ID = "pay_invoice_without_permission"
requestEvent.NostrId = reqEvent.ID
responses := []*Nip47Response{}
responses := []*nip47.Response{}
publishResponse := func(response *Nip47Response, tags nostr.Tags) {
publishResponse := func(response *nip47.Response, tags nostr.Tags) {
responses = append(responses, response)
}
@ -760,7 +761,7 @@ func TestHandlePayInvoiceEvent(t *testing.T) {
maxAmount := 1000
budgetRenewal := "never"
expiresAt := time.Now().Add(24 * time.Hour)
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.PAY_INVOICE_METHOD,
@ -773,10 +774,10 @@ func TestHandlePayInvoiceEvent(t *testing.T) {
reqEvent.ID = "pay_invoice_with_permission"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandlePayInvoiceEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, responses[0].Result.(Nip47PayResponse).Preimage, "123preimage")
assert.Equal(t, responses[0].Result.(nip47.PayResponse).Preimage, "123preimage")
// malformed invoice
err = json.Unmarshal([]byte(nip47PayJsonNoInvoice), request)
@ -788,7 +789,7 @@ func TestHandlePayInvoiceEvent(t *testing.T) {
reqEvent.ID = "pay_invoice_with_malformed_invoice"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandlePayInvoiceEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, nip47.ERROR_INTERNAL, responses[0].Error.Code)
@ -803,14 +804,14 @@ func TestHandlePayInvoiceEvent(t *testing.T) {
reqEvent.ID = "pay_invoice_with_wrong_request_method"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandlePayInvoiceEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, nip47.ERROR_RESTRICTED, responses[0].Error.Code)
// budget overflow
newMaxAmount := 100
err = svc.db.Model(&AppPermission{}).Where("app_id = ?", app.ID).Update("max_amount", newMaxAmount).Error
err = svc.db.Model(&db.AppPermission{}).Where("app_id = ?", app.ID).Update("max_amount", newMaxAmount).Error
assert.NoError(t, err)
err = json.Unmarshal([]byte(nip47PayJson), request)
@ -822,33 +823,33 @@ func TestHandlePayInvoiceEvent(t *testing.T) {
reqEvent.ID = "pay_invoice_with_budget_overflow"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandlePayInvoiceEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, nip47.ERROR_QUOTA_EXCEEDED, responses[0].Error.Code)
// budget expiry
newExpiry := time.Now().Add(-24 * time.Hour)
err = svc.db.Model(&AppPermission{}).Where("app_id = ?", app.ID).Update("max_amount", maxAmount).Update("expires_at", newExpiry).Error
err = svc.db.Model(&db.AppPermission{}).Where("app_id = ?", app.ID).Update("max_amount", maxAmount).Update("expires_at", newExpiry).Error
assert.NoError(t, err)
reqEvent.ID = "pay_invoice_with_budget_expiry"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandlePayInvoiceEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, nip47.ERROR_EXPIRED, responses[0].Error.Code)
// check again
err = svc.db.Model(&AppPermission{}).Where("app_id = ?", app.ID).Update("expires_at", nil).Error
err = svc.db.Model(&db.AppPermission{}).Where("app_id = ?", app.ID).Update("expires_at", nil).Error
assert.NoError(t, err)
reqEvent.ID = "pay_invoice_after_change"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandlePayInvoiceEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, responses[0].Result.(Nip47PayResponse).Preimage, "123preimage")
assert.Equal(t, responses[0].Result.(nip47.PayResponse).Preimage, "123preimage")
}
func TestHandlePayKeysendEvent(t *testing.T) {
@ -861,7 +862,7 @@ func TestHandlePayKeysendEvent(t *testing.T) {
app, ss, err := createApp(svc)
assert.NoError(t, err)
request := &Nip47Request{}
request := &nip47.Request{}
err = json.Unmarshal([]byte(nip47KeysendJson), request)
assert.NoError(t, err)
@ -873,16 +874,16 @@ func TestHandlePayKeysendEvent(t *testing.T) {
PubKey: app.NostrPubkey,
Content: payload,
}
requestEvent := &RequestEvent{
requestEvent := &db.RequestEvent{
Content: reqEvent.Content,
}
reqEvent.ID = "pay_keysend_without_permission"
requestEvent.NostrId = reqEvent.ID
responses := []*Nip47Response{}
responses := []*nip47.Response{}
publishResponse := func(response *Nip47Response, tags nostr.Tags) {
publishResponse := func(response *nip47.Response, tags nostr.Tags) {
responses = append(responses, response)
}
@ -897,7 +898,7 @@ func TestHandlePayKeysendEvent(t *testing.T) {
// because we need the same permission for keysend although
// it works even with nip47.PAY_KEYSEND_METHOD, see
// https://github.com/getAlby/nostr-wallet-connect/issues/189
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.PAY_INVOICE_METHOD,
@ -910,14 +911,14 @@ func TestHandlePayKeysendEvent(t *testing.T) {
reqEvent.ID = "pay_keysend_with_permission"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandlePayKeysendEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, responses[0].Result.(Nip47PayResponse).Preimage, "12345preimage")
assert.Equal(t, responses[0].Result.(nip47.PayResponse).Preimage, "12345preimage")
// budget overflow
newMaxAmount := 100
err = svc.db.Model(&AppPermission{}).Where("app_id = ?", app.ID).Update("max_amount", newMaxAmount).Error
err = svc.db.Model(&db.AppPermission{}).Where("app_id = ?", app.ID).Update("max_amount", newMaxAmount).Error
assert.NoError(t, err)
err = json.Unmarshal([]byte(nip47KeysendJson), request)
@ -929,7 +930,7 @@ func TestHandlePayKeysendEvent(t *testing.T) {
reqEvent.ID = "pay_keysend_with_budget_overflow"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandlePayKeysendEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, nip47.ERROR_QUOTA_EXCEEDED, responses[0].Error.Code)
@ -945,7 +946,7 @@ func TestHandleLookupInvoiceEvent(t *testing.T) {
app, ss, err := createApp(svc)
assert.NoError(t, err)
request := &Nip47Request{}
request := &nip47.Request{}
err = json.Unmarshal([]byte(nip47LookupInvoiceJson), request)
assert.NoError(t, err)
@ -957,16 +958,16 @@ func TestHandleLookupInvoiceEvent(t *testing.T) {
PubKey: app.NostrPubkey,
Content: payload,
}
requestEvent := &RequestEvent{
requestEvent := &db.RequestEvent{
Content: reqEvent.Content,
}
reqEvent.ID = "test_lookup_invoice_without_permission"
requestEvent.NostrId = reqEvent.ID
responses := []*Nip47Response{}
responses := []*nip47.Response{}
publishResponse := func(response *Nip47Response, tags nostr.Tags) {
publishResponse := func(response *nip47.Response, tags nostr.Tags) {
responses = append(responses, response)
}
@ -976,7 +977,7 @@ func TestHandleLookupInvoiceEvent(t *testing.T) {
// with permission
expiresAt := time.Now().Add(24 * time.Hour)
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.LOOKUP_INVOICE_METHOD,
@ -987,10 +988,10 @@ func TestHandleLookupInvoiceEvent(t *testing.T) {
reqEvent.ID = "test_lookup_invoice_with_permission"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandleLookupInvoiceEvent(ctx, request, requestEvent, app, publishResponse)
transaction := responses[0].Result.(*Nip47LookupInvoiceResponse)
transaction := responses[0].Result.(*nip47.LookupInvoiceResponse)
assert.Equal(t, mockTransaction.Type, transaction.Type)
assert.Equal(t, mockTransaction.Invoice, transaction.Invoice)
assert.Equal(t, mockTransaction.Description, transaction.Description)
@ -1012,7 +1013,7 @@ func TestHandleMakeInvoiceEvent(t *testing.T) {
app, ss, err := createApp(svc)
assert.NoError(t, err)
request := &Nip47Request{}
request := &nip47.Request{}
err = json.Unmarshal([]byte(nip47MakeInvoiceJson), request)
assert.NoError(t, err)
@ -1024,16 +1025,16 @@ func TestHandleMakeInvoiceEvent(t *testing.T) {
PubKey: app.NostrPubkey,
Content: payload,
}
requestEvent := &RequestEvent{
requestEvent := &db.RequestEvent{
Content: reqEvent.Content,
}
reqEvent.ID = "test_make_invoice_without_permission"
requestEvent.NostrId = reqEvent.ID
responses := []*Nip47Response{}
responses := []*nip47.Response{}
publishResponse := func(response *Nip47Response, tags nostr.Tags) {
publishResponse := func(response *nip47.Response, tags nostr.Tags) {
responses = append(responses, response)
}
@ -1043,7 +1044,7 @@ func TestHandleMakeInvoiceEvent(t *testing.T) {
// with permission
expiresAt := time.Now().Add(24 * time.Hour)
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.MAKE_INVOICE_METHOD,
@ -1054,10 +1055,10 @@ func TestHandleMakeInvoiceEvent(t *testing.T) {
reqEvent.ID = "test_make_invoice_with_permission"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandleMakeInvoiceEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, mockTransaction.Preimage, responses[0].Result.(*Nip47MakeInvoiceResponse).Preimage)
assert.Equal(t, mockTransaction.Preimage, responses[0].Result.(*nip47.MakeInvoiceResponse).Preimage)
}
func TestHandleListTransactionsEvent(t *testing.T) {
@ -1070,7 +1071,7 @@ func TestHandleListTransactionsEvent(t *testing.T) {
app, ss, err := createApp(svc)
assert.NoError(t, err)
request := &Nip47Request{}
request := &nip47.Request{}
err = json.Unmarshal([]byte(nip47ListTransactionsJson), request)
assert.NoError(t, err)
@ -1082,16 +1083,16 @@ func TestHandleListTransactionsEvent(t *testing.T) {
PubKey: app.NostrPubkey,
Content: payload,
}
requestEvent := &RequestEvent{
requestEvent := &db.RequestEvent{
Content: reqEvent.Content,
}
reqEvent.ID = "test_list_transactions_without_permission"
requestEvent.NostrId = reqEvent.ID
responses := []*Nip47Response{}
responses := []*nip47.Response{}
publishResponse := func(response *Nip47Response, tags nostr.Tags) {
publishResponse := func(response *nip47.Response, tags nostr.Tags) {
responses = append(responses, response)
}
@ -1101,7 +1102,7 @@ func TestHandleListTransactionsEvent(t *testing.T) {
// with permission
expiresAt := time.Now().Add(24 * time.Hour)
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.LIST_TRANSACTIONS_METHOD,
@ -1112,11 +1113,11 @@ func TestHandleListTransactionsEvent(t *testing.T) {
reqEvent.ID = "test_list_transactions_with_permission"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandleListTransactionsEvent(ctx, request, requestEvent, app, publishResponse)
assert.Equal(t, 2, len(responses[0].Result.(*Nip47ListTransactionsResponse).Transactions))
transaction := responses[0].Result.(*Nip47ListTransactionsResponse).Transactions[0]
assert.Equal(t, 2, len(responses[0].Result.(*nip47.ListTransactionsResponse).Transactions))
transaction := responses[0].Result.(*nip47.ListTransactionsResponse).Transactions[0]
assert.Equal(t, mockTransactions[0].Type, transaction.Type)
assert.Equal(t, mockTransactions[0].Invoice, transaction.Invoice)
assert.Equal(t, mockTransactions[0].Description, transaction.Description)
@ -1138,7 +1139,7 @@ func TestHandleGetInfoEvent(t *testing.T) {
app, ss, err := createApp(svc)
assert.NoError(t, err)
request := &Nip47Request{}
request := &nip47.Request{}
err = json.Unmarshal([]byte(nip47GetInfoJson), request)
assert.NoError(t, err)
@ -1150,16 +1151,16 @@ func TestHandleGetInfoEvent(t *testing.T) {
PubKey: app.NostrPubkey,
Content: payload,
}
requestEvent := &RequestEvent{
requestEvent := &db.RequestEvent{
Content: reqEvent.Content,
}
reqEvent.ID = "test_get_info_without_permission"
requestEvent.NostrId = reqEvent.ID
responses := []*Nip47Response{}
responses := []*nip47.Response{}
publishResponse := func(response *Nip47Response, tags nostr.Tags) {
publishResponse := func(response *nip47.Response, tags nostr.Tags) {
responses = append(responses, response)
}
@ -1168,7 +1169,7 @@ func TestHandleGetInfoEvent(t *testing.T) {
assert.Equal(t, nip47.ERROR_RESTRICTED, responses[0].Error.Code)
expiresAt := time.Now().Add(24 * time.Hour)
appPermission := &AppPermission{
appPermission := &db.AppPermission{
AppId: app.ID,
App: *app,
RequestMethod: nip47.GET_INFO_METHOD,
@ -1179,10 +1180,10 @@ func TestHandleGetInfoEvent(t *testing.T) {
reqEvent.ID = "test_get_info_with_permission"
requestEvent.NostrId = reqEvent.ID
responses = []*Nip47Response{}
responses = []*nip47.Response{}
svc.HandleGetInfoEvent(ctx, request, requestEvent, app, publishResponse)
nodeInfo := responses[0].Result.(*Nip47GetInfoResponse)
nodeInfo := responses[0].Result.(*nip47.GetInfoResponse)
assert.Equal(t, mockNodeInfo.Alias, nodeInfo.Alias)
assert.Equal(t, mockNodeInfo.Color, nodeInfo.Color)
assert.Equal(t, mockNodeInfo.Pubkey, nodeInfo.Pubkey)
@ -1203,44 +1204,45 @@ func createTestService(ln *MockLn) (svc *Service, err error) {
logger.SetOutput(os.Stdout)
logger.SetLevel(logrus.InfoLevel)
err = migrations.Migrate(gormDb, &config.AppConfig{
appConfig := &config.AppConfig{
Workdir: ".test",
}, logger)
if err != nil {
return nil, err
}
sk := nostr.GeneratePrivateKey()
pk, err := nostr.GetPublicKey(sk)
err = migrations.Migrate(gormDb, appConfig, logger)
if err != nil {
return nil, err
}
cfg := config.NewConfig(
gormDb,
appConfig,
logger,
)
cfg.Start("")
return &Service{
cfg: &Config{
db: gormDb,
NostrSecretKey: sk,
NostrPublicKey: pk,
},
cfg: cfg,
db: gormDb,
lnClient: ln,
Logger: logger,
EventPublisher: events.NewEventPublisher(logger),
logger: logger,
eventPublisher: events.NewEventPublisher(logger),
}, nil
}
func createApp(svc *Service) (app *App, ss []byte, err error) {
func createApp(svc *Service) (app *db.App, ss []byte, err error) {
senderPrivkey := nostr.GeneratePrivateKey()
senderPubkey, err := nostr.GetPublicKey(senderPrivkey)
if err != nil {
return nil, nil, err
}
ss, err = nip04.ComputeSharedSecret(svc.cfg.NostrPublicKey, senderPrivkey)
ss, err = nip04.ComputeSharedSecret(svc.cfg.GetNostrPublicKey(), senderPrivkey)
if err != nil {
return nil, nil, err
}
app = &App{Name: "test", NostrPubkey: senderPubkey}
app = &db.App{Name: "test", NostrPubkey: senderPubkey}
err = svc.db.Create(app).Error
if err != nil {
return nil, nil, err
@ -1256,8 +1258,8 @@ func NewMockLn() (*MockLn, error) {
return &MockLn{}, nil
}
func (mln *MockLn) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.Nip47PayInvoiceResponse, error) {
return &lnclient.Nip47PayInvoiceResponse{
func (mln *MockLn) SendPaymentSync(ctx context.Context, payReq string) (*lnclient.PayInvoiceResponse, error) {
return &lnclient.PayInvoiceResponse{
Preimage: "123preimage",
}, nil
}
@ -1274,15 +1276,15 @@ func (mln *MockLn) GetInfo(ctx context.Context) (info *lnclient.NodeInfo, err er
return &mockNodeInfo, nil
}
func (mln *MockLn) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *Nip47Transaction, err error) {
func (mln *MockLn) MakeInvoice(ctx context.Context, amount int64, description string, descriptionHash string, expiry int64) (transaction *nip47.Transaction, err error) {
return mockTransaction, nil
}
func (mln *MockLn) LookupInvoice(ctx context.Context, paymentHash string) (transaction *Nip47Transaction, err error) {
func (mln *MockLn) LookupInvoice(ctx context.Context, paymentHash string) (transaction *nip47.Transaction, err error) {
return mockTransaction, nil
}
func (mln *MockLn) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (invoices []Nip47Transaction, err error) {
func (mln *MockLn) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaid bool, invoiceType string) (invoices []nip47.Transaction, err error) {
return mockTransactions, nil
}
func (mln *MockLn) Shutdown() error {
@ -1343,3 +1345,4 @@ func (mln *MockLn) GetNodeStatus(ctx context.Context) (nodeStatus *lnclient.Node
func (mln *MockLn) GetNetworkGraph(nodeIds []string) (lnclient.NetworkGraphResponse, error) {
return nil, nil
}
func (mln *MockLn) UpdateLastWalletSyncRequest() {}

View file

@ -7,31 +7,28 @@ import (
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
"github.com/sirupsen/logrus"
"github.com/getAlby/nostr-wallet-connect/events"
)
func (svc *Service) StartNostr(ctx context.Context, encryptionKey string) error {
relayUrl, _ := svc.cfg.Get("Relay", encryptionKey)
nostrSecretKey, _ := svc.cfg.Get("NostrSecretKey", encryptionKey)
if nostrSecretKey == "" {
nostrSecretKey = nostr.GeneratePrivateKey()
svc.cfg.SetUpdate("NostrSecretKey", nostrSecretKey, encryptionKey)
}
nostrPublicKey, err := nostr.GetPublicKey(nostrSecretKey)
if err != nil {
svc.Logger.Errorf("Error converting nostr privkey to pubkey: %v", err)
return err
}
svc.cfg.NostrSecretKey = nostrSecretKey
svc.cfg.NostrPublicKey = nostrPublicKey
relayUrl := svc.cfg.GetRelayUrl()
npub, err := nip19.EncodePublicKey(svc.cfg.NostrPublicKey)
err := svc.cfg.Start(encryptionKey)
if err != nil {
svc.Logger.Fatalf("Error converting nostr privkey to pubkey: %v", err)
svc.logger.WithError(err).Fatal("Failed to start config")
}
svc.Logger.Infof("Starting nostr-wallet-connect. npub: %s hex: %s", npub, svc.cfg.NostrPublicKey)
npub, err := nip19.EncodePublicKey(svc.cfg.GetNostrPublicKey())
if err != nil {
svc.logger.WithError(err).Fatal("Error converting nostr privkey to pubkey")
}
svc.logger.WithFields(logrus.Fields{
"npub": npub,
"hex": svc.cfg.GetNostrPublicKey(),
}).Info("Starting nostr-wallet-connect")
svc.wg.Add(1)
go func() {
//Start infinite loop which will be only broken by canceling ctx (SIGINT)
@ -43,11 +40,11 @@ func (svc *Service) StartNostr(ctx context.Context, encryptionKey string) error
if i > 0 {
sleepDuration := 10
contextCancelled := false
svc.Logger.Infof("[Iteration %d] Retrying in %d seconds...", i, sleepDuration)
svc.logger.Infof("[Iteration %d] Retrying in %d seconds...", i, sleepDuration)
select {
case <-ctx.Done(): //context cancelled
svc.Logger.Info("service context cancelled while waiting for retry")
svc.logger.Info("service context cancelled while waiting for retry")
contextCancelled = true
case <-time.After(time.Duration(sleepDuration) * time.Second): //timeout
}
@ -58,49 +55,49 @@ func (svc *Service) StartNostr(ctx context.Context, encryptionKey string) error
if relay != nil && relay.IsConnected() {
err := relay.Close()
if err != nil {
svc.Logger.WithError(err).Error("Could not close relay connection")
svc.logger.WithError(err).Error("Could not close relay connection")
}
}
//connect to the relay
svc.Logger.Infof("Connecting to the relay: %s", relayUrl)
svc.logger.Infof("Connecting to the relay: %s", relayUrl)
relay, err := nostr.RelayConnect(ctx, relayUrl, nostr.WithNoticeHandler(svc.noticeHandler))
if err != nil {
svc.Logger.WithError(err).Error("Failed to connect to relay")
svc.logger.WithError(err).Error("Failed to connect to relay")
continue
}
//publish event with NIP-47 info
err = svc.PublishNip47Info(ctx, relay)
if err != nil {
svc.Logger.WithError(err).Error("Could not publish NIP47 info")
svc.logger.WithError(err).Error("Could not publish NIP47 info")
}
svc.Logger.Info("Subscribing to events")
sub, err := relay.Subscribe(ctx, svc.createFilters(svc.cfg.NostrPublicKey))
svc.logger.Info("Subscribing to events")
sub, err := relay.Subscribe(ctx, svc.createFilters(svc.cfg.GetNostrPublicKey()))
if err != nil {
svc.Logger.WithError(err).Error("Failed to subscribe to events")
svc.logger.WithError(err).Error("Failed to subscribe to events")
continue
}
err = svc.StartSubscription(sub.Context, sub)
if err != nil {
//err being non-nil means that we have an error on the websocket error channel. In this case we just try to reconnect.
svc.Logger.WithError(err).Error("Got an error from the relay while listening to subscription.")
svc.logger.WithError(err).Error("Got an error from the relay while listening to subscription.")
continue
}
//err being nil means that the context was canceled and we should exit the program.
break
}
svc.Logger.Info("Disconnecting from relay...")
svc.logger.Info("Disconnecting from relay...")
if relay != nil && relay.IsConnected() {
err := relay.Close()
if err != nil {
svc.Logger.WithError(err).Error("Could not close relay connection")
svc.logger.WithError(err).Error("Could not close relay connection")
}
}
svc.Shutdown()
svc.Logger.Info("Relay subroutine ended")
svc.logger.Info("Relay subroutine ended")
svc.wg.Done()
}()
return nil
@ -108,7 +105,7 @@ func (svc *Service) StartNostr(ctx context.Context, encryptionKey string) error
func (svc *Service) StartApp(encryptionKey string) error {
if !svc.cfg.CheckUnlockPassword(encryptionKey) {
svc.Logger.Errorf("Invalid password")
svc.logger.Errorf("Invalid password")
return errors.New("invalid password")
}
@ -116,8 +113,8 @@ func (svc *Service) StartApp(encryptionKey string) error {
err := svc.launchLNBackend(ctx, encryptionKey)
if err != nil {
svc.Logger.Errorf("Failed to launch LN backend: %v", err)
svc.EventPublisher.Publish(&events.Event{
svc.logger.Errorf("Failed to launch LN backend: %v", err)
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_node_start_failed",
})
cancelFn()
@ -129,6 +126,7 @@ func (svc *Service) StartApp(encryptionKey string) error {
return nil
}
// TODO: remove and call StopLNClient() instead
func (svc *Service) StopApp() {
if svc.appCancelFn != nil {
svc.appCancelFn()
@ -138,7 +136,7 @@ func (svc *Service) StopApp() {
func (svc *Service) Shutdown() {
svc.StopLNClient()
svc.EventPublisher.Publish(&events.Event{
svc.eventPublisher.Publish(&events.Event{
Event: "nwc_stopped",
})
// wait for any remaining events

View file

@ -1,4 +1,4 @@
package main
package utils
import (
"fmt"

View file

@ -5,6 +5,7 @@ import (
"embed"
"log"
"github.com/getAlby/nostr-wallet-connect/api"
"github.com/sirupsen/logrus"
"github.com/wailsapp/wails/v2"
"github.com/wailsapp/wails/v2/pkg/options"
@ -17,13 +18,13 @@ var assets embed.FS
type WailsApp struct {
ctx context.Context
svc *Service
api *API
api api.API
}
func NewApp(svc *Service) *WailsApp {
return &WailsApp{
svc: svc,
api: NewAPI(svc),
api: api.NewAPI(svc, svc.logger, svc.db),
}
}
@ -34,7 +35,7 @@ func (app *WailsApp) startup(ctx context.Context) {
}
func LaunchWailsApp(app *WailsApp) {
logger := NewWailsLogger(app.svc.Logger)
logger := NewWailsLogger(app.svc.logger)
err := wails.Run(&options.App{
Title: "Nostr Wallet Connect",

View file

@ -9,7 +9,10 @@ import (
"github.com/sirupsen/logrus"
"github.com/getAlby/nostr-wallet-connect/models/api"
"github.com/getAlby/nostr-wallet-connect/alby"
"github.com/getAlby/nostr-wallet-connect/api"
"github.com/getAlby/nostr-wallet-connect/db"
"github.com/getAlby/nostr-wallet-connect/lsp"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
@ -33,9 +36,9 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
case len(authCodeMatch) > 1:
code := authCodeMatch[1]
err := app.svc.AlbyOAuthSvc.CallbackHandler(ctx, code)
err := app.svc.albyOAuthSvc.CallbackHandler(ctx, code)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -55,7 +58,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
case len(appMatch) > 1:
pubkey := appMatch[1]
userApp := App{}
userApp := db.App{}
findResult := app.svc.db.Where("nostr_pubkey = ?", pubkey).First(&userApp)
if findResult.RowsAffected == 0 {
@ -70,7 +73,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
updateAppRequest := &api.UpdateAppRequest{}
err := json.Unmarshal([]byte(body), updateAppRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -146,9 +149,9 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
switch route {
case "/api/alby/me":
me, err := app.svc.AlbyOAuthSvc.GetMe(ctx)
me, err := app.svc.albyOAuthSvc.GetMe(ctx)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -157,30 +160,30 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
}
return WailsRequestRouterResponse{Body: me, Error: ""}
case "/api/alby/balance":
balance, err := app.svc.AlbyOAuthSvc.GetBalance(ctx)
balance, err := app.svc.albyOAuthSvc.GetBalance(ctx)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
}).WithError(err).Error("Failed to decode request to wails router")
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
return WailsRequestRouterResponse{Body: &api.AlbyBalanceResponse{
return WailsRequestRouterResponse{Body: &alby.AlbyBalanceResponse{
Sats: balance.Balance,
}, Error: ""}
case "/api/alby/pay":
payRequest := &api.AlbyPayRequest{}
payRequest := &alby.AlbyPayRequest{}
err := json.Unmarshal([]byte(body), payRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
}).WithError(err).Error("Failed to decode request to wails router")
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
err = app.svc.AlbyOAuthSvc.SendPayment(ctx, payRequest.Invoice)
err = app.svc.albyOAuthSvc.SendPayment(ctx, payRequest.Invoice)
if err != nil {
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
@ -197,7 +200,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
createAppRequest := &api.CreateAppRequest{}
err := json.Unmarshal([]byte(body), createAppRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -214,7 +217,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
resetRouterRequest := &api.ResetRouterRequest{}
err := json.Unmarshal([]byte(body), resetRouterRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -222,7 +225,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
err = app.api.ResetRouter(resetRouterRequest.Key, true)
err = app.api.ResetRouter(resetRouterRequest.Key)
if err != nil {
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
@ -248,7 +251,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
openChannelRequest := &api.OpenChannelRequest{}
err := json.Unmarshal([]byte(body), openChannelRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -289,7 +292,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
redeemOnchainFundsRequest := &api.RedeemOnchainFundsRequest{}
err := json.Unmarshal([]byte(body), redeemOnchainFundsRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -310,6 +313,15 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
return WailsRequestRouterResponse{Body: *signMessageResponse, Error: ""}
// TODO: review naming
case "/api/instant-channel-invoices":
newInstantChannelRequest := &lsp.NewInstantChannelInvoiceRequest{}
err := json.Unmarshal([]byte(body), newInstantChannelRequest)
newInstantChannelResponseResponse, err := app.api.GetLSPService().NewInstantChannelInvoice(ctx, newInstantChannelRequest)
if err != nil {
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
return WailsRequestRouterResponse{Body: *newInstantChannelResponseResponse, Error: ""}
case "/api/peers":
switch method {
case "GET":
@ -322,7 +334,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
connectPeerRequest := &api.ConnectPeerRequest{}
err := json.Unmarshal([]byte(body), connectPeerRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -356,7 +368,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
res := WailsRequestRouterResponse{Body: *infoResponse, Error: ""}
return res
case "/api/alby/link-account":
err := app.svc.AlbyOAuthSvc.LinkAccount(ctx)
err := app.svc.albyOAuthSvc.LinkAccount(ctx)
if err != nil {
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
@ -370,7 +382,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
backupReminderRequest := &api.BackupReminderRequest{}
err := json.Unmarshal([]byte(body), backupReminderRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -380,7 +392,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
err = app.api.SetNextBackupReminder(backupReminderRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -392,7 +404,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
changeUnlockPasswordRequest := &api.ChangeUnlockPasswordRequest{}
err := json.Unmarshal([]byte(body), changeUnlockPasswordRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -402,7 +414,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
err = app.api.ChangeUnlockPassword(changeUnlockPasswordRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -414,7 +426,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
startRequest := &api.StartRequest{}
err := json.Unmarshal([]byte(body), startRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -423,7 +435,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
}
err = app.api.Start(startRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -437,7 +449,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
setupRequest := &api.SetupRequest{}
err := json.Unmarshal([]byte(body), setupRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -446,7 +458,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
}
err = app.api.Setup(ctx, setupRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -458,7 +470,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
sendPaymentProbesRequest := &api.SendPaymentProbesRequest{}
err := json.Unmarshal([]byte(body), sendPaymentProbesRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -467,7 +479,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
}
sendPaymentProbesResponse, err := app.api.SendPaymentProbes(ctx, sendPaymentProbesRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -479,7 +491,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
sendSpontaneousPaymentProbesRequest := &api.SendSpontaneousPaymentProbesRequest{}
err := json.Unmarshal([]byte(body), sendSpontaneousPaymentProbesRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -488,7 +500,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
}
sendSpontaneousPaymentProbesResponse, err := app.api.SendSpontaneousPaymentProbes(ctx, sendSpontaneousPaymentProbesRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -500,7 +512,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
backupRequest := &api.BasicBackupRequest{}
err := json.Unmarshal([]byte(body), backupRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -513,7 +525,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
DefaultFilename: "nwc.bkp",
})
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -523,7 +535,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
backupFile, err := os.Create(saveFilePath)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -533,14 +545,10 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
defer backupFile.Close()
backupReq := api.BasicBackupRequest{
UnlockPassword: backupRequest.UnlockPassword,
}
err = app.api.CreateBackup(&backupReq, backupFile)
err = app.api.GetBackupService().CreateBackup(backupRequest.UnlockPassword, backupFile)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -552,7 +560,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
restoreRequest := &api.BasicRestoreWailsRequest{}
err := json.Unmarshal([]byte(body), restoreRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -565,7 +573,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
DefaultFilename: "nwc.bkp",
})
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -575,7 +583,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
backupFile, err := os.Open(backupFilePath)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -585,9 +593,9 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
defer backupFile.Close()
err = app.api.RestoreBackup(restoreRequest.UnlockPassword, backupFile)
err = app.api.GetBackupService().RestoreBackup(restoreRequest.UnlockPassword, backupFile)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -605,7 +613,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
getLogOutputRequest := &api.GetLogOutputRequest{}
err := json.Unmarshal([]byte(body), getLogOutputRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -614,7 +622,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
}
logOutputResponse, err := app.api.GetLogOutput(ctx, logType, getLogOutputRequest)
if err != nil {
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
"body": body,
@ -624,7 +632,7 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
return WailsRequestRouterResponse{Body: logOutputResponse, Error: ""}
}
app.svc.Logger.WithFields(logrus.Fields{
app.svc.logger.WithFields(logrus.Fields{
"route": route,
"method": method,
}).Error("Unhandled route")