http_service.go raw

   1  package http
   2  
   3  import (
   4  	"bytes"
   5  	"encoding/json"
   6  	"errors"
   7  	"fmt"
   8  	"net/http"
   9  	"slices"
  10  	"strconv"
  11  	"strings"
  12  	"time"
  13  
  14  	"github.com/golang-jwt/jwt/v5"
  15  	echojwt "github.com/labstack/echo-jwt/v4"
  16  	"github.com/labstack/echo/v4"
  17  	"github.com/labstack/echo/v4/middleware"
  18  	"github.com/sirupsen/logrus"
  19  	"gorm.io/gorm"
  20  
  21  	"github.com/getAlby/hub/apps"
  22  	"github.com/getAlby/hub/config"
  23  	"github.com/getAlby/hub/events"
  24  	"github.com/getAlby/hub/logger"
  25  	"github.com/getAlby/hub/service"
  26  
  27  	"github.com/getAlby/hub/api"
  28  	"github.com/getAlby/hub/frontend"
  29  )
  30  
  31  type authTokenResponse struct {
  32  	Token string `json:"token"`
  33  }
  34  
  35  type jwtCustomClaims struct {
  36  	// we can add extra claims here
  37  	// Name  string `json:"name"`
  38  	// Admin bool   `json:"admin"`
  39  	Permission string `json:"permission,omitempty"` // "full" or "readonly"
  40  	jwt.RegisteredClaims
  41  }
  42  
  43  type HttpService struct {
  44  	api            api.API
  45  	albyHttpSvc    *AlbyHttpService
  46  	cfg            config.Config
  47  	eventPublisher events.EventPublisher
  48  	db             *gorm.DB
  49  	appsSvc        apps.AppsService
  50  }
  51  
  52  func NewHttpService(svc service.Service, eventPublisher events.EventPublisher) *HttpService {
  53  	return &HttpService{
  54  		api:            api.NewAPI(svc, svc.GetDB(), svc.GetConfig(), svc.GetKeys(), svc.GetAlbySvc(), svc.GetAlbyOAuthSvc(), eventPublisher),
  55  		albyHttpSvc:    NewAlbyHttpService(svc, svc.GetAlbySvc(), svc.GetAlbyOAuthSvc(), svc.GetConfig().GetEnv()),
  56  		cfg:            svc.GetConfig(),
  57  		eventPublisher: eventPublisher,
  58  		db:             svc.GetDB(),
  59  		appsSvc:        apps.NewAppsService(svc.GetDB(), eventPublisher, svc.GetKeys(), svc.GetConfig()),
  60  	}
  61  }
  62  
  63  func (httpSvc *HttpService) RegisterSharedRoutes(e *echo.Echo) {
  64  	e.HideBanner = true
  65  
  66  	e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
  67  		ContentTypeNosniff:    "nosniff",
  68  		XFrameOptions:         "DENY",
  69  		// when making changes here, also update the CSP in frontend/vite.config.ts
  70  		ContentSecurityPolicy: "default-src 'self'; img-src 'self' https://uploads.getalby-assets.com https://cdn.getalby-assets.com https://getalby.com; connect-src 'self' https://api.getalby.com https://getalby.com https://zapplanner.albylabs.com wss://relay.mleku.dev wss://relay.getalby.com wss://relay2.getalby.com; frame-src https://www.youtube-nocookie.com",
  71  		ReferrerPolicy:        "no-referrer",
  72  	}))
  73  	e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
  74  		LogURI:       true,
  75  		LogStatus:    true,
  76  		LogRemoteIP:  true,
  77  		LogUserAgent: true,
  78  		LogHost:      true,
  79  		LogRequestID: true,
  80  		LogValuesFunc: func(c echo.Context, values middleware.RequestLoggerValues) error {
  81  			logger.Logger.WithFields(logrus.Fields{
  82  				"uri":        values.URI,
  83  				"status":     values.Status,
  84  				"remote_ip":  values.RemoteIP,
  85  				"user_agent": values.UserAgent,
  86  				"host":       values.Host,
  87  				"request_id": values.RequestID,
  88  			}).Info("handled API request")
  89  			return nil
  90  		},
  91  	}))
  92  
  93  	e.Use(middleware.Recover())
  94  	e.Use(middleware.RequestID())
  95  
  96  	e.GET("/api/info", httpSvc.infoHandler)
  97  	e.POST("/api/setup", httpSvc.setupHandler)
  98  	e.POST("/api/restore", httpSvc.restoreBackupHandler)
  99  
 100  	// A single global rate limiter (one bucket for all callers, not per-IP)
 101  	// shared by every endpoint that verifies the unlock password, to bound how
 102  	// fast the password can be guessed.
 103  	unlockRateLimiter := middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
 104  		Store: middleware.NewRateLimiterMemoryStoreWithConfig(
 105  			// burst of 2 so unlocking and then immediately acting is not blocked
 106  			middleware.RateLimiterMemoryStoreConfig{Rate: 1, Burst: 2},
 107  		),
 108  		IdentifierExtractor: func(c echo.Context) (string, error) {
 109  			return "", nil
 110  		},
 111  	})
 112  	e.POST("/api/start", httpSvc.startHandler, unlockRateLimiter)
 113  	e.POST("/api/unlock", httpSvc.unlockHandler, unlockRateLimiter)
 114  	e.POST("/api/backup", httpSvc.createBackupHandler, unlockRateLimiter)
 115  	e.GET("/logout", httpSvc.logoutHandler)
 116  
 117  	frontend.RegisterHandlers(e)
 118  
 119  	// restricted routes
 120  	// Configure middleware with the custom claims type
 121  	jwtConfig := echojwt.Config{
 122  		NewClaimsFunc: func(c echo.Context) jwt.Claims {
 123  			return new(jwtCustomClaims)
 124  		},
 125  		// use a custom key func as the JWT secret will change if the user changes their unlock password
 126  		KeyFunc: func(token *jwt.Token) (interface{}, error) {
 127  			secret, err := httpSvc.cfg.GetJWTSecret()
 128  			if err != nil {
 129  				return nil, err
 130  			}
 131  			return []byte(secret), nil
 132  		},
 133  	}
 134  	// Read-only API group - accessible to both full and readonly tokens
 135  	readOnlyApiGroup := e.Group("/api")
 136  	readOnlyApiGroup.Use(echojwt.WithConfig(jwtConfig))
 137  
 138  	readOnlyApiGroup.GET("/apps", httpSvc.appsListHandler)
 139  	readOnlyApiGroup.GET("/apps/:pubkey", httpSvc.appsShowByPubkeyHandler)
 140  	readOnlyApiGroup.GET("/v2/apps/:id", httpSvc.appsShowHandler)
 141  	readOnlyApiGroup.GET("/channels", httpSvc.channelsListHandler)
 142  	readOnlyApiGroup.GET("/channels/suggestions", httpSvc.channelPeerSuggestionsHandler)
 143  	readOnlyApiGroup.GET("/channel-offer", httpSvc.channelOfferHandler)
 144  	readOnlyApiGroup.GET("/node/connection-info", httpSvc.nodeConnectionInfoHandler)
 145  	readOnlyApiGroup.GET("/node/status", httpSvc.nodeStatusHandler)
 146  	readOnlyApiGroup.GET("/node/network-graph", httpSvc.nodeNetworkGraphHandler)
 147  	readOnlyApiGroup.GET("/node/transactions", httpSvc.listOnchainTransactionsHandler)
 148  	readOnlyApiGroup.GET("/peers", httpSvc.listPeers)
 149  	readOnlyApiGroup.GET("/wallet/address", httpSvc.onchainAddressHandler)
 150  	readOnlyApiGroup.GET("/wallet/capabilities", httpSvc.capabilitiesHandler)
 151  	readOnlyApiGroup.GET("/transactions", httpSvc.listTransactionsHandler)
 152  	readOnlyApiGroup.GET("/transactions/:paymentHash", httpSvc.lookupTransactionHandler)
 153  	readOnlyApiGroup.GET("/balances", httpSvc.balancesHandler)
 154  	readOnlyApiGroup.GET("/mempool", httpSvc.mempoolApiHandler)
 155  	readOnlyApiGroup.GET("/health", httpSvc.healthHandler)
 156  	readOnlyApiGroup.GET("/commands", httpSvc.getCustomNodeCommandsHandler)
 157  	readOnlyApiGroup.GET("/swaps", httpSvc.listSwapsHandler)
 158  	readOnlyApiGroup.GET("/swaps/:swapId", httpSvc.lookupSwapHandler)
 159  	readOnlyApiGroup.GET("/swaps/out/info", httpSvc.getSwapOutInfoHandler)
 160  	readOnlyApiGroup.GET("/swaps/in/info", httpSvc.getSwapInInfoHandler)
 161  	readOnlyApiGroup.GET("/autoswap", httpSvc.getAutoSwapConfigHandler)
 162  	readOnlyApiGroup.GET("/forwards", httpSvc.forwardsHandler)
 163  
 164  	// Full access API group - requires a token with full permissions
 165  	fullAccessApiGroup := e.Group("/api")
 166  	fullAccessApiGroup.Use(echojwt.WithConfig(jwtConfig))
 167  	fullAccessApiGroup.Use(httpSvc.requireFullAccess)
 168  
 169  	fullAccessApiGroup.POST("/event", httpSvc.eventHandler)
 170  	fullAccessApiGroup.PATCH("/unlock-password", httpSvc.changeUnlockPasswordHandler, unlockRateLimiter)
 171  	fullAccessApiGroup.PATCH("/auto-unlock", httpSvc.autoUnlockHandler, unlockRateLimiter)
 172  	fullAccessApiGroup.PATCH("/settings", httpSvc.updateSettingsHandler)
 173  	fullAccessApiGroup.PATCH("/apps/:pubkey", httpSvc.appsUpdateHandler)
 174  	fullAccessApiGroup.PATCH("/transactions/:id/labels", httpSvc.setTransactionUserLabelsHandler)
 175  	fullAccessApiGroup.DELETE("/apps/:pubkey", httpSvc.appsDeleteHandler)
 176  	fullAccessApiGroup.POST("/transfers", httpSvc.transfersHandler)
 177  	fullAccessApiGroup.POST("/apps", httpSvc.appsCreateHandler, unlockRateLimiter)
 178  	fullAccessApiGroup.POST("/lightning-addresses", httpSvc.lightningAddressesCreateHandler)
 179  	fullAccessApiGroup.DELETE("/lightning-addresses/:appId", httpSvc.lightningAddressesDeleteHandler)
 180  	fullAccessApiGroup.POST("/mnemonic", httpSvc.mnemonicHandler, unlockRateLimiter)
 181  	fullAccessApiGroup.PATCH("/backup-reminder", httpSvc.backupReminderHandler)
 182  	fullAccessApiGroup.POST("/channels", httpSvc.openChannelHandler)
 183  	fullAccessApiGroup.POST("/channels/rebalance", httpSvc.rebalanceChannelHandler)
 184  	fullAccessApiGroup.POST("/lsp-orders", httpSvc.newInstantChannelInvoiceHandler)
 185  	fullAccessApiGroup.POST("/node/migrate-storage", httpSvc.migrateNodeStorageHandler)
 186  	fullAccessApiGroup.POST("/peers", httpSvc.connectPeerHandler)
 187  	fullAccessApiGroup.DELETE("/peers/:peerId", httpSvc.disconnectPeerHandler)
 188  	fullAccessApiGroup.DELETE("/peers/:peerId/channels/:channelId", httpSvc.closeChannelHandler)
 189  	fullAccessApiGroup.PATCH("/peers/:peerId/channels/:channelId", httpSvc.updateChannelHandler)
 190  	fullAccessApiGroup.POST("/wallet/new-address", httpSvc.newOnchainAddressHandler)
 191  	fullAccessApiGroup.POST("/wallet/redeem-onchain-funds", httpSvc.redeemOnchainFundsHandler)
 192  	fullAccessApiGroup.POST("/wallet/sign-message", httpSvc.signMessageHandler)
 193  	fullAccessApiGroup.POST("/wallet/sync", httpSvc.walletSyncHandler)
 194  	fullAccessApiGroup.POST("/payments/:invoice", httpSvc.sendPaymentHandler)
 195  	fullAccessApiGroup.POST("/invoices", httpSvc.makeInvoiceHandler)
 196  	fullAccessApiGroup.POST("/offers", httpSvc.makeOfferHandler)
 197  	fullAccessApiGroup.POST("/reset-router", httpSvc.resetRouterHandler)
 198  	fullAccessApiGroup.POST("/stop", httpSvc.stopHandler)
 199  	fullAccessApiGroup.POST("/command", httpSvc.execCustomNodeCommandHandler)
 200  	fullAccessApiGroup.POST("/swaps/out", httpSvc.initiateSwapOutHandler)
 201  	fullAccessApiGroup.POST("/swaps/in", httpSvc.initiateSwapInHandler)
 202  	fullAccessApiGroup.POST("/swaps/refund", httpSvc.refundSwapHandler)
 203  	fullAccessApiGroup.GET("/swaps/mnemonic", httpSvc.swapMnemonicHandler)
 204  	fullAccessApiGroup.GET("/log/:type", httpSvc.getLogOutputHandler)
 205  	fullAccessApiGroup.POST("/autoswap", httpSvc.enableAutoSwapOutHandler, unlockRateLimiter)
 206  	fullAccessApiGroup.DELETE("/autoswap", httpSvc.disableAutoSwapOutHandler)
 207  	fullAccessApiGroup.POST("/node/alias", httpSvc.setNodeAliasHandler)
 208  
 209  	httpSvc.albyHttpSvc.RegisterSharedRoutes(readOnlyApiGroup, fullAccessApiGroup, e)
 210  }
 211  
 212  func (httpSvc *HttpService) infoHandler(c echo.Context) error {
 213  	responseBody, err := httpSvc.api.GetInfo(c.Request().Context())
 214  	if err != nil {
 215  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 216  			Message: err.Error(),
 217  		})
 218  	}
 219  
 220  	authHeader := c.Request().Header.Get("Authorization")
 221  	if authHeader != "" {
 222  		parts := strings.Split(authHeader, " ")
 223  		if parts[0] == "Bearer" {
 224  			tokenString := parts[1]
 225  			token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
 226  				secret, err := httpSvc.cfg.GetJWTSecret()
 227  				if err != nil {
 228  					return nil, err
 229  				}
 230  
 231  				return []byte(secret), nil
 232  			})
 233  			if err != nil {
 234  				logger.Logger.WithError(err).Error("failed to parse token")
 235  			}
 236  			responseBody.Unlocked = err == nil && token != nil && token.Valid
 237  		}
 238  	}
 239  
 240  	return c.JSON(http.StatusOK, responseBody)
 241  }
 242  
 243  func (httpSvc *HttpService) eventHandler(c echo.Context) error {
 244  	var sendEventRequest api.SendEventRequest
 245  	if err := c.Bind(&sendEventRequest); err != nil {
 246  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 247  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 248  		})
 249  	}
 250  
 251  	httpSvc.api.SendEvent(sendEventRequest.Event, sendEventRequest.Properties)
 252  
 253  	return c.NoContent(http.StatusOK)
 254  }
 255  
 256  func (httpSvc *HttpService) mnemonicHandler(c echo.Context) error {
 257  	var mnemonicRequest api.MnemonicRequest
 258  	if err := c.Bind(&mnemonicRequest); err != nil {
 259  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 260  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 261  		})
 262  	}
 263  
 264  	responseBody, err := httpSvc.api.GetMnemonic(mnemonicRequest.UnlockPassword)
 265  
 266  	if err != nil {
 267  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 268  			Message: err.Error(),
 269  		})
 270  	}
 271  
 272  	return c.JSON(http.StatusOK, responseBody)
 273  }
 274  
 275  func (httpSvc *HttpService) backupReminderHandler(c echo.Context) error {
 276  	var backupReminderRequest api.BackupReminderRequest
 277  	if err := c.Bind(&backupReminderRequest); err != nil {
 278  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 279  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 280  		})
 281  	}
 282  
 283  	err := httpSvc.api.SetNextBackupReminder(&backupReminderRequest)
 284  	if err != nil {
 285  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 286  			Message: fmt.Sprintf("Failed to store backup reminder: %s", err.Error()),
 287  		})
 288  	}
 289  
 290  	return c.NoContent(http.StatusNoContent)
 291  }
 292  
 293  func (httpSvc *HttpService) startHandler(c echo.Context) error {
 294  	var startRequest api.StartRequest
 295  	if err := c.Bind(&startRequest); err != nil {
 296  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 297  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 298  		})
 299  	}
 300  
 301  	if !httpSvc.cfg.CheckUnlockPassword(startRequest.UnlockPassword) {
 302  		return c.JSON(http.StatusUnauthorized, ErrorResponse{
 303  			Message: "Invalid password",
 304  		})
 305  	}
 306  
 307  	err := httpSvc.cfg.LoadJWTSecret(startRequest.UnlockPassword)
 308  
 309  	if err != nil {
 310  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 311  			Message: fmt.Sprintf("Failed to load JWT secret: %s", err.Error()),
 312  		})
 313  	}
 314  
 315  	token, err := httpSvc.createJWT(nil, "full")
 316  
 317  	if err != nil {
 318  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 319  			Message: fmt.Sprintf("Failed to save session: %s", err.Error()),
 320  		})
 321  	}
 322  
 323  	go httpSvc.api.Start(&startRequest)
 324  
 325  	return c.JSON(http.StatusOK, &authTokenResponse{
 326  		Token: token,
 327  	})
 328  }
 329  
 330  func (httpSvc *HttpService) unlockHandler(c echo.Context) error {
 331  	var unlockRequest api.UnlockRequest
 332  	if err := c.Bind(&unlockRequest); err != nil {
 333  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 334  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 335  		})
 336  	}
 337  
 338  	if !httpSvc.cfg.CheckUnlockPassword(unlockRequest.UnlockPassword) {
 339  		return c.JSON(http.StatusUnauthorized, ErrorResponse{
 340  			Message: "Invalid password",
 341  		})
 342  	}
 343  
 344  	if unlockRequest.Permission == "" {
 345  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 346  			Message: "Permission field is required",
 347  		})
 348  	}
 349  
 350  	if !slices.Contains([]string{"full", "readonly"}, unlockRequest.Permission) {
 351  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 352  			Message: "Permission field is unknown",
 353  		})
 354  	}
 355  
 356  	_, err := httpSvc.api.GetNodeStatus(c.Request().Context())
 357  	if err != nil {
 358  		if errors.Is(err, api.ErrLNClientNotStarted) {
 359  			return c.JSON(http.StatusBadRequest, ErrorResponse{
 360  				Message: "Node is not running, start it before unlocking.",
 361  			})
 362  		}
 363  
 364  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 365  			Message: err.Error(),
 366  		})
 367  	}
 368  
 369  	token, err := httpSvc.createJWT(unlockRequest.TokenExpiryDays, unlockRequest.Permission)
 370  	if err != nil {
 371  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 372  			Message: fmt.Sprintf("Failed to save session: %s", err.Error()),
 373  		})
 374  	}
 375  
 376  	httpSvc.eventPublisher.Publish(&events.Event{
 377  		Event: "nwc_unlocked",
 378  	})
 379  
 380  	return c.JSON(http.StatusOK, &authTokenResponse{
 381  		Token: token,
 382  	})
 383  }
 384  
 385  func (httpSvc *HttpService) requireFullAccess(next echo.HandlerFunc) echo.HandlerFunc {
 386  	return func(c echo.Context) error {
 387  		token := c.Get("user").(*jwt.Token)
 388  		claims := token.Claims.(*jwtCustomClaims)
 389  
 390  		// Allow if no permission specified (backward compatibility) or if full access
 391  		if claims.Permission == "" || claims.Permission == "full" {
 392  			return next(c)
 393  		}
 394  
 395  		return c.JSON(http.StatusForbidden, ErrorResponse{
 396  			Message: "This operation requires full access permissions",
 397  		})
 398  	}
 399  }
 400  
 401  func (httpSvc *HttpService) changeUnlockPasswordHandler(c echo.Context) error {
 402  	var changeUnlockPasswordRequest api.ChangeUnlockPasswordRequest
 403  	if err := c.Bind(&changeUnlockPasswordRequest); err != nil {
 404  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 405  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 406  		})
 407  	}
 408  
 409  	err := httpSvc.api.ChangeUnlockPassword(&changeUnlockPasswordRequest)
 410  	if err != nil {
 411  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 412  			Message: fmt.Sprintf("Failed to change unlock password: %s", err.Error()),
 413  		})
 414  	}
 415  
 416  	return c.NoContent(http.StatusNoContent)
 417  }
 418  
 419  func (httpSvc *HttpService) updateSettingsHandler(c echo.Context) error {
 420  	var updateSettingsRequest api.UpdateSettingsRequest
 421  	if err := c.Bind(&updateSettingsRequest); err != nil {
 422  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 423  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 424  		})
 425  	}
 426  
 427  	err := httpSvc.api.UpdateSettings(&updateSettingsRequest)
 428  	if err != nil {
 429  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 430  			Message: fmt.Sprintf("Failed to update settings: %s", err.Error()),
 431  		})
 432  	}
 433  
 434  	return c.NoContent(http.StatusNoContent)
 435  }
 436  
 437  func (httpSvc *HttpService) autoUnlockHandler(c echo.Context) error {
 438  	var autoUnlockRequest api.AutoUnlockRequest
 439  	if err := c.Bind(&autoUnlockRequest); err != nil {
 440  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 441  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 442  		})
 443  	}
 444  
 445  	err := httpSvc.api.SetAutoUnlockPassword(autoUnlockRequest.UnlockPassword)
 446  	if err != nil {
 447  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 448  			Message: fmt.Sprintf("Failed to set auto unlock password: %s", err.Error()),
 449  		})
 450  	}
 451  
 452  	return c.NoContent(http.StatusNoContent)
 453  }
 454  
 455  func (httpSvc *HttpService) createJWT(tokenExpiryDays *uint64, permission string) (string, error) {
 456  	if !slices.Contains([]string{"full", "readonly"}, permission) {
 457  		return "", errors.New("invalid token permission")
 458  	}
 459  
 460  	expiryDays := uint64(30)
 461  	if tokenExpiryDays != nil {
 462  		expiryDays = *tokenExpiryDays
 463  	}
 464  
 465  	// Set custom claims
 466  	claims := &jwtCustomClaims{
 467  		Permission: permission,
 468  		RegisteredClaims: jwt.RegisteredClaims{
 469  			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * time.Duration(expiryDays))),
 470  		},
 471  	}
 472  
 473  	// Create token with claims
 474  	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
 475  
 476  	if token == nil {
 477  		return "", errors.New("failed to create token")
 478  	}
 479  
 480  	secret, err := httpSvc.cfg.GetJWTSecret()
 481  	if err != nil {
 482  		return "", err
 483  	}
 484  
 485  	signed, err := token.SignedString([]byte(secret))
 486  	if err != nil {
 487  		return "", err
 488  	}
 489  	return signed, nil
 490  }
 491  
 492  func (httpSvc *HttpService) channelsListHandler(c echo.Context) error {
 493  	ctx := c.Request().Context()
 494  
 495  	channels, err := httpSvc.api.ListChannels(ctx)
 496  
 497  	if err != nil {
 498  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 499  			Message: err.Error(),
 500  		})
 501  	}
 502  
 503  	return c.JSON(http.StatusOK, channels)
 504  }
 505  
 506  func (httpSvc *HttpService) channelPeerSuggestionsHandler(c echo.Context) error {
 507  	ctx := c.Request().Context()
 508  
 509  	suggestions, err := httpSvc.api.GetChannelPeerSuggestions(ctx)
 510  
 511  	if err != nil {
 512  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 513  			Message: err.Error(),
 514  		})
 515  	}
 516  
 517  	return c.JSON(http.StatusOK, suggestions)
 518  }
 519  
 520  func (httpSvc *HttpService) channelOfferHandler(c echo.Context) error {
 521  	ctx := c.Request().Context()
 522  
 523  	suggestions, err := httpSvc.api.GetLSPChannelOffer(ctx)
 524  
 525  	if err != nil {
 526  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 527  			Message: err.Error(),
 528  		})
 529  	}
 530  
 531  	return c.JSON(http.StatusOK, suggestions)
 532  }
 533  
 534  func (httpSvc *HttpService) resetRouterHandler(c echo.Context) error {
 535  	var resetRouterRequest api.ResetRouterRequest
 536  	if err := c.Bind(&resetRouterRequest); err != nil {
 537  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 538  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 539  		})
 540  	}
 541  
 542  	err := httpSvc.api.ResetRouter(resetRouterRequest.Key)
 543  
 544  	if err != nil {
 545  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 546  			Message: err.Error(),
 547  		})
 548  	}
 549  
 550  	return c.NoContent(http.StatusNoContent)
 551  }
 552  
 553  func (httpSvc *HttpService) stopHandler(c echo.Context) error {
 554  
 555  	err := httpSvc.api.Stop()
 556  
 557  	if err != nil {
 558  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 559  			Message: err.Error(),
 560  		})
 561  	}
 562  
 563  	return c.NoContent(http.StatusNoContent)
 564  }
 565  
 566  func (httpSvc *HttpService) nodeConnectionInfoHandler(c echo.Context) error {
 567  	ctx := c.Request().Context()
 568  
 569  	info, err := httpSvc.api.GetNodeConnectionInfo(ctx)
 570  
 571  	if err != nil {
 572  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 573  			Message: err.Error(),
 574  		})
 575  	}
 576  
 577  	return c.JSON(http.StatusOK, info)
 578  }
 579  
 580  func (httpSvc *HttpService) nodeStatusHandler(c echo.Context) error {
 581  	ctx := c.Request().Context()
 582  
 583  	info, err := httpSvc.api.GetNodeStatus(ctx)
 584  
 585  	if err != nil {
 586  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 587  			Message: err.Error(),
 588  		})
 589  	}
 590  
 591  	return c.JSON(http.StatusOK, info)
 592  }
 593  
 594  func (httpSvc *HttpService) nodeNetworkGraphHandler(c echo.Context) error {
 595  	ctx := c.Request().Context()
 596  
 597  	nodeIds := strings.Split(c.QueryParam("nodeIds"), ",")
 598  
 599  	info, err := httpSvc.api.GetNetworkGraph(ctx, nodeIds)
 600  
 601  	if err != nil {
 602  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 603  			Message: err.Error(),
 604  		})
 605  	}
 606  
 607  	return c.JSON(http.StatusOK, info)
 608  }
 609  
 610  func (httpSvc *HttpService) migrateNodeStorageHandler(c echo.Context) error {
 611  	ctx := c.Request().Context()
 612  	var migrateNodeStorageRequest api.MigrateNodeStorageRequest
 613  	if err := c.Bind(&migrateNodeStorageRequest); err != nil {
 614  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 615  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 616  		})
 617  	}
 618  
 619  	err := httpSvc.api.MigrateNodeStorage(ctx, migrateNodeStorageRequest.To)
 620  
 621  	if err != nil {
 622  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 623  			Message: err.Error(),
 624  		})
 625  	}
 626  
 627  	return c.NoContent(http.StatusNoContent)
 628  }
 629  
 630  func (httpSvc *HttpService) balancesHandler(c echo.Context) error {
 631  	ctx := c.Request().Context()
 632  
 633  	balances, err := httpSvc.api.GetBalances(ctx)
 634  
 635  	if err != nil {
 636  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 637  			Message: err.Error(),
 638  		})
 639  	}
 640  
 641  	return c.JSON(http.StatusOK, balances)
 642  }
 643  
 644  func (httpSvc *HttpService) sendPaymentHandler(c echo.Context) error {
 645  	ctx := c.Request().Context()
 646  
 647  	var payInvoiceRequest api.PayInvoiceRequest
 648  	if err := c.Bind(&payInvoiceRequest); err != nil {
 649  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 650  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 651  		})
 652  	}
 653  	amountMsat := api.ResolveToMsat(payInvoiceRequest.AmountSat, payInvoiceRequest.AmountMsat, nil, payInvoiceRequest.Amount)
 654  
 655  	paymentResponse, err := httpSvc.api.SendPayment(ctx, c.Param("invoice"), amountMsat, payInvoiceRequest.Metadata, payInvoiceRequest.FromAppID)
 656  
 657  	if err != nil {
 658  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 659  			Message: err.Error(),
 660  		})
 661  	}
 662  
 663  	return c.JSON(http.StatusOK, paymentResponse)
 664  }
 665  
 666  func (httpSvc *HttpService) makeOfferHandler(c echo.Context) error {
 667  	ctx := c.Request().Context()
 668  
 669  	var makeOfferRequest api.MakeOfferRequest
 670  	if err := c.Bind(&makeOfferRequest); err != nil {
 671  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 672  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 673  		})
 674  	}
 675  
 676  	offer, err := httpSvc.api.MakeOffer(ctx, makeOfferRequest.Description)
 677  
 678  	if err != nil {
 679  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 680  			Message: fmt.Sprintf("Failed to generate BOLT-12 offer: %s", err.Error()),
 681  		})
 682  	}
 683  
 684  	return c.JSON(http.StatusOK, offer)
 685  }
 686  
 687  func (httpSvc *HttpService) makeInvoiceHandler(c echo.Context) error {
 688  	var makeInvoiceRequest api.MakeInvoiceRequest
 689  	if err := c.Bind(&makeInvoiceRequest); err != nil {
 690  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 691  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 692  		})
 693  	}
 694  
 695  	amountMsat := uint64(0)
 696  	resolvedAmountMsat := api.ResolveToMsat(makeInvoiceRequest.AmountSat, makeInvoiceRequest.AmountMsat, nil, makeInvoiceRequest.Amount)
 697  	if resolvedAmountMsat != nil {
 698  		amountMsat = *resolvedAmountMsat
 699  	}
 700  
 701  	invoice, err := httpSvc.api.CreateInvoice(c.Request().Context(), amountMsat, makeInvoiceRequest.Description, makeInvoiceRequest.ToAppID)
 702  
 703  	if err != nil {
 704  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 705  			Message: err.Error(),
 706  		})
 707  	}
 708  
 709  	return c.JSON(http.StatusOK, invoice)
 710  }
 711  
 712  func (httpSvc *HttpService) lookupTransactionHandler(c echo.Context) error {
 713  	ctx := c.Request().Context()
 714  
 715  	transaction, err := httpSvc.api.LookupInvoice(ctx, c.Param("paymentHash"))
 716  
 717  	if err != nil {
 718  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 719  			Message: err.Error(),
 720  		})
 721  	}
 722  
 723  	return c.JSON(http.StatusOK, transaction)
 724  }
 725  
 726  func (httpSvc *HttpService) setTransactionUserLabelsHandler(c echo.Context) error {
 727  	ctx := c.Request().Context()
 728  
 729  	var requestData api.SetTransactionUserLabelsRequest
 730  	if err := c.Bind(&requestData); err != nil {
 731  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 732  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 733  		})
 734  	}
 735  
 736  	transactionID, err := strconv.ParseUint(c.Param("id"), 10, 64)
 737  	if err != nil {
 738  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 739  			Message: "Invalid transaction ID",
 740  		})
 741  	}
 742  
 743  	err = httpSvc.api.SetTransactionUserLabels(ctx, uint(transactionID), requestData.Labels)
 744  	if err != nil {
 745  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 746  			Message: err.Error(),
 747  		})
 748  	}
 749  
 750  	return c.NoContent(http.StatusNoContent)
 751  }
 752  
 753  func (httpSvc *HttpService) listTransactionsHandler(c echo.Context) error {
 754  	ctx := c.Request().Context()
 755  
 756  	limit := uint64(20)
 757  	offset := uint64(0)
 758  	var appId *uint
 759  
 760  	if limitParam := c.QueryParam("limit"); limitParam != "" {
 761  		if parsedLimit, err := strconv.ParseUint(limitParam, 10, 64); err == nil {
 762  			limit = parsedLimit
 763  		}
 764  	}
 765  
 766  	if offsetParam := c.QueryParam("offset"); offsetParam != "" {
 767  		if parsedOffset, err := strconv.ParseUint(offsetParam, 10, 64); err == nil {
 768  			offset = parsedOffset
 769  		}
 770  	}
 771  
 772  	if appIdParam := c.QueryParam("appId"); appIdParam != "" {
 773  		if parsedAppId, err := strconv.ParseUint(appIdParam, 10, 64); err == nil {
 774  			var unsignedAppId = uint(parsedAppId)
 775  			appId = &unsignedAppId
 776  		}
 777  	}
 778  
 779  	filters, err := api.ParseListTransactionsFilters(c.QueryParams())
 780  	if err != nil {
 781  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 782  			Message: err.Error(),
 783  		})
 784  	}
 785  
 786  	transactions, err := httpSvc.api.ListTransactions(ctx, appId, limit, offset, filters)
 787  
 788  	if err != nil {
 789  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 790  			Message: err.Error(),
 791  		})
 792  	}
 793  
 794  	return c.JSON(http.StatusOK, transactions)
 795  }
 796  
 797  func (httpSvc *HttpService) listOnchainTransactionsHandler(c echo.Context) error {
 798  	ctx := c.Request().Context()
 799  
 800  	transactions, err := httpSvc.api.ListOnchainTransactions(ctx)
 801  
 802  	if err != nil {
 803  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 804  			Message: err.Error(),
 805  		})
 806  	}
 807  
 808  	return c.JSON(http.StatusOK, transactions)
 809  }
 810  
 811  func (httpSvc *HttpService) walletSyncHandler(c echo.Context) error {
 812  	httpSvc.api.SyncWallet()
 813  
 814  	return c.NoContent(http.StatusNoContent)
 815  }
 816  
 817  func (httpSvc *HttpService) mempoolApiHandler(c echo.Context) error {
 818  	endpoint := c.QueryParam("endpoint")
 819  	if endpoint == "" {
 820  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 821  			Message: "Invalid pubkey parameter",
 822  		})
 823  	}
 824  
 825  	response, err := httpSvc.api.RequestMempoolApi(c.Request().Context(), endpoint)
 826  	if err != nil {
 827  		logger.Logger.WithField("endpoint", endpoint).WithError(err).Error("Failed to request mempool API")
 828  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 829  			Message: fmt.Sprintf("Failed to request mempool API: %s", err.Error()),
 830  		})
 831  	}
 832  
 833  	return c.JSON(http.StatusOK, response)
 834  }
 835  
 836  func (httpSvc *HttpService) capabilitiesHandler(c echo.Context) error {
 837  	response, err := httpSvc.api.GetWalletCapabilities(c.Request().Context())
 838  	if err != nil {
 839  		logger.Logger.WithError(err).Error("Failed to request wallet capabilities")
 840  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 841  			Message: fmt.Sprintf("Failed to request wallet capabilities: %s", err.Error()),
 842  		})
 843  	}
 844  
 845  	return c.JSON(http.StatusOK, response)
 846  }
 847  
 848  func (httpSvc *HttpService) listPeers(c echo.Context) error {
 849  	peers, err := httpSvc.api.ListPeers(c.Request().Context())
 850  	if err != nil {
 851  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 852  			Message: fmt.Sprintf("Failed to list peers: %s", err.Error()),
 853  		})
 854  	}
 855  
 856  	return c.JSON(http.StatusOK, peers)
 857  }
 858  
 859  func (httpSvc *HttpService) connectPeerHandler(c echo.Context) error {
 860  	ctx := c.Request().Context()
 861  
 862  	var connectPeerRequest api.ConnectPeerRequest
 863  	if err := c.Bind(&connectPeerRequest); err != nil {
 864  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 865  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 866  		})
 867  	}
 868  
 869  	err := httpSvc.api.ConnectPeer(ctx, &connectPeerRequest)
 870  
 871  	if err != nil {
 872  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 873  			Message: fmt.Sprintf("Failed to connect peer: %s", err.Error()),
 874  		})
 875  	}
 876  
 877  	return c.NoContent(http.StatusNoContent)
 878  }
 879  
 880  func (httpSvc *HttpService) openChannelHandler(c echo.Context) error {
 881  	ctx := c.Request().Context()
 882  
 883  	var openChannelRequest api.OpenChannelRequest
 884  	if err := c.Bind(&openChannelRequest); err != nil {
 885  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 886  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 887  		})
 888  	}
 889  
 890  	openChannelResponse, err := httpSvc.api.OpenChannel(ctx, &openChannelRequest)
 891  
 892  	if err != nil {
 893  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 894  			Message: fmt.Sprintf("Failed to open channel: %s", err.Error()),
 895  		})
 896  	}
 897  
 898  	return c.JSON(http.StatusOK, openChannelResponse)
 899  }
 900  
 901  func (httpSvc *HttpService) rebalanceChannelHandler(c echo.Context) error {
 902  	ctx := c.Request().Context()
 903  
 904  	var rebalanceChannelRequest api.RebalanceChannelRequest
 905  	if err := c.Bind(&rebalanceChannelRequest); err != nil {
 906  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 907  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 908  		})
 909  	}
 910  
 911  	rebalanceChannelResponse, err := httpSvc.api.RebalanceChannel(ctx, &rebalanceChannelRequest)
 912  
 913  	if err != nil {
 914  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 915  			Message: fmt.Sprintf("Failed to rebalance channel: %s", err.Error()),
 916  		})
 917  	}
 918  
 919  	return c.JSON(http.StatusOK, rebalanceChannelResponse)
 920  }
 921  
 922  func (httpSvc *HttpService) disconnectPeerHandler(c echo.Context) error {
 923  	ctx := c.Request().Context()
 924  
 925  	err := httpSvc.api.DisconnectPeer(ctx, c.Param("peerId"))
 926  
 927  	if err != nil {
 928  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 929  			Message: fmt.Sprintf("Failed to disconnect peer: %s", err.Error()),
 930  		})
 931  	}
 932  
 933  	return c.NoContent(http.StatusNoContent)
 934  }
 935  
 936  func (httpSvc *HttpService) closeChannelHandler(c echo.Context) error {
 937  	ctx := c.Request().Context()
 938  
 939  	closeChannelResponse, err := httpSvc.api.CloseChannel(ctx, c.Param("peerId"), c.Param("channelId"), c.QueryParam("force") == "true")
 940  
 941  	if err != nil {
 942  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 943  			Message: fmt.Sprintf("Failed to close channel: %s", err.Error()),
 944  		})
 945  	}
 946  
 947  	return c.JSON(http.StatusOK, closeChannelResponse)
 948  }
 949  
 950  func (httpSvc *HttpService) updateChannelHandler(c echo.Context) error {
 951  	ctx := c.Request().Context()
 952  
 953  	var updateChannelRequest api.UpdateChannelRequest
 954  	if err := c.Bind(&updateChannelRequest); err != nil {
 955  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 956  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 957  		})
 958  	}
 959  
 960  	updateChannelRequest.NodeId = c.Param("peerId")
 961  	updateChannelRequest.ChannelId = c.Param("channelId")
 962  
 963  	err := httpSvc.api.UpdateChannel(ctx, &updateChannelRequest)
 964  
 965  	if err != nil {
 966  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 967  			Message: fmt.Sprintf("Failed to update channel: %s", err.Error()),
 968  		})
 969  	}
 970  
 971  	return c.NoContent(http.StatusNoContent)
 972  }
 973  
 974  func (httpSvc *HttpService) newInstantChannelInvoiceHandler(c echo.Context) error {
 975  	ctx := c.Request().Context()
 976  
 977  	var newWrappedInvoiceRequest api.LSPOrderRequest
 978  	if err := c.Bind(&newWrappedInvoiceRequest); err != nil {
 979  		return c.JSON(http.StatusBadRequest, ErrorResponse{
 980  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
 981  		})
 982  	}
 983  
 984  	newLSPOrderResponse, err := httpSvc.api.RequestLSPOrder(ctx, &newWrappedInvoiceRequest)
 985  
 986  	if err != nil {
 987  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
 988  			Message: fmt.Sprintf("Failed to request new channel order from LSP: %s", err.Error()),
 989  		})
 990  	}
 991  
 992  	return c.JSON(http.StatusOK, newLSPOrderResponse)
 993  }
 994  
 995  func (httpSvc *HttpService) onchainAddressHandler(c echo.Context) error {
 996  	ctx := c.Request().Context()
 997  
 998  	address, err := httpSvc.api.GetUnusedOnchainAddress(ctx)
 999  
1000  	if err != nil {
1001  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1002  			Message: fmt.Sprintf("Failed to request new onchain address: %s", err.Error()),
1003  		})
1004  	}
1005  
1006  	return c.JSON(http.StatusOK, address)
1007  }
1008  
1009  func (httpSvc *HttpService) newOnchainAddressHandler(c echo.Context) error {
1010  	ctx := c.Request().Context()
1011  
1012  	address, err := httpSvc.api.GetNewOnchainAddress(ctx)
1013  
1014  	if err != nil {
1015  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1016  			Message: fmt.Sprintf("Failed to request new onchain address: %s", err.Error()),
1017  		})
1018  	}
1019  
1020  	return c.JSON(http.StatusOK, address)
1021  }
1022  
1023  func (httpSvc *HttpService) redeemOnchainFundsHandler(c echo.Context) error {
1024  	ctx := c.Request().Context()
1025  
1026  	var redeemOnchainFundsRequest api.RedeemOnchainFundsRequest
1027  	if err := c.Bind(&redeemOnchainFundsRequest); err != nil {
1028  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1029  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1030  		})
1031  	}
1032  
1033  	amountSat := uint64(0)
1034  	resolvedAmountSat := api.ResolveToSat(redeemOnchainFundsRequest.AmountSat, nil, redeemOnchainFundsRequest.Amount, nil)
1035  	if resolvedAmountSat != nil {
1036  		amountSat = *resolvedAmountSat
1037  	}
1038  
1039  	redeemOnchainFundsResponse, err := httpSvc.api.RedeemOnchainFunds(ctx, redeemOnchainFundsRequest.ToAddress, amountSat, redeemOnchainFundsRequest.FeeRate, redeemOnchainFundsRequest.SendAll)
1040  
1041  	if err != nil {
1042  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1043  			Message: fmt.Sprintf("Failed to redeem onchain funds: %s", err.Error()),
1044  		})
1045  	}
1046  
1047  	return c.JSON(http.StatusOK, redeemOnchainFundsResponse)
1048  }
1049  
1050  func (httpSvc *HttpService) signMessageHandler(c echo.Context) error {
1051  	ctx := c.Request().Context()
1052  
1053  	var signMessageRequest api.SignMessageRequest
1054  	if err := c.Bind(&signMessageRequest); err != nil {
1055  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1056  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1057  		})
1058  	}
1059  
1060  	signMessageResponse, err := httpSvc.api.SignMessage(ctx, signMessageRequest.Message)
1061  
1062  	if err != nil {
1063  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1064  			Message: fmt.Sprintf("Failed to sign message: %s", err.Error()),
1065  		})
1066  	}
1067  	return c.JSON(http.StatusOK, signMessageResponse)
1068  }
1069  
1070  func (httpSvc *HttpService) appsListHandler(c echo.Context) error {
1071  	limit := uint64(0)
1072  	offset := uint64(0)
1073  
1074  	if limitParam := c.QueryParam("limit"); limitParam != "" {
1075  		if parsedLimit, err := strconv.ParseUint(limitParam, 10, 64); err == nil {
1076  			limit = parsedLimit
1077  		}
1078  	}
1079  
1080  	if offsetParam := c.QueryParam("offset"); offsetParam != "" {
1081  		if parsedOffset, err := strconv.ParseUint(offsetParam, 10, 64); err == nil {
1082  			offset = parsedOffset
1083  		}
1084  	}
1085  
1086  	filtersJSON := c.QueryParam("filters")
1087  	var filters api.ListAppsFilters
1088  	if filtersJSON != "" {
1089  		err := json.Unmarshal([]byte(filtersJSON), &filters)
1090  		if err != nil {
1091  			logger.Logger.WithError(err).WithFields(logrus.Fields{
1092  				"filters": filtersJSON,
1093  			}).Error("Failed to deserialize app filters")
1094  			return err
1095  		}
1096  	}
1097  
1098  	orderBy := c.QueryParam("order_by")
1099  
1100  	apps, err := httpSvc.api.ListApps(limit, offset, filters, orderBy)
1101  
1102  	if err != nil {
1103  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1104  			Message: err.Error(),
1105  		})
1106  	}
1107  
1108  	return c.JSON(http.StatusOK, apps)
1109  }
1110  
1111  func (httpSvc *HttpService) appsShowByPubkeyHandler(c echo.Context) error {
1112  	dbApp := httpSvc.appsSvc.GetAppByPubkey(c.Param("pubkey"))
1113  
1114  	if dbApp == nil {
1115  		return c.JSON(http.StatusNotFound, ErrorResponse{
1116  			Message: "App not found",
1117  		})
1118  	}
1119  
1120  	response, err := httpSvc.api.GetApp(dbApp)
1121  	if err != nil {
1122  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1123  			Message: err.Error(),
1124  		})
1125  	}
1126  
1127  	return c.JSON(http.StatusOK, response)
1128  }
1129  
1130  func (httpSvc *HttpService) appsShowHandler(c echo.Context) error {
1131  	appIdStr := c.Param("id")
1132  	if appIdStr == "" {
1133  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1134  			Message: "App ID is required",
1135  		})
1136  	}
1137  
1138  	appId, err := strconv.ParseUint(appIdStr, 10, 64)
1139  	if err != nil {
1140  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1141  			Message: "Invalid App ID",
1142  		})
1143  	}
1144  
1145  	dbApp := httpSvc.appsSvc.GetAppById(uint(appId))
1146  
1147  	if dbApp == nil {
1148  		return c.JSON(http.StatusNotFound, ErrorResponse{
1149  			Message: "App not found",
1150  		})
1151  	}
1152  
1153  	response, err := httpSvc.api.GetApp(dbApp)
1154  	if err != nil {
1155  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1156  			Message: err.Error(),
1157  		})
1158  	}
1159  
1160  	return c.JSON(http.StatusOK, response)
1161  }
1162  
1163  func (httpSvc *HttpService) appsUpdateHandler(c echo.Context) error {
1164  	var requestData api.UpdateAppRequest
1165  	if err := c.Bind(&requestData); err != nil {
1166  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1167  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1168  		})
1169  	}
1170  
1171  	dbApp := httpSvc.appsSvc.GetAppByPubkey(c.Param("pubkey"))
1172  
1173  	if dbApp == nil {
1174  		return c.JSON(http.StatusNotFound, ErrorResponse{
1175  			Message: "App not found",
1176  		})
1177  	}
1178  
1179  	err := httpSvc.api.UpdateApp(dbApp, &requestData)
1180  
1181  	if err != nil {
1182  		logger.Logger.WithError(err).Error("Failed to update app")
1183  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1184  			Message: fmt.Sprintf("Failed to update app: %v", err),
1185  		})
1186  	}
1187  
1188  	return c.NoContent(http.StatusNoContent)
1189  }
1190  
1191  func (httpSvc *HttpService) transfersHandler(c echo.Context) error {
1192  	var requestData api.TransferRequest
1193  	if err := c.Bind(&requestData); err != nil {
1194  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1195  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1196  		})
1197  	}
1198  
1199  	amountMsat := uint64(0)
1200  	resolvedAmountMsat := api.ResolveToMsat(requestData.AmountSat, requestData.AmountMsat, nil, nil)
1201  	if resolvedAmountMsat != nil {
1202  		amountMsat = *resolvedAmountMsat
1203  	}
1204  
1205  	err := httpSvc.api.Transfer(c.Request().Context(), requestData.FromAppId, requestData.ToAppId, amountMsat, requestData.Description)
1206  
1207  	if err != nil {
1208  		logger.Logger.WithError(err).Error("Failed to transfer funds")
1209  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1210  			Message: fmt.Sprintf("Failed to transfer funds: %v", err),
1211  		})
1212  	}
1213  
1214  	return c.NoContent(http.StatusNoContent)
1215  }
1216  
1217  func (httpSvc *HttpService) appsDeleteHandler(c echo.Context) error {
1218  	dbApp := httpSvc.appsSvc.GetAppByPubkey(c.Param("pubkey"))
1219  	if dbApp == nil {
1220  		return c.JSON(http.StatusNotFound, ErrorResponse{
1221  			Message: "App not found",
1222  		})
1223  	}
1224  
1225  	if err := httpSvc.api.DeleteApp(dbApp); err != nil {
1226  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1227  			Message: "Failed to delete app",
1228  		})
1229  	}
1230  	return c.NoContent(http.StatusNoContent)
1231  }
1232  
1233  func (httpSvc *HttpService) appsCreateHandler(c echo.Context) error {
1234  	var requestData api.CreateAppRequest
1235  	if err := c.Bind(&requestData); err != nil {
1236  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1237  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1238  		})
1239  	}
1240  
1241  	responseBody, err := httpSvc.api.CreateApp(&requestData)
1242  
1243  	if err != nil {
1244  		logger.Logger.WithField("appName", requestData.Name).WithError(err).Error("Failed to save app")
1245  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1246  			Message: fmt.Sprintf("Failed to save app: %v", err),
1247  		})
1248  	}
1249  
1250  	return c.JSON(http.StatusOK, responseBody)
1251  }
1252  
1253  func (httpSvc *HttpService) lightningAddressesCreateHandler(c echo.Context) error {
1254  	var requestData api.CreateLightningAddressRequest
1255  	if err := c.Bind(&requestData); err != nil {
1256  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1257  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1258  		})
1259  	}
1260  
1261  	err := httpSvc.api.CreateLightningAddress(c.Request().Context(), &requestData)
1262  
1263  	if err != nil {
1264  		logger.Logger.WithField("request", requestData).WithError(err).Error("Failed to create lightning address")
1265  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1266  			Message: err.Error(),
1267  		})
1268  	}
1269  
1270  	return c.NoContent(http.StatusNoContent)
1271  }
1272  
1273  func (httpSvc *HttpService) lightningAddressesDeleteHandler(c echo.Context) error {
1274  	appIdStr := c.Param("appId")
1275  	if appIdStr == "" {
1276  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1277  			Message: "App ID is required",
1278  		})
1279  	}
1280  
1281  	appId, err := strconv.ParseUint(appIdStr, 10, 64)
1282  	if err != nil {
1283  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1284  			Message: "Invalid App ID",
1285  		})
1286  	}
1287  
1288  	err = httpSvc.api.DeleteLightningAddress(c.Request().Context(), uint(appId))
1289  	if err != nil {
1290  		logger.Logger.WithField("appId", appId).WithError(err).Error("Failed to delete lightning address")
1291  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1292  			Message: err.Error(),
1293  		})
1294  	}
1295  
1296  	return c.NoContent(http.StatusNoContent)
1297  }
1298  
1299  func (httpSvc *HttpService) setupHandler(c echo.Context) error {
1300  	var setupRequest api.SetupRequest
1301  	if err := c.Bind(&setupRequest); err != nil {
1302  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1303  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1304  		})
1305  	}
1306  
1307  	err := httpSvc.api.Setup(c.Request().Context(), &setupRequest)
1308  	if err != nil {
1309  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1310  			Message: fmt.Sprintf("Failed to setup node: %s", err.Error()),
1311  		})
1312  	}
1313  
1314  	return c.NoContent(http.StatusNoContent)
1315  }
1316  
1317  func (httpSvc *HttpService) getLogOutputHandler(c echo.Context) error {
1318  	var getLogRequest api.GetLogOutputRequest
1319  	if err := c.Bind(&getLogRequest); err != nil {
1320  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1321  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1322  		})
1323  	}
1324  
1325  	logType := c.Param("type")
1326  	if logType != api.LogTypeNode && logType != api.LogTypeApp {
1327  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1328  			Message: fmt.Sprintf("Invalid log type parameter: '%s'", logType),
1329  		})
1330  	}
1331  
1332  	getLogResponse, err := httpSvc.api.GetLogOutput(c.Request().Context(), logType, &getLogRequest)
1333  	if err != nil {
1334  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1335  			Message: fmt.Sprintf("Failed to get log output: %v", err),
1336  		})
1337  	}
1338  
1339  	return c.JSON(http.StatusOK, getLogResponse)
1340  }
1341  
1342  func (httpSvc *HttpService) getCustomNodeCommandsHandler(c echo.Context) error {
1343  	nodeCommandsResponse, err := httpSvc.api.GetCustomNodeCommands()
1344  	if err != nil {
1345  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1346  			Message: fmt.Sprintf("Failed to get node commands: %v", err),
1347  		})
1348  	}
1349  
1350  	return c.JSON(http.StatusOK, nodeCommandsResponse)
1351  }
1352  
1353  func (httpSvc *HttpService) execCustomNodeCommandHandler(c echo.Context) error {
1354  	var execCommandRequest api.ExecuteCustomNodeCommandRequest
1355  	if err := c.Bind(&execCommandRequest); err != nil {
1356  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1357  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1358  		})
1359  	}
1360  
1361  	execCommandResponse, err := httpSvc.api.ExecuteCustomNodeCommand(c.Request().Context(), execCommandRequest.Command)
1362  	if err != nil {
1363  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1364  			Message: fmt.Sprintf("Failed to execute command: %v", err),
1365  		})
1366  	}
1367  
1368  	return c.JSON(http.StatusOK, execCommandResponse)
1369  }
1370  
1371  func (httpSvc *HttpService) logoutHandler(c echo.Context) error {
1372  	redirectUrl := httpSvc.cfg.GetEnv().GetBaseFrontendUrl()
1373  	if redirectUrl == "" {
1374  		redirectUrl = "/"
1375  	}
1376  
1377  	return c.Redirect(http.StatusFound, redirectUrl)
1378  }
1379  
1380  func (httpSvc *HttpService) createBackupHandler(c echo.Context) error {
1381  	var backupRequest api.BasicBackupRequest
1382  	if err := c.Bind(&backupRequest); err != nil {
1383  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1384  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1385  		})
1386  	}
1387  
1388  	if !httpSvc.cfg.CheckUnlockPassword(backupRequest.UnlockPassword) {
1389  		return c.JSON(http.StatusUnauthorized, ErrorResponse{
1390  			Message: "Invalid password",
1391  		})
1392  	}
1393  
1394  	var buffer bytes.Buffer
1395  	err := httpSvc.api.CreateBackup(backupRequest.UnlockPassword, &buffer)
1396  	if err != nil {
1397  		return c.String(500, fmt.Sprintf("Failed to create backup: %v", err))
1398  	}
1399  
1400  	c.Response().Header().Set("Content-Type", "application/octet-stream")
1401  	c.Response().Header().Set("Content-Disposition", "attachment; filename=albyhub.bkp")
1402  	c.Response().WriteHeader(http.StatusOK)
1403  	c.Response().Write(buffer.Bytes())
1404  	return nil
1405  }
1406  
1407  func (httpSvc *HttpService) restoreBackupHandler(c echo.Context) error {
1408  	info, err := httpSvc.api.GetInfo(c.Request().Context())
1409  	if err != nil {
1410  		return err
1411  	}
1412  	if info.SetupCompleted {
1413  		return errors.New("setup already completed")
1414  	}
1415  
1416  	password := c.FormValue("unlockPassword")
1417  
1418  	fileHeader, err := c.FormFile("backup")
1419  	if err != nil {
1420  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1421  			Message: fmt.Sprintf("Failed to get backup file header: %v", err),
1422  		})
1423  	}
1424  
1425  	file, err := fileHeader.Open()
1426  	if err != nil {
1427  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1428  			Message: fmt.Sprintf("Failed to open backup file: %v", err),
1429  		})
1430  	}
1431  	defer file.Close()
1432  
1433  	err = httpSvc.api.RestoreBackup(password, file)
1434  	if err != nil {
1435  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1436  			Message: fmt.Sprintf("Failed to restore backup: %v", err),
1437  		})
1438  	}
1439  
1440  	return c.NoContent(http.StatusNoContent)
1441  }
1442  
1443  func (httpSvc *HttpService) healthHandler(c echo.Context) error {
1444  	healthResponse, err := httpSvc.api.Health(c.Request().Context())
1445  	if err != nil {
1446  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1447  			Message: fmt.Sprintf("Failed to check node health: %v", err),
1448  		})
1449  	}
1450  
1451  	return c.JSON(http.StatusOK, healthResponse)
1452  }
1453  
1454  func (httpSvc *HttpService) listSwapsHandler(c echo.Context) error {
1455  	swaps, err := httpSvc.api.ListSwaps()
1456  
1457  	if err != nil {
1458  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1459  			Message: err.Error(),
1460  		})
1461  	}
1462  
1463  	return c.JSON(http.StatusOK, swaps)
1464  }
1465  
1466  func (httpSvc *HttpService) lookupSwapHandler(c echo.Context) error {
1467  	swap, err := httpSvc.api.LookupSwap(c.Param("swapId"))
1468  	if err != nil {
1469  		return c.JSON(http.StatusNotFound, ErrorResponse{
1470  			Message: "App not found",
1471  		})
1472  	}
1473  
1474  	return c.JSON(http.StatusOK, swap)
1475  }
1476  
1477  func (httpSvc *HttpService) getSwapOutInfoHandler(c echo.Context) error {
1478  	swapOutFeesResponse, err := httpSvc.api.GetSwapOutInfo()
1479  	if err != nil {
1480  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1481  			Message: fmt.Sprintf("Failed to get swap out info: %v", err),
1482  		})
1483  	}
1484  
1485  	return c.JSON(http.StatusOK, swapOutFeesResponse)
1486  }
1487  
1488  func (httpSvc *HttpService) getSwapInInfoHandler(c echo.Context) error {
1489  	swapOutFeesResponse, err := httpSvc.api.GetSwapInInfo()
1490  	if err != nil {
1491  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1492  			Message: fmt.Sprintf("Failed to get swap in info: %v", err),
1493  		})
1494  	}
1495  
1496  	return c.JSON(http.StatusOK, swapOutFeesResponse)
1497  }
1498  
1499  func (httpSvc *HttpService) initiateSwapOutHandler(c echo.Context) error {
1500  	var initiateSwapOutRequest api.InitiateSwapRequest
1501  	if err := c.Bind(&initiateSwapOutRequest); err != nil {
1502  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1503  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1504  		})
1505  	}
1506  
1507  	swapOutResponse, err := httpSvc.api.InitiateSwapOut(c.Request().Context(), &initiateSwapOutRequest)
1508  	if err != nil {
1509  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1510  			Message: fmt.Sprintf("Failed to initiate swap out: %v", err),
1511  		})
1512  	}
1513  
1514  	return c.JSON(http.StatusOK, swapOutResponse)
1515  }
1516  
1517  func (httpSvc *HttpService) initiateSwapInHandler(c echo.Context) error {
1518  	var initiateSwapInRequest api.InitiateSwapRequest
1519  	if err := c.Bind(&initiateSwapInRequest); err != nil {
1520  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1521  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1522  		})
1523  	}
1524  
1525  	txId, err := httpSvc.api.InitiateSwapIn(c.Request().Context(), &initiateSwapInRequest)
1526  	if err != nil {
1527  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1528  			Message: fmt.Sprintf("Failed to initiate swap in: %v", err),
1529  		})
1530  	}
1531  
1532  	return c.JSON(http.StatusOK, txId)
1533  }
1534  
1535  func (httpSvc *HttpService) refundSwapHandler(c echo.Context) error {
1536  	var refundSwapInRequest api.RefundSwapRequest
1537  	if err := c.Bind(&refundSwapInRequest); err != nil {
1538  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1539  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1540  		})
1541  	}
1542  
1543  	err := httpSvc.api.RefundSwap(&refundSwapInRequest)
1544  	if err != nil {
1545  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1546  			Message: err.Error(),
1547  		})
1548  	}
1549  
1550  	return c.NoContent(http.StatusNoContent)
1551  }
1552  
1553  func (httpSvc *HttpService) swapMnemonicHandler(c echo.Context) error {
1554  	mnemonic := httpSvc.api.GetSwapMnemonic()
1555  	return c.JSON(http.StatusOK, mnemonic)
1556  }
1557  
1558  func (httpSvc *HttpService) getAutoSwapConfigHandler(c echo.Context) error {
1559  	getAutoSwapConfigResponse, err := httpSvc.api.GetAutoSwapConfig()
1560  	if err != nil {
1561  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1562  			Message: fmt.Sprintf("Failed to get swap settings: %v", err),
1563  		})
1564  	}
1565  
1566  	return c.JSON(http.StatusOK, getAutoSwapConfigResponse)
1567  }
1568  
1569  func (httpSvc *HttpService) enableAutoSwapOutHandler(c echo.Context) error {
1570  	var enableAutoSwapRequest api.EnableAutoSwapRequest
1571  	if err := c.Bind(&enableAutoSwapRequest); err != nil {
1572  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1573  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1574  		})
1575  	}
1576  
1577  	err := httpSvc.api.EnableAutoSwapOut(c.Request().Context(), &enableAutoSwapRequest)
1578  	if err != nil {
1579  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1580  			Message: fmt.Sprintf("Failed to save swap settings: %v", err),
1581  		})
1582  	}
1583  
1584  	return c.NoContent(http.StatusNoContent)
1585  }
1586  
1587  func (httpSvc *HttpService) disableAutoSwapOutHandler(c echo.Context) error {
1588  	err := httpSvc.api.DisableAutoSwap()
1589  
1590  	if err != nil {
1591  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1592  			Message: err.Error(),
1593  		})
1594  	}
1595  
1596  	return c.NoContent(http.StatusNoContent)
1597  }
1598  
1599  func (httpSvc *HttpService) setNodeAliasHandler(c echo.Context) error {
1600  	var setNodeAliasRequest api.SetNodeAliasRequest
1601  	if err := c.Bind(&setNodeAliasRequest); err != nil {
1602  		return c.JSON(http.StatusBadRequest, ErrorResponse{
1603  			Message: fmt.Sprintf("Bad request: %s", err.Error()),
1604  		})
1605  	}
1606  
1607  	err := httpSvc.api.SetNodeAlias(setNodeAliasRequest.NodeAlias)
1608  	if err != nil {
1609  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1610  			Message: fmt.Sprintf("Failed to set node alias: %s", err.Error()),
1611  		})
1612  	}
1613  
1614  	return c.NoContent(http.StatusNoContent)
1615  }
1616  
1617  func (httpSvc *HttpService) forwardsHandler(c echo.Context) error {
1618  	forwards, err := httpSvc.api.GetForwards()
1619  	if err != nil {
1620  		return c.JSON(http.StatusInternalServerError, ErrorResponse{
1621  			Message: fmt.Sprintf("Failed to get forwards: %s", err.Error()),
1622  		})
1623  	}
1624  
1625  	return c.JSON(http.StatusOK, forwards)
1626  }
1627