diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 77dc533ad..b3aae52ec 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -88,7 +88,8 @@ var ( monitorCommand, quoteCommand, listAuthCommand, fetchL402Command, listSwapsCommand, swapInfoCommand, getLiquidityParamsCommand, setLiquidityRuleCommand, suggestSwapCommand, setParamsCommand, - getInfoCommand, abandonSwapCommand, reservationsCommands, + getInfoCommand, abandonSwapCommand, recoverCommand, + reservationsCommands, instantOutCommand, listInstantOutsCommand, stopCommand, printManCommand, printMarkdownCommand, } diff --git a/cmd/loop/recover.go b/cmd/loop/recover.go new file mode 100644 index 000000000..668790245 --- /dev/null +++ b/cmd/loop/recover.go @@ -0,0 +1,49 @@ +package main + +import ( + "context" + + "github.com/lightninglabs/loop/looprpc" + "github.com/urfave/cli/v3" +) + +var recoverCommand = &cli.Command{ + Name: "recover", + Usage: "restore static address and L402 state from a local backup file", + Description: "Restores the local static-address state and L402 token " + + "from an encrypted backup file. If --backup_file is omitted, " + + "loopd will use the latest timestamped network-specific backup " + + "file.", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "backup_file", + Usage: "path to an encrypted backup file; if omitted, " + + "loopd uses the latest timestamped backup file path", + }, + }, + Action: runRecover, +} + +func runRecover(ctx context.Context, cmd *cli.Command) error { + if cmd.NArg() > 0 { + return showCommandHelp(ctx, cmd) + } + + client, cleanup, err := getClient(cmd) + if err != nil { + return err + } + defer cleanup() + + resp, err := client.Recover( + ctx, &looprpc.RecoverRequest{ + BackupFile: cmd.String("backup_file"), + }, + ) + if err != nil { + return err + } + + printRespJSON(resp) + return nil +} diff --git a/docs/loop.1 b/docs/loop.1 index 7d767cdff..40894d980 100644 --- a/docs/loop.1 +++ b/docs/loop.1 @@ -403,6 +403,16 @@ abandon a swap with a given swap hash .PP \fB--i_know_what_i_am_doing\fP: Specify this flag if you made sure that you read and understood the following consequence of applying this command. +.SH recover +.PP +restore static address and L402 state from a local backup file + +.PP +\fB--backup_file\fP="": path to an encrypted backup file; if omitted, loopd uses the latest timestamped backup file path + +.PP +\fB--help, -h\fP: show help + .SH reservations, r .PP manage reservations diff --git a/docs/loop.md b/docs/loop.md index 3a847e96c..7ef500523 100644 --- a/docs/loop.md +++ b/docs/loop.md @@ -418,6 +418,25 @@ The following flags are supported: | `--i_know_what_i_am_doing` | Specify this flag if you made sure that you read and understood the following consequence of applying this command | bool | `false` | | `--help` (`-h`) | show help | bool | `false` | +### `recover` command + +restore static address and L402 state from a local backup file. + +Restores the local static-address state and L402 token from an encrypted backup file. If --backup_file is omitted, loopd will use the latest timestamped network-specific backup file. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] recover [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|---------------------|--------------------------------------------------------------------------------------------------|--------|:-------------:| +| `--backup_file="…"` | path to an encrypted backup file; if omitted, loopd uses the latest timestamped backup file path | string | +| `--help` (`-h`) | show help | bool | `false` | + ### `reservations` command (aliases: `r`) manage reservations. diff --git a/loopd/daemon.go b/loopd/daemon.go index 880e19621..21326cebc 100644 --- a/loopd/daemon.go +++ b/loopd/daemon.go @@ -22,6 +22,7 @@ import ( "github.com/lightninglabs/loop/loopdb" loop_looprpc "github.com/lightninglabs/loop/looprpc" "github.com/lightninglabs/loop/notifications" + "github.com/lightninglabs/loop/recovery" "github.com/lightninglabs/loop/staticaddr/address" "github.com/lightninglabs/loop/staticaddr/deposit" "github.com/lightninglabs/loop/staticaddr/loopin" @@ -577,17 +578,47 @@ func (d *Daemon) initialize(withMacaroonService bool) error { withdrawalManager *withdraw.Manager openChannelManager *openchannel.Manager staticLoopInManager *loopin.Manager + recoveryService *recovery.Service ) + writeRecoveryBackup := func(ctx context.Context, source string) error { + if recoveryService == nil { + return nil + } + + backupFile, err := recoveryService.WriteBackup(ctx) + if err != nil { + return err + } + if backupFile != "" { + infof("Wrote encrypted recovery backup to %s after %s", + backupFile, source) + } + + return nil + } + // Static address manager setup. staticAddressStore := address.NewSqlStore(baseDb) addrCfg := &address.ManagerConfig{ AddressClient: staticAddressClient, - FetchL402: swapClient.Server.FetchL402, + FetchL402: func(ctx context.Context) error { + err := swapClient.Server.FetchL402(ctx) + if err != nil { + return err + } + + return writeRecoveryBackup(ctx, "retrieving a paid L402") + }, Store: staticAddressStore, WalletKit: d.lnd.WalletKit, ChainParams: d.lnd.ChainParams, ChainNotifier: d.lnd.ChainNotifier, + OnStaticAddressCreated: func(ctx context.Context) error { + return writeRecoveryBackup( + ctx, "creating a static address", + ) + }, } staticAddressManager, err = address.NewManager( addrCfg, int32(blockHeight), @@ -689,6 +720,15 @@ func (d *Daemon) initialize(withMacaroonService bool) error { return fmt.Errorf("unable to create loop-in manager: %w", err) } + recoveryService = recovery.NewService( + d.cfg.DataDir, d.cfg.Network, d.lnd.Signer, d.lnd.WalletKit, + staticAddressManager, depositManager, + ) + err = writeRecoveryBackup(d.mainCtx, "startup") + if err != nil { + return fmt.Errorf("unable to write backup file: %w", err) + } + var ( reservationManager *reservation.Manager instantOutManager *instantout.Manager @@ -753,6 +793,7 @@ func (d *Daemon) initialize(withMacaroonService bool) error { staticLoopInManager: staticLoopInManager, openChannelManager: openChannelManager, assetClient: d.assetClient, + recoveryService: recoveryService, stopDaemon: d.Stop, } diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index 62187d3e5..c4dae9e6d 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -29,6 +29,7 @@ import ( "github.com/lightninglabs/loop/liquidity" "github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/looprpc" + "github.com/lightninglabs/loop/recovery" "github.com/lightninglabs/loop/staticaddr/address" "github.com/lightninglabs/loop/staticaddr/deposit" "github.com/lightninglabs/loop/staticaddr/loopin" @@ -101,6 +102,7 @@ type swapClientServer struct { staticLoopInManager *loopin.Manager openChannelManager *openchannel.Manager assetClient *assets.TapdClient + recoveryService *recovery.Service swaps map[lntypes.Hash]loop.SwapInfo subscribers map[int]chan<- any statusChan chan loop.SwapInfo @@ -1265,9 +1267,59 @@ func (s *swapClientServer) FetchL402Token(ctx context.Context, return nil, err } + err = s.writeRecoveryBackup(ctx, "retrieving a paid L402") + if err != nil { + return nil, err + } + return &looprpc.FetchL402TokenResponse{}, nil } +// Recover restores the local paid L402 token material and static-address state +// from an encrypted backup file. +func (s *swapClientServer) Recover(ctx context.Context, + req *looprpc.RecoverRequest) (*looprpc.RecoverResponse, error) { + + if s.recoveryService == nil { + return nil, status.Error( + codes.Unavailable, "recovery service not configured", + ) + } + + result, err := s.recoveryService.Restore(ctx, req.GetBackupFile()) + if err != nil { + return nil, err + } + + return &looprpc.RecoverResponse{ + BackupFile: result.BackupFile, + RestoredL402: result.RestoredL402, + RestoredStaticAddress: result.RestoredStaticAddress, + StaticAddress: result.StaticAddress, + NumDepositsFound: uint32(result.NumDepositsFound), + DepositReconciliationError: result.DepositReconciliationError, + }, nil +} + +func (s *swapClientServer) writeRecoveryBackup(ctx context.Context, + source string) error { + + if s.recoveryService == nil { + return nil + } + + backupFile, err := s.recoveryService.WriteBackup(ctx) + if err != nil { + return err + } + if backupFile != "" { + infof("Wrote encrypted recovery backup to %s after %s", + backupFile, source) + } + + return nil +} + // GetInfo returns basic information about the loop daemon and details to swaps // from the swap store. func (s *swapClientServer) GetInfo(ctx context.Context, diff --git a/looprpc/client.pb.go b/looprpc/client.pb.go index 35ae71c82..f0821da39 100644 --- a/looprpc/client.pb.go +++ b/looprpc/client.pb.go @@ -2962,6 +2962,144 @@ func (*FetchL402TokenResponse) Descriptor() ([]byte, []int) { return file_client_proto_rawDescGZIP(), []int{29} } +type RecoverRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + // Optional path to the encrypted backup file. If omitted, loopd restores from + // the latest timestamped recovery backup in the active network data + // directory. + BackupFile string `protobuf:"bytes,1,opt,name=backup_file,json=backupFile,proto3" json:"backup_file,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RecoverRequest) Reset() { + *x = RecoverRequest{} + mi := &file_client_proto_msgTypes[30] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RecoverRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecoverRequest) ProtoMessage() {} + +func (x *RecoverRequest) ProtoReflect() protoreflect.Message { + mi := &file_client_proto_msgTypes[30] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RecoverRequest.ProtoReflect.Descriptor instead. +func (*RecoverRequest) Descriptor() ([]byte, []int) { + return file_client_proto_rawDescGZIP(), []int{30} +} + +func (x *RecoverRequest) GetBackupFile() string { + if x != nil { + return x.BackupFile + } + return "" +} + +type RecoverResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + // The backup file that was restored. + BackupFile string `protobuf:"bytes,1,opt,name=backup_file,json=backupFile,proto3" json:"backup_file,omitempty"` + // Whether a paid L402 token was restored into the local token store. + RestoredL402 bool `protobuf:"varint,2,opt,name=restored_l402,json=restoredL402,proto3" json:"restored_l402,omitempty"` + // Whether static-address state was restored into loopd and lnd. + RestoredStaticAddress bool `protobuf:"varint,3,opt,name=restored_static_address,json=restoredStaticAddress,proto3" json:"restored_static_address,omitempty"` + // The restored static address, if any. + StaticAddress string `protobuf:"bytes,4,opt,name=static_address,json=staticAddress,proto3" json:"static_address,omitempty"` + // The number of deposits found during best-effort reconciliation. + NumDepositsFound uint32 `protobuf:"varint,5,opt,name=num_deposits_found,json=numDepositsFound,proto3" json:"num_deposits_found,omitempty"` + // Best-effort deposit reconciliation error text, if reconciliation failed + // after state restore completed. + DepositReconciliationError string `protobuf:"bytes,6,opt,name=deposit_reconciliation_error,json=depositReconciliationError,proto3" json:"deposit_reconciliation_error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RecoverResponse) Reset() { + *x = RecoverResponse{} + mi := &file_client_proto_msgTypes[31] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RecoverResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RecoverResponse) ProtoMessage() {} + +func (x *RecoverResponse) ProtoReflect() protoreflect.Message { + mi := &file_client_proto_msgTypes[31] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RecoverResponse.ProtoReflect.Descriptor instead. +func (*RecoverResponse) Descriptor() ([]byte, []int) { + return file_client_proto_rawDescGZIP(), []int{31} +} + +func (x *RecoverResponse) GetBackupFile() string { + if x != nil { + return x.BackupFile + } + return "" +} + +func (x *RecoverResponse) GetRestoredL402() bool { + if x != nil { + return x.RestoredL402 + } + return false +} + +func (x *RecoverResponse) GetRestoredStaticAddress() bool { + if x != nil { + return x.RestoredStaticAddress + } + return false +} + +func (x *RecoverResponse) GetStaticAddress() string { + if x != nil { + return x.StaticAddress + } + return "" +} + +func (x *RecoverResponse) GetNumDepositsFound() uint32 { + if x != nil { + return x.NumDepositsFound + } + return 0 +} + +func (x *RecoverResponse) GetDepositReconciliationError() string { + if x != nil { + return x.DepositReconciliationError + } + return "" +} + type L402Token struct { state protoimpl.MessageState `protogen:"open.v1"` // The base macaroon that was baked by the auth server. @@ -2991,7 +3129,7 @@ type L402Token struct { func (x *L402Token) Reset() { *x = L402Token{} - mi := &file_client_proto_msgTypes[30] + mi := &file_client_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3003,7 +3141,7 @@ func (x *L402Token) String() string { func (*L402Token) ProtoMessage() {} func (x *L402Token) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[30] + mi := &file_client_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3016,7 +3154,7 @@ func (x *L402Token) ProtoReflect() protoreflect.Message { // Deprecated: Use L402Token.ProtoReflect.Descriptor instead. func (*L402Token) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{30} + return file_client_proto_rawDescGZIP(), []int{32} } func (x *L402Token) GetBaseMacaroon() []byte { @@ -3100,7 +3238,7 @@ type LoopStats struct { func (x *LoopStats) Reset() { *x = LoopStats{} - mi := &file_client_proto_msgTypes[31] + mi := &file_client_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3112,7 +3250,7 @@ func (x *LoopStats) String() string { func (*LoopStats) ProtoMessage() {} func (x *LoopStats) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[31] + mi := &file_client_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3125,7 +3263,7 @@ func (x *LoopStats) ProtoReflect() protoreflect.Message { // Deprecated: Use LoopStats.ProtoReflect.Descriptor instead. func (*LoopStats) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{31} + return file_client_proto_rawDescGZIP(), []int{33} } func (x *LoopStats) GetPendingCount() uint64 { @@ -3171,7 +3309,7 @@ type GetInfoRequest struct { func (x *GetInfoRequest) Reset() { *x = GetInfoRequest{} - mi := &file_client_proto_msgTypes[32] + mi := &file_client_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3183,7 +3321,7 @@ func (x *GetInfoRequest) String() string { func (*GetInfoRequest) ProtoMessage() {} func (x *GetInfoRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[32] + mi := &file_client_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3196,7 +3334,7 @@ func (x *GetInfoRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetInfoRequest.ProtoReflect.Descriptor instead. func (*GetInfoRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{32} + return file_client_proto_rawDescGZIP(), []int{34} } type GetInfoResponse struct { @@ -3227,7 +3365,7 @@ type GetInfoResponse struct { func (x *GetInfoResponse) Reset() { *x = GetInfoResponse{} - mi := &file_client_proto_msgTypes[33] + mi := &file_client_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3239,7 +3377,7 @@ func (x *GetInfoResponse) String() string { func (*GetInfoResponse) ProtoMessage() {} func (x *GetInfoResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[33] + mi := &file_client_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3252,7 +3390,7 @@ func (x *GetInfoResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetInfoResponse.ProtoReflect.Descriptor instead. func (*GetInfoResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{33} + return file_client_proto_rawDescGZIP(), []int{35} } func (x *GetInfoResponse) GetVersion() string { @@ -3326,7 +3464,7 @@ type GetLiquidityParamsRequest struct { func (x *GetLiquidityParamsRequest) Reset() { *x = GetLiquidityParamsRequest{} - mi := &file_client_proto_msgTypes[34] + mi := &file_client_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3338,7 +3476,7 @@ func (x *GetLiquidityParamsRequest) String() string { func (*GetLiquidityParamsRequest) ProtoMessage() {} func (x *GetLiquidityParamsRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[34] + mi := &file_client_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3351,7 +3489,7 @@ func (x *GetLiquidityParamsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetLiquidityParamsRequest.ProtoReflect.Descriptor instead. func (*GetLiquidityParamsRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{34} + return file_client_proto_rawDescGZIP(), []int{36} } type LiquidityParameters struct { @@ -3461,7 +3599,7 @@ type LiquidityParameters struct { func (x *LiquidityParameters) Reset() { *x = LiquidityParameters{} - mi := &file_client_proto_msgTypes[35] + mi := &file_client_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3473,7 +3611,7 @@ func (x *LiquidityParameters) String() string { func (*LiquidityParameters) ProtoMessage() {} func (x *LiquidityParameters) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[35] + mi := &file_client_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3486,7 +3624,7 @@ func (x *LiquidityParameters) ProtoReflect() protoreflect.Message { // Deprecated: Use LiquidityParameters.ProtoReflect.Descriptor instead. func (*LiquidityParameters) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{35} + return file_client_proto_rawDescGZIP(), []int{37} } func (x *LiquidityParameters) GetRules() []*LiquidityRule { @@ -3696,7 +3834,7 @@ type EasyAssetAutoloopParams struct { func (x *EasyAssetAutoloopParams) Reset() { *x = EasyAssetAutoloopParams{} - mi := &file_client_proto_msgTypes[36] + mi := &file_client_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3708,7 +3846,7 @@ func (x *EasyAssetAutoloopParams) String() string { func (*EasyAssetAutoloopParams) ProtoMessage() {} func (x *EasyAssetAutoloopParams) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[36] + mi := &file_client_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3721,7 +3859,7 @@ func (x *EasyAssetAutoloopParams) ProtoReflect() protoreflect.Message { // Deprecated: Use EasyAssetAutoloopParams.ProtoReflect.Descriptor instead. func (*EasyAssetAutoloopParams) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{36} + return file_client_proto_rawDescGZIP(), []int{38} } func (x *EasyAssetAutoloopParams) GetEnabled() bool { @@ -3765,7 +3903,7 @@ type LiquidityRule struct { func (x *LiquidityRule) Reset() { *x = LiquidityRule{} - mi := &file_client_proto_msgTypes[37] + mi := &file_client_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3777,7 +3915,7 @@ func (x *LiquidityRule) String() string { func (*LiquidityRule) ProtoMessage() {} func (x *LiquidityRule) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[37] + mi := &file_client_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3790,7 +3928,7 @@ func (x *LiquidityRule) ProtoReflect() protoreflect.Message { // Deprecated: Use LiquidityRule.ProtoReflect.Descriptor instead. func (*LiquidityRule) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{37} + return file_client_proto_rawDescGZIP(), []int{39} } func (x *LiquidityRule) GetChannelId() uint64 { @@ -3848,7 +3986,7 @@ type SetLiquidityParamsRequest struct { func (x *SetLiquidityParamsRequest) Reset() { *x = SetLiquidityParamsRequest{} - mi := &file_client_proto_msgTypes[38] + mi := &file_client_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3860,7 +3998,7 @@ func (x *SetLiquidityParamsRequest) String() string { func (*SetLiquidityParamsRequest) ProtoMessage() {} func (x *SetLiquidityParamsRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[38] + mi := &file_client_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3873,7 +4011,7 @@ func (x *SetLiquidityParamsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SetLiquidityParamsRequest.ProtoReflect.Descriptor instead. func (*SetLiquidityParamsRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{38} + return file_client_proto_rawDescGZIP(), []int{40} } func (x *SetLiquidityParamsRequest) GetParameters() *LiquidityParameters { @@ -3891,7 +4029,7 @@ type SetLiquidityParamsResponse struct { func (x *SetLiquidityParamsResponse) Reset() { *x = SetLiquidityParamsResponse{} - mi := &file_client_proto_msgTypes[39] + mi := &file_client_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3903,7 +4041,7 @@ func (x *SetLiquidityParamsResponse) String() string { func (*SetLiquidityParamsResponse) ProtoMessage() {} func (x *SetLiquidityParamsResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[39] + mi := &file_client_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3916,7 +4054,7 @@ func (x *SetLiquidityParamsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SetLiquidityParamsResponse.ProtoReflect.Descriptor instead. func (*SetLiquidityParamsResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{39} + return file_client_proto_rawDescGZIP(), []int{41} } type SuggestSwapsRequest struct { @@ -3927,7 +4065,7 @@ type SuggestSwapsRequest struct { func (x *SuggestSwapsRequest) Reset() { *x = SuggestSwapsRequest{} - mi := &file_client_proto_msgTypes[40] + mi := &file_client_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3939,7 +4077,7 @@ func (x *SuggestSwapsRequest) String() string { func (*SuggestSwapsRequest) ProtoMessage() {} func (x *SuggestSwapsRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[40] + mi := &file_client_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3952,7 +4090,7 @@ func (x *SuggestSwapsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SuggestSwapsRequest.ProtoReflect.Descriptor instead. func (*SuggestSwapsRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{40} + return file_client_proto_rawDescGZIP(), []int{42} } type Disqualified struct { @@ -3969,7 +4107,7 @@ type Disqualified struct { func (x *Disqualified) Reset() { *x = Disqualified{} - mi := &file_client_proto_msgTypes[41] + mi := &file_client_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3981,7 +4119,7 @@ func (x *Disqualified) String() string { func (*Disqualified) ProtoMessage() {} func (x *Disqualified) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[41] + mi := &file_client_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3994,7 +4132,7 @@ func (x *Disqualified) ProtoReflect() protoreflect.Message { // Deprecated: Use Disqualified.ProtoReflect.Descriptor instead. func (*Disqualified) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{41} + return file_client_proto_rawDescGZIP(), []int{43} } func (x *Disqualified) GetChannelId() uint64 { @@ -4033,7 +4171,7 @@ type SuggestSwapsResponse struct { func (x *SuggestSwapsResponse) Reset() { *x = SuggestSwapsResponse{} - mi := &file_client_proto_msgTypes[42] + mi := &file_client_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4045,7 +4183,7 @@ func (x *SuggestSwapsResponse) String() string { func (*SuggestSwapsResponse) ProtoMessage() {} func (x *SuggestSwapsResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[42] + mi := &file_client_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4058,7 +4196,7 @@ func (x *SuggestSwapsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SuggestSwapsResponse.ProtoReflect.Descriptor instead. func (*SuggestSwapsResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{42} + return file_client_proto_rawDescGZIP(), []int{44} } func (x *SuggestSwapsResponse) GetLoopOut() []*LoopOutRequest { @@ -4097,7 +4235,7 @@ type AbandonSwapRequest struct { func (x *AbandonSwapRequest) Reset() { *x = AbandonSwapRequest{} - mi := &file_client_proto_msgTypes[43] + mi := &file_client_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4109,7 +4247,7 @@ func (x *AbandonSwapRequest) String() string { func (*AbandonSwapRequest) ProtoMessage() {} func (x *AbandonSwapRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[43] + mi := &file_client_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4122,7 +4260,7 @@ func (x *AbandonSwapRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AbandonSwapRequest.ProtoReflect.Descriptor instead. func (*AbandonSwapRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{43} + return file_client_proto_rawDescGZIP(), []int{45} } func (x *AbandonSwapRequest) GetId() []byte { @@ -4147,7 +4285,7 @@ type AbandonSwapResponse struct { func (x *AbandonSwapResponse) Reset() { *x = AbandonSwapResponse{} - mi := &file_client_proto_msgTypes[44] + mi := &file_client_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4159,7 +4297,7 @@ func (x *AbandonSwapResponse) String() string { func (*AbandonSwapResponse) ProtoMessage() {} func (x *AbandonSwapResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[44] + mi := &file_client_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4172,7 +4310,7 @@ func (x *AbandonSwapResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AbandonSwapResponse.ProtoReflect.Descriptor instead. func (*AbandonSwapResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{44} + return file_client_proto_rawDescGZIP(), []int{46} } type ListReservationsRequest struct { @@ -4183,7 +4321,7 @@ type ListReservationsRequest struct { func (x *ListReservationsRequest) Reset() { *x = ListReservationsRequest{} - mi := &file_client_proto_msgTypes[45] + mi := &file_client_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4195,7 +4333,7 @@ func (x *ListReservationsRequest) String() string { func (*ListReservationsRequest) ProtoMessage() {} func (x *ListReservationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[45] + mi := &file_client_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4208,7 +4346,7 @@ func (x *ListReservationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListReservationsRequest.ProtoReflect.Descriptor instead. func (*ListReservationsRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{45} + return file_client_proto_rawDescGZIP(), []int{47} } type ListReservationsResponse struct { @@ -4221,7 +4359,7 @@ type ListReservationsResponse struct { func (x *ListReservationsResponse) Reset() { *x = ListReservationsResponse{} - mi := &file_client_proto_msgTypes[46] + mi := &file_client_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4233,7 +4371,7 @@ func (x *ListReservationsResponse) String() string { func (*ListReservationsResponse) ProtoMessage() {} func (x *ListReservationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[46] + mi := &file_client_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4246,7 +4384,7 @@ func (x *ListReservationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListReservationsResponse.ProtoReflect.Descriptor instead. func (*ListReservationsResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{46} + return file_client_proto_rawDescGZIP(), []int{48} } func (x *ListReservationsResponse) GetReservations() []*ClientReservation { @@ -4276,7 +4414,7 @@ type ClientReservation struct { func (x *ClientReservation) Reset() { *x = ClientReservation{} - mi := &file_client_proto_msgTypes[47] + mi := &file_client_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4288,7 +4426,7 @@ func (x *ClientReservation) String() string { func (*ClientReservation) ProtoMessage() {} func (x *ClientReservation) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[47] + mi := &file_client_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4301,7 +4439,7 @@ func (x *ClientReservation) ProtoReflect() protoreflect.Message { // Deprecated: Use ClientReservation.ProtoReflect.Descriptor instead. func (*ClientReservation) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{47} + return file_client_proto_rawDescGZIP(), []int{49} } func (x *ClientReservation) GetReservationId() []byte { @@ -4363,7 +4501,7 @@ type InstantOutRequest struct { func (x *InstantOutRequest) Reset() { *x = InstantOutRequest{} - mi := &file_client_proto_msgTypes[48] + mi := &file_client_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4375,7 +4513,7 @@ func (x *InstantOutRequest) String() string { func (*InstantOutRequest) ProtoMessage() {} func (x *InstantOutRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[48] + mi := &file_client_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4388,7 +4526,7 @@ func (x *InstantOutRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InstantOutRequest.ProtoReflect.Descriptor instead. func (*InstantOutRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{48} + return file_client_proto_rawDescGZIP(), []int{50} } func (x *InstantOutRequest) GetReservationIds() [][]byte { @@ -4426,7 +4564,7 @@ type InstantOutResponse struct { func (x *InstantOutResponse) Reset() { *x = InstantOutResponse{} - mi := &file_client_proto_msgTypes[49] + mi := &file_client_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4438,7 +4576,7 @@ func (x *InstantOutResponse) String() string { func (*InstantOutResponse) ProtoMessage() {} func (x *InstantOutResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[49] + mi := &file_client_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4451,7 +4589,7 @@ func (x *InstantOutResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use InstantOutResponse.ProtoReflect.Descriptor instead. func (*InstantOutResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{49} + return file_client_proto_rawDescGZIP(), []int{51} } func (x *InstantOutResponse) GetInstantOutHash() []byte { @@ -4492,7 +4630,7 @@ type InstantOutQuoteRequest struct { func (x *InstantOutQuoteRequest) Reset() { *x = InstantOutQuoteRequest{} - mi := &file_client_proto_msgTypes[50] + mi := &file_client_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4504,7 +4642,7 @@ func (x *InstantOutQuoteRequest) String() string { func (*InstantOutQuoteRequest) ProtoMessage() {} func (x *InstantOutQuoteRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[50] + mi := &file_client_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4517,7 +4655,7 @@ func (x *InstantOutQuoteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InstantOutQuoteRequest.ProtoReflect.Descriptor instead. func (*InstantOutQuoteRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{50} + return file_client_proto_rawDescGZIP(), []int{52} } func (x *InstantOutQuoteRequest) GetAmt() uint64 { @@ -4555,7 +4693,7 @@ type InstantOutQuoteResponse struct { func (x *InstantOutQuoteResponse) Reset() { *x = InstantOutQuoteResponse{} - mi := &file_client_proto_msgTypes[51] + mi := &file_client_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4567,7 +4705,7 @@ func (x *InstantOutQuoteResponse) String() string { func (*InstantOutQuoteResponse) ProtoMessage() {} func (x *InstantOutQuoteResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[51] + mi := &file_client_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4580,7 +4718,7 @@ func (x *InstantOutQuoteResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use InstantOutQuoteResponse.ProtoReflect.Descriptor instead. func (*InstantOutQuoteResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{51} + return file_client_proto_rawDescGZIP(), []int{53} } func (x *InstantOutQuoteResponse) GetServiceFeeSat() int64 { @@ -4605,7 +4743,7 @@ type ListInstantOutsRequest struct { func (x *ListInstantOutsRequest) Reset() { *x = ListInstantOutsRequest{} - mi := &file_client_proto_msgTypes[52] + mi := &file_client_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4617,7 +4755,7 @@ func (x *ListInstantOutsRequest) String() string { func (*ListInstantOutsRequest) ProtoMessage() {} func (x *ListInstantOutsRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[52] + mi := &file_client_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4630,7 +4768,7 @@ func (x *ListInstantOutsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListInstantOutsRequest.ProtoReflect.Descriptor instead. func (*ListInstantOutsRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{52} + return file_client_proto_rawDescGZIP(), []int{54} } type ListInstantOutsResponse struct { @@ -4643,7 +4781,7 @@ type ListInstantOutsResponse struct { func (x *ListInstantOutsResponse) Reset() { *x = ListInstantOutsResponse{} - mi := &file_client_proto_msgTypes[53] + mi := &file_client_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4655,7 +4793,7 @@ func (x *ListInstantOutsResponse) String() string { func (*ListInstantOutsResponse) ProtoMessage() {} func (x *ListInstantOutsResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[53] + mi := &file_client_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4668,7 +4806,7 @@ func (x *ListInstantOutsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListInstantOutsResponse.ProtoReflect.Descriptor instead. func (*ListInstantOutsResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{53} + return file_client_proto_rawDescGZIP(), []int{55} } func (x *ListInstantOutsResponse) GetSwaps() []*InstantOut { @@ -4696,7 +4834,7 @@ type InstantOut struct { func (x *InstantOut) Reset() { *x = InstantOut{} - mi := &file_client_proto_msgTypes[54] + mi := &file_client_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4708,7 +4846,7 @@ func (x *InstantOut) String() string { func (*InstantOut) ProtoMessage() {} func (x *InstantOut) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[54] + mi := &file_client_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4721,7 +4859,7 @@ func (x *InstantOut) ProtoReflect() protoreflect.Message { // Deprecated: Use InstantOut.ProtoReflect.Descriptor instead. func (*InstantOut) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{54} + return file_client_proto_rawDescGZIP(), []int{56} } func (x *InstantOut) GetSwapHash() []byte { @@ -4769,7 +4907,7 @@ type NewStaticAddressRequest struct { func (x *NewStaticAddressRequest) Reset() { *x = NewStaticAddressRequest{} - mi := &file_client_proto_msgTypes[55] + mi := &file_client_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4781,7 +4919,7 @@ func (x *NewStaticAddressRequest) String() string { func (*NewStaticAddressRequest) ProtoMessage() {} func (x *NewStaticAddressRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[55] + mi := &file_client_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4794,7 +4932,7 @@ func (x *NewStaticAddressRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use NewStaticAddressRequest.ProtoReflect.Descriptor instead. func (*NewStaticAddressRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{55} + return file_client_proto_rawDescGZIP(), []int{57} } func (x *NewStaticAddressRequest) GetClientKey() []byte { @@ -4816,7 +4954,7 @@ type NewStaticAddressResponse struct { func (x *NewStaticAddressResponse) Reset() { *x = NewStaticAddressResponse{} - mi := &file_client_proto_msgTypes[56] + mi := &file_client_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4828,7 +4966,7 @@ func (x *NewStaticAddressResponse) String() string { func (*NewStaticAddressResponse) ProtoMessage() {} func (x *NewStaticAddressResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[56] + mi := &file_client_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4841,7 +4979,7 @@ func (x *NewStaticAddressResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NewStaticAddressResponse.ProtoReflect.Descriptor instead. func (*NewStaticAddressResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{56} + return file_client_proto_rawDescGZIP(), []int{58} } func (x *NewStaticAddressResponse) GetAddress() string { @@ -4871,7 +5009,7 @@ type ListUnspentDepositsRequest struct { func (x *ListUnspentDepositsRequest) Reset() { *x = ListUnspentDepositsRequest{} - mi := &file_client_proto_msgTypes[57] + mi := &file_client_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4883,7 +5021,7 @@ func (x *ListUnspentDepositsRequest) String() string { func (*ListUnspentDepositsRequest) ProtoMessage() {} func (x *ListUnspentDepositsRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[57] + mi := &file_client_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4896,7 +5034,7 @@ func (x *ListUnspentDepositsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUnspentDepositsRequest.ProtoReflect.Descriptor instead. func (*ListUnspentDepositsRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{57} + return file_client_proto_rawDescGZIP(), []int{59} } func (x *ListUnspentDepositsRequest) GetMinConfs() int32 { @@ -4923,7 +5061,7 @@ type ListUnspentDepositsResponse struct { func (x *ListUnspentDepositsResponse) Reset() { *x = ListUnspentDepositsResponse{} - mi := &file_client_proto_msgTypes[58] + mi := &file_client_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4935,7 +5073,7 @@ func (x *ListUnspentDepositsResponse) String() string { func (*ListUnspentDepositsResponse) ProtoMessage() {} func (x *ListUnspentDepositsResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[58] + mi := &file_client_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4948,7 +5086,7 @@ func (x *ListUnspentDepositsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListUnspentDepositsResponse.ProtoReflect.Descriptor instead. func (*ListUnspentDepositsResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{58} + return file_client_proto_rawDescGZIP(), []int{60} } func (x *ListUnspentDepositsResponse) GetUtxos() []*Utxo { @@ -4974,7 +5112,7 @@ type Utxo struct { func (x *Utxo) Reset() { *x = Utxo{} - mi := &file_client_proto_msgTypes[59] + mi := &file_client_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4986,7 +5124,7 @@ func (x *Utxo) String() string { func (*Utxo) ProtoMessage() {} func (x *Utxo) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[59] + mi := &file_client_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4999,7 +5137,7 @@ func (x *Utxo) ProtoReflect() protoreflect.Message { // Deprecated: Use Utxo.ProtoReflect.Descriptor instead. func (*Utxo) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{59} + return file_client_proto_rawDescGZIP(), []int{61} } func (x *Utxo) GetStaticAddress() string { @@ -5052,7 +5190,7 @@ type WithdrawDepositsRequest struct { func (x *WithdrawDepositsRequest) Reset() { *x = WithdrawDepositsRequest{} - mi := &file_client_proto_msgTypes[60] + mi := &file_client_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5064,7 +5202,7 @@ func (x *WithdrawDepositsRequest) String() string { func (*WithdrawDepositsRequest) ProtoMessage() {} func (x *WithdrawDepositsRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[60] + mi := &file_client_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5077,7 +5215,7 @@ func (x *WithdrawDepositsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WithdrawDepositsRequest.ProtoReflect.Descriptor instead. func (*WithdrawDepositsRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{60} + return file_client_proto_rawDescGZIP(), []int{62} } func (x *WithdrawDepositsRequest) GetOutpoints() []*lnrpc.OutPoint { @@ -5127,7 +5265,7 @@ type WithdrawDepositsResponse struct { func (x *WithdrawDepositsResponse) Reset() { *x = WithdrawDepositsResponse{} - mi := &file_client_proto_msgTypes[61] + mi := &file_client_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5139,7 +5277,7 @@ func (x *WithdrawDepositsResponse) String() string { func (*WithdrawDepositsResponse) ProtoMessage() {} func (x *WithdrawDepositsResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[61] + mi := &file_client_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5152,7 +5290,7 @@ func (x *WithdrawDepositsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WithdrawDepositsResponse.ProtoReflect.Descriptor instead. func (*WithdrawDepositsResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{61} + return file_client_proto_rawDescGZIP(), []int{63} } func (x *WithdrawDepositsResponse) GetWithdrawalTxHash() string { @@ -5181,7 +5319,7 @@ type ListStaticAddressDepositsRequest struct { func (x *ListStaticAddressDepositsRequest) Reset() { *x = ListStaticAddressDepositsRequest{} - mi := &file_client_proto_msgTypes[62] + mi := &file_client_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5193,7 +5331,7 @@ func (x *ListStaticAddressDepositsRequest) String() string { func (*ListStaticAddressDepositsRequest) ProtoMessage() {} func (x *ListStaticAddressDepositsRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[62] + mi := &file_client_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5206,7 +5344,7 @@ func (x *ListStaticAddressDepositsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListStaticAddressDepositsRequest.ProtoReflect.Descriptor instead. func (*ListStaticAddressDepositsRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{62} + return file_client_proto_rawDescGZIP(), []int{64} } func (x *ListStaticAddressDepositsRequest) GetStateFilter() DepositState { @@ -5233,7 +5371,7 @@ type ListStaticAddressDepositsResponse struct { func (x *ListStaticAddressDepositsResponse) Reset() { *x = ListStaticAddressDepositsResponse{} - mi := &file_client_proto_msgTypes[63] + mi := &file_client_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5245,7 +5383,7 @@ func (x *ListStaticAddressDepositsResponse) String() string { func (*ListStaticAddressDepositsResponse) ProtoMessage() {} func (x *ListStaticAddressDepositsResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[63] + mi := &file_client_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5258,7 +5396,7 @@ func (x *ListStaticAddressDepositsResponse) ProtoReflect() protoreflect.Message // Deprecated: Use ListStaticAddressDepositsResponse.ProtoReflect.Descriptor instead. func (*ListStaticAddressDepositsResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{63} + return file_client_proto_rawDescGZIP(), []int{65} } func (x *ListStaticAddressDepositsResponse) GetFilteredDeposits() []*Deposit { @@ -5276,7 +5414,7 @@ type ListStaticAddressWithdrawalRequest struct { func (x *ListStaticAddressWithdrawalRequest) Reset() { *x = ListStaticAddressWithdrawalRequest{} - mi := &file_client_proto_msgTypes[64] + mi := &file_client_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5288,7 +5426,7 @@ func (x *ListStaticAddressWithdrawalRequest) String() string { func (*ListStaticAddressWithdrawalRequest) ProtoMessage() {} func (x *ListStaticAddressWithdrawalRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[64] + mi := &file_client_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5301,7 +5439,7 @@ func (x *ListStaticAddressWithdrawalRequest) ProtoReflect() protoreflect.Message // Deprecated: Use ListStaticAddressWithdrawalRequest.ProtoReflect.Descriptor instead. func (*ListStaticAddressWithdrawalRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{64} + return file_client_proto_rawDescGZIP(), []int{66} } type ListStaticAddressWithdrawalResponse struct { @@ -5314,7 +5452,7 @@ type ListStaticAddressWithdrawalResponse struct { func (x *ListStaticAddressWithdrawalResponse) Reset() { *x = ListStaticAddressWithdrawalResponse{} - mi := &file_client_proto_msgTypes[65] + mi := &file_client_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5326,7 +5464,7 @@ func (x *ListStaticAddressWithdrawalResponse) String() string { func (*ListStaticAddressWithdrawalResponse) ProtoMessage() {} func (x *ListStaticAddressWithdrawalResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[65] + mi := &file_client_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5339,7 +5477,7 @@ func (x *ListStaticAddressWithdrawalResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use ListStaticAddressWithdrawalResponse.ProtoReflect.Descriptor instead. func (*ListStaticAddressWithdrawalResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{65} + return file_client_proto_rawDescGZIP(), []int{67} } func (x *ListStaticAddressWithdrawalResponse) GetWithdrawals() []*StaticAddressWithdrawal { @@ -5357,7 +5495,7 @@ type ListStaticAddressSwapsRequest struct { func (x *ListStaticAddressSwapsRequest) Reset() { *x = ListStaticAddressSwapsRequest{} - mi := &file_client_proto_msgTypes[66] + mi := &file_client_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5369,7 +5507,7 @@ func (x *ListStaticAddressSwapsRequest) String() string { func (*ListStaticAddressSwapsRequest) ProtoMessage() {} func (x *ListStaticAddressSwapsRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[66] + mi := &file_client_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5382,7 +5520,7 @@ func (x *ListStaticAddressSwapsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListStaticAddressSwapsRequest.ProtoReflect.Descriptor instead. func (*ListStaticAddressSwapsRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{66} + return file_client_proto_rawDescGZIP(), []int{68} } type ListStaticAddressSwapsResponse struct { @@ -5395,7 +5533,7 @@ type ListStaticAddressSwapsResponse struct { func (x *ListStaticAddressSwapsResponse) Reset() { *x = ListStaticAddressSwapsResponse{} - mi := &file_client_proto_msgTypes[67] + mi := &file_client_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5407,7 +5545,7 @@ func (x *ListStaticAddressSwapsResponse) String() string { func (*ListStaticAddressSwapsResponse) ProtoMessage() {} func (x *ListStaticAddressSwapsResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[67] + mi := &file_client_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5420,7 +5558,7 @@ func (x *ListStaticAddressSwapsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListStaticAddressSwapsResponse.ProtoReflect.Descriptor instead. func (*ListStaticAddressSwapsResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{67} + return file_client_proto_rawDescGZIP(), []int{69} } func (x *ListStaticAddressSwapsResponse) GetSwaps() []*StaticAddressLoopInSwap { @@ -5438,7 +5576,7 @@ type StaticAddressSummaryRequest struct { func (x *StaticAddressSummaryRequest) Reset() { *x = StaticAddressSummaryRequest{} - mi := &file_client_proto_msgTypes[68] + mi := &file_client_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5450,7 +5588,7 @@ func (x *StaticAddressSummaryRequest) String() string { func (*StaticAddressSummaryRequest) ProtoMessage() {} func (x *StaticAddressSummaryRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[68] + mi := &file_client_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5463,7 +5601,7 @@ func (x *StaticAddressSummaryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticAddressSummaryRequest.ProtoReflect.Descriptor instead. func (*StaticAddressSummaryRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{68} + return file_client_proto_rawDescGZIP(), []int{70} } type StaticAddressSummaryResponse struct { @@ -5494,7 +5632,7 @@ type StaticAddressSummaryResponse struct { func (x *StaticAddressSummaryResponse) Reset() { *x = StaticAddressSummaryResponse{} - mi := &file_client_proto_msgTypes[69] + mi := &file_client_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5506,7 +5644,7 @@ func (x *StaticAddressSummaryResponse) String() string { func (*StaticAddressSummaryResponse) ProtoMessage() {} func (x *StaticAddressSummaryResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[69] + mi := &file_client_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5519,7 +5657,7 @@ func (x *StaticAddressSummaryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticAddressSummaryResponse.ProtoReflect.Descriptor instead. func (*StaticAddressSummaryResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{69} + return file_client_proto_rawDescGZIP(), []int{71} } func (x *StaticAddressSummaryResponse) GetStaticAddress() string { @@ -5616,7 +5754,7 @@ type Deposit struct { func (x *Deposit) Reset() { *x = Deposit{} - mi := &file_client_proto_msgTypes[70] + mi := &file_client_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5628,7 +5766,7 @@ func (x *Deposit) String() string { func (*Deposit) ProtoMessage() {} func (x *Deposit) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[70] + mi := &file_client_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5641,7 +5779,7 @@ func (x *Deposit) ProtoReflect() protoreflect.Message { // Deprecated: Use Deposit.ProtoReflect.Descriptor instead. func (*Deposit) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{70} + return file_client_proto_rawDescGZIP(), []int{72} } func (x *Deposit) GetId() []byte { @@ -5715,7 +5853,7 @@ type StaticAddressWithdrawal struct { func (x *StaticAddressWithdrawal) Reset() { *x = StaticAddressWithdrawal{} - mi := &file_client_proto_msgTypes[71] + mi := &file_client_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5727,7 +5865,7 @@ func (x *StaticAddressWithdrawal) String() string { func (*StaticAddressWithdrawal) ProtoMessage() {} func (x *StaticAddressWithdrawal) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[71] + mi := &file_client_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5740,7 +5878,7 @@ func (x *StaticAddressWithdrawal) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticAddressWithdrawal.ProtoReflect.Descriptor instead. func (*StaticAddressWithdrawal) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{71} + return file_client_proto_rawDescGZIP(), []int{73} } func (x *StaticAddressWithdrawal) GetTxId() string { @@ -5805,7 +5943,7 @@ type StaticAddressLoopInSwap struct { func (x *StaticAddressLoopInSwap) Reset() { *x = StaticAddressLoopInSwap{} - mi := &file_client_proto_msgTypes[72] + mi := &file_client_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5817,7 +5955,7 @@ func (x *StaticAddressLoopInSwap) String() string { func (*StaticAddressLoopInSwap) ProtoMessage() {} func (x *StaticAddressLoopInSwap) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[72] + mi := &file_client_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5830,7 +5968,7 @@ func (x *StaticAddressLoopInSwap) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticAddressLoopInSwap.ProtoReflect.Descriptor instead. func (*StaticAddressLoopInSwap) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{72} + return file_client_proto_rawDescGZIP(), []int{74} } func (x *StaticAddressLoopInSwap) GetSwapHash() []byte { @@ -5928,7 +6066,7 @@ type StaticAddressLoopInRequest struct { func (x *StaticAddressLoopInRequest) Reset() { *x = StaticAddressLoopInRequest{} - mi := &file_client_proto_msgTypes[73] + mi := &file_client_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5940,7 +6078,7 @@ func (x *StaticAddressLoopInRequest) String() string { func (*StaticAddressLoopInRequest) ProtoMessage() {} func (x *StaticAddressLoopInRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[73] + mi := &file_client_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5953,7 +6091,7 @@ func (x *StaticAddressLoopInRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticAddressLoopInRequest.ProtoReflect.Descriptor instead. func (*StaticAddressLoopInRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{73} + return file_client_proto_rawDescGZIP(), []int{75} } func (x *StaticAddressLoopInRequest) GetOutpoints() []string { @@ -6073,7 +6211,7 @@ type StaticAddressLoopInResponse struct { func (x *StaticAddressLoopInResponse) Reset() { *x = StaticAddressLoopInResponse{} - mi := &file_client_proto_msgTypes[74] + mi := &file_client_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6085,7 +6223,7 @@ func (x *StaticAddressLoopInResponse) String() string { func (*StaticAddressLoopInResponse) ProtoMessage() {} func (x *StaticAddressLoopInResponse) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[74] + mi := &file_client_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6098,7 +6236,7 @@ func (x *StaticAddressLoopInResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use StaticAddressLoopInResponse.ProtoReflect.Descriptor instead. func (*StaticAddressLoopInResponse) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{74} + return file_client_proto_rawDescGZIP(), []int{76} } func (x *StaticAddressLoopInResponse) GetSwapHash() []byte { @@ -6227,7 +6365,7 @@ type AssetLoopOutRequest struct { func (x *AssetLoopOutRequest) Reset() { *x = AssetLoopOutRequest{} - mi := &file_client_proto_msgTypes[75] + mi := &file_client_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6239,7 +6377,7 @@ func (x *AssetLoopOutRequest) String() string { func (*AssetLoopOutRequest) ProtoMessage() {} func (x *AssetLoopOutRequest) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[75] + mi := &file_client_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6252,7 +6390,7 @@ func (x *AssetLoopOutRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AssetLoopOutRequest.ProtoReflect.Descriptor instead. func (*AssetLoopOutRequest) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{75} + return file_client_proto_rawDescGZIP(), []int{77} } func (x *AssetLoopOutRequest) GetAssetId() []byte { @@ -6307,7 +6445,7 @@ type AssetRfqInfo struct { func (x *AssetRfqInfo) Reset() { *x = AssetRfqInfo{} - mi := &file_client_proto_msgTypes[76] + mi := &file_client_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6319,7 +6457,7 @@ func (x *AssetRfqInfo) String() string { func (*AssetRfqInfo) ProtoMessage() {} func (x *AssetRfqInfo) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[76] + mi := &file_client_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6332,7 +6470,7 @@ func (x *AssetRfqInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use AssetRfqInfo.ProtoReflect.Descriptor instead. func (*AssetRfqInfo) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{76} + return file_client_proto_rawDescGZIP(), []int{78} } func (x *AssetRfqInfo) GetPrepayRfqId() []byte { @@ -6421,7 +6559,7 @@ type FixedPoint struct { func (x *FixedPoint) Reset() { *x = FixedPoint{} - mi := &file_client_proto_msgTypes[77] + mi := &file_client_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6433,7 +6571,7 @@ func (x *FixedPoint) String() string { func (*FixedPoint) ProtoMessage() {} func (x *FixedPoint) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[77] + mi := &file_client_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6446,7 +6584,7 @@ func (x *FixedPoint) ProtoReflect() protoreflect.Message { // Deprecated: Use FixedPoint.ProtoReflect.Descriptor instead. func (*FixedPoint) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{77} + return file_client_proto_rawDescGZIP(), []int{79} } func (x *FixedPoint) GetCoefficient() string { @@ -6477,7 +6615,7 @@ type AssetLoopOutInfo struct { func (x *AssetLoopOutInfo) Reset() { *x = AssetLoopOutInfo{} - mi := &file_client_proto_msgTypes[78] + mi := &file_client_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6489,7 +6627,7 @@ func (x *AssetLoopOutInfo) String() string { func (*AssetLoopOutInfo) ProtoMessage() {} func (x *AssetLoopOutInfo) ProtoReflect() protoreflect.Message { - mi := &file_client_proto_msgTypes[78] + mi := &file_client_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6502,7 +6640,7 @@ func (x *AssetLoopOutInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use AssetLoopOutInfo.ProtoReflect.Descriptor instead. func (*AssetLoopOutInfo) Descriptor() ([]byte, []int) { - return file_client_proto_rawDescGZIP(), []int{78} + return file_client_proto_rawDescGZIP(), []int{80} } func (x *AssetLoopOutInfo) GetAssetId() string { @@ -6700,7 +6838,18 @@ const file_client_proto_rawDesc = "" + "\x0eTokensResponse\x12*\n" + "\x06tokens\x18\x01 \x03(\v2\x12.looprpc.L402TokenR\x06tokens\"\x17\n" + "\x15FetchL402TokenRequest\"\x18\n" + - "\x16FetchL402TokenResponse\"\xcb\x02\n" + + "\x16FetchL402TokenResponse\"1\n" + + "\x0eRecoverRequest\x12\x1f\n" + + "\vbackup_file\x18\x01 \x01(\tR\n" + + "backupFile\"\xa6\x02\n" + + "\x0fRecoverResponse\x12\x1f\n" + + "\vbackup_file\x18\x01 \x01(\tR\n" + + "backupFile\x12#\n" + + "\rrestored_l402\x18\x02 \x01(\bR\frestoredL402\x126\n" + + "\x17restored_static_address\x18\x03 \x01(\bR\x15restoredStaticAddress\x12%\n" + + "\x0estatic_address\x18\x04 \x01(\tR\rstaticAddress\x12,\n" + + "\x12num_deposits_found\x18\x05 \x01(\rR\x10numDepositsFound\x12@\n" + + "\x1cdeposit_reconciliation_error\x18\x06 \x01(\tR\x1adepositReconciliationError\"\xcb\x02\n" + "\tL402Token\x12#\n" + "\rbase_macaroon\x18\x01 \x01(\fR\fbaseMacaroon\x12!\n" + "\fpayment_hash\x18\x02 \x01(\fR\vpaymentHash\x12)\n" + @@ -7030,7 +7179,7 @@ const file_client_proto_rawDesc = "" + "\x1eSUCCEEDED_TRANSITIONING_FAILED\x10\t\x12\x13\n" + "\x0fUNLOCK_DEPOSITS\x10\n" + "\x12\x1e\n" + - "\x1aFAILED_STATIC_ADDRESS_SWAP\x10\v2\xca\x14\n" + + "\x1aFAILED_STATIC_ADDRESS_SWAP\x10\v2\x88\x15\n" + "\n" + "SwapClient\x129\n" + "\aLoopOut\x12\x17.looprpc.LoopOutRequest\x1a\x15.looprpc.SwapResponse\x127\n" + @@ -7048,6 +7197,7 @@ const file_client_proto_rawDesc = "" + "\rGetL402Tokens\x12\x16.looprpc.TokensRequest\x1a\x17.looprpc.TokensResponse\x12@\n" + "\rGetLsatTokens\x12\x16.looprpc.TokensRequest\x1a\x17.looprpc.TokensResponse\x12Q\n" + "\x0eFetchL402Token\x12\x1e.looprpc.FetchL402TokenRequest\x1a\x1f.looprpc.FetchL402TokenResponse\x12<\n" + + "\aRecover\x12\x17.looprpc.RecoverRequest\x1a\x18.looprpc.RecoverResponse\x12<\n" + "\aGetInfo\x12\x17.looprpc.GetInfoRequest\x1a\x18.looprpc.GetInfoResponse\x12E\n" + "\n" + "StopDaemon\x12\x1a.looprpc.StopDaemonRequest\x1a\x1b.looprpc.StopDaemonResponse\x12V\n" + @@ -7082,7 +7232,7 @@ func file_client_proto_rawDescGZIP() []byte { } var file_client_proto_enumTypes = make([]protoimpl.EnumInfo, 9) -var file_client_proto_msgTypes = make([]protoimpl.MessageInfo, 80) +var file_client_proto_msgTypes = make([]protoimpl.MessageInfo, 82) var file_client_proto_goTypes = []any{ (AddressType)(0), // 0: looprpc.AddressType (SwapType)(0), // 1: looprpc.SwapType @@ -7123,117 +7273,119 @@ var file_client_proto_goTypes = []any{ (*TokensResponse)(nil), // 36: looprpc.TokensResponse (*FetchL402TokenRequest)(nil), // 37: looprpc.FetchL402TokenRequest (*FetchL402TokenResponse)(nil), // 38: looprpc.FetchL402TokenResponse - (*L402Token)(nil), // 39: looprpc.L402Token - (*LoopStats)(nil), // 40: looprpc.LoopStats - (*GetInfoRequest)(nil), // 41: looprpc.GetInfoRequest - (*GetInfoResponse)(nil), // 42: looprpc.GetInfoResponse - (*GetLiquidityParamsRequest)(nil), // 43: looprpc.GetLiquidityParamsRequest - (*LiquidityParameters)(nil), // 44: looprpc.LiquidityParameters - (*EasyAssetAutoloopParams)(nil), // 45: looprpc.EasyAssetAutoloopParams - (*LiquidityRule)(nil), // 46: looprpc.LiquidityRule - (*SetLiquidityParamsRequest)(nil), // 47: looprpc.SetLiquidityParamsRequest - (*SetLiquidityParamsResponse)(nil), // 48: looprpc.SetLiquidityParamsResponse - (*SuggestSwapsRequest)(nil), // 49: looprpc.SuggestSwapsRequest - (*Disqualified)(nil), // 50: looprpc.Disqualified - (*SuggestSwapsResponse)(nil), // 51: looprpc.SuggestSwapsResponse - (*AbandonSwapRequest)(nil), // 52: looprpc.AbandonSwapRequest - (*AbandonSwapResponse)(nil), // 53: looprpc.AbandonSwapResponse - (*ListReservationsRequest)(nil), // 54: looprpc.ListReservationsRequest - (*ListReservationsResponse)(nil), // 55: looprpc.ListReservationsResponse - (*ClientReservation)(nil), // 56: looprpc.ClientReservation - (*InstantOutRequest)(nil), // 57: looprpc.InstantOutRequest - (*InstantOutResponse)(nil), // 58: looprpc.InstantOutResponse - (*InstantOutQuoteRequest)(nil), // 59: looprpc.InstantOutQuoteRequest - (*InstantOutQuoteResponse)(nil), // 60: looprpc.InstantOutQuoteResponse - (*ListInstantOutsRequest)(nil), // 61: looprpc.ListInstantOutsRequest - (*ListInstantOutsResponse)(nil), // 62: looprpc.ListInstantOutsResponse - (*InstantOut)(nil), // 63: looprpc.InstantOut - (*NewStaticAddressRequest)(nil), // 64: looprpc.NewStaticAddressRequest - (*NewStaticAddressResponse)(nil), // 65: looprpc.NewStaticAddressResponse - (*ListUnspentDepositsRequest)(nil), // 66: looprpc.ListUnspentDepositsRequest - (*ListUnspentDepositsResponse)(nil), // 67: looprpc.ListUnspentDepositsResponse - (*Utxo)(nil), // 68: looprpc.Utxo - (*WithdrawDepositsRequest)(nil), // 69: looprpc.WithdrawDepositsRequest - (*WithdrawDepositsResponse)(nil), // 70: looprpc.WithdrawDepositsResponse - (*ListStaticAddressDepositsRequest)(nil), // 71: looprpc.ListStaticAddressDepositsRequest - (*ListStaticAddressDepositsResponse)(nil), // 72: looprpc.ListStaticAddressDepositsResponse - (*ListStaticAddressWithdrawalRequest)(nil), // 73: looprpc.ListStaticAddressWithdrawalRequest - (*ListStaticAddressWithdrawalResponse)(nil), // 74: looprpc.ListStaticAddressWithdrawalResponse - (*ListStaticAddressSwapsRequest)(nil), // 75: looprpc.ListStaticAddressSwapsRequest - (*ListStaticAddressSwapsResponse)(nil), // 76: looprpc.ListStaticAddressSwapsResponse - (*StaticAddressSummaryRequest)(nil), // 77: looprpc.StaticAddressSummaryRequest - (*StaticAddressSummaryResponse)(nil), // 78: looprpc.StaticAddressSummaryResponse - (*Deposit)(nil), // 79: looprpc.Deposit - (*StaticAddressWithdrawal)(nil), // 80: looprpc.StaticAddressWithdrawal - (*StaticAddressLoopInSwap)(nil), // 81: looprpc.StaticAddressLoopInSwap - (*StaticAddressLoopInRequest)(nil), // 82: looprpc.StaticAddressLoopInRequest - (*StaticAddressLoopInResponse)(nil), // 83: looprpc.StaticAddressLoopInResponse - (*AssetLoopOutRequest)(nil), // 84: looprpc.AssetLoopOutRequest - (*AssetRfqInfo)(nil), // 85: looprpc.AssetRfqInfo - (*FixedPoint)(nil), // 86: looprpc.FixedPoint - (*AssetLoopOutInfo)(nil), // 87: looprpc.AssetLoopOutInfo - nil, // 88: looprpc.LiquidityParameters.EasyAssetParamsEntry - (*lnrpc.OpenChannelRequest)(nil), // 89: lnrpc.OpenChannelRequest - (*swapserverrpc.RouteHint)(nil), // 90: looprpc.RouteHint - (*lnrpc.OutPoint)(nil), // 91: lnrpc.OutPoint + (*RecoverRequest)(nil), // 39: looprpc.RecoverRequest + (*RecoverResponse)(nil), // 40: looprpc.RecoverResponse + (*L402Token)(nil), // 41: looprpc.L402Token + (*LoopStats)(nil), // 42: looprpc.LoopStats + (*GetInfoRequest)(nil), // 43: looprpc.GetInfoRequest + (*GetInfoResponse)(nil), // 44: looprpc.GetInfoResponse + (*GetLiquidityParamsRequest)(nil), // 45: looprpc.GetLiquidityParamsRequest + (*LiquidityParameters)(nil), // 46: looprpc.LiquidityParameters + (*EasyAssetAutoloopParams)(nil), // 47: looprpc.EasyAssetAutoloopParams + (*LiquidityRule)(nil), // 48: looprpc.LiquidityRule + (*SetLiquidityParamsRequest)(nil), // 49: looprpc.SetLiquidityParamsRequest + (*SetLiquidityParamsResponse)(nil), // 50: looprpc.SetLiquidityParamsResponse + (*SuggestSwapsRequest)(nil), // 51: looprpc.SuggestSwapsRequest + (*Disqualified)(nil), // 52: looprpc.Disqualified + (*SuggestSwapsResponse)(nil), // 53: looprpc.SuggestSwapsResponse + (*AbandonSwapRequest)(nil), // 54: looprpc.AbandonSwapRequest + (*AbandonSwapResponse)(nil), // 55: looprpc.AbandonSwapResponse + (*ListReservationsRequest)(nil), // 56: looprpc.ListReservationsRequest + (*ListReservationsResponse)(nil), // 57: looprpc.ListReservationsResponse + (*ClientReservation)(nil), // 58: looprpc.ClientReservation + (*InstantOutRequest)(nil), // 59: looprpc.InstantOutRequest + (*InstantOutResponse)(nil), // 60: looprpc.InstantOutResponse + (*InstantOutQuoteRequest)(nil), // 61: looprpc.InstantOutQuoteRequest + (*InstantOutQuoteResponse)(nil), // 62: looprpc.InstantOutQuoteResponse + (*ListInstantOutsRequest)(nil), // 63: looprpc.ListInstantOutsRequest + (*ListInstantOutsResponse)(nil), // 64: looprpc.ListInstantOutsResponse + (*InstantOut)(nil), // 65: looprpc.InstantOut + (*NewStaticAddressRequest)(nil), // 66: looprpc.NewStaticAddressRequest + (*NewStaticAddressResponse)(nil), // 67: looprpc.NewStaticAddressResponse + (*ListUnspentDepositsRequest)(nil), // 68: looprpc.ListUnspentDepositsRequest + (*ListUnspentDepositsResponse)(nil), // 69: looprpc.ListUnspentDepositsResponse + (*Utxo)(nil), // 70: looprpc.Utxo + (*WithdrawDepositsRequest)(nil), // 71: looprpc.WithdrawDepositsRequest + (*WithdrawDepositsResponse)(nil), // 72: looprpc.WithdrawDepositsResponse + (*ListStaticAddressDepositsRequest)(nil), // 73: looprpc.ListStaticAddressDepositsRequest + (*ListStaticAddressDepositsResponse)(nil), // 74: looprpc.ListStaticAddressDepositsResponse + (*ListStaticAddressWithdrawalRequest)(nil), // 75: looprpc.ListStaticAddressWithdrawalRequest + (*ListStaticAddressWithdrawalResponse)(nil), // 76: looprpc.ListStaticAddressWithdrawalResponse + (*ListStaticAddressSwapsRequest)(nil), // 77: looprpc.ListStaticAddressSwapsRequest + (*ListStaticAddressSwapsResponse)(nil), // 78: looprpc.ListStaticAddressSwapsResponse + (*StaticAddressSummaryRequest)(nil), // 79: looprpc.StaticAddressSummaryRequest + (*StaticAddressSummaryResponse)(nil), // 80: looprpc.StaticAddressSummaryResponse + (*Deposit)(nil), // 81: looprpc.Deposit + (*StaticAddressWithdrawal)(nil), // 82: looprpc.StaticAddressWithdrawal + (*StaticAddressLoopInSwap)(nil), // 83: looprpc.StaticAddressLoopInSwap + (*StaticAddressLoopInRequest)(nil), // 84: looprpc.StaticAddressLoopInRequest + (*StaticAddressLoopInResponse)(nil), // 85: looprpc.StaticAddressLoopInResponse + (*AssetLoopOutRequest)(nil), // 86: looprpc.AssetLoopOutRequest + (*AssetRfqInfo)(nil), // 87: looprpc.AssetRfqInfo + (*FixedPoint)(nil), // 88: looprpc.FixedPoint + (*AssetLoopOutInfo)(nil), // 89: looprpc.AssetLoopOutInfo + nil, // 90: looprpc.LiquidityParameters.EasyAssetParamsEntry + (*lnrpc.OpenChannelRequest)(nil), // 91: lnrpc.OpenChannelRequest + (*swapserverrpc.RouteHint)(nil), // 92: looprpc.RouteHint + (*lnrpc.OutPoint)(nil), // 93: lnrpc.OutPoint } var file_client_proto_depIdxs = []int32{ - 89, // 0: looprpc.StaticOpenChannelRequest.open_channel_request:type_name -> lnrpc.OpenChannelRequest + 91, // 0: looprpc.StaticOpenChannelRequest.open_channel_request:type_name -> lnrpc.OpenChannelRequest 0, // 1: looprpc.LoopOutRequest.account_addr_type:type_name -> looprpc.AddressType - 84, // 2: looprpc.LoopOutRequest.asset_info:type_name -> looprpc.AssetLoopOutRequest - 85, // 3: looprpc.LoopOutRequest.asset_rfq_info:type_name -> looprpc.AssetRfqInfo - 90, // 4: looprpc.LoopInRequest.route_hints:type_name -> looprpc.RouteHint + 86, // 2: looprpc.LoopOutRequest.asset_info:type_name -> looprpc.AssetLoopOutRequest + 87, // 3: looprpc.LoopOutRequest.asset_rfq_info:type_name -> looprpc.AssetRfqInfo + 92, // 4: looprpc.LoopInRequest.route_hints:type_name -> looprpc.RouteHint 1, // 5: looprpc.SwapStatus.type:type_name -> looprpc.SwapType 2, // 6: looprpc.SwapStatus.state:type_name -> looprpc.SwapState 3, // 7: looprpc.SwapStatus.failure_reason:type_name -> looprpc.FailureReason - 87, // 8: looprpc.SwapStatus.asset_info:type_name -> looprpc.AssetLoopOutInfo + 89, // 8: looprpc.SwapStatus.asset_info:type_name -> looprpc.AssetLoopOutInfo 19, // 9: looprpc.ListSwapsRequest.list_swap_filter:type_name -> looprpc.ListSwapsFilter 8, // 10: looprpc.ListSwapsFilter.swap_type:type_name -> looprpc.ListSwapsFilter.SwapTypeFilter 17, // 11: looprpc.ListSwapsResponse.swaps:type_name -> looprpc.SwapStatus 23, // 12: looprpc.SweepHtlcResponse.not_requested:type_name -> looprpc.PublishNotRequested 24, // 13: looprpc.SweepHtlcResponse.published:type_name -> looprpc.PublishSucceeded 25, // 14: looprpc.SweepHtlcResponse.failed:type_name -> looprpc.PublishFailed - 90, // 15: looprpc.QuoteRequest.loop_in_route_hints:type_name -> looprpc.RouteHint - 84, // 16: looprpc.QuoteRequest.asset_info:type_name -> looprpc.AssetLoopOutRequest - 85, // 17: looprpc.OutQuoteResponse.asset_rfq_info:type_name -> looprpc.AssetRfqInfo - 90, // 18: looprpc.ProbeRequest.route_hints:type_name -> looprpc.RouteHint - 39, // 19: looprpc.TokensResponse.tokens:type_name -> looprpc.L402Token - 40, // 20: looprpc.GetInfoResponse.loop_out_stats:type_name -> looprpc.LoopStats - 40, // 21: looprpc.GetInfoResponse.loop_in_stats:type_name -> looprpc.LoopStats - 46, // 22: looprpc.LiquidityParameters.rules:type_name -> looprpc.LiquidityRule + 92, // 15: looprpc.QuoteRequest.loop_in_route_hints:type_name -> looprpc.RouteHint + 86, // 16: looprpc.QuoteRequest.asset_info:type_name -> looprpc.AssetLoopOutRequest + 87, // 17: looprpc.OutQuoteResponse.asset_rfq_info:type_name -> looprpc.AssetRfqInfo + 92, // 18: looprpc.ProbeRequest.route_hints:type_name -> looprpc.RouteHint + 41, // 19: looprpc.TokensResponse.tokens:type_name -> looprpc.L402Token + 42, // 20: looprpc.GetInfoResponse.loop_out_stats:type_name -> looprpc.LoopStats + 42, // 21: looprpc.GetInfoResponse.loop_in_stats:type_name -> looprpc.LoopStats + 48, // 22: looprpc.LiquidityParameters.rules:type_name -> looprpc.LiquidityRule 0, // 23: looprpc.LiquidityParameters.account_addr_type:type_name -> looprpc.AddressType - 88, // 24: looprpc.LiquidityParameters.easy_asset_params:type_name -> looprpc.LiquidityParameters.EasyAssetParamsEntry + 90, // 24: looprpc.LiquidityParameters.easy_asset_params:type_name -> looprpc.LiquidityParameters.EasyAssetParamsEntry 1, // 25: looprpc.LiquidityRule.swap_type:type_name -> looprpc.SwapType 4, // 26: looprpc.LiquidityRule.type:type_name -> looprpc.LiquidityRuleType - 44, // 27: looprpc.SetLiquidityParamsRequest.parameters:type_name -> looprpc.LiquidityParameters + 46, // 27: looprpc.SetLiquidityParamsRequest.parameters:type_name -> looprpc.LiquidityParameters 5, // 28: looprpc.Disqualified.reason:type_name -> looprpc.AutoReason 13, // 29: looprpc.SuggestSwapsResponse.loop_out:type_name -> looprpc.LoopOutRequest 14, // 30: looprpc.SuggestSwapsResponse.loop_in:type_name -> looprpc.LoopInRequest - 50, // 31: looprpc.SuggestSwapsResponse.disqualified:type_name -> looprpc.Disqualified - 56, // 32: looprpc.ListReservationsResponse.reservations:type_name -> looprpc.ClientReservation - 63, // 33: looprpc.ListInstantOutsResponse.swaps:type_name -> looprpc.InstantOut - 68, // 34: looprpc.ListUnspentDepositsResponse.utxos:type_name -> looprpc.Utxo - 91, // 35: looprpc.WithdrawDepositsRequest.outpoints:type_name -> lnrpc.OutPoint + 52, // 31: looprpc.SuggestSwapsResponse.disqualified:type_name -> looprpc.Disqualified + 58, // 32: looprpc.ListReservationsResponse.reservations:type_name -> looprpc.ClientReservation + 65, // 33: looprpc.ListInstantOutsResponse.swaps:type_name -> looprpc.InstantOut + 70, // 34: looprpc.ListUnspentDepositsResponse.utxos:type_name -> looprpc.Utxo + 93, // 35: looprpc.WithdrawDepositsRequest.outpoints:type_name -> lnrpc.OutPoint 6, // 36: looprpc.ListStaticAddressDepositsRequest.state_filter:type_name -> looprpc.DepositState - 79, // 37: looprpc.ListStaticAddressDepositsResponse.filtered_deposits:type_name -> looprpc.Deposit - 80, // 38: looprpc.ListStaticAddressWithdrawalResponse.withdrawals:type_name -> looprpc.StaticAddressWithdrawal - 81, // 39: looprpc.ListStaticAddressSwapsResponse.swaps:type_name -> looprpc.StaticAddressLoopInSwap + 81, // 37: looprpc.ListStaticAddressDepositsResponse.filtered_deposits:type_name -> looprpc.Deposit + 82, // 38: looprpc.ListStaticAddressWithdrawalResponse.withdrawals:type_name -> looprpc.StaticAddressWithdrawal + 83, // 39: looprpc.ListStaticAddressSwapsResponse.swaps:type_name -> looprpc.StaticAddressLoopInSwap 6, // 40: looprpc.Deposit.state:type_name -> looprpc.DepositState - 79, // 41: looprpc.StaticAddressWithdrawal.deposits:type_name -> looprpc.Deposit + 81, // 41: looprpc.StaticAddressWithdrawal.deposits:type_name -> looprpc.Deposit 7, // 42: looprpc.StaticAddressLoopInSwap.state:type_name -> looprpc.StaticAddressLoopInSwapState - 79, // 43: looprpc.StaticAddressLoopInSwap.deposits:type_name -> looprpc.Deposit - 90, // 44: looprpc.StaticAddressLoopInRequest.route_hints:type_name -> looprpc.RouteHint - 79, // 45: looprpc.StaticAddressLoopInResponse.used_deposits:type_name -> looprpc.Deposit - 86, // 46: looprpc.AssetRfqInfo.prepay_asset_rate:type_name -> looprpc.FixedPoint - 86, // 47: looprpc.AssetRfqInfo.swap_asset_rate:type_name -> looprpc.FixedPoint - 45, // 48: looprpc.LiquidityParameters.EasyAssetParamsEntry.value:type_name -> looprpc.EasyAssetAutoloopParams + 81, // 43: looprpc.StaticAddressLoopInSwap.deposits:type_name -> looprpc.Deposit + 92, // 44: looprpc.StaticAddressLoopInRequest.route_hints:type_name -> looprpc.RouteHint + 81, // 45: looprpc.StaticAddressLoopInResponse.used_deposits:type_name -> looprpc.Deposit + 88, // 46: looprpc.AssetRfqInfo.prepay_asset_rate:type_name -> looprpc.FixedPoint + 88, // 47: looprpc.AssetRfqInfo.swap_asset_rate:type_name -> looprpc.FixedPoint + 47, // 48: looprpc.LiquidityParameters.EasyAssetParamsEntry.value:type_name -> looprpc.EasyAssetAutoloopParams 13, // 49: looprpc.SwapClient.LoopOut:input_type -> looprpc.LoopOutRequest 14, // 50: looprpc.SwapClient.LoopIn:input_type -> looprpc.LoopInRequest 16, // 51: looprpc.SwapClient.Monitor:input_type -> looprpc.MonitorRequest 18, // 52: looprpc.SwapClient.ListSwaps:input_type -> looprpc.ListSwapsRequest 21, // 53: looprpc.SwapClient.SweepHtlc:input_type -> looprpc.SweepHtlcRequest 26, // 54: looprpc.SwapClient.SwapInfo:input_type -> looprpc.SwapInfoRequest - 52, // 55: looprpc.SwapClient.AbandonSwap:input_type -> looprpc.AbandonSwapRequest + 54, // 55: looprpc.SwapClient.AbandonSwap:input_type -> looprpc.AbandonSwapRequest 27, // 56: looprpc.SwapClient.LoopOutTerms:input_type -> looprpc.TermsRequest 30, // 57: looprpc.SwapClient.LoopOutQuote:input_type -> looprpc.QuoteRequest 27, // 58: looprpc.SwapClient.GetLoopInTerms:input_type -> looprpc.TermsRequest @@ -7242,59 +7394,61 @@ var file_client_proto_depIdxs = []int32{ 35, // 61: looprpc.SwapClient.GetL402Tokens:input_type -> looprpc.TokensRequest 35, // 62: looprpc.SwapClient.GetLsatTokens:input_type -> looprpc.TokensRequest 37, // 63: looprpc.SwapClient.FetchL402Token:input_type -> looprpc.FetchL402TokenRequest - 41, // 64: looprpc.SwapClient.GetInfo:input_type -> looprpc.GetInfoRequest - 11, // 65: looprpc.SwapClient.StopDaemon:input_type -> looprpc.StopDaemonRequest - 43, // 66: looprpc.SwapClient.GetLiquidityParams:input_type -> looprpc.GetLiquidityParamsRequest - 47, // 67: looprpc.SwapClient.SetLiquidityParams:input_type -> looprpc.SetLiquidityParamsRequest - 49, // 68: looprpc.SwapClient.SuggestSwaps:input_type -> looprpc.SuggestSwapsRequest - 54, // 69: looprpc.SwapClient.ListReservations:input_type -> looprpc.ListReservationsRequest - 57, // 70: looprpc.SwapClient.InstantOut:input_type -> looprpc.InstantOutRequest - 59, // 71: looprpc.SwapClient.InstantOutQuote:input_type -> looprpc.InstantOutQuoteRequest - 61, // 72: looprpc.SwapClient.ListInstantOuts:input_type -> looprpc.ListInstantOutsRequest - 64, // 73: looprpc.SwapClient.NewStaticAddress:input_type -> looprpc.NewStaticAddressRequest - 66, // 74: looprpc.SwapClient.ListUnspentDeposits:input_type -> looprpc.ListUnspentDepositsRequest - 69, // 75: looprpc.SwapClient.WithdrawDeposits:input_type -> looprpc.WithdrawDepositsRequest - 71, // 76: looprpc.SwapClient.ListStaticAddressDeposits:input_type -> looprpc.ListStaticAddressDepositsRequest - 73, // 77: looprpc.SwapClient.ListStaticAddressWithdrawals:input_type -> looprpc.ListStaticAddressWithdrawalRequest - 75, // 78: looprpc.SwapClient.ListStaticAddressSwaps:input_type -> looprpc.ListStaticAddressSwapsRequest - 77, // 79: looprpc.SwapClient.GetStaticAddressSummary:input_type -> looprpc.StaticAddressSummaryRequest - 82, // 80: looprpc.SwapClient.StaticAddressLoopIn:input_type -> looprpc.StaticAddressLoopInRequest - 9, // 81: looprpc.SwapClient.StaticOpenChannel:input_type -> looprpc.StaticOpenChannelRequest - 15, // 82: looprpc.SwapClient.LoopOut:output_type -> looprpc.SwapResponse - 15, // 83: looprpc.SwapClient.LoopIn:output_type -> looprpc.SwapResponse - 17, // 84: looprpc.SwapClient.Monitor:output_type -> looprpc.SwapStatus - 20, // 85: looprpc.SwapClient.ListSwaps:output_type -> looprpc.ListSwapsResponse - 22, // 86: looprpc.SwapClient.SweepHtlc:output_type -> looprpc.SweepHtlcResponse - 17, // 87: looprpc.SwapClient.SwapInfo:output_type -> looprpc.SwapStatus - 53, // 88: looprpc.SwapClient.AbandonSwap:output_type -> looprpc.AbandonSwapResponse - 29, // 89: looprpc.SwapClient.LoopOutTerms:output_type -> looprpc.OutTermsResponse - 32, // 90: looprpc.SwapClient.LoopOutQuote:output_type -> looprpc.OutQuoteResponse - 28, // 91: looprpc.SwapClient.GetLoopInTerms:output_type -> looprpc.InTermsResponse - 31, // 92: looprpc.SwapClient.GetLoopInQuote:output_type -> looprpc.InQuoteResponse - 34, // 93: looprpc.SwapClient.Probe:output_type -> looprpc.ProbeResponse - 36, // 94: looprpc.SwapClient.GetL402Tokens:output_type -> looprpc.TokensResponse - 36, // 95: looprpc.SwapClient.GetLsatTokens:output_type -> looprpc.TokensResponse - 38, // 96: looprpc.SwapClient.FetchL402Token:output_type -> looprpc.FetchL402TokenResponse - 42, // 97: looprpc.SwapClient.GetInfo:output_type -> looprpc.GetInfoResponse - 12, // 98: looprpc.SwapClient.StopDaemon:output_type -> looprpc.StopDaemonResponse - 44, // 99: looprpc.SwapClient.GetLiquidityParams:output_type -> looprpc.LiquidityParameters - 48, // 100: looprpc.SwapClient.SetLiquidityParams:output_type -> looprpc.SetLiquidityParamsResponse - 51, // 101: looprpc.SwapClient.SuggestSwaps:output_type -> looprpc.SuggestSwapsResponse - 55, // 102: looprpc.SwapClient.ListReservations:output_type -> looprpc.ListReservationsResponse - 58, // 103: looprpc.SwapClient.InstantOut:output_type -> looprpc.InstantOutResponse - 60, // 104: looprpc.SwapClient.InstantOutQuote:output_type -> looprpc.InstantOutQuoteResponse - 62, // 105: looprpc.SwapClient.ListInstantOuts:output_type -> looprpc.ListInstantOutsResponse - 65, // 106: looprpc.SwapClient.NewStaticAddress:output_type -> looprpc.NewStaticAddressResponse - 67, // 107: looprpc.SwapClient.ListUnspentDeposits:output_type -> looprpc.ListUnspentDepositsResponse - 70, // 108: looprpc.SwapClient.WithdrawDeposits:output_type -> looprpc.WithdrawDepositsResponse - 72, // 109: looprpc.SwapClient.ListStaticAddressDeposits:output_type -> looprpc.ListStaticAddressDepositsResponse - 74, // 110: looprpc.SwapClient.ListStaticAddressWithdrawals:output_type -> looprpc.ListStaticAddressWithdrawalResponse - 76, // 111: looprpc.SwapClient.ListStaticAddressSwaps:output_type -> looprpc.ListStaticAddressSwapsResponse - 78, // 112: looprpc.SwapClient.GetStaticAddressSummary:output_type -> looprpc.StaticAddressSummaryResponse - 83, // 113: looprpc.SwapClient.StaticAddressLoopIn:output_type -> looprpc.StaticAddressLoopInResponse - 10, // 114: looprpc.SwapClient.StaticOpenChannel:output_type -> looprpc.StaticOpenChannelResponse - 82, // [82:115] is the sub-list for method output_type - 49, // [49:82] is the sub-list for method input_type + 39, // 64: looprpc.SwapClient.Recover:input_type -> looprpc.RecoverRequest + 43, // 65: looprpc.SwapClient.GetInfo:input_type -> looprpc.GetInfoRequest + 11, // 66: looprpc.SwapClient.StopDaemon:input_type -> looprpc.StopDaemonRequest + 45, // 67: looprpc.SwapClient.GetLiquidityParams:input_type -> looprpc.GetLiquidityParamsRequest + 49, // 68: looprpc.SwapClient.SetLiquidityParams:input_type -> looprpc.SetLiquidityParamsRequest + 51, // 69: looprpc.SwapClient.SuggestSwaps:input_type -> looprpc.SuggestSwapsRequest + 56, // 70: looprpc.SwapClient.ListReservations:input_type -> looprpc.ListReservationsRequest + 59, // 71: looprpc.SwapClient.InstantOut:input_type -> looprpc.InstantOutRequest + 61, // 72: looprpc.SwapClient.InstantOutQuote:input_type -> looprpc.InstantOutQuoteRequest + 63, // 73: looprpc.SwapClient.ListInstantOuts:input_type -> looprpc.ListInstantOutsRequest + 66, // 74: looprpc.SwapClient.NewStaticAddress:input_type -> looprpc.NewStaticAddressRequest + 68, // 75: looprpc.SwapClient.ListUnspentDeposits:input_type -> looprpc.ListUnspentDepositsRequest + 71, // 76: looprpc.SwapClient.WithdrawDeposits:input_type -> looprpc.WithdrawDepositsRequest + 73, // 77: looprpc.SwapClient.ListStaticAddressDeposits:input_type -> looprpc.ListStaticAddressDepositsRequest + 75, // 78: looprpc.SwapClient.ListStaticAddressWithdrawals:input_type -> looprpc.ListStaticAddressWithdrawalRequest + 77, // 79: looprpc.SwapClient.ListStaticAddressSwaps:input_type -> looprpc.ListStaticAddressSwapsRequest + 79, // 80: looprpc.SwapClient.GetStaticAddressSummary:input_type -> looprpc.StaticAddressSummaryRequest + 84, // 81: looprpc.SwapClient.StaticAddressLoopIn:input_type -> looprpc.StaticAddressLoopInRequest + 9, // 82: looprpc.SwapClient.StaticOpenChannel:input_type -> looprpc.StaticOpenChannelRequest + 15, // 83: looprpc.SwapClient.LoopOut:output_type -> looprpc.SwapResponse + 15, // 84: looprpc.SwapClient.LoopIn:output_type -> looprpc.SwapResponse + 17, // 85: looprpc.SwapClient.Monitor:output_type -> looprpc.SwapStatus + 20, // 86: looprpc.SwapClient.ListSwaps:output_type -> looprpc.ListSwapsResponse + 22, // 87: looprpc.SwapClient.SweepHtlc:output_type -> looprpc.SweepHtlcResponse + 17, // 88: looprpc.SwapClient.SwapInfo:output_type -> looprpc.SwapStatus + 55, // 89: looprpc.SwapClient.AbandonSwap:output_type -> looprpc.AbandonSwapResponse + 29, // 90: looprpc.SwapClient.LoopOutTerms:output_type -> looprpc.OutTermsResponse + 32, // 91: looprpc.SwapClient.LoopOutQuote:output_type -> looprpc.OutQuoteResponse + 28, // 92: looprpc.SwapClient.GetLoopInTerms:output_type -> looprpc.InTermsResponse + 31, // 93: looprpc.SwapClient.GetLoopInQuote:output_type -> looprpc.InQuoteResponse + 34, // 94: looprpc.SwapClient.Probe:output_type -> looprpc.ProbeResponse + 36, // 95: looprpc.SwapClient.GetL402Tokens:output_type -> looprpc.TokensResponse + 36, // 96: looprpc.SwapClient.GetLsatTokens:output_type -> looprpc.TokensResponse + 38, // 97: looprpc.SwapClient.FetchL402Token:output_type -> looprpc.FetchL402TokenResponse + 40, // 98: looprpc.SwapClient.Recover:output_type -> looprpc.RecoverResponse + 44, // 99: looprpc.SwapClient.GetInfo:output_type -> looprpc.GetInfoResponse + 12, // 100: looprpc.SwapClient.StopDaemon:output_type -> looprpc.StopDaemonResponse + 46, // 101: looprpc.SwapClient.GetLiquidityParams:output_type -> looprpc.LiquidityParameters + 50, // 102: looprpc.SwapClient.SetLiquidityParams:output_type -> looprpc.SetLiquidityParamsResponse + 53, // 103: looprpc.SwapClient.SuggestSwaps:output_type -> looprpc.SuggestSwapsResponse + 57, // 104: looprpc.SwapClient.ListReservations:output_type -> looprpc.ListReservationsResponse + 60, // 105: looprpc.SwapClient.InstantOut:output_type -> looprpc.InstantOutResponse + 62, // 106: looprpc.SwapClient.InstantOutQuote:output_type -> looprpc.InstantOutQuoteResponse + 64, // 107: looprpc.SwapClient.ListInstantOuts:output_type -> looprpc.ListInstantOutsResponse + 67, // 108: looprpc.SwapClient.NewStaticAddress:output_type -> looprpc.NewStaticAddressResponse + 69, // 109: looprpc.SwapClient.ListUnspentDeposits:output_type -> looprpc.ListUnspentDepositsResponse + 72, // 110: looprpc.SwapClient.WithdrawDeposits:output_type -> looprpc.WithdrawDepositsResponse + 74, // 111: looprpc.SwapClient.ListStaticAddressDeposits:output_type -> looprpc.ListStaticAddressDepositsResponse + 76, // 112: looprpc.SwapClient.ListStaticAddressWithdrawals:output_type -> looprpc.ListStaticAddressWithdrawalResponse + 78, // 113: looprpc.SwapClient.ListStaticAddressSwaps:output_type -> looprpc.ListStaticAddressSwapsResponse + 80, // 114: looprpc.SwapClient.GetStaticAddressSummary:output_type -> looprpc.StaticAddressSummaryResponse + 85, // 115: looprpc.SwapClient.StaticAddressLoopIn:output_type -> looprpc.StaticAddressLoopInResponse + 10, // 116: looprpc.SwapClient.StaticOpenChannel:output_type -> looprpc.StaticOpenChannelResponse + 83, // [83:117] is the sub-list for method output_type + 49, // [49:83] is the sub-list for method input_type 49, // [49:49] is the sub-list for extension type_name 49, // [49:49] is the sub-list for extension extendee 0, // [0:49] is the sub-list for field type_name @@ -7316,7 +7470,7 @@ func file_client_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_client_proto_rawDesc), len(file_client_proto_rawDesc)), NumEnums: 9, - NumMessages: 80, + NumMessages: 82, NumExtensions: 0, NumServices: 1, }, diff --git a/looprpc/client.pb.gw.go b/looprpc/client.pb.gw.go index c2ef63f88..f80f4ce6b 100644 --- a/looprpc/client.pb.gw.go +++ b/looprpc/client.pb.gw.go @@ -479,6 +479,32 @@ func local_request_SwapClient_GetL402Tokens_1(ctx context.Context, marshaler run } +func request_SwapClient_Recover_0(ctx context.Context, marshaler runtime.Marshaler, client SwapClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RecoverRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Recover(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_SwapClient_Recover_0(ctx context.Context, marshaler runtime.Marshaler, server SwapClientServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq RecoverRequest + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Recover(ctx, &protoReq) + return msg, metadata, err + +} + func request_SwapClient_GetInfo_0(ctx context.Context, marshaler runtime.Marshaler, client SwapClientClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq GetInfoRequest var metadata runtime.ServerMetadata @@ -1175,6 +1201,31 @@ func RegisterSwapClientHandlerServer(ctx context.Context, mux *runtime.ServeMux, }) + mux.Handle("POST", pattern_SwapClient_Recover_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/looprpc.SwapClient/Recover", runtime.WithHTTPPathPattern("/v1/recover")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_SwapClient_Recover_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SwapClient_Recover_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_SwapClient_GetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -1905,6 +1956,28 @@ func RegisterSwapClientHandlerClient(ctx context.Context, mux *runtime.ServeMux, }) + mux.Handle("POST", pattern_SwapClient_Recover_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + var err error + var annotatedContext context.Context + annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/looprpc.SwapClient/Recover", runtime.WithHTTPPathPattern("/v1/recover")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_SwapClient_Recover_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + + forward_SwapClient_Recover_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_SwapClient_GetInfo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -2307,6 +2380,8 @@ var ( pattern_SwapClient_GetL402Tokens_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "lsat", "tokens"}, "")) + pattern_SwapClient_Recover_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1}, []string{"v1", "recover"}, "")) + pattern_SwapClient_GetInfo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "loop", "info"}, "")) pattern_SwapClient_StopDaemon_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "daemon", "stop"}, "")) @@ -2367,6 +2442,8 @@ var ( forward_SwapClient_GetL402Tokens_1 = runtime.ForwardResponseMessage + forward_SwapClient_Recover_0 = runtime.ForwardResponseMessage + forward_SwapClient_GetInfo_0 = runtime.ForwardResponseMessage forward_SwapClient_StopDaemon_0 = runtime.ForwardResponseMessage diff --git a/looprpc/client.proto b/looprpc/client.proto index cf14ffa0a..dcb9c5f1b 100644 --- a/looprpc/client.proto +++ b/looprpc/client.proto @@ -102,6 +102,12 @@ service SwapClient { */ rpc FetchL402Token (FetchL402TokenRequest) returns (FetchL402TokenResponse); + /* loop: `recover` + Recover restores the local static-address and L402 state from an encrypted + local backup file. + */ + rpc Recover (RecoverRequest) returns (RecoverResponse); + /* loop: `getinfo` GetInfo gets basic information about the loop daemon. */ @@ -1060,6 +1066,48 @@ message FetchL402TokenRequest { message FetchL402TokenResponse { } +message RecoverRequest { + /* + Optional path to the encrypted backup file. If omitted, loopd restores from + the latest timestamped recovery backup in the active network data + directory. + */ + string backup_file = 1; +} + +message RecoverResponse { + /* + The backup file that was restored. + */ + string backup_file = 1; + + /* + Whether a paid L402 token was restored into the local token store. + */ + bool restored_l402 = 2; + + /* + Whether static-address state was restored into loopd and lnd. + */ + bool restored_static_address = 3; + + /* + The restored static address, if any. + */ + string static_address = 4; + + /* + The number of deposits found during best-effort reconciliation. + */ + uint32 num_deposits_found = 5; + + /* + Best-effort deposit reconciliation error text, if reconciliation failed + after state restore completed. + */ + string deposit_reconciliation_error = 6; +} + message L402Token { /* The base macaroon that was baked by the auth server. diff --git a/looprpc/client.swagger.json b/looprpc/client.swagger.json index 3d75d15da..dae6d8246 100644 --- a/looprpc/client.swagger.json +++ b/looprpc/client.swagger.json @@ -868,6 +868,39 @@ ] } }, + "/v1/recover": { + "post": { + "summary": "loop: `recover`\nRecover restores the local static-address and L402 state from an encrypted\nlocal backup file.", + "operationId": "SwapClient_Recover", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/looprpcRecoverResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/rpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/looprpcRecoverRequest" + } + } + ], + "tags": [ + "SwapClient" + ] + } + }, "/v1/staticaddr": { "post": { "summary": "loop: `static newstaticaddress`\nNewStaticAddress requests a new static address for loop-ins from the server.", @@ -2599,6 +2632,45 @@ "type": "object", "description": "PublishSucceeded is returned by SweepHtlc if publishing was requested in\nSweepHtlcRequest and it succeeded." }, + "looprpcRecoverRequest": { + "type": "object", + "properties": { + "backup_file": { + "type": "string", + "description": "Optional path to the encrypted backup file. If omitted, loopd restores from\nthe latest timestamped recovery backup in the active network data\ndirectory." + } + } + }, + "looprpcRecoverResponse": { + "type": "object", + "properties": { + "backup_file": { + "type": "string", + "description": "The backup file that was restored." + }, + "restored_l402": { + "type": "boolean", + "description": "Whether a paid L402 token was restored into the local token store." + }, + "restored_static_address": { + "type": "boolean", + "description": "Whether static-address state was restored into loopd and lnd." + }, + "static_address": { + "type": "string", + "description": "The restored static address, if any." + }, + "num_deposits_found": { + "type": "integer", + "format": "int64", + "description": "The number of deposits found during best-effort reconciliation." + }, + "deposit_reconciliation_error": { + "type": "string", + "description": "Best-effort deposit reconciliation error text, if reconciliation failed\nafter state restore completed." + } + } + }, "looprpcRouteHint": { "type": "object", "properties": { diff --git a/looprpc/client.yaml b/looprpc/client.yaml index 5213afe4d..88c038b89 100644 --- a/looprpc/client.yaml +++ b/looprpc/client.yaml @@ -33,6 +33,9 @@ http: get: "/v1/l402/tokens" additional_bindings: - get: "/v1/lsat/tokens" + - selector: looprpc.SwapClient.Recover + post: "/v1/recover" + body: "*" - selector: looprpc.SwapClient.GetLiquidityParams get: "/v1/liquidity/params" - selector: looprpc.SwapClient.SetLiquidityParams diff --git a/looprpc/client_grpc.pb.go b/looprpc/client_grpc.pb.go index b03cc9e87..47fdbd0ef 100644 --- a/looprpc/client_grpc.pb.go +++ b/looprpc/client_grpc.pb.go @@ -76,6 +76,10 @@ type SwapClientClient interface { // FetchL402Token fetches an L402 token from the server, this is required in // order to receive reservation notifications from the server. FetchL402Token(ctx context.Context, in *FetchL402TokenRequest, opts ...grpc.CallOption) (*FetchL402TokenResponse, error) + // loop: `recover` + // Recover restores the local static-address and L402 state from an encrypted + // local backup file. + Recover(ctx context.Context, in *RecoverRequest, opts ...grpc.CallOption) (*RecoverResponse, error) // loop: `getinfo` // GetInfo gets basic information about the loop daemon. GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) @@ -312,6 +316,15 @@ func (c *swapClientClient) FetchL402Token(ctx context.Context, in *FetchL402Toke return out, nil } +func (c *swapClientClient) Recover(ctx context.Context, in *RecoverRequest, opts ...grpc.CallOption) (*RecoverResponse, error) { + out := new(RecoverResponse) + err := c.cc.Invoke(ctx, "/looprpc.SwapClient/Recover", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *swapClientClient) GetInfo(ctx context.Context, in *GetInfoRequest, opts ...grpc.CallOption) (*GetInfoResponse, error) { out := new(GetInfoResponse) err := c.cc.Invoke(ctx, "/looprpc.SwapClient/GetInfo", in, out, opts...) @@ -536,6 +549,10 @@ type SwapClientServer interface { // FetchL402Token fetches an L402 token from the server, this is required in // order to receive reservation notifications from the server. FetchL402Token(context.Context, *FetchL402TokenRequest) (*FetchL402TokenResponse, error) + // loop: `recover` + // Recover restores the local static-address and L402 state from an encrypted + // local backup file. + Recover(context.Context, *RecoverRequest) (*RecoverResponse, error) // loop: `getinfo` // GetInfo gets basic information about the loop daemon. GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) @@ -656,6 +673,9 @@ func (UnimplementedSwapClientServer) GetLsatTokens(context.Context, *TokensReque func (UnimplementedSwapClientServer) FetchL402Token(context.Context, *FetchL402TokenRequest) (*FetchL402TokenResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method FetchL402Token not implemented") } +func (UnimplementedSwapClientServer) Recover(context.Context, *RecoverRequest) (*RecoverResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Recover not implemented") +} func (UnimplementedSwapClientServer) GetInfo(context.Context, *GetInfoRequest) (*GetInfoResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetInfo not implemented") } @@ -996,6 +1016,24 @@ func _SwapClient_FetchL402Token_Handler(srv interface{}, ctx context.Context, de return interceptor(ctx, in, info, handler) } +func _SwapClient_Recover_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RecoverRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SwapClientServer).Recover(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/looprpc.SwapClient/Recover", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SwapClientServer).Recover(ctx, req.(*RecoverRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _SwapClient_GetInfo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetInfoRequest) if err := dec(in); err != nil { @@ -1383,6 +1421,10 @@ var SwapClient_ServiceDesc = grpc.ServiceDesc{ MethodName: "FetchL402Token", Handler: _SwapClient_FetchL402Token_Handler, }, + { + MethodName: "Recover", + Handler: _SwapClient_Recover_Handler, + }, { MethodName: "GetInfo", Handler: _SwapClient_GetInfo_Handler, diff --git a/looprpc/perms.go b/looprpc/perms.go index 6ceff7189..d5820bdd3 100644 --- a/looprpc/perms.go +++ b/looprpc/perms.go @@ -151,6 +151,13 @@ var RequiredPermissions = map[string][]bakery.Op{ Entity: "auth", Action: "write", }}, + "/looprpc.SwapClient/Recover": {{ + Entity: "auth", + Action: "write", + }, { + Entity: "loop", + Action: "in", + }}, "/looprpc.SwapClient/SuggestSwaps": {{ Entity: "suggestions", Action: "read", diff --git a/looprpc/swapclient.pb.json.go b/looprpc/swapclient.pb.json.go index ef1297dc3..a168d8152 100644 --- a/looprpc/swapclient.pb.json.go +++ b/looprpc/swapclient.pb.json.go @@ -413,6 +413,31 @@ func RegisterSwapClientJSONCallbacks(registry map[string]func(ctx context.Contex callback(string(respBytes), nil) } + registry["looprpc.SwapClient.Recover"] = func(ctx context.Context, + conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { + + req := &RecoverRequest{} + err := marshaler.Unmarshal([]byte(reqJSON), req) + if err != nil { + callback("", err) + return + } + + client := NewSwapClientClient(conn) + resp, err := client.Recover(ctx, req) + if err != nil { + callback("", err) + return + } + + respBytes, err := marshaler.Marshal(resp) + if err != nil { + callback("", err) + return + } + callback(string(respBytes), nil) + } + registry["looprpc.SwapClient.GetInfo"] = func(ctx context.Context, conn *grpc.ClientConn, reqJSON string, callback func(string, error)) { diff --git a/recovery/README.md b/recovery/README.md new file mode 100644 index 000000000..3f0b5d046 --- /dev/null +++ b/recovery/README.md @@ -0,0 +1,184 @@ +# Recovery Package + +This package implements phase-1 local recovery for Loop static addresses and +L402 authentication state. + +## Goal + +The recovery flow is meant to let a fresh or repaired Loop instance continue +using the same static address and the same L402-backed server identity after a +local crash, disk loss, or Loop data-directory replacement. + +The package also keeps older generations of recoverable state around. That +matters when a user first creates one paid-L402/static-address pair, later +loses the local Loop state, and then creates a different paid-L402/static- +address pair. In that case the older deposits are still recoverable as long as +the older timestamped backup file is kept. + +The recovery data is intentionally local: + +- It is written into the active Loop network data directory. +- It contains only the minimum local state needed to rebuild client-side state. +- It is encrypted with a key derived from the same lnd seed material that backs + static-address keys. + +## What Is Backed Up + +Each encrypted backup stores: + +- A backup format version. +- The active Loop network. +- Static-address protocol version. +- Static-address server pubkey. +- Static-address expiry. +- The exact client key locator when available. +- The static-address `pkScript`. +- The derived taproot address string as an additional recovery hint. +- The address initiation height. +- The paid local `l402.token` file. + +The paid token file is preserved as a raw blob instead of decomposed token +fields so the restore path remains compatible with Aperture's current file-store +format without requiring a public token constructor. + +A given backup can contain: + +- Only a paid L402 token. +- A paid L402 token together with a static address. + +Deposit state itself is not serialized into the backup file. Deposits are +rediscovered on restore through the normal reconciliation path. + +## File Location + +This package keeps two backup forms in the active Loop network data directory: + +- A canonical latest snapshot: + `/L402_backup.enc` +- Immutable timestamped snapshots: + `/L402_backup_.enc` + or `/L402_backup__.enc` + +In the normal `loopd` layout this resolves inside the active network-specific +directory, for example: + +`~/.loop/mainnet/L402_backup.enc` + +and + +`~/.loop/mainnet/L402_backup_20260414T093001.000000123Z.enc` + +The canonical `L402_backup.enc` file is always updated to the latest snapshot +for compatibility and convenience. The timestamped files are the archival +history that lets the user recover older generations. + +## Encryption Model + +The file is encrypted with `secretbox` using a symmetric key derived from lnd +via `Signer.DeriveSharedKey`. + +The derivation uses: + +- A fixed NUMS public key. +- The static-address key family. +- Key index `0`. + +This gives the backup a deterministic local encryption key tied to the lnd seed +without requiring an interactive password in phase 1. + +## Backup Creation Flow + +On startup, `loopd` asks this package to backfill the latest snapshot if any +recoverable state exists. + +The backup write is a no-op when: + +- No static address exists locally. +- No paid `l402.token` file exists locally. + +If either state source exists, the package: + +1. Collects the local static-address parameters. +2. Reads the paid `l402.token` file from the Loop data dir. +3. Serializes the payload as JSON. +4. Encrypts it. +5. If the recoverable state changed, atomically writes a new timestamped backup. +6. Updates `L402_backup.enc` to the same latest snapshot. + +If the recoverable state did not change, no new timestamped archive is created. +This avoids producing duplicate backups on every startup. + +The daemon also triggers backup refreshes when: + +- A usable paid L402 token has just been fetched and stored locally. +- A new static address has just been created, stored, and imported into lnd. + +Pending or otherwise intermediate L402 token state is intentionally not backed +up. + +## Restore Flow + +`loop recover --backup_file ` eventually calls into this package from +inside `loopd`. + +If the backup path is omitted, the package uses the timestamped backup with the +latest filename timestamp, falling back to `L402_backup.enc` for legacy or +backfill-only states. + +Restore performs the following steps: + +1. Read and decrypt the backup file. +2. Validate backup version and network. +3. If static-address metadata is present, reconstruct the static-address client + pubkey: + - First try the exact backed-up key locator. + - If the locator is missing or unusable, scan backward with a gap of 20 in + the static-address key family. +4. If static-address metadata is present, re-create the local static-address + record. +5. If static-address metadata is present, re-import the static-address + tapscript into lnd. +6. If a paid token is present, restore the paid `l402.token` file into the + local token store directory. +7. If static-address metadata is present, trigger best-effort deposit + reconciliation. + +Restoring an older timestamped backup is expected to be done into a fresh Loop +data directory because Loop still only supports a single local static address at +a time. The same recommendation applies when the local directory already +contains a different paid L402 token, because recovery is intended to recreate a +specific prior generation of local state rather than merge multiple ones. + +## Deposit Reconciliation + +This package does not attempt to fully rebuild the complete historical deposit +state from chain data alone. + +Instead, after static-address restore it calls the deposit manager's normal +reconciliation path and lets Loop discover currently visible deposits from lnd's +wallet view. + +This is intentionally best effort: + +- It is enough to recover practical local usability. +- It avoids duplicating the full deposit FSM logic in the recovery layer. +- It keeps the restore path aligned with normal manager behavior. + +## Package Boundaries + +This package owns: + +- Backup payload definition. +- Encryption/decryption. +- Reading/writing backup files. +- Reading/writing paid L402 token files. +- Static-address key re-derivation and restore orchestration. +- Post-restore deposit reconciliation orchestration. + +This package does not own: + +- gRPC transport. +- CLI command handling. +- `loopd` startup wiring. + +Those remain in `loopd/` and `cmd/loop/`. diff --git a/recovery/service.go b/recovery/service.go new file mode 100644 index 000000000..23929a180 --- /dev/null +++ b/recovery/service.go @@ -0,0 +1,767 @@ +package recovery + +import ( + "bytes" + "context" + "crypto/rand" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "slices" + "strings" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/loop/staticaddr/address" + staticaddrscript "github.com/lightninglabs/loop/staticaddr/script" + staticaddrversion "github.com/lightninglabs/loop/staticaddr/version" + "github.com/lightninglabs/loop/swap" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" + "github.com/lightningnetwork/lnd/lncfg" + "golang.org/x/crypto/nacl/secretbox" +) + +const ( + backupVersion = 1 + + backupBaseName = "L402_backup" + + backupFileExt = ".enc" + + // BackupFileName is the default encrypted backup file name written to + // loop's active network data directory. + BackupFileName = backupBaseName + backupFileExt + + backupKeyGap = 20 + + backupTimeFormat = "20060102T150405.000000000Z" + + paidTokenFileName = "l402.token" +) + +var backupKeyLocator = keychain.KeyLocator{ + Family: keychain.KeyFamily(swap.StaticAddressKeyFamily), + Index: 0, +} + +var backupMagic = []byte("loopbak1") + +// StaticAddressManager is the subset of static-address behavior required for +// creating and restoring recovery backups. +type StaticAddressManager interface { + GetStaticAddressParameters(context.Context) (*address.Parameters, error) + GetTaprootAddress(clientPubkey, serverPubkey *btcec.PublicKey, + expiry int64) (*btcutil.AddressTaproot, error) + RestoreAddress(context.Context, + *address.Parameters) (*btcutil.AddressTaproot, error) +} + +// DepositManager is the subset of deposit-manager behavior required to +// reconcile deposits after restore. +type DepositManager interface { + ReconcileDeposits(context.Context) (int, error) +} + +// RecoverResult describes the outcome of a restore attempt. +type RecoverResult struct { + BackupFile string + StaticAddress string + RestoredStaticAddress bool + RestoredL402 bool + NumDepositsFound int + DepositReconciliationError string +} + +// Service coordinates creation and restoration of encrypted local recovery +// backups for Loop static-address and L402 state. +type Service struct { + dataDir string + network string + signer lndclient.SignerClient + walletKit lndclient.WalletKitClient + staticAddressManager StaticAddressManager + depositManager DepositManager + now func() time.Time +} + +type backupPayload struct { + Version uint32 `json:"version"` + Network string `json:"network"` + StaticAddress *staticAddressBackup `json:"static_address,omitempty"` + TokenFiles []*l402TokenFileEntry `json:"token_files,omitempty"` +} + +type staticAddressBackup struct { + ProtocolVersion uint32 `json:"protocol_version"` + ServerPubKey []byte `json:"server_pubkey"` + Expiry uint32 `json:"expiry"` + ClientKeyFamily int32 `json:"client_key_family"` + ClientKeyIndex uint32 `json:"client_key_index"` + PkScript []byte `json:"pk_script,omitempty"` + Address string `json:"address,omitempty"` + InitiationHeight int32 `json:"initiation_height,omitempty"` +} + +type l402TokenFileEntry struct { + Name string `json:"name"` + Data []byte `json:"data"` +} + +// NewService constructs a recovery service for a specific loop network data +// directory. +func NewService(dataDir, network string, signer lndclient.SignerClient, + walletKit lndclient.WalletKitClient, + staticAddressManager StaticAddressManager, + depositManager DepositManager) *Service { + + return &Service{ + dataDir: dataDir, + network: network, + signer: signer, + walletKit: walletKit, + staticAddressManager: staticAddressManager, + depositManager: depositManager, + now: time.Now, + } +} + +// DefaultBackupFilePath returns the default backup file path for the supplied +// loop data directory. +func DefaultBackupFilePath(dataDir string) string { + return filepath.Join(dataDir, BackupFileName) +} + +// WriteBackup writes an encrypted backup file if there is static-address or +// paid L402 state to preserve. It returns an empty path when there is nothing +// new to back up yet. +func (s *Service) WriteBackup(ctx context.Context) (string, error) { + payload, hasState, err := s.buildPayload(ctx) + if err != nil || !hasState { + return "", err + } + + plaintext, err := json.Marshal(payload) + if err != nil { + return "", err + } + + key, err := s.deriveEncryptionKey(ctx) + if err != nil { + return "", err + } + + fileName, wroteBackup, err := s.writeBackupFiles(key, plaintext) + if err != nil || !wroteBackup { + return "", err + } + + return fileName, nil +} + +// Restore restores the local static-address and L402 state from an encrypted +// backup file. If backupFile is empty, the most recent timestamped backup file +// is used, falling back to the legacy canonical backup path. +func (s *Service) Restore(ctx context.Context, backupFile string) ( + *RecoverResult, error) { + + fileName, err := s.resolveBackupFile(backupFile) + if err != nil { + return nil, err + } + + encrypted, err := os.ReadFile(fileName) + if err != nil { + return nil, err + } + + key, err := s.deriveEncryptionKey(ctx) + if err != nil { + return nil, err + } + + plaintext, err := decryptBackupPayload(key, encrypted) + if err != nil { + return nil, err + } + + var payload backupPayload + err = json.Unmarshal(plaintext, &payload) + if err != nil { + return nil, err + } + + err = payload.validateNetwork(s.network) + if err != nil { + return nil, err + } + + result := &RecoverResult{ + BackupFile: fileName, + } + + if payload.StaticAddress != nil { + addr, err := s.restoreStaticAddress(ctx, payload.StaticAddress) + if err != nil { + return nil, err + } + + result.StaticAddress = addr + result.RestoredStaticAddress = true + } + + restoredL402, err := s.restoreTokenFiles(payload.TokenFiles) + if err != nil { + return nil, err + } + result.RestoredL402 = restoredL402 + + if payload.StaticAddress != nil && s.depositManager != nil { + numDeposits, err := s.depositManager.ReconcileDeposits(ctx) + if err != nil { + result.DepositReconciliationError = err.Error() + } else { + result.NumDepositsFound = numDeposits + } + } + + return result, nil +} + +func (p *backupPayload) validateNetwork(currentNetwork string) error { + switch { + case p.Version != backupVersion: + return fmt.Errorf("unsupported backup version %d", p.Version) + + case p.Network == "": + return fmt.Errorf("backup file is missing a network") + + case p.Network != currentNetwork: + return fmt.Errorf("backup file network %s does not match "+ + "daemon network %s", p.Network, currentNetwork) + } + + return nil +} + +func (s *Service) buildPayload(ctx context.Context) (*backupPayload, bool, + error) { + + payload := &backupPayload{ + Version: backupVersion, + Network: s.network, + } + + if s.staticAddressManager != nil { + addrParams, err := s.staticAddressManager.GetStaticAddressParameters( + ctx, + ) + switch { + case err == nil: + taprootAddress, err := s.staticAddressManager.GetTaprootAddress( + addrParams.ClientPubkey, addrParams.ServerPubkey, + int64(addrParams.Expiry), + ) + if err != nil { + return nil, false, err + } + + payload.StaticAddress = &staticAddressBackup{ + ProtocolVersion: uint32(addrParams.ProtocolVersion), + ServerPubKey: addrParams.ServerPubkey.SerializeCompressed(), + Expiry: addrParams.Expiry, + ClientKeyFamily: int32(addrParams.KeyLocator.Family), + ClientKeyIndex: addrParams.KeyLocator.Index, + PkScript: slices.Clone(addrParams.PkScript), + Address: taprootAddress.String(), + InitiationHeight: addrParams.InitiationHeight, + } + + case errors.Is(err, address.ErrNoStaticAddress): + // No static address to back up yet. + + default: + return nil, false, err + } + } + + tokenFiles, err := s.readTokenFiles() + if err != nil { + return nil, false, err + } + payload.TokenFiles = tokenFiles + + return payload, payload.StaticAddress != nil || len(payload.TokenFiles) > 0, + nil +} + +func (s *Service) readTokenFiles() ([]*l402TokenFileEntry, error) { + path := filepath.Join(s.dataDir, paidTokenFileName) + data, err := os.ReadFile(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, nil + } + + return nil, err + } + + return []*l402TokenFileEntry{{ + Name: paidTokenFileName, + Data: data, + }}, nil +} + +func (s *Service) resolveBackupFile(backupFile string) (string, error) { + if backupFile != "" { + return lncfg.CleanAndExpandPath(backupFile), nil + } + + return latestBackupFilePath(s.dataDir) +} + +func (s *Service) writeBackupFiles(key [32]byte, plaintext []byte) (string, + bool, error) { + + latestFile, err := latestBackupFilePath(s.dataDir) + switch { + case errors.Is(err, os.ErrNotExist): + // No prior backups yet. + + case err != nil: + return "", false, err + + default: + existingPlaintext, err := readBackupPlaintext(key, latestFile) + if err == nil && bytes.Equal(existingPlaintext, plaintext) { + canonicalFile := DefaultBackupFilePath(s.dataDir) + _, err := os.Stat(canonicalFile) + if err == nil { + return "", false, nil + } + if !errors.Is(err, os.ErrNotExist) { + return "", false, err + } + + encrypted, err := encryptBackupPayload(key, plaintext) + if err != nil { + return "", false, err + } + + err = writeFileAtomically(canonicalFile, encrypted) + if err != nil { + return "", false, err + } + + return canonicalFile, true, nil + } + } + + encrypted, err := encryptBackupPayload(key, plaintext) + if err != nil { + return "", false, err + } + + archiveFile, err := nextArchivedBackupFilePath( + s.dataDir, s.currentTime(), + ) + if err != nil { + return "", false, err + } + + err = writeFileAtomically(archiveFile, encrypted) + if err != nil { + return "", false, err + } + + err = writeFileAtomically(DefaultBackupFilePath(s.dataDir), encrypted) + if err != nil { + return "", false, err + } + + return archiveFile, true, nil +} + +func (s *Service) currentTime() time.Time { + if s.now != nil { + return s.now().UTC() + } + + return time.Now().UTC() +} + +func latestBackupFilePath(dataDir string) (string, error) { + dirEntries, err := os.ReadDir(dataDir) + if err != nil { + return "", err + } + + var ( + latestFile string + latestTime time.Time + ) + + for _, entry := range dirEntries { + if entry.IsDir() { + continue + } + + backupTime, ok := backupFileTimestamp(entry.Name()) + if !ok { + continue + } + + if latestFile == "" || backupTime.After(latestTime) || + (backupTime.Equal(latestTime) && + entry.Name() > filepath.Base(latestFile)) { + + latestTime = backupTime + latestFile = filepath.Join(dataDir, entry.Name()) + } + } + + if latestFile != "" { + return latestFile, nil + } + + fileName := DefaultBackupFilePath(dataDir) + _, err = os.Stat(fileName) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return "", os.ErrNotExist + } + + return "", err + } + + return fileName, nil +} + +func archivedBackupFilePath(dataDir string, backupTime time.Time) string { + return archivedBackupFilePathWithSuffix(dataDir, backupTime, 0) +} + +func archivedBackupFilePathWithSuffix(dataDir string, backupTime time.Time, + suffix int) string { + + name := fmt.Sprintf( + "%s_%s", backupBaseName, + backupTime.UTC().Format(backupTimeFormat), + ) + if suffix > 0 { + name = fmt.Sprintf("%s_%d", name, suffix) + } + + return filepath.Join(dataDir, name+backupFileExt) +} + +func nextArchivedBackupFilePath(dataDir string, + backupTime time.Time) (string, error) { + + for suffix := 0; ; suffix++ { + path := archivedBackupFilePathWithSuffix( + dataDir, backupTime, suffix, + ) + _, err := os.Stat(path) + if errors.Is(err, os.ErrNotExist) { + return path, nil + } + if err != nil { + return "", err + } + } +} + +func backupFileTimestamp(name string) (time.Time, bool) { + if !strings.HasPrefix(name, backupBaseName+"_") || + !strings.HasSuffix(name, backupFileExt) { + + return time.Time{}, false + } + + timestamp := strings.TrimSuffix( + strings.TrimPrefix(name, backupBaseName+"_"), backupFileExt, + ) + if separator := strings.Index(timestamp, "_"); separator != -1 { + timestamp = timestamp[:separator] + } + + backupTime, err := time.Parse(backupTimeFormat, timestamp) + if err != nil { + return time.Time{}, false + } + + return backupTime, true +} + +func readBackupPlaintext(key [32]byte, path string) ([]byte, error) { + ciphertext, err := os.ReadFile(path) + if err != nil { + return nil, err + } + + return decryptBackupPayload(key, ciphertext) +} + +func (s *Service) restoreStaticAddress(ctx context.Context, + backup *staticAddressBackup) (string, error) { + + if s.staticAddressManager == nil { + return "", fmt.Errorf("static address restore is unavailable") + } + + if !staticaddrversion.AddressProtocolVersion( + backup.ProtocolVersion, + ).Valid() { + + return "", fmt.Errorf("invalid static address protocol version %d", + backup.ProtocolVersion) + } + + serverPubKey, err := btcec.ParsePubKey(backup.ServerPubKey) + if err != nil { + return "", err + } + + clientPubKey, locator, err := s.resolveClientKey( + ctx, backup, serverPubKey, + ) + if err != nil { + return "", err + } + + addr, err := s.staticAddressManager.RestoreAddress( + ctx, &address.Parameters{ + ClientPubkey: clientPubKey, + ServerPubkey: serverPubKey, + Expiry: backup.Expiry, + PkScript: slices.Clone(backup.PkScript), + KeyLocator: locator, + ProtocolVersion: staticaddrversion.AddressProtocolVersion( + backup.ProtocolVersion, + ), + InitiationHeight: backup.InitiationHeight, + }, + ) + if err != nil { + return "", err + } + + return addr.String(), nil +} + +func (s *Service) restoreTokenFiles( + backupFiles []*l402TokenFileEntry) (bool, error) { + + if len(backupFiles) == 0 { + return false, nil + } + + existingFiles, err := s.readTokenFiles() + if err != nil { + return false, err + } + + existingByName := make(map[string][]byte, len(existingFiles)) + for _, file := range existingFiles { + existingByName[file.Name] = file.Data + } + + backupByName := make(map[string][]byte, len(backupFiles)) + for _, file := range backupFiles { + if !isTokenFileName(file.Name) { + return false, fmt.Errorf("unexpected token file name %q", + file.Name) + } + + backupByName[file.Name] = file.Data + } + + for name := range existingByName { + if _, ok := backupByName[name]; ok { + continue + } + + return false, fmt.Errorf("token store already contains "+ + "unexpected file %q", name) + } + + restored := false + for name, data := range backupByName { + path := filepath.Join(s.dataDir, name) + if current, ok := existingByName[name]; ok { + if !bytes.Equal(current, data) { + return false, fmt.Errorf("token file %q already exists "+ + "with different contents", name) + } + + continue + } + + err := writeFileAtomically(path, data) + if err != nil { + return false, err + } + restored = true + } + + return restored, nil +} + +func (s *Service) resolveClientKey(ctx context.Context, + backup *staticAddressBackup, serverPubKey *btcec.PublicKey) ( + *btcec.PublicKey, keychain.KeyLocator, error) { + + locator := keychain.KeyLocator{ + Family: keychain.KeyFamily(backup.ClientKeyFamily), + Index: backup.ClientKeyIndex, + } + + hasExactLocator := locator.Family != 0 || locator.Index != 0 + if hasExactLocator { + exactKey, err := s.walletKit.DeriveKey(ctx, &locator) + if err == nil { + match, err := s.staticAddressMatches( + exactKey.PubKey, serverPubKey, backup, + ) + if err == nil && match { + return exactKey.PubKey, locator, nil + } + } + } + + nextKey, err := s.walletKit.DeriveNextKey( + ctx, swap.StaticAddressKeyFamily, + ) + if err != nil { + return nil, keychain.KeyLocator{}, err + } + + startIndex := int(nextKey.Index) + endIndex := max(0, startIndex-backupKeyGap) + for idx := startIndex; idx >= endIndex; idx-- { + candidateLocator := keychain.KeyLocator{ + Family: keychain.KeyFamily(swap.StaticAddressKeyFamily), + Index: uint32(idx), + } + candidateKey, err := s.walletKit.DeriveKey( + ctx, &candidateLocator, + ) + if err != nil { + continue + } + + match, err := s.staticAddressMatches( + candidateKey.PubKey, serverPubKey, backup, + ) + if err != nil { + return nil, keychain.KeyLocator{}, err + } + if match { + return candidateKey.PubKey, candidateLocator, nil + } + } + + return nil, keychain.KeyLocator{}, fmt.Errorf("unable to derive " + + "static address client key from backup") +} + +func (s *Service) staticAddressMatches(clientPubKey, + serverPubKey *btcec.PublicKey, backup *staticAddressBackup) (bool, error) { + + staticAddress, err := staticaddrscript.NewStaticAddress( + input.MuSig2Version100RC2, int64(backup.Expiry), clientPubKey, + serverPubKey, + ) + if err != nil { + return false, err + } + + if len(backup.PkScript) != 0 { + pkScript, err := staticAddress.StaticAddressScript() + if err != nil { + return false, err + } + + if bytes.Equal(pkScript, backup.PkScript) { + return true, nil + } + } + + if backup.Address != "" { + address, err := s.staticAddressManager.GetTaprootAddress( + clientPubKey, serverPubKey, int64(backup.Expiry), + ) + if err != nil { + return false, err + } + + if address.String() == backup.Address { + return true, nil + } + } + + return false, nil +} + +func (s *Service) deriveEncryptionKey(ctx context.Context) ([32]byte, error) { + return s.signer.DeriveSharedKey( + ctx, lndclient.SharedKeyNUMS, &backupKeyLocator, + ) +} + +func encryptBackupPayload(key [32]byte, plaintext []byte) ([]byte, error) { + var nonce [24]byte + _, err := rand.Read(nonce[:]) + if err != nil { + return nil, err + } + + cipherText := secretbox.Seal(nil, plaintext, &nonce, &key) + encoded := make([]byte, 0, len(backupMagic)+len(nonce)+len(cipherText)) + encoded = append(encoded, backupMagic...) + encoded = append(encoded, nonce[:]...) + encoded = append(encoded, cipherText...) + + return encoded, nil +} + +func decryptBackupPayload(key [32]byte, ciphertext []byte) ([]byte, error) { + if len(ciphertext) < len(backupMagic)+24 { + return nil, fmt.Errorf("backup file is too short") + } + if !bytes.Equal(ciphertext[:len(backupMagic)], backupMagic) { + return nil, fmt.Errorf("backup file has an unknown format") + } + + var nonce [24]byte + copy(nonce[:], ciphertext[len(backupMagic):len(backupMagic)+24]) + + plaintext, ok := secretbox.Open( + nil, ciphertext[len(backupMagic)+24:], &nonce, &key, + ) + if !ok { + return nil, fmt.Errorf("unable to decrypt backup file") + } + + return plaintext, nil +} + +func writeFileAtomically(path string, data []byte) error { + tempPath := path + ".tmp" + + err := os.WriteFile(tempPath, data, 0600) + if err != nil { + return err + } + + return os.Rename(tempPath, path) +} + +func isTokenFileName(name string) bool { + return filepath.Base(name) == name && name == paidTokenFileName +} diff --git a/recovery/service_test.go b/recovery/service_test.go new file mode 100644 index 000000000..0c9908297 --- /dev/null +++ b/recovery/service_test.go @@ -0,0 +1,751 @@ +package recovery + +import ( + "context" + "encoding/json" + "errors" + "os" + "path/filepath" + "slices" + "testing" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg" + "github.com/lightninglabs/loop/staticaddr/address" + staticaddrscript "github.com/lightninglabs/loop/staticaddr/script" + staticaddrversion "github.com/lightninglabs/loop/staticaddr/version" + "github.com/lightninglabs/loop/swap" + testutils "github.com/lightninglabs/loop/test" + "github.com/lightningnetwork/lnd/input" + "github.com/lightningnetwork/lnd/keychain" + "github.com/stretchr/testify/require" +) + +func TestEncryptDecryptBackupPayload(t *testing.T) { + t.Parallel() + + var key [32]byte + copy(key[:], []byte("0123456789abcdefghijklmnopqrstuv")) + + plaintext := []byte("loop recovery backup payload") + + encrypted, err := encryptBackupPayload(key, plaintext) + require.NoError(t, err) + require.NotEqual(t, plaintext, encrypted) + + decrypted, err := decryptBackupPayload(key, encrypted) + require.NoError(t, err) + require.Equal(t, plaintext, decrypted) +} + +func TestWriteBackupReturnsEmptyWithoutState(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + lnd := testutils.NewMockLnd() + + svc := NewService( + dir, "testnet", lnd.Signer, lnd.WalletKit, nil, nil, + ) + + backupFile, err := svc.WriteBackup(context.Background()) + require.NoError(t, err) + require.Empty(t, backupFile) + + entries, err := os.ReadDir(dir) + require.NoError(t, err) + require.Empty(t, entries) +} + +func TestWriteBackupIncludesStaticAddressAndPaidToken(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + lnd := testutils.NewMockLnd() + + addrParams := makeStaticAddressParams( + t, lnd, 7, defaultRecoveryServerPubkey, 144, 321, + ) + staticMgr := &mockStaticAddressManager{ + chainParams: lnd.ChainParams, + params: addrParams, + } + + svc := NewService( + dir, "testnet", lnd.Signer, lnd.WalletKit, staticMgr, nil, + ) + writeTime := time.Date( + 2026, time.April, 14, 9, 30, 1, 123, time.UTC, + ) + svc.now = func() time.Time { + return writeTime + } + + writePaidToken(t, dir, "paid-token") + + backupFile, err := svc.WriteBackup(context.Background()) + require.NoError(t, err) + require.Equal(t, archivedBackupFilePath(dir, writeTime), backupFile) + + key, err := svc.deriveEncryptionKey(context.Background()) + require.NoError(t, err) + + plaintext, err := readBackupPlaintext(key, backupFile) + require.NoError(t, err) + + var payload backupPayload + err = json.Unmarshal(plaintext, &payload) + require.NoError(t, err) + + require.EqualValues(t, backupVersion, payload.Version) + require.Equal(t, "testnet", payload.Network) + require.NotNil(t, payload.StaticAddress) + require.EqualValues( + t, addrParams.ProtocolVersion, payload.StaticAddress.ProtocolVersion, + ) + require.Equal( + t, addrParams.ServerPubkey.SerializeCompressed(), + payload.StaticAddress.ServerPubKey, + ) + require.Equal(t, addrParams.Expiry, payload.StaticAddress.Expiry) + require.Equal( + t, int32(addrParams.KeyLocator.Family), + payload.StaticAddress.ClientKeyFamily, + ) + require.Equal( + t, addrParams.KeyLocator.Index, + payload.StaticAddress.ClientKeyIndex, + ) + require.Equal(t, addrParams.PkScript, payload.StaticAddress.PkScript) + require.Equal( + t, addrParams.InitiationHeight, + payload.StaticAddress.InitiationHeight, + ) + require.Len(t, payload.TokenFiles, 1) + require.Equal(t, paidTokenFileName, payload.TokenFiles[0].Name) + require.Equal(t, []byte("paid-token"), payload.TokenFiles[0].Data) + + canonicalPlaintext, err := readBackupPlaintext( + key, DefaultBackupFilePath(dir), + ) + require.NoError(t, err) + require.Equal(t, plaintext, canonicalPlaintext) +} + +func TestWriteBackupRecreatesCanonicalWithoutNewArchive(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + lnd := testutils.NewMockLnd() + + svc := NewService( + dir, "testnet", lnd.Signer, lnd.WalletKit, nil, nil, + ) + writeTime := time.Date( + 2026, time.April, 14, 9, 30, 1, 123, time.UTC, + ) + svc.now = func() time.Time { + return writeTime + } + + writePaidToken(t, dir, "paid-token") + + firstBackup, err := svc.WriteBackup(context.Background()) + require.NoError(t, err) + require.FileExists(t, firstBackup) + + err = os.Remove(DefaultBackupFilePath(dir)) + require.NoError(t, err) + + secondBackup, err := svc.WriteBackup(context.Background()) + require.NoError(t, err) + require.Equal(t, DefaultBackupFilePath(dir), secondBackup) + require.FileExists(t, DefaultBackupFilePath(dir)) + + latestFile, err := latestBackupFilePath(dir) + require.NoError(t, err) + require.Equal(t, firstBackup, latestFile) +} + +func TestRestoreLatestBackupPrefersNewestTimestampedArchive(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + lnd := testutils.NewMockLnd() + + svc := NewService( + dir, "testnet", lnd.Signer, lnd.WalletKit, nil, nil, + ) + firstTime := time.Date( + 2026, time.April, 14, 9, 30, 1, 123, time.UTC, + ) + secondTime := firstTime.Add(time.Second) + + svc.now = func() time.Time { + return firstTime + } + writePaidToken(t, dir, "paid-token-one") + firstBackup, err := svc.WriteBackup(context.Background()) + require.NoError(t, err) + + svc.now = func() time.Time { + return secondTime + } + writePaidToken(t, dir, "paid-token-two") + secondBackup, err := svc.WriteBackup(context.Background()) + require.NoError(t, err) + + err = os.Remove(filepath.Join(dir, paidTokenFileName)) + require.NoError(t, err) + + result, err := svc.Restore(context.Background(), "") + require.NoError(t, err) + require.Equal(t, secondBackup, result.BackupFile) + require.True(t, result.RestoredL402) + require.False(t, result.RestoredStaticAddress) + require.NotEqual(t, firstBackup, result.BackupFile) + + restoredToken, err := os.ReadFile(filepath.Join(dir, paidTokenFileName)) + require.NoError(t, err) + require.Equal(t, []byte("paid-token-two"), restoredToken) +} + +func TestRestoreStaticAddressAndPaidToken(t *testing.T) { + t.Parallel() + + ctx := context.Background() + lnd := testutils.NewMockLnd() + backupDir := t.TempDir() + restoreDir := t.TempDir() + + addrParams := makeStaticAddressParams( + t, lnd, 7, defaultRecoveryServerPubkey, 144, 321, + ) + sourceStaticMgr := &mockStaticAddressManager{ + chainParams: lnd.ChainParams, + params: addrParams, + } + sourceSvc := NewService( + backupDir, "testnet", lnd.Signer, lnd.WalletKit, + sourceStaticMgr, nil, + ) + sourceSvc.now = func() time.Time { + return time.Date(2026, time.April, 14, 9, 30, 1, 0, time.UTC) + } + + writePaidToken(t, backupDir, "paid-token") + backupFile, err := sourceSvc.WriteBackup(ctx) + require.NoError(t, err) + + destStaticMgr := &mockStaticAddressManager{ + chainParams: lnd.ChainParams, + } + depositMgr := &mockDepositManager{ + depositsFound: 3, + } + destSvc := NewService( + restoreDir, "testnet", lnd.Signer, lnd.WalletKit, + destStaticMgr, depositMgr, + ) + + result, err := destSvc.Restore(ctx, backupFile) + require.NoError(t, err) + require.Equal(t, backupFile, result.BackupFile) + require.True(t, result.RestoredStaticAddress) + require.True(t, result.RestoredL402) + require.Equal(t, 3, result.NumDepositsFound) + require.Empty(t, result.DepositReconciliationError) + + require.Len(t, destStaticMgr.restoreCalls, 1) + restoredParams := destStaticMgr.restoreCalls[0] + require.True(t, restoredParams.ClientPubkey.IsEqual(addrParams.ClientPubkey)) + require.True(t, restoredParams.ServerPubkey.IsEqual(addrParams.ServerPubkey)) + require.Equal(t, addrParams.Expiry, restoredParams.Expiry) + require.Equal(t, addrParams.PkScript, restoredParams.PkScript) + require.Equal(t, addrParams.KeyLocator, restoredParams.KeyLocator) + require.Equal( + t, addrParams.ProtocolVersion, restoredParams.ProtocolVersion, + ) + require.Equal( + t, addrParams.InitiationHeight, restoredParams.InitiationHeight, + ) + + require.Equal(t, 1, depositMgr.calls) + restoredToken, err := os.ReadFile( + filepath.Join(restoreDir, paidTokenFileName), + ) + require.NoError(t, err) + require.Equal(t, []byte("paid-token"), restoredToken) + + expectedAddr, err := destStaticMgr.GetTaprootAddress( + addrParams.ClientPubkey, addrParams.ServerPubkey, + int64(addrParams.Expiry), + ) + require.NoError(t, err) + require.Equal(t, expectedAddr.String(), result.StaticAddress) +} + +func TestRestoreReturnsDepositReconciliationError(t *testing.T) { + t.Parallel() + + ctx := context.Background() + lnd := testutils.NewMockLnd() + backupDir := t.TempDir() + restoreDir := t.TempDir() + + addrParams := makeStaticAddressParams( + t, lnd, 7, defaultRecoveryServerPubkey, 144, 321, + ) + sourceStaticMgr := &mockStaticAddressManager{ + chainParams: lnd.ChainParams, + params: addrParams, + } + sourceSvc := NewService( + backupDir, "testnet", lnd.Signer, lnd.WalletKit, + sourceStaticMgr, nil, + ) + writePaidToken(t, backupDir, "paid-token") + + backupFile, err := sourceSvc.WriteBackup(ctx) + require.NoError(t, err) + + depositErr := errors.New("reconcile failed") + destSvc := NewService( + restoreDir, "testnet", lnd.Signer, lnd.WalletKit, + &mockStaticAddressManager{chainParams: lnd.ChainParams}, + &mockDepositManager{err: depositErr}, + ) + + result, err := destSvc.Restore(ctx, backupFile) + require.NoError(t, err) + require.Equal(t, depositErr.Error(), result.DepositReconciliationError) + require.Equal(t, 0, result.NumDepositsFound) +} + +func TestRestoreRejectsNetworkMismatch(t *testing.T) { + t.Parallel() + + ctx := context.Background() + dir := t.TempDir() + lnd := testutils.NewMockLnd() + + sourceSvc := NewService( + dir, "testnet", lnd.Signer, lnd.WalletKit, nil, nil, + ) + writePaidToken(t, dir, "paid-token") + backupFile, err := sourceSvc.WriteBackup(ctx) + require.NoError(t, err) + + restoreSvc := NewService( + t.TempDir(), "mainnet", lnd.Signer, lnd.WalletKit, nil, nil, + ) + + _, err = restoreSvc.Restore(ctx, backupFile) + require.ErrorContains(t, err, "does not match") +} + +func TestRestoreFailsWithoutStaticAddressManager(t *testing.T) { + t.Parallel() + + ctx := context.Background() + lnd := testutils.NewMockLnd() + backupDir := t.TempDir() + + addrParams := makeStaticAddressParams( + t, lnd, 3, defaultRecoveryServerPubkey, 144, 321, + ) + sourceSvc := NewService( + backupDir, "testnet", lnd.Signer, lnd.WalletKit, + &mockStaticAddressManager{ + chainParams: lnd.ChainParams, + params: addrParams, + }, nil, + ) + writePaidToken(t, backupDir, "paid-token") + backupFile, err := sourceSvc.WriteBackup(ctx) + require.NoError(t, err) + + restoreSvc := NewService( + t.TempDir(), "testnet", lnd.Signer, lnd.WalletKit, nil, nil, + ) + + _, err = restoreSvc.Restore(ctx, backupFile) + require.ErrorContains(t, err, "static address restore is unavailable") +} + +func TestResolveClientKeyFallsBackToGapSearch(t *testing.T) { + t.Parallel() + + ctx := context.Background() + lnd := testutils.NewMockLnd() + targetIndex := uint32(5) + addrParams := makeStaticAddressParams( + t, lnd, targetIndex, defaultRecoveryServerPubkey, 144, 321, + ) + staticMgr := &mockStaticAddressManager{ + chainParams: lnd.ChainParams, + } + svc := NewService( + t.TempDir(), "testnet", lnd.Signer, lnd.WalletKit, + staticMgr, nil, + ) + + for range 8 { + _, err := lnd.WalletKit.DeriveNextKey( + ctx, swap.StaticAddressKeyFamily, + ) + require.NoError(t, err) + } + + taprootAddr, err := staticMgr.GetTaprootAddress( + addrParams.ClientPubkey, addrParams.ServerPubkey, + int64(addrParams.Expiry), + ) + require.NoError(t, err) + + backup := &staticAddressBackup{ + ProtocolVersion: uint32(addrParams.ProtocolVersion), + ServerPubKey: addrParams.ServerPubkey.SerializeCompressed(), + Expiry: addrParams.Expiry, + ClientKeyFamily: swap.StaticAddressKeyFamily, + ClientKeyIndex: 99, + PkScript: slices.Clone(addrParams.PkScript), + Address: taprootAddr.String(), + InitiationHeight: addrParams.InitiationHeight, + } + + clientKey, locator, err := svc.resolveClientKey( + ctx, backup, addrParams.ServerPubkey, + ) + require.NoError(t, err) + require.Equal(t, addrParams.KeyLocator, locator) + require.True(t, clientKey.IsEqual(addrParams.ClientPubkey)) +} + +func TestRestoreTokenFiles(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + svc := &Service{ + dataDir: dir, + } + + restored, err := svc.restoreTokenFiles([]*l402TokenFileEntry{{ + Name: "l402.token", + Data: []byte("paid-token"), + }}) + require.NoError(t, err) + require.True(t, restored) + + paidToken, err := os.ReadFile(filepath.Join(dir, "l402.token")) + require.NoError(t, err) + require.Equal(t, []byte("paid-token"), paidToken) + + restored, err = svc.restoreTokenFiles([]*l402TokenFileEntry{{ + Name: "l402.token", + Data: []byte("paid-token"), + }}) + require.NoError(t, err) + require.False(t, restored) +} + +func TestRestoreTokenFilesRejectsDifferentExistingToken(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + writePaidToken(t, dir, "current-token") + svc := &Service{ + dataDir: dir, + } + + _, err := svc.restoreTokenFiles([]*l402TokenFileEntry{{ + Name: "l402.token", + Data: []byte("backup-token"), + }}) + require.ErrorContains(t, err, "different contents") +} + +func TestRestoreTokenFilesRejectsInvalidName(t *testing.T) { + t.Parallel() + + svc := &Service{ + dataDir: t.TempDir(), + } + + _, err := svc.restoreTokenFiles([]*l402TokenFileEntry{{ + Name: "l402.token.pending", + Data: []byte("pending-token"), + }}) + require.ErrorContains(t, err, "unexpected token file name") +} + +func TestReadTokenFilesSkipsPendingToken(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + err := os.WriteFile( + filepath.Join(dir, "l402.token.pending"), []byte("pending"), 0600, + ) + require.NoError(t, err) + + err = os.WriteFile( + filepath.Join(dir, "l402.token"), []byte("paid"), 0600, + ) + require.NoError(t, err) + + svc := &Service{ + dataDir: dir, + } + + tokenFiles, err := svc.readTokenFiles() + require.NoError(t, err) + require.Len(t, tokenFiles, 1) + require.Equal(t, "l402.token", tokenFiles[0].Name) + require.Equal(t, []byte("paid"), tokenFiles[0].Data) +} + +func TestWriteBackupFilesCreatesTimestampedAndCanonicalBackups(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + writeTime1 := time.Date(2026, time.April, 14, 9, 30, 1, 123, time.UTC) + writeTime2 := writeTime1.Add(time.Second) + + var key [32]byte + copy(key[:], []byte("0123456789abcdefghijklmnopqrstuv")) + + svc := &Service{ + dataDir: dir, + now: func() time.Time { + return writeTime1 + }, + } + + path, wroteBackup, err := svc.writeBackupFiles(key, []byte("snapshot-one")) + require.NoError(t, err) + require.True(t, wroteBackup) + require.Equal(t, archivedBackupFilePath(dir, writeTime1), path) + + archiveData, err := os.ReadFile(path) + require.NoError(t, err) + + canonicalData, err := os.ReadFile(DefaultBackupFilePath(dir)) + require.NoError(t, err) + require.Equal(t, archiveData, canonicalData) + + path, wroteBackup, err = svc.writeBackupFiles(key, []byte("snapshot-one")) + require.NoError(t, err) + require.False(t, wroteBackup) + require.Empty(t, path) + + svc.now = func() time.Time { + return writeTime2 + } + + path, wroteBackup, err = svc.writeBackupFiles(key, []byte("snapshot-two")) + require.NoError(t, err) + require.True(t, wroteBackup) + require.Equal(t, archivedBackupFilePath(dir, writeTime2), path) + + latestFile, err := latestBackupFilePath(dir) + require.NoError(t, err) + require.Equal(t, path, latestFile) +} + +func TestLatestBackupFilePathFallsBackToCanonical(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + canonicalPath := DefaultBackupFilePath(dir) + err := os.WriteFile(canonicalPath, []byte("backup"), 0600) + require.NoError(t, err) + + latestFile, err := latestBackupFilePath(dir) + require.NoError(t, err) + require.Equal(t, canonicalPath, latestFile) +} + +func TestLatestBackupFilePathHandlesSameTimestampSuffixes(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + backupTime := time.Date(2026, time.April, 14, 9, 30, 1, 123, time.UTC) + + firstPath := archivedBackupFilePath(dir, backupTime) + secondPath := archivedBackupFilePathWithSuffix(dir, backupTime, 1) + + err := os.WriteFile(firstPath, []byte("first"), 0600) + require.NoError(t, err) + + err = os.WriteFile(secondPath, []byte("second"), 0600) + require.NoError(t, err) + + latestFile, err := latestBackupFilePath(dir) + require.NoError(t, err) + require.Equal(t, secondPath, latestFile) +} + +func TestLatestBackupFilePathIgnoresMalformedNames(t *testing.T) { + t.Parallel() + + dir := t.TempDir() + validTime := time.Date(2026, time.April, 14, 9, 30, 1, 123, time.UTC) + validPath := archivedBackupFilePath(dir, validTime) + + err := os.WriteFile(validPath, []byte("valid"), 0600) + require.NoError(t, err) + + err = os.WriteFile( + filepath.Join(dir, "L402_backup_not-a-timestamp.enc"), + []byte("invalid"), 0600, + ) + require.NoError(t, err) + err = os.WriteFile( + filepath.Join(dir, "L402_backup_20260414Tbad.enc"), + []byte("invalid"), 0600, + ) + require.NoError(t, err) + + latestFile, err := latestBackupFilePath(dir) + require.NoError(t, err) + require.Equal(t, validPath, latestFile) +} + +var defaultRecoveryServerPubkey = func() *btcec.PublicKey { + _, pubKey := testutils.CreateKey(42) + return pubKey +}() + +type mockStaticAddressManager struct { + chainParams *chaincfg.Params + params *address.Parameters + getParamsErr error + restoreErr error + restoreCalls []*address.Parameters +} + +func (m *mockStaticAddressManager) GetStaticAddressParameters( + context.Context) (*address.Parameters, error) { + + switch { + case m.getParamsErr != nil: + return nil, m.getParamsErr + + case m.params == nil: + return nil, address.ErrNoStaticAddress + + default: + return cloneAddressParameters(m.params), nil + } +} + +func (m *mockStaticAddressManager) GetTaprootAddress(clientPubkey, + serverPubkey *btcec.PublicKey, expiry int64) (*btcutil.AddressTaproot, + error) { + + return taprootAddress( + clientPubkey, serverPubkey, expiry, m.chainParams, + ) +} + +func (m *mockStaticAddressManager) RestoreAddress(_ context.Context, + params *address.Parameters) (*btcutil.AddressTaproot, error) { + + if m.restoreErr != nil { + return nil, m.restoreErr + } + + m.restoreCalls = append(m.restoreCalls, cloneAddressParameters(params)) + + return m.GetTaprootAddress( + params.ClientPubkey, params.ServerPubkey, int64(params.Expiry), + ) +} + +type mockDepositManager struct { + depositsFound int + err error + calls int +} + +func (m *mockDepositManager) ReconcileDeposits(context.Context) (int, error) { + m.calls++ + return m.depositsFound, m.err +} + +func makeStaticAddressParams(t *testing.T, lnd *testutils.LndMockServices, + index uint32, serverPubKey *btcec.PublicKey, expiry uint32, + initiationHeight int32) *address.Parameters { + + t.Helper() + + keyDesc, err := lnd.WalletKit.DeriveKey( + context.Background(), &keychain.KeyLocator{ + Family: keychain.KeyFamily(swap.StaticAddressKeyFamily), + Index: index, + }, + ) + require.NoError(t, err) + + staticAddress, err := staticaddrscript.NewStaticAddress( + input.MuSig2Version100RC2, int64(expiry), keyDesc.PubKey, + serverPubKey, + ) + require.NoError(t, err) + + pkScript, err := staticAddress.StaticAddressScript() + require.NoError(t, err) + + return &address.Parameters{ + ClientPubkey: keyDesc.PubKey, + ServerPubkey: serverPubKey, + Expiry: expiry, + PkScript: pkScript, + KeyLocator: keyDesc.KeyLocator, + ProtocolVersion: staticaddrversion.ProtocolVersion_V0, + InitiationHeight: initiationHeight, + } +} + +func cloneAddressParameters(params *address.Parameters) *address.Parameters { + if params == nil { + return nil + } + + return &address.Parameters{ + ClientPubkey: params.ClientPubkey, + ServerPubkey: params.ServerPubkey, + Expiry: params.Expiry, + PkScript: slices.Clone(params.PkScript), + KeyLocator: params.KeyLocator, + ProtocolVersion: params.ProtocolVersion, + InitiationHeight: params.InitiationHeight, + } +} + +func taprootAddress(clientPubkey, serverPubkey *btcec.PublicKey, expiry int64, + chainParams *chaincfg.Params) (*btcutil.AddressTaproot, error) { + + staticAddress, err := staticaddrscript.NewStaticAddress( + input.MuSig2Version100RC2, expiry, clientPubkey, serverPubkey, + ) + if err != nil { + return nil, err + } + + return btcutil.NewAddressTaproot( + schnorr.SerializePubKey(staticAddress.TaprootKey), chainParams, + ) +} + +func writePaidToken(t *testing.T, dir, token string) { + t.Helper() + + err := os.WriteFile( + filepath.Join(dir, paidTokenFileName), []byte(token), 0600, + ) + require.NoError(t, err) +} diff --git a/staticaddr/address/manager.go b/staticaddr/address/manager.go index e96d362bf..827e1e91f 100644 --- a/staticaddr/address/manager.go +++ b/staticaddr/address/manager.go @@ -3,7 +3,9 @@ package address import ( "bytes" "context" + "errors" "fmt" + "strings" "sync" "sync/atomic" @@ -21,6 +23,12 @@ import ( "github.com/lightningnetwork/lnd/lnwallet" ) +var ( + // ErrNoStaticAddress is returned when no static address parameters are + // present in the store. + ErrNoStaticAddress = errors.New("no static address parameters found") +) + // ManagerConfig holds the configuration for the address manager. type ManagerConfig struct { // AddressClient is the client that communicates with the loop server @@ -45,6 +53,10 @@ type ManagerConfig struct { // ChainNotifier is the chain notifier that is used to listen for new // blocks. ChainNotifier lndclient.ChainNotifierClient + + // OnStaticAddressCreated is called after a new static address has been + // stored locally and its tapscript has been imported into lnd. + OnStaticAddressCreated func(context.Context) error } // Manager manages the address state machines. @@ -199,6 +211,105 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot, return nil, 0, err } + err = m.importAddressTapscript(ctx, staticAddress) + if err != nil { + return nil, 0, err + } + + address, err := m.GetTaprootAddress( + clientPubKey.PubKey, serverPubKey, int64(serverParams.Expiry), + ) + if err != nil { + return nil, 0, err + } + + if m.cfg.OnStaticAddressCreated != nil { + err = m.cfg.OnStaticAddressCreated(ctx) + if err != nil { + return nil, 0, err + } + } + + return address, int64(serverParams.Expiry), nil +} + +// RestoreAddress recreates a static address record locally and makes sure the +// corresponding tapscript is imported into lnd. If the same address already +// exists locally, the call is idempotent. +func (m *Manager) RestoreAddress(ctx context.Context, + addrParams *Parameters) (*btcutil.AddressTaproot, error) { + + if addrParams == nil { + return nil, fmt.Errorf("missing static address parameters") + } + + staticAddress, err := script.NewStaticAddress( + input.MuSig2Version100RC2, int64(addrParams.Expiry), + addrParams.ClientPubkey, addrParams.ServerPubkey, + ) + if err != nil { + return nil, err + } + + pkScript, err := staticAddress.StaticAddressScript() + if err != nil { + return nil, err + } + + if len(addrParams.PkScript) != 0 && + !bytes.Equal(addrParams.PkScript, pkScript) { + + return nil, fmt.Errorf("static address pk script mismatch") + } + + addrParams.PkScript = pkScript + if addrParams.InitiationHeight <= 0 { + addrParams.InitiationHeight = m.currentHeight.Load() + } + + m.Lock() + existing, err := m.cfg.Store.GetAllStaticAddresses(ctx) + if err != nil { + m.Unlock() + + return nil, err + } + switch { + case len(existing) == 0: + err = m.cfg.Store.CreateStaticAddress(ctx, addrParams) + if err != nil { + m.Unlock() + + return nil, err + } + + case len(existing) > 1: + m.Unlock() + + return nil, fmt.Errorf("more than one static address found") + + case !sameAddressParameters(existing[0], addrParams): + m.Unlock() + + return nil, fmt.Errorf("existing static address differs from " + + "backup") + } + m.Unlock() + + err = m.importAddressTapscript(ctx, staticAddress) + if err != nil { + return nil, err + } + + return m.GetTaprootAddress( + addrParams.ClientPubkey, addrParams.ServerPubkey, + int64(addrParams.Expiry), + ) +} + +func (m *Manager) importAddressTapscript(ctx context.Context, + staticAddress *script.StaticAddress) error { + // Import the static address tapscript into our lnd wallet, so we can // track unspent outputs of it. tapScript := input.TapscriptFullTree( @@ -206,20 +317,33 @@ func (m *Manager) NewAddress(ctx context.Context) (*btcutil.AddressTaproot, ) addr, err := m.cfg.WalletKit.ImportTaprootScript(ctx, tapScript) if err != nil { - return nil, 0, err + // Restoring into an lnd instance that already imported the script is + // expected. Treat the duplicate import as success. + if strings.Contains(err.Error(), "already exists") { + log.Infof("Static address tapscript already imported") + return nil + } + + return err } log.Infof("Imported static address taproot script to lnd wallet: %v", addr) - address, err := m.GetTaprootAddress( - clientPubKey.PubKey, serverPubKey, int64(serverParams.Expiry), - ) - if err != nil { - return nil, 0, err + return nil +} + +func sameAddressParameters(a, b *Parameters) bool { + if a == nil || b == nil { + return false } - return address, int64(serverParams.Expiry), nil + return a.ClientPubkey.IsEqual(b.ClientPubkey) && + a.ServerPubkey.IsEqual(b.ServerPubkey) && + a.Expiry == b.Expiry && + bytes.Equal(a.PkScript, b.PkScript) && + a.KeyLocator == b.KeyLocator && + a.ProtocolVersion == b.ProtocolVersion } // GetTaprootAddress returns a taproot address for the given client and server @@ -297,7 +421,7 @@ func (m *Manager) GetStaticAddressParameters(ctx context.Context) (*Parameters, } if len(params) == 0 { - return nil, fmt.Errorf("no static address parameters found") + return nil, ErrNoStaticAddress } return params[0], nil diff --git a/staticaddr/address/manager_test.go b/staticaddr/address/manager_test.go index c23f246cd..4890ffae0 100644 --- a/staticaddr/address/manager_test.go +++ b/staticaddr/address/manager_test.go @@ -128,6 +128,89 @@ func TestManager(t *testing.T) { require.EqualValues(t, defaultExpiry, expiry) } +// TestRestoreAddress verifies that restoring an address recreates the same +// static address locally without requiring a server call. +func TestRestoreAddress(t *testing.T) { + ctxb := t.Context() + + testContext := NewAddressManagerTestContext(t) + + keyDesc, err := testContext.mockLnd.WalletKit.DeriveKey( + ctxb, &keychain.KeyLocator{ + Family: keychain.KeyFamily(swap.StaticAddressKeyFamily), + Index: 7, + }, + ) + require.NoError(t, err) + + staticAddress, err := script.NewStaticAddress( + input.MuSig2Version100RC2, int64(defaultExpiry), + keyDesc.PubKey, defaultServerPubkey, + ) + require.NoError(t, err) + + pkScript, err := staticAddress.StaticAddressScript() + require.NoError(t, err) + + addressParams := &Parameters{ + ClientPubkey: keyDesc.PubKey, + ServerPubkey: defaultServerPubkey, + Expiry: defaultExpiry, + PkScript: pkScript, + KeyLocator: keyDesc.KeyLocator, + ProtocolVersion: 0, + InitiationHeight: 123, + } + + taprootAddress, err := testContext.manager.RestoreAddress( + ctxb, addressParams, + ) + require.NoError(t, err) + + expectedAddress, err := btcutil.NewAddressTaproot( + schnorr.SerializePubKey(staticAddress.TaprootKey), + testContext.manager.cfg.ChainParams, + ) + require.NoError(t, err) + require.Equal(t, expectedAddress.String(), taprootAddress.String()) + + storedParams, err := testContext.manager.GetStaticAddressParameters(ctxb) + require.NoError(t, err) + require.True(t, sameAddressParameters(storedParams, addressParams)) +} + +func TestNewAddressCallsOnStaticAddressCreatedOnce(t *testing.T) { + ctxb := t.Context() + + testContext := NewAddressManagerTestContext(t) + var calls int + testContext.manager.cfg.OnStaticAddressCreated = func(context.Context) error { + calls++ + return nil + } + + _, _, err := testContext.manager.NewAddress(ctxb) + require.NoError(t, err) + require.Equal(t, 1, calls) + + _, _, err = testContext.manager.NewAddress(ctxb) + require.NoError(t, err) + require.Equal(t, 1, calls) +} + +func TestNewAddressPropagatesOnStaticAddressCreatedError(t *testing.T) { + ctxb := t.Context() + + testContext := NewAddressManagerTestContext(t) + expectedErr := context.DeadlineExceeded + testContext.manager.cfg.OnStaticAddressCreated = func(context.Context) error { + return expectedErr + } + + _, _, err := testContext.manager.NewAddress(ctxb) + require.ErrorIs(t, err, expectedErr) +} + // GenerateExpectedTaprootAddress generates the expected taproot address that // the predefined parameters are supposed to generate. func GenerateExpectedTaprootAddress(t *ManagerTestContext) ( diff --git a/staticaddr/deposit/manager.go b/staticaddr/deposit/manager.go index af8820302..9376bfea3 100644 --- a/staticaddr/deposit/manager.go +++ b/staticaddr/deposit/manager.go @@ -64,6 +64,10 @@ type Manager struct { // mu guards access to the activeDeposits map. mu sync.Mutex + // reconcileMu serializes deposit recovery and reconciliation so restore + // requests can't race the background polling loop. + reconcileMu sync.Mutex + // activeDeposits contains all the active static address outputs. activeDeposits map[wire.OutPoint]*FSM @@ -108,7 +112,7 @@ func (m *Manager) Run(ctx context.Context, initChan chan struct{}) error { // Reconcile immediately on startup so deposits are available // before the first ticker fires. - err = m.reconcileDeposits(ctx) + _, err = m.ReconcileDeposits(ctx) if err != nil { log.Errorf("unable to reconcile deposits: %v", err) } @@ -162,6 +166,9 @@ func (m *Manager) Run(ctx context.Context, initChan chan struct{}) error { // recoverDeposits recovers static address parameters, previous deposits and // state machines from the database and starts the deposit notifier. func (m *Manager) recoverDeposits(ctx context.Context) error { + m.reconcileMu.Lock() + defer m.reconcileMu.Unlock() + log.Infof("Recovering static address parameters and deposits...") // Recover deposits. @@ -218,7 +225,7 @@ func (m *Manager) pollDeposits(ctx context.Context) { for { select { case <-ticker.C: - err := m.reconcileDeposits(ctx) + _, err := m.ReconcileDeposits(ctx) if err != nil { log.Errorf("unable to reconcile "+ "deposits: %v", err) @@ -235,38 +242,47 @@ func (m *Manager) pollDeposits(ctx context.Context) { // wallet and matches it against the deposits in our memory that we've seen so // far. It picks the newly identified deposits and starts a state machine per // deposit to track its progress. -func (m *Manager) reconcileDeposits(ctx context.Context) error { +func (m *Manager) reconcileDeposits(ctx context.Context) (int, error) { log.Tracef("Reconciling new deposits...") utxos, err := m.cfg.AddressManager.ListUnspent( ctx, MinConfs, MaxConfs, ) if err != nil { - return fmt.Errorf("unable to list new deposits: %w", err) + return 0, fmt.Errorf("unable to list new deposits: %w", err) } newDeposits := m.filterNewDeposits(utxos) if len(newDeposits) == 0 { log.Tracef("No new deposits...") - return nil + return 0, nil } for _, utxo := range newDeposits { deposit, err := m.createNewDeposit(ctx, utxo) if err != nil { - return fmt.Errorf("unable to retain new deposit: %w", + return 0, fmt.Errorf("unable to retain new deposit: %w", err) } log.Debugf("Received deposit: %v", deposit) err = m.startDepositFsm(ctx, deposit) if err != nil { - return fmt.Errorf("unable to start new deposit FSM: %w", + return 0, fmt.Errorf("unable to start new deposit FSM: %w", err) } } - return nil + return len(newDeposits), nil +} + +// ReconcileDeposits triggers a best-effort reconciliation pass and returns the +// number of newly discovered deposits. +func (m *Manager) ReconcileDeposits(ctx context.Context) (int, error) { + m.reconcileMu.Lock() + defer m.reconcileMu.Unlock() + + return m.reconcileDeposits(ctx) } // createNewDeposit transforms the wallet utxo into a deposit struct and stores