diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 04d635050..ff0a1a0ef 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,7 +43,25 @@ jobs: - name: run check run: make rpc-check - + + ######################## + # SQL compile and check + ######################## + sqlc-check: + name: SQL compilation check + runs-on: ubuntu-latest + steps: + - name: git checkout + uses: actions/checkout@v2 + + - name: setup go ${{ env.GO_VERSION }} + uses: actions/setup-go@v2 + with: + go-version: '~${{ env.GO_VERSION }}' + + - name: run check + run: make sqlc-check + ######################## # go mod check ######################## @@ -85,6 +103,26 @@ jobs: - name: lint run: make lint + ######################## + # Verify documentation + ######################## + docs-check: + name: verify that auto-generated documentation is up-to-date + runs-on: ubuntu-latest + steps: + - name: git checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: setup go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 + with: + go-version: '~${{ env.GO_VERSION }}' + + - name: check + run: make docs-check + ######################## # run unit tests ######################## diff --git a/Makefile b/Makefile index 505ceba1e..f698ec9cb 100644 --- a/Makefile +++ b/Makefile @@ -170,6 +170,15 @@ sqlc-check: sqlc @$(call print, "Verifying sql code generation.") if test -n "$$(git status --porcelain '*.go')"; then echo "SQL models not properly generated!"; git status --porcelain '*.go'; exit 1; fi +docs: build + @$(call print, "Building man and markdown files in docs/") + ./loop-debug man > docs/loop.1 + ./loop-debug markdown > docs/loop.md + +docs-check: docs + @$(call print, "Verifying man and markdown files in docs/") + if test -n "$$(git status --porcelain 'docs/loop.*')"; then echo "Man and markdown files not properly generated!"; git diff; exit 1; fi + fsm: @$(call print, "Generating state machine docs") ./scripts/fsm-generate.sh; diff --git a/README.md b/README.md index 1f9938fa2..1394fa963 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,9 @@ loop in ``` ### More info -For more information about using Loop checkout our [Loop FAQs](./docs/faqs.md). + +- [Loop FAQs](./docs/faqs.md) +- [Loop CLI manual](./docs/loop.md) ## Development diff --git a/cmd/loop/debug.go b/cmd/loop/debug.go index d3215a394..b96423dd3 100644 --- a/cmd/loop/debug.go +++ b/cmd/loop/debug.go @@ -7,7 +7,7 @@ import ( "context" "github.com/lightninglabs/loop/looprpc" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) func init() { @@ -15,25 +15,24 @@ func init() { commands = append(commands, forceAutoloopCmd) } -var forceAutoloopCmd = cli.Command{ +var forceAutoloopCmd = &cli.Command{ Name: "forceautoloop", Usage: ` Forces to trigger an autoloop step, regardless of the current internal autoloop timer. THIS MUST NOT BE USED IN A PROD ENVIRONMENT. `, Action: forceAutoloop, + Hidden: true, } -func forceAutoloop(ctx *cli.Context) error { - client, cleanup, err := getDebugClient(ctx) +func forceAutoloop(ctx context.Context, cmd *cli.Command) error { + client, cleanup, err := getDebugClient(ctx, cmd) if err != nil { return err } defer cleanup() - cfg, err := client.ForceAutoLoop( - context.Background(), &looprpc.ForceAutoLoopRequest{}, - ) + cfg, err := client.ForceAutoLoop(ctx, &looprpc.ForceAutoLoopRequest{}) if err != nil { return err } @@ -43,13 +42,13 @@ func forceAutoloop(ctx *cli.Context) error { return nil } -func getDebugClient(ctx *cli.Context) (looprpc.DebugClient, func(), error) { - rpcServer := ctx.GlobalString("rpcserver") - tlsCertPath, macaroonPath, err := extractPathArgs(ctx) +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, err := getClientConn(rpcServer, tlsCertPath, macaroonPath) + conn, err := getClientConn(ctx, rpcServer, tlsCertPath, macaroonPath) if err != nil { return nil, nil, err } diff --git a/cmd/loop/docs.go b/cmd/loop/docs.go new file mode 100644 index 000000000..f5b49a9a1 --- /dev/null +++ b/cmd/loop/docs.go @@ -0,0 +1,152 @@ +package main + +import ( + "context" + _ "embed" + "fmt" + + docs "github.com/urfave/cli-docs/v3" + "github.com/urfave/cli/v3" +) + +//go:embed markdown_tabular.md.gotmpl +var markdownTabularDocTemplate string + +// We have a copy of this template taken from +// https://github.com/urfave/cli-docs where we remove column +// "Environment variables" if it has no values. +// TODO: remove this when https://github.com/urfave/cli-docs/pull/15 +// is merged. +func init() { + docs.MarkdownTabularDocTemplate = markdownTabularDocTemplate +} + +var printManCommand = &cli.Command{ + Name: "man", + Usage: "prints man file", + Description: "Prints documentation of loop CLI in man format", + Action: printMan, + Hidden: true, +} + +func printMan(_ context.Context, cmd *cli.Command) error { + root := filterNestedHelpCommands(cmd.Root()) + + const userCommandsSection = 1 + man, err := docs.ToManWithSection(root, userCommandsSection) + if err != nil { + return fmt.Errorf("failed to produce man: %w", err) + } + + fmt.Println(man) + + return nil +} + +var printMarkdownCommand = &cli.Command{ + Name: "markdown", + Usage: "prints markdown file", + Description: "Prints documentation of loop CLI in markdown format", + Action: printMarkdown, + Hidden: true, +} + +func printMarkdown(_ context.Context, cmd *cli.Command) error { + root := filterNestedHelpCommands(cmd.Root()) + + md, err := docs.ToTabularMarkdown(root, "loop") + if err != nil { + return fmt.Errorf("failed to produce man: %w", err) + } + + fmt.Println(md) + + return nil +} + +// filterNestedHelpCommands clones cmd, drops nested help commands, and normalises +// flag defaults so generated documentation avoids absolute paths. +func filterNestedHelpCommands(cmd *cli.Command) *cli.Command { + cloned := cloneCommand(cmd, 0) + overrideDocFlags(cloned) + return cloned +} + +// cloneCommand clones the command, filtering out nested "help" subcommands. +func cloneCommand(cmd *cli.Command, depth int) *cli.Command { + if cmd == nil { + return nil + } + + cloned := *cmd + if len(cmd.Commands) == 0 { + return &cloned + } + + filtered := make([]*cli.Command, 0, len(cmd.Commands)) + for _, sub := range cmd.Commands { + if sub == nil { + continue + } + childDepth := depth + 1 + + // TODO: remove when https://github.com/urfave/cli-docs/pull/16 + if childDepth > 0 && sub.Name == "help" { + continue + } + + filtered = append(filtered, cloneCommand(sub, childDepth)) + } + + cloned.Commands = filtered + return &cloned +} + +// overrideDocFlags walks the command tree and replaces string flag defaults +// that leak user-specific filesystem paths, keeping generated docs stable. +func overrideDocFlags(cmd *cli.Command) { + if cmd == nil { + return + } + + if len(cmd.Flags) > 0 { + clonedFlags := make([]cli.Flag, len(cmd.Flags)) + for i, fl := range cmd.Flags { + clonedFlags[i] = cloneFlagWithOverrides(fl) + } + cmd.Flags = clonedFlags + } + + for _, sub := range cmd.Commands { + overrideDocFlags(sub) + } +} + +// docFlagOverrides maps global flag names to the canonical values we want to +// show in documentation instead of user-specific absolute paths. +var docFlagOverrides = map[string]string{ + loopDirFlag.Name: "~/.loop", + tlsCertFlag.Name: "~/.loop/mainnet/tls.cert", + macaroonPathFlag.Name: "~/.loop/mainnet/loop.macaroon", +} + +// cloneFlagWithOverrides returns a copy of flag with overridden default values +// when the flag participates in docFlagOverrides. Non-string flags are reused +// unchanged to minimise allocations. +func cloneFlagWithOverrides(flag cli.Flag) cli.Flag { + sf, ok := flag.(*cli.StringFlag) + if !ok { + return flag + } + + value, ok := docFlagOverrides[sf.Name] + if !ok { + return flag + } + + cloned := *sf + cloned.Value = value + cloned.DefaultText = value + + return &cloned +} diff --git a/cmd/loop/info.go b/cmd/loop/info.go index 1fbdae24e..b2f3c118e 100644 --- a/cmd/loop/info.go +++ b/cmd/loop/info.go @@ -4,10 +4,10 @@ import ( "context" "github.com/lightninglabs/loop/looprpc" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) -var getInfoCommand = cli.Command{ +var getInfoCommand = &cli.Command{ Name: "getinfo", Usage: "show general information about the loop daemon", Description: "Displays general information about the daemon like " + @@ -16,16 +16,14 @@ var getInfoCommand = cli.Command{ Action: getInfo, } -func getInfo(ctx *cli.Context) error { - client, cleanup, err := getClient(ctx) +func getInfo(ctx context.Context, cmd *cli.Command) error { + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() - cfg, err := client.GetInfo( - context.Background(), &looprpc.GetInfoRequest{}, - ) + cfg, err := client.GetInfo(ctx, &looprpc.GetInfoRequest{}) if err != nil { return err } diff --git a/cmd/loop/instantout.go b/cmd/loop/instantout.go index 699e5006a..cffc094ee 100644 --- a/cmd/loop/instantout.go +++ b/cmd/loop/instantout.go @@ -9,10 +9,10 @@ import ( "github.com/lightninglabs/loop/instantout/reservation" "github.com/lightninglabs/loop/looprpc" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) -var instantOutCommand = cli.Command{ +var instantOutCommand = &cli.Command{ Name: "instantout", Usage: "perform an instant off-chain to on-chain swap (looping out)", Description: ` @@ -20,12 +20,12 @@ var instantOutCommand = cli.Command{ will be chosen via the cli. `, Flags: []cli.Flag{ - cli.StringFlag{ + &cli.StringFlag{ Name: "channel", Usage: "the comma-separated list of short " + "channel IDs of the channels to loop out", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "addr", Usage: "the optional address that the looped out funds " + "should be sent to, if let blank the funds " + @@ -35,13 +35,13 @@ var instantOutCommand = cli.Command{ Action: instantOut, } -func instantOut(ctx *cli.Context) error { +func instantOut(ctx context.Context, cmd *cli.Command) error { // Parse outgoing channel set. Don't string split if the flag is empty. // Otherwise, strings.Split returns a slice of length one with an empty // element. var outgoingChanSet []uint64 - if ctx.IsSet("channel") { - chanStrings := strings.Split(ctx.String("channel"), ",") + if cmd.IsSet("channel") { + chanStrings := strings.Split(cmd.String("channel"), ",") for _, chanString := range chanStrings { chanID, err := strconv.ParseUint(chanString, 10, 64) if err != nil { @@ -53,7 +53,7 @@ func instantOut(ctx *cli.Context) error { } // First set up the swap client itself. - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } @@ -61,7 +61,7 @@ func instantOut(ctx *cli.Context) error { // Now we fetch all the confirmed reservations. reservations, err := client.ListReservations( - context.Background(), &looprpc.ListReservationsRequest{}, + ctx, &looprpc.ListReservationsRequest{}, ) if err != nil { return err @@ -156,7 +156,7 @@ func instantOut(ctx *cli.Context) error { // Now that we have the selected reservations we can estimate the // fee-rates. quote, err := client.InstantOutQuote( - context.Background(), &looprpc.InstantOutQuoteRequest{ + ctx, &looprpc.InstantOutQuoteRequest{ Amt: selectedAmt, ReservationIds: selectedReservations, }, @@ -180,11 +180,11 @@ func instantOut(ctx *cli.Context) error { // Now we can request the instant out swap. instantOutRes, err := client.InstantOut( - context.Background(), + ctx, &looprpc.InstantOutRequest{ ReservationIds: selectedReservations, OutgoingChanSet: outgoingChanSet, - DestAddr: ctx.String("addr"), + DestAddr: cmd.String("addr"), }, ) if err != nil { @@ -202,7 +202,7 @@ func instantOut(ctx *cli.Context) error { return nil } -var listInstantOutsCommand = cli.Command{ +var listInstantOutsCommand = &cli.Command{ Name: "listinstantouts", Usage: "list all instant out swaps", Description: ` @@ -211,16 +211,16 @@ var listInstantOutsCommand = cli.Command{ Action: listInstantOuts, } -func listInstantOuts(ctx *cli.Context) error { +func listInstantOuts(ctx context.Context, cmd *cli.Command) error { // First set up the swap client itself. - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() resp, err := client.ListInstantOuts( - context.Background(), &looprpc.ListInstantOutsRequest{}, + ctx, &looprpc.ListInstantOutsRequest{}, ) if err != nil { return err diff --git a/cmd/loop/l402.go b/cmd/loop/l402.go index 5e1669a88..106011cf8 100644 --- a/cmd/loop/l402.go +++ b/cmd/loop/l402.go @@ -7,7 +7,7 @@ import ( "time" "github.com/lightninglabs/loop/looprpc" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" "gopkg.in/macaroon.v2" ) @@ -24,22 +24,22 @@ type printableToken struct { FileName string `json:"file_name"` } -var listAuthCommand = cli.Command{ +var listAuthCommand = &cli.Command{ Name: "listauth", Usage: "list all L402 tokens", Description: "Shows a list of all L402 tokens that loopd has paid for", Action: listAuth, } -func listAuth(ctx *cli.Context) error { - client, cleanup, err := getClient(ctx) +func listAuth(ctx context.Context, cmd *cli.Command) error { + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() resp, err := client.GetL402Tokens( - context.Background(), &looprpc.TokensRequest{}, + ctx, &looprpc.TokensRequest{}, ) if err != nil { return err @@ -74,7 +74,7 @@ func listAuth(ctx *cli.Context) error { return nil } -var fetchL402Command = cli.Command{ +var fetchL402Command = &cli.Command{ Name: "fetchl402", Usage: "fetches a new L402 authentication token from the server", Description: "Fetches a new L402 authentication token from the server. " + @@ -84,15 +84,15 @@ var fetchL402Command = cli.Command{ Action: fetchL402, } -func fetchL402(ctx *cli.Context) error { - client, cleanup, err := getClient(ctx) +func fetchL402(ctx context.Context, cmd *cli.Command) error { + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() res, err := client.FetchL402Token( - context.Background(), &looprpc.FetchL402TokenRequest{}, + ctx, &looprpc.FetchL402TokenRequest{}, ) if err != nil { return err diff --git a/cmd/loop/liquidity.go b/cmd/loop/liquidity.go index 3d58060e9..88ae1f880 100644 --- a/cmd/loop/liquidity.go +++ b/cmd/loop/liquidity.go @@ -10,12 +10,12 @@ import ( "github.com/lightninglabs/loop/liquidity" "github.com/lightninglabs/loop/looprpc" "github.com/lightningnetwork/lnd/routing/route" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -var getLiquidityParamsCommand = cli.Command{ +var getLiquidityParamsCommand = &cli.Command{ Name: "getparams", Usage: "show liquidity manager parameters", Description: "Displays the current set of parameters that are set " + @@ -23,15 +23,15 @@ var getLiquidityParamsCommand = cli.Command{ Action: getParams, } -func getParams(ctx *cli.Context) error { - client, cleanup, err := getClient(ctx) +func getParams(ctx context.Context, cmd *cli.Command) error { + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() cfg, err := client.GetLiquidityParams( - context.Background(), &looprpc.GetLiquidityParamsRequest{}, + ctx, &looprpc.GetLiquidityParamsRequest{}, ) if err != nil { return err @@ -42,13 +42,13 @@ func getParams(ctx *cli.Context) error { return nil } -var setLiquidityRuleCommand = cli.Command{ +var setLiquidityRuleCommand = &cli.Command{ Name: "setrule", Usage: "set liquidity manager rule for a channel/peer", Description: "Update or remove the liquidity rule for a channel/peer.", ArgsUsage: "{shortchanid | peerpubkey}", Flags: []cli.Flag{ - cli.StringFlag{ + &cli.StringFlag{ Name: "type", Usage: "the type of swap to perform, set to 'out' " + "for acquiring inbound liquidity or 'in' for " + @@ -56,18 +56,18 @@ var setLiquidityRuleCommand = cli.Command{ Value: "out", }, - cli.IntFlag{ + &cli.IntFlag{ Name: "incoming_threshold", Usage: "the minimum percentage of incoming liquidity " + "to total capacity beneath which to " + "recommend loop out to acquire incoming.", }, - cli.IntFlag{ + &cli.IntFlag{ Name: "outgoing_threshold", Usage: "the minimum percentage of outbound liquidity " + "that we do not want to drop below.", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "clear", Usage: "remove the rule currently set for the " + "channel/peer.", @@ -76,9 +76,9 @@ var setLiquidityRuleCommand = cli.Command{ Action: setRule, } -func setRule(ctx *cli.Context) error { +func setRule(ctx context.Context, cmd *cli.Command) error { // We require that a channel ID is set for this rule update. - if ctx.NArg() != 1 { + if cmd.NArg() != 1 { return fmt.Errorf("please set a channel id or peer pubkey " + "for the rule update") } @@ -87,9 +87,9 @@ func setRule(ctx *cli.Context) error { pubkey route.Vertex pubkeyRule bool ) - chanID, err := strconv.ParseUint(ctx.Args().First(), 10, 64) + chanID, err := strconv.ParseUint(cmd.Args().First(), 10, 64) if err != nil { - pubkey, err = route.NewVertexFromStr(ctx.Args().First()) + pubkey, err = route.NewVertexFromStr(cmd.Args().First()) if err != nil { return fmt.Errorf("please provide a valid pubkey: "+ "%v, or short channel ID", err) @@ -97,7 +97,7 @@ func setRule(ctx *cli.Context) error { pubkeyRule = true } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } @@ -107,15 +107,15 @@ func setRule(ctx *cli.Context) error { // SetParameters. To allow users to set only individual fields on the // cli, we lookup our current params, then update individual values. params, err := client.GetLiquidityParams( - context.Background(), &looprpc.GetLiquidityParamsRequest{}, + ctx, &looprpc.GetLiquidityParamsRequest{}, ) if err != nil { return err } var ( - inboundSet = ctx.IsSet("incoming_threshold") - outboundSet = ctx.IsSet("outgoing_threshold") + inboundSet = cmd.IsSet("incoming_threshold") + outboundSet = cmd.IsSet("outgoing_threshold") ruleSet bool otherRules []*looprpc.LiquidityRule ) @@ -144,7 +144,7 @@ func setRule(ctx *cli.Context) error { // If we want to clear the rule for this channel, check that we had a // rule set in the first place, and set our parameters to the current // set excluding the channel specified. - if ctx.IsSet("clear") { + if cmd.IsSet("clear") { if !ruleSet { return fmt.Errorf("cannot clear channel: %v, no rule "+ "set at present", chanID) @@ -157,7 +157,7 @@ func setRule(ctx *cli.Context) error { params.Rules = otherRules _, err = client.SetLiquidityParams( - context.Background(), + ctx, &looprpc.SetLiquidityParamsRequest{ Parameters: params, }, @@ -177,8 +177,8 @@ func setRule(ctx *cli.Context) error { ChannelId: chanID, Type: looprpc.LiquidityRuleType_THRESHOLD, } - if ctx.IsSet("type") { - switch ctx.String("type") { + if cmd.IsSet("type") { + switch cmd.String("type") { case "in": newRule.SwapType = looprpc.SwapType_LOOP_IN @@ -196,13 +196,13 @@ func setRule(ctx *cli.Context) error { if inboundSet { newRule.IncomingThreshold = uint32( - ctx.Int("incoming_threshold"), + cmd.Int("incoming_threshold"), ) } if outboundSet { newRule.OutgoingThreshold = uint32( - ctx.Int("outgoing_threshold"), + cmd.Int("outgoing_threshold"), ) } @@ -213,7 +213,7 @@ func setRule(ctx *cli.Context) error { // Update our parameters to the existing set, plus our new rule. _, err = client.SetLiquidityParams( - context.Background(), + ctx, &looprpc.SetLiquidityParamsRequest{ Parameters: params, }, @@ -222,7 +222,7 @@ func setRule(ctx *cli.Context) error { return err } -var setParamsCommand = cli.Command{ +var setParamsCommand = &cli.Command{ Name: "setparams", Usage: "update the parameters set for the liquidity manager", Description: "Updates the parameters set for the liquidity manager. " + @@ -230,69 +230,69 @@ var setParamsCommand = cli.Command{ "of setting them again upon loopd restart. To get the default" + "values, use `getparams` before any `setparams`.", Flags: []cli.Flag{ - cli.IntFlag{ + &cli.IntFlag{ Name: "sweeplimit", Usage: "the limit placed on our estimated sweep fee " + "in sat/vByte.", }, - cli.Float64Flag{ + &cli.Float64Flag{ Name: "feepercent", Usage: "the maximum percentage of swap amount to be " + "used across all fee categories", }, - cli.Float64Flag{ + &cli.Float64Flag{ Name: "maxswapfee", Usage: "the maximum percentage of swap volume we are " + "willing to pay in server fees.", }, - cli.Float64Flag{ + &cli.Float64Flag{ Name: "maxroutingfee", Usage: "the maximum percentage of off-chain payment " + "volume that we are willing to pay in routing" + "fees.", }, - cli.Float64Flag{ + &cli.Float64Flag{ Name: "maxprepayfee", Usage: "the maximum percentage of off-chain prepay " + "volume that we are willing to pay in " + "routing fees.", }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "maxprepay", Usage: "the maximum no-show (prepay) in satoshis that " + "swap suggestions should be limited to.", }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "maxminer", Usage: "the maximum miner fee in satoshis that swap " + "suggestions should be limited to.", }, - cli.IntFlag{ + &cli.IntFlag{ Name: "sweepconf", Usage: "the number of blocks from htlc height that " + "swap suggestion sweeps should target, used " + "to estimate max miner fee.", }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "failurebackoff", Usage: "the amount of time, in seconds, that " + "should pass before a channel that " + "previously had a failed swap will be " + "included in suggestions.", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "autoloop", Usage: "set to true to enable automated dispatch " + "of swaps, limited to the budget set by " + "autobudget.", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "destaddr", Usage: "custom address to be used as destination for " + "autoloop loop out, set to \"default\" in " + "order to revert to default behavior.", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "account", Usage: "the name of the account to generate a new " + "address from. You can list the names of " + @@ -300,74 +300,74 @@ var setParamsCommand = cli.Command{ "instance with \"lncli wallet accounts list\".", Value: "", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "account_addr_type", Usage: "the address type of the extended public key " + "specified in account. Currently only " + "pay-to-taproot-pubkey(p2tr) is supported", Value: "p2tr", }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "autobudget", Usage: "the maximum amount of fees in satoshis that " + "automatically dispatched loop out swaps may " + "spend.", }, - cli.DurationFlag{ + &cli.DurationFlag{ Name: "autobudgetrefreshperiod", Usage: "the time period over which the automated " + "loop budget is refreshed.", }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "autoinflight", Usage: "the maximum number of automatically " + "dispatched swaps that we allow to be in " + "flight.", }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "minamt", Usage: "the minimum amount in satoshis that the " + "autoloop client will dispatch per-swap.", }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "maxamt", Usage: "the maximum amount in satoshis that the " + "autoloop client will dispatch per-swap.", }, - cli.IntFlag{ + &cli.IntFlag{ Name: "htlc_conf", Usage: "the confirmation target for loop in on-chain " + "htlcs.", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "easyautoloop", Usage: "set to true to enable easy autoloop, which " + "will automatically dispatch swaps in order " + "to meet the target local balance.", }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "localbalancesat", Usage: "the target size of total local balance in " + "satoshis, used by easy autoloop.", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "asset_easyautoloop", Usage: "set to true to enable asset easy autoloop, which " + "will automatically dispatch asset swaps in order " + "to meet the target local balance.", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "asset_id", Usage: "If set to a valid asset ID, the easyautoloop " + "and localbalancesat flags will be set for the " + "specified asset.", }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "asset_localbalance", Usage: "the target size of total local balance in " + "asset units, used by asset easy autoloop.", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "fast", Usage: "if set new swaps are expected to be " + "published immediately, paying a potentially " + @@ -381,8 +381,8 @@ var setParamsCommand = cli.Command{ Action: setParams, } -func setParams(ctx *cli.Context) error { - client, cleanup, err := getClient(ctx) +func setParams(ctx context.Context, cmd *cli.Command) error { + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } @@ -392,7 +392,7 @@ func setParams(ctx *cli.Context) error { // SetParameters. To allow users to set only individual fields on the // cli, we lookup our current params, then update individual values. params, err := client.GetLiquidityParams( - context.Background(), &looprpc.GetLiquidityParamsRequest{}, + ctx, &looprpc.GetLiquidityParamsRequest{}, ) if err != nil { return err @@ -403,8 +403,8 @@ func setParams(ctx *cli.Context) error { // Update our existing parameters with the values provided by cli flags. // Our fee categories and fee percentage are exclusive, so track which // flags are set to ensure that we don't have nonsensical overlap. - if ctx.IsSet("maxswapfee") { - feeRate := ctx.Float64("maxswapfee") + if cmd.IsSet("maxswapfee") { + feeRate := cmd.Float64("maxswapfee") params.MaxSwapFeePpm, err = ppmFromPercentage(feeRate) if err != nil { return err @@ -414,16 +414,16 @@ func setParams(ctx *cli.Context) error { categoriesSet = true } - if ctx.IsSet("sweeplimit") { - satPerVByte := ctx.Int("sweeplimit") + if cmd.IsSet("sweeplimit") { + satPerVByte := cmd.Int("sweeplimit") params.SweepFeeRateSatPerVbyte = uint64(satPerVByte) flagSet = true categoriesSet = true } - if ctx.IsSet("feepercent") { - feeRate := ctx.Float64("feepercent") + if cmd.IsSet("feepercent") { + feeRate := cmd.Float64("feepercent") params.FeePpm, err = ppmFromPercentage(feeRate) if err != nil { return err @@ -433,8 +433,8 @@ func setParams(ctx *cli.Context) error { feePercentSet = true } - if ctx.IsSet("maxroutingfee") { - feeRate := ctx.Float64("maxroutingfee") + if cmd.IsSet("maxroutingfee") { + feeRate := cmd.Float64("maxroutingfee") params.MaxRoutingFeePpm, err = ppmFromPercentage(feeRate) if err != nil { return err @@ -444,8 +444,8 @@ func setParams(ctx *cli.Context) error { categoriesSet = true } - if ctx.IsSet("maxprepayfee") { - feeRate := ctx.Float64("maxprepayfee") + if cmd.IsSet("maxprepayfee") { + feeRate := cmd.Float64("maxprepayfee") params.MaxPrepayRoutingFeePpm, err = ppmFromPercentage(feeRate) if err != nil { return err @@ -455,59 +455,59 @@ func setParams(ctx *cli.Context) error { categoriesSet = true } - if ctx.IsSet("maxprepay") { - params.MaxPrepaySat = ctx.Uint64("maxprepay") + if cmd.IsSet("maxprepay") { + params.MaxPrepaySat = cmd.Uint64("maxprepay") flagSet = true categoriesSet = true } - if ctx.IsSet("maxminer") { - params.MaxMinerFeeSat = ctx.Uint64("maxminer") + if cmd.IsSet("maxminer") { + params.MaxMinerFeeSat = cmd.Uint64("maxminer") flagSet = true categoriesSet = true } - if ctx.IsSet("sweepconf") { - params.SweepConfTarget = int32(ctx.Int("sweepconf")) + if cmd.IsSet("sweepconf") { + params.SweepConfTarget = int32(cmd.Int("sweepconf")) flagSet = true } - if ctx.IsSet("failurebackoff") { - params.FailureBackoffSec = ctx.Uint64("failurebackoff") + if cmd.IsSet("failurebackoff") { + params.FailureBackoffSec = cmd.Uint64("failurebackoff") flagSet = true } - if ctx.IsSet("autoloop") { - params.Autoloop = ctx.Bool("autoloop") + if cmd.IsSet("autoloop") { + params.Autoloop = cmd.Bool("autoloop") flagSet = true } - if ctx.IsSet("autobudget") { - params.AutoloopBudgetSat = ctx.Uint64("autobudget") + if cmd.IsSet("autobudget") { + params.AutoloopBudgetSat = cmd.Uint64("autobudget") flagSet = true } switch { - case ctx.IsSet("destaddr") && ctx.IsSet("account"): + case cmd.IsSet("destaddr") && cmd.IsSet("account"): return fmt.Errorf("cannot set destaddr and account at the " + "same time") - case ctx.IsSet("destaddr"): - params.AutoloopDestAddress = ctx.String("destaddr") + case cmd.IsSet("destaddr"): + params.AutoloopDestAddress = cmd.String("destaddr") params.Account = "" flagSet = true - case ctx.IsSet("account") != ctx.IsSet("account_addr_type"): + case cmd.IsSet("account") != cmd.IsSet("account_addr_type"): return liquidity.ErrAccountAndAddrType - case ctx.IsSet("account"): - params.Account = ctx.String("account") + case cmd.IsSet("account"): + params.Account = cmd.String("account") params.AutoloopDestAddress = "" flagSet = true } - if ctx.IsSet("account_addr_type") { - switch ctx.String("account_addr_type") { + if cmd.IsSet("account_addr_type") { + switch cmd.String("account_addr_type") { case "p2tr": params.AccountAddrType = looprpc.AddressType_TAPROOT_PUBKEY @@ -516,78 +516,78 @@ func setParams(ctx *cli.Context) error { } } - if ctx.IsSet("autobudgetrefreshperiod") { + if cmd.IsSet("autobudgetrefreshperiod") { params.AutoloopBudgetRefreshPeriodSec = - uint64(ctx.Duration("autobudgetrefreshperiod").Seconds()) + uint64(cmd.Duration("autobudgetrefreshperiod").Seconds()) flagSet = true } - if ctx.IsSet("autoinflight") { - params.AutoMaxInFlight = ctx.Uint64("autoinflight") + if cmd.IsSet("autoinflight") { + params.AutoMaxInFlight = cmd.Uint64("autoinflight") flagSet = true } - if ctx.IsSet("minamt") { - params.MinSwapAmount = ctx.Uint64("minamt") + if cmd.IsSet("minamt") { + params.MinSwapAmount = cmd.Uint64("minamt") flagSet = true } - if ctx.IsSet("maxamt") { - params.MaxSwapAmount = ctx.Uint64("maxamt") + if cmd.IsSet("maxamt") { + params.MaxSwapAmount = cmd.Uint64("maxamt") flagSet = true } - if ctx.IsSet("htlc_conf") { - params.HtlcConfTarget = int32(ctx.Int("htlc_conf")) + if cmd.IsSet("htlc_conf") { + params.HtlcConfTarget = int32(cmd.Int("htlc_conf")) flagSet = true } // If we are setting easy autoloop parameters, we need to ensure that // the asset ID is set, and that we have a valid entry in our params // map. - if ctx.IsSet("asset_id") { + if cmd.IsSet("asset_id") { if params.EasyAssetParams == nil { params.EasyAssetParams = make( map[string]*looprpc.EasyAssetAutoloopParams, ) } - if _, ok := params.EasyAssetParams[ctx.String("asset_id")]; !ok { //nolint:lll - params.EasyAssetParams[ctx.String("asset_id")] = + if _, ok := params.EasyAssetParams[cmd.String("asset_id")]; !ok { //nolint:lll + params.EasyAssetParams[cmd.String("asset_id")] = &looprpc.EasyAssetAutoloopParams{} } } - if ctx.IsSet("easyautoloop") { - params.EasyAutoloop = ctx.Bool("easyautoloop") + if cmd.IsSet("easyautoloop") { + params.EasyAutoloop = cmd.Bool("easyautoloop") flagSet = true } - if ctx.IsSet("localbalancesat") { - params.EasyAutoloopLocalTargetSat = ctx.Uint64("localbalancesat") + if cmd.IsSet("localbalancesat") { + params.EasyAutoloopLocalTargetSat = cmd.Uint64("localbalancesat") flagSet = true } - if ctx.IsSet("asset_easyautoloop") { - if !ctx.IsSet("asset_id") { + if cmd.IsSet("asset_easyautoloop") { + if !cmd.IsSet("asset_id") { return fmt.Errorf("asset_id must be set to use " + "asset_easyautoloop") } - params.EasyAssetParams[ctx.String("asset_id")]. - Enabled = ctx.Bool("asset_easyautoloop") + params.EasyAssetParams[cmd.String("asset_id")]. + Enabled = cmd.Bool("asset_easyautoloop") flagSet = true } - if ctx.IsSet("asset_localbalance") { - if !ctx.IsSet("asset_id") { + if cmd.IsSet("asset_localbalance") { + if !cmd.IsSet("asset_id") { return fmt.Errorf("asset_id must be set to use " + "asset_localbalance") } - params.EasyAssetParams[ctx.String("asset_id")]. - LocalTargetAssetAmt = ctx.Uint64("asset_localbalance") + params.EasyAssetParams[cmd.String("asset_id")]. + LocalTargetAssetAmt = cmd.Uint64("asset_localbalance") flagSet = true } - if ctx.IsSet("fast") { + if cmd.IsSet("fast") { params.FastSwapPublication = true } @@ -619,7 +619,7 @@ func setParams(ctx *cli.Context) error { } // Update our parameters to our mutated values. _, err = client.SetLiquidityParams( - context.Background(), &looprpc.SetLiquidityParamsRequest{ + ctx, &looprpc.SetLiquidityParamsRequest{ Parameters: params, }, ) @@ -637,7 +637,7 @@ func ppmFromPercentage(percentage float64) (uint64, error) { return uint64(percentage / 100 * liquidity.FeeBase), nil } -var suggestSwapCommand = cli.Command{ +var suggestSwapCommand = &cli.Command{ Name: "suggestswaps", Usage: "show a list of suggested swaps", Description: "Displays a list of suggested swaps that aim to obtain " + @@ -646,15 +646,15 @@ var suggestSwapCommand = cli.Command{ Action: suggestSwap, } -func suggestSwap(ctx *cli.Context) error { - client, cleanup, err := getClient(ctx) +func suggestSwap(ctx context.Context, cmd *cli.Command) error { + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() resp, err := client.SuggestSwaps( - context.Background(), &looprpc.SuggestSwapsRequest{}, + ctx, &looprpc.SuggestSwapsRequest{}, ) if err == nil { printRespJSON(resp) diff --git a/cmd/loop/loopin.go b/cmd/loop/loopin.go index 055018077..177cd0aa9 100644 --- a/cmd/loop/loopin.go +++ b/cmd/loop/loopin.go @@ -3,52 +3,53 @@ package main import ( "context" "fmt" + "strconv" "github.com/lightninglabs/loop" "github.com/lightninglabs/loop/labels" "github.com/lightninglabs/loop/looprpc" "github.com/lightningnetwork/lnd/routing/route" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) var ( - lastHopFlag = cli.StringFlag{ + lastHopFlag = &cli.StringFlag{ Name: "last_hop", Usage: "the pubkey of the last hop to use for this swap", } - confTargetFlag = cli.Uint64Flag{ + confTargetFlag = &cli.Uint64Flag{ Name: "conf_target", Usage: "the target number of blocks the on-chain htlc " + "broadcast by the swap client should confirm within", } - labelFlag = cli.StringFlag{ + labelFlag = &cli.StringFlag{ Name: "label", Usage: fmt.Sprintf("an optional label for this swap,"+ "limited to %v characters. The label may not start "+ "with our reserved prefix: %v.", labels.MaxLength, labels.Reserved), } - routeHintsFlag = cli.StringSliceFlag{ + routeHintsFlag = &cli.StringSliceFlag{ Name: "route_hints", Usage: "route hints that can each be individually used " + "to assist in reaching the invoice's destination", } - privateFlag = cli.BoolFlag{ + privateFlag = &cli.BoolFlag{ Name: "private", Usage: "generates and passes routehints. Should be used if " + "the connected node is only reachable via private " + "channels", } - forceFlag = cli.BoolFlag{ + forceFlag = &cli.BoolFlag{ Name: "force, f", Usage: "Assumes yes during confirmation. Using this option " + "will result in an immediate swap", } - loopInCommand = cli.Command{ + loopInCommand = &cli.Command{ Name: "in", Usage: "perform an on-chain to off-chain swap (loop in)", ArgsUsage: "amt", @@ -65,14 +66,14 @@ var ( conf_target flag. `, Flags: []cli.Flag{ - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "amt", Usage: "the amount in satoshis to loop in. " + "To check for the minimum and " + "maximum amounts to loop " + "in please consult \"loop terms\"", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "external", Usage: "expect htlc to be published externally", }, @@ -88,18 +89,18 @@ var ( } ) -func loopIn(ctx *cli.Context) error { - args := ctx.Args() +func loopIn(ctx context.Context, cmd *cli.Command) error { + args := cmd.Args() var amtStr string switch { - case ctx.IsSet("amt"): - amtStr = ctx.String("amt") - case ctx.NArg() == 1: - amtStr = args[0] + case cmd.IsSet("amt"): + amtStr = strconv.FormatUint(cmd.Uint64("amt"), 10) + case cmd.NArg() == 1: + amtStr = args.First() default: // Show command help if no arguments and flags were provided. - return cli.ShowCommandHelp(ctx, "in") + return showCommandHelp(ctx, cmd) } amt, err := parseAmt(amtStr) @@ -107,14 +108,14 @@ func loopIn(ctx *cli.Context) error { return err } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() - external := ctx.Bool("external") - htlcConfTarget := int32(ctx.Uint64(confTargetFlag.Name)) + external := cmd.Bool("external") + htlcConfTarget := int32(cmd.Uint64(confTargetFlag.Name)) // External and confirmation target are mutually exclusive; either the // on chain htlc is being externally broadcast, or we are creating the @@ -125,15 +126,15 @@ func loopIn(ctx *cli.Context) error { } // Validate our label early so that we can fail before getting a quote. - label := ctx.String(labelFlag.Name) + label := cmd.String(labelFlag.Name) if err := labels.Validate(label); err != nil { return err } var lastHop []byte - if ctx.IsSet(lastHopFlag.Name) { + if cmd.IsSet(lastHopFlag.Name) { lastHopVertex, err := route.NewVertexFromStr( - ctx.String(lastHopFlag.Name), + cmd.String(lastHopFlag.Name), ) if err != nil { return err @@ -144,7 +145,7 @@ func loopIn(ctx *cli.Context) error { // Private and routehints are mutually exclusive as setting private // means we retrieve our own routehints from the connected node. - hints, err := validateRouteHints(ctx) + hints, err := validateRouteHints(cmd) if err != nil { return err } @@ -155,10 +156,10 @@ func loopIn(ctx *cli.Context) error { ExternalHtlc: external, LoopInLastHop: lastHop, LoopInRouteHints: hints, - Private: ctx.Bool(privateFlag.Name), + Private: cmd.Bool(privateFlag.Name), } - quote, err := client.GetLoopInQuote(context.Background(), quoteReq) + quote, err := client.GetLoopInQuote(ctx, quoteReq) if err != nil { return err } @@ -180,8 +181,8 @@ func loopIn(ctx *cli.Context) error { limits := getInLimits(quote) // Skip showing details if configured - if !(ctx.Bool("force") || ctx.Bool("f")) { - err = displayInDetails(quoteReq, quote, ctx.Bool("verbose")) + if !(cmd.Bool("force") || cmd.Bool("f")) { + err = displayInDetails(quoteReq, quote, cmd.Bool("verbose")) if err != nil { return err } @@ -197,10 +198,10 @@ func loopIn(ctx *cli.Context) error { Initiator: defaultInitiator, LastHop: lastHop, RouteHints: hints, - Private: ctx.Bool(privateFlag.Name), + Private: cmd.Bool(privateFlag.Name), } - resp, err := client.LoopIn(context.Background(), req) + resp, err := client.LoopIn(ctx, req) if err != nil { return err } diff --git a/cmd/loop/loopout.go b/cmd/loop/loopout.go index 57ad2c76e..37dedd2ef 100644 --- a/cmd/loop/loopout.go +++ b/cmd/loop/loopout.go @@ -14,17 +14,17 @@ import ( "github.com/lightninglabs/loop/labels" "github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/looprpc" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) var ( - channelFlag = cli.StringFlag{ + channelFlag = &cli.StringFlag{ Name: "channel", Usage: "the comma-separated list of short " + "channel IDs of the channels to loop out", } ) -var loopOutCommand = cli.Command{ +var loopOutCommand = &cli.Command{ Name: "out", Usage: "perform an off-chain to on-chain swap (looping out)", ArgsUsage: "amt [addr]", @@ -37,13 +37,13 @@ var loopOutCommand = cli.Command{ Optionally a BASE58/bech32 encoded bitcoin destination address may be specified. If not specified, a new wallet address will be generated.`, Flags: []cli.Flag{ - cli.StringFlag{ + &cli.StringFlag{ Name: "addr", Usage: "the optional address that the looped out funds " + "should be sent to, if let blank the funds " + "will go to lnd's wallet", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "account", Usage: "the name of the account to generate a new " + "address from. You can list the names of " + @@ -51,40 +51,40 @@ var loopOutCommand = cli.Command{ "instance with \"lncli wallet accounts list\"", Value: "", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "account_addr_type", Usage: "the address type of the extended public key " + "specified in account. Currently only " + "pay-to-taproot-pubkey(p2tr) is supported", Value: "p2tr", }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "amt", Usage: "the amount in satoshis to loop out. To check " + "for the minimum and maximum amounts to loop " + "out please consult \"loop terms\"", }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "htlc_confs", Usage: "the number of confirmations (in blocks) " + "that we require for the htlc extended by " + "the server before we reveal the preimage", Value: uint64(loopdb.DefaultLoopOutHtlcConfirmations), }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "conf_target", Usage: "the number of blocks from the swap " + "initiation height that the on-chain HTLC " + "should be swept within", Value: uint64(loop.DefaultSweepConfTarget), }, - cli.Int64Flag{ + &cli.Int64Flag{ Name: "max_swap_routing_fee", Usage: "the max off-chain swap routing fee in " + "satoshis, if not specified, a default max " + "fee will be used", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "fast", Usage: "indicate you want to swap immediately, " + "paying potentially a higher fee. If not " + @@ -94,7 +94,7 @@ var loopOutCommand = cli.Command{ "Not setting this flag therefore might " + "result in a lower swap fee", }, - cli.DurationFlag{ + &cli.DurationFlag{ Name: "payment_timeout", Usage: "the timeout for each individual off-chain " + "payment attempt. If not set, the default " + @@ -102,14 +102,14 @@ var loopOutCommand = cli.Command{ "payment might be retried, the actual total " + "time may be longer", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "asset_id", Usage: "the asset ID of the asset to loop out, " + "if this is set, the loop daemon will " + "require a connection to a taproot assets " + "daemon", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "asset_edge_node", Usage: "the pubkey of the edge node of the asset to " + "loop out, this is required if the taproot " + @@ -124,19 +124,22 @@ var loopOutCommand = cli.Command{ Action: loopOut, } -func loopOut(ctx *cli.Context) error { - args := ctx.Args() +func loopOut(ctx context.Context, cmd *cli.Command) error { + args := cmd.Args() - var amtStr string + var ( + amtStr string + remaining []string + ) switch { - case ctx.IsSet("amt"): - amtStr = ctx.String("amt") - case ctx.NArg() == 1 || ctx.NArg() == 2: - amtStr = args[0] - args = args.Tail() + case cmd.IsSet("amt"): + amtStr = strconv.FormatUint(cmd.Uint64("amt"), 10) + case cmd.NArg() == 1 || cmd.NArg() == 2: + amtStr = args.First() + remaining = args.Tail() default: // Show command help if no arguments and flags were provided. - return cli.ShowCommandHelp(ctx, "out") + return showCommandHelp(ctx, cmd) } amt, err := parseAmt(amtStr) @@ -148,12 +151,12 @@ func loopOut(ctx *cli.Context) error { // Otherwise, strings.Split returns a slice of length one with an empty // element. var outgoingChanSet []uint64 - if ctx.IsSet("channel") { - if ctx.IsSet("asset_id") { + if cmd.IsSet("channel") { + if cmd.IsSet("asset_id") { return fmt.Errorf("channel flag is not supported when " + "looping out assets") } - chanStrings := strings.Split(ctx.String("channel"), ",") + chanStrings := strings.Split(cmd.String("channel"), ",") for _, chanString := range chanStrings { chanID, err := strconv.ParseUint(chanString, 10, 64) if err != nil { @@ -165,12 +168,12 @@ func loopOut(ctx *cli.Context) error { } // Validate our label early so that we can fail before getting a quote. - label := ctx.String(labelFlag.Name) + label := cmd.String(labelFlag.Name) if err := labels.Validate(label); err != nil { return err } - if ctx.IsSet("addr") && ctx.IsSet("account") { + if cmd.IsSet("addr") && cmd.IsSet("account") { return fmt.Errorf("cannot set --addr and --account at the " + "same time. Please specify only one source for a new " + "address to sweep the loop amount to") @@ -181,24 +184,24 @@ func loopOut(ctx *cli.Context) error { account string ) switch { - case ctx.IsSet("addr"): - destAddr = ctx.String("addr") + case cmd.IsSet("addr"): + destAddr = cmd.String("addr") - case ctx.IsSet("account"): - account = ctx.String("account") + case cmd.IsSet("account"): + account = cmd.String("account") - case args.Present(): - destAddr = args.First() + case len(remaining) > 0: + destAddr = remaining[0] } - if ctx.IsSet("account") != ctx.IsSet("account_addr_type") { + if cmd.IsSet("account") != cmd.IsSet("account_addr_type") { return fmt.Errorf("cannot set account without specifying " + "account address type and vice versa") } var accountAddrType looprpc.AddressType - if ctx.IsSet("account_addr_type") { - switch ctx.String("account_addr_type") { + if cmd.IsSet("account_addr_type") { + switch cmd.String("account_addr_type") { case "p2tr": accountAddrType = looprpc.AddressType_TAPROOT_PUBKEY @@ -210,19 +213,19 @@ func loopOut(ctx *cli.Context) error { var assetLoopOutInfo *looprpc.AssetLoopOutRequest var assetId []byte - if ctx.IsSet("asset_id") { - if !ctx.IsSet("asset_edge_node") { + if cmd.IsSet("asset_id") { + if !cmd.IsSet("asset_edge_node") { return fmt.Errorf("asset edge node is required when " + "assetid is set") } - assetId, err = hex.DecodeString(ctx.String("asset_id")) + assetId, err = hex.DecodeString(cmd.String("asset_id")) if err != nil { return err } assetEdgeNode, err := hex.DecodeString( - ctx.String("asset_edge_node"), + cmd.String("asset_edge_node"), ) if err != nil { return err @@ -234,7 +237,7 @@ func loopOut(ctx *cli.Context) error { } } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } @@ -242,14 +245,14 @@ func loopOut(ctx *cli.Context) 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 := ctx.Bool("fast") + fast := cmd.Bool("fast") swapDeadline := time.Now() if !fast { swapDeadline = time.Now().Add(defaultSwapWaitTime) } - sweepConfTarget := int32(ctx.Uint64("conf_target")) - htlcConfs := int32(ctx.Uint64("htlc_confs")) + sweepConfTarget := int32(cmd.Uint64("conf_target")) + htlcConfs := int32(cmd.Uint64("htlc_confs")) if htlcConfs == 0 { return fmt.Errorf("at least 1 confirmation required for htlcs") } @@ -260,7 +263,7 @@ func loopOut(ctx *cli.Context) error { SwapPublicationDeadline: uint64(swapDeadline.Unix()), AssetInfo: assetLoopOutInfo, } - quote, err := client.LoopOutQuote(context.Background(), quoteReq) + quote, err := client.LoopOutQuote(ctx, quoteReq) if err != nil { return err } @@ -277,16 +280,16 @@ func loopOut(ctx *cli.Context) error { limits := getOutLimits(amt, quote) // If configured, use the specified maximum swap routing fee. - if ctx.IsSet("max_swap_routing_fee") { + if cmd.IsSet("max_swap_routing_fee") { limits.maxSwapRoutingFee = btcutil.Amount( - ctx.Int64("max_swap_routing_fee"), + cmd.Int64("max_swap_routing_fee"), ) } // Skip showing details if configured - if !(ctx.Bool("force") || ctx.Bool("f")) { + if !(cmd.Bool("force") || cmd.Bool("f")) { err = displayOutDetails( - limits, warning, quoteReq, quote, ctx.Bool("verbose"), + limits, warning, quoteReq, quote, cmd.Bool("verbose"), ) if err != nil { return err @@ -294,8 +297,8 @@ func loopOut(ctx *cli.Context) error { } var paymentTimeout int64 - if ctx.IsSet("payment_timeout") { - parsedTimeout := ctx.Duration("payment_timeout") + if cmd.IsSet("payment_timeout") { + parsedTimeout := cmd.Duration("payment_timeout") if parsedTimeout.Truncate(time.Second) != parsedTimeout { return fmt.Errorf("payment timeout must be a " + "whole number of seconds") @@ -312,7 +315,7 @@ func loopOut(ctx *cli.Context) error { } } - resp, err := client.LoopOut(context.Background(), &looprpc.LoopOutRequest{ + resp, err := client.LoopOut(ctx, &looprpc.LoopOutRequest{ Amt: int64(amt), Dest: destAddr, IsExternalAddr: destAddr != "", diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 4544dfac4..a0f5a5bda 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -21,7 +22,7 @@ import ( "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/macaroons" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/protobuf/proto" @@ -50,44 +51,46 @@ var ( // user agent string we send when using the command line utility. defaultInitiator = "loop-cli" - loopDirFlag = cli.StringFlag{ - Name: "loopdir", - Value: loopd.LoopDirBase, - Usage: "path to loop's base directory", - EnvVar: envVarLoopDir, + loopDirFlag = &cli.StringFlag{ + Name: "loopdir", + Value: loopd.LoopDirBase, + Usage: "path to loop's base directory", + Sources: cli.EnvVars(envVarLoopDir), } - networkFlag = cli.StringFlag{ - Name: "network, n", - Usage: "the network loop is running on e.g. mainnet, " + - "testnet, etc.", - Value: loopd.DefaultNetwork, - EnvVar: envVarNetwork, + networkFlag = &cli.StringFlag{ + Name: "network", + Aliases: []string{"n"}, + Usage: "the network loop is running on e.g. mainnet, testnet, etc.", + Value: loopd.DefaultNetwork, + Sources: cli.EnvVars(envVarNetwork), } - tlsCertFlag = cli.StringFlag{ - Name: "tlscertpath", - Usage: "path to loop's TLS certificate", - Value: loopd.DefaultTLSCertPath, - EnvVar: envVarTLSCertPath, + tlsCertFlag = &cli.StringFlag{ + Name: "tlscertpath", + Usage: "path to loop's TLS certificate", + Value: loopd.DefaultTLSCertPath, + Sources: cli.EnvVars(envVarTLSCertPath), } - macaroonPathFlag = cli.StringFlag{ - Name: "macaroonpath", - Usage: "path to macaroon file", - Value: loopd.DefaultMacaroonPath, - EnvVar: envVarMacaroonPath, + macaroonPathFlag = &cli.StringFlag{ + Name: "macaroonpath", + Usage: "path to macaroon file", + Value: loopd.DefaultMacaroonPath, + Sources: cli.EnvVars(envVarMacaroonPath), } - verboseFlag = cli.BoolFlag{ - Name: "verbose, v", - Usage: "show expanded details", + verboseFlag = &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"v"}, + Usage: "show expanded details", } - commands = []cli.Command{ + commands = []*cli.Command{ loopOutCommand, loopInCommand, termsCommand, monitorCommand, quoteCommand, listAuthCommand, fetchL402Command, listSwapsCommand, swapInfoCommand, getLiquidityParamsCommand, setLiquidityRuleCommand, suggestSwapCommand, setParamsCommand, getInfoCommand, abandonSwapCommand, reservationsCommands, instantOutCommand, listInstantOutsCommand, + printManCommand, printMarkdownCommand, } ) @@ -160,38 +163,40 @@ func fatal(err error) { } func main() { - app := cli.NewApp() - - app.Version = loop.RichVersion() - app.Name = "loop" - app.Usage = "control plane for your loopd" - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "rpcserver", - Value: "localhost:11010", - Usage: "loopd daemon address host:port", - EnvVar: envVarRPCServer, + rootCmd := &cli.Command{ + Name: "loop", + Usage: "control plane for your loopd", + Version: loop.RichVersion(), + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "rpcserver", + Value: "localhost:11010", + Usage: "loopd daemon address host:port", + Sources: cli.EnvVars(envVarRPCServer), + }, + networkFlag, + loopDirFlag, + tlsCertFlag, + macaroonPathFlag, + }, + Commands: commands, + Action: func(ctx context.Context, cmd *cli.Command) error { + return cli.ShowRootCommandHelp(cmd) }, - networkFlag, - loopDirFlag, - tlsCertFlag, - macaroonPathFlag, } - app.Commands = commands - err := app.Run(os.Args) - if err != nil { + if err := rootCmd.Run(context.Background(), os.Args); err != nil { fatal(err) } } -func getClient(ctx *cli.Context) (looprpc.SwapClientClient, func(), error) { - rpcServer := ctx.GlobalString("rpcserver") - tlsCertPath, macaroonPath, err := extractPathArgs(ctx) +func getClient(ctx context.Context, cmd *cli.Command) (looprpc.SwapClientClient, func(), error) { + rpcServer := cmd.String("rpcserver") + tlsCertPath, macaroonPath, err := extractPathArgs(cmd) if err != nil { return nil, nil, err } - conn, err := getClientConn(rpcServer, tlsCertPath, macaroonPath) + conn, err := getClientConn(ctx, rpcServer, tlsCertPath, macaroonPath) if err != nil { return nil, nil, err } @@ -207,11 +212,11 @@ func getMaxRoutingFee(amt btcutil.Amount) btcutil.Amount { // extractPathArgs parses the TLS certificate and macaroon paths from the // command. -func extractPathArgs(ctx *cli.Context) (string, string, error) { +func extractPathArgs(cmd *cli.Command) (string, string, error) { // We'll start off by parsing the network. This is needed to determine // the correct path to the TLS certificate and macaroon when not // specified. - networkStr := strings.ToLower(ctx.GlobalString("network")) + networkStr := strings.ToLower(cmd.String("network")) _, err := lndclient.Network(networkStr).ChainParams() if err != nil { return "", "", err @@ -220,12 +225,12 @@ func extractPathArgs(ctx *cli.Context) (string, string, error) { // We'll now fetch the loopdir so we can make a decision on how to // properly read the macaroons and also the cert. This will either be // the default, or will have been overwritten by the end user. - loopDir := lncfg.CleanAndExpandPath(ctx.GlobalString(loopDirFlag.Name)) + loopDir := lncfg.CleanAndExpandPath(cmd.String(loopDirFlag.Name)) - tlsCertPathRaw := ctx.GlobalString(tlsCertFlag.Name) + tlsCertPathRaw := cmd.String(tlsCertFlag.Name) tlsCertPath := lncfg.CleanAndExpandPath(tlsCertPathRaw) - macPathRaw := ctx.GlobalString(macaroonPathFlag.Name) + macPathRaw := cmd.String(macaroonPathFlag.Name) macPath := lncfg.CleanAndExpandPath(macPathRaw) // If a custom loop directory or network was set, we'll also check if @@ -361,8 +366,13 @@ func displayOutDetails(l *outLimits, warning string, req *looprpc.QuoteRequest, func parseAmt(text string) (btcutil.Amount, error) { amtInt64, err := strconv.ParseInt(text, 10, 64) if err != nil { - return 0, fmt.Errorf("invalid amt value") + return 0, fmt.Errorf("invalid amt value %q", text) } + + if amtInt64 < 0 { + return 0, fmt.Errorf("negative amount %d", amtInt64) + } + return btcutil.Amount(amtInt64), nil } @@ -405,7 +415,7 @@ func logSwap(swap *looprpc.SwapStatus) { fmt.Println() } -func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn, +func getClientConn(ctx context.Context, address, tlsCertPath, macaroonPath string) (*grpc.ClientConn, error) { // We always need to send a macaroon. @@ -427,7 +437,7 @@ func getClientConn(address, tlsCertPath, macaroonPath string) (*grpc.ClientConn, opts = append(opts, grpc.WithTransportCredentials(creds)) - conn, err := grpc.Dial(address, opts...) + conn, err := grpc.DialContext(ctx, address, opts...) if err != nil { return nil, fmt.Errorf("unable to connect to RPC server: %v", err) diff --git a/cmd/loop/markdown_tabular.md.gotmpl b/cmd/loop/markdown_tabular.md.gotmpl new file mode 100644 index 000000000..4697d33cd --- /dev/null +++ b/cmd/loop/markdown_tabular.md.gotmpl @@ -0,0 +1,80 @@ +{{ define "flags" }} +{{- $hasEnvVars := false -}} +{{- range . -}} +{{- if and (not $hasEnvVars) .EnvVars -}} +{{- $hasEnvVars = true -}} +{{- end -}} +{{- end }} +| Name | Description | Type | Default value {{ if $hasEnvVars }}| Environment variables {{ end }}| +|------|-------------|------|:-------------:{{ if $hasEnvVars }}|:---------------------:{{ end }}| +{{ range $flag := . -}} +{{- /**/ -}} | `{{ $flag.Name }}{{ if $flag.TakesValue }}="…"{{ end }}` {{ if $flag.Aliases }}(`{{ join $flag.Aliases "`, `" }}`) {{ end }} +{{- /**/ -}} | {{ $flag.Usage }} +{{- /**/ -}} | {{ $flag.Type }} +{{- /**/ -}} | {{ if $flag.Default }}`{{ $flag.Default }}`{{ end }} +{{- if $hasEnvVars -}} +{{- /**/ -}} | {{ if $flag.EnvVars }}`{{ join $flag.EnvVars "`, `" }}`{{ else }}*none*{{ end }} +{{- end -}} +{{- /**/ -}} | +{{ end }} +{{ end }} + +{{ define "command" }} +### `{{ .Name }}` {{ if gt .Level 0 }}sub{{ end }}command{{ if .Aliases }} (aliases: `{{ join .Aliases "`, `" }}`){{ end }} +{{ if .Usage }} +{{ .Usage }}. +{{ end }} +{{ if .UsageText }} +{{ range $line := .UsageText -}} +> {{ $line }} +{{ end -}} +{{ end }} +{{ if .Description }} +{{ .Description }}. +{{ end }} +Usage: + +```bash +$ {{ .AppPath }} [GLOBAL FLAGS] {{ .Name }}{{ if .Flags }} [COMMAND FLAGS]{{ end }} {{ if .ArgsUsage }}{{ .ArgsUsage }}{{ else }}[ARGUMENTS...]{{ end }} +``` + +{{ if .Flags -}} +The following flags are supported: +{{ template "flags" .Flags }} +{{ end -}} + +{{ if .SubCommands -}} +{{ range $subCmd := .SubCommands -}} +{{ template "command" $subCmd }} +{{ end -}} +{{ end -}} +{{ end }} + +## CLI interface{{ if .Name }} - {{ .Name }}{{ end }} + +{{ if .Description }}{{ .Description }}. +{{ end }} +{{ if .Usage }}{{ .Usage }}. +{{ end }} +{{ if .UsageText }} +{{ range $line := .UsageText -}} +> {{ $line }} +{{ end -}} +{{ end }} +Usage: + +```bash +$ {{ .AppPath }}{{ if .GlobalFlags }} [GLOBAL FLAGS]{{ end }} [COMMAND] [COMMAND FLAGS] {{ if .ArgsUsage }}{{ .ArgsUsage }}{{ else }}[ARGUMENTS...]{{ end }} +``` + +{{ if .GlobalFlags }} +Global flags: + +{{ template "flags" .GlobalFlags }} + +{{ end -}} +{{ if .Commands -}} +{{ range $cmd := .Commands -}} +{{ template "command" $cmd }} +{{ end }} +{{- end }} diff --git a/cmd/loop/monitor.go b/cmd/loop/monitor.go index 7247cf82d..28794edb8 100644 --- a/cmd/loop/monitor.go +++ b/cmd/loop/monitor.go @@ -5,25 +5,25 @@ import ( "fmt" "github.com/lightninglabs/loop/looprpc" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) -var monitorCommand = cli.Command{ +var monitorCommand = &cli.Command{ Name: "monitor", Usage: "monitor progress of any active swaps", Description: "Allows the user to monitor progress of any active swaps", Action: monitor, } -func monitor(ctx *cli.Context) error { - client, cleanup, err := getClient(ctx) +func monitor(ctx context.Context, cmd *cli.Command) error { + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() stream, err := client.Monitor( - context.Background(), &looprpc.MonitorRequest{}) + ctx, &looprpc.MonitorRequest{}) if err != nil { return err } diff --git a/cmd/loop/quote.go b/cmd/loop/quote.go index 22b5a0d88..db23304c1 100644 --- a/cmd/loop/quote.go +++ b/cmd/loop/quote.go @@ -14,23 +14,23 @@ import ( "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/routing/route" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) -var quoteCommand = cli.Command{ - Name: "quote", - Usage: "get a quote for the cost of a swap", - Subcommands: []cli.Command{quoteInCommand, quoteOutCommand}, +var quoteCommand = &cli.Command{ + Name: "quote", + Usage: "get a quote for the cost of a swap", + Commands: []*cli.Command{quoteInCommand, quoteOutCommand}, } -var quoteInCommand = cli.Command{ +var quoteInCommand = &cli.Command{ Name: "in", Usage: "get a quote for the cost of a loop in swap", ArgsUsage: "amt", Description: "Allows to determine the cost of a swap up front." + "Either specify an amount or deposit outpoints.", Flags: []cli.Flag{ - cli.StringFlag{ + &cli.StringFlag{ Name: lastHopFlag.Name, Usage: "the pubkey of the last hop to use for the " + "quote", @@ -39,7 +39,7 @@ var quoteInCommand = cli.Command{ verboseFlag, privateFlag, routeHintsFlag, - cli.StringSliceFlag{ + &cli.StringSliceFlag{ Name: "deposit_outpoint", Usage: "one or more static address deposit outpoints " + "to quote for. Deposit outpoints are not to " + @@ -51,10 +51,10 @@ var quoteInCommand = cli.Command{ Action: quoteIn, } -func quoteIn(ctx *cli.Context) error { +func quoteIn(ctx context.Context, cmd *cli.Command) error { // Show command help if the incorrect number arguments was provided. - if ctx.NArg() != 1 && !ctx.IsSet("deposit_outpoint") { - return cli.ShowCommandHelp(ctx, "in") + if cmd.NArg() != 1 && !cmd.IsSet("deposit_outpoint") { + return showCommandHelp(ctx, cmd) } var ( @@ -62,18 +62,17 @@ func quoteIn(ctx *cli.Context) error { depositAmt btcutil.Amount depositOutpoints []string err error - ctxb = context.Background() ) - if ctx.NArg() == 1 { - args := ctx.Args() - manualAmt, err = parseAmt(args[0]) + if cmd.NArg() == 1 { + args := cmd.Args() + manualAmt, err = parseAmt(args.First()) if err != nil { return err } } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } @@ -81,14 +80,14 @@ func quoteIn(ctx *cli.Context) error { // Private and routehints are mutually exclusive as setting private // means we retrieve our own routehints from the connected node. - hints, err := validateRouteHints(ctx) + hints, err := validateRouteHints(cmd) if err != nil { return err } - if ctx.IsSet("deposit_outpoint") { - depositOutpoints = ctx.StringSlice("deposit_outpoint") - depositAmt, err = depositAmount(ctxb, client, depositOutpoints) + if cmd.IsSet("deposit_outpoint") { + depositOutpoints = cmd.StringSlice("deposit_outpoint") + depositAmt, err = depositAmount(ctx, client, depositOutpoints) if err != nil { return err } @@ -96,15 +95,15 @@ func quoteIn(ctx *cli.Context) error { quoteReq := &looprpc.QuoteRequest{ Amt: int64(manualAmt), - ConfTarget: int32(ctx.Uint64("conf_target")), + ConfTarget: int32(cmd.Uint64("conf_target")), LoopInRouteHints: hints, - Private: ctx.Bool(privateFlag.Name), + Private: cmd.Bool(privateFlag.Name), DepositOutpoints: depositOutpoints, } - if ctx.IsSet(lastHopFlag.Name) { + if cmd.IsSet(lastHopFlag.Name) { lastHopVertex, err := route.NewVertexFromStr( - ctx.String(lastHopFlag.Name), + cmd.String(lastHopFlag.Name), ) if err != nil { return err @@ -113,7 +112,7 @@ func quoteIn(ctx *cli.Context) error { quoteReq.LoopInLastHop = lastHopVertex[:] } - quoteResp, err := client.GetLoopInQuote(ctxb, quoteReq) + quoteResp, err := client.GetLoopInQuote(ctx, quoteReq) if err != nil { return err } @@ -136,7 +135,7 @@ func quoteIn(ctx *cli.Context) error { if manualAmt == 0 { quoteReq.Amt = int64(depositAmt) } - printQuoteInResp(quoteReq, quoteResp, ctx.Bool("verbose")) + printQuoteInResp(quoteReq, quoteResp, cmd.Bool("verbose")) return nil } @@ -160,20 +159,20 @@ func depositAmount(ctx context.Context, client looprpc.SwapClientClient, return depositAmt, nil } -var quoteOutCommand = cli.Command{ +var quoteOutCommand = &cli.Command{ Name: "out", Usage: "get a quote for the cost of a loop out swap", ArgsUsage: "amt", Description: "Allows to determine the cost of a swap up front", Flags: []cli.Flag{ - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "conf_target", Usage: "the number of blocks from the swap " + "initiation height that the on-chain HTLC " + "should be swept within in a Loop Out", Value: uint64(loop.DefaultSweepConfTarget), }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "fast", Usage: "Indicate you want to swap immediately, " + "paying potentially a higher fee. If not " + @@ -188,42 +187,41 @@ var quoteOutCommand = cli.Command{ Action: quoteOut, } -func quoteOut(ctx *cli.Context) error { +func quoteOut(ctx context.Context, cmd *cli.Command) error { // Show command help if the incorrect number arguments was provided. - if ctx.NArg() != 1 { - return cli.ShowCommandHelp(ctx, "out") + if cmd.NArg() != 1 { + return showCommandHelp(ctx, cmd) } - args := ctx.Args() - amt, err := parseAmt(args[0]) + args := cmd.Args() + amt, err := parseAmt(args.First()) if err != nil { return err } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() - fast := ctx.Bool("fast") + fast := cmd.Bool("fast") swapDeadline := time.Now() if !fast { swapDeadline = time.Now().Add(defaultSwapWaitTime) } - ctxb := context.Background() quoteReq := &looprpc.QuoteRequest{ Amt: int64(amt), - ConfTarget: int32(ctx.Uint64("conf_target")), + ConfTarget: int32(cmd.Uint64("conf_target")), SwapPublicationDeadline: uint64(swapDeadline.Unix()), } - quoteResp, err := client.LoopOutQuote(ctxb, quoteReq) + quoteResp, err := client.LoopOutQuote(ctx, quoteReq) if err != nil { return err } - printQuoteOutResp(quoteReq, quoteResp, ctx.Bool("verbose")) + printQuoteOutResp(quoteReq, quoteResp, cmd.Bool("verbose")) return nil } diff --git a/cmd/loop/reservations.go b/cmd/loop/reservations.go index 9f5c123d0..4e26fc048 100644 --- a/cmd/loop/reservations.go +++ b/cmd/loop/reservations.go @@ -4,29 +4,29 @@ import ( "context" "github.com/lightninglabs/loop/looprpc" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) -var reservationsCommands = cli.Command{ +var reservationsCommands = &cli.Command{ - Name: "reservations", - ShortName: "r", - Usage: "manage reservations", + Name: "reservations", + Aliases: []string{"r"}, + Usage: "manage reservations", Description: ` With loopd running, you can use this command to manage your reservations. Reservations are 2-of-2 multisig utxos that the loop server can open to clients. The reservations are used to enable instant swaps. `, - Subcommands: []cli.Command{ + Commands: []*cli.Command{ listReservationsCommand, }, } var ( - listReservationsCommand = cli.Command{ + listReservationsCommand = &cli.Command{ Name: "list", - ShortName: "l", + Aliases: []string{"l"}, Usage: "list all reservations", ArgsUsage: "", Description: ` @@ -36,15 +36,15 @@ var ( } ) -func listReservations(ctx *cli.Context) error { - client, cleanup, err := getClient(ctx) +func listReservations(ctx context.Context, cmd *cli.Command) error { + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() resp, err := client.ListReservations( - context.Background(), &looprpc.ListReservationsRequest{}, + ctx, &looprpc.ListReservationsRequest{}, ) if err != nil { return err diff --git a/cmd/loop/staticaddr.go b/cmd/loop/staticaddr.go index 774b9e3da..27e07433a 100644 --- a/cmd/loop/staticaddr.go +++ b/cmd/loop/staticaddr.go @@ -15,18 +15,18 @@ import ( "github.com/lightninglabs/loop/staticaddr/loopin" "github.com/lightninglabs/loop/swapserverrpc" "github.com/lightningnetwork/lnd/routing/route" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) func init() { commands = append(commands, staticAddressCommands) } -var staticAddressCommands = cli.Command{ - Name: "static", - ShortName: "s", - Usage: "perform on-chain to off-chain swaps using static addresses.", - Subcommands: []cli.Command{ +var staticAddressCommands = &cli.Command{ + Name: "static", + Aliases: []string{"s"}, + Usage: "perform on-chain to off-chain swaps using static addresses.", + Commands: []*cli.Command{ newStaticAddressCommand, listUnspentCommand, listDepositsCommand, @@ -38,10 +38,10 @@ var staticAddressCommands = cli.Command{ }, } -var newStaticAddressCommand = cli.Command{ - Name: "new", - ShortName: "n", - Usage: "Create a new static loop in address.", +var newStaticAddressCommand = &cli.Command{ + Name: "new", + Aliases: []string{"n"}, + Usage: "Create a new static loop in address.", Description: ` Requests a new static loop in address from the server. Funds that are sent to this address will be locked by a 2:2 multisig between us and the @@ -52,10 +52,9 @@ var newStaticAddressCommand = cli.Command{ Action: newStaticAddress, } -func newStaticAddress(ctx *cli.Context) error { - ctxb := context.Background() - if ctx.NArg() > 0 { - return cli.ShowCommandHelp(ctx, "new") +func newStaticAddress(ctx context.Context, cmd *cli.Command) error { + if cmd.NArg() > 0 { + return showCommandHelp(ctx, cmd) } err := displayNewAddressWarning() @@ -63,14 +62,14 @@ func newStaticAddress(ctx *cli.Context) error { return err } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() resp, err := client.NewStaticAddress( - ctxb, &looprpc.NewStaticAddressRequest{}, + ctx, &looprpc.NewStaticAddressRequest{}, ) if err != nil { return err @@ -81,20 +80,20 @@ func newStaticAddress(ctx *cli.Context) error { return nil } -var listUnspentCommand = cli.Command{ - Name: "listunspent", - ShortName: "l", - Usage: "List unspent static address outputs.", +var listUnspentCommand = &cli.Command{ + Name: "listunspent", + Aliases: []string{"l"}, + Usage: "List unspent static address outputs.", Description: ` List all unspent static address outputs. `, Flags: []cli.Flag{ - cli.IntFlag{ + &cli.IntFlag{ Name: "min_confs", Usage: "The minimum amount of confirmations an " + "output should have to be listed.", }, - cli.IntFlag{ + &cli.IntFlag{ Name: "max_confs", Usage: "The maximum number of confirmations an " + "output could have to be listed.", @@ -103,22 +102,21 @@ var listUnspentCommand = cli.Command{ Action: listUnspent, } -func listUnspent(ctx *cli.Context) error { - ctxb := context.Background() - if ctx.NArg() > 0 { - return cli.ShowCommandHelp(ctx, "listunspent") +func listUnspent(ctx context.Context, cmd *cli.Command) error { + if cmd.NArg() > 0 { + return showCommandHelp(ctx, cmd) } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() resp, err := client.ListUnspentDeposits( - ctxb, &looprpc.ListUnspentDepositsRequest{ - MinConfs: int32(ctx.Int("min_confs")), - MaxConfs: int32(ctx.Int("max_confs")), + ctx, &looprpc.ListUnspentDepositsRequest{ + MinConfs: int32(cmd.Int("min_confs")), + MaxConfs: int32(cmd.Int("max_confs")), }) if err != nil { return err @@ -129,37 +127,37 @@ func listUnspent(ctx *cli.Context) error { return nil } -var withdrawalCommand = cli.Command{ - Name: "withdraw", - ShortName: "w", - Usage: "Withdraw from static address deposits.", +var withdrawalCommand = &cli.Command{ + Name: "withdraw", + Aliases: []string{"w"}, + Usage: "Withdraw from static address deposits.", Description: ` Withdraws from all or selected static address deposits by sweeping them to the internal wallet or an external address. `, Flags: []cli.Flag{ - cli.StringSliceFlag{ + &cli.StringSliceFlag{ Name: "utxo", Usage: "specify utxos as outpoints(tx:idx) which will" + "be withdrawn.", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "all", Usage: "withdraws all static address deposits.", }, - cli.StringFlag{ + &cli.StringFlag{ Name: "dest_addr", Usage: "the optional address that the withdrawn " + "funds should be sent to, if let blank the " + "funds will go to lnd's wallet", }, - cli.Int64Flag{ + &cli.Int64Flag{ Name: "sat_per_vbyte", Usage: "(optional) a manual fee expressed in " + "sat/vbyte that should be used when crafting " + "the transaction", }, - cli.IntFlag{ + &cli.IntFlag{ Name: "amount", Usage: "the number of satoshis that should be " + "withdrawn from the selected deposits. The " + @@ -169,22 +167,21 @@ var withdrawalCommand = cli.Command{ Action: withdraw, } -func withdraw(ctx *cli.Context) error { - if ctx.NArg() > 0 { - return cli.ShowCommandHelp(ctx, "withdraw") +func withdraw(ctx context.Context, cmd *cli.Command) error { + if cmd.NArg() > 0 { + return showCommandHelp(ctx, cmd) } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() var ( - isAllSelected = ctx.IsSet("all") - isUtxoSelected = ctx.IsSet("utxo") + isAllSelected = cmd.IsSet("all") + isUtxoSelected = cmd.IsSet("utxo") outpoints []*looprpc.OutPoint - ctxb = context.Background() destAddr string ) @@ -194,7 +191,7 @@ func withdraw(ctx *cli.Context) error { case isAllSelected: case isUtxoSelected: - utxos := ctx.StringSlice("utxo") + utxos := cmd.StringSlice("utxo") outpoints, err = utxosToOutpoints(utxos) if err != nil { return err @@ -204,17 +201,17 @@ func withdraw(ctx *cli.Context) error { return fmt.Errorf("unknown withdrawal request") } - if ctx.IsSet("dest_addr") { - destAddr = ctx.String("dest_addr") + if cmd.IsSet("dest_addr") { + destAddr = cmd.String("dest_addr") } - resp, err := client.WithdrawDeposits(ctxb, + resp, err := client.WithdrawDeposits(ctx, &looprpc.WithdrawDepositsRequest{ Outpoints: outpoints, All: isAllSelected, DestAddr: destAddr, - SatPerVbyte: int64(ctx.Uint64("sat_per_vbyte")), - Amount: ctx.Int64("amount"), + SatPerVbyte: int64(cmd.Uint64("sat_per_vbyte")), + Amount: cmd.Int64("amount"), }) if err != nil { return err @@ -225,14 +222,14 @@ func withdraw(ctx *cli.Context) error { return nil } -var listDepositsCommand = cli.Command{ +var listDepositsCommand = &cli.Command{ Name: "listdeposits", Usage: "Displays static address deposits. A filter can be applied to " + "only show deposits in a specific state.", Description: ` `, Flags: []cli.Flag{ - cli.StringFlag{ + &cli.StringFlag{ Name: "filter", Usage: "specify a filter to only display deposits in " + "the specified state. Leaving out the filter " + @@ -248,20 +245,19 @@ var listDepositsCommand = cli.Command{ Action: listDeposits, } -func listDeposits(ctx *cli.Context) error { - ctxb := context.Background() - if ctx.NArg() > 0 { - return cli.ShowCommandHelp(ctx, "listdeposits") +func listDeposits(ctx context.Context, cmd *cli.Command) error { + if cmd.NArg() > 0 { + return showCommandHelp(ctx, cmd) } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() var filterState looprpc.DepositState - switch ctx.String("filter") { + switch cmd.String("filter") { case "": // If no filter is specified, we'll default to showing all. @@ -300,7 +296,7 @@ func listDeposits(ctx *cli.Context) error { } resp, err := client.ListStaticAddressDeposits( - ctxb, &looprpc.ListStaticAddressDepositsRequest{ + ctx, &looprpc.ListStaticAddressDepositsRequest{ StateFilter: filterState, }, ) @@ -313,7 +309,7 @@ func listDeposits(ctx *cli.Context) error { return nil } -var listWithdrawalsCommand = cli.Command{ +var listWithdrawalsCommand = &cli.Command{ Name: "listwithdrawals", Usage: "Display a summary of past withdrawals.", Description: ` @@ -321,20 +317,19 @@ var listWithdrawalsCommand = cli.Command{ Action: listWithdrawals, } -func listWithdrawals(ctx *cli.Context) error { - ctxb := context.Background() - if ctx.NArg() > 0 { - return cli.ShowCommandHelp(ctx, "withdrawals") +func listWithdrawals(ctx context.Context, cmd *cli.Command) error { + if cmd.NArg() > 0 { + return showCommandHelp(ctx, cmd) } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() resp, err := client.ListStaticAddressWithdrawals( - ctxb, &looprpc.ListStaticAddressWithdrawalRequest{}, + ctx, &looprpc.ListStaticAddressWithdrawalRequest{}, ) if err != nil { return err @@ -345,7 +340,7 @@ func listWithdrawals(ctx *cli.Context) error { return nil } -var listStaticAddressSwapsCommand = cli.Command{ +var listStaticAddressSwapsCommand = &cli.Command{ Name: "listswaps", Usage: "Shows a list of finalized static address swaps.", Description: ` @@ -353,20 +348,19 @@ var listStaticAddressSwapsCommand = cli.Command{ Action: listStaticAddressSwaps, } -func listStaticAddressSwaps(ctx *cli.Context) error { - ctxb := context.Background() - if ctx.NArg() > 0 { - return cli.ShowCommandHelp(ctx, "listswaps") +func listStaticAddressSwaps(ctx context.Context, cmd *cli.Command) error { + if cmd.NArg() > 0 { + return showCommandHelp(ctx, cmd) } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() resp, err := client.ListStaticAddressSwaps( - ctxb, &looprpc.ListStaticAddressSwapsRequest{}, + ctx, &looprpc.ListStaticAddressSwapsRequest{}, ) if err != nil { return err @@ -377,10 +371,10 @@ func listStaticAddressSwaps(ctx *cli.Context) error { return nil } -var summaryCommand = cli.Command{ - Name: "summary", - ShortName: "s", - Usage: "Display a summary of static address related information.", +var summaryCommand = &cli.Command{ + Name: "summary", + Aliases: []string{"s"}, + Usage: "Display a summary of static address related information.", Description: ` Displays various static address related information about deposits, withdrawals and swaps. @@ -388,20 +382,19 @@ var summaryCommand = cli.Command{ Action: summary, } -func summary(ctx *cli.Context) error { - ctxb := context.Background() - if ctx.NArg() > 0 { - return cli.ShowCommandHelp(ctx, "summary") +func summary(ctx context.Context, cmd *cli.Command) error { + if cmd.NArg() > 0 { + return showCommandHelp(ctx, cmd) } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() resp, err := client.GetStaticAddressSummary( - ctxb, &looprpc.StaticAddressSummaryRequest{}, + ctx, &looprpc.StaticAddressSummaryRequest{}, ) if err != nil { return err @@ -452,9 +445,10 @@ func NewProtoOutPoint(op string) (*looprpc.OutPoint, error) { }, nil } -var staticAddressLoopInCommand = cli.Command{ - Name: "in", - Usage: "Loop in funds from static address deposits.", +var staticAddressLoopInCommand = &cli.Command{ + Name: "in", + Usage: "Loop in funds from static address deposits.", + ArgsUsage: "[amt] [--all | --utxo xxx:xx]", Description: ` Requests a loop-in swap based on static address deposits. After the creation of a static address funds can be sent to it. Once the funds are @@ -462,30 +456,30 @@ var staticAddressLoopInCommand = cli.Command{ funds are not needed they can we withdrawn back to the local lnd wallet. `, Flags: []cli.Flag{ - cli.StringSliceFlag{ + &cli.StringSliceFlag{ Name: "utxo", Usage: "specify the utxos of deposits as " + "outpoints(tx:idx) that should be looped in.", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "all", Usage: "loop in all static address deposits.", }, - cli.DurationFlag{ + &cli.DurationFlag{ Name: "payment_timeout", Usage: "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.", }, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "amount", Usage: "the number of satoshis that should be " + "swapped from the selected deposits. If there" + "is change it is sent back to the static " + "address.", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "fast", Usage: "Usage: complete the swap faster by paying a " + "higher fee, so the change output is " + @@ -501,40 +495,39 @@ var staticAddressLoopInCommand = cli.Command{ Action: staticAddressLoopIn, } -func staticAddressLoopIn(ctx *cli.Context) error { - if ctx.NumFlags() == 0 && ctx.NArg() == 0 { - return cli.ShowCommandHelp(ctx, "in") +func staticAddressLoopIn(ctx context.Context, cmd *cli.Command) error { + if cmd.NumFlags() == 0 && cmd.NArg() == 0 { + return showCommandHelp(ctx, cmd) } var selectedAmount int64 switch { - case ctx.NArg() == 1: - amt, err := parseAmt(ctx.Args().Get(0)) + case cmd.NArg() == 1: + amt, err := parseAmt(cmd.Args().Get(0)) if err != nil { return err } selectedAmount = int64(amt) - case ctx.NArg() > 1: + case cmd.NArg() > 1: return fmt.Errorf("only a single positional argument is " + "allowed") default: - selectedAmount = ctx.Int64("amount") + selectedAmount = cmd.Int64("amount") } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() var ( - ctxb = context.Background() - isAllSelected = ctx.IsSet("all") - isUtxoSelected = ctx.IsSet("utxo") + isAllSelected = cmd.IsSet("all") + isUtxoSelected = cmd.IsSet("utxo") autoSelectDepositsForQuote bool - label = ctx.String("static-loop-in") + label = cmd.String(labelFlag.Name) hints []*swapserverrpc.RouteHint lastHop []byte paymentTimeoutSeconds = uint32(loopin.DefaultPaymentTimeoutSeconds) @@ -547,14 +540,14 @@ func staticAddressLoopIn(ctx *cli.Context) error { // Private and route hints are mutually exclusive as setting private // means we retrieve our own route hints from the connected node. - hints, err = validateRouteHints(ctx) + hints, err = validateRouteHints(cmd) if err != nil { return err } - if ctx.IsSet(lastHopFlag.Name) { + if cmd.IsSet(lastHopFlag.Name) { lastHopVertex, err := route.NewVertexFromStr( - ctx.String(lastHopFlag.Name), + cmd.String(lastHopFlag.Name), ) if err != nil { return err @@ -565,7 +558,7 @@ func staticAddressLoopIn(ctx *cli.Context) error { // Get the amount we need to quote for. depositList, err := client.ListStaticAddressDeposits( - ctxb, &looprpc.ListStaticAddressDepositsRequest{ + ctx, &looprpc.ListStaticAddressDepositsRequest{ StateFilter: looprpc.DepositState_DEPOSITED, }, ) @@ -592,7 +585,7 @@ func staticAddressLoopIn(ctx *cli.Context) error { depositOutpoints = depositsToOutpoints(allDeposits) case isUtxoSelected: - depositOutpoints = ctx.StringSlice("utxo") + depositOutpoints = cmd.StringSlice("utxo") case selectedAmount > 0: // If only an amount is selected, we will trigger coin @@ -614,27 +607,27 @@ func staticAddressLoopIn(ctx *cli.Context) error { Amt: selectedAmount, LoopInRouteHints: hints, LoopInLastHop: lastHop, - Private: ctx.Bool(privateFlag.Name), + Private: cmd.Bool(privateFlag.Name), DepositOutpoints: depositOutpoints, AutoSelectDeposits: autoSelectDepositsForQuote, - Fast: ctx.Bool("fast"), + Fast: cmd.Bool("fast"), } - quote, err := client.GetLoopInQuote(ctxb, quoteReq) + quote, err := client.GetLoopInQuote(ctx, quoteReq) if err != nil { return err } limits := getInLimits(quote) - if !(ctx.Bool("force") || ctx.Bool("f")) { - err = displayInDetails(quoteReq, quote, ctx.Bool("verbose")) + if !(cmd.Bool("force") || cmd.Bool("f")) { + err = displayInDetails(quoteReq, quote, cmd.Bool("verbose")) if err != nil { return err } } - if ctx.IsSet("payment_timeout") { - paymentTimeoutSeconds = uint32(ctx.Duration("payment_timeout").Seconds()) + if cmd.IsSet("payment_timeout") { + paymentTimeoutSeconds = uint32(cmd.Duration("payment_timeout").Seconds()) } req := &looprpc.StaticAddressLoopInRequest{ @@ -642,15 +635,15 @@ func staticAddressLoopIn(ctx *cli.Context) error { Outpoints: depositOutpoints, MaxSwapFeeSatoshis: int64(limits.maxSwapFee), LastHop: lastHop, - Label: ctx.String(labelFlag.Name), + Label: label, Initiator: defaultInitiator, RouteHints: hints, - Private: ctx.Bool("private"), + Private: cmd.Bool("private"), PaymentTimeoutSeconds: paymentTimeoutSeconds, - Fast: ctx.Bool("fast"), + Fast: cmd.Bool("fast"), } - resp, err := client.StaticAddressLoopIn(ctxb, req) + resp, err := client.StaticAddressLoopIn(ctx, req) if err != nil { return err } diff --git a/cmd/loop/swaps.go b/cmd/loop/swaps.go index 73efb381b..899925e91 100644 --- a/cmd/loop/swaps.go +++ b/cmd/loop/swaps.go @@ -10,36 +10,36 @@ import ( "github.com/lightninglabs/loop/looprpc" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/routing/route" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) -var listSwapsCommand = cli.Command{ +var listSwapsCommand = &cli.Command{ Name: "listswaps", Usage: "list all swaps in the local database", Description: "Allows the user to get a list of all swaps that are " + "currently stored in the database", Action: listSwaps, Flags: []cli.Flag{ - cli.BoolFlag{ + &cli.BoolFlag{ Name: "loop_out_only", Usage: "only list swaps that are loop out swaps", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "loop_in_only", Usage: "only list swaps that are loop in swaps", }, - cli.BoolFlag{ + &cli.BoolFlag{ Name: "pending_only", Usage: "only list pending swaps", }, labelFlag, channelFlag, lastHopFlag, - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "max_swaps", Usage: "Max number of swaps to return after filtering", }, - cli.Int64Flag{ + &cli.Int64Flag{ Name: "start_time_ns", Usage: "Unix timestamp in nanoseconds to select swaps initiated " + "after this time", @@ -47,14 +47,14 @@ var listSwapsCommand = cli.Command{ }, } -func listSwaps(ctx *cli.Context) error { - client, cleanup, err := getClient(ctx) +func listSwaps(ctx context.Context, cmd *cli.Command) error { + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() - if ctx.Bool("loop_out_only") && ctx.Bool("loop_in_only") { + if cmd.Bool("loop_out_only") && cmd.Bool("loop_in_only") { return fmt.Errorf("only one of loop_out_only and loop_in_only " + "can be set") } @@ -63,21 +63,21 @@ func listSwaps(ctx *cli.Context) error { // Set the swap type filter. switch { - case ctx.Bool("loop_out_only"): + case cmd.Bool("loop_out_only"): filter.SwapType = looprpc.ListSwapsFilter_LOOP_OUT - case ctx.Bool("loop_in_only"): + case cmd.Bool("loop_in_only"): filter.SwapType = looprpc.ListSwapsFilter_LOOP_IN } // Set the pending only filter. - filter.PendingOnly = ctx.Bool("pending_only") + filter.PendingOnly = cmd.Bool("pending_only") // Parse outgoing channel set. Don't string split if the flag is empty. // Otherwise, strings.Split returns a slice of length one with an empty // element. var outgoingChanSet []uint64 - if ctx.IsSet(channelFlag.Name) { - chanStrings := strings.Split(ctx.String(channelFlag.Name), ",") + if cmd.IsSet(channelFlag.Name) { + chanStrings := strings.Split(cmd.String(channelFlag.Name), ",") for _, chanString := range chanStrings { chanID, err := strconv.ParseUint(chanString, 10, 64) if err != nil { @@ -91,9 +91,9 @@ func listSwaps(ctx *cli.Context) error { // Parse last hop. var lastHop []byte - if ctx.IsSet(lastHopFlag.Name) { + if cmd.IsSet(lastHopFlag.Name) { lastHopVertex, err := route.NewVertexFromStr( - ctx.String(lastHopFlag.Name), + cmd.String(lastHopFlag.Name), ) if err != nil { return err @@ -104,23 +104,19 @@ func listSwaps(ctx *cli.Context) error { } // Parse label. - if ctx.IsSet(labelFlag.Name) { - filter.Label = ctx.String(labelFlag.Name) + if cmd.IsSet(labelFlag.Name) { + filter.Label = cmd.String(labelFlag.Name) } // Parse start timestamp if set. - if ctx.IsSet("start_time_ns") { - startTimestamp, err := strconv.ParseInt(ctx.String("start_time_ns"), 10, 64) - if err != nil { - return fmt.Errorf("error parsing start timestamp: %w", err) - } - filter.StartTimestampNs = startTimestamp + if cmd.IsSet("start_time_ns") { + filter.StartTimestampNs = cmd.Int64("start_time_ns") } resp, err := client.ListSwaps( - context.Background(), &looprpc.ListSwapsRequest{ + ctx, &looprpc.ListSwapsRequest{ ListSwapFilter: filter, - MaxSwaps: ctx.Uint64("max_swaps"), + MaxSwaps: cmd.Uint64("max_swaps"), }, ) if err != nil { @@ -131,14 +127,14 @@ func listSwaps(ctx *cli.Context) error { return nil } -var swapInfoCommand = cli.Command{ +var swapInfoCommand = &cli.Command{ Name: "swapinfo", Usage: "show the status of a swap", ArgsUsage: "id", Description: "Allows the user to get the status of a single swap " + "currently stored in the database", Flags: []cli.Flag{ - cli.Uint64Flag{ + &cli.Uint64Flag{ Name: "id", Usage: "the ID of the swap", }, @@ -146,19 +142,18 @@ var swapInfoCommand = cli.Command{ Action: swapInfo, } -func swapInfo(ctx *cli.Context) error { - args := ctx.Args() +func swapInfo(ctx context.Context, cmd *cli.Command) error { + args := cmd.Args() var id string switch { - case ctx.IsSet("id"): - id = ctx.String("id") - case ctx.NArg() > 0: - id = args[0] - args = args.Tail() // nolint:wastedassign + case cmd.IsSet("id"): + id = cmd.String("id") + case cmd.NArg() > 0: + id = args.First() default: // Show command help if no arguments and flags were provided. - return cli.ShowCommandHelp(ctx, "swapinfo") + return showCommandHelp(ctx, cmd) } if len(id) != hex.EncodedLen(lntypes.HashSize) { @@ -169,14 +164,14 @@ func swapInfo(ctx *cli.Context) error { return fmt.Errorf("cannot hex decode id: %v", err) } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() resp, err := client.SwapInfo( - context.Background(), &looprpc.SwapInfoRequest{Id: idBytes}, + ctx, &looprpc.SwapInfoRequest{Id: idBytes}, ) if err != nil { return err @@ -186,7 +181,7 @@ func swapInfo(ctx *cli.Context) error { return nil } -var abandonSwapCommand = cli.Command{ +var abandonSwapCommand = &cli.Command{ Name: "abandonswap", Usage: "abandon a swap with a given swap hash", Description: "This command overrides the database and abandons a " + @@ -197,7 +192,7 @@ var abandonSwapCommand = cli.Command{ "no funds are locked by the swap.", ArgsUsage: "ID", Flags: []cli.Flag{ - cli.BoolFlag{ + &cli.BoolFlag{ Name: "i_know_what_i_am_doing", Usage: "Specify this flag if you made sure that you " + "read and understood the following " + @@ -207,21 +202,20 @@ var abandonSwapCommand = cli.Command{ Action: abandonSwap, } -func abandonSwap(ctx *cli.Context) error { - args := ctx.Args() +func abandonSwap(ctx context.Context, cmd *cli.Command) error { + args := cmd.Args() var id string switch { - case ctx.IsSet("id"): - id = ctx.String("id") + case cmd.IsSet("id"): + id = cmd.String("id") - case ctx.NArg() > 0: - id = args[0] - args = args.Tail() // nolint:wastedassign + case cmd.NArg() > 0: + id = args.First() default: // Show command help if no arguments and flags were provided. - return cli.ShowCommandHelp(ctx, "abandonswap") + return showCommandHelp(ctx, cmd) } if len(id) != hex.EncodedLen(lntypes.HashSize) { @@ -232,20 +226,20 @@ func abandonSwap(ctx *cli.Context) error { return fmt.Errorf("cannot hex decode id: %v", err) } - client, cleanup, err := getClient(ctx) + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } defer cleanup() - if !ctx.Bool("i_know_what_i_am_doing") { - return cli.ShowCommandHelp(ctx, "abandonswap") + if !cmd.Bool("i_know_what_i_am_doing") { + return showCommandHelp(ctx, cmd) } resp, err := client.AbandonSwap( - context.Background(), &looprpc.AbandonSwapRequest{ + ctx, &looprpc.AbandonSwapRequest{ Id: idBytes, - IKnowWhatIAmDoing: ctx.Bool("i_know_what_i_am_doing"), + IKnowWhatIAmDoing: cmd.Bool("i_know_what_i_am_doing"), }, ) if err != nil { diff --git a/cmd/loop/terms.go b/cmd/loop/terms.go index 506408f8c..c7230ada9 100644 --- a/cmd/loop/terms.go +++ b/cmd/loop/terms.go @@ -6,17 +6,17 @@ import ( "github.com/btcsuite/btcd/btcutil" "github.com/lightninglabs/loop/looprpc" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) -var termsCommand = cli.Command{ +var termsCommand = &cli.Command{ Name: "terms", Usage: "Display the current swap terms imposed by the server.", Action: terms, } -func terms(ctx *cli.Context) error { - client, cleanup, err := getClient(ctx) +func terms(ctx context.Context, cmd *cli.Command) error { + client, cleanup, err := getClient(ctx, cmd) if err != nil { return err } @@ -31,7 +31,7 @@ func terms(ctx *cli.Context) error { fmt.Println("Loop Out") fmt.Println("--------") req := &looprpc.TermsRequest{} - loopOutTerms, err := client.LoopOutTerms(context.Background(), req) + loopOutTerms, err := client.LoopOutTerms(ctx, req) if err != nil { fmt.Println(err) } else { @@ -49,7 +49,7 @@ func terms(ctx *cli.Context) error { fmt.Println("Loop In") fmt.Println("------") loopInTerms, err := client.GetLoopInTerms( - context.Background(), &looprpc.TermsRequest{}, + ctx, &looprpc.TermsRequest{}, ) if err != nil { fmt.Println(err) diff --git a/cmd/loop/utils.go b/cmd/loop/utils.go index 87e89d5ef..07a6016a6 100644 --- a/cmd/loop/utils.go +++ b/cmd/loop/utils.go @@ -1,31 +1,51 @@ package main import ( + "context" "encoding/json" "fmt" "github.com/lightninglabs/loop/swapserverrpc" - "github.com/urfave/cli" + "github.com/urfave/cli/v3" ) +// showCommandHelp prints help for the current command by delegating to the +// parent command when available. This ensures help output renders even when +// invoked from inside a subcommand's action. +func showCommandHelp(ctx context.Context, cmd *cli.Command) error { + lineage := cmd.Lineage() + if len(lineage) > 1 { + parent := lineage[1] + return cli.ShowCommandHelp(ctx, parent, cmd.Name) + } + return cli.ShowCommandHelp(ctx, cmd, cmd.Name) +} + // validateRouteHints ensures that the Private flag isn't set along with // the RouteHints flag. We don't allow both options to be set as these options // are alternatives to each other. Private autogenerates hopHints while // RouteHints are manually passed. -func validateRouteHints(ctx *cli.Context) ([]*swapserverrpc.RouteHint, error) { +func validateRouteHints(cmd *cli.Command) ([]*swapserverrpc.RouteHint, error) { var hints []*swapserverrpc.RouteHint - if ctx.IsSet(routeHintsFlag.Name) { - if ctx.IsSet(privateFlag.Name) { + if cmd.IsSet(routeHintsFlag.Name) { + if cmd.IsSet(privateFlag.Name) { return nil, fmt.Errorf( "private and route_hints both set", ) } - stringHints := []byte(ctx.String(routeHintsFlag.Name)) - err := json.Unmarshal(stringHints, &hints) - if err != nil { - return nil, fmt.Errorf("unable to parse json: %v", err) + jsonHints := cmd.StringSlice(routeHintsFlag.Name) + + hints := make([]*swapserverrpc.RouteHint, len(jsonHints)) + for i, jsonHint := range jsonHints { + var h swapserverrpc.RouteHint + err := json.Unmarshal([]byte(jsonHint), &h) + if err != nil { + return nil, fmt.Errorf("unable to parse %d-th "+ + "hint json %v: %w", i, jsonHint, err) + } + hints[i] = &h } } diff --git a/docs/loop.1 b/docs/loop.1 new file mode 100644 index 000000000..3971105ab --- /dev/null +++ b/docs/loop.1 @@ -0,0 +1,566 @@ +.nh +.TH loop 1 + +.SH NAME +.PP +loop - control plane for your loopd + + +.SH SYNOPSIS +.PP +loop + +.PP +.RS + +.nf +[--help|-h] +[--loopdir]=[value] +[--macaroonpath]=[value] +[--network|-n]=[value] +[--rpcserver]=[value] +[--tlscertpath]=[value] +[--version|-v] + +.fi +.RE + +.PP +\fBUsage\fP: + +.PP +.RS + +.nf +loop [GLOBAL OPTIONS] [command [COMMAND OPTIONS]] [ARGUMENTS...] + +.fi +.RE + + +.SH GLOBAL OPTIONS +.PP +\fB--help, -h\fP: show help + +.PP +\fB--loopdir\fP="": path to loop's base directory (default: ~/.loop) + +.PP +\fB--macaroonpath\fP="": path to macaroon file (default: ~/.loop/mainnet/loop.macaroon) + +.PP +\fB--network, -n\fP="": the network loop is running on e.g. mainnet, testnet, etc. (default: mainnet) + +.PP +\fB--rpcserver\fP="": loopd daemon address host:port (default: localhost:11010) + +.PP +\fB--tlscertpath\fP="": path to loop's TLS certificate (default: ~/.loop/mainnet/tls.cert) + +.PP +\fB--version, -v\fP: print the version + + +.SH COMMANDS +.SH out +.PP +perform an off-chain to on-chain swap (looping out) + +.PP +\fB--account\fP="": the name of the account to generate a new address from. You can list the names of valid accounts in your backing lnd instance with "lncli wallet accounts list" + +.PP +\fB--account_addr_type\fP="": the address type of the extended public key specified in account. Currently only pay-to-taproot-pubkey(p2tr) is supported (default: p2tr) + +.PP +\fB--addr\fP="": the optional address that the looped out funds should be sent to, if let blank the funds will go to lnd's wallet + +.PP +\fB--amt\fP="": the amount in satoshis to loop out. To check for the minimum and maximum amounts to loop out please consult "loop terms" (default: 0) + +.PP +\fB--asset_edge_node\fP="": the pubkey of the edge node of the asset to loop out, this is required if the taproot assets daemon has multiple channels of the given asset id with different edge nodes + +.PP +\fB--asset_id\fP="": the asset ID of the asset to loop out, if this is set, the loop daemon will require a connection to a taproot assets daemon + +.PP +\fB--channel\fP="": the comma-separated list of short channel IDs of the channels to loop out + +.PP +\fB--conf_target\fP="": the number of blocks from the swap initiation height that the on-chain HTLC should be swept within (default: 9) + +.PP +\fB--fast\fP: 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 its chain fees. Not setting this flag therefore might result in a lower swap fee + +.PP +\fB--force\fP: Assumes yes during confirmation. Using this option will result in an immediate swap + +.PP +\fB--help, -h\fP: show help + +.PP +\fB--htlc_confs\fP="": the number of confirmations (in blocks) that we require for the htlc extended by the server before we reveal the preimage (default: 1) + +.PP +\fB--label\fP="": an optional label for this swap,limited to 500 characters. The label may not start with our reserved prefix: [reserved]. + +.PP +\fB--max_swap_routing_fee\fP="": the max off-chain swap routing fee in satoshis, if not specified, a default max fee will be used (default: 0) + +.PP +\fB--payment_timeout\fP="": the timeout for each individual off-chain payment attempt. If not set, the default timeout of 1 hour will be used. As the payment might be retried, the actual total time may be longer (default: 0s) + +.PP +\fB--verbose, -v\fP: show expanded details + +.SH in +.PP +perform an on-chain to off-chain swap (loop in) + +.PP +\fB--amt\fP="": the amount in satoshis to loop in. To check for the minimum and maximum amounts to loop in please consult "loop terms" (default: 0) + +.PP +\fB--conf_target\fP="": the target number of blocks the on-chain htlc broadcast by the swap client should confirm within (default: 0) + +.PP +\fB--external\fP: expect htlc to be published externally + +.PP +\fB--force\fP: Assumes yes during confirmation. Using this option will result in an immediate swap + +.PP +\fB--help, -h\fP: show help + +.PP +\fB--label\fP="": an optional label for this swap,limited to 500 characters. The label may not start with our reserved prefix: [reserved]. + +.PP +\fB--last_hop\fP="": the pubkey of the last hop to use for this swap + +.PP +\fB--private\fP: generates and passes routehints. Should be used if the connected node is only reachable via private channels + +.PP +\fB--route_hints\fP="": route hints that can each be individually used to assist in reaching the invoice's destination (default: []) + +.PP +\fB--verbose, -v\fP: show expanded details + +.SH terms +.PP +Display the current swap terms imposed by the server. + +.PP +\fB--help, -h\fP: show help + +.SH monitor +.PP +monitor progress of any active swaps + +.PP +\fB--help, -h\fP: show help + +.SH quote +.PP +get a quote for the cost of a swap + +.PP +\fB--help, -h\fP: show help + +.SS in +.PP +get a quote for the cost of a loop in swap + +.PP +\fB--conf_target\fP="": the target number of blocks the on-chain htlc broadcast by the swap client should confirm within (default: 0) + +.PP +\fB--deposit_outpoint\fP="": 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 (default: []) + +.PP +\fB--help, -h\fP: show help + +.PP +\fB--last_hop\fP="": the pubkey of the last hop to use for the quote + +.PP +\fB--private\fP: generates and passes routehints. Should be used if the connected node is only reachable via private channels + +.PP +\fB--route_hints\fP="": route hints that can each be individually used to assist in reaching the invoice's destination (default: []) + +.PP +\fB--verbose, -v\fP: show expanded details + +.SS out +.PP +get a quote for the cost of a loop out swap + +.PP +\fB--conf_target\fP="": the number of blocks from the swap initiation height that the on-chain HTLC should be swept within in a Loop Out (default: 9) + +.PP +\fB--fast\fP: 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. + +.PP +\fB--help, -h\fP: show help + +.PP +\fB--verbose, -v\fP: show expanded details + +.SH listauth +.PP +list all L402 tokens + +.PP +\fB--help, -h\fP: show help + +.SH fetchl402 +.PP +fetches a new L402 authentication token from the server + +.PP +\fB--help, -h\fP: show help + +.SH listswaps +.PP +list all swaps in the local database + +.PP +\fB--channel\fP="": the comma-separated list of short channel IDs of the channels to loop out + +.PP +\fB--help, -h\fP: show help + +.PP +\fB--label\fP="": an optional label for this swap,limited to 500 characters. The label may not start with our reserved prefix: [reserved]. + +.PP +\fB--last_hop\fP="": the pubkey of the last hop to use for this swap + +.PP +\fB--loop_in_only\fP: only list swaps that are loop in swaps + +.PP +\fB--loop_out_only\fP: only list swaps that are loop out swaps + +.PP +\fB--max_swaps\fP="": Max number of swaps to return after filtering (default: 0) + +.PP +\fB--pending_only\fP: only list pending swaps + +.PP +\fB--start_time_ns\fP="": Unix timestamp in nanoseconds to select swaps initiated after this time (default: 0) + +.SH swapinfo +.PP +show the status of a swap + +.PP +\fB--help, -h\fP: show help + +.PP +\fB--id\fP="": the ID of the swap (default: 0) + +.SH getparams +.PP +show liquidity manager parameters + +.PP +\fB--help, -h\fP: show help + +.SH setrule +.PP +set liquidity manager rule for a channel/peer + +.PP +\fB--clear\fP: remove the rule currently set for the channel/peer. + +.PP +\fB--help, -h\fP: show help + +.PP +\fB--incoming_threshold\fP="": the minimum percentage of incoming liquidity to total capacity beneath which to recommend loop out to acquire incoming. (default: 0) + +.PP +\fB--outgoing_threshold\fP="": the minimum percentage of outbound liquidity that we do not want to drop below. (default: 0) + +.PP +\fB--type\fP="": the type of swap to perform, set to 'out' for acquiring inbound liquidity or 'in' for acquiring outbound liquidity. (default: out) + +.SH suggestswaps +.PP +show a list of suggested swaps + +.PP +\fB--help, -h\fP: show help + +.SH setparams +.PP +update the parameters set for the liquidity manager + +.PP +\fB--account\fP="": the name of the account to generate a new address from. You can list the names of valid accounts in your backing lnd instance with "lncli wallet accounts list". + +.PP +\fB--account_addr_type\fP="": the address type of the extended public key specified in account. Currently only pay-to-taproot-pubkey(p2tr) is supported (default: p2tr) + +.PP +\fB--asset_easyautoloop\fP: set to true to enable asset easy autoloop, which will automatically dispatch asset swaps in order to meet the target local balance. + +.PP +\fB--asset_id\fP="": If set to a valid asset ID, the easyautoloop and localbalancesat flags will be set for the specified asset. + +.PP +\fB--asset_localbalance\fP="": the target size of total local balance in asset units, used by asset easy autoloop. (default: 0) + +.PP +\fB--autobudget\fP="": the maximum amount of fees in satoshis that automatically dispatched loop out swaps may spend. (default: 0) + +.PP +\fB--autobudgetrefreshperiod\fP="": the time period over which the automated loop budget is refreshed. (default: 0s) + +.PP +\fB--autoinflight\fP="": the maximum number of automatically dispatched swaps that we allow to be in flight. (default: 0) + +.PP +\fB--autoloop\fP: set to true to enable automated dispatch of swaps, limited to the budget set by autobudget. + +.PP +\fB--destaddr\fP="": custom address to be used as destination for autoloop loop out, set to "default" in order to revert to default behavior. + +.PP +\fB--easyautoloop\fP: set to true to enable easy autoloop, which will automatically dispatch swaps in order to meet the target local balance. + +.PP +\fB--failurebackoff\fP="": the amount of time, in seconds, that should pass before a channel that previously had a failed swap will be included in suggestions. (default: 0) + +.PP +\fB--fast\fP: if set new swaps are expected to be published immediately, paying a potentially higher fee. If not set the swap server might choose to wait up to 30 minutes before publishing swap HTLCs on-chain, to save on chain fees. Not setting this flag therefore might result in a lower swap fees + +.PP +\fB--feepercent\fP="": the maximum percentage of swap amount to be used across all fee categories (default: 0) + +.PP +\fB--help, -h\fP: show help + +.PP +\fB--htlc_conf\fP="": the confirmation target for loop in on-chain htlcs. (default: 0) + +.PP +\fB--localbalancesat\fP="": the target size of total local balance in satoshis, used by easy autoloop. (default: 0) + +.PP +\fB--maxamt\fP="": the maximum amount in satoshis that the autoloop client will dispatch per-swap. (default: 0) + +.PP +\fB--maxminer\fP="": the maximum miner fee in satoshis that swap suggestions should be limited to. (default: 0) + +.PP +\fB--maxprepay\fP="": the maximum no-show (prepay) in satoshis that swap suggestions should be limited to. (default: 0) + +.PP +\fB--maxprepayfee\fP="": the maximum percentage of off-chain prepay volume that we are willing to pay in routing fees. (default: 0) + +.PP +\fB--maxroutingfee\fP="": the maximum percentage of off-chain payment volume that we are willing to pay in routingfees. (default: 0) + +.PP +\fB--maxswapfee\fP="": the maximum percentage of swap volume we are willing to pay in server fees. (default: 0) + +.PP +\fB--minamt\fP="": the minimum amount in satoshis that the autoloop client will dispatch per-swap. (default: 0) + +.PP +\fB--sweepconf\fP="": the number of blocks from htlc height that swap suggestion sweeps should target, used to estimate max miner fee. (default: 0) + +.PP +\fB--sweeplimit\fP="": the limit placed on our estimated sweep fee in sat/vByte. (default: 0) + +.SH getinfo +.PP +show general information about the loop daemon + +.PP +\fB--help, -h\fP: show help + +.SH abandonswap +.PP +abandon a swap with a given swap hash + +.PP +\fB--help, -h\fP: show help + +.PP +\fB--i_know_what_i_am_doing\fP: Specify this flag if you made sure that you read and understood the following consequence of applying this command. + +.SH reservations, r +.PP +manage reservations + +.PP +\fB--help, -h\fP: show help + +.SS list, l +.PP +list all reservations + +.PP +\fB--help, -h\fP: show help + +.SH instantout +.PP +perform an instant off-chain to on-chain swap (looping out) + +.PP +\fB--addr\fP="": the optional address that the looped out funds should be sent to, if let blank the funds will go to lnd's wallet + +.PP +\fB--channel\fP="": the comma-separated list of short channel IDs of the channels to loop out + +.PP +\fB--help, -h\fP: show help + +.SH listinstantouts +.PP +list all instant out swaps + +.PP +\fB--help, -h\fP: show help + +.SH static, s +.PP +perform on-chain to off-chain swaps using static addresses. + +.PP +\fB--help, -h\fP: show help + +.SS new, n +.PP +Create a new static loop in address. + +.PP +\fB--help, -h\fP: show help + +.SS listunspent, l +.PP +List unspent static address outputs. + +.PP +\fB--help, -h\fP: show help + +.PP +\fB--max_confs\fP="": The maximum number of confirmations an output could have to be listed. (default: 0) + +.PP +\fB--min_confs\fP="": The minimum amount of confirmations an output should have to be listed. (default: 0) + +.SS listdeposits +.PP +Displays static address deposits. A filter can be applied to only show deposits in a specific state. + +.PP +\fB--filter\fP="": specify a filter to only display deposits in the specified state. Leaving out the filter returns all deposits. +The state can be one of the following: +deposited +withdrawing +withdrawn +looping_in +looped_in +publish_expired_deposit +sweep_htlc_timeout +htlc_timeout_swept +wait_for_expiry_sweep +expired +failed +. + +.PP +\fB--help, -h\fP: show help + +.SS listwithdrawals +.PP +Display a summary of past withdrawals. + +.PP +\fB--help, -h\fP: show help + +.SS listswaps +.PP +Shows a list of finalized static address swaps. + +.PP +\fB--help, -h\fP: show help + +.SS withdraw, w +.PP +Withdraw from static address deposits. + +.PP +\fB--all\fP: withdraws all static address deposits. + +.PP +\fB--amount\fP="": the number of satoshis that should be withdrawn from the selected deposits. The change is sent back to the static address (default: 0) + +.PP +\fB--dest_addr\fP="": the optional address that the withdrawn funds should be sent to, if let blank the funds will go to lnd's wallet + +.PP +\fB--help, -h\fP: show help + +.PP +\fB--sat_per_vbyte\fP="": (optional) a manual fee expressed in sat/vbyte that should be used when crafting the transaction (default: 0) + +.PP +\fB--utxo\fP="": specify utxos as outpoints(tx:idx) which willbe withdrawn. (default: []) + +.SS summary, s +.PP +Display a summary of static address related information. + +.PP +\fB--help, -h\fP: show help + +.SS in +.PP +Loop in funds from static address deposits. + +.PP +\fB--all\fP: loop in all static address deposits. + +.PP +\fB--amount\fP="": 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) + +.PP +\fB--fast\fP: Usage: complete the swap faster by paying a higher fee, so the change output is available sooner + +.PP +\fB--force\fP: Assumes yes during confirmation. Using this option will result in an immediate swap + +.PP +\fB--help, -h\fP: show help + +.PP +\fB--label\fP="": an optional label for this swap,limited to 500 characters. The label may not start with our reserved prefix: [reserved]. + +.PP +\fB--last_hop\fP="": the pubkey of the last hop to use for this swap + +.PP +\fB--payment_timeout\fP="": 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) + +.PP +\fB--private\fP: generates and passes routehints. Should be used if the connected node is only reachable via private channels + +.PP +\fB--route_hints\fP="": route hints that can each be individually used to assist in reaching the invoice's destination (default: []) + +.PP +\fB--utxo\fP="": specify the utxos of deposits as outpoints(tx:idx) that should be looped in. (default: []) + +.PP +\fB--verbose, -v\fP: show expanded details + diff --git a/docs/loop.md b/docs/loop.md new file mode 100644 index 000000000..8f3461e81 --- /dev/null +++ b/docs/loop.md @@ -0,0 +1,643 @@ +## CLI interface - loop + +control plane for your loopd. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] [COMMAND] [COMMAND FLAGS] [ARGUMENTS...] +``` + +Global flags: + +| Name | Description | Type | Default value | Environment variables | +|------------------------|-----------------------------------------------------------|--------|:-------------------------------:|:----------------------:| +| `--rpcserver="…"` | loopd daemon address host:port | string | `localhost:11010` | `LOOPCLI_RPCSERVER` | +| `--network="…"` (`-n`) | the network loop is running on e.g. mainnet, testnet, etc | string | `mainnet` | `LOOPCLI_NETWORK` | +| `--loopdir="…"` | path to loop's base directory | string | `~/.loop` | `LOOPCLI_LOOPDIR` | +| `--tlscertpath="…"` | path to loop's TLS certificate | string | `~/.loop/mainnet/tls.cert` | `LOOPCLI_TLSCERTPATH` | +| `--macaroonpath="…"` | path to macaroon file | string | `~/.loop/mainnet/loop.macaroon` | `LOOPCLI_MACAROONPATH` | +| `--help` (`-h`) | show help | bool | `false` | *none* | +| `--version` (`-v`) | print the version | bool | `false` | *none* | + +### `out` command + +perform an off-chain to on-chain swap (looping out). + +Attempts to loop out the target amount into either the backing lnd's wallet, or a targeted address. The amount is to be specified in satoshis. Optionally a BASE58/bech32 encoded bitcoin destination address may be specified. If not specified, a new wallet address will be generated. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] out [COMMAND FLAGS] amt [addr] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|:-------------:| +| `--addr="…"` | the optional address that the looped out funds should be sent to, if let blank the funds will go to lnd's wallet | string | +| `--account="…"` | the name of the account to generate a new address from. You can list the names of valid accounts in your backing lnd instance with "lncli wallet accounts list" | string | +| `--account_addr_type="…"` | the address type of the extended public key specified in account. Currently only pay-to-taproot-pubkey(p2tr) is supported | string | `p2tr` | +| `--amt="…"` | the amount in satoshis to loop out. To check for the minimum and maximum amounts to loop out please consult "loop terms" | uint | `0` | +| `--htlc_confs="…"` | the number of confirmations (in blocks) that we require for the htlc extended by the server before we reveal the preimage | uint | `1` | +| `--conf_target="…"` | the number of blocks from the swap initiation height that the on-chain HTLC should be swept within | uint | `9` | +| `--max_swap_routing_fee="…"` | the max off-chain swap routing fee in satoshis, if not specified, a default max fee will be used | int | `0` | +| `--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 its chain fees. Not setting this flag therefore might result in a lower swap fee | bool | `false` | +| `--payment_timeout="…"` | the timeout for each individual off-chain payment attempt. If not set, the default timeout of 1 hour will be used. As the payment might be retried, the actual total time may be longer | duration | `0s` | +| `--asset_id="…"` | the asset ID of the asset to loop out, if this is set, the loop daemon will require a connection to a taproot assets daemon | string | +| `--asset_edge_node="…"` | the pubkey of the edge node of the asset to loop out, this is required if the taproot assets daemon has multiple channels of the given asset id with different edge nodes | string | +| `--force` | Assumes yes during confirmation. Using this option will result in an immediate swap | bool | `false` | +| `--label="…"` | an optional label for this swap,limited to 500 characters. The label may not start with our reserved prefix: [reserved] | string | +| `--verbose` (`-v`) | show expanded details | bool | `false` | +| `--channel="…"` | the comma-separated list of short channel IDs of the channels to loop out | string | +| `--help` (`-h`) | show help | bool | `false` | + +### `in` command + +perform an on-chain to off-chain swap (loop in). + +Send the amount in satoshis specified by the amt argument off-chain. By default the swap client will create and broadcast the on-chain htlc. The fee priority of this transaction can optionally be set using the conf_target flag. The external flag can be set to publish the on chain htlc independently. Note that this flag cannot be set with the conf_target flag. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] in [COMMAND FLAGS] amt +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|---------------------|-------------------------------------------------------------------------------------------------------------------------|--------|:-------------:| +| `--amt="…"` | the amount in satoshis to loop in. To check for the minimum and maximum amounts to loop in please consult "loop terms" | uint | `0` | +| `--external` | expect htlc to be published externally | bool | `false` | +| `--conf_target="…"` | the target number of blocks the on-chain htlc broadcast by the swap client should confirm within | uint | `0` | +| `--last_hop="…"` | the pubkey of the last hop to use for this swap | string | +| `--label="…"` | an optional label for this swap,limited to 500 characters. The label may not start with our reserved prefix: [reserved] | string | +| `--force` | Assumes yes during confirmation. Using this option will result in an immediate swap | bool | `false` | +| `--verbose` (`-v`) | show expanded details | bool | `false` | +| `--route_hints="…"` | route hints that can each be individually used to assist in reaching the invoice's destination | string | `[]` | +| `--private` | generates and passes routehints. Should be used if the connected node is only reachable via private channels | bool | `false` | +| `--help` (`-h`) | show help | bool | `false` | + +### `terms` command + +Display the current swap terms imposed by the server. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] terms [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `monitor` command + +monitor progress of any active swaps. + +Allows the user to monitor progress of any active swaps. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] monitor [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `quote` command + +get a quote for the cost of a swap. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] quote [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `quote in` subcommand + +get a quote for the cost of a loop in swap. + +Allows to determine the cost of a swap up front.Either specify an amount or deposit outpoints. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] quote in [COMMAND FLAGS] amt +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|:-------------:| +| `--last_hop="…"` | the pubkey of the last hop to use for the quote | string | +| `--conf_target="…"` | the target number of blocks the on-chain htlc broadcast by the swap client should confirm within | uint | `0` | +| `--verbose` (`-v`) | show expanded details | bool | `false` | +| `--private` | generates and passes routehints. Should be used if the connected node is only reachable via private channels | bool | `false` | +| `--route_hints="…"` | route hints that can each be individually used to assist in reaching the invoice's destination | string | `[]` | +| `--deposit_outpoint="…"` | 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 | string | `[]` | +| `--help` (`-h`) | show help | bool | `false` | + +### `quote out` subcommand + +get a quote for the cost of a loop out swap. + +Allows to determine the cost of a swap up front. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] quote out [COMMAND FLAGS] amt +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------|:-------------:| +| `--conf_target="…"` | the number of blocks from the swap initiation height that the on-chain HTLC should be swept within in a Loop Out | uint | `9` | +| `--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 | bool | `false` | +| `--verbose` (`-v`) | show expanded details | bool | `false` | +| `--help` (`-h`) | show help | bool | `false` | + +### `listauth` command + +list all L402 tokens. + +Shows a list of all L402 tokens that loopd has paid for. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] listauth [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `fetchl402` command + +fetches a new L402 authentication token from the server. + +Fetches a new L402 authentication token from the server. This token is required to listen to notifications from the server, such as reservation notifications. If a L402 is already present in the store, this command is a no-op. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] fetchl402 [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `listswaps` command + +list all swaps in the local database. + +Allows the user to get a list of all swaps that are currently stored in the database. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] listswaps [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------------|-------------------------------------------------------------------------------------------------------------------------|--------|:-------------:| +| `--loop_out_only` | only list swaps that are loop out swaps | bool | `false` | +| `--loop_in_only` | only list swaps that are loop in swaps | bool | `false` | +| `--pending_only` | only list pending swaps | bool | `false` | +| `--label="…"` | an optional label for this swap,limited to 500 characters. The label may not start with our reserved prefix: [reserved] | string | +| `--channel="…"` | the comma-separated list of short channel IDs of the channels to loop out | string | +| `--last_hop="…"` | the pubkey of the last hop to use for this swap | string | +| `--max_swaps="…"` | Max number of swaps to return after filtering | uint | `0` | +| `--start_time_ns="…"` | Unix timestamp in nanoseconds to select swaps initiated after this time | int | `0` | +| `--help` (`-h`) | show help | bool | `false` | + +### `swapinfo` command + +show the status of a swap. + +Allows the user to get the status of a single swap currently stored in the database. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] swapinfo [COMMAND FLAGS] id +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|--------------------|------|:-------------:| +| `--id="…"` | the ID of the swap | uint | `0` | +| `--help` (`-h`) | show help | bool | `false` | + +### `getparams` command + +show liquidity manager parameters. + +Displays the current set of parameters that are set for the liquidity manager. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] getparams [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `setrule` command + +set liquidity manager rule for a channel/peer. + +Update or remove the liquidity rule for a channel/peer. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] setrule [COMMAND FLAGS] {shortchanid | peerpubkey} +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|----------------------------|------------------------------------------------------------------------------------------------------------------------|--------|:-------------:| +| `--type="…"` | the type of swap to perform, set to 'out' for acquiring inbound liquidity or 'in' for acquiring outbound liquidity | string | `out` | +| `--incoming_threshold="…"` | the minimum percentage of incoming liquidity to total capacity beneath which to recommend loop out to acquire incoming | int | `0` | +| `--outgoing_threshold="…"` | the minimum percentage of outbound liquidity that we do not want to drop below | int | `0` | +| `--clear` | remove the rule currently set for the channel/peer | bool | `false` | +| `--help` (`-h`) | show help | bool | `false` | + +### `suggestswaps` command + +show a list of suggested swaps. + +Displays a list of suggested swaps that aim to obtain the liquidity balance as specified by the rules set in the liquidity manager. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] suggestswaps [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `setparams` command + +update the parameters set for the liquidity manager. + +Updates the parameters set for the liquidity manager. Note the parameters are persisted in db to save the trouble of setting them again upon loopd restart. To get the defaultvalues, use `getparams` before any `setparams`. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] setparams [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|---------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|:-------------:| +| `--sweeplimit="…"` | the limit placed on our estimated sweep fee in sat/vByte | int | `0` | +| `--feepercent="…"` | the maximum percentage of swap amount to be used across all fee categories | float | `0` | +| `--maxswapfee="…"` | the maximum percentage of swap volume we are willing to pay in server fees | float | `0` | +| `--maxroutingfee="…"` | the maximum percentage of off-chain payment volume that we are willing to pay in routingfees | float | `0` | +| `--maxprepayfee="…"` | the maximum percentage of off-chain prepay volume that we are willing to pay in routing fees | float | `0` | +| `--maxprepay="…"` | the maximum no-show (prepay) in satoshis that swap suggestions should be limited to | uint | `0` | +| `--maxminer="…"` | the maximum miner fee in satoshis that swap suggestions should be limited to | uint | `0` | +| `--sweepconf="…"` | the number of blocks from htlc height that swap suggestion sweeps should target, used to estimate max miner fee | int | `0` | +| `--failurebackoff="…"` | the amount of time, in seconds, that should pass before a channel that previously had a failed swap will be included in suggestions | uint | `0` | +| `--autoloop` | set to true to enable automated dispatch of swaps, limited to the budget set by autobudget | bool | `false` | +| `--destaddr="…"` | custom address to be used as destination for autoloop loop out, set to "default" in order to revert to default behavior | string | +| `--account="…"` | the name of the account to generate a new address from. You can list the names of valid accounts in your backing lnd instance with "lncli wallet accounts list" | string | +| `--account_addr_type="…"` | the address type of the extended public key specified in account. Currently only pay-to-taproot-pubkey(p2tr) is supported | string | `p2tr` | +| `--autobudget="…"` | the maximum amount of fees in satoshis that automatically dispatched loop out swaps may spend | uint | `0` | +| `--autobudgetrefreshperiod="…"` | the time period over which the automated loop budget is refreshed | duration | `0s` | +| `--autoinflight="…"` | the maximum number of automatically dispatched swaps that we allow to be in flight | uint | `0` | +| `--minamt="…"` | the minimum amount in satoshis that the autoloop client will dispatch per-swap | uint | `0` | +| `--maxamt="…"` | the maximum amount in satoshis that the autoloop client will dispatch per-swap | uint | `0` | +| `--htlc_conf="…"` | the confirmation target for loop in on-chain htlcs | int | `0` | +| `--easyautoloop` | set to true to enable easy autoloop, which will automatically dispatch swaps in order to meet the target local balance | bool | `false` | +| `--localbalancesat="…"` | the target size of total local balance in satoshis, used by easy autoloop | uint | `0` | +| `--asset_easyautoloop` | set to true to enable asset easy autoloop, which will automatically dispatch asset swaps in order to meet the target local balance | bool | `false` | +| `--asset_id="…"` | If set to a valid asset ID, the easyautoloop and localbalancesat flags will be set for the specified asset | string | +| `--asset_localbalance="…"` | the target size of total local balance in asset units, used by asset easy autoloop | uint | `0` | +| `--fast` | if set new swaps are expected to be published immediately, paying a potentially higher fee. If not set the swap server might choose to wait up to 30 minutes before publishing swap HTLCs on-chain, to save on chain fees. Not setting this flag therefore might result in a lower swap fees | bool | `false` | +| `--help` (`-h`) | show help | bool | `false` | + +### `getinfo` command + +show general information about the loop daemon. + +Displays general information about the daemon like current version, connection parameters and basic swap information. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] getinfo [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `abandonswap` command + +abandon a swap with a given swap hash. + +This command overrides the database and abandons a swap with a given swap hash. !!! 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. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] abandonswap [COMMAND FLAGS] ID +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|----------------------------|--------------------------------------------------------------------------------------------------------------------|------|:-------------:| +| `--i_know_what_i_am_doing` | Specify this flag if you made sure that you read and understood the following consequence of applying this command | bool | `false` | +| `--help` (`-h`) | show help | bool | `false` | + +### `reservations` command (aliases: `r`) + +manage reservations. + +With loopd running, you can use this command to manage your reservations. Reservations are 2-of-2 multisig utxos that the loop server can open to clients. The reservations are used to enable instant swaps. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] reservations [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `reservations list` subcommand (aliases: `l`) + +list all reservations. + +List all reservations. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] reservations list [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `instantout` command + +perform an instant off-chain to on-chain swap (looping out). + +Attempts to instantly loop out into the backing lnd's wallet. The amount will be chosen via the cli. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] instantout [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|------------------------------------------------------------------------------------------------------------------|--------|:-------------:| +| `--channel="…"` | the comma-separated list of short channel IDs of the channels to loop out | string | +| `--addr="…"` | the optional address that the looped out funds should be sent to, if let blank the funds will go to lnd's wallet | string | +| `--help` (`-h`) | show help | bool | `false` | + +### `listinstantouts` command + +list all instant out swaps. + +List all instant out swaps. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] listinstantouts [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `static` command (aliases: `s`) + +perform on-chain to off-chain swaps using static addresses. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] static [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `static new` subcommand (aliases: `n`) + +Create a new static loop in address. + +Requests a new static loop in address from the server. Funds that are sent to this address will be locked by a 2:2 multisig between us and the loop server, or a timeout path that we can sweep once it opens up. The funds can either be cooperatively spent with a signature from the server or looped in. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] static new [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `static listunspent` subcommand (aliases: `l`) + +List unspent static address outputs. + +List all unspent static address outputs. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] static listunspent [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-------------------|------------------------------------------------------------------------|------|:-------------:| +| `--min_confs="…"` | The minimum amount of confirmations an output should have to be listed | int | `0` | +| `--max_confs="…"` | The maximum number of confirmations an output could have to be listed | int | `0` | +| `--help` (`-h`) | show help | bool | `false` | + +### `static listdeposits` subcommand + +Displays static address deposits. A filter can be applied to only show deposits in a specific state. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] static listdeposits [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|:-------------:| +| `--filter="…"` | specify a filter to only display deposits in the specified state. Leaving out the filter returns all deposits. The state can be one of the following: deposited withdrawing withdrawn looping_in looped_in publish_expired_deposit sweep_htlc_timeout htlc_timeout_swept wait_for_expiry_sweep expired failed | string | +| `--help` (`-h`) | show help | bool | `false` | + +### `static listwithdrawals` subcommand + +Display a summary of past withdrawals. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] static listwithdrawals [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `static listswaps` subcommand + +Shows a list of finalized static address swaps. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] static listswaps [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `static withdraw` subcommand (aliases: `w`) + +Withdraw from static address deposits. + +Withdraws from all or selected static address deposits by sweeping them to the internal wallet or an external address. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] static withdraw [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------------|---------------------------------------------------------------------------------------------------------------------------|--------|:-------------:| +| `--utxo="…"` | specify utxos as outpoints(tx:idx) which willbe withdrawn | string | `[]` | +| `--all` | withdraws all static address deposits | bool | `false` | +| `--dest_addr="…"` | the optional address that the withdrawn funds should be sent to, if let blank the funds will go to lnd's wallet | string | +| `--sat_per_vbyte="…"` | (optional) a manual fee expressed in sat/vbyte that should be used when crafting the transaction | int | `0` | +| `--amount="…"` | the number of satoshis that should be withdrawn from the selected deposits. The change is sent back to the static address | int | `0` | +| `--help` (`-h`) | show help | bool | `false` | + +### `static summary` subcommand (aliases: `s`) + +Display a summary of static address related information. + +Displays various static address related information about deposits, withdrawals and swaps. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] static summary [COMMAND FLAGS] [ARGUMENTS...] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-----------------|-------------|------|:-------------:| +| `--help` (`-h`) | show help | bool | `false` | + +### `static in` subcommand + +Loop in funds from static address deposits. + +Requests a loop-in swap based on static address deposits. After the creation of a static address funds can be sent to it. Once the funds are confirmed on-chain they can be swapped instantaneously. If deposited funds are not needed they can we withdrawn back to the local lnd wallet. + +Usage: + +```bash +$ loop [GLOBAL FLAGS] static in [COMMAND FLAGS] [amt] [--all | --utxo xxx:xx] +``` + +The following flags are supported: + +| Name | Description | Type | Default value | +|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|:-------------:| +| `--utxo="…"` | specify the utxos of deposits as outpoints(tx:idx) that should be looped in | string | `[]` | +| `--all` | loop in all static address deposits | bool | `false` | +| `--payment_timeout="…"` | 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 | duration | `0s` | +| `--amount="…"` | the number of satoshis that should be swapped from the selected deposits. If thereis change it is sent back to the static address | uint | `0` | +| `--fast` | Usage: complete the swap faster by paying a higher fee, so the change output is available sooner | bool | `false` | +| `--last_hop="…"` | the pubkey of the last hop to use for this swap | string | +| `--label="…"` | an optional label for this swap,limited to 500 characters. The label may not start with our reserved prefix: [reserved] | string | +| `--route_hints="…"` | route hints that can each be individually used to assist in reaching the invoice's destination | string | `[]` | +| `--private` | generates and passes routehints. Should be used if the connected node is only reachable via private channels | bool | `false` | +| `--force` | Assumes yes during confirmation. Using this option will result in an immediate swap | bool | `false` | +| `--verbose` (`-v`) | show expanded details | bool | `false` | +| `--help` (`-h`) | show help | bool | `false` | + diff --git a/go.mod b/go.mod index 3b4768b9a..a247e8ba9 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,8 @@ require ( github.com/lightningnetwork/lnd/tor v1.1.6 github.com/ory/dockertest/v3 v3.10.0 github.com/stretchr/testify v1.10.0 - github.com/urfave/cli v1.22.14 + github.com/urfave/cli-docs/v3 v3.1.0 + github.com/urfave/cli/v3 v3.4.1 go.etcd.io/bbolt v1.3.11 golang.org/x/sync v0.12.0 google.golang.org/grpc v1.64.1 diff --git a/go.sum b/go.sum index 3781a9c6a..5c95d9cb4 100644 --- a/go.sum +++ b/go.sum @@ -600,7 +600,6 @@ git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3p github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -1306,7 +1305,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= @@ -1315,8 +1313,10 @@ github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4 github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02 h1:tcJ6OjwOMvExLlzrAVZute09ocAGa7KqOON60++Gz4E= github.com/tv42/zbase32 v0.0.0-20160707012821-501572607d02/go.mod h1:tHlrkM198S068ZqfrO6S8HsoJq2bF3ETfTL+kt4tInY= -github.com/urfave/cli v1.22.14 h1:ebbhrRiGK2i4naQJr+1Xj92HXZCrK7MsyTS/ob3HnAk= -github.com/urfave/cli v1.22.14/go.mod h1:X0eDS6pD6Exaclxm99NJ3FiCDRED7vIHpx2mDOHLvkA= +github.com/urfave/cli-docs/v3 v3.1.0 h1:Sa5xm19IpE5gpm6tZzXdfjdFxn67PnEsE4dpXF7vsKw= +github.com/urfave/cli-docs/v3 v3.1.0/go.mod h1:59d+5Hz1h6GSGJ10cvcEkbIe3j233t4XDqI72UIx7to= +github.com/urfave/cli/v3 v3.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM= +github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=