From 3dff64201721290dbb7b1e6f2c07812f0b12dd70 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Mon, 2 Feb 2026 19:22:41 -0500 Subject: [PATCH 01/18] cmd/loop: print homedir in help messages as ~ Make the output homedir-independent to facilitate tests. --- cmd/loop/default_path_text_test.go | 82 ++++++++++++++++++++++++++++++ cmd/loop/main.go | 58 +++++++++++++++++---- 2 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 cmd/loop/default_path_text_test.go diff --git a/cmd/loop/default_path_text_test.go b/cmd/loop/default_path_text_test.go new file mode 100644 index 000000000..1785966b1 --- /dev/null +++ b/cmd/loop/default_path_text_test.go @@ -0,0 +1,82 @@ +package main + +import ( + "errors" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +// TestDefaultPathText verifies HOME-based path elision behavior used for help +// defaults without relying on the real environment. +func TestDefaultPathText(t *testing.T) { + sep := string(filepath.Separator) + home := filepath.Clean(sep + "home" + sep + "alice") + + homeDir := func() (string, error) { return home, nil } + + tests := []struct { + name string + value string + homeFn func() (string, error) + want string + }{ + { + name: "empty value", + value: "", + homeFn: func() (string, error) { + return home, nil + }, + want: "", + }, + { + name: "nil homedir func", + value: home + sep + "data", + homeFn: nil, + want: home + sep + "data", + }, + { + name: "homedir error", + value: home + sep + "data", + homeFn: func() (string, error) { + return "", errors.New("homedir error") + }, + want: home + sep + "data", + }, + { + name: "exact home", + value: home, + homeFn: homeDir, + want: "~", + }, + { + name: "home prefix", + value: home + sep + "dir" + sep + "file", + homeFn: homeDir, + want: "~" + sep + "dir" + sep + "file", + }, + { + name: "non-home path", + value: filepath.Clean(sep + "var" + sep + "tmp"), + homeFn: homeDir, + want: filepath.Clean(sep + "var" + sep + "tmp"), + }, + { + name: "prefix but not path segment", + value: home + "x" + sep + "dir", + homeFn: homeDir, + want: home + "x" + sep + "dir", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got := defaultPathText(test.value, test.homeFn) + require.Equalf( + t, test.want, got, + "defaultPathText(%q)", test.value, + ) + }) + } +} diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 3f82be775..873a198d0 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -52,10 +52,11 @@ var ( defaultInitiator = "loop-cli" loopDirFlag = &cli.StringFlag{ - Name: "loopdir", - Value: loopd.LoopDirBase, - Usage: "path to loop's base directory", - Sources: cli.EnvVars(envVarLoopDir), + Name: "loopdir", + Value: loopd.LoopDirBase, + DefaultText: defaultPathText(loopd.LoopDirBase, os.UserHomeDir), + Usage: "path to loop's base directory", + Sources: cli.EnvVars(envVarLoopDir), } networkFlag = &cli.StringFlag{ Name: "network", @@ -66,15 +67,21 @@ var ( } tlsCertFlag = &cli.StringFlag{ - Name: "tlscertpath", - Usage: "path to loop's TLS certificate", - Value: loopd.DefaultTLSCertPath, + Name: "tlscertpath", + Usage: "path to loop's TLS certificate", + Value: loopd.DefaultTLSCertPath, + DefaultText: defaultPathText( + loopd.DefaultTLSCertPath, os.UserHomeDir, + ), Sources: cli.EnvVars(envVarTLSCertPath), } macaroonPathFlag = &cli.StringFlag{ - Name: "macaroonpath", - Usage: "path to macaroon file", - Value: loopd.DefaultMacaroonPath, + Name: "macaroonpath", + Usage: "path to macaroon file", + Value: loopd.DefaultMacaroonPath, + DefaultText: defaultPathText( + loopd.DefaultMacaroonPath, os.UserHomeDir, + ), Sources: cli.EnvVars(envVarMacaroonPath), } verboseFlag = &cli.BoolFlag{ @@ -132,6 +139,37 @@ const ( envVarMacaroonPath = "LOOPCLI_MACAROONPATH" ) +// defaultPathText returns a help-friendly path string that replaces the user's +// home directory with "~". The homeDir function is injected so callers can +// control environment-dependent behavior in tests. +func defaultPathText(value string, homeDir func() (string, error)) string { + if value == "" { + return value + } + + if homeDir == nil { + return value + } + + home, err := homeDir() + if err != nil || home == "" { + return value + } + + cleanHome := filepath.Clean(home) + cleanValue := filepath.Clean(value) + if cleanValue == cleanHome { + return "~" + } + + prefix := cleanHome + string(filepath.Separator) + if suffix, ok := strings.CutPrefix(cleanValue, prefix); ok { + return "~" + string(filepath.Separator) + suffix + } + + return value +} + func printJSON(resp any) { b, err := json.Marshal(resp) if err != nil { From fae968b59208cec5373f16fba45225160ab6a5d1 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 3 Feb 2026 00:07:41 -0500 Subject: [PATCH 02/18] cmd/loop: remove orphan flag access abandonSwap used to access flag --id which does not exist. --- cmd/loop/swaps.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/loop/swaps.go b/cmd/loop/swaps.go index 9aa298d20..9323f6ce5 100644 --- a/cmd/loop/swaps.go +++ b/cmd/loop/swaps.go @@ -206,9 +206,6 @@ func abandonSwap(ctx context.Context, cmd *cli.Command) error { var id string switch { - case cmd.IsSet("id"): - id = cmd.String("id") - case cmd.NArg() > 0: id = args.First() From a4b10162d7459961ecf4000a6d46ab7c97dd7cee Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 3 Feb 2026 01:54:28 -0500 Subject: [PATCH 03/18] cmd/loop: add client conn cleanup --- cmd/loop/debug.go | 3 +-- cmd/loop/main.go | 17 ++++++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/cmd/loop/debug.go b/cmd/loop/debug.go index 9bb17106c..e2f7318c0 100644 --- a/cmd/loop/debug.go +++ b/cmd/loop/debug.go @@ -48,11 +48,10 @@ func getDebugClient(ctx context.Context, cmd *cli.Command) (looprpc.DebugClient, if err != nil { return nil, nil, err } - conn, err := getClientConn(rpcServer, tlsCertPath, macaroonPath) + conn, cleanup, err := getClientConn(rpcServer, tlsCertPath, macaroonPath) if err != nil { return nil, nil, err } - cleanup := func() { conn.Close() } debugClient := looprpc.NewDebugClient(conn) return debugClient, cleanup, nil diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 873a198d0..d12fdea9f 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -251,11 +251,10 @@ func getClientWithConn(cmd *cli.Command) (looprpc.SwapClientClient, if err != nil { return nil, nil, nil, err } - conn, err := getClientConn(rpcServer, tlsCertPath, macaroonPath) + conn, cleanup, err := getClientConn(rpcServer, tlsCertPath, macaroonPath) if err != nil { return nil, nil, nil, err } - cleanup := func() { conn.Close() } loopClient := looprpc.NewSwapClientClient(conn) return loopClient, conn, cleanup, nil @@ -465,12 +464,12 @@ func logSwap(swap *looprpc.SwapStatus) { } func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn, - error) { + func(), error) { // We always need to send a macaroon. macOption, err := readMacaroon(macaroonPath) if err != nil { - return nil, err + return nil, nil, err } opts := []grpc.DialOption{ @@ -481,18 +480,22 @@ func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn, // Since TLS cannot be disabled, we'll always have a cert file to read. creds, err := credentials.NewClientTLSFromFile(tlsCertPath, "") if err != nil { - return nil, err + return nil, nil, err } opts = append(opts, grpc.WithTransportCredentials(creds)) conn, err := grpc.NewClient(address, opts...) if err != nil { - return nil, fmt.Errorf("unable to create RPC client: %v", + return nil, nil, fmt.Errorf("unable to create RPC client: %v", err) } - return conn, nil + cleanup := func() { + _ = conn.Close() + } + + return conn, cleanup, nil } // readMacaroon tries to read the macaroon file at the specified path and create From 498fb5c6d2fb68c624d4ef1eef980e10745d6fe8 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Wed, 4 Feb 2026 16:29:24 -0500 Subject: [PATCH 04/18] cmd/loop: factor newRootCommand --- cmd/loop/main.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cmd/loop/main.go b/cmd/loop/main.go index d12fdea9f..8921e1bb9 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -201,7 +201,15 @@ func fatal(err error) { } func main() { - rootCmd := &cli.Command{ + rootCmd := newRootCommand() + if err := rootCmd.Run(context.Background(), os.Args); err != nil { + fatal(err) + } +} + +// newRootCommand constructs the CLI root command for loop. +func newRootCommand() *cli.Command { + return &cli.Command{ Name: "loop", Usage: "control plane for your loopd", Version: loop.RichVersion(), @@ -222,10 +230,6 @@ func main() { return cli.ShowRootCommandHelp(cmd) }, } - - if err := rootCmd.Run(context.Background(), os.Args); err != nil { - fatal(err) - } } // getClient establishes a SwapClient RPC connection and returns the client and From 8159c19227017d2da7249fe974df989a3225acfa Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Wed, 4 Feb 2026 16:30:10 -0500 Subject: [PATCH 05/18] cmd/loop: add cli clock and hook --- cmd/loop/loopout.go | 4 ++-- cmd/loop/main.go | 14 ++++++++++++++ cmd/loop/quote.go | 4 ++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/cmd/loop/loopout.go b/cmd/loop/loopout.go index f1da46f61..8e6edf0df 100644 --- a/cmd/loop/loopout.go +++ b/cmd/loop/loopout.go @@ -248,9 +248,9 @@ func loopOut(ctx context.Context, cmd *cli.Command) error { // Set our maximum swap wait time. If a fast swap is requested we set // it to now, otherwise to 30 minutes in the future. fast := cmd.Bool("fast") - swapDeadline := time.Now() + swapDeadline := cliClock.Now() if !fast { - swapDeadline = time.Now().Add(defaultSwapWaitTime) + swapDeadline = cliClock.Now().Add(defaultSwapWaitTime) } sweepConfTarget := int32(cmd.Uint64("conf_target")) diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 8921e1bb9..f990f913a 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -19,6 +19,7 @@ import ( "github.com/lightninglabs/loop/loopd" "github.com/lightninglabs/loop/looprpc" "github.com/lightninglabs/loop/swap" + "github.com/lightningnetwork/lnd/clock" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/macaroons" @@ -99,6 +100,9 @@ var ( instantOutCommand, listInstantOutsCommand, stopCommand, printManCommand, printMarkdownCommand, } + + // cliClock provides the time source used by CLI commands. + cliClock clock.Clock = clock.NewDefaultClock() ) const ( @@ -264,6 +268,16 @@ func getClientWithConn(cmd *cli.Command) (looprpc.SwapClientClient, return loopClient, conn, cleanup, nil } +// hookClock overrides cliClock until the returned callback is called. +func hookClock(c clock.Clock) func() { + prev := cliClock + cliClock = c + + return func() { + cliClock = prev + } +} + func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount { return swap.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate) } diff --git a/cmd/loop/quote.go b/cmd/loop/quote.go index d6e7b6c26..46826eaef 100644 --- a/cmd/loop/quote.go +++ b/cmd/loop/quote.go @@ -206,9 +206,9 @@ func quoteOut(ctx context.Context, cmd *cli.Command) error { defer cleanup() fast := cmd.Bool("fast") - swapDeadline := time.Now() + swapDeadline := cliClock.Now() if !fast { - swapDeadline = time.Now().Add(defaultSwapWaitTime) + swapDeadline = cliClock.Now().Add(defaultSwapWaitTime) } quoteReq := &looprpc.QuoteRequest{ From 5a383f9e8ba843125020258ab098ebb2a7cadd62 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Wed, 4 Feb 2026 16:32:10 -0500 Subject: [PATCH 06/18] cmd/loop: add stdio hooks --- cmd/loop/session_stdio.go | 143 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 cmd/loop/session_stdio.go diff --git a/cmd/loop/session_stdio.go b/cmd/loop/session_stdio.go new file mode 100644 index 000000000..15cd152c4 --- /dev/null +++ b/cmd/loop/session_stdio.go @@ -0,0 +1,143 @@ +package main + +import ( + "io" + "os" + "sync" +) + +// hookStdout redirects stdout and returns a function to restore it. +func hookStdout(orig *os.File, forward io.Writer, + onChunk func([]byte)) (func() error, error) { + + return hookOutput( + func(f *os.File) { os.Stdout = f }, + orig, + forward, + onChunk, + ) +} + +// hookStderr redirects stderr and returns a function to restore it. +func hookStderr(orig *os.File, forward io.Writer, + onChunk func([]byte)) (func() error, error) { + + return hookOutput( + func(f *os.File) { os.Stderr = f }, + orig, + forward, + onChunk, + ) +} + +// hookOutput redirects an output stream and returns a restore function. +func hookOutput(setDest func(*os.File), orig *os.File, forward io.Writer, + onChunk func([]byte)) (func() error, error) { + + r, w, err := os.Pipe() + if err != nil { + return nil, err + } + + setDest(w) + + var wg sync.WaitGroup + wg.Go(func() { + defer r.Close() + + writer := composeWriter(forward, onChunk) + // Restoring the original descriptor closes the pipe from the + // other side, so io.Copy can fail as part of normal teardown. + _, _ = io.Copy(writer, r) + }) + + return func() error { + setDest(orig) + _ = w.Close() + wg.Wait() + + return nil + }, nil +} + +// hookStdin redirects stdin and returns a function to restore it. +func hookStdin(orig *os.File, source io.Reader, + onChunk func([]byte)) (func() error, error) { + + r, w, err := os.Pipe() + if err != nil { + return nil, err + } + + useOrig := false + if source == nil { + source = orig + useOrig = true + } else if source == orig { + useOrig = true + } + + if onChunk != nil { + source = io.TeeReader(source, chunkWriter(onChunk)) + } + + os.Stdin = r + + var wg sync.WaitGroup + wg.Go(func() { + defer w.Close() + + // Closing the pipe during hook restoration can terminate the + // copy loop with an expected error. + _, _ = io.Copy(w, source) + }) + + return func() error { + os.Stdin = orig + _ = r.Close() + _ = w.Close() + // When stdin is still backed by the original terminal, the copy + // goroutine can block indefinitely on user input. In that case + // we rely on closing the pipe or process exit to end the copy + // loop instead of waiting here and hanging the CLI shutdown + // path. + if !useOrig { + wg.Wait() + } + + return nil + }, nil +} + +// composeWriter builds a writer that forwards to the provided sinks. +func composeWriter(forward io.Writer, onChunk func([]byte)) io.Writer { + switch { + case forward != nil && onChunk != nil: + return io.MultiWriter(forward, chunkWriter(onChunk)) + + case forward != nil: + return forward + + case onChunk != nil: + return chunkWriter(onChunk) + + default: + return io.Discard + } +} + +// chunkWriter writes copy-safe chunks to a callback. +type chunkWriter func([]byte) + +// Write copies p before forwarding to the callback. +func (w chunkWriter) Write(p []byte) (int, error) { + if len(p) == 0 { + return 0, nil + } + + copyBuf := make([]byte, len(p)) + copy(copyBuf, p) + w(copyBuf) + + return len(p), nil +} From 1118cb8ad0f9ae99bad96cea373a6641349401df Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Wed, 4 Feb 2026 16:31:27 -0500 Subject: [PATCH 07/18] cmd/loop: add session gRPC transport hook --- cmd/loop/debug.go | 7 +-- cmd/loop/main.go | 20 +++++---- cmd/loop/session_transport.go | 83 +++++++++++++++++++++++++++++++++++ cmd/loop/stop.go | 3 +- 4 files changed, 97 insertions(+), 16 deletions(-) create mode 100644 cmd/loop/session_transport.go diff --git a/cmd/loop/debug.go b/cmd/loop/debug.go index e2f7318c0..84b24dae6 100644 --- a/cmd/loop/debug.go +++ b/cmd/loop/debug.go @@ -43,12 +43,7 @@ func forceAutoloop(ctx context.Context, cmd *cli.Command) error { } func getDebugClient(ctx context.Context, cmd *cli.Command) (looprpc.DebugClient, func(), error) { - rpcServer := cmd.String("rpcserver") - tlsCertPath, macaroonPath, err := extractPathArgs(cmd) - if err != nil { - return nil, nil, err - } - conn, cleanup, err := getClientConn(rpcServer, tlsCertPath, macaroonPath) + conn, cleanup, err := sessionTransport.Dial(cmd) if err != nil { return nil, nil, err } diff --git a/cmd/loop/main.go b/cmd/loop/main.go index f990f913a..f6726b1d4 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -252,14 +252,9 @@ func getClient(cmd *cli.Command) (looprpc.SwapClientClient, // getClientWithConn returns both the SwapClient RPC client and the underlying // gRPC connection so callers can perform connection-aware actions. func getClientWithConn(cmd *cli.Command) (looprpc.SwapClientClient, - *grpc.ClientConn, func(), error) { + daemonConn, func(), error) { - rpcServer := cmd.String("rpcserver") - tlsCertPath, macaroonPath, err := extractPathArgs(cmd) - if err != nil { - return nil, nil, nil, err - } - conn, cleanup, err := getClientConn(rpcServer, tlsCertPath, macaroonPath) + conn, cleanup, err := sessionTransport.Dial(cmd) if err != nil { return nil, nil, nil, err } @@ -481,7 +476,8 @@ func logSwap(swap *looprpc.SwapStatus) { fmt.Println() } -func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn, +// getClientConn dials the loopd gRPC server with TLS and macaroon auth. +func getClientConn(address, tlsCertPath, macaroonPath string) (daemonConn, func(), error) { // We always need to send a macaroon. @@ -495,6 +491,14 @@ func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn, macOption, } + // Install gRPC interceptors for session recording if needed. + if unary := sessionTransport.UnaryInterceptor(); unary != nil { + opts = append(opts, grpc.WithChainUnaryInterceptor(unary)) + } + if stream := sessionTransport.StreamInterceptor(); stream != nil { + opts = append(opts, grpc.WithChainStreamInterceptor(stream)) + } + // Since TLS cannot be disabled, we'll always have a cert file to read. creds, err := credentials.NewClientTLSFromFile(tlsCertPath, "") if err != nil { diff --git a/cmd/loop/session_transport.go b/cmd/loop/session_transport.go new file mode 100644 index 000000000..5f06d244f --- /dev/null +++ b/cmd/loop/session_transport.go @@ -0,0 +1,83 @@ +package main + +import ( + "context" + + "github.com/urfave/cli/v3" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" +) + +// grpcTransport customizes gRPC dialing and interceptors for sessions. +type grpcTransport interface { + // Dial returns a gRPC connection for the CLI. + Dial(cmd *cli.Command) (daemonConn, func(), error) + + // UnaryInterceptor returns the unary interceptor to apply for session + // flows. + UnaryInterceptor() grpc.UnaryClientInterceptor + + // StreamInterceptor returns the stream interceptor to apply for session + // flows. + StreamInterceptor() grpc.StreamClientInterceptor +} + +// daemonConn is the client connection interface required by stop/wait flows. +type daemonConn interface { + grpc.ClientConnInterface + + // GetState reports the current connectivity state of the client + // channel. + GetState() connectivity.State + + // WaitForStateChange blocks until the state changes or the context + // expires. + WaitForStateChange(ctx context.Context, + sourceState connectivity.State) bool + + // Connect forces the channel out of idle mode. + Connect() +} + +// directGrpcTransport establishes real gRPC connections to loopd. +type directGrpcTransport struct{} + +// Dial opens a direct gRPC connection. +func (t *directGrpcTransport) Dial( + cmd *cli.Command) (daemonConn, func(), error) { + + return dialDirectConn(cmd) +} + +// UnaryInterceptor returns nil because direct connections do not need wrapping. +func (t *directGrpcTransport) UnaryInterceptor() grpc.UnaryClientInterceptor { + return nil +} + +// StreamInterceptor returns nil because direct connections do not need +// wrapping. +func (t *directGrpcTransport) StreamInterceptor() grpc.StreamClientInterceptor { + return nil +} + +// sessionTransport defines the active gRPC transport for CLI commands. +var sessionTransport grpcTransport = &directGrpcTransport{} + +// hookGrpc installs the active gRPC session transport hook. +func hookGrpc(transport grpcTransport) func() { + prev := sessionTransport + sessionTransport = transport + + return func() { sessionTransport = prev } +} + +// dialDirectConn returns the standard CLI gRPC connection. +func dialDirectConn(cmd *cli.Command) (daemonConn, func(), error) { + rpcServer := cmd.String("rpcserver") + tlsCertPath, macaroonPath, err := extractPathArgs(cmd) + if err != nil { + return nil, nil, err + } + + return getClientConn(rpcServer, tlsCertPath, macaroonPath) +} diff --git a/cmd/loop/stop.go b/cmd/loop/stop.go index 293c8c6b9..75fbd387a 100644 --- a/cmd/loop/stop.go +++ b/cmd/loop/stop.go @@ -6,7 +6,6 @@ import ( "github.com/lightninglabs/loop/looprpc" "github.com/urfave/cli/v3" - "google.golang.org/grpc" "google.golang.org/grpc/connectivity" ) @@ -63,7 +62,7 @@ func stopDaemon(ctx context.Context, cmd *cli.Command) error { // waitForDaemonShutdown monitors the gRPC connectivity state until the daemon // disappears. To avoid getting stuck in idle mode we nudge the connection to // reconnect when needed. -func waitForDaemonShutdown(ctx context.Context, conn *grpc.ClientConn) error { +func waitForDaemonShutdown(ctx context.Context, conn daemonConn) error { for { state := conn.GetState() From 2f6c38ca3f7ed68d4c54f768ddef82d687a6e909 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Wed, 4 Feb 2026 19:01:44 -0500 Subject: [PATCH 08/18] cmd/loop: add session recorder plumbing --- cmd/loop/main.go | 134 +++++- cmd/loop/session_recorder.go | 690 ++++++++++++++++++++++++++++++ cmd/loop/session_recorder_test.go | 316 ++++++++++++++ 3 files changed, 1134 insertions(+), 6 deletions(-) create mode 100644 cmd/loop/session_recorder.go create mode 100644 cmd/loop/session_recorder_test.go diff --git a/cmd/loop/main.go b/cmd/loop/main.go index f6726b1d4..113404fdc 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -8,9 +8,11 @@ import ( "fmt" "io/ioutil" "os" + "os/signal" "path/filepath" "strconv" "strings" + "syscall" "time" "github.com/btcsuite/btcd/btcutil" @@ -103,8 +105,50 @@ var ( // cliClock provides the time source used by CLI commands. cliClock clock.Clock = clock.NewDefaultClock() + + // sessionRec is the active recorder when session capture is enabled. + sessionRec *sessionRecorder + + // forceDeterministicJSON is enabled by tests to obtain stable JSON + // output. + forceDeterministicJSON bool ) +// installSessionSignalHandler records signals and cancels the root context. +func installSessionSignalHandler(cancel context.CancelFunc) func() { + if sessionRec == nil { + return func() {} + } + + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) + + done := make(chan struct{}) + go func() { + defer close(done) + interrupted := false + for sig := range sigCh { + sessionRec.LogSignal(sig) + if !interrupted { + interrupted = true + cancel() + continue + } + + // A second signal requests immediate shutdown. Finalize the + // session first because os.Exit skips deferred cleanup. + _ = sessionRec.Finalize(fmt.Errorf("signal: %s", sig)) + os.Exit(130) + } + }() + + return func() { + signal.Stop(sigCh) + close(sigCh) + <-done + } +} + const ( // satAmtFmt formats a satoshi value into a one line string, intended to @@ -186,7 +230,8 @@ func printJSON(resp any) { fatal(err) } out.WriteString("\n") - _, _ = out.WriteTo(os.Stdout) + printBytes := maybeNormalizeJSON(out.Bytes()) + _, _ = os.Stdout.Write(printBytes) } func printRespJSON(resp proto.Message) { @@ -196,19 +241,71 @@ func printRespJSON(resp proto.Message) { return } - fmt.Println(string(jsonBytes)) + fmt.Println(string(maybeNormalizeJSON(jsonBytes))) } func fatal(err error) { fmt.Fprintf(os.Stderr, "[loop] %v\n", err) + if sessionRec != nil { + if finalizeErr := sessionRec.Finalize(err); finalizeErr != nil { + fmt.Fprintf(os.Stderr, "[loop] unable to finalize "+ + "session: %v\n", finalizeErr) + } + } os.Exit(1) } func main() { + var err error + sessionRec, err = newSessionRecorder(os.Args) + if err != nil { + fatal(err) + } + + // Intercept clock and stdio if needed. + if sessionRec != nil { + restoreClock := hookClock( + clock.NewTestClock(sessionRec.ClockStart()), + ) + defer restoreClock() + + if err := sessionRec.Start(nil, nil, nil); err != nil { + fatal(err) + } + } + + // Intercept clock calls if needed. + var restoreTransport func() + if sessionRec != nil { + restoreTransport = hookGrpc(sessionRec) + defer restoreTransport() + } + rootCmd := newRootCommand() - if err := rootCmd.Run(context.Background(), os.Args); err != nil { + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + if sessionRec != nil { + ctx = sessionRec.InjectContext(ctx) + } + + if sessionRec != nil { + signalStop := installSessionSignalHandler(cancel) + defer signalStop() + } + + if err := rootCmd.Run(ctx, os.Args); err != nil { fatal(err) } + + if sessionRec != nil { + if err := sessionRec.Finalize(nil); err != nil { + fmt.Fprintf(os.Stderr, "[loop] unable to finalize "+ + "session: %v\n", err) + } + } } // newRootCommand constructs the CLI root command for loop. @@ -238,9 +335,7 @@ func newRootCommand() *cli.Command { // getClient establishes a SwapClient RPC connection and returns the client and // a cleanup handler. -func getClient(cmd *cli.Command) (looprpc.SwapClientClient, - func(), error) { - +func getClient(cmd *cli.Command) (looprpc.SwapClientClient, func(), error) { client, _, cleanup, err := getClientWithConn(cmd) if err != nil { return nil, nil, err @@ -273,6 +368,33 @@ func hookClock(c clock.Clock) func() { } } +// maybeNormalizeJSON rewrites JSON output to avoid the build-dependent spacing +// introduced by google.golang.org/protobuf/internal/encoding/json (see +// WriteName in protobuf-go-hex-display/internal/encoding/json/encode.go, which +// uses internal/detrand.Bool). When recording or replaying sessions we ensure +// stable output by re-encoding with the standard library. +func maybeNormalizeJSON(raw []byte) []byte { + if sessionRec == nil && !forceDeterministicJSON { + return raw + } + + var parsed any + if err := json.Unmarshal(raw, &parsed); err != nil { + return raw + } + + normalized, err := json.MarshalIndent(parsed, "", " ") + if err != nil { + return raw + } + + if len(raw) > 0 && raw[len(raw)-1] == '\n' { + normalized = append(normalized, '\n') + } + + return normalized +} + func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount { return swap.CalcFee(amt, maxRoutingFeeBase, maxRoutingFeeRate) } diff --git a/cmd/loop/session_recorder.go b/cmd/loop/session_recorder.go new file mode 100644 index 000000000..49c55e553 --- /dev/null +++ b/cmd/loop/session_recorder.go @@ -0,0 +1,690 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" + + "github.com/lightninglabs/loop" + "github.com/urfave/cli/v3" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +const ( + sessionEnvVar = "LOOP_SESSION_RECORD" + sessionDefaultDir = "cmd/loop/testdata/sessions" + sessionFileExt = ".json" + defaultSessionSlug = "session" +) + +// Event identifiers in recorded sessions. +const ( + eventStdout = "stdout" + eventStderr = "stderr" + eventStdin = "stdin" + eventGrpc = "grpc" + eventExit = "exit" + eventSignal = "signal" +) + +// grpcMarshalOptions is marshalling options to encode gRPC messages in recorded +// sessions. +var grpcMarshalOptions = protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: true, +} + +// sessionRecorder captures CLI IO and gRPC traffic for replay. +type sessionRecorder struct { + mu sync.Mutex + started time.Time + filePath string + slug string + + metadata sessionMetadata + events []sessionEvent + eventErr error + + finalizeOnce sync.Once + + hooksMu sync.Mutex + hooksStarted bool + stdoutUnhook func() error + stderrUnhook func() error + stdinUnhook func() error +} + +// sessionFile is a single recorded CLI session in JSON format. They are +// stored inside sessionDefaultDir. +type sessionFile struct { + Metadata sessionMetadata `json:"metadata"` + Events []sessionEvent `json:"events"` +} + +// sessionMetadata stores static session details and runtime metadata. +type sessionMetadata struct { + Args []string `json:"args"` + Env map[string]string `json:"env"` + Version string `json:"version"` + ClockStartUnix int64 `json:"clock_start_unix"` + RunError *string `json:"run_error,omitempty"` + Duration *time.Duration `json:"duration,omitempty"` +} + +// sessionEvent records a single timestamped payload entry. +type sessionEvent struct { + TimeMS int64 `json:"time_ms"` + Kind string `json:"kind"` + Data json.RawMessage `json:"data"` +} + +// textPayload records stdout/stderr text chunks as newline-preserving lines. +type textPayload struct { + Lines []string `json:"lines"` +} + +// newTextPayload splits text into newline-preserving lines for fixture output. +func newTextPayload(text string) textPayload { + return textPayload{ + Lines: splitTextLines(text), + } +} + +// text joins a recorded text payload back into the original output. +func (p textPayload) text() string { + return joinTextLines(p.Lines) +} + +// stdinPayload records stdin chunks. +type stdinPayload struct { + Text string `json:"text"` +} + +// splitTextLines preserves line endings while splitting text for fixture +// storage. +func splitTextLines(text string) []string { + if text == "" { + return nil + } + + lines := strings.SplitAfter(text, "\n") + if lines[len(lines)-1] == "" { + lines = lines[:len(lines)-1] + } + + return lines +} + +// joinTextLines reconstructs text stored as newline-preserving lines. +func joinTextLines(lines []string) string { + return strings.Join(lines, "") +} + +// grpcPayload records a gRPC message or error event. +type grpcPayload struct { + Method string `json:"method"` + Event string `json:"event"` + MessageType string `json:"message_type,omitempty"` + Payload json.RawMessage `json:"payload,omitempty"` + Error string `json:"error,omitempty"` +} + +// exitPayload records the final run error, if any. +type exitPayload struct { + RunError *string `json:"run_error,omitempty"` +} + +// signalPayload records handled signals. +type signalPayload struct { + Signal string `json:"signal"` +} + +// newSessionRecorder creates a recorder when session recording is enabled. +func newSessionRecorder(args []string) (*sessionRecorder, error) { + // Session recording is disabled unless the env var is set. + envValue, ok := os.LookupEnv(sessionEnvVar) + if !ok { + return nil, nil + } + + // Allow explicit disable by setting the env var to false. + enabled, err := strconv.ParseBool(envValue) + if err != nil { + return nil, fmt.Errorf("invalid %s value %q", sessionEnvVar, + envValue) + } + if !enabled { + return nil, nil + } + + // Initialize the recorder before collecting metadata. + recorder := &sessionRecorder{ + started: time.Now(), + } + + // Derive the slug before resolving the output file path. + recorder.slug = deriveSessionSlug(args) + + // Capture metadata that remains stable for the session. + metadata := sessionMetadata{ + Args: append([]string(nil), args...), + Env: collectSessionEnv(), + Version: loop.RichVersion(), + ClockStartUnix: recorder.started.Unix(), + } + recorder.metadata = metadata + + // Resolve the session file location. + baseDir, fileName, err := recorder.resolveFilePath() + if err != nil { + return nil, err + } + recorder.filePath = filepath.Join(baseDir, fileName) + + return recorder, nil +} + +// collectSessionEnv extracts the environment variables recorded in sessions. +func collectSessionEnv() map[string]string { + env := make(map[string]string) + + // Record only LOOPCLI_ variables, excluding the recording toggle. + for _, kv := range os.Environ() { + parts := strings.SplitN(kv, "=", 2) + if len(parts) != 2 { + continue + } + key := parts[0] + value := parts[1] + if key == sessionEnvVar { + continue + } + if strings.HasPrefix(key, "LOOPCLI_") { + env[key] = value + } + } + + return env +} + +// resolveFilePath chooses the output directory and filename. +func (r *sessionRecorder) resolveFilePath() (string, string, error) { + if err := ensureSessionBaseDir(sessionDefaultDir); err != nil { + return "", "", err + } + + counter, err := nextSessionCounter(sessionDefaultDir) + if err != nil { + return "", "", err + } + + slug := r.slug + if slug == "" { + slug = defaultSessionSlug + } + + name := fmt.Sprintf("%02d_%s%s", counter, slug, sessionFileExt) + + return sessionDefaultDir, name, nil +} + +// ensureSessionBaseDir verifies that recording is running from the repo root. +func ensureSessionBaseDir(baseDir string) error { + info, err := os.Stat(baseDir) + if errors.Is(err, fs.ErrNotExist) { + return fmt.Errorf("%s does not exist; run session recording "+ + "from the repository root", baseDir) + } + if err != nil { + return err + } + if !info.IsDir() { + return fmt.Errorf("%s is not a directory", baseDir) + } + + return nil +} + +// logEvent records a new event with the elapsed timestamp. +func (r *sessionRecorder) logEvent(kind string, payload any) { + data, err := json.Marshal(payload) + if err != nil { + r.mu.Lock() + if r.eventErr == nil { + r.eventErr = fmt.Errorf("marshal session %s event: %w", + kind, err) + } + r.mu.Unlock() + + return + } + + event := sessionEvent{ + TimeMS: time.Since(r.started).Milliseconds(), + Kind: kind, + Data: data, + } + + r.mu.Lock() + defer r.mu.Unlock() + + r.events = append(r.events, event) +} + +// Start attaches stdin/stdout/stderr hooks for session recording. +func (r *sessionRecorder) Start(stdinSource io.Reader, + stdoutForward, stderrForward io.Writer) error { + + r.hooksMu.Lock() + defer r.hooksMu.Unlock() + + if r.hooksStarted { + return nil + } + + // Capture stdout and stderr first, then stdin. + origStdout := os.Stdout + if stdoutForward == nil { + stdoutForward = origStdout + } + outHook, err := hookStdout(origStdout, stdoutForward, func(p []byte) { + r.logEvent(eventStdout, newTextPayload(string(p))) + }) + if err != nil { + return err + } + + origStderr := os.Stderr + if stderrForward == nil { + stderrForward = origStderr + } + errHook, err := hookStderr(origStderr, stderrForward, func(p []byte) { + r.logEvent(eventStderr, newTextPayload(string(p))) + }) + if err != nil { + _ = outHook() + + return err + } + + origStdin := os.Stdin + if stdinSource == nil { + stdinSource = origStdin + } + stdinHook, err := hookStdin(origStdin, stdinSource, func(p []byte) { + r.logEvent(eventStdin, stdinPayload{Text: string(p)}) + }) + if err != nil { + _ = errHook() + _ = outHook() + + return err + } + + r.stdoutUnhook = outHook + r.stderrUnhook = errHook + r.stdinUnhook = stdinHook + r.hooksStarted = true + + return nil +} + +// stopHooks detaches any active IO hooks. +func (r *sessionRecorder) stopHooks() error { + r.hooksMu.Lock() + defer r.hooksMu.Unlock() + + var firstErr error + if r.stdoutUnhook != nil { + if err := r.stdoutUnhook(); err != nil && firstErr == nil { + firstErr = err + } + r.stdoutUnhook = nil + } + if r.stderrUnhook != nil { + if err := r.stderrUnhook(); err != nil && firstErr == nil { + firstErr = err + } + r.stderrUnhook = nil + } + if r.stdinUnhook != nil { + if err := r.stdinUnhook(); err != nil && firstErr == nil { + firstErr = err + } + r.stdinUnhook = nil + } + r.hooksStarted = false + + return firstErr +} + +// logExit records the final outcome and duration. +func (r *sessionRecorder) logExit(runErr error) { + var payload exitPayload + if runErr != nil { + msg := runErr.Error() + payload.RunError = &msg + } + + // Store the exit event first for the event stream. + r.logEvent(eventExit, payload) + + // Update metadata with the final run state. + duration := time.Since(r.started) + + r.mu.Lock() + defer r.mu.Unlock() + + if runErr != nil { + msg := runErr.Error() + r.metadata.RunError = &msg + } else { + r.metadata.RunError = nil + } + r.metadata.Duration = &duration +} + +// finalize writes the recorded session to disk once. +func (r *sessionRecorder) finalize(runErr error) error { + var finalizeErr error + r.finalizeOnce.Do(func() { + hookErr := r.stopHooks() + + r.logExit(runErr) + + r.mu.Lock() + metadata := r.metadata + events := append([]sessionEvent(nil), r.events...) + eventErr := r.eventErr + r.mu.Unlock() + + fileContent := sessionFile{ + Metadata: metadata, + Events: events, + } + + err := os.MkdirAll(filepath.Dir(r.filePath), 0o755) + if err != nil { + finalizeErr = errors.Join(err, eventErr, hookErr) + + return + } + + file, err := os.Create(r.filePath) + if err != nil { + finalizeErr = errors.Join(err, eventErr, hookErr) + + return + } + defer file.Close() + + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + if err := encoder.Encode(fileContent); err != nil { + finalizeErr = errors.Join(err, eventErr, hookErr) + + return + } + + finalizeErr = errors.Join(eventErr, hookErr) + }) + + return finalizeErr +} + +// Finalize records the exit event and flushes the session to disk. +func (r *sessionRecorder) Finalize(runErr error) error { + return r.finalize(runErr) +} + +// ClockStart returns the fixed session clock used while recording CLI actions. +func (r *sessionRecorder) ClockStart() time.Time { + return time.Unix(r.metadata.ClockStartUnix, 0) +} + +// Dial uses the direct gRPC connection for recording. +func (r *sessionRecorder) Dial(cmd *cli.Command) (daemonConn, func(), error) { + return dialDirectConn(cmd) +} + +// UnaryInterceptor captures unary RPCs for session playback. +func (r *sessionRecorder) UnaryInterceptor() grpc.UnaryClientInterceptor { + return func(ctx context.Context, method string, req, reply any, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, + opts ...grpc.CallOption) error { + + r.logGRPCMessage(method, "request", req, nil) + + err := invoker(ctx, method, req, reply, cc, opts...) + if err != nil { + r.logGRPCMessage(method, "error", nil, err) + + return err + } + + r.logGRPCMessage(method, "response", reply, nil) + + return nil + } +} + +// StreamInterceptor captures stream RPCs for session playback. +func (r *sessionRecorder) StreamInterceptor() grpc.StreamClientInterceptor { + return func(ctx context.Context, desc *grpc.StreamDesc, + cc *grpc.ClientConn, method string, streamer grpc.Streamer, + opts ...grpc.CallOption) (grpc.ClientStream, error) { + + clientStream, err := streamer(ctx, desc, cc, method, opts...) + if err != nil { + r.logGRPCMessage(method, "error", nil, err) + + return nil, err + } + + return &recordingClientStream{ + ClientStream: clientStream, + recorder: r, + method: method, + }, nil + } +} + +// recordingClientStream wraps a gRPC stream and logs message events. +type recordingClientStream struct { + grpc.ClientStream + + recorder *sessionRecorder + method string +} + +// SendMsg records the outgoing stream message. +func (s *recordingClientStream) SendMsg(m any) error { + s.recorder.logGRPCMessage(s.method, "send", m, nil) + err := s.ClientStream.SendMsg(m) + if err != nil { + s.recorder.logGRPCMessage(s.method, "error", nil, err) + } + + return err +} + +// RecvMsg records the incoming stream message. +func (s *recordingClientStream) RecvMsg(m any) error { + err := s.ClientStream.RecvMsg(m) + if err != nil { + s.recorder.logGRPCMessage(s.method, "error", nil, err) + + return err + } + + s.recorder.logGRPCMessage(s.method, "recv", m, nil) + + return nil +} + +// logGRPCMessage captures gRPC request/response data in the event stream. +func (r *sessionRecorder) logGRPCMessage(method, event string, msg any, + receptionErr error) { + + payload := grpcPayload{Method: method, Event: event} + + if receptionErr != nil { + payload.Error = receptionErr.Error() + r.logEvent(eventGrpc, payload) + + return + } + + if msg != nil { + if protoMsg, ok := msg.(proto.Message); ok { + payload.MessageType = string( + proto.MessageName(protoMsg), + ) + data, err := grpcMarshalOptions.Marshal(protoMsg) + if err == nil { + payload.Payload = data + } + } else { + data, err := json.Marshal(msg) + if err == nil { + payload.Payload = data + } + } + } + + r.logEvent(eventGrpc, payload) +} + +// LogSignal records an incoming signal event. +func (r *sessionRecorder) LogSignal(sig os.Signal) { + r.logEvent(eventSignal, signalPayload{Signal: sig.String()}) +} + +// InjectContext tags the outgoing context with the session name. +func (r *sessionRecorder) InjectContext(ctx context.Context) context.Context { + return metadata.AppendToOutgoingContext( + ctx, "loop-session", filepath.Base(r.filePath), + ) +} + +// deriveSessionSlug builds a stable slug from the command arguments. +func deriveSessionSlug(args []string) string { + if len(args) == 0 { + return "" + } + + base := filepath.Base(args[0]) + tokens := []string{base} + + for _, arg := range args[1:] { + if strings.HasPrefix(arg, "-") { + break + } + if arg == "" { + continue + } + tokens = append(tokens, arg) + } + + return sanitizeSlug(strings.Join(tokens, "-")) +} + +// sanitizeSlug normalizes a session slug to a safe filename. +func sanitizeSlug(value string) string { + value = strings.ToLower(value) + var builder strings.Builder + lastDash := false + for _, r := range value { + if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') { + builder.WriteRune(r) + lastDash = false + + continue + } + + if !lastDash { + builder.WriteRune('-') + lastDash = true + } + } + + slug := strings.Trim(builder.String(), "-") + if slug == "" { + return defaultSessionSlug + } + + return slug +} + +// nextSessionCounter finds the next available session number. +func nextSessionCounter(baseDir string) (int, error) { + maxCounter := 0 + entries, err := os.ReadDir(baseDir) + if errors.Is(err, fs.ErrNotExist) { + return 1, nil + } + if err != nil { + return 0, err + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + if filepath.Ext(entry.Name()) != sessionFileExt { + continue + } + + counter, ok := parseSessionCounter(entry.Name()) + if !ok { + continue + } + if counter > maxCounter { + maxCounter = counter + } + } + + return maxCounter + 1, nil +} + +// parseSessionCounter extracts the numeric prefix from a session filename. +func parseSessionCounter(name string) (int, bool) { + base := strings.TrimSuffix(name, filepath.Ext(name)) + if base == "" { + return 0, false + } + + parts := strings.SplitN(base, "_", 2) + if parts[0] == "" { + return 0, false + } + + for _, r := range parts[0] { + if r < '0' || r > '9' { + return 0, false + } + } + + value, err := strconv.Atoi(parts[0]) + if err != nil { + return 0, false + } + + if value < 0 { + return 0, false + } + + return value, true +} diff --git a/cmd/loop/session_recorder_test.go b/cmd/loop/session_recorder_test.go new file mode 100644 index 000000000..4de50e9bc --- /dev/null +++ b/cmd/loop/session_recorder_test.go @@ -0,0 +1,316 @@ +package main + +import ( + "encoding/json" + "errors" + "os" + "path/filepath" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +const ( + testLoopArg = "loop" + testOutArg = "out" + testLoopOutSlug = "loop-out" + testVersion = "test-version" + testClockStart = int64(1769407086) +) + +// TestDeriveSessionSlug verifies slug derivation from CLI arguments. +func TestDeriveSessionSlug(t *testing.T) { + tests := []struct { + name string + args []string + want string + }{ + { + name: "empty_args", + want: "", + }, + { + name: "binary_only", + args: []string{"/usr/local/bin/loop"}, + want: testLoopArg, + }, + { + name: "with_subcommand", + args: []string{testLoopArg, testOutArg}, + want: testLoopOutSlug, + }, + { + name: "stops_at_flag", + args: []string{testLoopArg, testOutArg, "--network", "regtest"}, + want: testLoopOutSlug, + }, + { + name: "skips_empty_args", + args: []string{testLoopArg, "", "quote", testOutArg}, + want: "loop-quote-out", + }, + { + name: "sanitizes_tokens", + args: []string{testLoopArg, "Quote", "Out"}, + want: "loop-quote-out", + }, + { + name: "sanitizes_path_base", + args: []string{"/tmp/loop-cli", testOutArg}, + want: "loop-cli-out", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + slug := deriveSessionSlug(test.args) + require.Equal(t, test.want, slug) + }) + } +} + +// TestSanitizeSlug verifies slug normalization behavior. +func TestSanitizeSlug(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + { + name: "already_clean", + input: testLoopOutSlug, + want: testLoopOutSlug, + }, + { + name: "upper_and_spaces", + input: "Loop Out", + want: testLoopOutSlug, + }, + { + name: "symbols_collapsed", + input: "loop@@@out", + want: testLoopOutSlug, + }, + { + name: "trims_dashes", + input: "--loop-out--", + want: testLoopOutSlug, + }, + { + name: "empty_to_default", + input: "!!!", + want: defaultSessionSlug, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + slug := sanitizeSlug(test.input) + require.Equal(t, test.want, slug) + }) + } +} + +// TestParseSessionCounter verifies session counter parsing. +func TestParseSessionCounter(t *testing.T) { + tests := []struct { + name string + input string + want int + ok bool + }{ + { + name: "with_suffix", + input: "01_loop-out.json", + want: 1, + ok: true, + }, + { + name: "with_extra_underscores", + input: "12_loop_in.json", + want: 12, + ok: true, + }, + { + name: "no_suffix", + input: "99", + want: 99, + ok: true, + }, + { + name: "no_extension", + input: "7_loop-out", + want: 7, + ok: true, + }, + { + name: "empty_name", + input: "", + ok: false, + }, + { + name: "missing_prefix", + input: "_loop.json", + ok: false, + }, + { + name: "non_numeric_prefix", + input: "loop.json", + ok: false, + }, + { + name: "mixed_prefix", + input: "1a_loop.json", + ok: false, + }, + { + name: "negative_prefix", + input: "-1_loop.json", + ok: false, + }, + { + name: "plus_prefix", + input: "+1_loop.json", + ok: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + value, ok := parseSessionCounter(test.input) + require.Equal(t, test.ok, ok) + if test.ok { + require.Equal(t, test.want, value) + } + }) + } +} + +// TestSessionFinalizeWritesFileWhenHookRestoreFails verifies that session +// data is still written to disk when hook restoration returns an error. +func TestSessionFinalizeWritesFileWhenHookRestoreFails(t *testing.T) { + recorder := &sessionRecorder{ + started: time.Now().Add(-time.Second), + filePath: filepath.Join(t.TempDir(), "session.json"), + metadata: sessionMetadata{ + Args: []string{testLoopArg, testOutArg}, + Env: map[string]string{}, + Version: testVersion, + ClockStartUnix: testClockStart, + }, + hooksStarted: true, + stdoutUnhook: func() error { + return errors.New("stdout restore failed") + }, + } + + err := recorder.Finalize(nil) + require.EqualError(t, err, "stdout restore failed") + + blob, err := os.ReadFile(recorder.filePath) + require.NoError(t, err) + + var data sessionFile + require.NoError(t, json.Unmarshal(blob, &data)) + require.Equal(t, recorder.metadata.Args, data.Metadata.Args) + require.Equal(t, recorder.metadata.Version, data.Metadata.Version) + require.NotNil(t, data.Metadata.Duration) + require.Nil(t, data.Metadata.RunError) + require.Len(t, data.Events, 1) + require.Equal(t, eventExit, data.Events[0].Kind) +} + +// TestSessionFinalizeReportsWriteAndHookErrors verifies that a write failure +// and hook restoration failure are both returned. +func TestSessionFinalizeReportsWriteAndHookErrors(t *testing.T) { + recorder := &sessionRecorder{ + started: time.Now().Add(-time.Second), + filePath: t.TempDir(), + metadata: sessionMetadata{ + Args: []string{testLoopArg, testOutArg}, + Env: map[string]string{}, + Version: testVersion, + ClockStartUnix: testClockStart, + }, + hooksStarted: true, + stdoutUnhook: func() error { + return errors.New("stdout restore failed") + }, + } + + err := recorder.Finalize(nil) + require.ErrorContains(t, err, "is a directory") + require.ErrorContains(t, err, "stdout restore failed") +} + +// TestSessionFinalizeReportsDeferredMarshalError verifies that marshal +// failures encountered while recording are reported from Finalize while the +// session file is still written to disk. +func TestSessionFinalizeReportsDeferredMarshalError(t *testing.T) { + recorder := &sessionRecorder{ + started: time.Now().Add(-time.Second), + filePath: filepath.Join(t.TempDir(), "session.json"), + metadata: sessionMetadata{ + Args: []string{testLoopArg, testOutArg}, + Env: map[string]string{}, + Version: testVersion, + ClockStartUnix: testClockStart, + }, + } + + // This logEvent will fail, because chan int is not JSON marshallable. + recorder.logEvent(eventStdout, make(chan int)) + + err := recorder.Finalize(nil) + require.ErrorContains(t, err, "marshal session stdout event") + require.ErrorContains(t, err, "unsupported type: chan int") + + blob, err := os.ReadFile(recorder.filePath) + require.NoError(t, err) + + var data sessionFile + require.NoError(t, json.Unmarshal(blob, &data)) + require.Equal(t, recorder.metadata.Args, data.Metadata.Args) + require.Equal(t, recorder.metadata.Version, data.Metadata.Version) + require.NotNil(t, data.Metadata.Duration) + require.Nil(t, data.Metadata.RunError) + require.Len(t, data.Events, 1) + require.Equal(t, eventExit, data.Events[0].Kind) +} + +// TestNewSessionRecorderRequiresRepoRoot verifies that recording fails clearly +// when the session fixture directory is not available from the current working +// directory. +func TestNewSessionRecorderRequiresRepoRoot(t *testing.T) { + t.Chdir(t.TempDir()) + t.Setenv(sessionEnvVar, "true") + + recorder, err := newSessionRecorder([]string{testLoopArg, testOutArg}) + require.Nil(t, recorder) + require.EqualError(t, err, "cmd/loop/testdata/sessions does not "+ + "exist; run session recording from the repository root") +} + +// TestNewSessionRecorderCapturesClockStart verifies that new recordings store +// the clock start used for deterministic replay. +func TestNewSessionRecorderCapturesClockStart(t *testing.T) { + repoRoot := t.TempDir() + require.NoError( + t, os.MkdirAll(filepath.Join(repoRoot, sessionDefaultDir), 0o755), + ) + t.Chdir(repoRoot) + t.Setenv(sessionEnvVar, "true") + + before := time.Now().Unix() + recorder, err := newSessionRecorder([]string{testLoopArg, testOutArg}) + after := time.Now().Unix() + require.NoError(t, err) + require.NotNil(t, recorder) + require.GreaterOrEqual(t, recorder.metadata.ClockStartUnix, before) + require.LessOrEqual(t, recorder.metadata.ClockStartUnix, after) + require.Equal( + t, time.Unix(recorder.metadata.ClockStartUnix, 0), + recorder.ClockStart(), + ) +} From f607d6730e4e0427d7365e3479074d1b7feb047c Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 3 Feb 2026 00:54:32 -0500 Subject: [PATCH 09/18] cmd/loop: add session replay tests --- cmd/loop/session_replay_test.go | 1205 +++++++++++++++++++++++++++++++ go.mod | 1 + 2 files changed, 1206 insertions(+) create mode 100644 cmd/loop/session_replay_test.go diff --git a/cmd/loop/session_replay_test.go b/cmd/loop/session_replay_test.go new file mode 100644 index 000000000..4cb453ed3 --- /dev/null +++ b/cmd/loop/session_replay_test.go @@ -0,0 +1,1205 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "io/fs" + "os" + "path" + "reflect" + "regexp" + "runtime" + "slices" + "strings" + "sync" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/lightningnetwork/lnd/clock" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v3" + "google.golang.org/grpc" + "google.golang.org/grpc/connectivity" + "google.golang.org/grpc/metadata" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +// sessionsFS exposes the recorded session fixtures for replay tests. +var sessionsFS = func() fs.FS { + // Locate this file to anchor the testdata path. + _, filename, _, ok := runtime.Caller(0) + if !ok { + return emptyFS{} + } + + // Derive the cmd/loop directory containing this file. + loopDir := path.Dir(filename) + + // Build a filesystem rooted at the cmd/loop directory. + cmdLoopFS := os.DirFS(loopDir) + + // Restrict the filesystem to the session fixture directory. + sub, err := fs.Sub(cmdLoopFS, "testdata/sessions") + if err != nil { + return emptyFS{} + } + + return sub +}() + +// emptyFS is a filesystem that always reports no such file. +type emptyFS struct{} + +// Open implements fs.FS by always returning a missing file error. +func (emptyFS) Open(string) (fs.File, error) { + return nil, fs.ErrNotExist +} + +// recordedSession holds the captured data required for a replay. +type recordedSession struct { + args []string + env map[string]string + stdin string + stdout string + stderr string + runError *string + clockStartUnix int64 + conn *recordedClientConn +} + +// loadRecordedSessionFS loads a session from an fs.FS. +func loadRecordedSessionFS(fsys fs.FS, path string) (*recordedSession, error) { + // Read the JSON file from the provided filesystem. + blob, err := fs.ReadFile(fsys, path) + if err != nil { + return nil, err + } + + // Parse the recorded JSON into a replay struct. + return parseRecordedSession(blob) +} + +// parseRecordedSession decodes a session JSON blob into replay data. +func parseRecordedSession(blob []byte) (*recordedSession, error) { + // Decode the JSON file into the sessionFile struct. + var data sessionFile + if err := json.Unmarshal(blob, &data); err != nil { + return nil, err + } + + clockStartUnix := data.Metadata.ClockStartUnix + if clockStartUnix <= 0 { + return nil, errors.New("missing or invalid " + + "metadata.clock_start_unix") + } + + // Build a gRPC replay connection from recorded events. + conn, err := newRecordedClientConn(data.Events) + if err != nil { + return nil, err + } + + // Build buffers for replay IO streams. + var ( + stdoutBuilder strings.Builder + stderrBuilder strings.Builder + stdinBuilder strings.Builder + ) + + // Collect stdin/stdout/stderr payloads into buffers. + for _, event := range data.Events { + switch event.Kind { + case eventStdout: + var payload textPayload + err := json.Unmarshal(event.Data, &payload) + if err != nil { + return nil, err + } + stdoutBuilder.WriteString(payload.text()) + + case eventStderr: + var payload textPayload + err := json.Unmarshal(event.Data, &payload) + if err != nil { + return nil, err + } + stderrBuilder.WriteString(payload.text()) + + case eventStdin: + var payload stdinPayload + err := json.Unmarshal(event.Data, &payload) + if err != nil { + return nil, err + } + stdinBuilder.WriteString(payload.Text) + } + } + + // Initialize the replay payload with metadata. + return &recordedSession{ + args: append([]string(nil), data.Metadata.Args...), + env: data.Metadata.Env, + runError: data.Metadata.RunError, + clockStartUnix: clockStartUnix, + conn: conn, + stdin: stdinBuilder.String(), + stdout: stdoutBuilder.String(), + stderr: stderrBuilder.String(), + }, nil +} + +// TestParseRecordedSessionRequiresClockStart verifies that replay fixtures +// carry the per-session clock anchor needed for deterministic time handling. +func TestParseRecordedSessionRequiresClockStart(t *testing.T) { + t.Parallel() + + valid := sessionFile{ + Metadata: sessionMetadata{ + Args: []string{"loop", "quote", "out"}, + Env: map[string]string{}, + Version: "test-version", + ClockStartUnix: 1769407086, + }, + } + + blob, err := json.Marshal(valid) + require.NoError(t, err) + + session, err := parseRecordedSession(blob) + require.NoError(t, err) + require.Equal(t, valid.Metadata.ClockStartUnix, session.clockStartUnix) + + tests := []struct { + name string + blob []byte + }{ + { + name: "missing_clock_start_unix", + blob: []byte(`{ + "metadata": { + "args": ["loop", "quote", "out"], + "env": {}, + "version": "test-version" + }, + "events": [] +}`), + }, + { + name: "zero_clock_start_unix", + blob: []byte(`{ + "metadata": { + "args": ["loop", "quote", "out"], + "env": {}, + "version": "test-version", + "clock_start_unix": 0 + }, + "events": [] +}`), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + _, err := parseRecordedSession(test.blob) + require.ErrorContains( + t, err, "metadata.clock_start_unix", + ) + }) + } +} + +// applyEnv sets environment variables and returns a restore function. +func applyEnv(values map[string]string) func() { + // No environment changes are needed. + if len(values) == 0 { + return func() {} + } + + // Track previous values for restoration. + type previous struct { + value string + set bool + } + + // Capture existing environment and apply recorded values. + prev := make(map[string]previous, len(values)) + for k, v := range values { + curr, set := os.LookupEnv(k) + prev[k] = previous{value: curr, set: set} + _ = os.Setenv(k, v) + } + + // Restore the original environment when done. + return func() { + for k, p := range prev { + if p.set { + _ = os.Setenv(k, p.value) + } else { + _ = os.Unsetenv(k) + } + } + } +} + +// protoMarshal encodes protobufs in a stable JSON form for comparisons. +var protoMarshal = protojson.MarshalOptions{ + UseProtoNames: true, + EmitUnpopulated: true, +} + +// protoUnmarshal decodes protobuf JSON while ignoring unknown fields. +var protoUnmarshal = protojson.UnmarshalOptions{ + DiscardUnknown: true, +} + +// replayTransport provides a recorded gRPC connection for session replays. +type replayTransport struct { + conn *recordedClientConn +} + +// Dial returns the recorded gRPC connection for replay. +func (t *replayTransport) Dial(cmd *cli.Command) (daemonConn, func(), error) { + // Ignore the command; replays always use the recorded connection. + return t.conn, func() {}, nil +} + +// UnaryInterceptor returns nil because replay uses a recorded connection. +func (t *replayTransport) UnaryInterceptor() grpc.UnaryClientInterceptor { + // No wrapping is needed for the recorded connection. + return nil +} + +// StreamInterceptor returns nil because replay uses a recorded connection. +func (t *replayTransport) StreamInterceptor() grpc.StreamClientInterceptor { + // No wrapping is needed for the recorded connection. + return nil +} + +// recordedClientConn replays gRPC events captured during a session run. +type recordedClientConn struct { + events []grpcPayload + idx int + mu sync.Mutex +} + +// GetState reports the connection as shut down for replay. +func (c *recordedClientConn) GetState() connectivity.State { + return connectivity.Shutdown +} + +// WaitForStateChange returns immediately because replay is static. +func (c *recordedClientConn) WaitForStateChange(ctx context.Context, + state connectivity.State) bool { + + return true +} + +// Connect is a no-op for the replay connection. +func (c *recordedClientConn) Connect() {} + +// newRecordedClientConn builds a replay connection from session events. +func newRecordedClientConn(events []sessionEvent) (*recordedClientConn, error) { + var payloads []grpcPayload + for _, event := range events { + if event.Kind != eventGrpc { + continue + } + + var payload grpcPayload + if err := json.Unmarshal(event.Data, &payload); err != nil { + return nil, err + } + payloads = append(payloads, payload) + } + + return &recordedClientConn{events: payloads}, nil +} + +// Invoke replays unary gRPC calls by consuming recorded events. +func (c *recordedClientConn) Invoke(ctx context.Context, method string, + args any, reply any, opts ...grpc.CallOption) error { + + // Guard concurrent access to the event stream. + c.mu.Lock() + defer c.mu.Unlock() + + // Validate the recorded request against the actual request. + req, reqIdx, err := c.consumeLocked(method, "request") + if err != nil { + return err + } + + err = compareMessageWithContext( + method, req.Event, reqIdx, args, req.Payload, + ) + if err != nil { + return err + } + + // Fetch the recorded response or error. + resp, respIdx, err := c.consumeLocked(method, "response", "error") + if err != nil { + return err + } + + // Translate recorded errors into returned errors. + if resp.Event == "error" { + if resp.Error == io.EOF.Error() { + return io.EOF + } + + return errors.New(resp.Error) + } + + // Decode protobuf responses when the reply is a proto message. + if replyMsg, ok := reply.(proto.Message); ok { + if resp.MessageType != "" { + got := string(proto.MessageName(replyMsg)) + if got != resp.MessageType { + return fmt.Errorf("grpc %s response[%d] type "+ + "mismatch: got %s want %s", method, + respIdx, got, resp.MessageType) + } + } + + if len(resp.Payload) > 0 { + err := protoUnmarshal.Unmarshal(resp.Payload, replyMsg) + if err != nil { + return fmt.Errorf("grpc %s response[%d] "+ + "unmarshal: %w", method, respIdx, err) + } + } + + return nil + } + + // Decode JSON responses when the reply is not a proto message. + if len(resp.Payload) > 0 { + if err := json.Unmarshal(resp.Payload, reply); err != nil { + return fmt.Errorf("grpc %s response[%d] unmarshal: "+ + "%w", method, respIdx, err) + } + } + + return nil +} + +// NewStream creates a replay stream backed by recorded events. +func (c *recordedClientConn) NewStream(ctx context.Context, + desc *grpc.StreamDesc, method string, + opts ...grpc.CallOption) (grpc.ClientStream, error) { + + // Create a stream wrapper that consumes events as needed. + return &replayStream{ + conn: c, + method: method, + ctx: ctx, + }, nil +} + +// replayStream is a stream implementation backed by recorded events. +type replayStream struct { + conn *recordedClientConn + method string + ctx context.Context +} + +// Header returns empty metadata for replay. +func (s *replayStream) Header() (metadata.MD, error) { + return nil, nil +} + +// Trailer returns empty metadata for replay. +func (s *replayStream) Trailer() metadata.MD { + return nil +} + +// CloseSend is a no-op for replay streams. +func (s *replayStream) CloseSend() error { + return nil +} + +// Context returns the stream context. +func (s *replayStream) Context() context.Context { + return s.ctx +} + +// SendMsg validates an outgoing stream message against recorded events. +func (s *replayStream) SendMsg(m any) error { + // Protect the event stream with the shared mutex. + s.conn.mu.Lock() + defer s.conn.mu.Unlock() + + // Consume and compare the next recorded send event. + evt, evtIdx, err := s.conn.consumeLocked(s.method, "send") + if err != nil { + return err + } + + return compareMessageWithContext( + s.method, evt.Event, evtIdx, m, evt.Payload, + ) +} + +// RecvMsg replays the next recorded stream response. +func (s *replayStream) RecvMsg(m any) error { + // Protect the event stream with the shared mutex. + s.conn.mu.Lock() + defer s.conn.mu.Unlock() + + // Consume the next recorded receive event. + evt, evtIdx, err := s.conn.consumeLocked(s.method, "recv", "error") + if err != nil { + return err + } + + // Translate a recorded error into a return value. + if evt.Event == "error" { + if evt.Error == io.EOF.Error() { + return io.EOF + } + + return errors.New(evt.Error) + } + + // Decode protobuf payloads into the provided message. + if msg, ok := m.(proto.Message); ok { + if evt.MessageType != "" { + got := string(proto.MessageName(msg)) + if got != evt.MessageType { + return fmt.Errorf("grpc %s recv[%d] type "+ + "mismatch: got %s want %s", s.method, + evtIdx, got, evt.MessageType) + } + } + + err := protoUnmarshal.Unmarshal(evt.Payload, msg) + if err != nil { + return fmt.Errorf("grpc %s recv[%d] unmarshal: %w", + s.method, evtIdx, err) + } + + return nil + } + + // Decode JSON payloads into non-protobuf messages. + if len(evt.Payload) > 0 { + if err := json.Unmarshal(evt.Payload, m); err != nil { + return fmt.Errorf("grpc %s recv[%d] unmarshal: %w", + s.method, evtIdx, err) + } + } + + return nil +} + +// consumeLocked fetches the next matching event. +// The caller must hold c.mu. +func (c *recordedClientConn) consumeLocked(method, expected string, + alternatives ...string) (*grpcPayload, int, error) { + + // Ensure there is another event to consume. + if c.idx >= len(c.events) { + return nil, c.idx, fmt.Errorf("grpc %s event[%d] missing, "+ + "expected %s", method, c.idx, expected) + } + + // Fetch the next event and advance the cursor. + idx := c.idx + evt := c.events[c.idx] + c.idx++ + + // Verify the method matches. + if evt.Method != method { + return nil, idx, fmt.Errorf("grpc event[%d] unexpected "+ + "method %s, want %s", idx, evt.Method, method) + } + + // Accept the expected event or a documented alternative. + if evt.Event == expected || slices.Contains(alternatives, evt.Event) { + return &evt, idx, nil + } + + return nil, idx, fmt.Errorf("grpc %s event[%d] unexpected event %s, "+ + "expected %s", method, idx, evt.Event, expected) +} + +// assertFullyConsumed verifies that replay consumed the entire recorded gRPC +// interaction. +func (c *recordedClientConn) assertFullyConsumed() error { + c.mu.Lock() + defer c.mu.Unlock() + + if c.idx == len(c.events) { + return nil + } + + evt := c.events[c.idx] + + return fmt.Errorf("grpc event[%d] for %s was not consumed", + c.idx, evt.Method) +} + +// compareMessageWithContext marshals and compares a message against recorded +// JSON. +func compareMessageWithContext(method, event string, idx int, msg any, + raw json.RawMessage) error { + + // Nothing to compare if there is no recorded payload. + if len(raw) == 0 { + return nil + } + + // Marshal the actual message to JSON and compare. + switch typed := msg.(type) { + case proto.Message: + actual, err := protoMarshal.Marshal(typed) + if err != nil { + return err + } + + return compareJSONWithContext(method, event, idx, actual, raw) + + default: + actual, err := json.Marshal(typed) + if err != nil { + return err + } + + return compareJSONWithContext(method, event, idx, actual, raw) + } +} + +// compareJSONWithContext compares two JSON-encoded payloads with context. +func compareJSONWithContext(method, event string, idx int, actual []byte, + recorded json.RawMessage) error { + + // Decode the actual and recorded payloads to generic maps. + var ( + actualValue any + recordedValue any + ) + + if err := json.Unmarshal(actual, &actualValue); err != nil { + return fmt.Errorf("grpc %s %s[%d] unmarshal actual: %w", + method, event, idx, err) + } + if err := json.Unmarshal(recorded, &recordedValue); err != nil { + return fmt.Errorf("grpc %s %s[%d] unmarshal recorded: %w", + method, event, idx, err) + } + + // Compare the decoded payloads and report any diff. + if diff := cmp.Diff(recordedValue, actualValue); diff != "" { + return fmt.Errorf("grpc %s %s[%d] mismatch (-want +got):\n%s", + method, event, idx, diff) + } + + return nil +} + +// TestRecordedSessions replays all recorded sessions and compares output. +// +// NOTE: Do not add t.Parallel() here; the replay harness mutates package-level +// globals such as the active transport, clock, and JSON normalization mode. +func TestRecordedSessions(t *testing.T) { + // Skip the test entirely when there is no session directory. + if _, err := fs.ReadDir(sessionsFS, "."); err != nil { + if errors.Is(err, fs.ErrNotExist) { + t.Skip("no recorded sessions present") + } + require.NoError(t, err) + } + + // Collect all session JSON files. + var sessionFiles []string + walkErr := fs.WalkDir(sessionsFS, ".", + func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.IsDir() { + return nil + } + if strings.HasSuffix(d.Name(), sessionFileExt) { + sessionFiles = append(sessionFiles, path) + } + + return nil + }) + require.NoError(t, walkErr) + if len(sessionFiles) == 0 { + t.Skip("no recorded sessions present") + } + + for _, path := range sessionFiles { + t.Run(path, func(t *testing.T) { + // Force deterministic JSON output for replay. + prevDeterministic := forceDeterministicJSON + forceDeterministicJSON = true + defer func() { + forceDeterministicJSON = prevDeterministic + }() + + // Load the recorded session fixture. + replay, err := loadRecordedSessionFS(sessionsFS, path) + require.NoErrorf(t, err, "load session %s", path) + + // Capture replay output for comparison. + var ( + stdoutBuf bytes.Buffer + stderrBuf bytes.Buffer + ) + + // Hook stdout for capture. + stdoutUnhook, err := hookStdout( + os.Stdout, nil, func(p []byte) { + stdoutBuf.Write(p) + }, + ) + require.NoErrorf(t, err, "hook stdout for %s", path) + + // Hook stderr for capture. + stderrUnhook, err := hookStderr( + os.Stderr, nil, func(p []byte) { + stderrBuf.Write(p) + }, + ) + require.NoErrorf(t, err, "hook stderr for %s", path) + + // Hook stdin to feed the recorded input. + stdinUnhook, err := hookStdin( + os.Stdin, bytes.NewBufferString(replay.stdin), + nil, + ) + require.NoErrorf(t, err, "hook stdin for %s", path) + + // Install the replay gRPC transport. + restoreTransport := hookGrpc( + &replayTransport{conn: replay.conn}, + ) + defer restoreTransport() + + // Apply the recorded environment variables. + restoreEnv := applyEnv(replay.env) + defer restoreEnv() + + // Use a fixed clock during replay. + restoreClock := hookClock( + clock.NewTestClock( + time.Unix(replay.clockStartUnix, 0), + ), + ) + defer restoreClock() + + // Ensure the session recorder is disabled during + // replay runs. + sessionRec = nil + + // Run the CLI command with a fresh root. + cmd := newRootCommandForReplay() + + err = cmd.Run(t.Context(), replay.args) + if err != nil { + fmt.Fprintf(os.Stderr, "[loop] %v\n", err) + } + + // Restore IO hooks before checking output. + require.NoErrorf( + t, stdoutUnhook(), "unhook stdout for %s", path, + ) + require.NoErrorf( + t, stderrUnhook(), "unhook stderr for %s", path, + ) + require.NoErrorf( + t, stdinUnhook(), "unhook stdin for %s", path, + ) + + if replay.runError != nil { + require.Error(t, err, "expected run error") + + require.Equalf( + t, *replay.runError, err.Error(), + "run error mismatch for %s", path, + ) + } else { + require.NoErrorf( + t, err, "command failed for %s", path, + ) + } + + require.NoErrorf( + t, replay.conn.assertFullyConsumed(), + "grpc replay incomplete for %s", path, + ) + + // Compare captured output to the recorded streams. + requireTextEqual( + t, "stdout", replay.stdout, stdoutBuf.String(), + ) + requireTextEqual( + t, "stderr", replay.stderr, stderrBuf.String(), + ) + }) + } +} + +// newRootCommandForReplay returns a root command clone with fresh flag state. +func newRootCommandForReplay() *cli.Command { + // Clone the root command tree to avoid shared flag state. + return cloneCommandForReplay(newRootCommand()) +} + +// cloneCommandForReplay deep-clones a command tree for deterministic replays +// because the CLI uses package-level command pointers with mutable flag state. +// Without cloning, flag state from one replay would bleed into subsequent +// replays. +// +// NOTE: This relies on urfave/cli storing replay-relevant configuration in +// exported fields. If upgrading urfave/cli, verify that +// TestCloneCommandForReplayResetsFlagState still passes. +func cloneCommandForReplay(cmd *cli.Command) *cli.Command { + // Guard against nil command trees. + if cmd == nil { + return nil + } + + // Clone the command struct and nested configuration. + cloned := cloneCommandStruct(cmd) + cloned.Flags, cloned.MutuallyExclusiveFlags = cloneFlagsWithGroups( + cmd.Flags, cmd.MutuallyExclusiveFlags, + ) + cloned.Arguments = cloneArguments(cmd.Arguments) + cloned.Commands = cloneCommands(cmd.Commands) + + return cloned +} + +// cloneCommandStruct copies exported fields of a command into a new instance. +func cloneCommandStruct(cmd *cli.Command) *cli.Command { + // Guard against nil command trees. + if cmd == nil { + return nil + } + + // Copy exported fields only to avoid deep internals. + src := reflect.ValueOf(cmd).Elem() + dst := reflect.New(src.Type()).Elem() + copyExportedFields(dst, src) + + return dst.Addr().Interface().(*cli.Command) +} + +// cloneCommands clones a list of subcommands for replay. +func cloneCommands(cmds []*cli.Command) []*cli.Command { + // Return nil to preserve the original structure. + if len(cmds) == 0 { + return nil + } + + // Clone each subcommand recursively. + cloned := make([]*cli.Command, len(cmds)) + for i, cmd := range cmds { + cloned[i] = cloneCommandForReplay(cmd) + } + + return cloned +} + +// cloneFlagsWithGroups clones flags and rebinds mutually exclusive groups. +func cloneFlagsWithGroups(flags []cli.Flag, + groups []cli.MutuallyExclusiveFlags) ([]cli.Flag, + []cli.MutuallyExclusiveFlags) { + + // Clone flags and then remap mutually exclusive groups. + clonedFlags, clonedMap := cloneFlags(flags) + clonedGroups := cloneMutuallyExclusiveFlags(groups, clonedMap) + + return clonedFlags, clonedGroups +} + +// cloneFlags creates fresh flag instances and returns a map of originals to +// clones. +func cloneFlags(flags []cli.Flag) ([]cli.Flag, map[cli.Flag]cli.Flag) { + // Return an empty map when no flags are present. + if len(flags) == 0 { + return nil, map[cli.Flag]cli.Flag{} + } + + // Clone each flag and record the mapping. + cloned := make([]cli.Flag, len(flags)) + clonedMap := make(map[cli.Flag]cli.Flag, len(flags)) + for i, flag := range flags { + if flag == nil { + continue + } + flagCopy := cloneFlag(flag) + cloned[i] = flagCopy + clonedMap[flag] = flagCopy + } + + return cloned, clonedMap +} + +// cloneMutuallyExclusiveFlags clones flag groups using the provided flag map. +func cloneMutuallyExclusiveFlags(groups []cli.MutuallyExclusiveFlags, + clonedMap map[cli.Flag]cli.Flag) []cli.MutuallyExclusiveFlags { + + // Return nil to preserve the original structure. + if len(groups) == 0 { + return nil + } + + // Clone each group and its flags. + clonedGroups := make([]cli.MutuallyExclusiveFlags, len(groups)) + for i, group := range groups { + clonedGroup := cli.MutuallyExclusiveFlags{ + Required: group.Required, + Category: group.Category, + } + + if len(group.Flags) == 0 { + clonedGroups[i] = clonedGroup + continue + } + + clonedGroup.Flags = make([][]cli.Flag, len(group.Flags)) + for j, option := range group.Flags { + if len(option) == 0 { + continue + } + + clonedOption := make([]cli.Flag, len(option)) + for k, flag := range option { + if flag == nil { + continue + } + + clone, ok := clonedMap[flag] + if !ok { + clone = cloneFlag(flag) + clonedMap[flag] = clone + } + clonedOption[k] = clone + } + clonedGroup.Flags[j] = clonedOption + } + + clonedGroups[i] = clonedGroup + } + + return clonedGroups +} + +// cloneFlag clones a single flag by copying its exported fields. +func cloneFlag(flag cli.Flag) cli.Flag { + // Preserve nil flags as-is. + if flag == nil { + return nil + } + + // Clone the concrete flag struct. + cloned, ok := cloneStructWithExportedFields(flag) + if !ok { + return flag + } + clonedFlag, ok := cloned.(cli.Flag) + if !ok { + return flag + } + + return clonedFlag +} + +// cloneArguments clones positional argument definitions. +func cloneArguments(args []cli.Argument) []cli.Argument { + // Return nil to preserve the original structure. + if len(args) == 0 { + return nil + } + + // Clone each argument. + cloned := make([]cli.Argument, len(args)) + for i, arg := range args { + cloned[i] = cloneArgument(arg) + } + + return cloned +} + +// cloneArgument clones a single argument by copying its exported fields. +func cloneArgument(arg cli.Argument) cli.Argument { + // Preserve nil arguments as-is. + if arg == nil { + return nil + } + + // Clone the concrete argument struct. + cloned, ok := cloneStructWithExportedFields(arg) + if !ok { + return arg + } + clonedArg, ok := cloned.(cli.Argument) + if !ok { + return arg + } + + return clonedArg +} + +// cloneStructWithExportedFields clones a pointer-to-struct by exported fields. +func cloneStructWithExportedFields(src any) (any, bool) { + // Validate the input type. + if src == nil { + return nil, false + } + + value := reflect.ValueOf(src) + if value.Kind() != reflect.Pointer || + value.Elem().Kind() != reflect.Struct { + + return nil, false + } + + // Allocate a new struct value and copy exported fields. + cloned := reflect.New(value.Elem().Type()) + copyExportedFields(cloned.Elem(), value.Elem()) + + return cloned.Interface(), true +} + +// copyExportedFields copies exported fields from src into dst. +func copyExportedFields(dst, src reflect.Value) { + // Iterate fields and copy only exported ones. + for i := 0; i < src.NumField(); i++ { + field := src.Type().Field(i) + if field.PkgPath != "" { + continue + } + + dstField := dst.Field(i) + if !dstField.CanSet() { + continue + } + + dstField.Set(cloneValue(src.Field(i))) + } +} + +// cloneValue shallow-clones slices and maps while preserving other values. +func cloneValue(value reflect.Value) reflect.Value { + // Return invalid values as-is. + if !value.IsValid() { + return value + } + + // Clone supported types while preserving value semantics. + switch value.Kind() { + case reflect.Slice: + if value.IsNil() { + return value + } + cloned := reflect.MakeSlice( + value.Type(), value.Len(), value.Len(), + ) + reflect.Copy(cloned, value) + + return cloned + + case reflect.Map: + if value.IsNil() { + return value + } + cloned := reflect.MakeMapWithSize(value.Type(), value.Len()) + for _, key := range value.MapKeys() { + cloned.SetMapIndex(key, value.MapIndex(key)) + } + + return cloned + + default: + return value + } +} + +// requireTextEqual compares text after normalizing timestamps. +func requireTextEqual(t *testing.T, label, expected, actual string) { + t.Helper() + + // Normalize timezone-dependent timestamps before comparing. + expected = normalizeTimestamps(expected) + actual = normalizeTimestamps(actual) + if diff := cmp.Diff(expected, actual); diff != "" { + t.Fatalf("%s mismatch (-want +got):\n%s", label, diff) + } +} + +// rfc3339TimestampRegex matches RFC3339 timestamps embedded in CLI output. +var rfc3339TimestampRegex = regexp.MustCompile( + `\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})`, +) + +// timeStringTimestampRegex matches time.String-style timestamps embedded in +// CLI output. +var timeStringTimestampRegex = regexp.MustCompile( + `\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} [+-]\d{4} [A-Z]{2,5}`, +) + +// normalizeTimestamps rewrites embedded timestamps to UTC to avoid +// environment-dependent timezone output during session replay. +func normalizeTimestamps(text string) string { + // Normalize RFC3339 timestamps first. + rfc3339Replacer := func(ts string) string { + parsed, err := time.Parse(time.RFC3339Nano, ts) + if err != nil { + return ts + } + + return parsed.UTC().Format(time.RFC3339Nano) + } + text = rfc3339TimestampRegex.ReplaceAllStringFunc( + text, rfc3339Replacer, + ) + + // Normalize time.String timestamps next. + timeReplacer := func(ts string) string { + parsed, err := time.Parse("2006-01-02 15:04:05 -0700 MST", ts) + if err != nil { + return ts + } + + return parsed.UTC().Format("2006-01-02 15:04:05 -0700 MST") + } + + text = timeStringTimestampRegex.ReplaceAllStringFunc( + text, timeReplacer, + ) + + return text +} + +// TestCloneCommandForReplayResetsFlagState verifies cloned commands reset flag +// state. +func TestCloneCommandForReplayResetsFlagState(t *testing.T) { + // Prepare a flag that has been set. + originalFlag := &cli.StringFlag{ + Name: "alpha", + Usage: "alpha usage", + Aliases: []string{"a"}, + } + require.NoError(t, originalFlag.Set("alpha", "value")) + require.True(t, originalFlag.IsSet()) + + // Prepare a shared flag that appears in multiple command locations. + sharedFlag := &cli.BoolFlag{Name: "shared"} + require.NoError(t, sharedFlag.Set("shared", "true")) + require.True(t, sharedFlag.IsSet()) + + // Prepare a parsed argument. + originalArg := &cli.StringArg{ + Name: "arg", + UsageText: "arg usage", + Value: "default", + } + _, err := originalArg.Parse([]string{"parsed"}) + require.NoError(t, err) + require.Equal(t, "parsed", originalArg.Get()) + + // Build a command tree with flags, args, and metadata. + root := &cli.Command{ + Name: "root", + Flags: []cli.Flag{originalFlag, sharedFlag}, + MutuallyExclusiveFlags: []cli.MutuallyExclusiveFlags{ + { + Flags: [][]cli.Flag{ + {originalFlag}, + {sharedFlag}, + }, + Required: true, + Category: "cat", + }, + }, + Arguments: []cli.Argument{originalArg}, + Metadata: map[string]any{ + "key": "value", + }, + Commands: []*cli.Command{ + { + Name: "sub", + Flags: []cli.Flag{sharedFlag}, + }, + }, + } + + // Clone the command tree for replay. + cloned := cloneCommandForReplay(root) + + // Validate structural equivalence. + require.NotSame(t, root, cloned) + require.Len(t, cloned.Flags, len(root.Flags)) + require.Len(t, cloned.Commands, len(root.Commands)) + require.Len( + t, cloned.MutuallyExclusiveFlags, + len(root.MutuallyExclusiveFlags), + ) + require.Len(t, cloned.Arguments, len(root.Arguments)) + + // Ensure flags are cloned and reset. + clonedAlpha := findFlagByName( + t, cloned.Flags, "alpha", + ).(*cli.StringFlag) + require.NotSame(t, originalFlag, clonedAlpha) + require.False(t, clonedAlpha.IsSet()) + require.Equal(t, originalFlag.Name, clonedAlpha.Name) + require.Equal(t, originalFlag.Usage, clonedAlpha.Usage) + require.Equal(t, originalFlag.Aliases, clonedAlpha.Aliases) + + // Ensure the clone is independent. + clonedAlpha.Aliases[0] = "b" + require.Equal(t, []string{"a"}, originalFlag.Aliases) + + // Ensure shared flags are cloned and reset. + clonedShared := findFlagByName( + t, cloned.Flags, "shared", + ).(*cli.BoolFlag) + require.NotSame(t, sharedFlag, clonedShared) + require.False(t, clonedShared.IsSet()) + + // Ensure the mutual exclusion groups reference clones. + group := cloned.MutuallyExclusiveFlags[0] + require.Same(t, clonedAlpha, group.Flags[0][0].(*cli.StringFlag)) + require.Same(t, clonedShared, group.Flags[1][0].(*cli.BoolFlag)) + + // Ensure positional arguments are cloned and reset. + clonedArg := cloned.Arguments[0].(*cli.StringArg) + require.NotSame(t, originalArg, clonedArg) + require.Equal(t, "default", clonedArg.Get()) + + // Ensure metadata maps are independent. + cloned.Metadata["key"] = "updated" + require.Equal(t, "value", root.Metadata["key"]) +} + +// findFlagByName locates a flag by name or alias. +func findFlagByName(t *testing.T, flags []cli.Flag, name string) cli.Flag { + t.Helper() + + // Scan each flag and its aliases for the name. + for _, flag := range flags { + if flag == nil { + continue + } + if slices.Contains(flag.Names(), name) { + return flag + } + } + t.Fatalf("flag %q not found", name) + + return nil +} diff --git a/go.mod b/go.mod index 259221452..24636d3c0 100644 --- a/go.mod +++ b/go.mod @@ -95,6 +95,7 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.0.1 // indirect + github.com/google/go-cmp v0.7.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect From d1c51dfde4197a3d41f164360c24501dca543435 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 3 Feb 2026 00:55:12 -0500 Subject: [PATCH 10/18] cmd/loop: add session recording guide --- cmd/loop/testdata/sessions/AGENTS.md | 68 ++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 cmd/loop/testdata/sessions/AGENTS.md diff --git a/cmd/loop/testdata/sessions/AGENTS.md b/cmd/loop/testdata/sessions/AGENTS.md new file mode 100644 index 000000000..8eb816182 --- /dev/null +++ b/cmd/loop/testdata/sessions/AGENTS.md @@ -0,0 +1,68 @@ +# Session Recording Notes (Loop CLI) + +## How to record sessions +- Use the local CLI binary: `/home/user/bin/loop`. +- Always include `--network regtest` **after** the main command and subcommands so it does not become part of the session filename. +- Record with `LOOP_SESSION_RECORD=true`, e.g.: + - `LOOP_SESSION_RECORD=true /home/user/bin/loop quote out --network regtest 500000` +- After recording several related commands, group the resulting JSON files into a subdir under `cmd/loop/testdata/sessions/`. + +## Control panel HTTP server (regtest helpers) +Base URL: `http://127.0.0.1:12345` +- Implementation: [httpcmd](https://github.com/starius/httpcmd/) +- Config: [control panel config](https://gist.github.com/starius/6604ffe27f51d55f4cf715b4202637dd) +- `/mine`: + - Mines a block. Returns JSON with `exit_code`, `stdout`, `stderr`, `duration`, `timed_out`. +- `/deposit`: + - Sends a deposit to the client static address. Returns JSON with a `txid` on success. + - May fail with insufficient funds unless blocks have been mined first. +- `/reservation`: + - Opens a reservation for instant out. Returns JSON with reservation details (id, amount, state, etc.). +- `/loop-log`: + - Returns loopd logs (text). Use a tail filter when inspecting. +- `/loop-server-log`: + - Returns loop server logs (text). Use a tail filter when inspecting. + - Tip: The response is JSON with a large `stdout` field; extract first, then tail: + - `curl -s http://127.0.0.1:12345/loop-server-log | jq -r '.stdout' > /tmp/loop-server-log.txt` + - `tail -n 50 /tmp/loop-server-log.txt` + +## Useful regtest flow observations +- Deposits may be unconfirmed for a few blocks; mine multiple blocks to move a deposit into `DEPOSITED`. +- After making a deposit, `loop static summary` reflects unconfirmed + confirmed values; `loop static listdeposits` lists confirmed deposits. +- Reservations should be opened (via `/reservation`) before `instantout`; list them with `loop reservations list`. +- `instantout` uses interactive selection; `ALL` then `y` works for a simple scenario: + - `printf "ALL\ny\n" | LOOP_SESSION_RECORD=true /home/user/bin/loop instantout --network regtest` + +## Coverage notes (high-level) +- Sessions were recorded for: terms, getinfo, quote in/out, listauth, fetchl402, getparams, setrule (error + success), suggestswaps (error + success), reservations list, instantout, listinstantouts, static withdraw/listwithdrawals/listswaps, listswaps, swapinfo, loop out (forced), abandon swap (help path), plus existing static-loop-in/basic-swaps. +- Some paths are intentionally skipped for now: + - `stop` (would shut down loopd; replay expects a real gRPC conn). + - Asset quote paths (`getAssetAmt`, `unmarshalFixedPoint`) require tapd/asset quotes. + - Real dialer/macaroon path handling (`extractPathArgs`, `readMacaroon`, `getClientConn`) aren’t exercised by replay. + + +## Replay stability notes +- Session replay now clones the CLI command tree per run, so flag state (`IsSet`) does not leak between sessions. +- Historical warning: earlier replays could have sticky flags across runs; if you see odd ordering-dependent failures, re-check that the replay uses the cloned command path. + +## Session coverage map +| Subdir | Commands / scenarios | +| --- | --- | +| `basic-swaps/` | `loop out` (success), `loop in` (success), `loop monitor` | +| `getinfo/` | `loop getinfo` | +| `instantout/` | `loop reservations list`, `loop instantout` (ALL + confirm), `loop instantout` (channel flag), `loop instantout` (select index), `loop instantout` (no confirmed reservations), `loop instantout` (cancel), `loop instantout` (invalid selection), `loop listinstantouts` | +| `l402/` | `loop listauth`, `loop fetchl402` | +| `liquidity/` | `loop getparams`, `loop setparams` (no flags, feepercent, conflict, many flags/categories, destaddr, account, includeallpeers; includes error cases), `loop setrule` (missing threshold, no args, incoming/outgoing/type in/out, clear), `loop suggestswaps` (error + success) | +| `loopin/` | `loop in` (invalid amount), `loop in` (external + conf_target error), `loop in` (route_hints + private error), `loop in` (external cancel + verbose, last_hop/amt flag), `loop in` (external force) | +| `loopout/` | `loop out` (forced success), `loop out` (invalid amount), `loop out` (addr + account error), `loop out` (invalid account address type), `loop out` (amt flag + channel + max routing fee + payment timeout), `loop out` (addr flag), `loop out` (positional addr), `loop out` (account + account_addr_type) | +| `misc/` | `loop terms` | +| `quote/` | `loop quote out` (success + verbose), `loop quote in` (help + verbose), `loop quote out` (help), `loop quote in` (deposit_outpoint success), `loop quote in` (positional + last_hop) | +| `static/` | `loop static withdraw` (no selection error), `loop static withdraw` (invalid utxo), `loop static withdraw` (all success), `loop static withdraw` (utxo + dest_addr success), `loop static listwithdrawals`, `loop static listswaps` | +| `static-loop-in/` | `loop static new`, `loop static` (help), `loop static listunspent` (incl alias), `loop static listdeposits`, `loop static summary`, `loop static in` (multiple args/flags cases), `loop static in` (duplicate outpoints), `loop static in` (positional low amount error), `loop static in` (positional + last_hop + payment_timeout), `loop static in` (all cancel) | +| `static-filters/` | `loop static listdeposits --filter ...` for each state (deposited/withdrawing/withdrawn/looping_in/looped_in/publish_expired_deposit/sweep_htlc_timeout/htlc_timeout_swept/wait_for_expiry_sweep/expired/failed) | +| `swaps/` | `loop listswaps` (success + conflicting filters + loop_out_only filters + loop_in_only), `loop swapinfo` (success + invalid id + id flag errors), `loop abandonswap` (help + invalid id + success) | + +## Missing / not covered yet +- `loop stop` (would terminate loopd; replay expects a live gRPC server). +- Docs generators: `loop man`, `loop markdown` (handled in a separate docs pipeline). +- Asset/tapd scenarios (explicitly out of scope for now). From 1d2073d832285cd1d798633dd89c8b49cb57383d Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 3 Feb 2026 00:55:52 -0500 Subject: [PATCH 11/18] testdata: add basic swap sessions --- .../sessions/basic-swaps/01_loop-out.json | 146 ++++++ .../sessions/basic-swaps/02_loop-in.json | 131 ++++++ .../sessions/basic-swaps/03_loop-monitor.json | 424 ++++++++++++++++++ .../sessions/getinfo/01_loop-getinfo.json | 96 ++++ .../testdata/sessions/misc/01_loop-terms.json | 103 +++++ 5 files changed, 900 insertions(+) create mode 100644 cmd/loop/testdata/sessions/basic-swaps/01_loop-out.json create mode 100644 cmd/loop/testdata/sessions/basic-swaps/02_loop-in.json create mode 100644 cmd/loop/testdata/sessions/basic-swaps/03_loop-monitor.json create mode 100644 cmd/loop/testdata/sessions/getinfo/01_loop-getinfo.json create mode 100644 cmd/loop/testdata/sessions/misc/01_loop-terms.json diff --git a/cmd/loop/testdata/sessions/basic-swaps/01_loop-out.json b/cmd/loop/testdata/sessions/basic-swaps/01_loop-out.json new file mode 100644 index 000000000..5a4f7b0eb --- /dev/null +++ b/cmd/loop/testdata/sessions/basic-swaps/01_loop-out.json @@ -0,0 +1,146 @@ +{ + "metadata": { + "args": [ + "loop", + "out", + "--network", + "regtest", + "500000", + "--fast" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-64-g3b735173216470e023b3b72949dd82af9adbebe1 commit_hash=3b735173216470e023b3b72949dd82af9adbebe1", + "duration": 1332472129, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "500000", + "conf_target": 9, + "external_htlc": false, + "swap_publication_deadline": "1769407086", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 22, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "response", + "message_type": "looprpc.OutQuoteResponse", + "payload": { + "swap_fee_sat": "3875", + "prepay_amt_sat": "1337", + "htlc_sweep_fee_sat": "3793", + "swap_payment_dest": "AhbcGtFvFiW2ru6gxtm66Xv9DpxBdSBXK4a7EX7jbSWd", + "cltv_delta": 0, + "conf_target": 9, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 23, + "kind": "stdout", + "data": { + "lines": [ + "Send off-chain: 500000 sat\n", + "Receive on-chain: 492332 sat\n", + "Estimated total fee: 7668 sat\n", + "\n", + "Fast swap requested.\n", + "\n", + "CONTINUE SWAP? (y/n): " + ] + } + }, + { + "time_ms": 1240, + "kind": "stdin", + "data": { + "text": "y\n" + } + }, + { + "time_ms": 1241, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOut", + "event": "request", + "message_type": "looprpc.LoopOutRequest", + "payload": { + "amt": "500000", + "dest": "", + "max_swap_routing_fee": "10010", + "max_prepay_routing_fee": "36", + "max_swap_fee": "3875", + "max_prepay_amt": "1337", + "max_miner_fee": "948250", + "loop_out_channel": "0", + "outgoing_chan_set": [], + "sweep_conf_target": 9, + "htlc_confirmations": 1, + "swap_publication_deadline": "1769407086", + "label": "", + "initiator": "loop-cli", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "is_external_addr": false, + "reservation_ids": [], + "payment_timeout": 0, + "asset_info": null, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 1327, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOut", + "event": "response", + "message_type": "looprpc.SwapResponse", + "payload": { + "id": "e5f3a1d5cee33e239571d0c1e8cbc6926cbc1d784b9fe185ff6589e949db4549", + "id_bytes": "5fOh1c7jPiOVcdDB6MvGkmy8HXhLn+GF/2WJ6UnbRUk=", + "htlc_address": "bcrt1p69ekm32epfcy8ymkqdj5clvh37vmlgw76k9x5nfs76c7qgghnpqqmtf80x", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1p69ekm32epfcy8ymkqdj5clvh37vmlgw76k9x5nfs76c7qgghnpqqmtf80x", + "server_message": "" + } + } + }, + { + "time_ms": 1329, + "kind": "stdout", + "data": { + "lines": [ + "Swap initiated\n", + "ID: e5f3a1d5cee33e239571d0c1e8cbc6926cbc1d784b9fe185ff6589e949db4549\n", + "\n", + "Run `loop monitor` to monitor progress.\n" + ] + } + }, + { + "time_ms": 1332, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/basic-swaps/02_loop-in.json b/cmd/loop/testdata/sessions/basic-swaps/02_loop-in.json new file mode 100644 index 000000000..1e627beea --- /dev/null +++ b/cmd/loop/testdata/sessions/basic-swaps/02_loop-in.json @@ -0,0 +1,131 @@ +{ + "metadata": { + "args": [ + "loop", + "in", + "--network", + "regtest", + "500000" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-69-g057ef82340ab279a5659ab6ae7edab61448a2acd commit_hash=057ef82340ab279a5659ab6ae7edab61448a2acd", + "duration": 2146386627, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 5, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "500000", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 444, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1825", + "htlc_publish_fee_sat": "3875", + "cltv_delta": 0, + "conf_target": 6, + "quoted_amt": "500000" + } + } + }, + { + "time_ms": 445, + "kind": "stdout", + "data": { + "lines": [ + "Send on-chain: 500000 sat\n", + "Receive off-chain: 494300 sat\n", + "Estimated total fee: 5700 sat\n", + "\n", + "CONTINUE SWAP? (y/n): " + ] + } + }, + { + "time_ms": 1682, + "kind": "stdin", + "data": { + "text": "y\n" + } + }, + { + "time_ms": 1682, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopIn", + "event": "request", + "message_type": "looprpc.LoopInRequest", + "payload": { + "amt": "500000", + "max_swap_fee": "1825", + "max_miner_fee": "11625", + "last_hop": "", + "external_htlc": false, + "htlc_conf_target": 0, + "label": "", + "initiator": "loop-cli", + "route_hints": [], + "private": false + } + } + }, + { + "time_ms": 2140, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopIn", + "event": "response", + "message_type": "looprpc.SwapResponse", + "payload": { + "id": "33b431ce62ab7f8f3ff5e2aee9452ec9aa18713e734ecd79fc9cfb4bd77cc7d8", + "id_bytes": "M7QxzmKrf48/9eKu6UUuyaoYcT5zTs15/Jz7S9d8x9g=", + "htlc_address": "bcrt1pmtw7fch8de39wxfqunuucudgulkvyxm59epz9p0ph673squ7l2ns3gpcsu", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1pmtw7fch8de39wxfqunuucudgulkvyxm59epz9p0ph673squ7l2ns3gpcsu", + "server_message": "" + } + } + }, + { + "time_ms": 2141, + "kind": "stdout", + "data": { + "lines": [ + "Swap initiated\n", + "ID: 33b431ce62ab7f8f3ff5e2aee9452ec9aa18713e734ecd79fc9cfb4bd77cc7d8\n", + "HTLC address (P2TR): bcrt1pmtw7fch8de39wxfqunuucudgulkvyxm59epz9p0ph673squ7l2ns3gpcsu\n", + "\n", + "Run `loop monitor` to monitor progress.\n" + ] + } + }, + { + "time_ms": 2146, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/basic-swaps/03_loop-monitor.json b/cmd/loop/testdata/sessions/basic-swaps/03_loop-monitor.json new file mode 100644 index 000000000..15de842ae --- /dev/null +++ b/cmd/loop/testdata/sessions/basic-swaps/03_loop-monitor.json @@ -0,0 +1,424 @@ +{ + "metadata": { + "args": [ + "loop", + "monitor", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "run_error": "recv: rpc error: code = Canceled desc = context canceled", + "duration": 58532880576, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 8, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/Monitor", + "event": "send", + "message_type": "looprpc.MonitorRequest", + "payload": {} + } + }, + { + "time_ms": 8, + "kind": "stdout", + "data": { + "lines": [ + "Note: offchain cost may report as 0 after loopd restart during swap\n" + ] + } + }, + { + "time_ms": 13, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/Monitor", + "event": "recv", + "message_type": "looprpc.SwapStatus", + "payload": { + "amt": "500000", + "id": "33b431ce62ab7f8f3ff5e2aee9452ec9aa18713e734ecd79fc9cfb4bd77cc7d8", + "id_bytes": "M7QxzmKrf48/9eKu6UUuyaoYcT5zTs15/Jz7S9d8x9g=", + "type": "LOOP_IN", + "state": "INVOICE_SETTLED", + "failure_reason": "FAILURE_REASON_NONE", + "initiation_time": "1769413263544127799", + "last_update_time": "1769413312288899857", + "htlc_address": "bcrt1pmtw7fch8de39wxfqunuucudgulkvyxm59epz9p0ph673squ7l2ns3gpcsu", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1pmtw7fch8de39wxfqunuucudgulkvyxm59epz9p0ph673squ7l2ns3gpcsu", + "cost_server": "0", + "cost_onchain": "3850", + "cost_offchain": "0", + "last_hop": "", + "outgoing_chan_set": [], + "label": "", + "asset_info": null + } + } + }, + { + "time_ms": 13, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/Monitor", + "event": "recv", + "message_type": "looprpc.SwapStatus", + "payload": { + "amt": "500000", + "id": "4a8f1dd97addf8222c7613f6a41e3942f4bf9ca64026107c484c1f7e551df658", + "id_bytes": "So8d2Xrd+CIsdhP2pB45QvS/nKZAJhB8SEwfflUd9lg=", + "type": "LOOP_OUT", + "state": "SUCCESS", + "failure_reason": "FAILURE_REASON_NONE", + "initiation_time": "1769407041704702192", + "last_update_time": "1769413318432807159", + "htlc_address": "bcrt1pmvvj28wnuwe70xfy6kjzwxcg7cx0kc2dy8l6armutegs8y3p0j7qg0atua", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1pmvvj28wnuwe70xfy6kjzwxcg7cx0kc2dy8l6armutegs8y3p0j7qg0atua", + "cost_server": "3875", + "cost_onchain": "2775", + "cost_offchain": "0", + "last_hop": "", + "outgoing_chan_set": [], + "label": "", + "asset_info": null + } + } + }, + { + "time_ms": 14, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/Monitor", + "event": "recv", + "message_type": "looprpc.SwapStatus", + "payload": { + "amt": "500000", + "id": "e5f3a1d5cee33e239571d0c1e8cbc6926cbc1d784b9fe185ff6589e949db4549", + "id_bytes": "5fOh1c7jPiOVcdDB6MvGkmy8HXhLn+GF/2WJ6UnbRUk=", + "type": "LOOP_OUT", + "state": "SUCCESS", + "failure_reason": "FAILURE_REASON_NONE", + "initiation_time": "1769407087874686997", + "last_update_time": "1769413322775449292", + "htlc_address": "bcrt1p69ekm32epfcy8ymkqdj5clvh37vmlgw76k9x5nfs76c7qgghnpqqmtf80x", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1p69ekm32epfcy8ymkqdj5clvh37vmlgw76k9x5nfs76c7qgghnpqqmtf80x", + "cost_server": "3875", + "cost_onchain": "2775", + "cost_offchain": "0", + "last_hop": "", + "outgoing_chan_set": [], + "label": "", + "asset_info": null + } + } + }, + { + "time_ms": 14, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/Monitor", + "event": "recv", + "message_type": "looprpc.SwapStatus", + "payload": { + "amt": "500000", + "id": "39424d7e6cf6d9c613c4026f5e6da48fd2b65989007895a0db7b937b7f8563e4", + "id_bytes": "OUJNfmz22cYTxAJvXm2kj9K2WYkAeJWg23uTe3+FY+Q=", + "type": "LOOP_OUT", + "state": "SUCCESS", + "failure_reason": "FAILURE_REASON_NONE", + "initiation_time": "1769413204269210604", + "last_update_time": "1769413329442159169", + "htlc_address": "bcrt1prz80rm04fhf5y73ps9txxslnhj8e3nqcsnmz6ca6vs6jkrce2l9svtd8sl", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1prz80rm04fhf5y73ps9txxslnhj8e3nqcsnmz6ca6vs6jkrce2l9svtd8sl", + "cost_server": "3875", + "cost_onchain": "2774", + "cost_offchain": "0", + "last_hop": "", + "outgoing_chan_set": [], + "label": "", + "asset_info": null + } + } + }, + { + "time_ms": 14, + "kind": "stdout", + "data": { + "lines": [ + "2026-01-26T02:41:52-05:00 LOOP_IN INVOICE_SETTLED 0.00500000 BTC - P2TR: bcrt1pmtw7fch8de39wxfqunuucudgulkvyxm59epz9p0ph673squ7l2ns3gpcsu (cost: server 0, onchain 3850, offchain 0)\n", + "2026-01-26T02:41:58-05:00 LOOP_OUT SUCCESS 0.00500000 BTC - (cost: server 3875, onchain 2775, offchain 0)\n", + "2026-01-26T02:42:02-05:00 LOOP_OUT SUCCESS 0.00500000 BTC - (cost: server 3875, onchain 2775, offchain 0)\n", + "2026-01-26T02:42:09-05:00 LOOP_OUT SUCCESS 0.00500000 BTC - (cost: server 3875, onchain 2774, offchain 0)\n" + ] + } + }, + { + "time_ms": 19978, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/Monitor", + "event": "recv", + "message_type": "looprpc.SwapStatus", + "payload": { + "amt": "500000", + "id": "13da0be986d8a1f095794f78c5e716150e93c92834e3be52f6eb2fbfc84b8eb0", + "id_bytes": "E9oL6YbYofCVeU94xecWFQ6TySg0475S9usvv8hLjrA=", + "type": "LOOP_OUT", + "state": "INITIATED", + "failure_reason": "FAILURE_REASON_NONE", + "initiation_time": "1769414430311530071", + "last_update_time": "1769414430311530071", + "htlc_address": "bcrt1ps8vvwey05ezrmhe4mpz60nre256ggl2uyazs7fayh83z7ptnclnstaxvu2", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1ps8vvwey05ezrmhe4mpz60nre256ggl2uyazs7fayh83z7ptnclnstaxvu2", + "cost_server": "0", + "cost_onchain": "0", + "cost_offchain": "0", + "last_hop": "", + "outgoing_chan_set": [], + "label": "", + "asset_info": null + } + } + }, + { + "time_ms": 19978, + "kind": "stdout", + "data": { + "lines": [ + "2026-01-26T03:00:30-05:00 LOOP_OUT INITIATED 0.00500000 BTC - \n" + ] + } + }, + { + "time_ms": 32802, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/Monitor", + "event": "recv", + "message_type": "looprpc.SwapStatus", + "payload": { + "amt": "500000", + "id": "3f4ec343ba4087eec3cdf501b4e19733d9685cc151db360bb67d2c924677ce71", + "id_bytes": "P07DQ7pAh+7DzfUBtOGXM9loXMFR2zYLtn0skkZ3znE=", + "type": "LOOP_IN", + "state": "INITIATED", + "failure_reason": "FAILURE_REASON_NONE", + "initiation_time": "1769414443188873986", + "last_update_time": "1769414443188873986", + "htlc_address": "bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch", + "cost_server": "0", + "cost_onchain": "0", + "cost_offchain": "0", + "last_hop": "", + "outgoing_chan_set": [], + "label": "", + "asset_info": null + } + } + }, + { + "time_ms": 32802, + "kind": "stdout", + "data": { + "lines": [ + "2026-01-26T03:00:43-05:00 LOOP_IN INITIATED 0.00500000 BTC - P2TR: bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch\n" + ] + } + }, + { + "time_ms": 32829, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/Monitor", + "event": "recv", + "message_type": "looprpc.SwapStatus", + "payload": { + "amt": "500000", + "id": "3f4ec343ba4087eec3cdf501b4e19733d9685cc151db360bb67d2c924677ce71", + "id_bytes": "P07DQ7pAh+7DzfUBtOGXM9loXMFR2zYLtn0skkZ3znE=", + "type": "LOOP_IN", + "state": "HTLC_PUBLISHED", + "failure_reason": "FAILURE_REASON_NONE", + "initiation_time": "1769414443188873986", + "last_update_time": "1769414443212089572", + "htlc_address": "bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch", + "cost_server": "0", + "cost_onchain": "0", + "cost_offchain": "0", + "last_hop": "", + "outgoing_chan_set": [], + "label": "", + "asset_info": null + } + } + }, + { + "time_ms": 32829, + "kind": "stdout", + "data": { + "lines": [ + "2026-01-26T03:00:43-05:00 LOOP_IN HTLC_PUBLISHED 0.00500000 BTC - P2TR: bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch\n" + ] + } + }, + { + "time_ms": 39513, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/Monitor", + "event": "recv", + "message_type": "looprpc.SwapStatus", + "payload": { + "amt": "500000", + "id": "3f4ec343ba4087eec3cdf501b4e19733d9685cc151db360bb67d2c924677ce71", + "id_bytes": "P07DQ7pAh+7DzfUBtOGXM9loXMFR2zYLtn0skkZ3znE=", + "type": "LOOP_IN", + "state": "INVOICE_SETTLED", + "failure_reason": "FAILURE_REASON_NONE", + "initiation_time": "1769414443188873986", + "last_update_time": "1769414449902523194", + "htlc_address": "bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch", + "cost_server": "0", + "cost_onchain": "3850", + "cost_offchain": "0", + "last_hop": "", + "outgoing_chan_set": [], + "label": "", + "asset_info": null + } + } + }, + { + "time_ms": 39513, + "kind": "stdout", + "data": { + "lines": [ + "2026-01-26T03:00:49-05:00 LOOP_IN INVOICE_SETTLED 0.00500000 BTC - P2TR: bcrt1ppuw9wtl65avd026t9nkxux8whz3gyhkjh6n9cryp8a5xz9jacf9qlgxsch (cost: server 0, onchain 3850, offchain 0)\n" + ] + } + }, + { + "time_ms": 40341, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/Monitor", + "event": "recv", + "message_type": "looprpc.SwapStatus", + "payload": { + "amt": "500000", + "id": "13da0be986d8a1f095794f78c5e716150e93c92834e3be52f6eb2fbfc84b8eb0", + "id_bytes": "E9oL6YbYofCVeU94xecWFQ6TySg0475S9usvv8hLjrA=", + "type": "LOOP_OUT", + "state": "PREIMAGE_REVEALED", + "failure_reason": "FAILURE_REASON_NONE", + "initiation_time": "1769414430311530071", + "last_update_time": "1769414450735780318", + "htlc_address": "bcrt1ps8vvwey05ezrmhe4mpz60nre256ggl2uyazs7fayh83z7ptnclnstaxvu2", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1ps8vvwey05ezrmhe4mpz60nre256ggl2uyazs7fayh83z7ptnclnstaxvu2", + "cost_server": "0", + "cost_onchain": "0", + "cost_offchain": "0", + "last_hop": "", + "outgoing_chan_set": [], + "label": "", + "asset_info": null + } + } + }, + { + "time_ms": 40341, + "kind": "stdout", + "data": { + "lines": [ + "2026-01-26T03:00:50-05:00 LOOP_OUT PREIMAGE_REVEALED 0.00500000 BTC - \n" + ] + } + }, + { + "time_ms": 44110, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/Monitor", + "event": "recv", + "message_type": "looprpc.SwapStatus", + "payload": { + "amt": "500000", + "id": "13da0be986d8a1f095794f78c5e716150e93c92834e3be52f6eb2fbfc84b8eb0", + "id_bytes": "E9oL6YbYofCVeU94xecWFQ6TySg0475S9usvv8hLjrA=", + "type": "LOOP_OUT", + "state": "SUCCESS", + "failure_reason": "FAILURE_REASON_NONE", + "initiation_time": "1769414430311530071", + "last_update_time": "1769414454503356191", + "htlc_address": "bcrt1ps8vvwey05ezrmhe4mpz60nre256ggl2uyazs7fayh83z7ptnclnstaxvu2", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1ps8vvwey05ezrmhe4mpz60nre256ggl2uyazs7fayh83z7ptnclnstaxvu2", + "cost_server": "3873", + "cost_onchain": "2774", + "cost_offchain": "0", + "last_hop": "", + "outgoing_chan_set": [], + "label": "", + "asset_info": null + } + } + }, + { + "time_ms": 44110, + "kind": "stdout", + "data": { + "lines": [ + "2026-01-26T03:00:54-05:00 LOOP_OUT SUCCESS 0.00500000 BTC - (cost: server 3873, onchain 2774, offchain 0)\n" + ] + } + }, + { + "time_ms": 58531, + "kind": "signal", + "data": { + "signal": "interrupt" + } + }, + { + "time_ms": 58531, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/Monitor", + "event": "error", + "error": "rpc error: code = Canceled desc = context canceled" + } + }, + { + "time_ms": 58532, + "kind": "stderr", + "data": { + "lines": [ + "[loop] recv: rpc error: code = Canceled desc = context canceled\n" + ] + } + }, + { + "time_ms": 58532, + "kind": "exit", + "data": { + "run_error": "recv: rpc error: code = Canceled desc = context canceled" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/getinfo/01_loop-getinfo.json b/cmd/loop/testdata/sessions/getinfo/01_loop-getinfo.json new file mode 100644 index 000000000..09924b933 --- /dev/null +++ b/cmd/loop/testdata/sessions/getinfo/01_loop-getinfo.json @@ -0,0 +1,96 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "getinfo", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 200590737, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 4, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetInfo", + "event": "request", + "message_type": "looprpc.GetInfoRequest", + "payload": {} + } + }, + { + "time_ms": 200, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetInfo", + "event": "response", + "message_type": "looprpc.GetInfoResponse", + "payload": { + "version": "0.31.7-beta", + "network": "regtest", + "rpc_listen": "localhost:11010", + "rest_listen": "localhost:8081", + "macaroon_path": "/home/user/.loop/regtest/loop.macaroon", + "tls_cert_path": "/home/user/.loop/regtest/tls.cert", + "loop_out_stats": { + "pending_count": "0", + "success_count": "0", + "fail_count": "0", + "sum_pending_amt": "0", + "sum_succeeded_amt": "0" + }, + "loop_in_stats": { + "pending_count": "0", + "success_count": "0", + "fail_count": "0", + "sum_pending_amt": "0", + "sum_succeeded_amt": "0" + }, + "commit_hash": "579ea31979e0cdb1ee6049ab97cc2768888eac40" + } + } + }, + { + "time_ms": 200, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"commit_hash\": \"579ea31979e0cdb1ee6049ab97cc2768888eac40\",\n", + " \"loop_in_stats\": {\n", + " \"fail_count\": \"0\",\n", + " \"pending_count\": \"0\",\n", + " \"success_count\": \"0\",\n", + " \"sum_pending_amt\": \"0\",\n", + " \"sum_succeeded_amt\": \"0\"\n", + " },\n", + " \"loop_out_stats\": {\n", + " \"fail_count\": \"0\",\n", + " \"pending_count\": \"0\",\n", + " \"success_count\": \"0\",\n", + " \"sum_pending_amt\": \"0\",\n", + " \"sum_succeeded_amt\": \"0\"\n", + " },\n", + " \"macaroon_path\": \"/home/user/.loop/regtest/loop.macaroon\",\n", + " \"network\": \"regtest\",\n", + " \"rest_listen\": \"localhost:8081\",\n", + " \"rpc_listen\": \"localhost:11010\",\n", + " \"tls_cert_path\": \"/home/user/.loop/regtest/tls.cert\",\n", + " \"version\": \"0.31.7-beta\"\n", + "}\n" + ] + } + }, + { + "time_ms": 200, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/misc/01_loop-terms.json b/cmd/loop/testdata/sessions/misc/01_loop-terms.json new file mode 100644 index 000000000..8cc2dc7af --- /dev/null +++ b/cmd/loop/testdata/sessions/misc/01_loop-terms.json @@ -0,0 +1,103 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "terms", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 199188467, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 7, + "kind": "stdout", + "data": { + "lines": [ + "Loop Out\n", + "--------\n" + ] + } + }, + { + "time_ms": 7, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutTerms", + "event": "request", + "message_type": "looprpc.TermsRequest", + "payload": {} + } + }, + { + "time_ms": 156, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutTerms", + "event": "response", + "message_type": "looprpc.OutTermsResponse", + "payload": { + "min_swap_amount": "50000", + "max_swap_amount": "1000000", + "min_cltv_delta": 50, + "max_cltv_delta": 250 + } + } + }, + { + "time_ms": 156, + "kind": "stdout", + "data": { + "lines": [ + "Amount: 50000 - 1000000\n", + "Cltv delta: 50 - 250\n", + "\n", + "Loop In\n", + "------\n" + ] + } + }, + { + "time_ms": 156, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInTerms", + "event": "request", + "message_type": "looprpc.TermsRequest", + "payload": {} + } + }, + { + "time_ms": 198, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInTerms", + "event": "response", + "message_type": "looprpc.InTermsResponse", + "payload": { + "min_swap_amount": "50000", + "max_swap_amount": "1000000" + } + } + }, + { + "time_ms": 198, + "kind": "stdout", + "data": { + "lines": [ + "Amount: 50000 - 1000000\n" + ] + } + }, + { + "time_ms": 199, + "kind": "exit", + "data": {} + } + ] +} From db79a1cc179b27dba7f530854e35c5161d9d93c2 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 3 Feb 2026 00:56:26 -0500 Subject: [PATCH 12/18] testdata: add instantout and l402 sessions --- .../instantout/01_loop-reservations-list.json | 107 +++++++++ .../instantout/02_loop-instantout.json | 189 ++++++++++++++++ .../instantout/03_loop-listinstantouts.json | 80 +++++++ .../04_loop-instantout-no-confirmed.json | 94 ++++++++ .../instantout/05_loop-instantout-cancel.json | 155 +++++++++++++ .../06_loop-instantout-invalid-selection.json | 113 ++++++++++ .../07_loop-instantout-channel.json | 206 ++++++++++++++++++ .../08_loop-instantout-select-index.json | 199 +++++++++++++++++ .../sessions/l402/01_loop-listauth.json | 79 +++++++ .../sessions/l402/02_loop-fetchl402.json | 52 +++++ 10 files changed, 1274 insertions(+) create mode 100644 cmd/loop/testdata/sessions/instantout/01_loop-reservations-list.json create mode 100644 cmd/loop/testdata/sessions/instantout/02_loop-instantout.json create mode 100644 cmd/loop/testdata/sessions/instantout/03_loop-listinstantouts.json create mode 100644 cmd/loop/testdata/sessions/instantout/04_loop-instantout-no-confirmed.json create mode 100644 cmd/loop/testdata/sessions/instantout/05_loop-instantout-cancel.json create mode 100644 cmd/loop/testdata/sessions/instantout/06_loop-instantout-invalid-selection.json create mode 100644 cmd/loop/testdata/sessions/instantout/07_loop-instantout-channel.json create mode 100644 cmd/loop/testdata/sessions/instantout/08_loop-instantout-select-index.json create mode 100644 cmd/loop/testdata/sessions/l402/01_loop-listauth.json create mode 100644 cmd/loop/testdata/sessions/l402/02_loop-fetchl402.json diff --git a/cmd/loop/testdata/sessions/instantout/01_loop-reservations-list.json b/cmd/loop/testdata/sessions/instantout/01_loop-reservations-list.json new file mode 100644 index 000000000..ce21a9e9a --- /dev/null +++ b/cmd/loop/testdata/sessions/instantout/01_loop-reservations-list.json @@ -0,0 +1,107 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "reservations", + "list", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 177945855, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "request", + "message_type": "looprpc.ListReservationsRequest", + "payload": {} + } + }, + { + "time_ms": 177, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "response", + "message_type": "looprpc.ListReservationsResponse", + "payload": { + "reservations": [ + { + "reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=", + "state": "Confirmed", + "amount": "800000", + "tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17", + "vout": 0, + "expiry": 1619 + }, + { + "reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=", + "state": "Confirmed", + "amount": "800000", + "tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836", + "vout": 0, + "expiry": 1623 + }, + { + "reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=", + "state": "Confirmed", + "amount": "800000", + "tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952", + "vout": 0, + "expiry": 1626 + } + ] + } + } + }, + { + "time_ms": 177, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"reservations\": [\n", + " {\n", + " \"amount\": \"800000\",\n", + " \"expiry\": 1619,\n", + " \"reservation_id\": \"b3f443e8b8ff797766b450ad77fe80beb250acfd7887562b6e5c9964c5884d87\",\n", + " \"state\": \"Confirmed\",\n", + " \"tx_id\": \"8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17\",\n", + " \"vout\": 0\n", + " },\n", + " {\n", + " \"amount\": \"800000\",\n", + " \"expiry\": 1623,\n", + " \"reservation_id\": \"5a5f824ec63e576cd4be42b96ecf5622db64bca2eb28cf00d64a59e5567f307b\",\n", + " \"state\": \"Confirmed\",\n", + " \"tx_id\": \"beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836\",\n", + " \"vout\": 0\n", + " },\n", + " {\n", + " \"amount\": \"800000\",\n", + " \"expiry\": 1626,\n", + " \"reservation_id\": \"32eeb97db85ac84b51ce8ba028b067a1e44df1ffad10cd7e3bd42e36f5087db2\",\n", + " \"state\": \"Confirmed\",\n", + " \"tx_id\": \"5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952\",\n", + " \"vout\": 0\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 177, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/instantout/02_loop-instantout.json b/cmd/loop/testdata/sessions/instantout/02_loop-instantout.json new file mode 100644 index 000000000..6dae20b15 --- /dev/null +++ b/cmd/loop/testdata/sessions/instantout/02_loop-instantout.json @@ -0,0 +1,189 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "instantout", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 1574538838, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 0, + "kind": "stdin", + "data": { + "text": "ALL\ny\n" + } + }, + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "request", + "message_type": "looprpc.ListReservationsRequest", + "payload": {} + } + }, + { + "time_ms": 152, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "response", + "message_type": "looprpc.ListReservationsResponse", + "payload": { + "reservations": [ + { + "reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=", + "state": "Confirmed", + "amount": "800000", + "tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17", + "vout": 0, + "expiry": 1619 + }, + { + "reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=", + "state": "Confirmed", + "amount": "800000", + "tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836", + "vout": 0, + "expiry": 1623 + }, + { + "reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=", + "state": "Confirmed", + "amount": "800000", + "tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952", + "vout": 0, + "expiry": 1626 + } + ] + } + } + }, + { + "time_ms": 152, + "kind": "stdout", + "data": { + "lines": [ + "Available reservations: \n", + "\n", + "Reservation 1: shortid b3f443, amt 800000, expiry height 1619 \n", + "Reservation 2: shortid 5a5f82, amt 800000, expiry height 1623 \n", + "Reservation 3: shortid 32eeb9, amt 800000, expiry height 1626 \n", + "\n", + "Max amount to instant out: 2400000\n", + "\n", + "Select reservations for instantout (e.g. '1,2,3')\n" + ] + } + }, + { + "time_ms": 152, + "kind": "stdout", + "data": { + "lines": [ + "Type 'ALL' to use all available reservations.\n" + ] + } + }, + { + "time_ms": 152, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOutQuote", + "event": "request", + "message_type": "looprpc.InstantOutQuoteRequest", + "payload": { + "amt": "2400000", + "num_reservations": 0, + "reservation_ids": [ + "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=", + "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=", + "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=" + ] + } + } + }, + { + "time_ms": 177, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOutQuote", + "event": "response", + "message_type": "looprpc.InstantOutQuoteResponse", + "payload": { + "service_fee_sat": "4800", + "sweep_fee_sat": "5650" + } + } + }, + { + "time_ms": 177, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOut", + "event": "request", + "message_type": "looprpc.InstantOutRequest", + "payload": { + "reservation_ids": [ + "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=", + "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=", + "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=" + ], + "outgoing_chan_set": [], + "dest_addr": "" + } + } + }, + { + "time_ms": 177, + "kind": "stdout", + "data": { + "lines": [ + "\n", + "Estimated on-chain fee: 5650 sat\n", + "Service fee: 4800 sat\n", + "\n", + "CONTINUE SWAP? (y/n): Starting instant swap out\n" + ] + } + }, + { + "time_ms": 1574, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOut", + "event": "response", + "message_type": "looprpc.InstantOutResponse", + "payload": { + "instant_out_hash": "uxbiybKoHjn4B4b+yHRFGI9e12jMMtTLEvNnF7wYriU=", + "sweep_tx_id": "a1650cb339118e58f317bbbbd8cff720e0cc02a9fe159c9286724d6f5722fef2", + "state": "PushPreimage" + } + } + }, + { + "time_ms": 1574, + "kind": "stdout", + "data": { + "lines": [ + "Instant out swap initiated with ID: bb16e2c9b2a81e39f80786fec87445188f5ed768cc32d4cb12f36717bc18ae25, State: PushPreimage \n", + "Sweepless sweep tx id: a1650cb339118e58f317bbbbd8cff720e0cc02a9fe159c9286724d6f5722fef2 \n" + ] + } + }, + { + "time_ms": 1574, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/instantout/03_loop-listinstantouts.json b/cmd/loop/testdata/sessions/instantout/03_loop-listinstantouts.json new file mode 100644 index 000000000..84b2d368a --- /dev/null +++ b/cmd/loop/testdata/sessions/instantout/03_loop-listinstantouts.json @@ -0,0 +1,80 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "listinstantouts", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 142800927, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListInstantOuts", + "event": "request", + "message_type": "looprpc.ListInstantOutsRequest", + "payload": {} + } + }, + { + "time_ms": 142, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListInstantOuts", + "event": "response", + "message_type": "looprpc.ListInstantOutsResponse", + "payload": { + "swaps": [ + { + "swap_hash": "uxbiybKoHjn4B4b+yHRFGI9e12jMMtTLEvNnF7wYriU=", + "state": "WaitForSweeplessSweepConfirmed", + "amount": "2400000", + "reservation_ids": [ + "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=", + "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=", + "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=" + ], + "sweep_tx_id": "a1650cb339118e58f317bbbbd8cff720e0cc02a9fe159c9286724d6f5722fef2" + } + ] + } + } + }, + { + "time_ms": 142, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"swaps\": [\n", + " {\n", + " \"amount\": \"2400000\",\n", + " \"reservation_ids\": [\n", + " \"b3f443e8b8ff797766b450ad77fe80beb250acfd7887562b6e5c9964c5884d87\",\n", + " \"5a5f824ec63e576cd4be42b96ecf5622db64bca2eb28cf00d64a59e5567f307b\",\n", + " \"32eeb97db85ac84b51ce8ba028b067a1e44df1ffad10cd7e3bd42e36f5087db2\"\n", + " ],\n", + " \"state\": \"WaitForSweeplessSweepConfirmed\",\n", + " \"swap_hash\": \"bb16e2c9b2a81e39f80786fec87445188f5ed768cc32d4cb12f36717bc18ae25\",\n", + " \"sweep_tx_id\": \"a1650cb339118e58f317bbbbd8cff720e0cc02a9fe159c9286724d6f5722fef2\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 142, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/instantout/04_loop-instantout-no-confirmed.json b/cmd/loop/testdata/sessions/instantout/04_loop-instantout-no-confirmed.json new file mode 100644 index 000000000..52f49b965 --- /dev/null +++ b/cmd/loop/testdata/sessions/instantout/04_loop-instantout-no-confirmed.json @@ -0,0 +1,94 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "instantout", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 271911644, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "stdin", + "data": { + "text": "ALL\nn\n" + } + }, + { + "time_ms": 9, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "request", + "message_type": "looprpc.ListReservationsRequest", + "payload": {} + } + }, + { + "time_ms": 270, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "response", + "message_type": "looprpc.ListReservationsResponse", + "payload": { + "reservations": [ + { + "reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=", + "state": "Spent", + "amount": "800000", + "tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17", + "vout": 0, + "expiry": 1619 + }, + { + "reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=", + "state": "Spent", + "amount": "800000", + "tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836", + "vout": 0, + "expiry": 1623 + }, + { + "reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=", + "state": "Spent", + "amount": "800000", + "tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952", + "vout": 0, + "expiry": 1626 + }, + { + "reservation_id": "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=", + "state": "WaitForConfirmation", + "amount": "800000", + "tx_id": "", + "vout": 0, + "expiry": 1632 + } + ] + } + } + }, + { + "time_ms": 271, + "kind": "stdout", + "data": { + "lines": [ + "No confirmed reservations found \n" + ] + } + }, + { + "time_ms": 271, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/instantout/05_loop-instantout-cancel.json b/cmd/loop/testdata/sessions/instantout/05_loop-instantout-cancel.json new file mode 100644 index 000000000..b01dc87a3 --- /dev/null +++ b/cmd/loop/testdata/sessions/instantout/05_loop-instantout-cancel.json @@ -0,0 +1,155 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "instantout", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "swap canceled", + "duration": 229272887, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stdin", + "data": { + "text": "ALL\nn\n" + } + }, + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "request", + "message_type": "looprpc.ListReservationsRequest", + "payload": {} + } + }, + { + "time_ms": 188, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "response", + "message_type": "looprpc.ListReservationsResponse", + "payload": { + "reservations": [ + { + "reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=", + "state": "Spent", + "amount": "800000", + "tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17", + "vout": 0, + "expiry": 1619 + }, + { + "reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=", + "state": "Spent", + "amount": "800000", + "tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836", + "vout": 0, + "expiry": 1623 + }, + { + "reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=", + "state": "Spent", + "amount": "800000", + "tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952", + "vout": 0, + "expiry": 1626 + }, + { + "reservation_id": "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=", + "state": "Confirmed", + "amount": "800000", + "tx_id": "322d031e9ab55e0bcdac421c57d2d5d7705b80d01d3217a672aa1bf73edfc963", + "vout": 0, + "expiry": 1632 + } + ] + } + } + }, + { + "time_ms": 188, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOutQuote", + "event": "request", + "message_type": "looprpc.InstantOutQuoteRequest", + "payload": { + "amt": "800000", + "num_reservations": 0, + "reservation_ids": [ + "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=" + ] + } + } + }, + { + "time_ms": 188, + "kind": "stdout", + "data": { + "lines": [ + "Available reservations: \n", + "\n", + "Reservation 1: shortid fc0b25, amt 800000, expiry height 1632 \n", + "\n", + "Max amount to instant out: 800000\n", + "\n", + "Select reservations for instantout (e.g. '1,2,3')\n", + "Type 'ALL' to use all available reservations.\n" + ] + } + }, + { + "time_ms": 228, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOutQuote", + "event": "response", + "message_type": "looprpc.InstantOutQuoteResponse", + "payload": { + "service_fee_sat": "1600", + "sweep_fee_sat": "2773" + } + } + }, + { + "time_ms": 228, + "kind": "stdout", + "data": { + "lines": [ + "\n", + "Estimated on-chain fee: 2773 sat\n", + "Service fee: 1600 sat\n", + "\n", + "CONTINUE SWAP? (y/n): " + ] + } + }, + { + "time_ms": 229, + "kind": "stderr", + "data": { + "lines": [ + "[loop] swap canceled\n" + ] + } + }, + { + "time_ms": 229, + "kind": "exit", + "data": { + "run_error": "swap canceled" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/instantout/06_loop-instantout-invalid-selection.json b/cmd/loop/testdata/sessions/instantout/06_loop-instantout-invalid-selection.json new file mode 100644 index 000000000..788abeeab --- /dev/null +++ b/cmd/loop/testdata/sessions/instantout/06_loop-instantout-invalid-selection.json @@ -0,0 +1,113 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "instantout", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "invalid index 99", + "duration": 203873152, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 0, + "kind": "stdin", + "data": { + "text": "99\n" + } + }, + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "request", + "message_type": "looprpc.ListReservationsRequest", + "payload": {} + } + }, + { + "time_ms": 202, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "response", + "message_type": "looprpc.ListReservationsResponse", + "payload": { + "reservations": [ + { + "reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=", + "state": "Spent", + "amount": "800000", + "tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17", + "vout": 0, + "expiry": 1619 + }, + { + "reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=", + "state": "Spent", + "amount": "800000", + "tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836", + "vout": 0, + "expiry": 1623 + }, + { + "reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=", + "state": "Spent", + "amount": "800000", + "tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952", + "vout": 0, + "expiry": 1626 + }, + { + "reservation_id": "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=", + "state": "Confirmed", + "amount": "800000", + "tx_id": "322d031e9ab55e0bcdac421c57d2d5d7705b80d01d3217a672aa1bf73edfc963", + "vout": 0, + "expiry": 1632 + } + ] + } + } + }, + { + "time_ms": 202, + "kind": "stdout", + "data": { + "lines": [ + "Available reservations: \n", + "\n", + "Reservation 1: shortid fc0b25, amt 800000, expiry height 1632 \n", + "\n", + "Max amount to instant out: 800000\n", + "\n", + "Select reservations for instantout (e.g. '1,2,3')\n", + "Type 'ALL' to use all available reservations.\n" + ] + } + }, + { + "time_ms": 203, + "kind": "stderr", + "data": { + "lines": [ + "[loop] invalid index 99\n" + ] + } + }, + { + "time_ms": 203, + "kind": "exit", + "data": { + "run_error": "invalid index 99" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/instantout/07_loop-instantout-channel.json b/cmd/loop/testdata/sessions/instantout/07_loop-instantout-channel.json new file mode 100644 index 000000000..205ae43e8 --- /dev/null +++ b/cmd/loop/testdata/sessions/instantout/07_loop-instantout-channel.json @@ -0,0 +1,206 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "instantout", + "--network", + "regtest", + "--channel", + "125344325763072" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 1716733605, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stdin", + "data": { + "text": "ALL\ny\n" + } + }, + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "request", + "message_type": "looprpc.ListReservationsRequest", + "payload": {} + } + }, + { + "time_ms": 317, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "response", + "message_type": "looprpc.ListReservationsResponse", + "payload": { + "reservations": [ + { + "reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=", + "state": "Spent", + "amount": "800000", + "tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17", + "vout": 0, + "expiry": 1619 + }, + { + "reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=", + "state": "Spent", + "amount": "800000", + "tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836", + "vout": 0, + "expiry": 1623 + }, + { + "reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=", + "state": "Spent", + "amount": "800000", + "tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952", + "vout": 0, + "expiry": 1626 + }, + { + "reservation_id": "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=", + "state": "Confirmed", + "amount": "800000", + "tx_id": "322d031e9ab55e0bcdac421c57d2d5d7705b80d01d3217a672aa1bf73edfc963", + "vout": 0, + "expiry": 1632 + }, + { + "reservation_id": "0JK1m4RmC5iKV9dR/SlixC0IEGmobPMTlVgmk13thx8=", + "state": "Confirmed", + "amount": "800000", + "tx_id": "081eed1a78e87498ca29af380aaecc64e949c47aeb400375e662fdf630554ace", + "vout": 0, + "expiry": 1653 + } + ] + } + } + }, + { + "time_ms": 317, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOutQuote", + "event": "request", + "message_type": "looprpc.InstantOutQuoteRequest", + "payload": { + "amt": "1600000", + "num_reservations": 0, + "reservation_ids": [ + "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=", + "0JK1m4RmC5iKV9dR/SlixC0IEGmobPMTlVgmk13thx8=" + ] + } + } + }, + { + "time_ms": 317, + "kind": "stdout", + "data": { + "lines": [ + "Available reservations: \n", + "\n", + "Reservation 1: shortid fc0b25, amt 800000, expiry height 1632 \n", + "Reservation 2: shortid d092b5, amt 800000, expiry height 1653 \n", + "\n", + "Max amount to instant out: 1600000\n", + "\n", + "Select reservations for instantout (e.g. '1,2,3')\n", + "Type 'ALL' to use all available reservations.\n" + ] + } + }, + { + "time_ms": 359, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOutQuote", + "event": "response", + "message_type": "looprpc.InstantOutQuoteResponse", + "payload": { + "service_fee_sat": "3200", + "sweep_fee_sat": "4210" + } + } + }, + { + "time_ms": 359, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOut", + "event": "request", + "message_type": "looprpc.InstantOutRequest", + "payload": { + "reservation_ids": [ + "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=", + "0JK1m4RmC5iKV9dR/SlixC0IEGmobPMTlVgmk13thx8=" + ], + "outgoing_chan_set": [ + "125344325763072" + ], + "dest_addr": "" + } + } + }, + { + "time_ms": 360, + "kind": "stdout", + "data": { + "lines": [ + "\n", + "Estimated on-chain fee: 4210 sat\n", + "Service fee: 3200 sat\n", + "\n", + "CONTINUE SWAP? (y/n): Starting instant swap out\n" + ] + } + }, + { + "time_ms": 1711, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOut", + "event": "response", + "message_type": "looprpc.InstantOutResponse", + "payload": { + "instant_out_hash": "RfjHjKyop9NqqQzGXnyF1mnv9mPeWJXyz+5seKnKZ4k=", + "sweep_tx_id": "6f6f29961d2b4ab9f893850a0250f1f55fb93b144966833a5546986b171255a4", + "state": "PushPreimage" + } + } + }, + { + "time_ms": 1711, + "kind": "stdout", + "data": { + "lines": [ + "Instant out swap initiated with ID: 45f8c78caca8a7d36aa90cc65e7c85d669eff663de5895f2cfee6c78a9ca6789, State: PushPreimage \n" + ] + } + }, + { + "time_ms": 1711, + "kind": "stdout", + "data": { + "lines": [ + "Sweepless sweep tx id: 6f6f29961d2b4ab9f893850a0250f1f55fb93b144966833a5546986b171255a4 \n" + ] + } + }, + { + "time_ms": 1716, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/instantout/08_loop-instantout-select-index.json b/cmd/loop/testdata/sessions/instantout/08_loop-instantout-select-index.json new file mode 100644 index 000000000..9a6d103b3 --- /dev/null +++ b/cmd/loop/testdata/sessions/instantout/08_loop-instantout-select-index.json @@ -0,0 +1,199 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "instantout", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 1377442432, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "stdin", + "data": { + "text": "1\ny\n" + } + }, + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "request", + "message_type": "looprpc.ListReservationsRequest", + "payload": {} + } + }, + { + "time_ms": 178, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListReservations", + "event": "response", + "message_type": "looprpc.ListReservationsResponse", + "payload": { + "reservations": [ + { + "reservation_id": "s/RD6Lj/eXdmtFCtd/6AvrJQrP14h1YrblyZZMWITYc=", + "state": "Spent", + "amount": "800000", + "tx_id": "8b3b5e235ad24be42bc3802c6694566964c394df247b4ae1f2f4fd785e5acd17", + "vout": 0, + "expiry": 1619 + }, + { + "reservation_id": "Wl+CTsY+V2zUvkK5bs9WIttkvKLrKM8A1kpZ5VZ/MHs=", + "state": "Spent", + "amount": "800000", + "tx_id": "beb447976c6c1a93fdadee41280b912e62b305c7c30815dc172d58732b6c3836", + "vout": 0, + "expiry": 1623 + }, + { + "reservation_id": "Mu65fbhayEtRzougKLBnoeRN8f+tEM1+O9QuNvUIfbI=", + "state": "Spent", + "amount": "800000", + "tx_id": "5f898ca1b3852c62889f1113e07c73d0a3d721fd98d0a5433c6b7122cc706952", + "vout": 0, + "expiry": 1626 + }, + { + "reservation_id": "/AslVrUe/D6Eum+CZsg1x+EalEVACi6ICfc8rVURH3U=", + "state": "Spent", + "amount": "800000", + "tx_id": "322d031e9ab55e0bcdac421c57d2d5d7705b80d01d3217a672aa1bf73edfc963", + "vout": 0, + "expiry": 1632 + }, + { + "reservation_id": "0JK1m4RmC5iKV9dR/SlixC0IEGmobPMTlVgmk13thx8=", + "state": "Spent", + "amount": "800000", + "tx_id": "081eed1a78e87498ca29af380aaecc64e949c47aeb400375e662fdf630554ace", + "vout": 0, + "expiry": 1653 + }, + { + "reservation_id": "cSfKVONNmsK9+p4Uc5nc3ZtE+37uOODHeq1vprhh/x4=", + "state": "Confirmed", + "amount": "800000", + "tx_id": "46808a670542c1f3bf20ade2af77c254e3535378aab6ccd32c58de9976b0cf26", + "vout": 0, + "expiry": 1671 + } + ] + } + } + }, + { + "time_ms": 179, + "kind": "stdout", + "data": { + "lines": [ + "Available reservations: \n", + "\n", + "Reservation 1: shortid 7127ca, amt 800000, expiry height 1671 \n", + "\n", + "Max amount to instant out: 800000\n", + "\n", + "Select reservations for instantout (e.g. '1,2,3')\n", + "Type 'ALL' to use all available reservations.\n" + ] + } + }, + { + "time_ms": 179, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOutQuote", + "event": "request", + "message_type": "looprpc.InstantOutQuoteRequest", + "payload": { + "amt": "800000", + "num_reservations": 0, + "reservation_ids": [ + "cSfKVONNmsK9+p4Uc5nc3ZtE+37uOODHeq1vprhh/x4=" + ] + } + } + }, + { + "time_ms": 186, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOutQuote", + "event": "response", + "message_type": "looprpc.InstantOutQuoteResponse", + "payload": { + "service_fee_sat": "1600", + "sweep_fee_sat": "2772" + } + } + }, + { + "time_ms": 186, + "kind": "stdout", + "data": { + "lines": [ + "\n", + "Estimated on-chain fee: 2772 sat\n", + "Service fee: 1600 sat\n", + "\n", + "CONTINUE SWAP? (y/n): Starting instant swap out\n" + ] + } + }, + { + "time_ms": 186, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOut", + "event": "request", + "message_type": "looprpc.InstantOutRequest", + "payload": { + "reservation_ids": [ + "cSfKVONNmsK9+p4Uc5nc3ZtE+37uOODHeq1vprhh/x4=" + ], + "outgoing_chan_set": [], + "dest_addr": "" + } + } + }, + { + "time_ms": 1376, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/InstantOut", + "event": "response", + "message_type": "looprpc.InstantOutResponse", + "payload": { + "instant_out_hash": "0LvpOmXv+cqCnXgQ/lNOlbCvlI0AZkkkS1pFJk2n3us=", + "sweep_tx_id": "650e166a7d346f2b268c26c7fe075d8e2dc25655a747f546f5a039f01973d979", + "state": "WaitForSweeplessSweepConfirmed" + } + } + }, + { + "time_ms": 1377, + "kind": "stdout", + "data": { + "lines": [ + "Instant out swap initiated with ID: d0bbe93a65eff9ca829d7810fe534e95b0af948d006649244b5a45264da7deeb, State: WaitForSweeplessSweepConfirmed \n", + "Sweepless sweep tx id: 650e166a7d346f2b268c26c7fe075d8e2dc25655a747f546f5a039f01973d979 \n" + ] + } + }, + { + "time_ms": 1377, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/l402/01_loop-listauth.json b/cmd/loop/testdata/sessions/l402/01_loop-listauth.json new file mode 100644 index 000000000..81e0d87c1 --- /dev/null +++ b/cmd/loop/testdata/sessions/l402/01_loop-listauth.json @@ -0,0 +1,79 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "listauth", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 157631491, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetL402Tokens", + "event": "request", + "message_type": "looprpc.TokensRequest", + "payload": {} + } + }, + { + "time_ms": 157, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetL402Tokens", + "event": "response", + "message_type": "looprpc.TokensResponse", + "payload": { + "tokens": [ + { + "base_macaroon": "AgEEbHNhdAJCAAAkJPkWepsd1GgURnFywD7MZi7yoPZC/BjJHNrIs6en83L1T2GVJqpTIlrcw09RnFx5xHfjsNDjLRIekGAPVN/oAAIPc2VydmljZXM9bG9vcDowAAISbG9vcF9jYXBhYmlsaXRpZXM9AAAGIGIkKNbF9/LxQzqEKurnzP7lkGJbA2CsVbkwrKXyUTx/", + "payment_hash": "JCT5FnqbHdRoFEZxcsA+zGYu8qD2QvwYyRzayLOnp/M=", + "payment_preimage": "pSZZqEORw/4tPCPPS3sTDeh/2mq3EAzwqqWor/xwTmM=", + "amount_paid_msat": "1000000", + "routing_fee_paid_msat": "0", + "time_created": "1770076917", + "expired": false, + "storage_name": "/home/user/.loop/regtest/l402.token", + "id": "72f54f619526aa53225adcc34f519c5c79c477e3b0d0e32d121e90600f54dfe8" + } + ] + } + } + }, + { + "time_ms": 157, + "kind": "stdout", + "data": { + "lines": [ + "[\n", + " {\n", + " \"amount_paid_msat\": 1000000,\n", + " \"base_macaroon\": \"0201046c736174024200002424f9167a9b1dd46814467172c03ecc662ef2a0f642fc18c91cdac8b3a7a7f372f54f619526aa53225adcc34f519c5c79c477e3b0d0e32d121e90600f54dfe800020f73657276696365733d6c6f6f703a300002126c6f6f705f6361706162696c69746965733d00000620622428d6c5f7f2f1433a842aeae7ccfee590625b0360ac55b930aca5f2513c7f\",\n", + " \"expired\": false,\n", + " \"file_name\": \"/home/user/.loop/regtest/l402.token\",\n", + " \"id\": \"72f54f619526aa53225adcc34f519c5c79c477e3b0d0e32d121e90600f54dfe8\",\n", + " \"payment_hash\": \"2424f9167a9b1dd46814467172c03ecc662ef2a0f642fc18c91cdac8b3a7a7f3\",\n", + " \"payment_preimage\": \"a52659a84391c3fe2d3c23cf4b7b130de87fda6ab7100cf0aaa5a8affc704e63\",\n", + " \"routing_fee_paid_msat\": 0,\n", + " \"time_created\": \"2026-02-02T19:01:57-05:00\",\n", + " \"valid_until\": \"\"\n", + " }\n", + "]\n" + ] + } + }, + { + "time_ms": 157, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/l402/02_loop-fetchl402.json b/cmd/loop/testdata/sessions/l402/02_loop-fetchl402.json new file mode 100644 index 000000000..818003682 --- /dev/null +++ b/cmd/loop/testdata/sessions/l402/02_loop-fetchl402.json @@ -0,0 +1,52 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "fetchl402", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 177710196, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/FetchL402Token", + "event": "request", + "message_type": "looprpc.FetchL402TokenRequest", + "payload": {} + } + }, + { + "time_ms": 177, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/FetchL402Token", + "event": "response", + "message_type": "looprpc.FetchL402TokenResponse", + "payload": {} + } + }, + { + "time_ms": 177, + "kind": "stdout", + "data": { + "lines": [ + "{}\n" + ] + } + }, + { + "time_ms": 177, + "kind": "exit", + "data": {} + } + ] +} From 9e9b15c57fe8158e218f1ce7ba8cc6e42d364e09 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 3 Feb 2026 00:57:01 -0500 Subject: [PATCH 13/18] testdata: add loop in/out and quote sessions --- .../loopin/01_loop-in-invalid-amount.json | 36 +++++ .../02_loop-in-external-conf-target.json | 39 +++++ .../03_loop-in-route-hints-private.json | 39 +++++ .../loopin/04_loop-in-external-cancel.json | 101 +++++++++++++ .../05_loop-in-external-verbose-cancel.json | 104 ++++++++++++++ .../loopin/06_loop-in-external-force.json | 116 +++++++++++++++ .../sessions/loopout/01_loop-out.json | 127 ++++++++++++++++ .../loopout/02_loop-out-invalid-amount.json | 36 +++++ .../loopout/03_loop-out-addr-account.json | 40 ++++++ ...04_loop-out-invalid-account-addr-type.json | 40 ++++++ ...5_loop-out-amt-channel-maxfee-timeout.json | 135 ++++++++++++++++++ .../loopout/06_loop-out-addr-flag.json | 129 +++++++++++++++++ .../loopout/07_loop-out-addr-positional.json | 128 +++++++++++++++++ .../sessions/loopout/08_loop-out-account.json | 131 +++++++++++++++++ .../sessions/quote/01_loop-quote-out.json | 76 ++++++++++ .../sessions/quote/02_loop-quote-in-help.json | 92 ++++++++++++ .../quote/03_loop-quote-out-help.json | 55 +++++++ .../sessions/quote/04_loop-quote-in.json | 114 +++++++++++++++ .../quote/05_loop-quote-in-last-hop.json | 76 ++++++++++ .../quote/06_loop-quote-out-verbose.json | 93 ++++++++++++ .../quote/07_loop-quote-in-verbose.json | 81 +++++++++++ 21 files changed, 1788 insertions(+) create mode 100644 cmd/loop/testdata/sessions/loopin/01_loop-in-invalid-amount.json create mode 100644 cmd/loop/testdata/sessions/loopin/02_loop-in-external-conf-target.json create mode 100644 cmd/loop/testdata/sessions/loopin/03_loop-in-route-hints-private.json create mode 100644 cmd/loop/testdata/sessions/loopin/04_loop-in-external-cancel.json create mode 100644 cmd/loop/testdata/sessions/loopin/05_loop-in-external-verbose-cancel.json create mode 100644 cmd/loop/testdata/sessions/loopin/06_loop-in-external-force.json create mode 100644 cmd/loop/testdata/sessions/loopout/01_loop-out.json create mode 100644 cmd/loop/testdata/sessions/loopout/02_loop-out-invalid-amount.json create mode 100644 cmd/loop/testdata/sessions/loopout/03_loop-out-addr-account.json create mode 100644 cmd/loop/testdata/sessions/loopout/04_loop-out-invalid-account-addr-type.json create mode 100644 cmd/loop/testdata/sessions/loopout/05_loop-out-amt-channel-maxfee-timeout.json create mode 100644 cmd/loop/testdata/sessions/loopout/06_loop-out-addr-flag.json create mode 100644 cmd/loop/testdata/sessions/loopout/07_loop-out-addr-positional.json create mode 100644 cmd/loop/testdata/sessions/loopout/08_loop-out-account.json create mode 100644 cmd/loop/testdata/sessions/quote/01_loop-quote-out.json create mode 100644 cmd/loop/testdata/sessions/quote/02_loop-quote-in-help.json create mode 100644 cmd/loop/testdata/sessions/quote/03_loop-quote-out-help.json create mode 100644 cmd/loop/testdata/sessions/quote/04_loop-quote-in.json create mode 100644 cmd/loop/testdata/sessions/quote/05_loop-quote-in-last-hop.json create mode 100644 cmd/loop/testdata/sessions/quote/06_loop-quote-out-verbose.json create mode 100644 cmd/loop/testdata/sessions/quote/07_loop-quote-in-verbose.json diff --git a/cmd/loop/testdata/sessions/loopin/01_loop-in-invalid-amount.json b/cmd/loop/testdata/sessions/loopin/01_loop-in-invalid-amount.json new file mode 100644 index 000000000..439732f61 --- /dev/null +++ b/cmd/loop/testdata/sessions/loopin/01_loop-in-invalid-amount.json @@ -0,0 +1,36 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "in", + "--network", + "regtest", + "notanumber" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "invalid amt value \"notanumber\"", + "duration": 3964079, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "stderr", + "data": { + "lines": [ + "[loop] invalid amt value \"notanumber\"\n" + ] + } + }, + { + "time_ms": 3, + "kind": "exit", + "data": { + "run_error": "invalid amt value \"notanumber\"" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopin/02_loop-in-external-conf-target.json b/cmd/loop/testdata/sessions/loopin/02_loop-in-external-conf-target.json new file mode 100644 index 000000000..67a20d254 --- /dev/null +++ b/cmd/loop/testdata/sessions/loopin/02_loop-in-external-conf-target.json @@ -0,0 +1,39 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "in", + "--external", + "--conf_target", + "6", + "--network", + "regtest", + "10000" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "external and conf_target both set", + "duration": 13741983, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 13, + "kind": "stderr", + "data": { + "lines": [ + "[loop] external and conf_target both set\n" + ] + } + }, + { + "time_ms": 13, + "kind": "exit", + "data": { + "run_error": "external and conf_target both set" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopin/03_loop-in-route-hints-private.json b/cmd/loop/testdata/sessions/loopin/03_loop-in-route-hints-private.json new file mode 100644 index 000000000..e0aba7e06 --- /dev/null +++ b/cmd/loop/testdata/sessions/loopin/03_loop-in-route-hints-private.json @@ -0,0 +1,39 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "in", + "--route_hints", + "{}", + "--private", + "--network", + "regtest", + "10000" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "private and route_hints both set", + "duration": 2834558, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "stderr", + "data": { + "lines": [ + "[loop] private and route_hints both set\n" + ] + } + }, + { + "time_ms": 2, + "kind": "exit", + "data": { + "run_error": "private and route_hints both set" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopin/04_loop-in-external-cancel.json b/cmd/loop/testdata/sessions/loopin/04_loop-in-external-cancel.json new file mode 100644 index 000000000..33bfdaf8d --- /dev/null +++ b/cmd/loop/testdata/sessions/loopin/04_loop-in-external-cancel.json @@ -0,0 +1,101 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "in", + "--network", + "regtest", + "--external", + "--amt", + "50000", + "--last_hop", + "0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "swap canceled", + "duration": 539895736, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stdin", + "data": { + "text": "n\n" + } + }, + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "50000", + "conf_target": 0, + "external_htlc": true, + "swap_publication_deadline": "0", + "loop_in_last_hop": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 539, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1801", + "htlc_publish_fee_sat": "0", + "cltv_delta": 0, + "conf_target": 0, + "quoted_amt": "50000" + } + } + }, + { + "time_ms": 539, + "kind": "stderr", + "data": { + "lines": [ + "[loop] swap canceled\n" + ] + } + }, + { + "time_ms": 539, + "kind": "stdout", + "data": { + "lines": [ + "On-chain fee for external loop in is not included.\n", + "Sufficient fees will need to be paid when constructing the transaction in the external wallet.\n", + "\n", + "Send on-chain: 50000 sat\n", + "Receive off-chain: 48199 sat\n", + "Loop service fee: 1801 sat\n", + "\n", + "CONTINUE SWAP? (y/n): " + ] + } + }, + { + "time_ms": 539, + "kind": "exit", + "data": { + "run_error": "swap canceled" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopin/05_loop-in-external-verbose-cancel.json b/cmd/loop/testdata/sessions/loopin/05_loop-in-external-verbose-cancel.json new file mode 100644 index 000000000..e387b1bb9 --- /dev/null +++ b/cmd/loop/testdata/sessions/loopin/05_loop-in-external-verbose-cancel.json @@ -0,0 +1,104 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "in", + "--network", + "regtest", + "--external", + "--verbose", + "--amt", + "50000", + "--last_hop", + "0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "swap canceled", + "duration": 164102130, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 0, + "kind": "stdin", + "data": { + "text": "n\n" + } + }, + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "50000", + "conf_target": 0, + "external_htlc": true, + "swap_publication_deadline": "0", + "loop_in_last_hop": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 163, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1801", + "htlc_publish_fee_sat": "0", + "cltv_delta": 0, + "conf_target": 0, + "quoted_amt": "50000" + } + } + }, + { + "time_ms": 163, + "kind": "stdout", + "data": { + "lines": [ + "On-chain fee for external loop in is not included.\n", + "Sufficient fees will need to be paid when constructing the transaction in the external wallet.\n", + "\n", + "Send on-chain: 50000 sat\n", + "Receive off-chain: 48199 sat\n", + "Loop service fee: 1801 sat\n", + "\n", + "CLTV expiry delta: 0 block\n", + "\n", + "CONTINUE SWAP? (y/n): " + ] + } + }, + { + "time_ms": 164, + "kind": "stderr", + "data": { + "lines": [ + "[loop] swap canceled\n" + ] + } + }, + { + "time_ms": 164, + "kind": "exit", + "data": { + "run_error": "swap canceled" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopin/06_loop-in-external-force.json b/cmd/loop/testdata/sessions/loopin/06_loop-in-external-force.json new file mode 100644 index 000000000..ce757782c --- /dev/null +++ b/cmd/loop/testdata/sessions/loopin/06_loop-in-external-force.json @@ -0,0 +1,116 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "in", + "--network", + "regtest", + "--external", + "--amt", + "500000", + "--force" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 1067066878, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "500000", + "conf_target": 0, + "external_htlc": true, + "swap_publication_deadline": "0", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 561, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1824", + "htlc_publish_fee_sat": "0", + "cltv_delta": 0, + "conf_target": 0, + "quoted_amt": "500000" + } + } + }, + { + "time_ms": 561, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopIn", + "event": "request", + "message_type": "looprpc.LoopInRequest", + "payload": { + "amt": "500000", + "max_swap_fee": "1824", + "max_miner_fee": "0", + "last_hop": "", + "external_htlc": true, + "htlc_conf_target": 0, + "label": "", + "initiator": "loop-cli", + "route_hints": [], + "private": false + } + } + }, + { + "time_ms": 1065, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopIn", + "event": "response", + "message_type": "looprpc.SwapResponse", + "payload": { + "id": "2f46a232e0f584fadf2abc7e7aaeefab0176f21e0fba5344a10e21838ec390fe", + "id_bytes": "L0aiMuD1hPrfKrx+eq7vqwF28h4PulNEoQ4hg47DkP4=", + "htlc_address": "bcrt1pcred99xc9l2cxphneej4ph6d6mczc0442d6ulyh3a9ad389z2aaq4n775y", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1pcred99xc9l2cxphneej4ph6d6mczc0442d6ulyh3a9ad389z2aaq4n775y", + "server_message": "" + } + } + }, + { + "time_ms": 1066, + "kind": "stdout", + "data": { + "lines": [ + "Swap initiated\n", + "ID: 2f46a232e0f584fadf2abc7e7aaeefab0176f21e0fba5344a10e21838ec390fe\n", + "HTLC address (P2TR): bcrt1pcred99xc9l2cxphneej4ph6d6mczc0442d6ulyh3a9ad389z2aaq4n775y\n", + "\n", + "Run `loop monitor` to monitor progress.\n" + ] + } + }, + { + "time_ms": 1067, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopout/01_loop-out.json b/cmd/loop/testdata/sessions/loopout/01_loop-out.json new file mode 100644 index 000000000..93ccddc0f --- /dev/null +++ b/cmd/loop/testdata/sessions/loopout/01_loop-out.json @@ -0,0 +1,127 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "out", + "--network", + "regtest", + "--force", + "--fast", + "500000" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 228730686, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "500000", + "conf_target": 9, + "external_htlc": false, + "swap_publication_deadline": "1769407086", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 139, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "response", + "message_type": "looprpc.OutQuoteResponse", + "payload": { + "swap_fee_sat": "3875", + "prepay_amt_sat": "1337", + "htlc_sweep_fee_sat": "3793", + "swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "cltv_delta": 0, + "conf_target": 9, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 139, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOut", + "event": "request", + "message_type": "looprpc.LoopOutRequest", + "payload": { + "amt": "500000", + "dest": "", + "max_swap_routing_fee": "10010", + "max_prepay_routing_fee": "36", + "max_swap_fee": "3875", + "max_prepay_amt": "1337", + "max_miner_fee": "948250", + "loop_out_channel": "0", + "outgoing_chan_set": [], + "sweep_conf_target": 9, + "htlc_confirmations": 1, + "swap_publication_deadline": "1769407086", + "label": "", + "initiator": "loop-cli", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "is_external_addr": false, + "reservation_ids": [], + "payment_timeout": 0, + "asset_info": null, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 228, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOut", + "event": "response", + "message_type": "looprpc.SwapResponse", + "payload": { + "id": "46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b", + "id_bytes": "RrjbZBEKYAi9NrQjQ5y7oejnbAJk6KaPRC/CKjwrT2s=", + "htlc_address": "bcrt1pj90ffd4j9hc2a58xpl3q77c6pf8ckhqhv9w9m2529kqsk77f6lksrca9pv", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1pj90ffd4j9hc2a58xpl3q77c6pf8ckhqhv9w9m2529kqsk77f6lksrca9pv", + "server_message": "" + } + } + }, + { + "time_ms": 228, + "kind": "stdout", + "data": { + "lines": [ + "Swap initiated\n", + "ID: 46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\n", + "\n", + "Run `loop monitor` to monitor progress.\n" + ] + } + }, + { + "time_ms": 228, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopout/02_loop-out-invalid-amount.json b/cmd/loop/testdata/sessions/loopout/02_loop-out-invalid-amount.json new file mode 100644 index 000000000..2a66ec43a --- /dev/null +++ b/cmd/loop/testdata/sessions/loopout/02_loop-out-invalid-amount.json @@ -0,0 +1,36 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "out", + "--network", + "regtest", + "notanumber" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "invalid amt value \"notanumber\"", + "duration": 1215843, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stderr", + "data": { + "lines": [ + "[loop] invalid amt value \"notanumber\"\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": { + "run_error": "invalid amt value \"notanumber\"" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopout/03_loop-out-addr-account.json b/cmd/loop/testdata/sessions/loopout/03_loop-out-addr-account.json new file mode 100644 index 000000000..645dae2a8 --- /dev/null +++ b/cmd/loop/testdata/sessions/loopout/03_loop-out-addr-account.json @@ -0,0 +1,40 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "out", + "--addr", + "bcrt1qtest", + "--account", + "testacct", + "--network", + "regtest", + "50000" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "cannot set --addr and --account at the same time. Please specify only one source for a new address to sweep the loop amount to", + "duration": 1276860, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stderr", + "data": { + "lines": [ + "[loop] cannot set --addr and --account at the same time. Please specify only one source for a new address to sweep the loop amount to\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": { + "run_error": "cannot set --addr and --account at the same time. Please specify only one source for a new address to sweep the loop amount to" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopout/04_loop-out-invalid-account-addr-type.json b/cmd/loop/testdata/sessions/loopout/04_loop-out-invalid-account-addr-type.json new file mode 100644 index 000000000..fcf5551bd --- /dev/null +++ b/cmd/loop/testdata/sessions/loopout/04_loop-out-invalid-account-addr-type.json @@ -0,0 +1,40 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "out", + "--account", + "testacct", + "--account_addr_type", + "foo", + "--network", + "regtest", + "50000" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "unknown account address type", + "duration": 1107998, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stderr", + "data": { + "lines": [ + "[loop] unknown account address type\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": { + "run_error": "unknown account address type" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopout/05_loop-out-amt-channel-maxfee-timeout.json b/cmd/loop/testdata/sessions/loopout/05_loop-out-amt-channel-maxfee-timeout.json new file mode 100644 index 000000000..fa252f681 --- /dev/null +++ b/cmd/loop/testdata/sessions/loopout/05_loop-out-amt-channel-maxfee-timeout.json @@ -0,0 +1,135 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "out", + "--network", + "regtest", + "--amt", + "500000", + "--channel", + "125344325763072", + "--max_swap_routing_fee", + "1000", + "--payment_timeout", + "30s", + "--force" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 288562226, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "500000", + "conf_target": 9, + "external_htlc": false, + "swap_publication_deadline": "1769408886", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 161, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "response", + "message_type": "looprpc.OutQuoteResponse", + "payload": { + "swap_fee_sat": "3873", + "prepay_amt_sat": "1337", + "htlc_sweep_fee_sat": "3791", + "swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "cltv_delta": 0, + "conf_target": 9, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 161, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOut", + "event": "request", + "message_type": "looprpc.LoopOutRequest", + "payload": { + "amt": "500000", + "dest": "", + "max_swap_routing_fee": "1000", + "max_prepay_routing_fee": "36", + "max_swap_fee": "3873", + "max_prepay_amt": "1337", + "max_miner_fee": "947750", + "loop_out_channel": "0", + "outgoing_chan_set": [ + "125344325763072" + ], + "sweep_conf_target": 9, + "htlc_confirmations": 1, + "swap_publication_deadline": "1769408886", + "label": "", + "initiator": "loop-cli", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "is_external_addr": false, + "reservation_ids": [], + "payment_timeout": 30, + "asset_info": null, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 288, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOut", + "event": "response", + "message_type": "looprpc.SwapResponse", + "payload": { + "id": "a564cdea5bac2fa1e350da6e7096ddd3bc0256c157f9f4b9b71a1262742f4f0d", + "id_bytes": "pWTN6lusL6HjUNpucJbd07wCVsFX+fS5txoSYnQvTw0=", + "htlc_address": "bcrt1pg84j6m04s6ecvlgput74dh4kfppn0lxn7f9h5cyl7vylyu3l55ss2f9xyh", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1pg84j6m04s6ecvlgput74dh4kfppn0lxn7f9h5cyl7vylyu3l55ss2f9xyh", + "server_message": "" + } + } + }, + { + "time_ms": 288, + "kind": "stdout", + "data": { + "lines": [ + "Swap initiated\n", + "ID: a564cdea5bac2fa1e350da6e7096ddd3bc0256c157f9f4b9b71a1262742f4f0d\n", + "\n", + "Run `loop monitor` to monitor progress.\n" + ] + } + }, + { + "time_ms": 288, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopout/06_loop-out-addr-flag.json b/cmd/loop/testdata/sessions/loopout/06_loop-out-addr-flag.json new file mode 100644 index 000000000..3228c5a09 --- /dev/null +++ b/cmd/loop/testdata/sessions/loopout/06_loop-out-addr-flag.json @@ -0,0 +1,129 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "out", + "--network", + "regtest", + "--amt", + "500000", + "--addr", + "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw", + "--force" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 299201197, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "500000", + "conf_target": 9, + "external_htlc": false, + "swap_publication_deadline": "1769408886", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 230, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "response", + "message_type": "looprpc.OutQuoteResponse", + "payload": { + "swap_fee_sat": "3873", + "prepay_amt_sat": "1337", + "htlc_sweep_fee_sat": "3791", + "swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "cltv_delta": 0, + "conf_target": 9, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 230, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOut", + "event": "request", + "message_type": "looprpc.LoopOutRequest", + "payload": { + "amt": "500000", + "dest": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw", + "max_swap_routing_fee": "10010", + "max_prepay_routing_fee": "36", + "max_swap_fee": "3873", + "max_prepay_amt": "1337", + "max_miner_fee": "947750", + "loop_out_channel": "0", + "outgoing_chan_set": [], + "sweep_conf_target": 9, + "htlc_confirmations": 1, + "swap_publication_deadline": "1769408886", + "label": "", + "initiator": "loop-cli", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "is_external_addr": true, + "reservation_ids": [], + "payment_timeout": 0, + "asset_info": null, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 298, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOut", + "event": "response", + "message_type": "looprpc.SwapResponse", + "payload": { + "id": "67712e10d785636ec4b19c0c3d0f7b7612c1de5ccb2cb81df94e32ff13914fab", + "id_bytes": "Z3EuENeFY27EsZwMPQ97dhLB3lzLLLgd+U4y/xORT6s=", + "htlc_address": "bcrt1p0fn96tdw5ngu9cpww0w8q96tcps6w7jezaf0xnw0wa7e5d5gxv3st47amw", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1p0fn96tdw5ngu9cpww0w8q96tcps6w7jezaf0xnw0wa7e5d5gxv3st47amw", + "server_message": "" + } + } + }, + { + "time_ms": 298, + "kind": "stdout", + "data": { + "lines": [ + "Swap initiated\n", + "ID: 67712e10d785636ec4b19c0c3d0f7b7612c1de5ccb2cb81df94e32ff13914fab\n", + "\n", + "Run `loop monitor` to monitor progress.\n" + ] + } + }, + { + "time_ms": 299, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopout/07_loop-out-addr-positional.json b/cmd/loop/testdata/sessions/loopout/07_loop-out-addr-positional.json new file mode 100644 index 000000000..468b650c8 --- /dev/null +++ b/cmd/loop/testdata/sessions/loopout/07_loop-out-addr-positional.json @@ -0,0 +1,128 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "out", + "--network", + "regtest", + "--amt", + "500000", + "--force", + "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 290631176, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "500000", + "conf_target": 9, + "external_htlc": false, + "swap_publication_deadline": "1769408886", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 205, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "response", + "message_type": "looprpc.OutQuoteResponse", + "payload": { + "swap_fee_sat": "3873", + "prepay_amt_sat": "1337", + "htlc_sweep_fee_sat": "3791", + "swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "cltv_delta": 0, + "conf_target": 9, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 205, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOut", + "event": "request", + "message_type": "looprpc.LoopOutRequest", + "payload": { + "amt": "500000", + "dest": "", + "max_swap_routing_fee": "10010", + "max_prepay_routing_fee": "36", + "max_swap_fee": "3873", + "max_prepay_amt": "1337", + "max_miner_fee": "947750", + "loop_out_channel": "0", + "outgoing_chan_set": [], + "sweep_conf_target": 9, + "htlc_confirmations": 1, + "swap_publication_deadline": "1769408886", + "label": "", + "initiator": "loop-cli", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "is_external_addr": false, + "reservation_ids": [], + "payment_timeout": 0, + "asset_info": null, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 290, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOut", + "event": "response", + "message_type": "looprpc.SwapResponse", + "payload": { + "id": "aa7d0c558627aaa8af70503cd81d4679400cc9771063cb474c32c625d5f2a8f3", + "id_bytes": "qn0MVYYnqqivcFA82B1GeUAMyXcQY8tHTDLGJdXyqPM=", + "htlc_address": "bcrt1p9jhv4amddjazuyzs8xr2u2fzzqdt2s5tx95ffhd43avxvq7d0xxsykf5r5", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1p9jhv4amddjazuyzs8xr2u2fzzqdt2s5tx95ffhd43avxvq7d0xxsykf5r5", + "server_message": "" + } + } + }, + { + "time_ms": 290, + "kind": "stdout", + "data": { + "lines": [ + "Swap initiated\n", + "ID: aa7d0c558627aaa8af70503cd81d4679400cc9771063cb474c32c625d5f2a8f3\n", + "\n", + "Run `loop monitor` to monitor progress.\n" + ] + } + }, + { + "time_ms": 290, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/loopout/08_loop-out-account.json b/cmd/loop/testdata/sessions/loopout/08_loop-out-account.json new file mode 100644 index 000000000..36f65cef6 --- /dev/null +++ b/cmd/loop/testdata/sessions/loopout/08_loop-out-account.json @@ -0,0 +1,131 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "out", + "--network", + "regtest", + "--amt", + "500000", + "--account", + "default", + "--account_addr_type", + "p2tr", + "--force" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 466934696, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "500000", + "conf_target": 9, + "external_htlc": false, + "swap_publication_deadline": "1769408886", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 360, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "response", + "message_type": "looprpc.OutQuoteResponse", + "payload": { + "swap_fee_sat": "3873", + "prepay_amt_sat": "1337", + "htlc_sweep_fee_sat": "3791", + "swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "cltv_delta": 0, + "conf_target": 9, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 360, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOut", + "event": "request", + "message_type": "looprpc.LoopOutRequest", + "payload": { + "amt": "500000", + "dest": "", + "max_swap_routing_fee": "10010", + "max_prepay_routing_fee": "36", + "max_swap_fee": "3873", + "max_prepay_amt": "1337", + "max_miner_fee": "947750", + "loop_out_channel": "0", + "outgoing_chan_set": [], + "sweep_conf_target": 9, + "htlc_confirmations": 1, + "swap_publication_deadline": "1769408886", + "label": "", + "initiator": "loop-cli", + "account": "default", + "account_addr_type": "TAPROOT_PUBKEY", + "is_external_addr": false, + "reservation_ids": [], + "payment_timeout": 0, + "asset_info": null, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 462, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOut", + "event": "response", + "message_type": "looprpc.SwapResponse", + "payload": { + "id": "04c1ef677f170f50ad3f221f49a779b99a464907bde2e0003a7b1dd6402d1419", + "id_bytes": "BMHvZ38XD1CtPyIfSad5uZpGSQe94uAAOnsd1kAtFBk=", + "htlc_address": "bcrt1p8lvg7l4zkdu0l4pe74h96240ndmp3demreml3zeqy28dvgq7j95q23zqcy", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1p8lvg7l4zkdu0l4pe74h96240ndmp3demreml3zeqy28dvgq7j95q23zqcy", + "server_message": "" + } + } + }, + { + "time_ms": 462, + "kind": "stdout", + "data": { + "lines": [ + "Swap initiated\n", + "ID: 04c1ef677f170f50ad3f221f49a779b99a464907bde2e0003a7b1dd6402d1419\n", + "\n", + "Run `loop monitor` to monitor progress.\n" + ] + } + }, + { + "time_ms": 466, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/quote/01_loop-quote-out.json b/cmd/loop/testdata/sessions/quote/01_loop-quote-out.json new file mode 100644 index 000000000..73d63e473 --- /dev/null +++ b/cmd/loop/testdata/sessions/quote/01_loop-quote-out.json @@ -0,0 +1,76 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "quote", + "out", + "--network", + "regtest", + "500000" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 159166421, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "500000", + "conf_target": 9, + "external_htlc": false, + "swap_publication_deadline": "1769408886", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 158, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "response", + "message_type": "looprpc.OutQuoteResponse", + "payload": { + "swap_fee_sat": "3875", + "prepay_amt_sat": "1337", + "htlc_sweep_fee_sat": "3793", + "swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "cltv_delta": 0, + "conf_target": 9, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 158, + "kind": "stdout", + "data": { + "lines": [ + "Send off-chain: 500000 sat\n", + "Receive on-chain: 492332 sat\n", + "Estimated total fee: 7668 sat\n" + ] + } + }, + { + "time_ms": 159, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/quote/02_loop-quote-in-help.json b/cmd/loop/testdata/sessions/quote/02_loop-quote-in-help.json new file mode 100644 index 000000000..b7aa504d4 --- /dev/null +++ b/cmd/loop/testdata/sessions/quote/02_loop-quote-in-help.json @@ -0,0 +1,92 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "quote", + "in", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 1248128, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + "NAME:\n", + " loop quote in - get a quote for the cost of a loop in swap\n", + "\n", + "USAGE:\n", + " loop quote in amt\n", + "\n", + "DESCRIPTION:\n", + " Allows to determine the cost of a swap up front.Either specify an amount or deposit outpoints.\n", + "\n", + "OPTIONS:\n" + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + " --last_hop string the pubkey of the last hop to use for the quote\n", + " --conf_target uint the target number of blocks the on-chain htlc broadcast by the swap client should confirm within (default: 0)\n", + " --verbose, -v show expanded details (default: false)\n", + " --private generates and passes routehints. Should be used if the connected node is only reachable via private channels (default: false)\n", + " --route_hints string [ --route_hints string ] " + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + " route hints that can each be individually used to assist in reaching the invoice's destination\n", + " --deposit_outpoint string [ --deposit_outpoint string ] one or more static address deposit outpoints to quote for. Deposit outpoints are not to be used in combination with an amount. Eachadditional outpoint can be added by specifying --deposit_outpoint tx_id:idx\n" + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + " --help, -h " + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + " " + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + "show help\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/quote/03_loop-quote-out-help.json b/cmd/loop/testdata/sessions/quote/03_loop-quote-out-help.json new file mode 100644 index 000000000..ea579620d --- /dev/null +++ b/cmd/loop/testdata/sessions/quote/03_loop-quote-out-help.json @@ -0,0 +1,55 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "quote", + "out", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 1687027, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + "NAME:" + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + "\n", + " loop quote out - get a quote for the cost of a loop out swap\n", + "\n", + "USAGE:\n", + " loop quote out amt\n", + "\n", + "DESCRIPTION:\n", + " Allows to determine the cost of a swap up front\n", + "\n", + "OPTIONS:\n", + " --conf_target uint the number of blocks from the swap initiation height that the on-chain HTLC should be swept within in a Loop Out (default: 9)\n", + " --fast Indicate you want to swap immediately, paying potentially a higher fee. If not set the swap server might choose to wait up to 30 minutes before publishing the swap HTLC on-chain, to save on chain fees. Not setting this flag might result in a lower swap fee. (default: false)\n", + " --verbose, -v show expanded details (default: false)\n", + " --help, -h show help\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/quote/04_loop-quote-in.json b/cmd/loop/testdata/sessions/quote/04_loop-quote-in.json new file mode 100644 index 000000000..59c05adb5 --- /dev/null +++ b/cmd/loop/testdata/sessions/quote/04_loop-quote-in.json @@ -0,0 +1,114 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "quote", + "in", + "--network", + "regtest", + "--deposit_outpoint", + "edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 622954327, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "UNKNOWN_STATE", + "outpoints": [ + "edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0" + ] + } + } + }, + { + "time_ms": 165, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "aCYqEEyewyXea+w3uOMb2HW70vXwuc4togzwvWNvxEg=", + "state": "DEPOSITED", + "outpoint": "edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0", + "value": "500000", + "confirmation_height": "125", + "blocks_until_expiry": "14394", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 165, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "0", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [ + "edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0" + ], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 622, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1825", + "htlc_publish_fee_sat": "0", + "cltv_delta": 0, + "conf_target": 0, + "quoted_amt": "500000" + } + } + }, + { + "time_ms": 622, + "kind": "stdout", + "data": { + "lines": [ + "Previously deposited on-chain: 500000 sat\n", + "Receive off-chain: 498175 sat\n", + "Estimated total fee: 1825 sat\n" + ] + } + }, + { + "time_ms": 622, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/quote/05_loop-quote-in-last-hop.json b/cmd/loop/testdata/sessions/quote/05_loop-quote-in-last-hop.json new file mode 100644 index 000000000..331b319d9 --- /dev/null +++ b/cmd/loop/testdata/sessions/quote/05_loop-quote-in-last-hop.json @@ -0,0 +1,76 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "quote", + "in", + "--network", + "regtest", + "--last_hop", + "0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7", + "50000" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 226964878, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "50000", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 226, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1801", + "htlc_publish_fee_sat": "3873", + "cltv_delta": 0, + "conf_target": 6, + "quoted_amt": "50000" + } + } + }, + { + "time_ms": 226, + "kind": "stdout", + "data": { + "lines": [ + "Send on-chain: 50000 sat\n", + "Receive off-chain: 44326 sat\n", + "Estimated total fee: 5674 sat\n" + ] + } + }, + { + "time_ms": 226, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/quote/06_loop-quote-out-verbose.json b/cmd/loop/testdata/sessions/quote/06_loop-quote-out-verbose.json new file mode 100644 index 000000000..3a16805ab --- /dev/null +++ b/cmd/loop/testdata/sessions/quote/06_loop-quote-out-verbose.json @@ -0,0 +1,93 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "quote", + "out", + "--network", + "regtest", + "--verbose", + "50000" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 273326957, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "50000", + "conf_target": 9, + "external_htlc": false, + "swap_publication_deadline": "1769408886", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 272, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/LoopOutQuote", + "event": "response", + "message_type": "looprpc.OutQuoteResponse", + "payload": { + "swap_fee_sat": "3828", + "prepay_amt_sat": "1337", + "htlc_sweep_fee_sat": "3791", + "swap_payment_dest": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "cltv_delta": 0, + "conf_target": 9, + "asset_rfq_info": null + } + } + }, + { + "time_ms": 273, + "kind": "stdout", + "data": { + "lines": [ + "Send off-chain: 50000 sat\n", + "Receive on-chain: 42381 sat\n", + "\n", + "Estimated on-chain fee: 3791 sat\n", + "Loop service fee: 3828 sat\n", + "Estimated total fee: 7619 sat\n", + "\n" + ] + } + }, + { + "time_ms": 273, + "kind": "stdout", + "data": { + "lines": [ + "No show penalty (prepay): 1337 sat\n", + "Conf target: 9 block\n", + "CLTV expiry delta: 0 block\n", + "Publication deadline: 2026-01-26 01:28:06 -0500 EST\n" + ] + } + }, + { + "time_ms": 273, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/quote/07_loop-quote-in-verbose.json b/cmd/loop/testdata/sessions/quote/07_loop-quote-in-verbose.json new file mode 100644 index 000000000..7972e2747 --- /dev/null +++ b/cmd/loop/testdata/sessions/quote/07_loop-quote-in-verbose.json @@ -0,0 +1,81 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "quote", + "in", + "--network", + "regtest", + "--verbose", + "50000" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 380680881, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "50000", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 379, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1800", + "htlc_publish_fee_sat": "3871", + "cltv_delta": 0, + "conf_target": 6, + "quoted_amt": "50000" + } + } + }, + { + "time_ms": 380, + "kind": "stdout", + "data": { + "lines": [ + "Send on-chain: 50000 sat\n", + "Receive off-chain: 44329 sat\n", + "\n", + "Estimated on-chain fee: 3871 sat\n", + "Loop service fee: 1800 sat\n", + "Estimated total fee: 5671 sat\n", + "\n", + "Conf target: 6 block\n", + "CLTV expiry delta: 0 block\n" + ] + } + }, + { + "time_ms": 380, + "kind": "exit", + "data": {} + } + ] +} From da95f1f5fa60ddc2044ed4d4adf0838f1bc600a3 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 3 Feb 2026 00:57:32 -0500 Subject: [PATCH 14/18] testdata: add liquidity sessions --- .../sessions/liquidity/01_loop-getparams.json | 108 ++++++++++ .../liquidity/02_loop-setparams-no-flags.json | 92 +++++++++ .../03_loop-setparams-feepercent.json | 123 ++++++++++++ ...04_loop-setparams-feepercent-conflict.json | 96 +++++++++ .../05_loop-setrule-missing-threshold.json | 93 +++++++++ .../liquidity/06_loop-setrule-no-args.json | 35 ++++ .../liquidity/07_loop-suggestswaps.json | 54 +++++ .../liquidity/08_loop-setrule-success.json | 142 +++++++++++++ .../09_loop-suggestswaps-success.json | 60 ++++++ .../liquidity/10_loop-setrule-incoming.json | 150 ++++++++++++++ .../11_loop-setrule-outgoing-type-in.json | 179 +++++++++++++++++ .../liquidity/12_loop-setrule-clear.json | 149 ++++++++++++++ .../13_loop-setparams-many-flags-error.json | 185 +++++++++++++++++ ...oop-setparams-many-flags-error-minamt.json | 187 ++++++++++++++++++ .../15_loop-setparams-many-flags.json | 176 +++++++++++++++++ .../liquidity/16_loop-setparams-destaddr.json | 145 ++++++++++++++ .../liquidity/17_loop-setparams-account.json | 147 ++++++++++++++ .../18_loop-setparams-includeallpeers.json | 142 +++++++++++++ .../liquidity/19_loop-setrule-type-out.json | 152 ++++++++++++++ 19 files changed, 2415 insertions(+) create mode 100644 cmd/loop/testdata/sessions/liquidity/01_loop-getparams.json create mode 100644 cmd/loop/testdata/sessions/liquidity/02_loop-setparams-no-flags.json create mode 100644 cmd/loop/testdata/sessions/liquidity/03_loop-setparams-feepercent.json create mode 100644 cmd/loop/testdata/sessions/liquidity/04_loop-setparams-feepercent-conflict.json create mode 100644 cmd/loop/testdata/sessions/liquidity/05_loop-setrule-missing-threshold.json create mode 100644 cmd/loop/testdata/sessions/liquidity/06_loop-setrule-no-args.json create mode 100644 cmd/loop/testdata/sessions/liquidity/07_loop-suggestswaps.json create mode 100644 cmd/loop/testdata/sessions/liquidity/08_loop-setrule-success.json create mode 100644 cmd/loop/testdata/sessions/liquidity/09_loop-suggestswaps-success.json create mode 100644 cmd/loop/testdata/sessions/liquidity/10_loop-setrule-incoming.json create mode 100644 cmd/loop/testdata/sessions/liquidity/11_loop-setrule-outgoing-type-in.json create mode 100644 cmd/loop/testdata/sessions/liquidity/12_loop-setrule-clear.json create mode 100644 cmd/loop/testdata/sessions/liquidity/13_loop-setparams-many-flags-error.json create mode 100644 cmd/loop/testdata/sessions/liquidity/14_loop-setparams-many-flags-error-minamt.json create mode 100644 cmd/loop/testdata/sessions/liquidity/15_loop-setparams-many-flags.json create mode 100644 cmd/loop/testdata/sessions/liquidity/16_loop-setparams-destaddr.json create mode 100644 cmd/loop/testdata/sessions/liquidity/17_loop-setparams-account.json create mode 100644 cmd/loop/testdata/sessions/liquidity/18_loop-setparams-includeallpeers.json create mode 100644 cmd/loop/testdata/sessions/liquidity/19_loop-setrule-type-out.json diff --git a/cmd/loop/testdata/sessions/liquidity/01_loop-getparams.json b/cmd/loop/testdata/sessions/liquidity/01_loop-getparams.json new file mode 100644 index 000000000..8d1e3b378 --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/01_loop-getparams.json @@ -0,0 +1,108 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "getparams", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 213058408, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 212, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 212, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"account\": \"\",\n", + " \"account_addr_type\": \"ADDRESS_TYPE_UNKNOWN\",\n", + " \"auto_max_in_flight\": \"1\",\n", + " \"autoloop\": false,\n", + " \"autoloop_budget_last_refresh\": \"1770076824\",\n", + " \"autoloop_budget_refresh_period_sec\": \"604800\",\n", + " \"autoloop_budget_sat\": \"335544\",\n", + " \"autoloop_budget_start_sec\": \"0\",\n", + " \"autoloop_dest_address\": \"\",\n", + " \"easy_asset_params\": {},\n", + " \"easy_autoloop\": false,\n", + " \"easy_autoloop_excluded_peers\": [],\n", + " \"easy_autoloop_local_target_sat\": \"0\",\n", + " \"failure_backoff_sec\": \"86400\",\n", + " \"fast_swap_publication\": true,\n", + " \"fee_ppm\": \"20000\",\n", + " \"htlc_conf_target\": 6,\n", + " \"max_miner_fee_sat\": \"0\",\n", + " \"max_prepay_routing_fee_ppm\": \"0\",\n", + " \"max_prepay_sat\": \"0\",\n", + " \"max_routing_fee_ppm\": \"0\",\n", + " \"max_swap_amount\": \"0\",\n", + " \"max_swap_fee_ppm\": \"0\",\n", + " \"min_swap_amount\": \"0\",\n", + " \"rules\": [],\n", + " \"sweep_conf_target\": 100,\n", + " \"sweep_fee_rate_sat_per_vbyte\": \"0\"\n", + "}\n" + ] + } + }, + { + "time_ms": 213, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/02_loop-setparams-no-flags.json b/cmd/loop/testdata/sessions/liquidity/02_loop-setparams-no-flags.json new file mode 100644 index 000000000..00ab0b4fb --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/02_loop-setparams-no-flags.json @@ -0,0 +1,92 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setparams", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "at least one flag required to set params", + "duration": 150177446, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 5, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 149, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 150, + "kind": "stderr", + "data": { + "lines": [ + "[loop] at least one flag required to set params\n" + ] + } + }, + { + "time_ms": 150, + "kind": "exit", + "data": { + "run_error": "at least one flag required to set params" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/03_loop-setparams-feepercent.json b/cmd/loop/testdata/sessions/liquidity/03_loop-setparams-feepercent.json new file mode 100644 index 000000000..95e99cd59 --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/03_loop-setparams-feepercent.json @@ -0,0 +1,123 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setparams", + "--network", + "regtest", + "--feepercent", + "2" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 249608692, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 6, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 188, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 189, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "request", + "message_type": "looprpc.SetLiquidityParamsRequest", + "payload": { + "parameters": { + "rules": [], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + } + }, + { + "time_ms": 248, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "response", + "message_type": "looprpc.SetLiquidityParamsResponse", + "payload": {} + } + }, + { + "time_ms": 249, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/04_loop-setparams-feepercent-conflict.json b/cmd/loop/testdata/sessions/liquidity/04_loop-setparams-feepercent-conflict.json new file mode 100644 index 000000000..38fed2a71 --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/04_loop-setparams-feepercent-conflict.json @@ -0,0 +1,96 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setparams", + "--network", + "regtest", + "--feepercent", + "2", + "--sweeplimit", + "1" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "feepercent cannot be set with specific fee category flags", + "duration": 161206474, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 4, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 159, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 161, + "kind": "stderr", + "data": { + "lines": [ + "[loop] feepercent cannot be set with specific fee category flags\n" + ] + } + }, + { + "time_ms": 161, + "kind": "exit", + "data": { + "run_error": "feepercent cannot be set with specific fee category flags" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/05_loop-setrule-missing-threshold.json b/cmd/loop/testdata/sessions/liquidity/05_loop-setrule-missing-threshold.json new file mode 100644 index 000000000..813ddec33 --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/05_loop-setrule-missing-threshold.json @@ -0,0 +1,93 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setrule", + "--network", + "regtest", + "034fa8802fdc9f8c137e8f2cce2e4d30e590a1314e16bd060ac7d772dc698ff4bc" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "provide at least one flag to set rules or use the --clear flag to remove rules", + "duration": 143258905, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 142, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 143, + "kind": "stderr", + "data": { + "lines": [ + "[loop] provide at least one flag to set rules or use the --clear flag to remove rules\n" + ] + } + }, + { + "time_ms": 143, + "kind": "exit", + "data": { + "run_error": "provide at least one flag to set rules or use the --clear flag to remove rules" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/06_loop-setrule-no-args.json b/cmd/loop/testdata/sessions/liquidity/06_loop-setrule-no-args.json new file mode 100644 index 000000000..51314cba3 --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/06_loop-setrule-no-args.json @@ -0,0 +1,35 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setrule", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "please set a channel id or peer pubkey for the rule update", + "duration": 1924009, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stderr", + "data": { + "lines": [ + "[loop] please set a channel id or peer pubkey for the rule update\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": { + "run_error": "please set a channel id or peer pubkey for the rule update" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/07_loop-suggestswaps.json b/cmd/loop/testdata/sessions/liquidity/07_loop-suggestswaps.json new file mode 100644 index 000000000..7cb4c514d --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/07_loop-suggestswaps.json @@ -0,0 +1,54 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "suggestswaps", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "rpc error: code = FailedPrecondition desc = no rules set for autoloop", + "duration": 178932401, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SuggestSwaps", + "event": "request", + "message_type": "looprpc.SuggestSwapsRequest", + "payload": {} + } + }, + { + "time_ms": 178, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SuggestSwaps", + "event": "error", + "error": "rpc error: code = FailedPrecondition desc = no rules set for autoloop" + } + }, + { + "time_ms": 178, + "kind": "stderr", + "data": { + "lines": [ + "[loop] rpc error: code = FailedPrecondition desc = no rules set for autoloop\n" + ] + } + }, + { + "time_ms": 178, + "kind": "exit", + "data": { + "run_error": "rpc error: code = FailedPrecondition desc = no rules set for autoloop" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/08_loop-setrule-success.json b/cmd/loop/testdata/sessions/liquidity/08_loop-setrule-success.json new file mode 100644 index 000000000..08331612e --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/08_loop-setrule-success.json @@ -0,0 +1,142 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setrule", + "--network", + "regtest", + "--incoming_threshold", + "10", + "034fa8802fdc9f8c137e8f2cce2e4d30e590a1314e16bd060ac7d772dc698ff4bc" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 130661787, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 124, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 124, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "request", + "message_type": "looprpc.SetLiquidityParamsRequest", + "payload": { + "parameters": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + } + }, + { + "time_ms": 130, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "response", + "message_type": "looprpc.SetLiquidityParamsResponse", + "payload": {} + } + }, + { + "time_ms": 130, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/09_loop-suggestswaps-success.json b/cmd/loop/testdata/sessions/liquidity/09_loop-suggestswaps-success.json new file mode 100644 index 000000000..7d41dfabc --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/09_loop-suggestswaps-success.json @@ -0,0 +1,60 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "suggestswaps", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 137916583, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SuggestSwaps", + "event": "request", + "message_type": "looprpc.SuggestSwapsRequest", + "payload": {} + } + }, + { + "time_ms": 137, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SuggestSwaps", + "event": "response", + "message_type": "looprpc.SuggestSwapsResponse", + "payload": { + "loop_out": [], + "loop_in": [], + "disqualified": [] + } + } + }, + { + "time_ms": 137, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"disqualified\": [],\n", + " \"loop_in\": [],\n", + " \"loop_out\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 137, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/10_loop-setrule-incoming.json b/cmd/loop/testdata/sessions/liquidity/10_loop-setrule-incoming.json new file mode 100644 index 000000000..340fd514f --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/10_loop-setrule-incoming.json @@ -0,0 +1,150 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setrule", + "--network", + "regtest", + "--incoming_threshold", + "40", + "125344325763072" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 204743867, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 135, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 135, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "request", + "message_type": "looprpc.SetLiquidityParamsRequest", + "payload": { + "parameters": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + }, + { + "channel_id": "125344325763072", + "swap_type": "LOOP_OUT", + "pubkey": "", + "type": "THRESHOLD", + "incoming_threshold": 40, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + } + }, + { + "time_ms": 204, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "response", + "message_type": "looprpc.SetLiquidityParamsResponse", + "payload": {} + } + }, + { + "time_ms": 204, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/11_loop-setrule-outgoing-type-in.json b/cmd/loop/testdata/sessions/liquidity/11_loop-setrule-outgoing-type-in.json new file mode 100644 index 000000000..88d24c24b --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/11_loop-setrule-outgoing-type-in.json @@ -0,0 +1,179 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setrule", + "--network", + "regtest", + "--type", + "in", + "--outgoing_threshold", + "30", + "124244814135296" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "rpc error: code = Unknown desc = channel level rules not supported for loop in swaps, only peer-level rules allowed", + "duration": 212373344, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 168, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "125344325763072", + "swap_type": "LOOP_OUT", + "pubkey": "", + "type": "THRESHOLD", + "incoming_threshold": 40, + "outgoing_threshold": 0 + }, + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 168, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "request", + "message_type": "looprpc.SetLiquidityParamsRequest", + "payload": { + "parameters": { + "rules": [ + { + "channel_id": "125344325763072", + "swap_type": "LOOP_OUT", + "pubkey": "", + "type": "THRESHOLD", + "incoming_threshold": 40, + "outgoing_threshold": 0 + }, + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + }, + { + "channel_id": "124244814135296", + "swap_type": "LOOP_IN", + "pubkey": "", + "type": "THRESHOLD", + "incoming_threshold": 0, + "outgoing_threshold": 30 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + } + }, + { + "time_ms": 212, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "error", + "error": "rpc error: code = Unknown desc = channel level rules not supported for loop in swaps, only peer-level rules allowed" + } + }, + { + "time_ms": 212, + "kind": "stderr", + "data": { + "lines": [ + "[loop] rpc error: code = Unknown desc = channel level rules not supported for loop in swaps, only peer-level rules allowed\n" + ] + } + }, + { + "time_ms": 212, + "kind": "exit", + "data": { + "run_error": "rpc error: code = Unknown desc = channel level rules not supported for loop in swaps, only peer-level rules allowed" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/12_loop-setrule-clear.json b/cmd/loop/testdata/sessions/liquidity/12_loop-setrule-clear.json new file mode 100644 index 000000000..3114845c7 --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/12_loop-setrule-clear.json @@ -0,0 +1,149 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setrule", + "--network", + "regtest", + "--clear", + "125344325763072" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 232507711, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 150, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "125344325763072", + "swap_type": "LOOP_OUT", + "pubkey": "", + "type": "THRESHOLD", + "incoming_threshold": 40, + "outgoing_threshold": 0 + }, + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 150, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "request", + "message_type": "looprpc.SetLiquidityParamsRequest", + "payload": { + "parameters": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + } + }, + { + "time_ms": 231, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "response", + "message_type": "looprpc.SetLiquidityParamsResponse", + "payload": {} + } + }, + { + "time_ms": 232, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/13_loop-setparams-many-flags-error.json b/cmd/loop/testdata/sessions/liquidity/13_loop-setparams-many-flags-error.json new file mode 100644 index 000000000..f6acf1b1f --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/13_loop-setparams-many-flags-error.json @@ -0,0 +1,185 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setparams", + "--network", + "regtest", + "--maxswapfee", + "0.1", + "--maxroutingfee", + "0.1", + "--maxprepayfee", + "0.1", + "--maxprepay", + "1000", + "--maxminer", + "1000", + "--sweepconf", + "10", + "--failurebackoff", + "60", + "--autoloop", + "--autobudget", + "1000", + "--autobudgetrefreshperiod", + "1h", + "--autoinflight", + "2", + "--minamt", + "1000", + "--maxamt", + "200000", + "--htlc_conf", + "2", + "--easyautoloop", + "--localbalancesat", + "1000000", + "--easyautoloop_excludepeer", + "0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7", + "--fast" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "rpc error: code = Unknown desc = sweep fee rate limit must be > 1 sat/vByte", + "duration": 325173938, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 276, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 276, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "request", + "message_type": "looprpc.SetLiquidityParamsRequest", + "payload": { + "parameters": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "0", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "1000", + "max_routing_fee_ppm": "1000", + "max_prepay_routing_fee_ppm": "1000", + "max_prepay_sat": "1000", + "max_miner_fee_sat": "1000", + "sweep_conf_target": 10, + "failure_backoff_sec": "60", + "autoloop": true, + "autoloop_budget_sat": "1000", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "2", + "min_swap_amount": "1000", + "max_swap_amount": "200000", + "htlc_conf_target": 2, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "3600", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": true, + "easy_autoloop_local_target_sat": "1000000", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [ + "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3" + ] + } + } + } + }, + { + "time_ms": 324, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "error", + "error": "rpc error: code = Unknown desc = sweep fee rate limit must be > 1 sat/vByte" + } + }, + { + "time_ms": 325, + "kind": "stderr", + "data": { + "lines": [ + "[loop] rpc error: code = Unknown desc = sweep fee rate limit must be > 1 sat/vByte\n" + ] + } + }, + { + "time_ms": 325, + "kind": "exit", + "data": { + "run_error": "rpc error: code = Unknown desc = sweep fee rate limit must be > 1 sat/vByte" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/14_loop-setparams-many-flags-error-minamt.json b/cmd/loop/testdata/sessions/liquidity/14_loop-setparams-many-flags-error-minamt.json new file mode 100644 index 000000000..84e68bc16 --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/14_loop-setparams-many-flags-error-minamt.json @@ -0,0 +1,187 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setparams", + "--network", + "regtest", + "--sweeplimit", + "2", + "--maxswapfee", + "0.1", + "--maxroutingfee", + "0.1", + "--maxprepayfee", + "0.1", + "--maxprepay", + "1000", + "--maxminer", + "1000", + "--sweepconf", + "10", + "--failurebackoff", + "60", + "--autoloop", + "--autobudget", + "1000", + "--autobudgetrefreshperiod", + "1h", + "--autoinflight", + "2", + "--minamt", + "1000", + "--maxamt", + "200000", + "--htlc_conf", + "2", + "--easyautoloop", + "--localbalancesat", + "1000000", + "--easyautoloop_excludepeer", + "0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7", + "--fast" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "rpc error: code = Unknown desc = minimum swap amount is less than server minimum", + "duration": 273083406, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 191, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 191, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "request", + "message_type": "looprpc.SetLiquidityParamsRequest", + "payload": { + "parameters": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "0", + "sweep_fee_rate_sat_per_vbyte": "2", + "max_swap_fee_ppm": "1000", + "max_routing_fee_ppm": "1000", + "max_prepay_routing_fee_ppm": "1000", + "max_prepay_sat": "1000", + "max_miner_fee_sat": "1000", + "sweep_conf_target": 10, + "failure_backoff_sec": "60", + "autoloop": true, + "autoloop_budget_sat": "1000", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "2", + "min_swap_amount": "1000", + "max_swap_amount": "200000", + "htlc_conf_target": 2, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "3600", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": true, + "easy_autoloop_local_target_sat": "1000000", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [ + "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3" + ] + } + } + } + }, + { + "time_ms": 272, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "error", + "error": "rpc error: code = Unknown desc = minimum swap amount is less than server minimum" + } + }, + { + "time_ms": 272, + "kind": "stderr", + "data": { + "lines": [ + "[loop] rpc error: code = Unknown desc = minimum swap amount is less than server minimum\n" + ] + } + }, + { + "time_ms": 273, + "kind": "exit", + "data": { + "run_error": "rpc error: code = Unknown desc = minimum swap amount is less than server minimum" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/15_loop-setparams-many-flags.json b/cmd/loop/testdata/sessions/liquidity/15_loop-setparams-many-flags.json new file mode 100644 index 000000000..31141db2b --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/15_loop-setparams-many-flags.json @@ -0,0 +1,176 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setparams", + "--network", + "regtest", + "--sweeplimit", + "2", + "--maxswapfee", + "0.1", + "--maxroutingfee", + "0.1", + "--maxprepayfee", + "0.1", + "--maxprepay", + "1000", + "--maxminer", + "1000", + "--sweepconf", + "10", + "--failurebackoff", + "60", + "--autoloop", + "--autobudget", + "1000", + "--autobudgetrefreshperiod", + "1h", + "--autoinflight", + "2", + "--minamt", + "500000", + "--maxamt", + "1000000", + "--htlc_conf", + "2", + "--easyautoloop", + "--localbalancesat", + "1000000", + "--easyautoloop_excludepeer", + "0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7", + "--fast" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 184771728, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 160, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "20000", + "sweep_fee_rate_sat_per_vbyte": "0", + "max_swap_fee_ppm": "0", + "max_routing_fee_ppm": "0", + "max_prepay_routing_fee_ppm": "0", + "max_prepay_sat": "0", + "max_miner_fee_sat": "0", + "sweep_conf_target": 100, + "failure_backoff_sec": "86400", + "autoloop": false, + "autoloop_budget_sat": "335544", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "1", + "min_swap_amount": "0", + "max_swap_amount": "0", + "htlc_conf_target": 6, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "604800", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": false, + "easy_autoloop_local_target_sat": "0", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 161, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "request", + "message_type": "looprpc.SetLiquidityParamsRequest", + "payload": { + "parameters": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "0", + "sweep_fee_rate_sat_per_vbyte": "2", + "max_swap_fee_ppm": "1000", + "max_routing_fee_ppm": "1000", + "max_prepay_routing_fee_ppm": "1000", + "max_prepay_sat": "1000", + "max_miner_fee_sat": "1000", + "sweep_conf_target": 10, + "failure_backoff_sec": "60", + "autoloop": true, + "autoloop_budget_sat": "1000", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "2", + "min_swap_amount": "500000", + "max_swap_amount": "1000000", + "htlc_conf_target": 2, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "3600", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": true, + "easy_autoloop_local_target_sat": "1000000", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [ + "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3" + ] + } + } + } + }, + { + "time_ms": 182, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "response", + "message_type": "looprpc.SetLiquidityParamsResponse", + "payload": {} + } + }, + { + "time_ms": 184, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/16_loop-setparams-destaddr.json b/cmd/loop/testdata/sessions/liquidity/16_loop-setparams-destaddr.json new file mode 100644 index 000000000..f99b78b1f --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/16_loop-setparams-destaddr.json @@ -0,0 +1,145 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setparams", + "--network", + "regtest", + "--destaddr", + "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 235471909, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 223, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "0", + "sweep_fee_rate_sat_per_vbyte": "2", + "max_swap_fee_ppm": "1000", + "max_routing_fee_ppm": "1000", + "max_prepay_routing_fee_ppm": "1000", + "max_prepay_sat": "1000", + "max_miner_fee_sat": "1000", + "sweep_conf_target": 10, + "failure_backoff_sec": "60", + "autoloop": true, + "autoloop_budget_sat": "1000", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "2", + "min_swap_amount": "500000", + "max_swap_amount": "1000000", + "htlc_conf_target": 2, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "3600", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": true, + "easy_autoloop_local_target_sat": "1000000", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [ + "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3" + ] + } + } + }, + { + "time_ms": 224, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "request", + "message_type": "looprpc.SetLiquidityParamsRequest", + "payload": { + "parameters": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "0", + "sweep_fee_rate_sat_per_vbyte": "2", + "max_swap_fee_ppm": "1000", + "max_routing_fee_ppm": "1000", + "max_prepay_routing_fee_ppm": "1000", + "max_prepay_sat": "1000", + "max_miner_fee_sat": "1000", + "sweep_conf_target": 10, + "failure_backoff_sec": "60", + "autoloop": true, + "autoloop_budget_sat": "1000", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "2", + "min_swap_amount": "500000", + "max_swap_amount": "1000000", + "htlc_conf_target": 2, + "autoloop_dest_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw", + "autoloop_budget_refresh_period_sec": "3600", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": true, + "easy_autoloop_local_target_sat": "1000000", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [ + "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3" + ] + } + } + } + }, + { + "time_ms": 234, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "response", + "message_type": "looprpc.SetLiquidityParamsResponse", + "payload": {} + } + }, + { + "time_ms": 235, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/17_loop-setparams-account.json b/cmd/loop/testdata/sessions/liquidity/17_loop-setparams-account.json new file mode 100644 index 000000000..f91d0cea0 --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/17_loop-setparams-account.json @@ -0,0 +1,147 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setparams", + "--network", + "regtest", + "--account", + "default", + "--account_addr_type", + "p2tr" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 264493138, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 249, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "0", + "sweep_fee_rate_sat_per_vbyte": "2", + "max_swap_fee_ppm": "1000", + "max_routing_fee_ppm": "1000", + "max_prepay_routing_fee_ppm": "1000", + "max_prepay_sat": "1000", + "max_miner_fee_sat": "1000", + "sweep_conf_target": 10, + "failure_backoff_sec": "60", + "autoloop": true, + "autoloop_budget_sat": "1000", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "2", + "min_swap_amount": "500000", + "max_swap_amount": "1000000", + "htlc_conf_target": 2, + "autoloop_dest_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw", + "autoloop_budget_refresh_period_sec": "3600", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": true, + "easy_autoloop_local_target_sat": "1000000", + "account": "", + "account_addr_type": "ADDRESS_TYPE_UNKNOWN", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [ + "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3" + ] + } + } + }, + { + "time_ms": 250, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "request", + "message_type": "looprpc.SetLiquidityParamsRequest", + "payload": { + "parameters": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "0", + "sweep_fee_rate_sat_per_vbyte": "2", + "max_swap_fee_ppm": "1000", + "max_routing_fee_ppm": "1000", + "max_prepay_routing_fee_ppm": "1000", + "max_prepay_sat": "1000", + "max_miner_fee_sat": "1000", + "sweep_conf_target": 10, + "failure_backoff_sec": "60", + "autoloop": true, + "autoloop_budget_sat": "1000", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "2", + "min_swap_amount": "500000", + "max_swap_amount": "1000000", + "htlc_conf_target": 2, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "3600", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": true, + "easy_autoloop_local_target_sat": "1000000", + "account": "default", + "account_addr_type": "TAPROOT_PUBKEY", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [ + "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3" + ] + } + } + } + }, + { + "time_ms": 264, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "response", + "message_type": "looprpc.SetLiquidityParamsResponse", + "payload": {} + } + }, + { + "time_ms": 264, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/18_loop-setparams-includeallpeers.json b/cmd/loop/testdata/sessions/liquidity/18_loop-setparams-includeallpeers.json new file mode 100644 index 000000000..911d68ef3 --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/18_loop-setparams-includeallpeers.json @@ -0,0 +1,142 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setparams", + "--network", + "regtest", + "--easyatutoloop_includeallpeers" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 304215261, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 263, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "0", + "sweep_fee_rate_sat_per_vbyte": "2", + "max_swap_fee_ppm": "1000", + "max_routing_fee_ppm": "1000", + "max_prepay_routing_fee_ppm": "1000", + "max_prepay_sat": "1000", + "max_miner_fee_sat": "1000", + "sweep_conf_target": 10, + "failure_backoff_sec": "60", + "autoloop": true, + "autoloop_budget_sat": "1000", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "2", + "min_swap_amount": "500000", + "max_swap_amount": "1000000", + "htlc_conf_target": 2, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "3600", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": true, + "easy_autoloop_local_target_sat": "1000000", + "account": "default", + "account_addr_type": "TAPROOT_PUBKEY", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [ + "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3" + ] + } + } + }, + { + "time_ms": 271, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "request", + "message_type": "looprpc.SetLiquidityParamsRequest", + "payload": { + "parameters": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "0", + "sweep_fee_rate_sat_per_vbyte": "2", + "max_swap_fee_ppm": "1000", + "max_routing_fee_ppm": "1000", + "max_prepay_routing_fee_ppm": "1000", + "max_prepay_sat": "1000", + "max_miner_fee_sat": "1000", + "sweep_conf_target": 10, + "failure_backoff_sec": "60", + "autoloop": true, + "autoloop_budget_sat": "1000", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "2", + "min_swap_amount": "500000", + "max_swap_amount": "1000000", + "htlc_conf_target": 2, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "3600", + "autoloop_budget_last_refresh": "1770076824", + "easy_autoloop": true, + "easy_autoloop_local_target_sat": "1000000", + "account": "default", + "account_addr_type": "TAPROOT_PUBKEY", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + } + }, + { + "time_ms": 303, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "response", + "message_type": "looprpc.SetLiquidityParamsResponse", + "payload": {} + } + }, + { + "time_ms": 304, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/liquidity/19_loop-setrule-type-out.json b/cmd/loop/testdata/sessions/liquidity/19_loop-setrule-type-out.json new file mode 100644 index 000000000..ea0a37827 --- /dev/null +++ b/cmd/loop/testdata/sessions/liquidity/19_loop-setrule-type-out.json @@ -0,0 +1,152 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "setrule", + "--network", + "regtest", + "--type", + "out", + "--incoming_threshold", + "20", + "125344325763072" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 195037901, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "request", + "message_type": "looprpc.GetLiquidityParamsRequest", + "payload": {} + } + }, + { + "time_ms": 137, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLiquidityParams", + "event": "response", + "message_type": "looprpc.LiquidityParameters", + "payload": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "0", + "sweep_fee_rate_sat_per_vbyte": "2", + "max_swap_fee_ppm": "1000", + "max_routing_fee_ppm": "1000", + "max_prepay_routing_fee_ppm": "1000", + "max_prepay_sat": "1000", + "max_miner_fee_sat": "1000", + "sweep_conf_target": 10, + "failure_backoff_sec": "60", + "autoloop": true, + "autoloop_budget_sat": "1000", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "2", + "min_swap_amount": "500000", + "max_swap_amount": "1000000", + "htlc_conf_target": 2, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "3600", + "autoloop_budget_last_refresh": "1770092424", + "easy_autoloop": true, + "easy_autoloop_local_target_sat": "1000000", + "account": "default", + "account_addr_type": "TAPROOT_PUBKEY", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + }, + { + "time_ms": 137, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "request", + "message_type": "looprpc.SetLiquidityParamsRequest", + "payload": { + "parameters": { + "rules": [ + { + "channel_id": "0", + "swap_type": "LOOP_OUT", + "pubkey": "A0+ogC/cn4wTfo8szi5NMOWQoTFOFr0GCsfXctxpj/S8", + "type": "THRESHOLD", + "incoming_threshold": 10, + "outgoing_threshold": 0 + }, + { + "channel_id": "125344325763072", + "swap_type": "LOOP_OUT", + "pubkey": "", + "type": "THRESHOLD", + "incoming_threshold": 20, + "outgoing_threshold": 0 + } + ], + "fee_ppm": "0", + "sweep_fee_rate_sat_per_vbyte": "2", + "max_swap_fee_ppm": "1000", + "max_routing_fee_ppm": "1000", + "max_prepay_routing_fee_ppm": "1000", + "max_prepay_sat": "1000", + "max_miner_fee_sat": "1000", + "sweep_conf_target": 10, + "failure_backoff_sec": "60", + "autoloop": true, + "autoloop_budget_sat": "1000", + "autoloop_budget_start_sec": "0", + "auto_max_in_flight": "2", + "min_swap_amount": "500000", + "max_swap_amount": "1000000", + "htlc_conf_target": 2, + "autoloop_dest_address": "", + "autoloop_budget_refresh_period_sec": "3600", + "autoloop_budget_last_refresh": "1770092424", + "easy_autoloop": true, + "easy_autoloop_local_target_sat": "1000000", + "account": "default", + "account_addr_type": "TAPROOT_PUBKEY", + "easy_asset_params": {}, + "fast_swap_publication": true, + "easy_autoloop_excluded_peers": [] + } + } + } + }, + { + "time_ms": 194, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SetLiquidityParams", + "event": "response", + "message_type": "looprpc.SetLiquidityParamsResponse", + "payload": {} + } + }, + { + "time_ms": 195, + "kind": "exit", + "data": {} + } + ] +} From a6c6b5ae59821791340f9816503f16146ac42b38 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Tue, 3 Feb 2026 00:58:09 -0500 Subject: [PATCH 15/18] testdata: add static address and swaps sessions --- ...01_loop-static-listdeposits-deposited.json | 62 +++++ ..._loop-static-listdeposits-withdrawing.json | 82 +++++++ ...03_loop-static-listdeposits-withdrawn.json | 136 +++++++++++ ...4_loop-static-listdeposits-looping_in.json | 62 +++++ ...05_loop-static-listdeposits-looped_in.json | 82 +++++++ ...-listdeposits-publish_expired_deposit.json | 62 +++++ ...tatic-listdeposits-sweep_htlc_timeout.json | 62 +++++ ...tatic-listdeposits-htlc_timeout_swept.json | 62 +++++ ...ic-listdeposits-wait_for_expiry_sweep.json | 62 +++++ .../10_loop-static-listdeposits-expired.json | 62 +++++ .../11_loop-static-listdeposits-failed.json | 172 ++++++++++++++ .../static-loop-in/01_loop-static-new.json | 78 +++++++ .../static-loop-in/04_loop-static.json | 56 +++++ .../05_loop-static-listunspent.json | 72 ++++++ .../static-loop-in/06_loop-static-l.json | 72 ++++++ .../07_loop-static-listdeposits.json | 58 +++++ .../08_loop-static-listunspent.json | 72 ++++++ .../09_loop-static-listunspent.json | 72 ++++++ .../10_loop-static-listdeposits.json | 78 +++++++ .../11_loop-static-summary.json | 73 ++++++ .../static-loop-in/12_loop-static-in.json | 69 ++++++ .../static-loop-in/13_loop-static-in.json | 103 +++++++++ .../static-loop-in/14_loop-static-in.json | 162 +++++++++++++ .../static-loop-in/15_loop-static-in.json | 214 ++++++++++++++++++ ...16_loop-static-in-duplicate-outpoints.json | 75 ++++++ .../17_loop-static-in-positional-low-amt.json | 108 +++++++++ ...-static-in-positional-payment-timeout.json | 195 ++++++++++++++++ .../19_loop-static-in-all-cancel.json | 132 +++++++++++ .../01_loop-static-withdraw-no-selection.json | 36 +++ .../02_loop-static-listwithdrawals.json | 95 ++++++++ .../static/03_loop-static-listswaps.json | 57 +++++ .../04_loop-static-withdraw-invalid-utxo.json | 38 ++++ .../static/05_loop-static-withdraw-all.json | 66 ++++++ ...06_loop-static-withdraw-utxo-destaddr.json | 75 ++++++ .../sessions/swaps/01_loop-listswaps.json | 111 +++++++++ .../sessions/swaps/02_loop-swapinfo.json | 93 ++++++++ .../sessions/swaps/03_loop-abandonswap.json | 45 ++++ ...04_loop-listswaps-conflicting-filters.json | 37 +++ .../swaps/05_loop-swapinfo-invalid-id.json | 36 +++ .../swaps/06_loop-abandonswap-invalid-id.json | 37 +++ .../07_loop-listswaps-loop-out-filtered.json | 82 +++++++ .../swaps/08_loop-listswaps-loop-in-only.json | 70 ++++++ .../09_loop-swapinfo-id-flag-parse-error.json | 39 ++++ .../swaps/10_loop-swapinfo-id-flag.json | 37 +++ .../swaps/11_loop-abandonswap-success.json | 57 +++++ 45 files changed, 3606 insertions(+) create mode 100644 cmd/loop/testdata/sessions/static-filters/01_loop-static-listdeposits-deposited.json create mode 100644 cmd/loop/testdata/sessions/static-filters/02_loop-static-listdeposits-withdrawing.json create mode 100644 cmd/loop/testdata/sessions/static-filters/03_loop-static-listdeposits-withdrawn.json create mode 100644 cmd/loop/testdata/sessions/static-filters/04_loop-static-listdeposits-looping_in.json create mode 100644 cmd/loop/testdata/sessions/static-filters/05_loop-static-listdeposits-looped_in.json create mode 100644 cmd/loop/testdata/sessions/static-filters/06_loop-static-listdeposits-publish_expired_deposit.json create mode 100644 cmd/loop/testdata/sessions/static-filters/07_loop-static-listdeposits-sweep_htlc_timeout.json create mode 100644 cmd/loop/testdata/sessions/static-filters/08_loop-static-listdeposits-htlc_timeout_swept.json create mode 100644 cmd/loop/testdata/sessions/static-filters/09_loop-static-listdeposits-wait_for_expiry_sweep.json create mode 100644 cmd/loop/testdata/sessions/static-filters/10_loop-static-listdeposits-expired.json create mode 100644 cmd/loop/testdata/sessions/static-filters/11_loop-static-listdeposits-failed.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/01_loop-static-new.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/04_loop-static.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/05_loop-static-listunspent.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/06_loop-static-l.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/07_loop-static-listdeposits.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/08_loop-static-listunspent.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/09_loop-static-listunspent.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/10_loop-static-listdeposits.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/11_loop-static-summary.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/12_loop-static-in.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/13_loop-static-in.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/14_loop-static-in.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/15_loop-static-in.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/16_loop-static-in-duplicate-outpoints.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/17_loop-static-in-positional-low-amt.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/18_loop-static-in-positional-payment-timeout.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/19_loop-static-in-all-cancel.json create mode 100644 cmd/loop/testdata/sessions/static/01_loop-static-withdraw-no-selection.json create mode 100644 cmd/loop/testdata/sessions/static/02_loop-static-listwithdrawals.json create mode 100644 cmd/loop/testdata/sessions/static/03_loop-static-listswaps.json create mode 100644 cmd/loop/testdata/sessions/static/04_loop-static-withdraw-invalid-utxo.json create mode 100644 cmd/loop/testdata/sessions/static/05_loop-static-withdraw-all.json create mode 100644 cmd/loop/testdata/sessions/static/06_loop-static-withdraw-utxo-destaddr.json create mode 100644 cmd/loop/testdata/sessions/swaps/01_loop-listswaps.json create mode 100644 cmd/loop/testdata/sessions/swaps/02_loop-swapinfo.json create mode 100644 cmd/loop/testdata/sessions/swaps/03_loop-abandonswap.json create mode 100644 cmd/loop/testdata/sessions/swaps/04_loop-listswaps-conflicting-filters.json create mode 100644 cmd/loop/testdata/sessions/swaps/05_loop-swapinfo-invalid-id.json create mode 100644 cmd/loop/testdata/sessions/swaps/06_loop-abandonswap-invalid-id.json create mode 100644 cmd/loop/testdata/sessions/swaps/07_loop-listswaps-loop-out-filtered.json create mode 100644 cmd/loop/testdata/sessions/swaps/08_loop-listswaps-loop-in-only.json create mode 100644 cmd/loop/testdata/sessions/swaps/09_loop-swapinfo-id-flag-parse-error.json create mode 100644 cmd/loop/testdata/sessions/swaps/10_loop-swapinfo-id-flag.json create mode 100644 cmd/loop/testdata/sessions/swaps/11_loop-abandonswap-success.json diff --git a/cmd/loop/testdata/sessions/static-filters/01_loop-static-listdeposits-deposited.json b/cmd/loop/testdata/sessions/static-filters/01_loop-static-listdeposits-deposited.json new file mode 100644 index 000000000..449076a34 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/01_loop-static-listdeposits-deposited.json @@ -0,0 +1,62 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listdeposits", + "--network", + "regtest", + "--filter", + "deposited" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 304503708, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "DEPOSITED", + "outpoints": [] + } + } + }, + { + "time_ms": 303, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [] + } + } + }, + { + "time_ms": 304, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 304, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-filters/02_loop-static-listdeposits-withdrawing.json b/cmd/loop/testdata/sessions/static-filters/02_loop-static-listdeposits-withdrawing.json new file mode 100644 index 000000000..99186eb26 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/02_loop-static-listdeposits-withdrawing.json @@ -0,0 +1,82 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listdeposits", + "--network", + "regtest", + "--filter", + "withdrawing" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 210052149, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "WITHDRAWING", + "outpoints": [] + } + } + }, + { + "time_ms": 209, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "u38FDfC3w+H+YQEOEK1F4w3fes0wH6bgWi3bgltcLvs=", + "state": "WITHDRAWING", + "outpoint": "56cd081a3a6eadf25b7d3fe0b61207389352ed69a622d2ec28c5d669bf6a5313:0", + "value": "500000", + "confirmation_height": "160", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 209, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": [\n", + " {\n", + " \"blocks_until_expiry\": \"14395\",\n", + " \"confirmation_height\": \"160\",\n", + " \"id\": \"bb7f050df0b7c3e1fe61010e10ad45e30ddf7acd301fa6e05a2ddb825b5c2efb\",\n", + " \"outpoint\": \"56cd081a3a6eadf25b7d3fe0b61207389352ed69a622d2ec28c5d669bf6a5313:0\",\n", + " \"state\": \"WITHDRAWING\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 210, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-filters/03_loop-static-listdeposits-withdrawn.json b/cmd/loop/testdata/sessions/static-filters/03_loop-static-listdeposits-withdrawn.json new file mode 100644 index 000000000..79c024585 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/03_loop-static-listdeposits-withdrawn.json @@ -0,0 +1,136 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listdeposits", + "--network", + "regtest", + "--filter", + "withdrawn" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 271219039, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "WITHDRAWN", + "outpoints": [] + } + } + }, + { + "time_ms": 269, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "aCYqEEyewyXea+w3uOMb2HW70vXwuc4togzwvWNvxEg=", + "state": "WITHDRAWN", + "outpoint": "edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0", + "value": "500000", + "confirmation_height": "125", + "blocks_until_expiry": "14360", + "swap_hash": "" + }, + { + "id": "hrXizflpTI5zmOQq/eEJdm180hQiA5Bbpj+9DrE3DvM=", + "state": "WITHDRAWN", + "outpoint": "bb358e4f73ae97c4e2d99c6d64e852bba7cf56e13105b05d1200b8ae1796665e:0", + "value": "500000", + "confirmation_height": "127", + "blocks_until_expiry": "14362", + "swap_hash": "" + }, + { + "id": "bCkPdTbqUJeUav/6xqaZBqJtd1gj67rO3+by1pwHReQ=", + "state": "WITHDRAWN", + "outpoint": "5eaa7dd7a291665393eddf5dece91feef901f22665933cce7a0732a9b81c3001:0", + "value": "500000", + "confirmation_height": "128", + "blocks_until_expiry": "14363", + "swap_hash": "" + }, + { + "id": "AYK02JWxxGcpCue1xsQv92sqQiWAepQhHJcxcNWog+s=", + "state": "WITHDRAWN", + "outpoint": "7e6360d6e6a394cfd096adf0bfe1275c5a83541eb573e90e463a78dc715f8894:0", + "value": "500000", + "confirmation_height": "142", + "blocks_until_expiry": "14377", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 269, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": [\n", + " {\n", + " \"blocks_until_expiry\": \"14360\",\n", + " \"confirmation_height\": \"125\",\n", + " \"id\": \"68262a104c9ec325de6bec37b8e31bd875bbd2f5f0b9ce2da20cf0bd636fc448\",\n", + " \"outpoint\": \"edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0\",\n", + " \"state\": \"WITHDRAWN\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " },\n", + " {\n", + " \"blocks_until_expiry\": \"14362\",\n", + " \"confirmation_height\": \"127\",\n", + " \"id\": \"86b5e2cdf9694c8e7398e42afde109766d7cd2142203905ba63fbd0eb1370ef3\",\n", + " \"outpoint\": \"bb358e4f73ae97c4e2d99c6d64e852bba7cf56e13105b05d1200b8ae1796665e:0\",\n", + " \"state\": \"WITHDRAWN\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " },\n", + " {\n", + " \"blocks_until_expiry\": \"14363\",\n", + " \"confirmation_height\": \"128\",\n", + " \"id\": \"6c290f7536ea5097946afffac6a69906a26d775823ebbacedfe6f2d69c0745e4\",\n", + " \"outpoint\": \"5eaa7dd7a291665393eddf5dece91feef901f22665933cce7a0732a9b81c3001:0\",\n", + " \"state\": \"WITHDRAWN\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " },\n", + " {\n", + " \"blocks_until_expiry\": \"14377\",\n", + " \"confirmation_height\": \"142\",\n", + " \"id\": \"0182b4d895b1c467290ae7b5c6c42ff76b2a4225807a94211c973170d5a883eb\",\n", + " \"outpoint\": \"7e6360d6e6a394cfd096adf0bfe1275c5a83541eb573e90e463a78dc715f8894:0\",\n", + " \"state\": \"WITHDRAWN\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 271, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-filters/04_loop-static-listdeposits-looping_in.json b/cmd/loop/testdata/sessions/static-filters/04_loop-static-listdeposits-looping_in.json new file mode 100644 index 000000000..d99b07ac0 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/04_loop-static-listdeposits-looping_in.json @@ -0,0 +1,62 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listdeposits", + "--network", + "regtest", + "--filter", + "looping_in" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 268850770, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "LOOPING_IN", + "outpoints": [] + } + } + }, + { + "time_ms": 268, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [] + } + } + }, + { + "time_ms": 268, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 268, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-filters/05_loop-static-listdeposits-looped_in.json b/cmd/loop/testdata/sessions/static-filters/05_loop-static-listdeposits-looped_in.json new file mode 100644 index 000000000..e935ed5eb --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/05_loop-static-listdeposits-looped_in.json @@ -0,0 +1,82 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listdeposits", + "--network", + "regtest", + "--filter", + "looped_in" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 169458759, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "LOOPED_IN", + "outpoints": [] + } + } + }, + { + "time_ms": 168, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "j71tovlF3ikFqn+pOGB0TZOH00ZEhDYOluRnpR3jvJ0=", + "state": "LOOPED_IN", + "outpoint": "9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0", + "value": "500000", + "confirmation_height": "148", + "blocks_until_expiry": "14383", + "swap_hash": "hDAjN0JANkGTlqt5ZN14uFsaSBqfHbc9tc3e5XwkQ+c=" + } + ] + } + } + }, + { + "time_ms": 168, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": [\n", + " {\n", + " \"blocks_until_expiry\": \"14383\",\n", + " \"confirmation_height\": \"148\",\n", + " \"id\": \"8fbd6da2f945de2905aa7fa93860744d9387d3464484360e96e467a51de3bc9d\",\n", + " \"outpoint\": \"9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0\",\n", + " \"state\": \"LOOPED_IN\",\n", + " \"swap_hash\": \"84302337424036419396ab7964dd78b85b1a481a9f1db73db5cddee57c2443e7\",\n", + " \"value\": \"500000\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 169, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-filters/06_loop-static-listdeposits-publish_expired_deposit.json b/cmd/loop/testdata/sessions/static-filters/06_loop-static-listdeposits-publish_expired_deposit.json new file mode 100644 index 000000000..507965b85 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/06_loop-static-listdeposits-publish_expired_deposit.json @@ -0,0 +1,62 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listdeposits", + "--network", + "regtest", + "--filter", + "publish_expired_deposit" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 230057326, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "PUBLISH_EXPIRED", + "outpoints": [] + } + } + }, + { + "time_ms": 229, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [] + } + } + }, + { + "time_ms": 230, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 230, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-filters/07_loop-static-listdeposits-sweep_htlc_timeout.json b/cmd/loop/testdata/sessions/static-filters/07_loop-static-listdeposits-sweep_htlc_timeout.json new file mode 100644 index 000000000..8afa09bd1 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/07_loop-static-listdeposits-sweep_htlc_timeout.json @@ -0,0 +1,62 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listdeposits", + "--network", + "regtest", + "--filter", + "sweep_htlc_timeout" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 224559305, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "SWEEP_HTLC_TIMEOUT", + "outpoints": [] + } + } + }, + { + "time_ms": 223, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [] + } + } + }, + { + "time_ms": 224, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 224, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-filters/08_loop-static-listdeposits-htlc_timeout_swept.json b/cmd/loop/testdata/sessions/static-filters/08_loop-static-listdeposits-htlc_timeout_swept.json new file mode 100644 index 000000000..c7de35580 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/08_loop-static-listdeposits-htlc_timeout_swept.json @@ -0,0 +1,62 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listdeposits", + "--network", + "regtest", + "--filter", + "htlc_timeout_swept" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 211493641, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "HTLC_TIMEOUT_SWEPT", + "outpoints": [] + } + } + }, + { + "time_ms": 211, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [] + } + } + }, + { + "time_ms": 211, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 211, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-filters/09_loop-static-listdeposits-wait_for_expiry_sweep.json b/cmd/loop/testdata/sessions/static-filters/09_loop-static-listdeposits-wait_for_expiry_sweep.json new file mode 100644 index 000000000..655456ba3 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/09_loop-static-listdeposits-wait_for_expiry_sweep.json @@ -0,0 +1,62 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listdeposits", + "--network", + "regtest", + "--filter", + "wait_for_expiry_sweep" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 194966554, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "WAIT_FOR_EXPIRY_SWEEP", + "outpoints": [] + } + } + }, + { + "time_ms": 192, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [] + } + } + }, + { + "time_ms": 193, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 194, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-filters/10_loop-static-listdeposits-expired.json b/cmd/loop/testdata/sessions/static-filters/10_loop-static-listdeposits-expired.json new file mode 100644 index 000000000..aeac863cb --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/10_loop-static-listdeposits-expired.json @@ -0,0 +1,62 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listdeposits", + "--network", + "regtest", + "--filter", + "expired" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 224807103, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 4, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "EXPIRED", + "outpoints": [] + } + } + }, + { + "time_ms": 224, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [] + } + } + }, + { + "time_ms": 224, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 224, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-filters/11_loop-static-listdeposits-failed.json b/cmd/loop/testdata/sessions/static-filters/11_loop-static-listdeposits-failed.json new file mode 100644 index 000000000..407287ffd --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/11_loop-static-listdeposits-failed.json @@ -0,0 +1,172 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listdeposits", + "--network", + "regtest", + "--filter", + "failed" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 140385482, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "UNKNOWN_STATE", + "outpoints": [] + } + } + }, + { + "time_ms": 139, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "aCYqEEyewyXea+w3uOMb2HW70vXwuc4togzwvWNvxEg=", + "state": "WITHDRAWN", + "outpoint": "edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0", + "value": "500000", + "confirmation_height": "125", + "blocks_until_expiry": "14360", + "swap_hash": "" + }, + { + "id": "hrXizflpTI5zmOQq/eEJdm180hQiA5Bbpj+9DrE3DvM=", + "state": "WITHDRAWN", + "outpoint": "bb358e4f73ae97c4e2d99c6d64e852bba7cf56e13105b05d1200b8ae1796665e:0", + "value": "500000", + "confirmation_height": "127", + "blocks_until_expiry": "14362", + "swap_hash": "" + }, + { + "id": "bCkPdTbqUJeUav/6xqaZBqJtd1gj67rO3+by1pwHReQ=", + "state": "WITHDRAWN", + "outpoint": "5eaa7dd7a291665393eddf5dece91feef901f22665933cce7a0732a9b81c3001:0", + "value": "500000", + "confirmation_height": "128", + "blocks_until_expiry": "14363", + "swap_hash": "" + }, + { + "id": "AYK02JWxxGcpCue1xsQv92sqQiWAepQhHJcxcNWog+s=", + "state": "WITHDRAWN", + "outpoint": "7e6360d6e6a394cfd096adf0bfe1275c5a83541eb573e90e463a78dc715f8894:0", + "value": "500000", + "confirmation_height": "142", + "blocks_until_expiry": "14377", + "swap_hash": "" + }, + { + "id": "j71tovlF3ikFqn+pOGB0TZOH00ZEhDYOluRnpR3jvJ0=", + "state": "LOOPED_IN", + "outpoint": "9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0", + "value": "500000", + "confirmation_height": "148", + "blocks_until_expiry": "14383", + "swap_hash": "hDAjN0JANkGTlqt5ZN14uFsaSBqfHbc9tc3e5XwkQ+c=" + }, + { + "id": "u38FDfC3w+H+YQEOEK1F4w3fes0wH6bgWi3bgltcLvs=", + "state": "WITHDRAWING", + "outpoint": "56cd081a3a6eadf25b7d3fe0b61207389352ed69a622d2ec28c5d669bf6a5313:0", + "value": "500000", + "confirmation_height": "160", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 139, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": [\n", + " {\n", + " \"blocks_until_expiry\": \"14360\",\n", + " \"confirmation_height\": \"125\",\n", + " \"id\": \"68262a104c9ec325de6bec37b8e31bd875bbd2f5f0b9ce2da20cf0bd636fc448\",\n", + " \"outpoint\": \"edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0\",\n", + " \"state\": \"WITHDRAWN\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " },\n", + " {\n", + " \"blocks_until_expiry\": \"14362\",\n", + " \"confirmation_height\": \"127\",\n", + " \"id\": \"86b5e2cdf9694c8e7398e42afde109766d7cd2142203905ba63fbd0eb1370ef3\",\n", + " \"outpoint\": \"bb358e4f73ae97c4e2d99c6d64e852bba7cf56e13105b05d1200b8ae1796665e:0\",\n", + " \"state\": \"WITHDRAWN\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " },\n", + " {\n", + " \"blocks_until_expiry\": \"14363\",\n", + " \"confirmation_height\": \"128\",\n", + " \"id\": \"6c290f7536ea5097946afffac6a69906a26d775823ebbacedfe6f2d69c0745e4\",\n", + " \"outpoint\": \"5eaa7dd7a291665393eddf5dece91feef901f22665933cce7a0732a9b81c3001:0\",\n", + " \"state\": \"WITHDRAWN\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " },\n", + " {\n", + " \"blocks_until_expiry\": \"14377\",\n", + " \"confirmation_height\": \"142\",\n", + " \"id\": \"0182b4d895b1c467290ae7b5c6c42ff76b2a4225807a94211c973170d5a883eb\",\n", + " \"outpoint\": \"7e6360d6e6a394cfd096adf0bfe1275c5a83541eb573e90e463a78dc715f8894:0\",\n", + " \"state\": \"WITHDRAWN\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " },\n", + " {\n", + " \"blocks_until_expiry\": \"14383\",\n", + " \"confirmation_height\": \"148\",\n", + " \"id\": \"8fbd6da2f945de2905aa7fa93860744d9387d3464484360e96e467a51de3bc9d\",\n", + " \"outpoint\": \"9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0\",\n", + " \"state\": \"LOOPED_IN\",\n", + " \"swap_hash\": \"84302337424036419396ab7964dd78b85b1a481a9f1db73db5cddee57c2443e7\",\n", + " \"value\": \"500000\"\n", + " },\n", + " {\n", + " \"blocks_until_expiry\": \"14395\",\n", + " \"confirmation_height\": \"160\",\n", + " \"id\": \"bb7f050df0b7c3e1fe61010e10ad45e30ddf7acd301fa6e05a2ddb825b5c2efb\",\n", + " \"outpoint\": \"56cd081a3a6eadf25b7d3fe0b61207389352ed69a622d2ec28c5d669bf6a5313:0\",\n", + " \"state\": \"WITHDRAWING\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 140, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/01_loop-static-new.json b/cmd/loop/testdata/sessions/static-loop-in/01_loop-static-new.json new file mode 100644 index 000000000..69a7ab554 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/01_loop-static-new.json @@ -0,0 +1,78 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "new", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "duration": 2871791649, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + "\n", + "WARNING: Be aware that loosing your l402.token file in .loop under your home directory will take your ability to spend funds sent to the static address via loop-ins or withdrawals. You will have to wait until the deposit expires and your loop client sweeps the funds back to your lnd wallet. The deposit expiry could be months in the future.\n", + "\n", + "CONTINUE WITH NEW ADDRESS? (y/n): " + ] + } + }, + { + "time_ms": 2685, + "kind": "stdin", + "data": { + "text": "y\n" + } + }, + { + "time_ms": 2686, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/NewStaticAddress", + "event": "request", + "message_type": "looprpc.NewStaticAddressRequest", + "payload": { + "client_key": "" + } + } + }, + { + "time_ms": 2868, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/NewStaticAddress", + "event": "response", + "message_type": "looprpc.NewStaticAddressResponse", + "payload": { + "address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw", + "expiry": 14400 + } + } + }, + { + "time_ms": 2869, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"address\": \"bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw\",\n", + " \"expiry\": 14400\n", + "}\n" + ] + } + }, + { + "time_ms": 2871, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/04_loop-static.json b/cmd/loop/testdata/sessions/static-loop-in/04_loop-static.json new file mode 100644 index 000000000..b86228be0 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/04_loop-static.json @@ -0,0 +1,56 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "duration": 2504188, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "stdout", + "data": { + "lines": [ + "NAME:\n", + " loop static - perform on-chain to off-chain swaps using static addresses.\n", + "\n", + "USAGE:\n", + " loop static [command [command options]]\n", + "\n", + "COMMANDS:\n", + " new, n Create a new static loop in address.\n", + " listunspent, l List unspent static address outputs.\n", + " listdeposits Displays static address deposits. A filter can be applied to only show deposits in a specific state.\n", + " listwithdrawals Display a summary of past withdrawals.\n", + " listswaps Shows a list of finalized static address swaps.\n", + " withdraw, w Withdraw from static address deposits.\n", + " summary, s Display a summary of static address related information.\n", + " in Loop in funds from static address deposits.\n", + " openchannel Open a channel to a an existing peer.\n", + "\n" + ] + } + }, + { + "time_ms": 2, + "kind": "stdout", + "data": { + "lines": [ + "OPTIONS:\n", + " --help, -h show help\n" + ] + } + }, + { + "time_ms": 2, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/05_loop-static-listunspent.json b/cmd/loop/testdata/sessions/static-loop-in/05_loop-static-listunspent.json new file mode 100644 index 000000000..a9ab34a43 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/05_loop-static-listunspent.json @@ -0,0 +1,72 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "listunspent", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "duration": 22853293, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListUnspentDeposits", + "event": "request", + "message_type": "looprpc.ListUnspentDepositsRequest", + "payload": { + "min_confs": 0, + "max_confs": 0 + } + } + }, + { + "time_ms": 22, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListUnspentDeposits", + "event": "response", + "message_type": "looprpc.ListUnspentDepositsResponse", + "payload": { + "utxos": [ + { + "static_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw", + "amount_sat": "2500000", + "outpoint": "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0", + "confirmations": "0" + } + ] + } + } + }, + { + "time_ms": 22, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"utxos\": [\n", + " {\n", + " \"amount_sat\": \"2500000\",\n", + " \"confirmations\": \"0\",\n", + " \"outpoint\": \"188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0\",\n", + " \"static_address\": \"bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 22, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/06_loop-static-l.json b/cmd/loop/testdata/sessions/static-loop-in/06_loop-static-l.json new file mode 100644 index 000000000..4df67c2df --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/06_loop-static-l.json @@ -0,0 +1,72 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "l", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "duration": 17567260, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListUnspentDeposits", + "event": "request", + "message_type": "looprpc.ListUnspentDepositsRequest", + "payload": { + "min_confs": 0, + "max_confs": 0 + } + } + }, + { + "time_ms": 14, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListUnspentDeposits", + "event": "response", + "message_type": "looprpc.ListUnspentDepositsResponse", + "payload": { + "utxos": [ + { + "static_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw", + "amount_sat": "2500000", + "outpoint": "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0", + "confirmations": "0" + } + ] + } + } + }, + { + "time_ms": 14, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"utxos\": [\n", + " {\n", + " \"amount_sat\": \"2500000\",\n", + " \"confirmations\": \"0\",\n", + " \"outpoint\": \"188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0\",\n", + " \"static_address\": \"bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 17, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/07_loop-static-listdeposits.json b/cmd/loop/testdata/sessions/static-loop-in/07_loop-static-listdeposits.json new file mode 100644 index 000000000..f50a84e05 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/07_loop-static-listdeposits.json @@ -0,0 +1,58 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "listdeposits", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "duration": 20668519, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "UNKNOWN_STATE", + "outpoints": [] + } + } + }, + { + "time_ms": 19, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [] + } + } + }, + { + "time_ms": 19, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 20, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/08_loop-static-listunspent.json b/cmd/loop/testdata/sessions/static-loop-in/08_loop-static-listunspent.json new file mode 100644 index 000000000..465c98349 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/08_loop-static-listunspent.json @@ -0,0 +1,72 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "listunspent", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "duration": 20894580, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListUnspentDeposits", + "event": "request", + "message_type": "looprpc.ListUnspentDepositsRequest", + "payload": { + "min_confs": 0, + "max_confs": 0 + } + } + }, + { + "time_ms": 20, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListUnspentDeposits", + "event": "response", + "message_type": "looprpc.ListUnspentDepositsResponse", + "payload": { + "utxos": [ + { + "static_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw", + "amount_sat": "2500000", + "outpoint": "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0", + "confirmations": "1" + } + ] + } + } + }, + { + "time_ms": 20, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"utxos\": [\n", + " {\n", + " \"amount_sat\": \"2500000\",\n", + " \"confirmations\": \"1\",\n", + " \"outpoint\": \"188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0\",\n", + " \"static_address\": \"bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 20, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/09_loop-static-listunspent.json b/cmd/loop/testdata/sessions/static-loop-in/09_loop-static-listunspent.json new file mode 100644 index 000000000..8cb05ac6d --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/09_loop-static-listunspent.json @@ -0,0 +1,72 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "listunspent", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "duration": 11659340, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListUnspentDeposits", + "event": "request", + "message_type": "looprpc.ListUnspentDepositsRequest", + "payload": { + "min_confs": 0, + "max_confs": 0 + } + } + }, + { + "time_ms": 9, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListUnspentDeposits", + "event": "response", + "message_type": "looprpc.ListUnspentDepositsResponse", + "payload": { + "utxos": [ + { + "static_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw", + "amount_sat": "2500000", + "outpoint": "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0", + "confirmations": "6" + } + ] + } + } + }, + { + "time_ms": 11, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"utxos\": [\n", + " {\n", + " \"amount_sat\": \"2500000\",\n", + " \"confirmations\": \"6\",\n", + " \"outpoint\": \"188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0\",\n", + " \"static_address\": \"bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 11, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/10_loop-static-listdeposits.json b/cmd/loop/testdata/sessions/static-loop-in/10_loop-static-listdeposits.json new file mode 100644 index 000000000..ff4d0d14d --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/10_loop-static-listdeposits.json @@ -0,0 +1,78 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "listdeposits", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "duration": 18981734, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "UNKNOWN_STATE", + "outpoints": [] + } + } + }, + { + "time_ms": 18, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "6mq78FccC6ghF66fIIZhTqzqiykT3AVEtwwA3ng1PnE=", + "state": "DEPOSITED", + "outpoint": "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0", + "value": "2500000", + "confirmation_height": "131", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 18, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": [\n", + " {\n", + " \"blocks_until_expiry\": \"14395\",\n", + " \"confirmation_height\": \"131\",\n", + " \"id\": \"ea6abbf0571c0ba82117ae9f2086614eacea8b2913dc0544b70c00de78353e71\",\n", + " \"outpoint\": \"188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0\",\n", + " \"state\": \"DEPOSITED\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"2500000\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 18, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/11_loop-static-summary.json b/cmd/loop/testdata/sessions/static-loop-in/11_loop-static-summary.json new file mode 100644 index 000000000..6c4d6d99a --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/11_loop-static-summary.json @@ -0,0 +1,73 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "summary", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "duration": 16058920, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetStaticAddressSummary", + "event": "request", + "message_type": "looprpc.StaticAddressSummaryRequest", + "payload": {} + } + }, + { + "time_ms": 15, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetStaticAddressSummary", + "event": "response", + "message_type": "looprpc.StaticAddressSummaryResponse", + "payload": { + "static_address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw", + "relative_expiry_blocks": "14400", + "total_num_deposits": 1, + "value_channels_opened": "0", + "value_unconfirmed_satoshis": "0", + "value_deposited_satoshis": "2500000", + "value_expired_satoshis": "0", + "value_withdrawn_satoshis": "0", + "value_looped_in_satoshis": "0", + "value_htlc_timeout_sweeps_satoshis": "0" + } + } + }, + { + "time_ms": 15, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"relative_expiry_blocks\": \"14400\",\n", + " \"static_address\": \"bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw\",\n", + " \"total_num_deposits\": 1,\n", + " \"value_channels_opened\": \"0\",\n", + " \"value_deposited_satoshis\": \"2500000\",\n", + " \"value_expired_satoshis\": \"0\",\n", + " \"value_htlc_timeout_sweeps_satoshis\": \"0\",\n", + " \"value_looped_in_satoshis\": \"0\",\n", + " \"value_unconfirmed_satoshis\": \"0\",\n", + " \"value_withdrawn_satoshis\": \"0\"\n", + "}\n" + ] + } + }, + { + "time_ms": 16, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/12_loop-static-in.json b/cmd/loop/testdata/sessions/static-loop-in/12_loop-static-in.json new file mode 100644 index 000000000..111627d84 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/12_loop-static-in.json @@ -0,0 +1,69 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "in", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "run_error": "unknown quote request", + "duration": 12090944, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "DEPOSITED", + "outpoints": [] + } + } + }, + { + "time_ms": 11, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "6mq78FccC6ghF66fIIZhTqzqiykT3AVEtwwA3ng1PnE=", + "state": "DEPOSITED", + "outpoint": "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0", + "value": "2500000", + "confirmation_height": "131", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 12, + "kind": "stderr", + "data": { + "lines": [ + "[loop] unknown quote request\n" + ] + } + }, + { + "time_ms": 12, + "kind": "exit", + "data": { + "run_error": "unknown quote request" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/13_loop-static-in.json b/cmd/loop/testdata/sessions/static-loop-in/13_loop-static-in.json new file mode 100644 index 000000000..935f64d0e --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/13_loop-static-in.json @@ -0,0 +1,103 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "in", + "--all", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "run_error": "rpc error: code = Unknown desc = swap amount too high", + "duration": 29949728, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "DEPOSITED", + "outpoints": [] + } + } + }, + { + "time_ms": 19, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "6mq78FccC6ghF66fIIZhTqzqiykT3AVEtwwA3ng1PnE=", + "state": "DEPOSITED", + "outpoint": "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0", + "value": "2500000", + "confirmation_height": "131", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 19, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "0", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [ + "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0" + ], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 29, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "error", + "error": "rpc error: code = Unknown desc = swap amount too high" + } + }, + { + "time_ms": 29, + "kind": "stderr", + "data": { + "lines": [ + "[loop] rpc error: code = Unknown desc = swap amount too high\n" + ] + } + }, + { + "time_ms": 29, + "kind": "exit", + "data": { + "run_error": "rpc error: code = Unknown desc = swap amount too high" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/14_loop-static-in.json b/cmd/loop/testdata/sessions/static-loop-in/14_loop-static-in.json new file mode 100644 index 000000000..f79e0547e --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/14_loop-static-in.json @@ -0,0 +1,162 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "in", + "--help", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "duration": 2400415, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + "NAME:\n" + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + " loop static in - Loop in funds from static address deposits." + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + "\n", + "\n", + "USAGE:\n" + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + " loop static in [options] [amt] [--all | --utxo xxx:xx]\n", + "\n", + "DESCRIPTION:\n", + " " + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + "\n", + " Requests a loop-in swap based on static address deposits. After the\n", + " creation of a static address funds can be sent to it. Once the funds are\n", + " " + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + " confirmed on-chain they can be swapped instantaneously. If deposited\n", + " funds are not needed they can we withdrawn back to the local lnd wallet.\n", + " \n", + "\n", + "OPTIONS:\n" + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + " --utxo string [ --utxo string ] specify the utxos of deposits as outpoints(tx:idx) that should be looped in.\n", + " --all loop in all static address deposits. (default: false)\n", + " --payment_timeout duration " + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + " the maximum time in seconds that the server is allowed to take for the swap payment. The client can retry the swap with adjusted parameters after the payment timed out. (default: 0s)\n", + " --amt uint, --amount uint the number of satoshis that should be swapped from the selected deposits. If thereis change it is sent back to the static address. (default: 0)\n", + " --fast Usage: complete the swap faster by paying a higher fee, so the change output is available sooner (default: false)\n", + " --max_swap_fee_sat uint the maximum swap fee in satoshis. If set, the swap is rejected when the quoted fee exceeds this cap. The maximum allowed value is 10000000. On-chain fees for creating static deposits are unaffected. (default: 0)\n", + " --max_swap_fee_ppm uint the maximum swap fee expressed in parts per million of the swap amount. If set together with --max_swap_fee_sat the tighter cap is used. (default: 0)\n", + " --last_hop string the pubkey of the last hop to use for this swap\n", + " --label string an optional label for this swap,limited to 500 characters. The label may not start with our reserved prefix: [reserved].\n", + " --route_hints string [ --route_hints string ]" + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + " route hints that can each be individually used to assist in reaching the invoice's destination\n", + " --private generates and passes routehints. Should be used if the connected node is only reachable via private channels (default: false)\n", + " --force Assumes yes during confirmation. Using this option will result in an immediate swap (default: false)\n", + " --verbose, -v show expanded details (default: false)\n", + " --help, -h show help\n", + "\n" + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + "GLOBAL OPTIONS:\n", + " --rpcserver string " + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + "loopd daemon address host:port (default: \"localhost:11010\") [$LOOPCLI_RPCSERVER]\n", + " --network string, -n string the network loop is running on e.g. mainnet, testnet, etc. (default: \"mainnet\") [$LOOPCLI_NETWORK]\n", + " --loopdir string path to loop's base directory (default: ~/.loop) [$LOOPCLI_LOOPDIR]\n", + " --tlscertpath string" + ] + } + }, + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + " path to loop's TLS certificate (default: ~/.loop/mainnet/tls.cert) [$LOOPCLI_TLSCERTPATH]\n", + " --macaroonpath string path to macaroon file (default: ~/.loop/mainnet/loop.macaroon) [$LOOPCLI_MACAROONPATH]\n" + ] + } + }, + { + "time_ms": 2, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/15_loop-static-in.json b/cmd/loop/testdata/sessions/static-loop-in/15_loop-static-in.json new file mode 100644 index 000000000..ea600c31e --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/15_loop-static-in.json @@ -0,0 +1,214 @@ +{ + "metadata": { + "args": [ + "loop", + "static", + "in", + "--amt", + "500000", + "--all", + "--network", + "regtest" + ], + "env": {}, + "version": "0.31.7-beta commit=vbump-lndclient-70-g352a68cd43f1976a937faaf76041bc078fdd16f6 commit_hash=352a68cd43f1976a937faaf76041bc078fdd16f6", + "duration": 2826015164, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "DEPOSITED", + "outpoints": [] + } + } + }, + { + "time_ms": 22, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "6mq78FccC6ghF66fIIZhTqzqiykT3AVEtwwA3ng1PnE=", + "state": "DEPOSITED", + "outpoint": "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0", + "value": "2500000", + "confirmation_height": "131", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 22, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "500000", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [ + "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0" + ], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 65, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1824", + "htlc_publish_fee_sat": "0", + "cltv_delta": 0, + "conf_target": 0, + "quoted_amt": "500000" + } + } + }, + { + "time_ms": 65, + "kind": "stdout", + "data": { + "lines": [ + "Previously deposited on-chain: 500000 sat\n", + "Receive off-chain: 498176 sat\n", + "Estimated total fee: 1824 sat\n", + "\n", + "CONTINUE SWAP? (y/n): " + ] + } + }, + { + "time_ms": 2358, + "kind": "stdin", + "data": { + "text": "y\n" + } + }, + { + "time_ms": 2358, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/StaticAddressLoopIn", + "event": "request", + "message_type": "looprpc.StaticAddressLoopInRequest", + "payload": { + "outpoints": [ + "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0" + ], + "max_swap_fee_satoshis": "1824", + "last_hop": "", + "label": "", + "initiator": "loop-cli", + "route_hints": [], + "private": false, + "payment_timeout_seconds": 60, + "amount": "500000", + "fast": false + } + } + }, + { + "time_ms": 2824, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/StaticAddressLoopIn", + "event": "response", + "message_type": "looprpc.StaticAddressLoopInResponse", + "payload": { + "swap_hash": "nxn7UEKl3m2i8c4YPF4iT9eALbQI6a/dWY7oF0srzj8=", + "state": "SignHtlcTx", + "amount": "2500000", + "htlc_cltv": 1136, + "quoted_swap_fee_satoshis": "1824", + "max_swap_fee_satoshis": "1824", + "initiation_height": 136, + "protocol_version": "V0", + "label": "", + "initiator": "loop-cli", + "payment_timeout_seconds": 60, + "used_deposits": [ + { + "id": "6mq78FccC6ghF66fIIZhTqzqiykT3AVEtwwA3ng1PnE=", + "state": "LOOPING_IN", + "outpoint": "188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0", + "value": "2500000", + "confirmation_height": "131", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ], + "swap_amount": "500000", + "change": "2000000", + "fast": false + } + } + }, + { + "time_ms": 2825, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"amount\": \"2500000\",\n", + " \"change\": \"2000000\",\n", + " \"fast\": false,\n", + " \"htlc_cltv\": 1136,\n", + " \"initiation_height\": 136,\n", + " \"initiator\": \"loop-cli\",\n", + " \"label\": \"\",\n", + " \"max_swap_fee_satoshis\": \"1824\",\n", + " \"payment_timeout_seconds\": 60,\n", + " \"protocol_version\": \"V0\",\n", + " \"quoted_swap_fee_satoshis\": \"1824\",\n", + " \"state\": \"SignHtlcTx\",\n", + " \"swap_amount\": \"500000\",\n", + " \"swap_hash\": \"9f19fb5042a5de6da2f1ce183c5e224fd7802db408e9afdd598ee8174b2bce3f\",\n", + " \"used_deposits\": [\n", + " {\n", + " \"blocks_until_expiry\": \"14395\",\n", + " \"confirmation_height\": \"131\",\n", + " \"id\": \"ea6abbf0571c0ba82117ae9f2086614eacea8b2913dc0544b70c00de78353e71\",\n", + " \"outpoint\": \"188f55042e49cfa9942cc1f8e216c5e8679a7036e9ee6449d0fcc6c6b81561be:0\",\n", + " \"state\": \"LOOPING_IN\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"2500000\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 2826, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/16_loop-static-in-duplicate-outpoints.json b/cmd/loop/testdata/sessions/static-loop-in/16_loop-static-in-duplicate-outpoints.json new file mode 100644 index 000000000..01fe9bd45 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/16_loop-static-in-duplicate-outpoints.json @@ -0,0 +1,75 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "in", + "--utxo", + "9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0", + "--utxo", + "9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "duplicate outpoints detected", + "duration": 174401375, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "DEPOSITED", + "outpoints": [] + } + } + }, + { + "time_ms": 173, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "j71tovlF3ikFqn+pOGB0TZOH00ZEhDYOluRnpR3jvJ0=", + "state": "DEPOSITED", + "outpoint": "9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0", + "value": "500000", + "confirmation_height": "148", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 174, + "kind": "stderr", + "data": { + "lines": [ + "[loop] duplicate outpoints detected\n" + ] + } + }, + { + "time_ms": 174, + "kind": "exit", + "data": { + "run_error": "duplicate outpoints detected" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/17_loop-static-in-positional-low-amt.json b/cmd/loop/testdata/sessions/static-loop-in/17_loop-static-in-positional-low-amt.json new file mode 100644 index 000000000..0172b3b57 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/17_loop-static-in-positional-low-amt.json @@ -0,0 +1,108 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "in", + "--network", + "regtest", + "10000", + "--payment_timeout", + "30s", + "--last_hop", + "0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7", + "--force" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "rpc error: code = Unknown desc = swap amount too low", + "duration": 226739365, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "DEPOSITED", + "outpoints": [] + } + } + }, + { + "time_ms": 182, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "j71tovlF3ikFqn+pOGB0TZOH00ZEhDYOluRnpR3jvJ0=", + "state": "DEPOSITED", + "outpoint": "9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0", + "value": "500000", + "confirmation_height": "148", + "blocks_until_expiry": "14383", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 183, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "10000", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": true, + "fast": false + } + } + }, + { + "time_ms": 225, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "error", + "error": "rpc error: code = Unknown desc = swap amount too low" + } + }, + { + "time_ms": 226, + "kind": "stderr", + "data": { + "lines": [ + "[loop] rpc error: code = Unknown desc = swap amount too low\n" + ] + } + }, + { + "time_ms": 226, + "kind": "exit", + "data": { + "run_error": "rpc error: code = Unknown desc = swap amount too low" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/18_loop-static-in-positional-payment-timeout.json b/cmd/loop/testdata/sessions/static-loop-in/18_loop-static-in-positional-payment-timeout.json new file mode 100644 index 000000000..5a6b7c1cf --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/18_loop-static-in-positional-payment-timeout.json @@ -0,0 +1,195 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "in", + "--network", + "regtest", + "500000", + "--payment_timeout", + "30s", + "--last_hop", + "0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7", + "--force" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 1078774451, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 4, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "DEPOSITED", + "outpoints": [] + } + } + }, + { + "time_ms": 179, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "j71tovlF3ikFqn+pOGB0TZOH00ZEhDYOluRnpR3jvJ0=", + "state": "DEPOSITED", + "outpoint": "9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0", + "value": "500000", + "confirmation_height": "148", + "blocks_until_expiry": "14383", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 180, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "500000", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [], + "asset_info": null, + "auto_select_deposits": true, + "fast": false + } + } + }, + { + "time_ms": 500, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1824", + "htlc_publish_fee_sat": "0", + "cltv_delta": 0, + "conf_target": 0, + "quoted_amt": "500000" + } + } + }, + { + "time_ms": 500, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/StaticAddressLoopIn", + "event": "request", + "message_type": "looprpc.StaticAddressLoopInRequest", + "payload": { + "outpoints": [], + "max_swap_fee_satoshis": "1824", + "last_hop": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "label": "", + "initiator": "loop-cli", + "route_hints": [], + "private": false, + "payment_timeout_seconds": 30, + "amount": "500000", + "fast": false + } + } + }, + { + "time_ms": 1078, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/StaticAddressLoopIn", + "event": "response", + "message_type": "looprpc.StaticAddressLoopInResponse", + "payload": { + "swap_hash": "hDAjN0JANkGTlqt5ZN14uFsaSBqfHbc9tc3e5XwkQ+c=", + "state": "SignHtlcTx", + "amount": "500000", + "htlc_cltv": 1165, + "quoted_swap_fee_satoshis": "1824", + "max_swap_fee_satoshis": "1824", + "initiation_height": 165, + "protocol_version": "V0", + "label": "", + "initiator": "loop-cli", + "payment_timeout_seconds": 30, + "used_deposits": [ + { + "id": "j71tovlF3ikFqn+pOGB0TZOH00ZEhDYOluRnpR3jvJ0=", + "state": "LOOPING_IN", + "outpoint": "9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0", + "value": "500000", + "confirmation_height": "148", + "blocks_until_expiry": "14383", + "swap_hash": "" + } + ], + "swap_amount": "500000", + "change": "0", + "fast": false + } + } + }, + { + "time_ms": 1078, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"amount\": \"500000\",\n", + " \"change\": \"0\",\n", + " \"fast\": false,\n", + " \"htlc_cltv\": 1165,\n", + " \"initiation_height\": 165,\n", + " \"initiator\": \"loop-cli\",\n", + " \"label\": \"\",\n", + " \"max_swap_fee_satoshis\": \"1824\",\n", + " \"payment_timeout_seconds\": 30,\n", + " \"protocol_version\": \"V0\",\n", + " \"quoted_swap_fee_satoshis\": \"1824\",\n", + " \"state\": \"SignHtlcTx\",\n", + " \"swap_amount\": \"500000\",\n", + " \"swap_hash\": \"84302337424036419396ab7964dd78b85b1a481a9f1db73db5cddee57c2443e7\",\n", + " \"used_deposits\": [\n", + " {\n", + " \"blocks_until_expiry\": \"14383\",\n", + " \"confirmation_height\": \"148\",\n", + " \"id\": \"8fbd6da2f945de2905aa7fa93860744d9387d3464484360e96e467a51de3bc9d\",\n", + " \"outpoint\": \"9fa0d5dd5348794aa0541dd2729497f0907890606d044e1c4757bdc848f38df8:0\",\n", + " \"state\": \"LOOPING_IN\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 1078, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/19_loop-static-in-all-cancel.json b/cmd/loop/testdata/sessions/static-loop-in/19_loop-static-in-all-cancel.json new file mode 100644 index 000000000..073bcb3ba --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/19_loop-static-in-all-cancel.json @@ -0,0 +1,132 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "in", + "--network", + "regtest", + "--all" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "swap canceled", + "duration": 446675015, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "stdin", + "data": { + "text": "n\n" + } + }, + { + "time_ms": 5, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "DEPOSITED", + "outpoints": [] + } + } + }, + { + "time_ms": 387, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "S7MSLTegUia00YiHa4Q+ZLxUl4M4FfD8lunKy9HyI1E=", + "state": "DEPOSITED", + "outpoint": "bbd8a81ea27aa5427a6e0000853f33328946f7297ab43cb0a549bee300ffd8b9:1", + "value": "500000", + "confirmation_height": "166", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 388, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "0", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [ + "bbd8a81ea27aa5427a6e0000853f33328946f7297ab43cb0a549bee300ffd8b9:1" + ], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 446, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1823", + "htlc_publish_fee_sat": "0", + "cltv_delta": 0, + "conf_target": 0, + "quoted_amt": "500000" + } + } + }, + { + "time_ms": 446, + "kind": "stdout", + "data": { + "lines": [ + "Previously deposited on-chain: 500000 sat\n", + "Receive off-chain: 498177 sat\n", + "Estimated total fee: 1823 sat\n", + "\n", + "CONTINUE SWAP? (y/n): " + ] + } + }, + { + "time_ms": 446, + "kind": "stderr", + "data": { + "lines": [ + "[loop] swap canceled\n" + ] + } + }, + { + "time_ms": 446, + "kind": "exit", + "data": { + "run_error": "swap canceled" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static/01_loop-static-withdraw-no-selection.json b/cmd/loop/testdata/sessions/static/01_loop-static-withdraw-no-selection.json new file mode 100644 index 000000000..179acb97e --- /dev/null +++ b/cmd/loop/testdata/sessions/static/01_loop-static-withdraw-no-selection.json @@ -0,0 +1,36 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "withdraw", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "must select either all or some utxos", + "duration": 1520351, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stderr", + "data": { + "lines": [ + "[loop] must select either all or some utxos\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": { + "run_error": "must select either all or some utxos" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static/02_loop-static-listwithdrawals.json b/cmd/loop/testdata/sessions/static/02_loop-static-listwithdrawals.json new file mode 100644 index 000000000..15b5d4e1f --- /dev/null +++ b/cmd/loop/testdata/sessions/static/02_loop-static-listwithdrawals.json @@ -0,0 +1,95 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listwithdrawals", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 164024932, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressWithdrawals", + "event": "request", + "message_type": "looprpc.ListStaticAddressWithdrawalRequest", + "payload": {} + } + }, + { + "time_ms": 163, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressWithdrawals", + "event": "response", + "message_type": "looprpc.ListStaticAddressWithdrawalResponse", + "payload": { + "withdrawals": [ + { + "tx_id": "0000000000000000000000000000000000000000000000000000000000000000", + "deposits": [ + { + "id": "aCYqEEyewyXea+w3uOMb2HW70vXwuc4togzwvWNvxEg=", + "state": "WITHDRAWING", + "outpoint": "edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0", + "value": "500000", + "confirmation_height": "125", + "blocks_until_expiry": "0", + "swap_hash": "" + } + ], + "total_deposit_amount_satoshis": "500000", + "withdrawn_amount_satoshis": "0", + "change_amount_satoshis": "0", + "confirmation_height": 0 + } + ] + } + } + }, + { + "time_ms": 163, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"withdrawals\": [\n", + " {\n", + " \"change_amount_satoshis\": \"0\",\n", + " \"confirmation_height\": 0,\n", + " \"deposits\": [\n", + " {\n", + " \"blocks_until_expiry\": \"0\",\n", + " \"confirmation_height\": \"125\",\n", + " \"id\": \"68262a104c9ec325de6bec37b8e31bd875bbd2f5f0b9ce2da20cf0bd636fc448\",\n", + " \"outpoint\": \"edcdab8f0b1138d853a453b8b7a5ac3c694bd53ad38b7ccf062e45f99440e6e6:0\",\n", + " \"state\": \"WITHDRAWING\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " }\n", + " ],\n", + " \"total_deposit_amount_satoshis\": \"500000\",\n", + " \"tx_id\": \"0000000000000000000000000000000000000000000000000000000000000000\",\n", + " \"withdrawn_amount_satoshis\": \"0\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 164, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static/03_loop-static-listswaps.json b/cmd/loop/testdata/sessions/static/03_loop-static-listswaps.json new file mode 100644 index 000000000..d1e2be4f9 --- /dev/null +++ b/cmd/loop/testdata/sessions/static/03_loop-static-listswaps.json @@ -0,0 +1,57 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "listswaps", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 152223840, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressSwaps", + "event": "request", + "message_type": "looprpc.ListStaticAddressSwapsRequest", + "payload": {} + } + }, + { + "time_ms": 151, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressSwaps", + "event": "response", + "message_type": "looprpc.ListStaticAddressSwapsResponse", + "payload": { + "swaps": [] + } + } + }, + { + "time_ms": 151, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"swaps\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 152, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static/04_loop-static-withdraw-invalid-utxo.json b/cmd/loop/testdata/sessions/static/04_loop-static-withdraw-invalid-utxo.json new file mode 100644 index 000000000..61dca5253 --- /dev/null +++ b/cmd/loop/testdata/sessions/static/04_loop-static-withdraw-invalid-utxo.json @@ -0,0 +1,38 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "withdraw", + "--utxo", + "abc", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "outpoint should be of the form txid:index", + "duration": 8829122, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 8, + "kind": "stderr", + "data": { + "lines": [ + "[loop] outpoint should be of the form txid:index\n" + ] + } + }, + { + "time_ms": 8, + "kind": "exit", + "data": { + "run_error": "outpoint should be of the form txid:index" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static/05_loop-static-withdraw-all.json b/cmd/loop/testdata/sessions/static/05_loop-static-withdraw-all.json new file mode 100644 index 000000000..94545b2cf --- /dev/null +++ b/cmd/loop/testdata/sessions/static/05_loop-static-withdraw-all.json @@ -0,0 +1,66 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "withdraw", + "--all", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 582593010, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/WithdrawDeposits", + "event": "request", + "message_type": "looprpc.WithdrawDepositsRequest", + "payload": { + "outpoints": [], + "all": true, + "dest_addr": "", + "sat_per_vbyte": "0", + "amount": "0" + } + } + }, + { + "time_ms": 582, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/WithdrawDeposits", + "event": "response", + "message_type": "looprpc.WithdrawDepositsResponse", + "payload": { + "withdrawal_tx_hash": "849b3e491dd45688fe66fc05fc0b7a89a147cad749486c46f515b1e815a16d31", + "address": "bcrt1pk2jdgms8l67x6mp2kjq45k2sd7mdpfcf988lckk774uguxzh0x5scddya7" + } + } + }, + { + "time_ms": 582, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"address\": \"bcrt1pk2jdgms8l67x6mp2kjq45k2sd7mdpfcf988lckk774uguxzh0x5scddya7\",\n", + " \"withdrawal_tx_hash\": \"849b3e491dd45688fe66fc05fc0b7a89a147cad749486c46f515b1e815a16d31\"\n", + "}\n" + ] + } + }, + { + "time_ms": 582, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static/06_loop-static-withdraw-utxo-destaddr.json b/cmd/loop/testdata/sessions/static/06_loop-static-withdraw-utxo-destaddr.json new file mode 100644 index 000000000..1f9a1e9d3 --- /dev/null +++ b/cmd/loop/testdata/sessions/static/06_loop-static-withdraw-utxo-destaddr.json @@ -0,0 +1,75 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "static", + "withdraw", + "--network", + "regtest", + "--utxo", + "56cd081a3a6eadf25b7d3fe0b61207389352ed69a622d2ec28c5d669bf6a5313:0", + "--dest_addr", + "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 308497131, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/WithdrawDeposits", + "event": "request", + "message_type": "looprpc.WithdrawDepositsRequest", + "payload": { + "outpoints": [ + { + "txid_bytes": "", + "txid_str": "56cd081a3a6eadf25b7d3fe0b61207389352ed69a622d2ec28c5d669bf6a5313", + "output_index": 0 + } + ], + "all": false, + "dest_addr": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw", + "sat_per_vbyte": "0", + "amount": "0" + } + } + }, + { + "time_ms": 308, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/WithdrawDeposits", + "event": "response", + "message_type": "looprpc.WithdrawDepositsResponse", + "payload": { + "withdrawal_tx_hash": "4578dde9139327e5122960b778fe09f426cf6c1df5fa276cca18d72ccb3d88ba", + "address": "bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw" + } + } + }, + { + "time_ms": 308, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"address\": \"bcrt1pfu9g59aqtxd39653f76y4c8z7r3t9tmcvrvhl57a3dgj3epdwxdqcd9fpw\",\n", + " \"withdrawal_tx_hash\": \"4578dde9139327e5122960b778fe09f426cf6c1df5fa276cca18d72ccb3d88ba\"\n", + "}\n" + ] + } + }, + { + "time_ms": 308, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/swaps/01_loop-listswaps.json b/cmd/loop/testdata/sessions/swaps/01_loop-listswaps.json new file mode 100644 index 000000000..d2ecc6553 --- /dev/null +++ b/cmd/loop/testdata/sessions/swaps/01_loop-listswaps.json @@ -0,0 +1,111 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "listswaps", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 150727157, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListSwaps", + "event": "request", + "message_type": "looprpc.ListSwapsRequest", + "payload": { + "list_swap_filter": { + "swap_type": "ANY", + "pending_only": false, + "outgoing_chan_set": [], + "label": "", + "loop_in_last_hop": "", + "asset_swap_only": false, + "start_timestamp_ns": "0" + }, + "max_swaps": "0" + } + } + }, + { + "time_ms": 149, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListSwaps", + "event": "response", + "message_type": "looprpc.ListSwapsResponse", + "payload": { + "swaps": [ + { + "amt": "500000", + "id": "46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b", + "id_bytes": "RrjbZBEKYAi9NrQjQ5y7oejnbAJk6KaPRC/CKjwrT2s=", + "type": "LOOP_OUT", + "state": "INITIATED", + "failure_reason": "FAILURE_REASON_NONE", + "initiation_time": "1770081011767003660", + "last_update_time": "1770081011767003660", + "htlc_address": "bcrt1pj90ffd4j9hc2a58xpl3q77c6pf8ckhqhv9w9m2529kqsk77f6lksrca9pv", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1pj90ffd4j9hc2a58xpl3q77c6pf8ckhqhv9w9m2529kqsk77f6lksrca9pv", + "cost_server": "0", + "cost_onchain": "0", + "cost_offchain": "0", + "last_hop": "", + "outgoing_chan_set": [], + "label": "", + "asset_info": null + } + ], + "next_start_time": "0" + } + } + }, + { + "time_ms": 150, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"next_start_time\": \"0\",\n", + " \"swaps\": [\n", + " {\n", + " \"amt\": \"500000\",\n", + " \"asset_info\": null,\n", + " \"cost_offchain\": \"0\",\n", + " \"cost_onchain\": \"0\",\n", + " \"cost_server\": \"0\",\n", + " \"failure_reason\": \"FAILURE_REASON_NONE\",\n", + " \"htlc_address\": \"bcrt1pj90ffd4j9hc2a58xpl3q77c6pf8ckhqhv9w9m2529kqsk77f6lksrca9pv\",\n", + " \"htlc_address_p2tr\": \"bcrt1pj90ffd4j9hc2a58xpl3q77c6pf8ckhqhv9w9m2529kqsk77f6lksrca9pv\",\n", + " \"htlc_address_p2wsh\": \"\",\n", + " \"id\": \"46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\",\n", + " \"id_bytes\": \"46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\",\n", + " \"initiation_time\": \"1770081011767003660\",\n", + " \"label\": \"\",\n", + " \"last_hop\": \"\",\n", + " \"last_update_time\": \"1770081011767003660\",\n", + " \"outgoing_chan_set\": [],\n", + " \"state\": \"INITIATED\",\n", + " \"type\": \"LOOP_OUT\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 150, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/swaps/02_loop-swapinfo.json b/cmd/loop/testdata/sessions/swaps/02_loop-swapinfo.json new file mode 100644 index 000000000..79a4353d7 --- /dev/null +++ b/cmd/loop/testdata/sessions/swaps/02_loop-swapinfo.json @@ -0,0 +1,93 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "swapinfo", + "--network", + "regtest", + "46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 191339781, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SwapInfo", + "event": "request", + "message_type": "looprpc.SwapInfoRequest", + "payload": { + "id": "RrjbZBEKYAi9NrQjQ5y7oejnbAJk6KaPRC/CKjwrT2s=" + } + } + }, + { + "time_ms": 191, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/SwapInfo", + "event": "response", + "message_type": "looprpc.SwapStatus", + "payload": { + "amt": "500000", + "id": "46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b", + "id_bytes": "RrjbZBEKYAi9NrQjQ5y7oejnbAJk6KaPRC/CKjwrT2s=", + "type": "LOOP_OUT", + "state": "INITIATED", + "failure_reason": "FAILURE_REASON_NONE", + "initiation_time": "1770081011767003660", + "last_update_time": "1770081011767003660", + "htlc_address": "bcrt1pj90ffd4j9hc2a58xpl3q77c6pf8ckhqhv9w9m2529kqsk77f6lksrca9pv", + "htlc_address_p2wsh": "", + "htlc_address_p2tr": "bcrt1pj90ffd4j9hc2a58xpl3q77c6pf8ckhqhv9w9m2529kqsk77f6lksrca9pv", + "cost_server": "0", + "cost_onchain": "0", + "cost_offchain": "0", + "last_hop": "", + "outgoing_chan_set": [], + "label": "", + "asset_info": null + } + } + }, + { + "time_ms": 191, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"amt\": \"500000\",\n", + " \"asset_info\": null,\n", + " \"cost_offchain\": \"0\",\n", + " \"cost_onchain\": \"0\",\n", + " \"cost_server\": \"0\",\n", + " \"failure_reason\": \"FAILURE_REASON_NONE\",\n", + " \"htlc_address\": \"bcrt1pj90ffd4j9hc2a58xpl3q77c6pf8ckhqhv9w9m2529kqsk77f6lksrca9pv\",\n", + " \"htlc_address_p2tr\": \"bcrt1pj90ffd4j9hc2a58xpl3q77c6pf8ckhqhv9w9m2529kqsk77f6lksrca9pv\",\n", + " \"htlc_address_p2wsh\": \"\",\n", + " \"id\": \"46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\",\n", + " \"id_bytes\": \"46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\",\n", + " \"initiation_time\": \"1770081011767003660\",\n", + " \"label\": \"\",\n", + " \"last_hop\": \"\",\n", + " \"last_update_time\": \"1770081011767003660\",\n", + " \"outgoing_chan_set\": [],\n", + " \"state\": \"INITIATED\",\n", + " \"type\": \"LOOP_OUT\"\n", + "}\n" + ] + } + }, + { + "time_ms": 191, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/swaps/03_loop-abandonswap.json b/cmd/loop/testdata/sessions/swaps/03_loop-abandonswap.json new file mode 100644 index 000000000..b7b247dd0 --- /dev/null +++ b/cmd/loop/testdata/sessions/swaps/03_loop-abandonswap.json @@ -0,0 +1,45 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "abandonswap", + "--network", + "regtest" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 2764352, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stdout", + "data": { + "lines": [ + "NAME:\n", + " loop abandonswap - abandon a swap with a given swap hash\n", + "\n", + "USAGE:\n", + " loop abandonswap ID\n", + "\n", + "DESCRIPTION:\n", + " This command overrides the database and abandons a swap with a given swap hash.\n", + "\n", + " !!! This command might potentially lead to loss of funds if it is applied to swaps that are still waiting for pending user funds. Before executing this command make sure that no funds are locked by the swap.\n", + "\n", + "OPTIONS:\n", + " --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. (default: false)\n", + " --help, -h show help\n" + ] + } + }, + { + "time_ms": 2, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/swaps/04_loop-listswaps-conflicting-filters.json b/cmd/loop/testdata/sessions/swaps/04_loop-listswaps-conflicting-filters.json new file mode 100644 index 000000000..a26edacd7 --- /dev/null +++ b/cmd/loop/testdata/sessions/swaps/04_loop-listswaps-conflicting-filters.json @@ -0,0 +1,37 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "listswaps", + "--network", + "regtest", + "--loop_out_only", + "--loop_in_only" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "only one of loop_out_only and loop_in_only can be set", + "duration": 1714263, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stderr", + "data": { + "lines": [ + "[loop] only one of loop_out_only and loop_in_only can be set\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": { + "run_error": "only one of loop_out_only and loop_in_only can be set" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/swaps/05_loop-swapinfo-invalid-id.json b/cmd/loop/testdata/sessions/swaps/05_loop-swapinfo-invalid-id.json new file mode 100644 index 000000000..70528fbd6 --- /dev/null +++ b/cmd/loop/testdata/sessions/swaps/05_loop-swapinfo-invalid-id.json @@ -0,0 +1,36 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "swapinfo", + "--network", + "regtest", + "deadbeef" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "invalid swap ID", + "duration": 1081414, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stderr", + "data": { + "lines": [ + "[loop] invalid swap ID\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": { + "run_error": "invalid swap ID" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/swaps/06_loop-abandonswap-invalid-id.json b/cmd/loop/testdata/sessions/swaps/06_loop-abandonswap-invalid-id.json new file mode 100644 index 000000000..75a9f66e7 --- /dev/null +++ b/cmd/loop/testdata/sessions/swaps/06_loop-abandonswap-invalid-id.json @@ -0,0 +1,37 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "abandonswap", + "--network", + "regtest", + "--i_know_what_i_am_doing", + "deadbeef" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "invalid swap ID", + "duration": 1742129, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stderr", + "data": { + "lines": [ + "[loop] invalid swap ID\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": { + "run_error": "invalid swap ID" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/swaps/07_loop-listswaps-loop-out-filtered.json b/cmd/loop/testdata/sessions/swaps/07_loop-listswaps-loop-out-filtered.json new file mode 100644 index 000000000..d54c69012 --- /dev/null +++ b/cmd/loop/testdata/sessions/swaps/07_loop-listswaps-loop-out-filtered.json @@ -0,0 +1,82 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "listswaps", + "--network", + "regtest", + "--loop_out_only", + "--channel", + "125344325763072", + "--last_hop", + "0271d6e29301159d9e1cc5d3983479a51f3b3c0c682eda7f16aa1f47dfe09b22f7", + "--label", + "testlabel", + "--start_time_ns", + "1", + "--max_swaps", + "5" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 146940860, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListSwaps", + "event": "request", + "message_type": "looprpc.ListSwapsRequest", + "payload": { + "list_swap_filter": { + "swap_type": "LOOP_OUT", + "pending_only": false, + "outgoing_chan_set": [ + "125344325763072" + ], + "label": "testlabel", + "loop_in_last_hop": "AnHW4pMBFZ2eHMXTmDR5pR87PAxoLtp/FqofR9/gmyL3", + "asset_swap_only": false, + "start_timestamp_ns": "1" + }, + "max_swaps": "5" + } + } + }, + { + "time_ms": 146, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListSwaps", + "event": "response", + "message_type": "looprpc.ListSwapsResponse", + "payload": { + "swaps": [], + "next_start_time": "0" + } + } + }, + { + "time_ms": 146, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"next_start_time\": \"0\",\n", + " \"swaps\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 146, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/swaps/08_loop-listswaps-loop-in-only.json b/cmd/loop/testdata/sessions/swaps/08_loop-listswaps-loop-in-only.json new file mode 100644 index 000000000..91b36a13f --- /dev/null +++ b/cmd/loop/testdata/sessions/swaps/08_loop-listswaps-loop-in-only.json @@ -0,0 +1,70 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "listswaps", + "--network", + "regtest", + "--loop_in_only" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 237970153, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListSwaps", + "event": "request", + "message_type": "looprpc.ListSwapsRequest", + "payload": { + "list_swap_filter": { + "swap_type": "LOOP_IN", + "pending_only": false, + "outgoing_chan_set": [], + "label": "", + "loop_in_last_hop": "", + "asset_swap_only": false, + "start_timestamp_ns": "0" + }, + "max_swaps": "0" + } + } + }, + { + "time_ms": 236, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListSwaps", + "event": "response", + "message_type": "looprpc.ListSwapsResponse", + "payload": { + "swaps": [], + "next_start_time": "0" + } + } + }, + { + "time_ms": 236, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"next_start_time\": \"0\",\n", + " \"swaps\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 237, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/swaps/09_loop-swapinfo-id-flag-parse-error.json b/cmd/loop/testdata/sessions/swaps/09_loop-swapinfo-id-flag-parse-error.json new file mode 100644 index 000000000..9bb6bbca1 --- /dev/null +++ b/cmd/loop/testdata/sessions/swaps/09_loop-swapinfo-id-flag-parse-error.json @@ -0,0 +1,39 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "swapinfo", + "--network", + "regtest", + "--id", + "46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "invalid value \"46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\" for flag -id: strconv.ParseUint: parsing \"46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\": invalid syntax", + "duration": 2097605, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stderr", + "data": { + "lines": [ + "Incorrect Usage: invalid value \"46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\" for flag -id: strconv.ParseUint: parsing \"46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\": invalid syntax\n", + "\n", + "[loop] invalid value \"46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\" for flag -id: strconv.ParseUint: parsing \"46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\": invalid syntax\n" + ] + } + }, + { + "time_ms": 2, + "kind": "exit", + "data": { + "run_error": "invalid value \"46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\" for flag -id: strconv.ParseUint: parsing \"46b8db64110a6008bd36b423439cbba1e8e76c0264e8a68f442fc22a3c2b4f6b\": invalid syntax" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/swaps/10_loop-swapinfo-id-flag.json b/cmd/loop/testdata/sessions/swaps/10_loop-swapinfo-id-flag.json new file mode 100644 index 000000000..2f4324b90 --- /dev/null +++ b/cmd/loop/testdata/sessions/swaps/10_loop-swapinfo-id-flag.json @@ -0,0 +1,37 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "swapinfo", + "--network", + "regtest", + "--id", + "1" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "run_error": "invalid swap ID", + "duration": 6122492, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 6, + "kind": "stderr", + "data": { + "lines": [ + "[loop] invalid swap ID\n" + ] + } + }, + { + "time_ms": 6, + "kind": "exit", + "data": { + "run_error": "invalid swap ID" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/swaps/11_loop-abandonswap-success.json b/cmd/loop/testdata/sessions/swaps/11_loop-abandonswap-success.json new file mode 100644 index 000000000..794f793e3 --- /dev/null +++ b/cmd/loop/testdata/sessions/swaps/11_loop-abandonswap-success.json @@ -0,0 +1,57 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "abandonswap", + "--network", + "regtest", + "--i_know_what_i_am_doing", + "2f46a232e0f584fadf2abc7e7aaeefab0176f21e0fba5344a10e21838ec390fe" + ], + "env": { + "HOME": "/home/user" + }, + "version": "0.31.7-beta commit=v0.31.7-beta-28-g6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845 commit_hash=6d8ddfc59ddc2dcfd1a9b4e4b3c53a9cf15dd845", + "duration": 194305706, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/AbandonSwap", + "event": "request", + "message_type": "looprpc.AbandonSwapRequest", + "payload": { + "id": "L0aiMuD1hPrfKrx+eq7vqwF28h4PulNEoQ4hg47DkP4=", + "i_know_what_i_am_doing": true + } + } + }, + { + "time_ms": 193, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/AbandonSwap", + "event": "response", + "message_type": "looprpc.AbandonSwapResponse", + "payload": {} + } + }, + { + "time_ms": 193, + "kind": "stdout", + "data": { + "lines": [ + "{}\n" + ] + } + }, + { + "time_ms": 194, + "kind": "exit", + "data": {} + } + ] +} From 90c180c926179aa6cb895e95a5b1f044c06a8741 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Thu, 9 Apr 2026 02:32:33 -0500 Subject: [PATCH 16/18] testdata: add sessions for newly added features --- ...p-static-listdeposits-opening_channel.json | 65 +++++ ...static-listdeposits-channel_published.json | 65 +++++ ...stdeposits-channel_published-nonempty.json | 103 ++++++++ ...0_loop-static-summary-channels-opened.json | 78 ++++++ ...oop-static-in-max-swap-fee-sat-reject.json | 146 +++++++++++ ...oop-static-in-max-swap-fee-ppm-reject.json | 146 +++++++++++ .../23_loop-static-in-max-swap-fee-both.json | 230 ++++++++++++++++++ ...op-static-in-max-swap-fee-sat-success.json | 221 +++++++++++++++++ .../01_loop-static-openchannel-help.json | 98 ++++++++ ...op-static-openchannel-missing-node-id.json | 39 +++ ...nchannel-missing-local-amt-or-fundmax.json | 41 ++++ ...penchannel-local-amt-fundmax-conflict.json | 44 ++++ ...static-openchannel-insufficient-funds.json | 93 +++++++ .../06_loop-static-openchannel-success.json | 102 ++++++++ 14 files changed, 1471 insertions(+) create mode 100644 cmd/loop/testdata/sessions/static-filters/12_loop-static-listdeposits-opening_channel.json create mode 100644 cmd/loop/testdata/sessions/static-filters/13_loop-static-listdeposits-channel_published.json create mode 100644 cmd/loop/testdata/sessions/static-filters/14_loop-static-listdeposits-channel_published-nonempty.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/20_loop-static-summary-channels-opened.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/21_loop-static-in-max-swap-fee-sat-reject.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/22_loop-static-in-max-swap-fee-ppm-reject.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/23_loop-static-in-max-swap-fee-both.json create mode 100644 cmd/loop/testdata/sessions/static-loop-in/24_loop-static-in-max-swap-fee-sat-success.json create mode 100644 cmd/loop/testdata/sessions/static-openchannel/01_loop-static-openchannel-help.json create mode 100644 cmd/loop/testdata/sessions/static-openchannel/02_loop-static-openchannel-missing-node-id.json create mode 100644 cmd/loop/testdata/sessions/static-openchannel/03_loop-static-openchannel-missing-local-amt-or-fundmax.json create mode 100644 cmd/loop/testdata/sessions/static-openchannel/04_loop-static-openchannel-local-amt-fundmax-conflict.json create mode 100644 cmd/loop/testdata/sessions/static-openchannel/05_loop-static-openchannel-insufficient-funds.json create mode 100644 cmd/loop/testdata/sessions/static-openchannel/06_loop-static-openchannel-success.json diff --git a/cmd/loop/testdata/sessions/static-filters/12_loop-static-listdeposits-opening_channel.json b/cmd/loop/testdata/sessions/static-filters/12_loop-static-listdeposits-opening_channel.json new file mode 100644 index 000000000..1f0ae9b9c --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/12_loop-static-listdeposits-opening_channel.json @@ -0,0 +1,65 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "listdeposits", + "--filter", + "opening_channel", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "duration": 27158800, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 9, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "OPENING_CHANNEL", + "outpoints": [] + } + } + }, + { + "time_ms": 26, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [] + } + } + }, + { + "time_ms": 27, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 27, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-filters/13_loop-static-listdeposits-channel_published.json b/cmd/loop/testdata/sessions/static-filters/13_loop-static-listdeposits-channel_published.json new file mode 100644 index 000000000..8d00b5012 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/13_loop-static-listdeposits-channel_published.json @@ -0,0 +1,65 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "listdeposits", + "--filter", + "channel_published", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "duration": 21086043, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "CHANNEL_PUBLISHED", + "outpoints": [] + } + } + }, + { + "time_ms": 20, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [] + } + } + }, + { + "time_ms": 21, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": []\n", + "}\n" + ] + } + }, + { + "time_ms": 21, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-filters/14_loop-static-listdeposits-channel_published-nonempty.json b/cmd/loop/testdata/sessions/static-filters/14_loop-static-listdeposits-channel_published-nonempty.json new file mode 100644 index 000000000..1cb79c441 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-filters/14_loop-static-listdeposits-channel_published-nonempty.json @@ -0,0 +1,103 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "listdeposits", + "--filter", + "channel_published", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "duration": 19305482, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 3, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "CHANNEL_PUBLISHED", + "outpoints": [] + } + } + }, + { + "time_ms": 18, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "eny+m5DyPUeqkusQqdMj96zm6eqrW3c3nGNCLBXaGcg=", + "state": "CHANNEL_PUBLISHED", + "outpoint": "0e70673c1da3343648c26f779555346f30d235314838b1160826d0d5c29b4fba:1", + "value": "500000", + "confirmation_height": "127", + "blocks_until_expiry": "14395", + "swap_hash": "" + }, + { + "id": "/5pDsggvkGouJ1iTQiDEzjI5PrKCOyklF64IHhba3tk=", + "state": "CHANNEL_PUBLISHED", + "outpoint": "d2d6e50f157f0d31b8688a4af4f064edf3454714e92369b2c8c4d82477edbaca:0", + "value": "1000000", + "confirmation_height": "127", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 18, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"filtered_deposits\": [\n", + " {\n", + " \"blocks_until_expiry\": \"14395\",\n", + " \"confirmation_height\": \"127\",\n", + " \"id\": \"7a7cbe9b90f23d47aa92eb10a9d323f7ace6e9eaab5b77379c63422c15da19c8\",\n", + " \"outpoint\": \"0e70673c1da3343648c26f779555346f30d235314838b1160826d0d5c29b4fba:1\",\n", + " \"state\": \"CHANNEL_PUBLISHED\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " },\n", + " {\n", + " \"blocks_until_expiry\": \"14395\",\n", + " \"confirmation_height\": \"127\",\n", + " \"id\": \"ff9a43b2082f906a2e2758934220c4ce32393eb2823b292517ae081e16daded9\",\n", + " \"outpoint\": \"d2d6e50f157f0d31b8688a4af4f064edf3454714e92369b2c8c4d82477edbaca:0\",\n", + " \"state\": \"CHANNEL_PUBLISHED\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"1000000\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 19, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/20_loop-static-summary-channels-opened.json b/cmd/loop/testdata/sessions/static-loop-in/20_loop-static-summary-channels-opened.json new file mode 100644 index 000000000..765f4fcce --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/20_loop-static-summary-channels-opened.json @@ -0,0 +1,78 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "summary", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "duration": 15210220, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 5, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetStaticAddressSummary", + "event": "request", + "message_type": "looprpc.StaticAddressSummaryRequest", + "payload": {} + } + }, + { + "time_ms": 14, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetStaticAddressSummary", + "event": "response", + "message_type": "looprpc.StaticAddressSummaryResponse", + "payload": { + "static_address": "bcrt1p604kzzh28764kkw45yps48weergwljggamhhe7tqfglzjzang6cs43f2m2", + "relative_expiry_blocks": "14400", + "total_num_deposits": 3, + "value_unconfirmed_satoshis": "496150", + "value_deposited_satoshis": "0", + "value_expired_satoshis": "0", + "value_withdrawn_satoshis": "0", + "value_looped_in_satoshis": "550000", + "value_htlc_timeout_sweeps_satoshis": "0", + "value_channels_opened": "1500000" + } + } + }, + { + "time_ms": 14, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"relative_expiry_blocks\": \"14400\",\n", + " \"static_address\": \"bcrt1p604kzzh28764kkw45yps48weergwljggamhhe7tqfglzjzang6cs43f2m2\",\n", + " \"total_num_deposits\": 3,\n", + " \"value_channels_opened\": \"1500000\",\n", + " \"value_deposited_satoshis\": \"0\",\n", + " \"value_expired_satoshis\": \"0\",\n", + " \"value_htlc_timeout_sweeps_satoshis\": \"0\",\n", + " \"value_looped_in_satoshis\": \"550000\",\n", + " \"value_unconfirmed_satoshis\": \"496150\",\n", + " \"value_withdrawn_satoshis\": \"0\"\n", + "}\n" + ] + } + }, + { + "time_ms": 15, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/21_loop-static-in-max-swap-fee-sat-reject.json b/cmd/loop/testdata/sessions/static-loop-in/21_loop-static-in-max-swap-fee-sat-reject.json new file mode 100644 index 000000000..0b96cefb0 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/21_loop-static-in-max-swap-fee-sat-reject.json @@ -0,0 +1,146 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "in", + "--utxo", + "da52bf383c4fe5c684221c311fc5756ccaee211b6c6e6f5ccc159622a6039271:1", + "--max_swap_fee_sat", + "1", + "--force", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "run_error": "quoted swap fee 1490 sat exceeds maximum allowed 1 sat", + "duration": 48978654, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "DEPOSITED", + "outpoints": [] + } + } + }, + { + "time_ms": 18, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "2KWFNthHKHO54uFldGgyg2D9oLlCMc/F4pkAyrc12oQ=", + "state": "DEPOSITED", + "outpoint": "f2280f0f086273be73bde92fd9b982208338a5ecebbe93b83b00c77c4d2f8d1b:0", + "value": "550000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + }, + { + "id": "f3yMP2jfWm1HJvlHui2aqPOJdxVAjN20pkUWgTZzJ4U=", + "state": "DEPOSITED", + "outpoint": "fc6844a2945bb7201c0acf11080be17f912849d32a3d1e95b4cd3a226b153c29:1", + "value": "496150", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + }, + { + "id": "gncTI+ldykA9lm9wqIvjnvCkde9qp4aUBEupuHMErGM=", + "state": "DEPOSITED", + "outpoint": "da52bf383c4fe5c684221c311fc5756ccaee211b6c6e6f5ccc159622a6039271:1", + "value": "500000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + }, + { + "id": "KxBwhGgyaujysguxE6F/goisqKhEOM8GCXuWQvlXtrc=", + "state": "DEPOSITED", + "outpoint": "61d11eb2957092e3e463d5c7295c3f9ff94acdacecad5271dec97baedb3d2da7:1", + "value": "1000000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 18, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "0", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [ + "da52bf383c4fe5c684221c311fc5756ccaee211b6c6e6f5ccc159622a6039271:1" + ], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 48, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1490", + "htlc_publish_fee_sat": "0", + "cltv_delta": 0, + "conf_target": 0, + "quoted_amt": "500000" + } + } + }, + { + "time_ms": 48, + "kind": "stderr", + "data": { + "lines": [ + "[loop] quoted swap fee 1490 sat exceeds maximum allowed 1 sat\n" + ] + } + }, + { + "time_ms": 48, + "kind": "exit", + "data": { + "run_error": "quoted swap fee 1490 sat exceeds maximum allowed 1 sat" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/22_loop-static-in-max-swap-fee-ppm-reject.json b/cmd/loop/testdata/sessions/static-loop-in/22_loop-static-in-max-swap-fee-ppm-reject.json new file mode 100644 index 000000000..b78ede676 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/22_loop-static-in-max-swap-fee-ppm-reject.json @@ -0,0 +1,146 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "in", + "--utxo", + "da52bf383c4fe5c684221c311fc5756ccaee211b6c6e6f5ccc159622a6039271:1", + "--max_swap_fee_ppm", + "1", + "--force", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "run_error": "ppm cap rounds to 0 sat for swap amount 500000; use --max_swap_fee_sat instead", + "duration": 40071886, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "DEPOSITED", + "outpoints": [] + } + } + }, + { + "time_ms": 14, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "2KWFNthHKHO54uFldGgyg2D9oLlCMc/F4pkAyrc12oQ=", + "state": "DEPOSITED", + "outpoint": "f2280f0f086273be73bde92fd9b982208338a5ecebbe93b83b00c77c4d2f8d1b:0", + "value": "550000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + }, + { + "id": "f3yMP2jfWm1HJvlHui2aqPOJdxVAjN20pkUWgTZzJ4U=", + "state": "DEPOSITED", + "outpoint": "fc6844a2945bb7201c0acf11080be17f912849d32a3d1e95b4cd3a226b153c29:1", + "value": "496150", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + }, + { + "id": "gncTI+ldykA9lm9wqIvjnvCkde9qp4aUBEupuHMErGM=", + "state": "DEPOSITED", + "outpoint": "da52bf383c4fe5c684221c311fc5756ccaee211b6c6e6f5ccc159622a6039271:1", + "value": "500000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + }, + { + "id": "KxBwhGgyaujysguxE6F/goisqKhEOM8GCXuWQvlXtrc=", + "state": "DEPOSITED", + "outpoint": "61d11eb2957092e3e463d5c7295c3f9ff94acdacecad5271dec97baedb3d2da7:1", + "value": "1000000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 14, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "0", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [ + "da52bf383c4fe5c684221c311fc5756ccaee211b6c6e6f5ccc159622a6039271:1" + ], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 39, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1490", + "htlc_publish_fee_sat": "0", + "cltv_delta": 0, + "conf_target": 0, + "quoted_amt": "500000" + } + } + }, + { + "time_ms": 40, + "kind": "stderr", + "data": { + "lines": [ + "[loop] ppm cap rounds to 0 sat for swap amount 500000; use --max_swap_fee_sat instead\n" + ] + } + }, + { + "time_ms": 40, + "kind": "exit", + "data": { + "run_error": "ppm cap rounds to 0 sat for swap amount 500000; use --max_swap_fee_sat instead" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/23_loop-static-in-max-swap-fee-both.json b/cmd/loop/testdata/sessions/static-loop-in/23_loop-static-in-max-swap-fee-both.json new file mode 100644 index 000000000..329b8e5b0 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/23_loop-static-in-max-swap-fee-both.json @@ -0,0 +1,230 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "in", + "--utxo", + "da52bf383c4fe5c684221c311fc5756ccaee211b6c6e6f5ccc159622a6039271:1", + "--max_swap_fee_sat", + "5000", + "--max_swap_fee_ppm", + "3000", + "--force", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "duration": 1091888190, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "DEPOSITED", + "outpoints": [] + } + } + }, + { + "time_ms": 15, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "2KWFNthHKHO54uFldGgyg2D9oLlCMc/F4pkAyrc12oQ=", + "state": "DEPOSITED", + "outpoint": "f2280f0f086273be73bde92fd9b982208338a5ecebbe93b83b00c77c4d2f8d1b:0", + "value": "550000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + }, + { + "id": "f3yMP2jfWm1HJvlHui2aqPOJdxVAjN20pkUWgTZzJ4U=", + "state": "DEPOSITED", + "outpoint": "fc6844a2945bb7201c0acf11080be17f912849d32a3d1e95b4cd3a226b153c29:1", + "value": "496150", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + }, + { + "id": "gncTI+ldykA9lm9wqIvjnvCkde9qp4aUBEupuHMErGM=", + "state": "DEPOSITED", + "outpoint": "da52bf383c4fe5c684221c311fc5756ccaee211b6c6e6f5ccc159622a6039271:1", + "value": "500000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + }, + { + "id": "KxBwhGgyaujysguxE6F/goisqKhEOM8GCXuWQvlXtrc=", + "state": "DEPOSITED", + "outpoint": "61d11eb2957092e3e463d5c7295c3f9ff94acdacecad5271dec97baedb3d2da7:1", + "value": "1000000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 15, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "0", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [ + "da52bf383c4fe5c684221c311fc5756ccaee211b6c6e6f5ccc159622a6039271:1" + ], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 50, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1490", + "htlc_publish_fee_sat": "0", + "cltv_delta": 0, + "conf_target": 0, + "quoted_amt": "500000" + } + } + }, + { + "time_ms": 50, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/StaticAddressLoopIn", + "event": "request", + "message_type": "looprpc.StaticAddressLoopInRequest", + "payload": { + "outpoints": [ + "da52bf383c4fe5c684221c311fc5756ccaee211b6c6e6f5ccc159622a6039271:1" + ], + "max_swap_fee_satoshis": "1500", + "last_hop": "", + "label": "", + "initiator": "loop-cli", + "route_hints": [], + "private": false, + "payment_timeout_seconds": 60, + "amount": "0", + "fast": false + } + } + }, + { + "time_ms": 1084, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/StaticAddressLoopIn", + "event": "response", + "message_type": "looprpc.StaticAddressLoopInResponse", + "payload": { + "swap_hash": "OECpzksa//ExHwhUSczT207uzQfeHw0CrU8E3RmTzio=", + "state": "SignHtlcTx", + "amount": "500000", + "htlc_cltv": 1138, + "quoted_swap_fee_satoshis": "1490", + "max_swap_fee_satoshis": "1500", + "initiation_height": 138, + "protocol_version": "V0", + "label": "", + "initiator": "loop-cli", + "payment_timeout_seconds": 60, + "used_deposits": [ + { + "id": "gncTI+ldykA9lm9wqIvjnvCkde9qp4aUBEupuHMErGM=", + "state": "LOOPING_IN", + "outpoint": "da52bf383c4fe5c684221c311fc5756ccaee211b6c6e6f5ccc159622a6039271:1", + "value": "500000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ], + "swap_amount": "500000", + "change": "0", + "fast": false + } + } + }, + { + "time_ms": 1090, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"amount\": \"500000\",\n", + " \"change\": \"0\",\n", + " \"fast\": false,\n", + " \"htlc_cltv\": 1138,\n", + " \"initiation_height\": 138,\n", + " \"initiator\": \"loop-cli\",\n", + " \"label\": \"\",\n", + " \"max_swap_fee_satoshis\": \"1500\",\n", + " \"payment_timeout_seconds\": 60,\n", + " \"protocol_version\": \"V0\",\n", + " \"quoted_swap_fee_satoshis\": \"1490\",\n", + " \"state\": \"SignHtlcTx\",\n", + " \"swap_amount\": \"500000\",\n", + " \"swap_hash\": \"3840a9ce4b1afff1311f085449ccd3db4eeecd07de1f0d02ad4f04dd1993ce2a\",\n", + " \"used_deposits\": [\n", + " {\n", + " \"blocks_until_expiry\": \"14395\",\n", + " \"confirmation_height\": \"133\",\n", + " \"id\": \"82771323e95dca403d966f70a88be39ef0a475ef6aa78694044ba9b87304ac63\",\n", + " \"outpoint\": \"da52bf383c4fe5c684221c311fc5756ccaee211b6c6e6f5ccc159622a6039271:1\",\n", + " \"state\": \"LOOPING_IN\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"500000\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 1091, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-loop-in/24_loop-static-in-max-swap-fee-sat-success.json b/cmd/loop/testdata/sessions/static-loop-in/24_loop-static-in-max-swap-fee-sat-success.json new file mode 100644 index 000000000..e108e966b --- /dev/null +++ b/cmd/loop/testdata/sessions/static-loop-in/24_loop-static-in-max-swap-fee-sat-success.json @@ -0,0 +1,221 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "in", + "--utxo", + "f2280f0f086273be73bde92fd9b982208338a5ecebbe93b83b00c77c4d2f8d1b:0", + "--amt", + "500000", + "--max_swap_fee_sat", + "5000", + "--force", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "duration": 1058534736, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "request", + "message_type": "looprpc.ListStaticAddressDepositsRequest", + "payload": { + "state_filter": "DEPOSITED", + "outpoints": [] + } + } + }, + { + "time_ms": 19, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/ListStaticAddressDeposits", + "event": "response", + "message_type": "looprpc.ListStaticAddressDepositsResponse", + "payload": { + "filtered_deposits": [ + { + "id": "2KWFNthHKHO54uFldGgyg2D9oLlCMc/F4pkAyrc12oQ=", + "state": "DEPOSITED", + "outpoint": "f2280f0f086273be73bde92fd9b982208338a5ecebbe93b83b00c77c4d2f8d1b:0", + "value": "550000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + }, + { + "id": "f3yMP2jfWm1HJvlHui2aqPOJdxVAjN20pkUWgTZzJ4U=", + "state": "DEPOSITED", + "outpoint": "fc6844a2945bb7201c0acf11080be17f912849d32a3d1e95b4cd3a226b153c29:1", + "value": "496150", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + }, + { + "id": "KxBwhGgyaujysguxE6F/goisqKhEOM8GCXuWQvlXtrc=", + "state": "DEPOSITED", + "outpoint": "61d11eb2957092e3e463d5c7295c3f9ff94acdacecad5271dec97baedb3d2da7:1", + "value": "1000000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ] + } + } + }, + { + "time_ms": 19, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "request", + "message_type": "looprpc.QuoteRequest", + "payload": { + "amt": "500000", + "conf_target": 0, + "external_htlc": false, + "swap_publication_deadline": "0", + "loop_in_last_hop": "", + "loop_in_route_hints": [], + "private": false, + "deposit_outpoints": [ + "f2280f0f086273be73bde92fd9b982208338a5ecebbe93b83b00c77c4d2f8d1b:0" + ], + "asset_info": null, + "auto_select_deposits": false, + "fast": false + } + } + }, + { + "time_ms": 45, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/GetLoopInQuote", + "event": "response", + "message_type": "looprpc.InQuoteResponse", + "payload": { + "swap_fee_sat": "1490", + "htlc_publish_fee_sat": "0", + "cltv_delta": 0, + "conf_target": 0, + "quoted_amt": "500000" + } + } + }, + { + "time_ms": 45, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/StaticAddressLoopIn", + "event": "request", + "message_type": "looprpc.StaticAddressLoopInRequest", + "payload": { + "outpoints": [ + "f2280f0f086273be73bde92fd9b982208338a5ecebbe93b83b00c77c4d2f8d1b:0" + ], + "max_swap_fee_satoshis": "5000", + "last_hop": "", + "label": "", + "initiator": "loop-cli", + "route_hints": [], + "private": false, + "payment_timeout_seconds": 60, + "amount": "500000", + "fast": false + } + } + }, + { + "time_ms": 1058, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/StaticAddressLoopIn", + "event": "response", + "message_type": "looprpc.StaticAddressLoopInResponse", + "payload": { + "swap_hash": "bPQ+uBfvKnG7IFuyRFhT+1mpeulpW0MjKT9IIaGrJQM=", + "state": "SignHtlcTx", + "amount": "550000", + "htlc_cltv": 1138, + "quoted_swap_fee_satoshis": "1490", + "max_swap_fee_satoshis": "5000", + "initiation_height": 138, + "protocol_version": "V0", + "label": "", + "initiator": "loop-cli", + "payment_timeout_seconds": 60, + "used_deposits": [ + { + "id": "2KWFNthHKHO54uFldGgyg2D9oLlCMc/F4pkAyrc12oQ=", + "state": "LOOPING_IN", + "outpoint": "f2280f0f086273be73bde92fd9b982208338a5ecebbe93b83b00c77c4d2f8d1b:0", + "value": "550000", + "confirmation_height": "133", + "blocks_until_expiry": "14395", + "swap_hash": "" + } + ], + "swap_amount": "500000", + "change": "50000", + "fast": false + } + } + }, + { + "time_ms": 1058, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"amount\": \"550000\",\n", + " \"change\": \"50000\",\n", + " \"fast\": false,\n", + " \"htlc_cltv\": 1138,\n", + " \"initiation_height\": 138,\n", + " \"initiator\": \"loop-cli\",\n", + " \"label\": \"\",\n", + " \"max_swap_fee_satoshis\": \"5000\",\n", + " \"payment_timeout_seconds\": 60,\n", + " \"protocol_version\": \"V0\",\n", + " \"quoted_swap_fee_satoshis\": \"1490\",\n", + " \"state\": \"SignHtlcTx\",\n", + " \"swap_amount\": \"500000\",\n", + " \"swap_hash\": \"6cf43eb817ef2a71bb205bb2445853fb59a97ae9695b4323293f4821a1ab2503\",\n", + " \"used_deposits\": [\n", + " {\n", + " \"blocks_until_expiry\": \"14395\",\n", + " \"confirmation_height\": \"133\",\n", + " \"id\": \"d8a58536d8472873b9e2e1657468328360fda0b94231cfc5e29900cab735da84\",\n", + " \"outpoint\": \"f2280f0f086273be73bde92fd9b982208338a5ecebbe93b83b00c77c4d2f8d1b:0\",\n", + " \"state\": \"LOOPING_IN\",\n", + " \"swap_hash\": \"\",\n", + " \"value\": \"550000\"\n", + " }\n", + " ]\n", + "}\n" + ] + } + }, + { + "time_ms": 1058, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-openchannel/01_loop-static-openchannel-help.json b/cmd/loop/testdata/sessions/static-openchannel/01_loop-static-openchannel-help.json new file mode 100644 index 000000000..0a20ef0c3 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-openchannel/01_loop-static-openchannel-help.json @@ -0,0 +1,98 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "openchannel", + "--help", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "duration": 2743367, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 2, + "kind": "stdout", + "data": { + "lines": [ + "NAME:\n", + " loop static openchannel - Open a channel to a an existing peer.\n", + "\n", + "USAGE:\n", + " loop static openchannel [options]\n", + "\n", + "DESCRIPTION:\n", + " \n", + " Attempt to open a new channel to an existing peer with the key \n", + " node-key.\n", + "\n", + " The channel will be initialized with local-amt satoshis locally and\n", + " push-amt satoshis for the remote node. Note that the push-amt is\n", + " deducted from the specified local-amt which implies that the local-amt\n", + " must be greater than the push-amt. Also note that specifying push-amt\n", + " means you give that amount to the remote node as part of the channel\n", + " opening. Once the channel is open, a channelPoint (txid:vout) of the\n", + " funding output is returned.\n", + "\n", + " If the remote peer supports the option upfront shutdown feature bit\n", + " (query listpeers to see their supported feature bits), an address to\n", + " enforce payout of funds on cooperative close can optionally be provided.\n", + " Note that if you set this value, you will not be able to cooperatively\n", + " close out to another address.\n", + "\n", + " One can also specify a short string memo to record some useful\n", + " information about the channel using the --memo argument. This is stored\n", + " locally only, and is purely for reference. It has no bearing on the\n", + " channel's operation. Max allowed length is 500 characters.\n", + "\n", + "OPTIONS:\n", + " --node_key string the identity public key of the target node/peer serialized in compressed format\n", + " --local_amt int the number of satoshis the wallet should commit to the channel (default: 0)\n", + " --base_fee_msat uint the base fee in milli-satoshis that will be charged for each forwarded HTLC, regardless of payment size (default: 0)\n", + " --fee_rate_ppm uint the fee rate ppm (parts per million) that will be charged proportionally based on the value of each forwarded HTLC, the lowest possible rate is 0 with a granularity of 0.000001 (millionths) (default: 0)\n", + " --push_amt int the number of satoshis to give the remote side as part of the initial commitment state, this is equivalent to first opening a channel and sending the remote party funds, but done all in one step (default: 0)\n", + " --sat_per_vbyte uint (optional) a manual fee expressed in sat/vbyte that should be used when crafting the transaction (default: 0)\n", + " --private make the channel private, such that it won't be announced to the greater network, and nodes other than the two channel endpoints must be explicitly told about it to be able to route through it (default: false)\n", + " --min_htlc_msat int (optional) the minimum value we will require for incoming HTLCs on the channel (default: 0)\n", + " --remote_csv_delay uint (optional) the number of blocks we will require our channel counterparty to wait before accessing its funds in case of unilateral close. If this is not set, we will scale the value according to the channel size (default: 0)\n", + " --max_local_csv uint (optional) the maximum number of blocks that we will allow the remote peer to require we wait before accessing our funds in the case of a unilateral close. (default: 0)\n", + " --close_address string (optional) an address to enforce payout of our funds to on cooperative close. Note that if this value is set on channel open, you will *not* be able to cooperatively close to a different address.\n", + " --remote_max_value_in_flight_msat uint (optional) the maximum value in msat that can be pending within the channel at any given time (default: 0)\n", + " --channel_type string (optional) the type of channel to propose to the remote peer (\"tweakless\", \"anchors\", \"taproot\")\n", + " --zero_conf (optional) whether a zero-conf channel open should be attempted. (default: false)\n", + " --scid_alias (optional) whether a scid-alias channel type should be negotiated. (default: false)\n", + " --remote_reserve_sats uint (optional) the minimum number of satoshis we require the remote node to keep as a direct payment. If not specified, a default of 1% of the channel capacity will be used. (default: 0)\n", + " --memo string (optional) a note-to-self containing some useful\n", + " information about the channel. This is stored\n", + " locally only, and is purely for reference. It\n", + " has no bearing on the channel's operation. Max\n", + " allowed length is 500 characters\n", + " --fundmax if set, the wallet will attempt to commit the maximum possible local amount to the channel. This must not be set at the same time as local_amt (default: false)\n", + " --utxo string [ --utxo string ] a utxo specified as outpoint(tx:idx) which will be used to fund a channel. This flag can be repeatedly used to fund a channel with a selection of utxos. The selected funds can either be entirely spent by specifying the fundmax flag or partially by selecting a fraction of the sum of the outpoints in local_amt\n", + " --help, -h show help\n", + "\n", + "GLOBAL OPTIONS:\n", + " --rpcserver string loopd daemon address host:port (default: \"localhost:11010\") [$LOOPCLI_RPCSERVER]\n", + " --network string, -n string the network loop is running on e.g. mainnet, testnet, etc. (default: \"mainnet\") [$LOOPCLI_NETWORK]\n", + " --loopdir string path to loop's base directory (default: ~/.loop) [$LOOPCLI_LOOPDIR]\n", + " --tlscertpath string path to loop's TLS certificate (default: ~/.loop/mainnet/tls.cert) [$LOOPCLI_TLSCERTPATH]\n", + " --macaroonpath string path to macaroon file (default: ~/.loop/mainnet/loop.macaroon) [$LOOPCLI_MACAROONPATH]\n" + ] + } + }, + { + "time_ms": 2, + "kind": "exit", + "data": {} + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-openchannel/02_loop-static-openchannel-missing-node-id.json b/cmd/loop/testdata/sessions/static-openchannel/02_loop-static-openchannel-missing-node-id.json new file mode 100644 index 000000000..b5d11c376 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-openchannel/02_loop-static-openchannel-missing-node-id.json @@ -0,0 +1,39 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "openchannel", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "run_error": "node id argument missing", + "duration": 1421679, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stderr", + "data": { + "lines": [ + "[loop] node id argument missing\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": { + "run_error": "node id argument missing" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-openchannel/03_loop-static-openchannel-missing-local-amt-or-fundmax.json b/cmd/loop/testdata/sessions/static-openchannel/03_loop-static-openchannel-missing-local-amt-or-fundmax.json new file mode 100644 index 000000000..4ce01af41 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-openchannel/03_loop-static-openchannel-missing-local-amt-or-fundmax.json @@ -0,0 +1,41 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "openchannel", + "--node_key", + "02fe50e9103469869b3431ac8d3ef9692ba54a725ef1ea0599d05028f4d81a5bf4", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "run_error": "either local_amt or fundmax must be specified", + "duration": 1498436, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stderr", + "data": { + "lines": [ + "[loop] either local_amt or fundmax must be specified\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": { + "run_error": "either local_amt or fundmax must be specified" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-openchannel/04_loop-static-openchannel-local-amt-fundmax-conflict.json b/cmd/loop/testdata/sessions/static-openchannel/04_loop-static-openchannel-local-amt-fundmax-conflict.json new file mode 100644 index 000000000..2246e2f05 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-openchannel/04_loop-static-openchannel-local-amt-fundmax-conflict.json @@ -0,0 +1,44 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "openchannel", + "--node_key", + "02fe50e9103469869b3431ac8d3ef9692ba54a725ef1ea0599d05028f4d81a5bf4", + "--local_amt", + "500000", + "--fundmax", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "run_error": "local_amt and fundmax are mutually exclusive", + "duration": 1994087, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 1, + "kind": "stderr", + "data": { + "lines": [ + "[loop] local_amt and fundmax are mutually exclusive\n" + ] + } + }, + { + "time_ms": 1, + "kind": "exit", + "data": { + "run_error": "local_amt and fundmax are mutually exclusive" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-openchannel/05_loop-static-openchannel-insufficient-funds.json b/cmd/loop/testdata/sessions/static-openchannel/05_loop-static-openchannel-insufficient-funds.json new file mode 100644 index 000000000..bcfc6397f --- /dev/null +++ b/cmd/loop/testdata/sessions/static-openchannel/05_loop-static-openchannel-insufficient-funds.json @@ -0,0 +1,93 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "openchannel", + "--node_key", + "02fe50e9103469869b3431ac8d3ef9692ba54a725ef1ea0599d05028f4d81a5bf4", + "--local_amt", + "500000", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "run_error": "rpc error: code = Unknown desc = error selecting deposits: insufficient funds to cover swap amount, try manually selecting deposits", + "duration": 32744709, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 8, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/StaticOpenChannel", + "event": "request", + "message_type": "looprpc.StaticOpenChannelRequest", + "payload": { + "open_channel_request": { + "sat_per_vbyte": "0", + "node_pubkey": "Av5Q6RA0aYabNDGsjT75aSulSnJe8eoFmdBQKPTYGlv0", + "node_pubkey_string": "", + "local_funding_amount": "500000", + "push_sat": "0", + "target_conf": 0, + "sat_per_byte": "0", + "private": false, + "min_htlc_msat": "0", + "remote_csv_delay": 0, + "min_confs": 1, + "spend_unconfirmed": false, + "close_address": "", + "funding_shim": null, + "remote_max_value_in_flight_msat": "0", + "remote_max_htlcs": 0, + "max_local_csv": 0, + "commitment_type": "UNKNOWN_COMMITMENT_TYPE", + "zero_conf": false, + "scid_alias": false, + "base_fee": "0", + "fee_rate": "0", + "use_base_fee": false, + "use_fee_rate": false, + "remote_chan_reserve_sat": "0", + "fund_max": false, + "memo": "", + "outpoints": [] + } + } + } + }, + { + "time_ms": 32, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/StaticOpenChannel", + "event": "error", + "error": "rpc error: code = Unknown desc = error selecting deposits: insufficient funds to cover swap amount, try manually selecting deposits" + } + }, + { + "time_ms": 32, + "kind": "stderr", + "data": { + "lines": [ + "[loop] rpc error: code = Unknown desc = error selecting deposits: insufficient funds to cover swap amount, try manually selecting deposits\n" + ] + } + }, + { + "time_ms": 32, + "kind": "exit", + "data": { + "run_error": "rpc error: code = Unknown desc = error selecting deposits: insufficient funds to cover swap amount, try manually selecting deposits" + } + } + ] +} diff --git a/cmd/loop/testdata/sessions/static-openchannel/06_loop-static-openchannel-success.json b/cmd/loop/testdata/sessions/static-openchannel/06_loop-static-openchannel-success.json new file mode 100644 index 000000000..2a75effa3 --- /dev/null +++ b/cmd/loop/testdata/sessions/static-openchannel/06_loop-static-openchannel-success.json @@ -0,0 +1,102 @@ +{ + "metadata": { + "args": [ + "/home/user/bin/loop", + "--network=regtest", + "--rpcserver=localhost:11010", + "--loopdir=/redacted/loop", + "--tlscertpath=/redacted/loop/regtest/tls.cert", + "--macaroonpath=/redacted/loop/regtest/loop.macaroon", + "static", + "openchannel", + "--node_key", + "02fe50e9103469869b3431ac8d3ef9692ba54a725ef1ea0599d05028f4d81a5bf4", + "--fundmax", + "--utxo", + "61d11eb2957092e3e463d5c7295c3f9ff94acdacecad5271dec97baedb3d2da7:1", + "--network", + "regtest" + ], + "env": {}, + "version": "0.33.0-beta commit=v0.32.1-beta-71-gbed29e41d84c34147ca60fb1aa99ecc6f2c8a079 commit_hash=bed29e41d84c34147ca60fb1aa99ecc6f2c8a079", + "duration": 283733780, + "clock_start_unix": 1769407086 + }, + "events": [ + { + "time_ms": 5, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/StaticOpenChannel", + "event": "request", + "message_type": "looprpc.StaticOpenChannelRequest", + "payload": { + "open_channel_request": { + "sat_per_vbyte": "0", + "node_pubkey": "Av5Q6RA0aYabNDGsjT75aSulSnJe8eoFmdBQKPTYGlv0", + "node_pubkey_string": "", + "local_funding_amount": "0", + "push_sat": "0", + "target_conf": 0, + "sat_per_byte": "0", + "private": false, + "min_htlc_msat": "0", + "remote_csv_delay": 0, + "min_confs": 1, + "spend_unconfirmed": false, + "close_address": "", + "funding_shim": null, + "remote_max_value_in_flight_msat": "0", + "remote_max_htlcs": 0, + "max_local_csv": 0, + "commitment_type": "UNKNOWN_COMMITMENT_TYPE", + "zero_conf": false, + "scid_alias": false, + "base_fee": "0", + "fee_rate": "0", + "use_base_fee": false, + "use_fee_rate": false, + "remote_chan_reserve_sat": "0", + "fund_max": true, + "memo": "", + "outpoints": [ + { + "txid_bytes": "", + "txid_str": "61d11eb2957092e3e463d5c7295c3f9ff94acdacecad5271dec97baedb3d2da7", + "output_index": 1 + } + ] + } + } + } + }, + { + "time_ms": 283, + "kind": "grpc", + "data": { + "method": "/looprpc.SwapClient/StaticOpenChannel", + "event": "response", + "message_type": "looprpc.StaticOpenChannelResponse", + "payload": { + "channel_open_outpoint": "20df544cf41467846a9951a3b80094f3b133dfabe81ca5808627a7aecb0e07e0:0" + } + } + }, + { + "time_ms": 283, + "kind": "stdout", + "data": { + "lines": [ + "{\n", + " \"channel_open_outpoint\": \"20df544cf41467846a9951a3b80094f3b133dfabe81ca5808627a7aecb0e07e0:0\"\n", + "}\n" + ] + } + }, + { + "time_ms": 283, + "kind": "exit", + "data": {} + } + ] +} From 07631c397fce47656826e3b07eabc611f0e52422 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Mon, 4 May 2026 01:23:05 -0500 Subject: [PATCH 17/18] cmd/loop: bless text-only session updates Add an env-gated bless mode to recorded-session replay so CLI-only text changes can refresh fixtures without a live recording pass. The replay still uses the recorded gRPC traffic, stdin, and environment, and it only rewrites stdout, stderr, and run_error after the command preserves the recorded success or failure shape. Keep the updater strict by refusing to bless sessions when replay leaves recorded gRPC events unconsumed, and add focused tests for the rewrite rules. Document the bless workflow next to the session fixtures, including the need for -count=1 so the Go test cache does not skip updates. --- cmd/loop/session_fixture_update_test.go | 618 ++++++++++++++++++++++++ cmd/loop/session_replay_test.go | 94 +++- cmd/loop/testdata/sessions/AGENTS.md | 12 + 3 files changed, 715 insertions(+), 9 deletions(-) create mode 100644 cmd/loop/session_fixture_update_test.go diff --git a/cmd/loop/session_fixture_update_test.go b/cmd/loop/session_fixture_update_test.go new file mode 100644 index 000000000..0c6418158 --- /dev/null +++ b/cmd/loop/session_fixture_update_test.go @@ -0,0 +1,618 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "path/filepath" + "runtime" + "slices" + "strconv" + "testing" + + "github.com/stretchr/testify/require" +) + +// updateRecordedSessionsEnvVar enables replay bless mode for text-only fixture +// updates. +const updateRecordedSessionsEnvVar = "LOOP_UPDATE_RECORDED_SESSIONS" + +// replayedSessionOutput captures the user-visible output produced by an +// offline session replay. +type replayedSessionOutput struct { + stdout string + stderr string + stdoutChunks []string + stderrChunks []string + runError *string +} + +// updateRecordedSessionsEnabled reports whether replay should bless text-only +// fixture updates. +func updateRecordedSessionsEnabled() (bool, error) { + raw, ok := os.LookupEnv(updateRecordedSessionsEnvVar) + if !ok { + return false, nil + } + + enabled, err := strconv.ParseBool(raw) + if err != nil { + return false, fmt.Errorf("invalid %s value %q", + updateRecordedSessionsEnvVar, raw) + } + + return enabled, nil +} + +// sessionFixturePath resolves a relative fixture path under +// cmd/loop/testdata/sessions to an absolute path. +func sessionFixturePath(rel string) (string, error) { + _, filename, _, ok := runtime.Caller(0) + if !ok { + return "", fmt.Errorf("locate session fixture dir") + } + + loopDir := filepath.Dir(filename) + + return filepath.Join( + loopDir, "testdata", "sessions", filepath.FromSlash(rel), + ), nil +} + +// loadSessionFilePath reads and decodes a session fixture from disk. +func loadSessionFilePath(path string) (sessionFile, error) { + blob, err := os.ReadFile(path) + if err != nil { + return sessionFile{}, err + } + + var fixture sessionFile + if err := json.Unmarshal(blob, &fixture); err != nil { + return sessionFile{}, err + } + + return fixture, nil +} + +// writeSessionFilePath writes a session fixture using the recorder's JSON +// formatting. +func writeSessionFilePath(path string, fixture sessionFile) error { + file, err := os.Create(path) + if err != nil { + return err + } + defer file.Close() + + encoder := json.NewEncoder(file) + encoder.SetIndent("", " ") + + return encoder.Encode(fixture) +} + +// maybeUpdateSessionFixture rewrites the text-only portions of a recorded +// session if the replay output changed. +func maybeUpdateSessionFixture(path string, fixture sessionFile, + output replayedSessionOutput) (bool, error) { + + updated, changed, err := rewriteSessionFixture(fixture, output) + if err != nil || !changed { + return changed, err + } + + if err := writeSessionFilePath(path, updated); err != nil { + return false, err + } + + return true, nil +} + +// rewriteSessionFixture rewrites stdout, stderr and run_error while leaving +// the rest of the recorded interaction unchanged. +func rewriteSessionFixture(fixture sessionFile, + output replayedSessionOutput) (sessionFile, bool, error) { + + updated := fixture + changed := false + + if !optionalTextEqual(updated.Metadata.RunError, output.runError) { + updated.Metadata.RunError = cloneOptionalString(output.runError) + changed = true + } + + var err error + updated.Events, changed, err = rewriteExitEventRunError( + updated.Events, output.runError, changed, + ) + if err != nil { + return sessionFile{}, false, err + } + + updated.Events, changed, err = rewriteTextEvents( + updated.Events, eventStdout, fixtureStdout(fixture), + output.stdout, output.stdoutChunks, changed, + ) + if err != nil { + return sessionFile{}, false, err + } + + updated.Events, changed, err = rewriteTextEvents( + updated.Events, eventStderr, fixtureStderr(fixture), + output.stderr, output.stderrChunks, changed, + ) + if err != nil { + return sessionFile{}, false, err + } + + return updated, changed, nil +} + +// rewriteExitEventRunError updates the exit payload to match the replayed run +// error. +func rewriteExitEventRunError(events []sessionEvent, runError *string, + changed bool) ([]sessionEvent, bool, error) { + + data, err := json.Marshal(exitPayload{ + RunError: cloneOptionalString(runError), + }) + if err != nil { + return nil, changed, err + } + + updated := append([]sessionEvent(nil), events...) + + for i, event := range slices.Backward(updated) { + if event.Kind != eventExit { + continue + } + + if bytes.Equal(event.Data, data) { + return updated, changed, nil + } + + updated[i].Data = data + + return updated, true, nil + } + + updated = append(updated, sessionEvent{ + Kind: eventExit, + Data: data, + }) + + return updated, true, nil +} + +// rewriteTextEvents updates one text stream when the normalized aggregate text +// changed. +func rewriteTextEvents(events []sessionEvent, kind, recorded, actual string, + chunks []string, changed bool) ([]sessionEvent, bool, error) { + + normalizedEqual := normalizeTimestamps(recorded) == + normalizeTimestamps(actual) + + sourceChunks := chunks + sourceCombined := actual + if normalizedEqual { + sourceChunks = fixtureTextChunksByKind(events, kind) + sourceCombined = recorded + } + + replacement, err := canonicalTextEvents( + events, kind, sourceChunks, sourceCombined, + ) + if err != nil { + return nil, changed, err + } + + if normalizedEqual && textEventsMatch(events, kind, replacement) { + return events, changed, nil + } + + updated, err := replaceTextEvents(events, kind, replacement) + if err != nil { + return nil, changed, err + } + + return updated, true, nil +} + +// canonicalTextEvents builds the canonical replacement events for one text +// stream. +func canonicalTextEvents(events []sessionEvent, kind string, chunks []string, + combined string) ([]sessionEvent, error) { + + indices := textEventIndices(events, kind) + replacement := replacementTextChunks(chunks, combined) + if len(indices) != len(replacement) { + replacement = replacementTextChunks(nil, combined) + } + + return buildReplacementTextEvents(events, indices, kind, replacement) +} + +// replaceTextEvents rewrites the recorded events for a text stream using the +// provided canonical replacement events. +func replaceTextEvents(events []sessionEvent, kind string, + replacements []sessionEvent) ([]sessionEvent, error) { + + indices := textEventIndices(events, kind) + if len(indices) == 0 && len(replacements) == 0 { + return append([]sessionEvent(nil), events...), nil + } + + if replacements == nil { + replacements = []sessionEvent{} + } + + return replaceTextEventsWithCanonical( + events, kind, indices, replacements, + ) +} + +// replaceTextEventsWithCanonical splices canonical replacement events into the +// event stream. +func replaceTextEventsWithCanonical(events []sessionEvent, kind string, + indices []int, replacements []sessionEvent) ([]sessionEvent, error) { + + if len(indices) == len(replacements) { + updated := append([]sessionEvent(nil), events...) + for i, idx := range indices { + updated[idx].Data = replacements[i].Data + } + + return updated, nil + } + + if len(indices) > 0 { + firstIdx := indices[0] + updated := make([]sessionEvent, 0, + len(events)-len(indices)+len(replacements)) + + inserted := false + for i, event := range events { + if event.Kind == kind { + if !inserted && i == firstIdx { + updated = append(updated, replacements...) + inserted = true + } + continue + } + + updated = append(updated, event) + } + + return updated, nil + } + + if len(replacements) == 0 { + return append([]sessionEvent(nil), events...), nil + } + + insertAt := len(events) + for i, event := range events { + if event.Kind == eventExit { + insertAt = i + break + } + } + + updated := make([]sessionEvent, 0, len(events)+len(replacements)) + updated = append(updated, events[:insertAt]...) + updated = append(updated, replacements...) + updated = append(updated, events[insertAt:]...) + + return updated, nil +} + +// textEventsMatch reports whether the existing events already use the canonical +// representation for a text stream. +func textEventsMatch(events []sessionEvent, kind string, + replacements []sessionEvent) bool { + + indices := textEventIndices(events, kind) + if len(indices) != len(replacements) { + return false + } + + for i, idx := range indices { + if events[idx].TimeMS != replacements[i].TimeMS { + return false + } + if !bytes.Equal(events[idx].Data, replacements[i].Data) { + return false + } + } + + return true +} + +// buildReplacementTextEvents creates replacement text events while reusing the +// closest recorded timestamps when possible. +func buildReplacementTextEvents(events []sessionEvent, indices []int, + kind string, chunks []string) ([]sessionEvent, error) { + + if len(chunks) == 0 { + return nil, nil + } + + replacements := make([]sessionEvent, 0, len(chunks)) + for i, chunk := range chunks { + data, err := json.Marshal(newTextPayload(chunk)) + if err != nil { + return nil, err + } + + replacements = append(replacements, sessionEvent{ + TimeMS: replacementEventTime(events, indices, i), + Kind: kind, + Data: data, + }) + } + + return replacements, nil +} + +// replacementEventTime picks a stable timestamp for a replacement text event. +func replacementEventTime(events []sessionEvent, indices []int, idx int) int64 { + if len(indices) == 0 { + return 0 + } + + if idx < len(indices) { + return events[indices[idx]].TimeMS + } + + return events[indices[len(indices)-1]].TimeMS +} + +// textEventIndices returns the indexes of text events with the given kind. +func textEventIndices(events []sessionEvent, kind string) []int { + var indices []int + for i, event := range events { + if event.Kind == kind { + indices = append(indices, i) + } + } + + return indices +} + +// replacementTextChunks returns the recorded write chunks to persist. If the +// hook callbacks did not observe any chunks, fall back to the aggregate text so +// the fixture still captures the visible output. +func replacementTextChunks(chunks []string, combined string) []string { + if len(chunks) > 0 { + return append([]string(nil), chunks...) + } + + if combined == "" { + return nil + } + + return []string{combined} +} + +// fixtureStdout returns the concatenated stdout text stored in a fixture. +func fixtureStdout(fixture sessionFile) string { + return fixtureTextByKind(fixture.Events, eventStdout) +} + +// fixtureStderr returns the concatenated stderr text stored in a fixture. +func fixtureStderr(fixture sessionFile) string { + return fixtureTextByKind(fixture.Events, eventStderr) +} + +// fixtureTextByKind concatenates all text payloads for a given event kind. +func fixtureTextByKind(events []sessionEvent, kind string) string { + var out bytes.Buffer + for _, text := range fixtureTextChunksByKind(events, kind) { + out.WriteString(text) + } + + return out.String() +} + +// fixtureTextChunksByKind returns the decoded text for each event of a given +// kind. +func fixtureTextChunksByKind(events []sessionEvent, kind string) []string { + var chunks []string + for _, event := range events { + if event.Kind != kind { + continue + } + + var payload textPayload + if err := json.Unmarshal(event.Data, &payload); err != nil { + continue + } + + chunks = append(chunks, payload.text()) + } + + return chunks +} + +// optionalTextEqual compares optional text values using the same timestamp +// normalization rules as replay assertions. +func optionalTextEqual(a, b *string) bool { + switch { + case a == nil && b == nil: + return true + + case a == nil || b == nil: + return false + } + + return normalizeTimestamps(*a) == normalizeTimestamps(*b) +} + +// cloneOptionalString copies an optional string. +func cloneOptionalString(value *string) *string { + if value == nil { + return nil + } + + cloned := *value + + return &cloned +} + +// TestUpdateRecordedSessionsEnabled verifies update-mode env parsing. +func TestUpdateRecordedSessionsEnabled(t *testing.T) { + if current, ok := os.LookupEnv(updateRecordedSessionsEnvVar); ok { + t.Setenv(updateRecordedSessionsEnvVar, current) + } else { + t.Setenv(updateRecordedSessionsEnvVar, "false") + } + _ = os.Unsetenv(updateRecordedSessionsEnvVar) + + enabled, err := updateRecordedSessionsEnabled() + require.NoError(t, err) + require.False(t, enabled) + + t.Setenv(updateRecordedSessionsEnvVar, "true") + enabled, err = updateRecordedSessionsEnabled() + require.NoError(t, err) + require.True(t, enabled) + + t.Setenv(updateRecordedSessionsEnvVar, "invalid") + enabled, err = updateRecordedSessionsEnabled() + require.False(t, enabled) + require.EqualError(t, err, + "invalid LOOP_UPDATE_RECORDED_SESSIONS value \"invalid\"") +} + +// TestRewriteSessionFixtureUpdatesMatchingChunks verifies that bless mode +// rewrites text and run errors in place when the write counts are unchanged. +func TestRewriteSessionFixtureUpdatesMatchingChunks(t *testing.T) { + oldErr := "old error" + newErr := "new error" + + fixture := sessionFile{ + Metadata: sessionMetadata{ + RunError: &oldErr, + }, + Events: []sessionEvent{ + textEvent(t, 5, eventStdout, "old stdout\n"), + textEvent(t, 6, eventStderr, "old stderr\n"), + exitEvent(t, 7, &oldErr), + }, + } + + updated, changed, err := rewriteSessionFixture(fixture, + replayedSessionOutput{ + stdout: "new stdout\n", + stderr: "new stderr\n", + stdoutChunks: []string{"new stdout\n"}, + stderrChunks: []string{"new stderr\n"}, + runError: &newErr, + }, + ) + require.NoError(t, err) + require.True(t, changed) + require.Equal(t, &newErr, updated.Metadata.RunError) + require.Equal(t, int64(5), updated.Events[0].TimeMS) + require.Equal(t, "new stdout\n", textEventText(t, updated.Events[0])) + require.Equal(t, int64(6), updated.Events[1].TimeMS) + require.Equal(t, "new stderr\n", textEventText(t, updated.Events[1])) + require.Equal(t, &newErr, exitEventRunError(t, updated.Events[2])) +} + +// TestRewriteSessionFixtureReplacesChangedChunkCount verifies that bless mode +// collapses mismatched write counts to one text event to keep fixture diffs +// small. +func TestRewriteSessionFixtureReplacesChangedChunkCount(t *testing.T) { + fixture := sessionFile{ + Events: []sessionEvent{ + textEvent(t, 10, eventStdout, "old "), + textEvent(t, 11, eventStdout, "text\n"), + exitEvent(t, 12, nil), + }, + } + + updated, changed, err := rewriteSessionFixture(fixture, + replayedSessionOutput{ + stdout: "new output\n", + stdoutChunks: []string{"new ", "output", "\n"}, + }, + ) + require.NoError(t, err) + require.True(t, changed) + require.Len(t, updated.Events, 2) + require.Equal(t, "new output\n", textEventText(t, updated.Events[0])) + require.Equal(t, int64(10), updated.Events[0].TimeMS) + require.Equal(t, eventExit, updated.Events[1].Kind) +} + +// TestRewriteSessionFixtureSkipsNormalizedTimestampNoise verifies that bless +// mode does not churn fixtures when only timestamp formatting differs. +func TestRewriteSessionFixtureSkipsNormalizedTimestampNoise(t *testing.T) { + fixture := sessionFile{ + Events: []sessionEvent{ + textEvent(t, 3, eventStdout, + "2026-05-04T10:00:00-05:00\n"), + exitEvent(t, 4, nil), + }, + } + + updated, changed, err := rewriteSessionFixture(fixture, + replayedSessionOutput{ + stdout: "2026-05-04T15:00:00Z\n", + stdoutChunks: []string{"2026-05-04T15:00:00Z\n"}, + }, + ) + require.NoError(t, err) + require.False(t, changed) + require.Equal(t, fixture, updated) +} + +// textEvent builds a text session event for tests. +func textEvent(t *testing.T, timeMS int64, kind, text string) sessionEvent { + t.Helper() + + data, err := json.Marshal(newTextPayload(text)) + require.NoError(t, err) + + return sessionEvent{ + TimeMS: timeMS, + Kind: kind, + Data: data, + } +} + +// exitEvent builds an exit session event for tests. +func exitEvent(t *testing.T, timeMS int64, runError *string) sessionEvent { + t.Helper() + + data, err := json.Marshal(exitPayload{ + RunError: cloneOptionalString(runError), + }) + require.NoError(t, err) + + return sessionEvent{ + TimeMS: timeMS, + Kind: eventExit, + Data: data, + } +} + +// textEventText decodes a text payload for assertions. +func textEventText(t *testing.T, event sessionEvent) string { + t.Helper() + + var payload textPayload + require.NoError(t, json.Unmarshal(event.Data, &payload)) + + return payload.text() +} + +// exitEventRunError decodes an exit payload for assertions. +func exitEventRunError(t *testing.T, event sessionEvent) *string { + t.Helper() + + var payload exitPayload + require.NoError(t, json.Unmarshal(event.Data, &payload)) + + return payload.RunError +} diff --git a/cmd/loop/session_replay_test.go b/cmd/loop/session_replay_test.go index 4cb453ed3..9c64eb4b3 100644 --- a/cmd/loop/session_replay_test.go +++ b/cmd/loop/session_replay_test.go @@ -609,6 +609,9 @@ func compareJSONWithContext(method, event string, idx int, actual []byte, // NOTE: Do not add t.Parallel() here; the replay harness mutates package-level // globals such as the active transport, clock, and JSON normalization mode. func TestRecordedSessions(t *testing.T) { + updateSessions, err := updateRecordedSessionsEnabled() + require.NoError(t, err) + // Skip the test entirely when there is no session directory. if _, err := fs.ReadDir(sessionsFS, "."); err != nil { if errors.Is(err, fs.ErrNotExist) { @@ -640,6 +643,20 @@ func TestRecordedSessions(t *testing.T) { for _, path := range sessionFiles { t.Run(path, func(t *testing.T) { + var ( + fixture sessionFile + fixturePath string + ) + if updateSessions { + fixturePath, err = sessionFixturePath(path) + require.NoErrorf(t, err, + "resolve fixture path for %s", path) + + fixture, err = loadSessionFilePath(fixturePath) + require.NoErrorf(t, err, + "load fixture for update %s", path) + } + // Force deterministic JSON output for replay. prevDeterministic := forceDeterministicJSON forceDeterministicJSON = true @@ -653,14 +670,17 @@ func TestRecordedSessions(t *testing.T) { // Capture replay output for comparison. var ( - stdoutBuf bytes.Buffer - stderrBuf bytes.Buffer + stdoutBuf bytes.Buffer + stderrBuf bytes.Buffer + stdoutChunks []string + stderrChunks []string ) // Hook stdout for capture. stdoutUnhook, err := hookStdout( os.Stdout, nil, func(p []byte) { stdoutBuf.Write(p) + stdoutChunks = append(stdoutChunks, string(p)) }, ) require.NoErrorf(t, err, "hook stdout for %s", path) @@ -669,6 +689,7 @@ func TestRecordedSessions(t *testing.T) { stderrUnhook, err := hookStderr( os.Stderr, nil, func(p []byte) { stderrBuf.Write(p) + stderrChunks = append(stderrChunks, string(p)) }, ) require.NoErrorf(t, err, "hook stderr for %s", path) @@ -721,17 +742,42 @@ func TestRecordedSessions(t *testing.T) { t, stdinUnhook(), "unhook stdin for %s", path, ) - if replay.runError != nil { - require.Error(t, err, "expected run error") + // Validate the recorded error status matches the + // replay result. + actualRunError := errorString(err) + requireReplayOutcomeClass( + t, path, replay.runError, actualRunError, + ) + if updateSessions { + require.NoErrorf( + t, replay.conn.assertFullyConsumed(), + "grpc replay incomplete for %s", path, + ) + + updated, updateErr := maybeUpdateSessionFixture( + fixturePath, fixture, replayedSessionOutput{ + stdout: stdoutBuf.String(), + stderr: stderrBuf.String(), + stdoutChunks: stdoutChunks, + stderrChunks: stderrChunks, + runError: actualRunError, + }, + ) + require.NoErrorf(t, updateErr, + "update fixture %s", path) + if updated { + t.Logf("updated %s", path) + } + + return + } + + if replay.runError != nil { require.Equalf( - t, *replay.runError, err.Error(), + t, *replay.runError, *actualRunError, "run error mismatch for %s", path, ) - } else { - require.NoErrorf( - t, err, "command failed for %s", path, - ) } require.NoErrorf( @@ -750,6 +796,36 @@ func TestRecordedSessions(t *testing.T) { } } +// errorString converts an error to an optional string pointer. +func errorString(err error) *string { + if err == nil { + return nil + } + + msg := err.Error() + + return &msg +} + +// requireReplayOutcomeClass verifies that replay preserved the recorded +// success/failure shape even when bless mode is updating user-visible text. +func requireReplayOutcomeClass(t *testing.T, path string, expected, + actual *string) { + + t.Helper() + + switch { + case expected == nil && actual == nil: + return + + case expected == nil && actual != nil: + t.Fatalf("command failed for %s: %v", path, *actual) + + case expected != nil && actual == nil: + t.Fatalf("expected run error for %s", path) + } +} + // newRootCommandForReplay returns a root command clone with fresh flag state. func newRootCommandForReplay() *cli.Command { // Clone the root command tree to avoid shared flag state. diff --git a/cmd/loop/testdata/sessions/AGENTS.md b/cmd/loop/testdata/sessions/AGENTS.md index 8eb816182..d5d131efd 100644 --- a/cmd/loop/testdata/sessions/AGENTS.md +++ b/cmd/loop/testdata/sessions/AGENTS.md @@ -45,6 +45,18 @@ Base URL: `http://127.0.0.1:12345` - Session replay now clones the CLI command tree per run, so flag state (`IsSet`) does not leak between sessions. - Historical warning: earlier replays could have sticky flags across runs; if you see odd ordering-dependent failures, re-check that the replay uses the cloned command path. +## Bless mode for CLI text changes +- Use `LOOP_UPDATE_RECORDED_SESSIONS=true` to let `TestRecordedSessions` rewrite recorded `stdout`, `stderr`, and `run_error` values after a successful offline replay. +- Always run bless mode with `-count=1` so the Go test cache does not skip the update: + - `LOOP_UPDATE_RECORDED_SESSIONS=true go test ./cmd/loop -run TestRecordedSessions -count=1 -v` +- You can target a narrower subset with `-run`, for example: + - `LOOP_UPDATE_RECORDED_SESSIONS=true go test ./cmd/loop -run 'TestRecordedSessions/static-openchannel' -count=1 -v` +- Bless mode is intentionally narrow: + - it reuses the recorded gRPC stream, stdin, and env, + - it refuses to bless a session if the command changes from success to failure or vice versa, + - and it refuses to bless a session if replay no longer consumes the same recorded gRPC interaction. +- Use live recording, not bless mode, when a command’s behavior or RPC flow changed. + ## Session coverage map | Subdir | Commands / scenarios | | --- | --- | From 96038b8b0522a33c13abd9bbc75d5eb087dffda6 Mon Sep 17 00:00:00 2001 From: Boris Nagaev Date: Sat, 9 May 2026 00:57:23 -0500 Subject: [PATCH 18/18] cmd/loop: fix openchannel help typo Correct the duplicated article in the static openchannel help text so the command summary reads cleanly. Refresh the recorded help fixtures that surface that summary directly, including the dedicated openchannel help output and the parent static command listing. --- cmd/loop/openchannel.go | 2 +- .../sessions/static-loop-in/04_loop-static.json | 12 ++---------- .../01_loop-static-openchannel-help.json | 2 +- docs/loop.1 | 2 +- docs/loop.md | 2 +- 5 files changed, 6 insertions(+), 14 deletions(-) diff --git a/cmd/loop/openchannel.go b/cmd/loop/openchannel.go index e7b18c60d..fd31b7442 100644 --- a/cmd/loop/openchannel.go +++ b/cmd/loop/openchannel.go @@ -24,7 +24,7 @@ var ( var openChannelCommand = &cli.Command{ Name: "openchannel", - Usage: "Open a channel to a an existing peer.", + Usage: "Open a channel to an existing peer.", Description: ` Attempt to open a new channel to an existing peer with the key node-key. diff --git a/cmd/loop/testdata/sessions/static-loop-in/04_loop-static.json b/cmd/loop/testdata/sessions/static-loop-in/04_loop-static.json index b86228be0..607d51d6f 100644 --- a/cmd/loop/testdata/sessions/static-loop-in/04_loop-static.json +++ b/cmd/loop/testdata/sessions/static-loop-in/04_loop-static.json @@ -32,16 +32,8 @@ " withdraw, w Withdraw from static address deposits.\n", " summary, s Display a summary of static address related information.\n", " in Loop in funds from static address deposits.\n", - " openchannel Open a channel to a an existing peer.\n", - "\n" - ] - } - }, - { - "time_ms": 2, - "kind": "stdout", - "data": { - "lines": [ + " openchannel Open a channel to an existing peer.\n", + "\n", "OPTIONS:\n", " --help, -h show help\n" ] diff --git a/cmd/loop/testdata/sessions/static-openchannel/01_loop-static-openchannel-help.json b/cmd/loop/testdata/sessions/static-openchannel/01_loop-static-openchannel-help.json index 0a20ef0c3..3861060df 100644 --- a/cmd/loop/testdata/sessions/static-openchannel/01_loop-static-openchannel-help.json +++ b/cmd/loop/testdata/sessions/static-openchannel/01_loop-static-openchannel-help.json @@ -25,7 +25,7 @@ "data": { "lines": [ "NAME:\n", - " loop static openchannel - Open a channel to a an existing peer.\n", + " loop static openchannel - Open a channel to an existing peer.\n", "\n", "USAGE:\n", " loop static openchannel [options]\n", diff --git a/docs/loop.1 b/docs/loop.1 index 7d767cdff..93346a1b3 100644 --- a/docs/loop.1 +++ b/docs/loop.1 @@ -590,7 +590,7 @@ Loop in funds from static address deposits. .SS openchannel .PP -Open a channel to a an existing peer. +Open a channel to an existing peer. .PP \fB--base_fee_msat\fP="": the base fee in milli-satoshis that will be charged for each forwarded HTLC, regardless of payment size (default: 0) diff --git a/docs/loop.md b/docs/loop.md index 3a847e96c..1577881bb 100644 --- a/docs/loop.md +++ b/docs/loop.md @@ -688,7 +688,7 @@ The following flags are supported: ### `static openchannel` subcommand -Open a channel to a an existing peer. +Open a channel to an existing peer. Attempt to open a new channel to an existing peer with the key node-key. The channel will be initialized with local-amt satoshis locally and push-amt satoshis for the remote node. Note that the push-amt is deducted from the specified local-amt which implies that the local-amt must be greater than the push-amt. Also note that specifying push-amt means you give that amount to the remote node as part of the channel opening. Once the channel is open, a channelPoint (txid:vout) of the funding output is returned. If the remote peer supports the option upfront shutdown feature bit (query listpeers to see their supported feature bits), an address to enforce payout of funds on cooperative close can optionally be provided. Note that if you set this value, you will not be able to cooperatively close out to another address. One can also specify a short string memo to record some useful information about the channel using the --memo argument. This is stored locally only, and is purely for reference. It has no bearing on the channel's operation. Max allowed length is 500 characters.