2024-05-30 00:06:06 +07:00
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
2025-01-30 14:20:17 +03:00
|
|
|
"flag"
|
2024-05-30 00:06:06 +07:00
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
2024-07-01 20:30:40 +07:00
|
|
|
"slices"
|
2024-06-19 23:25:39 +07:00
|
|
|
"sync"
|
2024-05-30 00:06:06 +07:00
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/sirupsen/logrus"
|
2024-09-03 21:31:28 +07:00
|
|
|
"gorm.io/datatypes"
|
2024-05-30 00:06:06 +07:00
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
2024-07-05 20:32:40 +07:00
|
|
|
"github.com/getAlby/hub/alby"
|
2024-10-28 13:29:34 +07:00
|
|
|
"github.com/getAlby/hub/apps"
|
2024-07-05 20:32:40 +07:00
|
|
|
"github.com/getAlby/hub/config"
|
2024-07-19 23:30:22 +07:00
|
|
|
"github.com/getAlby/hub/constants"
|
2024-07-05 20:32:40 +07:00
|
|
|
"github.com/getAlby/hub/db"
|
2024-07-19 23:30:22 +07:00
|
|
|
"github.com/getAlby/hub/db/queries"
|
2024-07-05 20:32:40 +07:00
|
|
|
"github.com/getAlby/hub/events"
|
|
|
|
|
"github.com/getAlby/hub/lnclient"
|
|
|
|
|
"github.com/getAlby/hub/logger"
|
|
|
|
|
permissions "github.com/getAlby/hub/nip47/permissions"
|
|
|
|
|
"github.com/getAlby/hub/service"
|
|
|
|
|
"github.com/getAlby/hub/service/keys"
|
|
|
|
|
"github.com/getAlby/hub/utils"
|
|
|
|
|
"github.com/getAlby/hub/version"
|
2024-05-30 00:06:06 +07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type api struct {
|
2024-09-04 10:46:12 +05:30
|
|
|
db *gorm.DB
|
2024-10-28 13:29:34 +07:00
|
|
|
appsSvc apps.AppsService
|
2024-09-04 10:46:12 +05:30
|
|
|
cfg config.Config
|
|
|
|
|
svc service.Service
|
|
|
|
|
permissionsSvc permissions.PermissionsService
|
|
|
|
|
keys keys.Keys
|
|
|
|
|
albyOAuthSvc alby.AlbyOAuthService
|
|
|
|
|
startupError error
|
|
|
|
|
startupErrorTime time.Time
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
2024-06-29 22:29:55 +07:00
|
|
|
func NewAPI(svc service.Service, gormDB *gorm.DB, config config.Config, keys keys.Keys, albyOAuthSvc alby.AlbyOAuthService, eventPublisher events.EventPublisher) *api {
|
2024-05-30 00:06:06 +07:00
|
|
|
return &api{
|
2024-06-17 19:42:09 +07:00
|
|
|
db: gormDB,
|
2024-11-07 13:06:01 +01:00
|
|
|
appsSvc: apps.NewAppsService(gormDB, eventPublisher, keys),
|
2024-06-17 19:42:09 +07:00
|
|
|
cfg: config,
|
|
|
|
|
svc: svc,
|
2024-06-29 22:29:55 +07:00
|
|
|
permissionsSvc: permissions.NewPermissionsService(gormDB, eventPublisher),
|
2024-06-17 19:42:09 +07:00
|
|
|
keys: keys,
|
|
|
|
|
albyOAuthSvc: albyOAuthSvc,
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *api) CreateApp(createAppRequest *CreateAppRequest) (*CreateAppResponse, error) {
|
2024-11-26 20:30:53 -06:00
|
|
|
backendType, _ := api.cfg.Get("LNBackendType", "")
|
|
|
|
|
if createAppRequest.Isolated &&
|
|
|
|
|
backendType != "LDK" &&
|
|
|
|
|
backendType != "LND" {
|
|
|
|
|
return nil, fmt.Errorf(
|
2025-01-17 10:17:21 +02:00
|
|
|
"sub-wallets are currently not supported on your node backend. Try LDK or LND")
|
2024-11-26 20:30:53 -06:00
|
|
|
}
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
expiresAt, err := api.parseExpiresAt(createAppRequest.ExpiresAt)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("invalid expiresAt: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-01 20:30:40 +07:00
|
|
|
if len(createAppRequest.Scopes) == 0 {
|
|
|
|
|
return nil, fmt.Errorf("won't create an app without scopes")
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
2024-07-01 20:30:40 +07:00
|
|
|
for _, scope := range createAppRequest.Scopes {
|
|
|
|
|
if !slices.Contains(permissions.AllScopes(), scope) {
|
|
|
|
|
return nil, fmt.Errorf("did not recognize requested scope: %s", scope)
|
2024-06-17 19:42:09 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-28 13:29:34 +07:00
|
|
|
app, pairingSecretKey, err := api.appsSvc.CreateApp(
|
2024-07-19 23:30:22 +07:00
|
|
|
createAppRequest.Name,
|
|
|
|
|
createAppRequest.Pubkey,
|
|
|
|
|
createAppRequest.MaxAmountSat,
|
|
|
|
|
createAppRequest.BudgetRenewal,
|
|
|
|
|
expiresAt,
|
|
|
|
|
createAppRequest.Scopes,
|
2024-09-02 17:40:34 +07:00
|
|
|
createAppRequest.Isolated,
|
2024-11-07 13:06:01 +01:00
|
|
|
createAppRequest.Metadata,
|
|
|
|
|
)
|
2024-05-30 00:06:06 +07:00
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-17 19:42:09 +07:00
|
|
|
relayUrl := api.cfg.GetRelayUrl()
|
2024-05-30 00:06:06 +07:00
|
|
|
|
|
|
|
|
responseBody := &CreateAppResponse{}
|
2024-08-23 14:15:15 +05:30
|
|
|
responseBody.Id = app.ID
|
2024-05-30 00:06:06 +07:00
|
|
|
responseBody.Name = createAppRequest.Name
|
2024-11-07 13:06:01 +01:00
|
|
|
responseBody.Pubkey = app.AppPubkey
|
2024-05-30 00:06:06 +07:00
|
|
|
responseBody.PairingSecret = pairingSecretKey
|
|
|
|
|
|
2024-09-03 07:52:54 +02:00
|
|
|
lightningAddress, err := api.albyOAuthSvc.GetLightningAddress()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
if createAppRequest.ReturnTo != "" {
|
|
|
|
|
returnToUrl, err := url.Parse(createAppRequest.ReturnTo)
|
|
|
|
|
if err == nil {
|
|
|
|
|
query := returnToUrl.Query()
|
|
|
|
|
query.Add("relay", relayUrl)
|
2024-11-07 13:06:01 +01:00
|
|
|
query.Add("pubkey", *app.WalletPubkey)
|
2024-09-04 20:57:27 +02:00
|
|
|
if lightningAddress != "" && !app.Isolated {
|
2024-09-03 07:52:54 +02:00
|
|
|
query.Add("lud16", lightningAddress)
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
returnToUrl.RawQuery = query.Encode()
|
|
|
|
|
responseBody.ReturnTo = returnToUrl.String()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var lud16 string
|
2024-09-04 20:57:27 +02:00
|
|
|
if lightningAddress != "" && !app.Isolated {
|
2024-09-03 07:52:54 +02:00
|
|
|
lud16 = fmt.Sprintf("&lud16=%s", lightningAddress)
|
|
|
|
|
}
|
2024-11-07 13:06:01 +01:00
|
|
|
responseBody.PairingUri = fmt.Sprintf("nostr+walletconnect://%s?relay=%s&secret=%s%s", *app.WalletPubkey, relayUrl, pairingSecretKey, lud16)
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
return responseBody, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *api) UpdateApp(userApp *db.App, updateAppRequest *UpdateAppRequest) error {
|
2024-08-22 23:45:48 +05:30
|
|
|
name := updateAppRequest.Name
|
|
|
|
|
|
|
|
|
|
if name == "" {
|
|
|
|
|
return fmt.Errorf("won't update an app to have no name")
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-19 23:30:22 +07:00
|
|
|
maxAmount := updateAppRequest.MaxAmountSat
|
2024-05-30 00:06:06 +07:00
|
|
|
budgetRenewal := updateAppRequest.BudgetRenewal
|
|
|
|
|
|
2024-07-01 20:30:40 +07:00
|
|
|
if len(updateAppRequest.Scopes) == 0 {
|
2024-05-30 00:06:06 +07:00
|
|
|
return fmt.Errorf("won't update an app to have no request methods")
|
|
|
|
|
}
|
2024-07-01 20:30:40 +07:00
|
|
|
newScopes := updateAppRequest.Scopes
|
2024-05-30 00:06:06 +07:00
|
|
|
|
|
|
|
|
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 {
|
2024-08-22 23:45:48 +05:30
|
|
|
// Update app name if it is not the same
|
|
|
|
|
if name != userApp.Name {
|
|
|
|
|
err := tx.Model(&db.App{}).Where("id", userApp.ID).Update("name", name).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-26 20:30:53 -06:00
|
|
|
// Update app isolation if it is not the same
|
|
|
|
|
if updateAppRequest.Isolated != userApp.Isolated {
|
|
|
|
|
err := tx.Model(&db.App{}).Where("id", userApp.ID).Update("isolated", updateAppRequest.Isolated).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the app metadata
|
2024-09-03 21:31:28 +07:00
|
|
|
if updateAppRequest.Metadata != nil {
|
|
|
|
|
var metadataBytes []byte
|
|
|
|
|
var err error
|
|
|
|
|
metadataBytes, err = json.Marshal(updateAppRequest.Metadata)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to serialize metadata")
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
err = tx.Model(&db.App{}).Where("id", userApp.ID).Update("metadata", datatypes.JSON(metadataBytes)).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
// Update existing permissions with new budget and expiry
|
2024-11-26 20:30:53 -06:00
|
|
|
err = tx.Model(&db.AppPermission{}).Where("app_id", userApp.ID).Updates(map[string]interface{}{
|
2024-05-30 00:06:06 +07:00
|
|
|
"ExpiresAt": expiresAt,
|
2024-07-19 23:30:22 +07:00
|
|
|
"MaxAmountSat": maxAmount,
|
2024-05-30 00:06:06 +07:00
|
|
|
"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
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-01 20:30:40 +07:00
|
|
|
existingScopeMap := make(map[string]bool)
|
2024-05-30 00:06:06 +07:00
|
|
|
for _, perm := range existingPermissions {
|
2024-07-01 20:30:40 +07:00
|
|
|
existingScopeMap[perm.Scope] = true
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add new permissions
|
2024-07-01 20:30:40 +07:00
|
|
|
for _, method := range newScopes {
|
|
|
|
|
if !existingScopeMap[method] {
|
2024-05-30 00:06:06 +07:00
|
|
|
perm := db.AppPermission{
|
|
|
|
|
App: *userApp,
|
2024-07-01 20:30:40 +07:00
|
|
|
Scope: method,
|
2024-05-30 00:06:06 +07:00
|
|
|
ExpiresAt: expiresAt,
|
2024-07-19 23:30:22 +07:00
|
|
|
MaxAmountSat: int(maxAmount),
|
2024-05-30 00:06:06 +07:00
|
|
|
BudgetRenewal: budgetRenewal,
|
|
|
|
|
}
|
|
|
|
|
if err := tx.Create(&perm).Error; err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-01 20:30:40 +07:00
|
|
|
delete(existingScopeMap, method)
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove old permissions
|
2024-07-01 20:30:40 +07:00
|
|
|
for method := range existingScopeMap {
|
|
|
|
|
if err := tx.Where("app_id = ? AND scope = ?", userApp.ID, method).Delete(&db.AppPermission{}).Error; err != nil {
|
2024-05-30 00:06:06 +07:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-12-20 12:54:45 +01:00
|
|
|
api.svc.GetEventPublisher().Publish(&events.Event{
|
|
|
|
|
Event: "nwc_app_updated",
|
|
|
|
|
Properties: map[string]interface{}{
|
|
|
|
|
"name": name,
|
|
|
|
|
"id": userApp.ID,
|
|
|
|
|
},
|
|
|
|
|
})
|
2024-05-30 00:06:06 +07:00
|
|
|
|
|
|
|
|
// commit transaction
|
|
|
|
|
return nil
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *api) DeleteApp(userApp *db.App) error {
|
2024-11-07 13:06:01 +01:00
|
|
|
return api.appsSvc.DeleteApp(userApp)
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
2024-07-19 23:30:22 +07:00
|
|
|
func (api *api) GetApp(dbApp *db.App) *App {
|
2024-05-30 00:06:06 +07:00
|
|
|
|
|
|
|
|
var lastEvent db.RequestEvent
|
2024-07-19 23:30:22 +07:00
|
|
|
lastEventResult := api.db.Where("app_id = ?", dbApp.ID).Order("id desc").Limit(1).Find(&lastEvent)
|
2024-05-30 00:06:06 +07:00
|
|
|
|
|
|
|
|
paySpecificPermission := db.AppPermission{}
|
|
|
|
|
appPermissions := []db.AppPermission{}
|
|
|
|
|
var expiresAt *time.Time
|
2024-07-19 23:30:22 +07:00
|
|
|
api.db.Where("app_id = ?", dbApp.ID).Find(&appPermissions)
|
2024-05-30 00:06:06 +07:00
|
|
|
|
|
|
|
|
requestMethods := []string{}
|
|
|
|
|
for _, appPerm := range appPermissions {
|
|
|
|
|
expiresAt = appPerm.ExpiresAt
|
2024-07-19 23:30:22 +07:00
|
|
|
if appPerm.Scope == constants.PAY_INVOICE_SCOPE {
|
2024-12-23 12:25:59 +03:00
|
|
|
// find the pay_invoice-specific permissions
|
2024-05-30 00:06:06 +07:00
|
|
|
paySpecificPermission = appPerm
|
|
|
|
|
}
|
2024-07-01 20:30:40 +07:00
|
|
|
requestMethods = append(requestMethods, appPerm.Scope)
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
2024-12-23 12:25:59 +03:00
|
|
|
// renewsIn := ""
|
2024-06-17 19:42:09 +07:00
|
|
|
budgetUsage := uint64(0)
|
2024-07-19 23:30:22 +07:00
|
|
|
maxAmount := uint64(paySpecificPermission.MaxAmountSat)
|
2024-07-22 11:45:51 +07:00
|
|
|
budgetUsage = queries.GetBudgetUsageSat(api.db, &paySpecificPermission)
|
2024-05-30 00:06:06 +07:00
|
|
|
|
2024-09-02 17:40:34 +07:00
|
|
|
var metadata Metadata
|
|
|
|
|
if dbApp.Metadata != nil {
|
|
|
|
|
jsonErr := json.Unmarshal(dbApp.Metadata, &metadata)
|
|
|
|
|
if jsonErr != nil {
|
|
|
|
|
logger.Logger.WithError(jsonErr).WithFields(logrus.Fields{
|
|
|
|
|
"app_id": dbApp.ID,
|
|
|
|
|
}).Error("Failed to deserialize app metadata")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
response := App{
|
2024-07-19 23:30:22 +07:00
|
|
|
ID: dbApp.ID,
|
|
|
|
|
Name: dbApp.Name,
|
|
|
|
|
Description: dbApp.Description,
|
|
|
|
|
CreatedAt: dbApp.CreatedAt,
|
|
|
|
|
UpdatedAt: dbApp.UpdatedAt,
|
2024-11-07 13:06:01 +01:00
|
|
|
AppPubkey: dbApp.AppPubkey,
|
2024-07-01 20:30:40 +07:00
|
|
|
ExpiresAt: expiresAt,
|
2024-07-19 23:30:22 +07:00
|
|
|
MaxAmountSat: maxAmount,
|
2024-07-01 20:30:40 +07:00
|
|
|
Scopes: requestMethods,
|
|
|
|
|
BudgetUsage: budgetUsage,
|
|
|
|
|
BudgetRenewal: paySpecificPermission.BudgetRenewal,
|
2024-07-19 23:30:22 +07:00
|
|
|
Isolated: dbApp.Isolated,
|
2024-09-02 17:40:34 +07:00
|
|
|
Metadata: metadata,
|
2024-07-19 23:30:22 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dbApp.Isolated {
|
|
|
|
|
response.Balance = queries.GetIsolatedBalance(api.db, dbApp.ID)
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if lastEventResult.RowsAffected > 0 {
|
|
|
|
|
response.LastEventAt = &lastEvent.CreatedAt
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &response
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *api) ListApps() ([]App, error) {
|
|
|
|
|
// TODO: join dbApps and permissions
|
|
|
|
|
dbApps := []db.App{}
|
2024-08-10 17:08:08 +07:00
|
|
|
err := api.db.Find(&dbApps).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to list apps")
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
|
2024-07-01 20:30:40 +07:00
|
|
|
appPermissions := []db.AppPermission{}
|
2024-08-10 17:08:08 +07:00
|
|
|
err = api.db.Find(&appPermissions).Error
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to list app permissions")
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
|
|
|
|
|
permissionsMap := make(map[uint][]db.AppPermission)
|
2024-07-01 20:30:40 +07:00
|
|
|
for _, perm := range appPermissions {
|
2024-05-30 00:06:06 +07:00
|
|
|
permissionsMap[perm.AppId] = append(permissionsMap[perm.AppId], perm)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apiApps := []App{}
|
2024-07-19 23:30:22 +07:00
|
|
|
for _, dbApp := range dbApps {
|
2024-05-30 00:06:06 +07:00
|
|
|
apiApp := App{
|
2024-07-19 23:30:22 +07:00
|
|
|
ID: dbApp.ID,
|
|
|
|
|
Name: dbApp.Name,
|
|
|
|
|
Description: dbApp.Description,
|
|
|
|
|
CreatedAt: dbApp.CreatedAt,
|
|
|
|
|
UpdatedAt: dbApp.UpdatedAt,
|
2024-11-07 13:06:01 +01:00
|
|
|
AppPubkey: dbApp.AppPubkey,
|
2024-07-19 23:30:22 +07:00
|
|
|
Isolated: dbApp.Isolated,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dbApp.Isolated {
|
|
|
|
|
apiApp.Balance = queries.GetIsolatedBalance(api.db, dbApp.ID)
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
2024-07-19 23:30:22 +07:00
|
|
|
for _, appPermission := range permissionsMap[dbApp.ID] {
|
2024-07-01 20:30:40 +07:00
|
|
|
apiApp.Scopes = append(apiApp.Scopes, appPermission.Scope)
|
|
|
|
|
apiApp.ExpiresAt = appPermission.ExpiresAt
|
2024-07-19 23:30:22 +07:00
|
|
|
if appPermission.Scope == constants.PAY_INVOICE_SCOPE {
|
2024-07-01 20:30:40 +07:00
|
|
|
apiApp.BudgetRenewal = appPermission.BudgetRenewal
|
2024-07-19 23:30:22 +07:00
|
|
|
apiApp.MaxAmountSat = uint64(appPermission.MaxAmountSat)
|
2024-07-22 11:45:51 +07:00
|
|
|
apiApp.BudgetUsage = queries.GetBudgetUsageSat(api.db, &appPermission)
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var lastEvent db.RequestEvent
|
2024-08-10 19:15:40 +07:00
|
|
|
lastEventResult := api.db.Where("app_id = ?", dbApp.ID).Order("id desc").Limit(1).Find(&lastEvent)
|
2024-05-30 00:06:06 +07:00
|
|
|
if lastEventResult.RowsAffected > 0 {
|
|
|
|
|
apiApp.LastEventAt = &lastEvent.CreatedAt
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-02 17:40:34 +07:00
|
|
|
var metadata Metadata
|
|
|
|
|
if dbApp.Metadata != nil {
|
|
|
|
|
jsonErr := json.Unmarshal(dbApp.Metadata, &metadata)
|
|
|
|
|
if jsonErr != nil {
|
|
|
|
|
logger.Logger.WithError(jsonErr).WithFields(logrus.Fields{
|
|
|
|
|
"app_id": dbApp.ID,
|
|
|
|
|
}).Error("Failed to deserialize app metadata")
|
|
|
|
|
}
|
|
|
|
|
apiApp.Metadata = metadata
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
apiApps = append(apiApps, apiApp)
|
|
|
|
|
}
|
|
|
|
|
return apiApps, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-31 09:10:26 +02:00
|
|
|
func (api *api) ListChannels(ctx context.Context) ([]Channel, error) {
|
2024-05-30 00:06:06 +07:00
|
|
|
if api.svc.GetLNClient() == nil {
|
|
|
|
|
return nil, errors.New("LNClient not started")
|
|
|
|
|
}
|
2024-07-31 09:10:26 +02:00
|
|
|
channels, err := api.svc.GetLNClient().ListChannels(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apiChannels := []Channel{}
|
|
|
|
|
for _, channel := range channels {
|
|
|
|
|
status := "offline"
|
|
|
|
|
if channel.Active {
|
|
|
|
|
status = "online"
|
|
|
|
|
} else if channel.Confirmations != nil && channel.ConfirmationsRequired != nil && *channel.ConfirmationsRequired > *channel.Confirmations {
|
|
|
|
|
status = "opening"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apiChannels = append(apiChannels, Channel{
|
|
|
|
|
LocalBalance: channel.LocalBalance,
|
|
|
|
|
LocalSpendableBalance: channel.LocalSpendableBalance,
|
|
|
|
|
RemoteBalance: channel.RemoteBalance,
|
|
|
|
|
Id: channel.Id,
|
|
|
|
|
RemotePubkey: channel.RemotePubkey,
|
|
|
|
|
FundingTxId: channel.FundingTxId,
|
2025-01-06 23:33:13 +07:00
|
|
|
FundingTxVout: channel.FundingTxVout,
|
2024-07-31 09:10:26 +02:00
|
|
|
Active: channel.Active,
|
|
|
|
|
Public: channel.Public,
|
|
|
|
|
InternalChannel: channel.InternalChannel,
|
|
|
|
|
Confirmations: channel.Confirmations,
|
|
|
|
|
ConfirmationsRequired: channel.ConfirmationsRequired,
|
|
|
|
|
ForwardingFeeBaseMsat: channel.ForwardingFeeBaseMsat,
|
|
|
|
|
UnspendablePunishmentReserve: channel.UnspendablePunishmentReserve,
|
|
|
|
|
CounterpartyUnspendablePunishmentReserve: channel.CounterpartyUnspendablePunishmentReserve,
|
|
|
|
|
Error: channel.Error,
|
2024-08-08 13:01:24 +07:00
|
|
|
IsOutbound: channel.IsOutbound,
|
2024-07-31 09:10:26 +02:00
|
|
|
Status: status,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
return apiChannels, nil
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *api) GetChannelPeerSuggestions(ctx context.Context) ([]alby.ChannelPeerSuggestion, error) {
|
2024-06-17 19:42:09 +07:00
|
|
|
return api.albyOAuthSvc.GetChannelPeerSuggestions(ctx)
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-07 20:08:10 +07:00
|
|
|
autoUnlockPassword, err := api.cfg.Get("AutoUnlockPassword", "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if autoUnlockPassword != "" {
|
|
|
|
|
return errors.New("Please disable auto-unlock before using this feature")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err = api.cfg.ChangeUnlockPassword(changeUnlockPasswordRequest.CurrentUnlockPassword, changeUnlockPasswordRequest.NewUnlockPassword)
|
2024-05-30 00:06:06 +07:00
|
|
|
|
|
|
|
|
if err != nil {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithError(err).Error("failed to change unlock password")
|
2024-05-30 00:06:06 +07:00
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-07 20:08:10 +07:00
|
|
|
func (api *api) SetAutoUnlockPassword(unlockPassword string) error {
|
|
|
|
|
if api.svc.GetLNClient() == nil {
|
|
|
|
|
return errors.New("LNClient not started")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err := api.cfg.SetAutoUnlockPassword(unlockPassword)
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("failed to set auto unlock password")
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
func (api *api) Stop() error {
|
2024-09-04 14:01:16 +07:00
|
|
|
if !startMutex.TryLock() {
|
|
|
|
|
// do not allow to stop twice in case this is somehow called twice
|
|
|
|
|
return errors.New("app is busy")
|
|
|
|
|
}
|
|
|
|
|
defer startMutex.Unlock()
|
|
|
|
|
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.Info("Running Stop command")
|
2024-05-30 00:06:06 +07:00
|
|
|
if api.svc.GetLNClient() == nil {
|
|
|
|
|
return errors.New("LNClient not started")
|
|
|
|
|
}
|
2024-06-17 19:42:09 +07:00
|
|
|
|
2024-07-13 20:01:33 +07:00
|
|
|
// stop the lnclient, nostr relay etc.
|
2024-05-30 00:06:06 +07:00
|
|
|
// The user will be forced to re-enter their unlock password to restart the node
|
2024-07-13 20:01:33 +07:00
|
|
|
api.svc.StopApp()
|
2024-06-17 19:42:09 +07:00
|
|
|
|
2024-07-13 20:01:33 +07:00
|
|
|
return nil
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 23:04:32 +07:00
|
|
|
func (api *api) DisconnectPeer(ctx context.Context, peerId string) error {
|
|
|
|
|
if api.svc.GetLNClient() == nil {
|
|
|
|
|
return errors.New("LNClient not started")
|
|
|
|
|
}
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithFields(logrus.Fields{
|
2024-05-30 23:04:32 +07:00
|
|
|
"peer_id": peerId,
|
|
|
|
|
}).Info("Disconnecting peer")
|
|
|
|
|
return api.svc.GetLNClient().DisconnectPeer(ctx, peerId)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
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")
|
|
|
|
|
}
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithFields(logrus.Fields{
|
2024-05-30 00:06:06 +07:00
|
|
|
"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,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-18 15:22:19 +07:00
|
|
|
func (api *api) UpdateChannel(ctx context.Context, updateChannelRequest *UpdateChannelRequest) error {
|
|
|
|
|
if api.svc.GetLNClient() == nil {
|
|
|
|
|
return errors.New("LNClient not started")
|
|
|
|
|
}
|
|
|
|
|
logger.Logger.WithFields(logrus.Fields{
|
|
|
|
|
"request": updateChannelRequest,
|
|
|
|
|
}).Info("updating channel")
|
|
|
|
|
return api.svc.GetLNClient().UpdateChannel(ctx, updateChannelRequest)
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-11 06:49:13 +03:00
|
|
|
func (api *api) GetNewOnchainAddress(ctx context.Context) (string, error) {
|
2024-05-30 00:06:06 +07:00
|
|
|
if api.svc.GetLNClient() == nil {
|
2024-06-11 06:49:13 +03:00
|
|
|
return "", errors.New("LNClient not started")
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
address, err := api.svc.GetLNClient().GetNewOnchainAddress(ctx)
|
|
|
|
|
if err != nil {
|
2024-06-11 06:49:13 +03:00
|
|
|
return "", err
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
2024-06-11 06:49:13 +03:00
|
|
|
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SetUpdate(config.OnchainAddressKey, address, "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save new onchain address to config")
|
|
|
|
|
}
|
2024-06-11 06:49:13 +03:00
|
|
|
|
|
|
|
|
return address, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *api) GetUnusedOnchainAddress(ctx context.Context) (string, error) {
|
|
|
|
|
if api.svc.GetLNClient() == nil {
|
|
|
|
|
return "", errors.New("LNClient not started")
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-17 19:42:09 +07:00
|
|
|
currentAddress, err := api.cfg.Get(config.OnchainAddressKey, "")
|
2024-06-11 06:49:13 +03:00
|
|
|
if err != nil {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithError(err).Error("Failed to get current address from config")
|
2024-06-11 06:49:13 +03:00
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if currentAddress != "" {
|
|
|
|
|
// check if address has any transactions
|
|
|
|
|
response, err := api.RequestEsploraApi("/address/" + currentAddress + "/txs")
|
|
|
|
|
if err != nil {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithError(err).Error("Failed to get current address transactions")
|
2024-06-11 06:49:13 +03:00
|
|
|
return currentAddress, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
transactions, ok := response.([]interface{})
|
|
|
|
|
if !ok {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithField("response", response).Error("Failed to cast esplora address txs response", response)
|
2024-06-11 06:49:13 +03:00
|
|
|
return currentAddress, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(transactions) == 0 {
|
|
|
|
|
// address has not been used yet
|
|
|
|
|
return currentAddress, nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newAddress, err := api.GetNewOnchainAddress(ctx)
|
|
|
|
|
if err != nil {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithError(err).Error("Failed to retrieve new onchain address")
|
2024-06-11 06:49:13 +03:00
|
|
|
return "", err
|
|
|
|
|
}
|
|
|
|
|
return newAddress, nil
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-04 11:44:58 +07:00
|
|
|
func (api *api) RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (*RedeemOnchainFundsResponse, error) {
|
2024-05-30 00:06:06 +07:00
|
|
|
if api.svc.GetLNClient() == nil {
|
|
|
|
|
return nil, errors.New("LNClient not started")
|
|
|
|
|
}
|
2024-09-04 11:44:58 +07:00
|
|
|
txId, err := api.svc.GetLNClient().RedeemOnchainFunds(ctx, toAddress, amount, sendAll)
|
2024-05-30 00:06:06 +07:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-11 06:49:13 +03:00
|
|
|
// TODO: remove dependency on this endpoint
|
2024-05-30 00:06:06 +07:00
|
|
|
func (api *api) RequestMempoolApi(endpoint string) (interface{}, error) {
|
2024-06-17 19:42:09 +07:00
|
|
|
url := api.cfg.GetEnv().MempoolApi + endpoint
|
2024-05-30 00:06:06 +07:00
|
|
|
|
|
|
|
|
client := http.Client{
|
|
|
|
|
Timeout: time.Second * 10,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
|
|
|
if err != nil {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
2024-05-30 00:06:06 +07:00
|
|
|
"url": url,
|
|
|
|
|
}).Error("Failed to create http request")
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res, err := client.Do(req)
|
|
|
|
|
if err != nil {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
2024-05-30 00:06:06 +07:00
|
|
|
"url": url,
|
|
|
|
|
}).Error("Failed to send request")
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
|
|
|
|
|
body, readErr := io.ReadAll(res.Body)
|
|
|
|
|
if readErr != nil {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithError(err).WithFields(logrus.Fields{
|
2024-05-30 00:06:06 +07:00
|
|
|
"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 {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithError(jsonErr).WithFields(logrus.Fields{
|
2024-05-30 00:06:06 +07:00
|
|
|
"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{}
|
2024-06-17 19:42:09 +07:00
|
|
|
backendType, _ := api.cfg.Get("LNBackendType", "")
|
2024-11-29 15:16:20 -06:00
|
|
|
ldkVssEnabled, _ := api.cfg.Get("LdkVssEnabled", "")
|
2025-01-07 20:08:10 +07:00
|
|
|
autoUnlockPassword, _ := api.cfg.Get("AutoUnlockPassword", "")
|
2024-08-24 20:25:05 +07:00
|
|
|
info.SetupCompleted = api.cfg.SetupCompleted()
|
2024-09-04 10:46:12 +05:30
|
|
|
if api.startupError != nil {
|
|
|
|
|
info.StartupError = api.startupError.Error()
|
|
|
|
|
info.StartupErrorTime = api.startupErrorTime
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
info.Running = api.svc.GetLNClient() != nil
|
|
|
|
|
info.BackendType = backendType
|
2024-06-17 19:42:09 +07:00
|
|
|
info.AlbyAuthUrl = api.albyOAuthSvc.GetAuthUrl()
|
|
|
|
|
info.OAuthRedirect = !api.cfg.GetEnv().IsDefaultClientId()
|
2024-06-22 12:22:01 +07:00
|
|
|
info.Version = version.Tag
|
2024-08-08 16:36:46 +07:00
|
|
|
info.EnableAdvancedSetup = api.cfg.GetEnv().EnableAdvancedSetup
|
2024-11-29 15:16:20 -06:00
|
|
|
info.LdkVssEnabled = ldkVssEnabled == "true"
|
2024-12-17 13:53:58 +07:00
|
|
|
info.VssSupported = backendType == config.LDKBackendType && api.cfg.GetEnv().LDKVssUrl != ""
|
2025-01-07 20:08:10 +07:00
|
|
|
info.AutoUnlockPasswordEnabled = autoUnlockPassword != ""
|
|
|
|
|
info.AutoUnlockPasswordSupported = api.cfg.GetEnv().IsDefaultClientId()
|
2024-06-17 19:42:09 +07:00
|
|
|
albyUserIdentifier, err := api.albyOAuthSvc.GetUserIdentifier()
|
2024-05-30 00:06:06 +07:00
|
|
|
if err != nil {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithError(err).Error("Failed to get alby user identifier")
|
2024-05-30 00:06:06 +07:00
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
info.AlbyUserIdentifier = albyUserIdentifier
|
2024-06-17 19:42:09 +07:00
|
|
|
info.AlbyAccountConnected = api.albyOAuthSvc.IsConnected(ctx)
|
2024-05-30 00:06:06 +07:00
|
|
|
if api.svc.GetLNClient() != nil {
|
|
|
|
|
nodeInfo, err := api.svc.GetLNClient().GetInfo(ctx)
|
|
|
|
|
if err != nil {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithError(err).Error("Failed to get nodeInfo")
|
2024-05-30 00:06:06 +07:00
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
info.Network = nodeInfo.Network
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-17 19:42:09 +07:00
|
|
|
info.NextBackupReminder, _ = api.cfg.Get("NextBackupReminder", "")
|
2024-05-30 00:06:06 +07:00
|
|
|
|
|
|
|
|
return &info, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-08 11:08:14 +02:00
|
|
|
func (api *api) GetMnemonic(unlockPassword string) (*MnemonicResponse, error) {
|
|
|
|
|
if !api.cfg.CheckUnlockPassword(unlockPassword) {
|
|
|
|
|
return nil, fmt.Errorf("wrong password")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mnemonic, err := api.cfg.Get("Mnemonic", unlockPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to fetch encryption key: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp := MnemonicResponse{
|
|
|
|
|
Mnemonic: mnemonic,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &resp, err
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *api) SetNextBackupReminder(backupReminderRequest *BackupReminderRequest) error {
|
2024-09-25 17:31:05 +07:00
|
|
|
err := api.cfg.SetUpdate("NextBackupReminder", backupReminderRequest.NextBackupReminder, "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save next backup reminder to config")
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-19 23:25:39 +07:00
|
|
|
var startMutex sync.Mutex
|
|
|
|
|
|
2024-09-04 10:46:12 +05:30
|
|
|
func (api *api) Start(startRequest *StartRequest) {
|
|
|
|
|
api.startupError = nil
|
|
|
|
|
err := api.StartInternal(startRequest)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to start node")
|
|
|
|
|
api.startupError = err
|
|
|
|
|
api.startupErrorTime = time.Now()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *api) StartInternal(startRequest *StartRequest) (err error) {
|
2024-06-19 23:25:39 +07:00
|
|
|
if !startMutex.TryLock() {
|
|
|
|
|
// do not allow to start twice in case this is somehow called twice
|
2024-09-04 14:01:16 +07:00
|
|
|
return errors.New("app is busy")
|
2024-06-19 23:25:39 +07:00
|
|
|
}
|
|
|
|
|
defer startMutex.Unlock()
|
2024-05-30 00:06:06 +07:00
|
|
|
return api.svc.StartApp(startRequest.UnlockPassword)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *api) Setup(ctx context.Context, setupRequest *SetupRequest) error {
|
2024-08-24 20:25:05 +07:00
|
|
|
if !startMutex.TryLock() {
|
|
|
|
|
// do not allow to start twice in case this is somehow called twice
|
2024-09-04 14:01:16 +07:00
|
|
|
return errors.New("app is busy")
|
2024-08-24 20:25:05 +07:00
|
|
|
}
|
|
|
|
|
defer startMutex.Unlock()
|
2024-05-30 00:06:06 +07:00
|
|
|
info, err := api.GetInfo(ctx)
|
|
|
|
|
if err != nil {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithError(err).Error("Failed to get info")
|
2024-05-30 00:06:06 +07:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if info.SetupCompleted {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.Error("Cannot re-setup node")
|
2024-05-30 00:06:06 +07:00
|
|
|
return errors.New("setup already completed")
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-24 20:25:05 +07:00
|
|
|
if setupRequest.UnlockPassword == "" {
|
|
|
|
|
return errors.New("no unlock password provided")
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SaveUnlockPasswordCheck(setupRequest.UnlockPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
|
|
|
|
|
// update next backup reminder
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SetUpdate("NextBackupReminder", setupRequest.NextBackupReminder, "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save next backup reminder")
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
// only update non-empty values
|
|
|
|
|
if setupRequest.LNBackendType != "" {
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SetUpdate("LNBackendType", setupRequest.LNBackendType, "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save backend type")
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
if setupRequest.BreezAPIKey != "" {
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SetUpdate("BreezAPIKey", setupRequest.BreezAPIKey, setupRequest.UnlockPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save breez api key")
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
if setupRequest.Mnemonic != "" {
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SetUpdate("Mnemonic", setupRequest.Mnemonic, setupRequest.UnlockPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save encrypted mnemonic")
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
if setupRequest.GreenlightInviteCode != "" {
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SetUpdate("GreenlightInviteCode", setupRequest.GreenlightInviteCode, setupRequest.UnlockPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save greenlight invite code")
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
if setupRequest.LNDAddress != "" {
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SetUpdate("LNDAddress", setupRequest.LNDAddress, setupRequest.UnlockPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save lnd address")
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
if setupRequest.LNDCertHex != "" {
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SetUpdate("LNDCertHex", setupRequest.LNDCertHex, setupRequest.UnlockPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save lnd cert hex")
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
if setupRequest.LNDMacaroonHex != "" {
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SetUpdate("LNDMacaroonHex", setupRequest.LNDMacaroonHex, setupRequest.UnlockPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save lnd macaroon hex")
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
2024-06-04 10:47:51 +03:00
|
|
|
if setupRequest.PhoenixdAddress != "" {
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SetUpdate("PhoenixdAddress", setupRequest.PhoenixdAddress, setupRequest.UnlockPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save phoenix address")
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-06-04 10:47:51 +03:00
|
|
|
}
|
|
|
|
|
if setupRequest.PhoenixdAuthorization != "" {
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SetUpdate("PhoenixdAuthorization", setupRequest.PhoenixdAuthorization, setupRequest.UnlockPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save phoenix auth")
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-06-04 10:47:51 +03:00
|
|
|
}
|
|
|
|
|
|
2024-06-14 19:12:22 +03:00
|
|
|
if setupRequest.CashuMintUrl != "" {
|
2024-09-25 17:31:05 +07:00
|
|
|
err = api.cfg.SetUpdate("CashuMintUrl", setupRequest.CashuMintUrl, setupRequest.UnlockPassword)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Logger.WithError(err).Error("Failed to save cashu mint url")
|
|
|
|
|
return err
|
|
|
|
|
}
|
2024-06-14 19:12:22 +03:00
|
|
|
}
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-01 20:30:40 +07:00
|
|
|
func (api *api) GetWalletCapabilities(ctx context.Context) (*WalletCapabilitiesResponse, error) {
|
|
|
|
|
if api.svc.GetLNClient() == nil {
|
|
|
|
|
return nil, errors.New("LNClient not started")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
methods := api.svc.GetLNClient().GetSupportedNIP47Methods()
|
|
|
|
|
notificationTypes := api.svc.GetLNClient().GetSupportedNIP47NotificationTypes()
|
|
|
|
|
|
|
|
|
|
scopes, err := permissions.RequestMethodsToScopes(methods)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if len(notificationTypes) > 0 {
|
2024-07-19 23:30:22 +07:00
|
|
|
scopes = append(scopes, constants.NOTIFICATIONS_SCOPE)
|
2024-07-01 20:30:40 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &WalletCapabilitiesResponse{
|
|
|
|
|
Methods: methods,
|
|
|
|
|
NotificationTypes: notificationTypes,
|
|
|
|
|
Scopes: scopes,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-16 12:32:35 +07:00
|
|
|
func (api *api) MigrateNodeStorage(ctx context.Context, to string) error {
|
|
|
|
|
if api.svc.GetLNClient() == nil {
|
|
|
|
|
return errors.New("LNClient not started")
|
|
|
|
|
}
|
|
|
|
|
if to != "VSS" {
|
|
|
|
|
return fmt.Errorf("Migration type not supported: %s", to)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ldkVssEnabled, err := api.cfg.Get("LdkVssEnabled", "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ldkVssEnabled == "true" {
|
|
|
|
|
return errors.New("VSS already enabled")
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-17 13:53:58 +07:00
|
|
|
if api.cfg.GetEnv().LDKVssUrl == "" {
|
|
|
|
|
return errors.New("No VSS URL set")
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-16 12:32:35 +07:00
|
|
|
api.cfg.SetUpdate("LdkVssEnabled", "true", "")
|
|
|
|
|
api.cfg.SetUpdate("LdkMigrateStorage", "VSS", "")
|
|
|
|
|
return api.Stop()
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-22 18:29:51 +05:30
|
|
|
func (api *api) GetNetworkGraph(ctx context.Context, nodeIds []string) (NetworkGraphResponse, error) {
|
2024-05-30 00:06:06 +07:00
|
|
|
if api.svc.GetLNClient() == nil {
|
|
|
|
|
return nil, errors.New("LNClient not started")
|
|
|
|
|
}
|
2024-08-22 18:29:51 +05:30
|
|
|
return api.svc.GetLNClient().GetNetworkGraph(ctx, nodeIds)
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 {
|
2024-06-17 19:42:09 +07:00
|
|
|
logFileName := logger.GetLogFilePath()
|
2024-12-23 12:25:59 +03:00
|
|
|
if logFileName == "" {
|
|
|
|
|
logData = []byte("file log is disabled")
|
|
|
|
|
} else {
|
|
|
|
|
logData, err = utils.ReadFileTail(logFileName, getLogRequest.MaxLen)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2024-05-30 00:06:06 +07:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return nil, fmt.Errorf("invalid log type: '%s'", logType)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &GetLogOutputResponse{Log: string(logData)}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-17 11:29:20 +03:00
|
|
|
func (api *api) Health(ctx context.Context) (*HealthResponse, error) {
|
|
|
|
|
var alarms []HealthAlarm
|
|
|
|
|
|
|
|
|
|
albyInfo, err := api.albyOAuthSvc.GetInfo(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if !albyInfo.Healthy {
|
|
|
|
|
alarms = append(alarms, NewHealthAlarm(HealthAlarmKindAlbyService, albyInfo.Incidents))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
isNostrRelayReady := api.svc.IsRelayReady()
|
|
|
|
|
if !isNostrRelayReady {
|
|
|
|
|
alarms = append(alarms, NewHealthAlarm(HealthAlarmKindNostrRelayOffline, nil))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lnClient := api.svc.GetLNClient()
|
|
|
|
|
|
|
|
|
|
if lnClient != nil {
|
|
|
|
|
nodeStatus, err := lnClient.GetNodeStatus(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if nodeStatus == nil || !nodeStatus.IsReady {
|
|
|
|
|
alarms = append(alarms, NewHealthAlarm(HealthAlarmKindNodeNotReady, nodeStatus))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
channels, err := lnClient.ListChannels(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
offlineChannels := slices.DeleteFunc(channels, func(channel lnclient.Channel) bool {
|
|
|
|
|
return channel.Active
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if len(offlineChannels) > 0 {
|
|
|
|
|
alarms = append(alarms, NewHealthAlarm(HealthAlarmKindChannelsOffline, nil))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &HealthResponse{Alarms: alarms}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-30 14:20:17 +03:00
|
|
|
func (api *api) GetCustomNodeCommands() (*CustomNodeCommandsResponse, error) {
|
|
|
|
|
lnClient := api.svc.GetLNClient()
|
|
|
|
|
if lnClient == nil {
|
|
|
|
|
return nil, errors.New("LNClient not started")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
allCommandDefs := lnClient.GetCustomNodeCommandDefinitions()
|
|
|
|
|
commandDefs := make([]CustomNodeCommandDef, 0, len(allCommandDefs))
|
|
|
|
|
for _, commandDef := range allCommandDefs {
|
|
|
|
|
argDefs := make([]CustomNodeCommandArgDef, 0, len(commandDef.Args))
|
|
|
|
|
for _, argDef := range commandDef.Args {
|
|
|
|
|
argDefs = append(argDefs, CustomNodeCommandArgDef{
|
|
|
|
|
Name: argDef.Name,
|
|
|
|
|
Description: argDef.Description,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
commandDefs = append(commandDefs, CustomNodeCommandDef{
|
|
|
|
|
Name: commandDef.Name,
|
|
|
|
|
Description: commandDef.Description,
|
|
|
|
|
Args: argDefs,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &CustomNodeCommandsResponse{Commands: commandDefs}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *api) ExecuteCustomNodeCommand(ctx context.Context, command string) (interface{}, error) {
|
|
|
|
|
lnClient := api.svc.GetLNClient()
|
|
|
|
|
if lnClient == nil {
|
|
|
|
|
return nil, errors.New("LNClient not started")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Split command line into arguments. Command name must be the first argument.
|
|
|
|
|
parsedArgs, err := utils.ParseCommandLine(command)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to parse node command: %w", err)
|
|
|
|
|
} else if len(parsedArgs) == 0 {
|
|
|
|
|
return nil, errors.New("no command provided")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Look up the requested command definition.
|
|
|
|
|
allCommandDefs := lnClient.GetCustomNodeCommandDefinitions()
|
|
|
|
|
commandDefIdx := slices.IndexFunc(allCommandDefs, func(def lnclient.CustomNodeCommandDef) bool {
|
|
|
|
|
return def.Name == parsedArgs[0]
|
|
|
|
|
})
|
|
|
|
|
if commandDefIdx < 0 {
|
|
|
|
|
return nil, fmt.Errorf("unknown command: %q", parsedArgs[0])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build flag set.
|
|
|
|
|
commandDef := allCommandDefs[commandDefIdx]
|
|
|
|
|
flagSet := flag.NewFlagSet(commandDef.Name, flag.ContinueOnError)
|
|
|
|
|
for _, argDef := range commandDef.Args {
|
|
|
|
|
flagSet.String(argDef.Name, "", argDef.Description)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err = flagSet.Parse(parsedArgs[1:]); err != nil {
|
|
|
|
|
return nil, fmt.Errorf("failed to parse command arguments: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect flags that have been set.
|
|
|
|
|
argValues := make(map[string]string)
|
|
|
|
|
flagSet.Visit(func(f *flag.Flag) {
|
|
|
|
|
argValues[f.Name] = f.Value.String()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
reqArgs := make([]lnclient.CustomNodeCommandArg, 0, len(argValues))
|
|
|
|
|
for _, argDef := range commandDef.Args {
|
|
|
|
|
if argValue, ok := argValues[argDef.Name]; ok {
|
|
|
|
|
reqArgs = append(reqArgs, lnclient.CustomNodeCommandArg{
|
|
|
|
|
Name: argDef.Name,
|
|
|
|
|
Value: argValue,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nodeResp, err := lnClient.ExecuteCustomNodeCommand(ctx, &lnclient.CustomNodeCommandRequest{
|
|
|
|
|
Name: commandDef.Name,
|
|
|
|
|
Args: reqArgs,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("node failed to execute custom command: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nodeResp.Response, nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 00:06:06 +07:00
|
|
|
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 {
|
2024-06-17 19:42:09 +07:00
|
|
|
logger.Logger.WithField("expiresAt", expiresAtString).Error("Invalid expiresAt")
|
2024-05-30 00:06:06 +07:00
|
|
|
return nil, fmt.Errorf("invalid expiresAt: %v", err)
|
|
|
|
|
}
|
|
|
|
|
expiresAt = &expiresAtValue
|
|
|
|
|
}
|
|
|
|
|
return expiresAt, nil
|
|
|
|
|
}
|