wails_handlers.go raw

   1  package wails
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"net/url"
   7  	"os"
   8  	"regexp"
   9  	"strconv"
  10  	"strings"
  11  
  12  	"github.com/sirupsen/logrus"
  13  
  14  	"github.com/wailsapp/wails/v2/pkg/runtime"
  15  
  16  	"github.com/getAlby/hub/alby"
  17  	"github.com/getAlby/hub/api"
  18  	"github.com/getAlby/hub/logger"
  19  )
  20  
  21  type WailsRequestRouterResponse struct {
  22  	Body  interface{} `json:"body"`
  23  	Error string      `json:"error"`
  24  }
  25  
  26  // TODO: make this match echo
  27  func (app *WailsApp) WailsRequestRouter(route string, method string, body string) WailsRequestRouterResponse {
  28  	ctx := app.ctx
  29  
  30  	// the grouping is done to avoid other parameters like &unused=true
  31  	albyCallbackRegex := regexp.MustCompile(
  32  		`/api/alby/callback\?code=([^&]+)(&.*)?`,
  33  	)
  34  
  35  	authCodeMatch := albyCallbackRegex.FindStringSubmatch(route)
  36  
  37  	switch {
  38  	case len(authCodeMatch) > 1:
  39  		code := authCodeMatch[1]
  40  
  41  		err := app.svc.GetAlbyOAuthSvc().CallbackHandler(ctx, code)
  42  		if err != nil {
  43  			logger.Logger.WithFields(logrus.Fields{
  44  				"route":  route,
  45  				"method": method,
  46  			}).WithError(err).Error("Failed to decode request to wails router")
  47  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
  48  		}
  49  		return WailsRequestRouterResponse{Body: nil, Error: ""}
  50  	}
  51  
  52  	appv2Regex := regexp.MustCompile(
  53  		`/api/v2/apps/([0-9a-f]+)`,
  54  	)
  55  
  56  	appv2Match := appv2Regex.FindStringSubmatch(route)
  57  
  58  	switch {
  59  	case len(appv2Match) > 1:
  60  		appIdStr := appv2Match[1]
  61  
  62  		appId, err := strconv.ParseUint(appIdStr, 10, 64)
  63  		if err != nil {
  64  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
  65  		}
  66  
  67  		dbApp := app.appsSvc.GetAppById(uint(appId))
  68  		if dbApp == nil {
  69  			return WailsRequestRouterResponse{Body: nil, Error: "App does not exist"}
  70  		}
  71  
  72  		switch method {
  73  		case "GET":
  74  			app, err := app.api.GetApp(dbApp)
  75  			if err != nil {
  76  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
  77  			}
  78  			return WailsRequestRouterResponse{Body: app, Error: ""}
  79  		}
  80  	}
  81  
  82  	appRegex := regexp.MustCompile(
  83  		`/api/apps/([0-9a-f]+)`,
  84  	)
  85  
  86  	appMatch := appRegex.FindStringSubmatch(route)
  87  
  88  	switch {
  89  	case len(appMatch) > 1:
  90  		pubkey := appMatch[1]
  91  		dbApp := app.appsSvc.GetAppByPubkey(pubkey)
  92  		if dbApp == nil {
  93  			return WailsRequestRouterResponse{Body: nil, Error: "App does not exist"}
  94  		}
  95  
  96  		switch method {
  97  		case "GET":
  98  			app, err := app.api.GetApp(dbApp)
  99  			if err != nil {
 100  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 101  			}
 102  			return WailsRequestRouterResponse{Body: app, Error: ""}
 103  		case "PATCH":
 104  			updateAppRequest := &api.UpdateAppRequest{}
 105  			err := json.Unmarshal([]byte(body), updateAppRequest)
 106  			if err != nil {
 107  				logger.Logger.WithFields(logrus.Fields{
 108  					"route":  route,
 109  					"method": method,
 110  				}).WithError(err).Error("Failed to decode request to wails router")
 111  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 112  			}
 113  			err = app.api.UpdateApp(dbApp, updateAppRequest)
 114  			if err != nil {
 115  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 116  			}
 117  			return WailsRequestRouterResponse{Body: nil, Error: ""}
 118  		case "DELETE":
 119  			err := app.api.DeleteApp(dbApp)
 120  			if err != nil {
 121  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 122  			}
 123  			return WailsRequestRouterResponse{Body: nil, Error: ""}
 124  		}
 125  	}
 126  
 127  	// list apps
 128  	if strings.HasPrefix(route, "/api/apps") && method == "GET" {
 129  		limit := uint64(0)
 130  		offset := uint64(0)
 131  		var filtersJSON string
 132  		var orderBy string
 133  
 134  		// Extract limit and offset parameters
 135  		paramRegex := regexp.MustCompile(`[?&](limit|offset|filters|order_by)=([^&]+)`)
 136  		paramMatches := paramRegex.FindAllStringSubmatch(route, -1)
 137  		for _, match := range paramMatches {
 138  			switch match[1] {
 139  			case "limit":
 140  				if parsedLimit, err := strconv.ParseUint(match[2], 10, 64); err == nil {
 141  					limit = parsedLimit
 142  				}
 143  			case "offset":
 144  				if parsedOffset, err := strconv.ParseUint(match[2], 10, 64); err == nil {
 145  					offset = parsedOffset
 146  				}
 147  			case "filters":
 148  				filtersJSON = match[2]
 149  			case "order_by":
 150  				orderBy = match[2]
 151  			}
 152  		}
 153  
 154  		var filters api.ListAppsFilters
 155  		if filtersJSON != "" {
 156  			err := json.Unmarshal([]byte(filtersJSON), &filters)
 157  			if err != nil {
 158  				logger.Logger.WithError(err).WithFields(logrus.Fields{
 159  					"filters": filtersJSON,
 160  				}).Error("Failed to deserialize app filters")
 161  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 162  			}
 163  		}
 164  
 165  		apps, err := app.api.ListApps(limit, offset, filters, orderBy)
 166  		if err != nil {
 167  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 168  		}
 169  		return WailsRequestRouterResponse{Body: apps, Error: ""}
 170  	}
 171  
 172  	peerChannelRegex := regexp.MustCompile(
 173  		`/api/peers/([^/]+)/channels/([^/]+)`,
 174  	)
 175  
 176  	peerChannelMatch := peerChannelRegex.FindStringSubmatch(route)
 177  
 178  	switch {
 179  	case len(peerChannelMatch) == 3:
 180  		peerId := peerChannelMatch[1]
 181  		channelId := peerChannelMatch[2]
 182  		switch method {
 183  		case "PATCH":
 184  			updateChannelRequest := &api.UpdateChannelRequest{}
 185  			err := json.Unmarshal([]byte(body), updateChannelRequest)
 186  			if err != nil {
 187  				logger.Logger.WithFields(logrus.Fields{
 188  					"route":  route,
 189  					"method": method,
 190  				}).WithError(err).Error("Failed to decode request to wails router")
 191  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 192  			}
 193  			updateChannelRequest.ChannelId = channelId
 194  			updateChannelRequest.NodeId = peerId
 195  
 196  			err = app.api.UpdateChannel(ctx, updateChannelRequest)
 197  			if err != nil {
 198  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 199  			}
 200  			return WailsRequestRouterResponse{Body: nil, Error: ""}
 201  		case "DELETE":
 202  			channelIdParts := strings.SplitN(channelId, "?", 2)
 203  
 204  			var force bool
 205  			if len(channelIdParts) == 2 {
 206  				channelId = channelIdParts[0]
 207  				queryParams := channelIdParts[1]
 208  				force = strings.Contains(queryParams, "force=true")
 209  			}
 210  
 211  			closeChannelResponse, err := app.api.CloseChannel(ctx, peerId, channelId, force)
 212  			if err != nil {
 213  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 214  			}
 215  			return WailsRequestRouterResponse{Body: closeChannelResponse, Error: ""}
 216  		}
 217  	}
 218  
 219  	peerRegex := regexp.MustCompile(
 220  		`/api/peers/([^/]+)`,
 221  	)
 222  
 223  	peerMatch := peerRegex.FindStringSubmatch(route)
 224  
 225  	switch {
 226  	case len(peerMatch) == 2:
 227  		peerId := peerMatch[1]
 228  		switch method {
 229  		case "DELETE":
 230  			err := app.api.DisconnectPeer(ctx, peerId)
 231  			if err != nil {
 232  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 233  			}
 234  			return WailsRequestRouterResponse{Body: nil, Error: ""}
 235  		}
 236  	}
 237  
 238  	networkGraphRegex := regexp.MustCompile(
 239  		`/api/node/network-graph\?nodeIds=(.+)`,
 240  	)
 241  
 242  	networkGraphMatch := networkGraphRegex.FindStringSubmatch(route)
 243  
 244  	switch {
 245  	case len(networkGraphMatch) == 2:
 246  		nodeIds := networkGraphMatch[1]
 247  		networkGraphResponse, err := app.api.GetNetworkGraph(ctx, strings.Split(nodeIds, ","))
 248  		if err != nil {
 249  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 250  		}
 251  		return WailsRequestRouterResponse{Body: networkGraphResponse, Error: ""}
 252  	}
 253  
 254  	mempoolApiRegex := regexp.MustCompile(
 255  		`/api/mempool\?endpoint=(.+)`,
 256  	)
 257  	mempoolApiEndpointMatch := mempoolApiRegex.FindStringSubmatch(route)
 258  
 259  	switch {
 260  	case len(mempoolApiEndpointMatch) > 1:
 261  		endpoint := mempoolApiEndpointMatch[1]
 262  		node, err := app.api.RequestMempoolApi(ctx, endpoint)
 263  		if err != nil {
 264  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 265  		}
 266  
 267  		return WailsRequestRouterResponse{Body: node, Error: ""}
 268  	}
 269  
 270  	transactionLabelRegex := regexp.MustCompile(
 271  		`/api/transactions/([0-9]+)/labels`,
 272  	)
 273  	transactionLabelMatch := transactionLabelRegex.FindStringSubmatch(route)
 274  
 275  	switch {
 276  	case len(transactionLabelMatch) > 1:
 277  		transactionID, err := strconv.ParseUint(transactionLabelMatch[1], 10, 64)
 278  		if err != nil {
 279  			return WailsRequestRouterResponse{Body: nil, Error: "Invalid transaction ID"}
 280  		}
 281  		switch method {
 282  		case "PATCH":
 283  			labelsRequest := &api.SetTransactionUserLabelsRequest{}
 284  			err = json.Unmarshal([]byte(body), labelsRequest)
 285  			if err != nil {
 286  				logger.Logger.WithFields(logrus.Fields{
 287  					"route":  route,
 288  					"method": method,
 289  				}).WithError(err).Error("Failed to decode request to wails router")
 290  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 291  			}
 292  			err = app.api.SetTransactionUserLabels(ctx, uint(transactionID), labelsRequest.Labels)
 293  			if err != nil {
 294  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 295  			}
 296  			return WailsRequestRouterResponse{Body: nil, Error: ""}
 297  		}
 298  	}
 299  
 300  	transactionRegex := regexp.MustCompile(
 301  		`/api/transactions/([0-9a-fA-F]+)`,
 302  	)
 303  	paymentHashMatch := transactionRegex.FindStringSubmatch(route)
 304  
 305  	switch {
 306  	case len(paymentHashMatch) > 1:
 307  		paymentHash := paymentHashMatch[1]
 308  		paymentInfo, err := app.api.LookupInvoice(ctx, paymentHash)
 309  		if err != nil {
 310  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 311  		}
 312  
 313  		return WailsRequestRouterResponse{Body: paymentInfo, Error: ""}
 314  	}
 315  
 316  	listTransactionsRegex := regexp.MustCompile(
 317  		`/api/transactions`,
 318  	)
 319  
 320  	switch {
 321  	case listTransactionsRegex.MatchString(route):
 322  		limit := uint64(20)
 323  		offset := uint64(0)
 324  		var appId *uint
 325  
 326  		parsedUrl, err := url.Parse(route)
 327  		if err != nil {
 328  			return WailsRequestRouterResponse{Body: nil, Error: "invalid route"}
 329  		}
 330  		query := parsedUrl.Query()
 331  
 332  		if limitParam := query.Get("limit"); limitParam != "" {
 333  			if parsedLimit, err := strconv.ParseUint(limitParam, 10, 64); err == nil {
 334  				limit = parsedLimit
 335  			}
 336  		}
 337  
 338  		if offsetParam := query.Get("offset"); offsetParam != "" {
 339  			if parsedOffset, err := strconv.ParseUint(offsetParam, 10, 64); err == nil {
 340  				offset = parsedOffset
 341  			}
 342  		}
 343  
 344  		if appIdParam := query.Get("appId"); appIdParam != "" {
 345  			if parsedAppId, err := strconv.ParseUint(appIdParam, 10, 64); err == nil {
 346  				var unsignedAppId = uint(parsedAppId)
 347  				appId = &unsignedAppId
 348  			}
 349  		}
 350  
 351  		filters, err := api.ParseListTransactionsFilters(query)
 352  		if err != nil {
 353  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 354  		}
 355  
 356  		transactions, err := app.api.ListTransactions(ctx, appId, limit, offset, filters)
 357  		if err != nil {
 358  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 359  		}
 360  		return WailsRequestRouterResponse{Body: transactions, Error: ""}
 361  	}
 362  
 363  	paymentRegex := regexp.MustCompile(
 364  		`/api/payments/([0-9a-zA-Z]+)`,
 365  	)
 366  	invoiceMatch := paymentRegex.FindStringSubmatch(route)
 367  
 368  	switch {
 369  	case len(invoiceMatch) > 1:
 370  		invoice := invoiceMatch[1]
 371  		payRequest := &api.PayInvoiceRequest{}
 372  		if body != "" {
 373  			err := json.Unmarshal([]byte(body), payRequest)
 374  			if err != nil {
 375  				logger.Logger.WithFields(logrus.Fields{
 376  					"route":  route,
 377  					"method": method,
 378  				}).WithError(err).Error("Failed to decode request to wails router")
 379  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 380  			}
 381  		}
 382  		amountMsat := api.ResolveToMsat(payRequest.AmountSat, payRequest.AmountMsat, nil, payRequest.Amount)
 383  		paymentResponse, err := app.api.SendPayment(ctx, invoice, amountMsat, payRequest.Metadata, payRequest.FromAppID)
 384  		if err != nil {
 385  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 386  		}
 387  
 388  		return WailsRequestRouterResponse{Body: paymentResponse, Error: ""}
 389  	}
 390  
 391  	albyRateRegex := regexp.MustCompile(`/api/alby/rates/([^/?]+)`)
 392  	albyRateMatch := albyRateRegex.FindStringSubmatch(route)
 393  
 394  	switch {
 395  	case len(albyRateMatch) > 1:
 396  		currency := albyRateMatch[1]
 397  		rate, err := app.svc.GetAlbySvc().GetBitcoinRate(ctx, currency)
 398  		if err != nil {
 399  			logger.Logger.WithFields(logrus.Fields{
 400  				"route":    route,
 401  				"method":   method,
 402  				"currency": currency,
 403  			}).WithError(err).Error("Failed to get Bitcoin rate")
 404  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 405  		}
 406  		return WailsRequestRouterResponse{Body: rate, Error: ""}
 407  	}
 408  
 409  	switch route {
 410  	case "/api/transfers":
 411  		transferRequest := &api.TransferRequest{}
 412  		err := json.Unmarshal([]byte(body), transferRequest)
 413  		if err != nil {
 414  			logger.Logger.WithFields(logrus.Fields{
 415  				"route":  route,
 416  				"method": method,
 417  			}).WithError(err).Error("Failed to decode request to wails router")
 418  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 419  		}
 420  		amountMsat := uint64(0)
 421  		resolvedAmountMsat := api.ResolveToMsat(transferRequest.AmountSat, transferRequest.AmountMsat, nil, nil)
 422  		if resolvedAmountMsat != nil {
 423  			amountMsat = *resolvedAmountMsat
 424  		}
 425  
 426  		err = app.api.Transfer(ctx, transferRequest.FromAppId, transferRequest.ToAppId, amountMsat, transferRequest.Description)
 427  		if err != nil {
 428  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 429  		}
 430  		return WailsRequestRouterResponse{Body: nil, Error: ""}
 431  	case "/api/alby/info":
 432  		info, err := app.svc.GetAlbySvc().GetInfo(ctx)
 433  		if err != nil {
 434  			logger.Logger.WithFields(logrus.Fields{
 435  				"route":  route,
 436  				"method": method,
 437  			}).WithError(err).Error("Failed to decode request to wails router")
 438  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 439  		}
 440  		return WailsRequestRouterResponse{Body: info, Error: ""}
 441  	case "/api/alby/me":
 442  		me, err := app.svc.GetAlbyOAuthSvc().GetMe(ctx)
 443  		if err != nil {
 444  			logger.Logger.WithFields(logrus.Fields{
 445  				"route":  route,
 446  				"method": method,
 447  			}).WithError(err).Error("Failed to decode request to wails router")
 448  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 449  		}
 450  		return WailsRequestRouterResponse{Body: me, Error: ""}
 451  	case "/api/alby/unlink-account":
 452  		err := app.svc.GetAlbyOAuthSvc().UnlinkAccount(ctx)
 453  		if err != nil {
 454  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 455  		}
 456  		return WailsRequestRouterResponse{Body: nil, Error: ""}
 457  	case "/api/alby/currencies":
 458  		currencies, err := app.svc.GetAlbySvc().GetCurrencies(ctx)
 459  		if err != nil {
 460  			logger.Logger.WithFields(logrus.Fields{
 461  				"route":  route,
 462  				"method": method,
 463  			}).WithError(err).Error("Failed to get currencies")
 464  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 465  		}
 466  		return WailsRequestRouterResponse{Body: currencies, Error: ""}
 467  	case "/api/alby/stories":
 468  		stories, err := app.api.GetStories(ctx)
 469  		if err != nil {
 470  			logger.Logger.WithFields(logrus.Fields{
 471  				"route":  route,
 472  				"method": method,
 473  			}).WithError(err).Error("Failed to get stories")
 474  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 475  		}
 476  		return WailsRequestRouterResponse{Body: stories, Error: ""}
 477  	case "/api/apps":
 478  		switch method {
 479  		case "POST":
 480  			createAppRequest := &api.CreateAppRequest{}
 481  			err := json.Unmarshal([]byte(body), createAppRequest)
 482  			if err != nil {
 483  				logger.Logger.WithFields(logrus.Fields{
 484  					"route":  route,
 485  					"method": method,
 486  				}).WithError(err).Error("Failed to decode request to wails router")
 487  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 488  			}
 489  
 490  			createAppResponse, err := app.api.CreateApp(createAppRequest)
 491  			if err != nil {
 492  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 493  			}
 494  			return WailsRequestRouterResponse{Body: createAppResponse, Error: ""}
 495  		}
 496  	case "/api/reset-router":
 497  		resetRouterRequest := &api.ResetRouterRequest{}
 498  		err := json.Unmarshal([]byte(body), resetRouterRequest)
 499  		if err != nil {
 500  			logger.Logger.WithFields(logrus.Fields{
 501  				"route":  route,
 502  				"method": method,
 503  			}).WithError(err).Error("Failed to decode request to wails router")
 504  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 505  		}
 506  
 507  		err = app.api.ResetRouter(resetRouterRequest.Key)
 508  		if err != nil {
 509  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 510  		}
 511  		res := WailsRequestRouterResponse{Body: nil, Error: ""}
 512  		return res
 513  	case "/api/stop":
 514  		err := app.api.Stop()
 515  		if err != nil {
 516  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 517  		}
 518  		res := WailsRequestRouterResponse{Body: nil, Error: ""}
 519  		return res
 520  	case "/api/channels":
 521  		switch method {
 522  		case "GET":
 523  			channels, err := app.api.ListChannels(ctx)
 524  			if err != nil {
 525  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 526  			}
 527  			res := WailsRequestRouterResponse{Body: channels, Error: ""}
 528  			return res
 529  		case "POST":
 530  			openChannelRequest := &api.OpenChannelRequest{}
 531  			err := json.Unmarshal([]byte(body), openChannelRequest)
 532  			if err != nil {
 533  				logger.Logger.WithFields(logrus.Fields{
 534  					"route":  route,
 535  					"method": method,
 536  				}).WithError(err).Error("Failed to decode request to wails router")
 537  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 538  			}
 539  			openChannelResponse, err := app.api.OpenChannel(ctx, openChannelRequest)
 540  			if err != nil {
 541  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 542  			}
 543  			return WailsRequestRouterResponse{Body: openChannelResponse, Error: ""}
 544  		}
 545  	case "/api/channel-offer":
 546  		offer, err := app.api.GetLSPChannelOffer(ctx)
 547  		if err != nil {
 548  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 549  		}
 550  		res := WailsRequestRouterResponse{Body: offer, Error: ""}
 551  		return res
 552  	case "/api/channels/suggestions":
 553  		suggestions, err := app.api.GetChannelPeerSuggestions(ctx)
 554  		if err != nil {
 555  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 556  		}
 557  		res := WailsRequestRouterResponse{Body: suggestions, Error: ""}
 558  		return res
 559  	case "/api/channels/rebalance":
 560  		rebalanceChannelRequest := &api.RebalanceChannelRequest{}
 561  		err := json.Unmarshal([]byte(body), rebalanceChannelRequest)
 562  		if err != nil {
 563  			logger.Logger.WithFields(logrus.Fields{
 564  				"route":  route,
 565  				"method": method,
 566  			}).WithError(err).Error("Failed to decode request to wails router")
 567  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 568  		}
 569  		rebalanceChannelResponse, err := app.api.RebalanceChannel(ctx, rebalanceChannelRequest)
 570  		if err != nil {
 571  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 572  		}
 573  		return WailsRequestRouterResponse{Body: rebalanceChannelResponse, Error: ""}
 574  	case "/api/balances":
 575  		balancesResponse, err := app.api.GetBalances(ctx)
 576  		if err != nil {
 577  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 578  		}
 579  		res := WailsRequestRouterResponse{Body: *balancesResponse, Error: ""}
 580  		return res
 581  	case "/api/invoices":
 582  		makeInvoiceRequest := &api.MakeInvoiceRequest{}
 583  		err := json.Unmarshal([]byte(body), makeInvoiceRequest)
 584  		if err != nil {
 585  			logger.Logger.WithFields(logrus.Fields{
 586  				"route":  route,
 587  				"method": method,
 588  			}).WithError(err).Error("Failed to decode request to wails router")
 589  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 590  		}
 591  		amountMsat := uint64(0)
 592  		resolvedAmountMsat := api.ResolveToMsat(makeInvoiceRequest.AmountSat, makeInvoiceRequest.AmountMsat, nil, makeInvoiceRequest.Amount)
 593  		if resolvedAmountMsat != nil {
 594  			amountMsat = *resolvedAmountMsat
 595  		}
 596  		invoice, err := app.api.CreateInvoice(ctx, amountMsat, makeInvoiceRequest.Description, makeInvoiceRequest.ToAppID)
 597  		if err != nil {
 598  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 599  		}
 600  		res := WailsRequestRouterResponse{Body: invoice, Error: ""}
 601  		return res
 602  	case "/api/wallet/sync":
 603  		app.api.SyncWallet()
 604  		return WailsRequestRouterResponse{Body: nil, Error: ""}
 605  	case "/api/wallet/address":
 606  		address, err := app.api.GetUnusedOnchainAddress(ctx)
 607  		if err != nil {
 608  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 609  		}
 610  		return WailsRequestRouterResponse{Body: address, Error: ""}
 611  	case "/api/wallet/new-address":
 612  		newAddress, err := app.api.GetNewOnchainAddress(ctx)
 613  		if err != nil {
 614  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 615  		}
 616  		return WailsRequestRouterResponse{Body: newAddress, Error: ""}
 617  	case "/api/wallet/redeem-onchain-funds":
 618  		redeemOnchainFundsRequest := &api.RedeemOnchainFundsRequest{}
 619  		err := json.Unmarshal([]byte(body), redeemOnchainFundsRequest)
 620  		if err != nil {
 621  			logger.Logger.WithFields(logrus.Fields{
 622  				"route":  route,
 623  				"method": method,
 624  			}).WithError(err).Error("Failed to decode request to wails router")
 625  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 626  		}
 627  
 628  		amountSat := uint64(0)
 629  		resolvedAmountSat := api.ResolveToSat(redeemOnchainFundsRequest.AmountSat, nil, redeemOnchainFundsRequest.Amount, nil)
 630  		if resolvedAmountSat != nil {
 631  			amountSat = *resolvedAmountSat
 632  		}
 633  
 634  		redeemOnchainFundsResponse, err := app.api.RedeemOnchainFunds(ctx, redeemOnchainFundsRequest.ToAddress, amountSat, redeemOnchainFundsRequest.FeeRate, redeemOnchainFundsRequest.SendAll)
 635  		if err != nil {
 636  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 637  		}
 638  		return WailsRequestRouterResponse{Body: *redeemOnchainFundsResponse, Error: ""}
 639  	case "/api/wallet/sign-message":
 640  		signMessageRequest := &api.SignMessageRequest{}
 641  		err := json.Unmarshal([]byte(body), signMessageRequest)
 642  		if err != nil {
 643  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 644  		}
 645  		signMessageResponse, err := app.api.SignMessage(ctx, signMessageRequest.Message)
 646  		if err != nil {
 647  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 648  		}
 649  		return WailsRequestRouterResponse{Body: *signMessageResponse, Error: ""}
 650  	case "/api/wallet/capabilities":
 651  		capabilitiesResponse, err := app.api.GetWalletCapabilities(ctx)
 652  		if err != nil {
 653  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 654  		}
 655  		return WailsRequestRouterResponse{Body: *capabilitiesResponse, Error: ""}
 656  	case "/api/lsp-orders":
 657  		newInstantChannelRequest := &api.LSPOrderRequest{}
 658  		err := json.Unmarshal([]byte(body), newInstantChannelRequest)
 659  		if err != nil {
 660  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 661  		}
 662  		newInstantChannelResponse, err := app.api.RequestLSPOrder(ctx, newInstantChannelRequest)
 663  		if err != nil {
 664  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 665  		}
 666  		return WailsRequestRouterResponse{Body: *newInstantChannelResponse, Error: ""}
 667  	case "/api/peers":
 668  		switch method {
 669  		case "GET":
 670  			peers, err := app.api.ListPeers(ctx)
 671  			if err != nil {
 672  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 673  			}
 674  			return WailsRequestRouterResponse{Body: peers, Error: ""}
 675  		case "POST":
 676  			connectPeerRequest := &api.ConnectPeerRequest{}
 677  			err := json.Unmarshal([]byte(body), connectPeerRequest)
 678  			if err != nil {
 679  				logger.Logger.WithFields(logrus.Fields{
 680  					"route":  route,
 681  					"method": method,
 682  				}).WithError(err).Error("Failed to decode request to wails router")
 683  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 684  			}
 685  			err = app.api.ConnectPeer(ctx, connectPeerRequest)
 686  			if err != nil {
 687  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 688  			}
 689  			return WailsRequestRouterResponse{Body: nil, Error: ""}
 690  		}
 691  	case "/api/node/connection-info":
 692  		nodeConnectionInfo, err := app.api.GetNodeConnectionInfo(ctx)
 693  		if err != nil {
 694  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 695  		}
 696  		return WailsRequestRouterResponse{Body: *nodeConnectionInfo, Error: ""}
 697  	case "/api/node/status":
 698  		nodeStatus, err := app.api.GetNodeStatus(ctx)
 699  		if err != nil {
 700  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 701  		}
 702  		if nodeStatus == nil {
 703  			return WailsRequestRouterResponse{Body: nil, Error: ""}
 704  		}
 705  		return WailsRequestRouterResponse{Body: *nodeStatus, Error: ""}
 706  	case "/api/node/transactions":
 707  		transactions, err := app.api.ListOnchainTransactions(ctx)
 708  		if err != nil {
 709  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 710  		}
 711  		return WailsRequestRouterResponse{Body: transactions, Error: ""}
 712  	case "/api/info":
 713  		infoResponse, err := app.api.GetInfo(ctx)
 714  		if err != nil {
 715  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 716  		}
 717  		infoResponse.Unlocked = infoResponse.Running
 718  		res := WailsRequestRouterResponse{Body: *infoResponse, Error: ""}
 719  		return res
 720  	case "/api/node/migrate-storage":
 721  		migrateNodeStorageRequest := &api.MigrateNodeStorageRequest{}
 722  		err := json.Unmarshal([]byte(body), migrateNodeStorageRequest)
 723  		if err != nil {
 724  			logger.Logger.WithFields(logrus.Fields{
 725  				"route":  route,
 726  				"method": method,
 727  			}).WithError(err).Error("Failed to decode request to wails router")
 728  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 729  		}
 730  		err = app.api.MigrateNodeStorage(ctx, migrateNodeStorageRequest.To)
 731  		if err != nil {
 732  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 733  		}
 734  		return WailsRequestRouterResponse{Body: nil, Error: ""}
 735  	case "/api/alby/auto-channel":
 736  		newAutoChannelRequest := &alby.AutoChannelRequest{}
 737  		err := json.Unmarshal([]byte(body), newAutoChannelRequest)
 738  		if err != nil {
 739  			logger.Logger.WithFields(logrus.Fields{
 740  				"route":  route,
 741  				"method": method,
 742  			}).WithError(err).Error("Failed to decode request to wails router")
 743  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 744  		}
 745  		autoChannelResponse, err := app.svc.GetAlbyOAuthSvc().RequestAutoChannel(ctx, app.svc.GetLNClient(), newAutoChannelRequest.IsPublic)
 746  		if err != nil {
 747  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 748  		}
 749  		return WailsRequestRouterResponse{Body: *autoChannelResponse, Error: ""}
 750  
 751  	case "/api/alby/link-account":
 752  		linkAccountRequest := &alby.AlbyLinkAccountRequest{}
 753  		err := json.Unmarshal([]byte(body), linkAccountRequest)
 754  		if err != nil {
 755  			logger.Logger.WithFields(logrus.Fields{
 756  				"route":  route,
 757  				"method": method,
 758  			}).WithError(err).Error("Failed to decode request to wails router")
 759  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 760  		}
 761  		err = app.svc.GetAlbyOAuthSvc().LinkAccount(ctx, app.svc.GetLNClient(), linkAccountRequest.Budget, linkAccountRequest.Renewal)
 762  		if err != nil {
 763  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 764  		}
 765  		res := WailsRequestRouterResponse{Error: ""}
 766  		return res
 767  	case "/api/mnemonic":
 768  		mnemonicRequest := &api.MnemonicRequest{}
 769  		err := json.Unmarshal([]byte(body), mnemonicRequest)
 770  		if err != nil {
 771  			logger.Logger.WithFields(logrus.Fields{
 772  				"route":  route,
 773  				"method": method,
 774  			}).WithError(err).Error("Failed to parse mnemonic request")
 775  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 776  		}
 777  		mnemonicResponse, err := app.api.GetMnemonic(mnemonicRequest.UnlockPassword)
 778  		if err != nil {
 779  			logger.Logger.WithFields(logrus.Fields{
 780  				"route":  route,
 781  				"method": method,
 782  			}).WithError(err).Error("Failed to get mnemonic")
 783  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 784  		}
 785  		res := WailsRequestRouterResponse{Body: *mnemonicResponse, Error: ""}
 786  		return res
 787  	case "/api/backup-reminder":
 788  		backupReminderRequest := &api.BackupReminderRequest{}
 789  		err := json.Unmarshal([]byte(body), backupReminderRequest)
 790  		if err != nil {
 791  			logger.Logger.WithFields(logrus.Fields{
 792  				"route":  route,
 793  				"method": method,
 794  			}).WithError(err).Error("Failed to decode request to wails router")
 795  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 796  		}
 797  
 798  		err = app.api.SetNextBackupReminder(backupReminderRequest)
 799  		if err != nil {
 800  			logger.Logger.WithFields(logrus.Fields{
 801  				"route":  route,
 802  				"method": method,
 803  			}).WithError(err).Error("Failed to store backup reminder")
 804  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 805  		}
 806  		return WailsRequestRouterResponse{Body: nil, Error: ""}
 807  	case "/api/unlock-password":
 808  		changeUnlockPasswordRequest := &api.ChangeUnlockPasswordRequest{}
 809  		err := json.Unmarshal([]byte(body), changeUnlockPasswordRequest)
 810  		if err != nil {
 811  			logger.Logger.WithFields(logrus.Fields{
 812  				"route":  route,
 813  				"method": method,
 814  			}).WithError(err).Error("Failed to decode request to wails router")
 815  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 816  		}
 817  
 818  		err = app.api.ChangeUnlockPassword(changeUnlockPasswordRequest)
 819  		if err != nil {
 820  			logger.Logger.WithFields(logrus.Fields{
 821  				"route":  route,
 822  				"method": method,
 823  			}).WithError(err).Error("Failed to change unlock password")
 824  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 825  		}
 826  		return WailsRequestRouterResponse{Body: nil, Error: ""}
 827  	case "/api/settings":
 828  		updateSettingsRequest := &api.UpdateSettingsRequest{}
 829  		err := json.Unmarshal([]byte(body), updateSettingsRequest)
 830  		if err != nil {
 831  			logger.Logger.WithFields(logrus.Fields{
 832  				"route":  route,
 833  				"method": method,
 834  			}).WithError(err).Error("Failed to decode request to wails router")
 835  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 836  		}
 837  
 838  		err = app.api.UpdateSettings(updateSettingsRequest)
 839  		if err != nil {
 840  			logger.Logger.WithFields(logrus.Fields{
 841  				"route":  route,
 842  				"method": method,
 843  			}).WithError(err).Error("Failed to update settings")
 844  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 845  		}
 846  		return WailsRequestRouterResponse{Body: nil, Error: ""}
 847  	case "/api/auto-unlock":
 848  		autoUnlockRequest := &api.AutoUnlockRequest{}
 849  		err := json.Unmarshal([]byte(body), autoUnlockRequest)
 850  		if err != nil {
 851  			logger.Logger.WithFields(logrus.Fields{
 852  				"route":  route,
 853  				"method": method,
 854  			}).WithError(err).Error("Failed to decode request to wails router")
 855  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 856  		}
 857  
 858  		err = app.api.SetAutoUnlockPassword(autoUnlockRequest.UnlockPassword)
 859  		if err != nil {
 860  			logger.Logger.WithFields(logrus.Fields{
 861  				"route":  route,
 862  				"method": method,
 863  			}).WithError(err).Error("Failed to set auto unlock password")
 864  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 865  		}
 866  		return WailsRequestRouterResponse{Body: nil, Error: ""}
 867  	case "/api/start":
 868  		startRequest := &api.StartRequest{}
 869  		err := json.Unmarshal([]byte(body), startRequest)
 870  		if err != nil {
 871  			logger.Logger.WithFields(logrus.Fields{
 872  				"route":  route,
 873  				"method": method,
 874  			}).WithError(err).Error("Failed to decode request to wails router")
 875  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 876  		}
 877  
 878  		go app.api.Start(startRequest)
 879  
 880  		return WailsRequestRouterResponse{Body: nil, Error: ""}
 881  	case "/api/setup":
 882  		setupRequest := &api.SetupRequest{}
 883  		err := json.Unmarshal([]byte(body), setupRequest)
 884  		if err != nil {
 885  			logger.Logger.WithFields(logrus.Fields{
 886  				"route":  route,
 887  				"method": method,
 888  			}).WithError(err).Error("Failed to decode request to wails router")
 889  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 890  		}
 891  		err = app.api.Setup(ctx, setupRequest)
 892  		if err != nil {
 893  			logger.Logger.WithFields(logrus.Fields{
 894  				"route":  route,
 895  				"method": method,
 896  			}).WithError(err).Error("Failed to setup node")
 897  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 898  		}
 899  		return WailsRequestRouterResponse{Body: nil, Error: ""}
 900  	case "/api/backup":
 901  		backupRequest := &api.BasicBackupRequest{}
 902  		err := json.Unmarshal([]byte(body), backupRequest)
 903  		if err != nil {
 904  			logger.Logger.WithFields(logrus.Fields{
 905  				"route":  route,
 906  				"method": method,
 907  			}).WithError(err).Error("Failed to decode request to wails router")
 908  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 909  		}
 910  
 911  		saveFilePath, err := runtime.SaveFileDialog(ctx, runtime.SaveDialogOptions{
 912  			Title:           "Save Backup File",
 913  			DefaultFilename: "albyhub.bkp",
 914  		})
 915  		if err != nil {
 916  			logger.Logger.WithFields(logrus.Fields{
 917  				"route":  route,
 918  				"method": method,
 919  			}).WithError(err).Error("Failed to open save file dialog")
 920  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 921  		}
 922  
 923  		backupFile, err := os.Create(saveFilePath)
 924  		if err != nil {
 925  			logger.Logger.WithFields(logrus.Fields{
 926  				"route":  route,
 927  				"method": method,
 928  			}).WithError(err).Error("Failed to create backup file")
 929  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 930  		}
 931  
 932  		defer backupFile.Close()
 933  
 934  		err = app.api.CreateBackup(backupRequest.UnlockPassword, backupFile)
 935  
 936  		if err != nil {
 937  			logger.Logger.WithFields(logrus.Fields{
 938  				"route":  route,
 939  				"method": method,
 940  			}).WithError(err).Error("Failed to create backup")
 941  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 942  		}
 943  		return WailsRequestRouterResponse{Body: nil, Error: ""}
 944  	case "/api/restore":
 945  		restoreRequest := &api.BasicRestoreWailsRequest{}
 946  		err := json.Unmarshal([]byte(body), restoreRequest)
 947  		if err != nil {
 948  			logger.Logger.WithFields(logrus.Fields{
 949  				"route":  route,
 950  				"method": method,
 951  			}).WithError(err).Error("Failed to decode request to wails router")
 952  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 953  		}
 954  
 955  		backupFilePath, err := runtime.OpenFileDialog(ctx, runtime.OpenDialogOptions{
 956  			Title:           "Select Backup File",
 957  			DefaultFilename: "albyhub.bkp",
 958  		})
 959  		if err != nil {
 960  			logger.Logger.WithFields(logrus.Fields{
 961  				"route":  route,
 962  				"method": method,
 963  			}).WithError(err).Error("Failed to open save file dialog")
 964  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 965  		}
 966  
 967  		backupFile, err := os.Open(backupFilePath)
 968  		if err != nil {
 969  			logger.Logger.WithFields(logrus.Fields{
 970  				"route":  route,
 971  				"method": method,
 972  			}).WithError(err).Error("Failed to open backup file")
 973  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 974  		}
 975  
 976  		defer backupFile.Close()
 977  
 978  		err = app.api.RestoreBackup(restoreRequest.UnlockPassword, backupFile)
 979  		if err != nil {
 980  			logger.Logger.WithFields(logrus.Fields{
 981  				"route":  route,
 982  				"method": method,
 983  			}).WithError(err).Error("Failed to restore backup")
 984  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 985  		}
 986  		return WailsRequestRouterResponse{Body: nil, Error: ""}
 987  	case "/api/health":
 988  		nodeHealth, err := app.api.Health(ctx)
 989  		if err != nil {
 990  			logger.Logger.WithFields(logrus.Fields{
 991  				"route":  route,
 992  				"method": method,
 993  			}).WithError(err).Error("Failed to check node health")
 994  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
 995  		}
 996  		return WailsRequestRouterResponse{Body: *nodeHealth, Error: ""}
 997  	case "/api/offers":
 998  		makeOfferRequest := &api.MakeOfferRequest{}
 999  		err := json.Unmarshal([]byte(body), makeOfferRequest)
1000  		if err != nil {
1001  			logger.Logger.WithFields(logrus.Fields{
1002  				"route":  route,
1003  				"method": method,
1004  			}).WithError(err).Error("Failed to decode request to wails router")
1005  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1006  		}
1007  		offer, err := app.api.MakeOffer(ctx, makeOfferRequest.Description)
1008  		if err != nil {
1009  			logger.Logger.WithFields(logrus.Fields{
1010  				"route":  route,
1011  				"method": method,
1012  			}).WithError(err).Error("Failed to generate BOLT-12 offer")
1013  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1014  		}
1015  		return WailsRequestRouterResponse{Body: offer, Error: ""}
1016  	case "/api/commands":
1017  		nodeCommandsResponse, err := app.api.GetCustomNodeCommands()
1018  		if err != nil {
1019  			logger.Logger.WithFields(logrus.Fields{
1020  				"route":  route,
1021  				"method": method,
1022  			}).WithError(err).Error("Failed to get node commands")
1023  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1024  		}
1025  		return WailsRequestRouterResponse{Body: nodeCommandsResponse, Error: ""}
1026  	case "/api/command":
1027  		commandRequest := &api.ExecuteCustomNodeCommandRequest{}
1028  		err := json.Unmarshal([]byte(body), commandRequest)
1029  		if err != nil {
1030  			logger.Logger.WithFields(logrus.Fields{
1031  				"route":  route,
1032  				"method": method,
1033  			}).WithError(err).Error("Failed to decode request to wails router")
1034  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1035  		}
1036  		commandResponse, err := app.api.ExecuteCustomNodeCommand(ctx, commandRequest.Command)
1037  		if err != nil {
1038  			logger.Logger.WithFields(logrus.Fields{
1039  				"route":  route,
1040  				"method": method,
1041  			}).WithError(err).Error("Failed to execute command")
1042  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1043  		}
1044  		return WailsRequestRouterResponse{Body: commandResponse, Error: ""}
1045  	case "/api/autoswap":
1046  		switch method {
1047  		case "GET":
1048  			autoSwapsConfig, err := app.api.GetAutoSwapConfig()
1049  			if err != nil {
1050  				logger.Logger.WithFields(logrus.Fields{
1051  					"route":  route,
1052  					"method": method,
1053  				}).WithError(err).Error("Failed to get auto swap configuration")
1054  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1055  			}
1056  			return WailsRequestRouterResponse{Body: autoSwapsConfig, Error: ""}
1057  		case "POST":
1058  			enableAutoSwapRequest := &api.EnableAutoSwapRequest{}
1059  			err := json.Unmarshal([]byte(body), enableAutoSwapRequest)
1060  			if err != nil {
1061  				logger.Logger.WithFields(logrus.Fields{
1062  					"route":  route,
1063  					"method": method,
1064  				}).WithError(err).Error("Failed to decode request to wails router")
1065  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1066  			}
1067  			err = app.api.EnableAutoSwapOut(ctx, enableAutoSwapRequest)
1068  			if err != nil {
1069  				logger.Logger.WithFields(logrus.Fields{
1070  					"route":  route,
1071  					"method": method,
1072  				}).WithError(err).Error("Failed to enable auto swap")
1073  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1074  			}
1075  			return WailsRequestRouterResponse{Body: nil, Error: ""}
1076  		case "DELETE":
1077  			err := app.api.DisableAutoSwap()
1078  			if err != nil {
1079  				logger.Logger.WithFields(logrus.Fields{
1080  					"route":  route,
1081  					"method": method,
1082  				}).WithError(err).Error("Failed to disable auto swap")
1083  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1084  			}
1085  			return WailsRequestRouterResponse{Body: nil, Error: ""}
1086  		}
1087  	case "/api/swaps/out/info":
1088  		swapOutInfo, err := app.api.GetSwapOutInfo()
1089  		if err != nil {
1090  			logger.Logger.WithFields(logrus.Fields{
1091  				"route":  route,
1092  				"method": method,
1093  			}).WithError(err).Error("Failed to get swap out info")
1094  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1095  		}
1096  		return WailsRequestRouterResponse{Body: swapOutInfo, Error: ""}
1097  	case "/api/swaps/in/info":
1098  		swapInInfo, err := app.api.GetSwapInInfo()
1099  		if err != nil {
1100  			logger.Logger.WithFields(logrus.Fields{
1101  				"route":  route,
1102  				"method": method,
1103  			}).WithError(err).Error("Failed to get swap in info")
1104  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1105  		}
1106  		return WailsRequestRouterResponse{Body: swapInInfo, Error: ""}
1107  	case "/api/swaps/out":
1108  		initiateSwapOutRequest := &api.InitiateSwapRequest{}
1109  		err := json.Unmarshal([]byte(body), initiateSwapOutRequest)
1110  		if err != nil {
1111  			logger.Logger.WithFields(logrus.Fields{
1112  				"route":  route,
1113  				"method": method,
1114  			}).WithError(err).Error("Failed to decode request to wails router")
1115  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1116  		}
1117  		swapOutResponse, err := app.api.InitiateSwapOut(ctx, initiateSwapOutRequest)
1118  		if err != nil {
1119  			logger.Logger.WithFields(logrus.Fields{
1120  				"route":  route,
1121  				"method": method,
1122  			}).WithError(err).Error("Failed to initiate swap out")
1123  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1124  		}
1125  		return WailsRequestRouterResponse{Body: swapOutResponse, Error: ""}
1126  	case "/api/swaps/in":
1127  		initiateSwapInRequest := &api.InitiateSwapRequest{}
1128  		err := json.Unmarshal([]byte(body), initiateSwapInRequest)
1129  		if err != nil {
1130  			logger.Logger.WithFields(logrus.Fields{
1131  				"route":  route,
1132  				"method": method,
1133  			}).WithError(err).Error("Failed to decode request to wails router")
1134  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1135  		}
1136  		swapInResponse, err := app.api.InitiateSwapIn(ctx, initiateSwapInRequest)
1137  		if err != nil {
1138  			logger.Logger.WithFields(logrus.Fields{
1139  				"route":  route,
1140  				"method": method,
1141  			}).WithError(err).Error("Failed to initiate swap in")
1142  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1143  		}
1144  		return WailsRequestRouterResponse{Body: swapInResponse, Error: ""}
1145  	case "/api/swaps/refund":
1146  		refundSwapRequest := &api.RefundSwapRequest{}
1147  		err := json.Unmarshal([]byte(body), refundSwapRequest)
1148  		if err != nil {
1149  			logger.Logger.WithFields(logrus.Fields{
1150  				"route":  route,
1151  				"method": method,
1152  			}).WithError(err).Error("Failed to decode request to wails router")
1153  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1154  		}
1155  		err = app.api.RefundSwap(refundSwapRequest)
1156  		if err != nil {
1157  			logger.Logger.WithFields(logrus.Fields{
1158  				"route":  route,
1159  				"method": method,
1160  			}).WithError(err).Error("Failed to process swap refund")
1161  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1162  		}
1163  		return WailsRequestRouterResponse{Body: nil, Error: ""}
1164  	case "/api/swaps/mnemonic":
1165  		mnemonic := app.api.GetSwapMnemonic()
1166  		return WailsRequestRouterResponse{Body: mnemonic, Error: ""}
1167  	case "/api/node/alias":
1168  		setNodeAliasRequest := &api.SetNodeAliasRequest{}
1169  		err := json.Unmarshal([]byte(body), setNodeAliasRequest)
1170  		if err != nil {
1171  			logger.Logger.WithFields(logrus.Fields{
1172  				"route":  route,
1173  				"method": method,
1174  			}).WithError(err).Error("Failed to decode request to wails router")
1175  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1176  		}
1177  
1178  		err = app.api.SetNodeAlias(setNodeAliasRequest.NodeAlias)
1179  		if err != nil {
1180  			logger.Logger.WithFields(logrus.Fields{
1181  				"route":  route,
1182  				"method": method,
1183  			}).WithError(err).Error("Failed to set node alias")
1184  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1185  		}
1186  		return WailsRequestRouterResponse{Body: nil, Error: ""}
1187  	case "/api/event":
1188  		switch method {
1189  		case "POST":
1190  			sendEventRequest := &api.SendEventRequest{}
1191  			err := json.Unmarshal([]byte(body), sendEventRequest)
1192  			if err != nil {
1193  				logger.Logger.WithFields(logrus.Fields{
1194  					"route":  route,
1195  					"method": method,
1196  				}).WithError(err).Error("Failed to send event")
1197  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1198  			}
1199  
1200  			app.api.SendEvent(sendEventRequest.Event, sendEventRequest.Properties)
1201  
1202  			return WailsRequestRouterResponse{Body: nil, Error: ""}
1203  		}
1204  	case "/api/lightning-addresses":
1205  		switch method {
1206  		case "POST":
1207  			createLightningAddressRequest := &api.CreateLightningAddressRequest{}
1208  			err := json.Unmarshal([]byte(body), createLightningAddressRequest)
1209  			if err != nil {
1210  				logger.Logger.WithFields(logrus.Fields{
1211  					"route":  route,
1212  					"method": method,
1213  				}).WithError(err).Error("Failed to decode request to wails router")
1214  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1215  			}
1216  
1217  			err = app.api.CreateLightningAddress(ctx, createLightningAddressRequest)
1218  			if err != nil {
1219  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1220  			}
1221  			return WailsRequestRouterResponse{Body: nil, Error: ""}
1222  		}
1223  	case "/api/forwards":
1224  		forwards, err := app.api.GetForwards()
1225  		if err != nil {
1226  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1227  		}
1228  		return WailsRequestRouterResponse{Body: forwards, Error: ""}
1229  	}
1230  
1231  	lightningAddressRegex := regexp.MustCompile(
1232  		`/api/lightning-addresses/([^/]+)`,
1233  	)
1234  	lightningAddressMatch := lightningAddressRegex.FindStringSubmatch(route)
1235  
1236  	switch {
1237  	case len(lightningAddressMatch) == 2:
1238  		appIdStr := lightningAddressMatch[1]
1239  		appId, err := strconv.ParseUint(appIdStr, 10, 64)
1240  		if err != nil {
1241  			return WailsRequestRouterResponse{Body: nil, Error: "Invalid app ID"}
1242  		}
1243  
1244  		switch method {
1245  		case "DELETE":
1246  			err := app.api.DeleteLightningAddress(ctx, uint(appId))
1247  			if err != nil {
1248  				return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1249  			}
1250  			return WailsRequestRouterResponse{Body: nil, Error: ""}
1251  		}
1252  	}
1253  
1254  	// Swap lookup and listing is shifted to the bottom so it
1255  	// doesn't interfere with other swap endpoints
1256  	swapRegex := regexp.MustCompile(
1257  		`/api/swaps/([0-9a-zA-Z]+)`,
1258  	)
1259  	swapIdMatch := swapRegex.FindStringSubmatch(route)
1260  
1261  	switch {
1262  	case len(swapIdMatch) > 1:
1263  		swapId := swapIdMatch[1]
1264  		swapInfo, err := app.api.LookupSwap(swapId)
1265  		if err != nil {
1266  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1267  		}
1268  
1269  		return WailsRequestRouterResponse{Body: swapInfo, Error: ""}
1270  	}
1271  
1272  	listSwapsRegex := regexp.MustCompile(
1273  		`/api/swaps`,
1274  	)
1275  
1276  	switch {
1277  	case listSwapsRegex.MatchString(route):
1278  		swaps, err := app.api.ListSwaps()
1279  		if err != nil {
1280  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1281  		}
1282  		return WailsRequestRouterResponse{Body: swaps, Error: ""}
1283  	}
1284  
1285  	if strings.HasPrefix(route, "/api/log/") {
1286  		logType := strings.TrimPrefix(route, "/api/log/")
1287  		logType = strings.Split(logType, "?")[0]
1288  		if logType != api.LogTypeNode && logType != api.LogTypeApp {
1289  			return WailsRequestRouterResponse{Body: nil, Error: fmt.Sprintf("Invalid log type: '%s'", logType)}
1290  		}
1291  		parsedUrl, err := url.Parse(route)
1292  		if err != nil {
1293  			return WailsRequestRouterResponse{Body: nil, Error: "Failed to parse route URL"}
1294  		}
1295  		queryParams := parsedUrl.Query()
1296  		getLogOutputRequest := &api.GetLogOutputRequest{}
1297  		if maxLen := queryParams.Get("maxLen"); maxLen != "" {
1298  			getLogOutputRequest.MaxLen, err = strconv.Atoi(maxLen)
1299  			if err != nil {
1300  				return WailsRequestRouterResponse{Body: nil, Error: "Invalid max length parameter"}
1301  			}
1302  		}
1303  		logOutputResponse, err := app.api.GetLogOutput(ctx, logType, getLogOutputRequest)
1304  		if err != nil {
1305  			logger.Logger.WithFields(logrus.Fields{
1306  				"route":  route,
1307  				"method": method,
1308  			}).WithError(err).Error("Failed to get log output")
1309  			return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
1310  		}
1311  		return WailsRequestRouterResponse{Body: logOutputResponse, Error: ""}
1312  	}
1313  
1314  	logger.Logger.WithFields(logrus.Fields{
1315  		"route":  route,
1316  		"method": method,
1317  	}).Error("Unhandled route")
1318  	return WailsRequestRouterResponse{Body: nil, Error: fmt.Sprintf("Unhandled route: %s %s", method, route)}
1319  }
1320