diff --git a/.claude/skills/waved.md b/.claude/skills/waved.md index 776cc8f50..431b3d33f 100644 --- a/.claude/skills/waved.md +++ b/.claude/skills/waved.md @@ -128,7 +128,10 @@ surfaces the raw waverpc methods underlying them. # Raw VTXO inventory + lifecycle wavecli ark vtxos list --no-tls wavecli ark vtxos list --status live --min_amount 10000 --no-tls -wavecli ark vtxos refresh --all --no-tls +# A real refresh is fee-gated: preview with --dry_run, consent with +# --yes (required on non-interactive stdin). +wavecli ark vtxos refresh --all --dry_run --no-tls +wavecli ark vtxos refresh --all --yes --no-tls # Raw transaction history (the wallet-shaped feed is `activity`) wavecli ark listtransactions --no-tls diff --git a/CONTEXT.md b/CONTEXT.md index 6778e04f9..6e3280482 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -103,7 +103,7 @@ wavecli ark vtxos list --ndjson | jq '.amount_sat' # Refresh — see BUGS_FOUND.md bug-1/bug-2; the refresh path is # currently not landing on the operator. wavecli ark vtxos refresh --outpoint txid:0 --dry_run -wavecli ark vtxos refresh --outpoint txid:0 +wavecli ark vtxos refresh --outpoint txid:0 --yes ``` ### Send operations diff --git a/cmd/wavecli/waveclicommands/AGENTS.md b/cmd/wavecli/waveclicommands/AGENTS.md index de16e0581..2ee80fe6e 100644 --- a/cmd/wavecli/waveclicommands/AGENTS.md +++ b/cmd/wavecli/waveclicommands/AGENTS.md @@ -152,6 +152,16 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/cmd/wave - `recovery escalate` refuses to run on non-interactive stdin unless `--yes` is passed — it never blocks on a y/N prompt an agent can't answer. +- `ark vtxos refresh` is gated on fee consent: a real refresh fetches + the dry-run estimate and prompts with it on a TTY, and refuses on + non-interactive stdin without `--yes` (same posture as `leave --all` + and `recovery escalate`). The MCP tool enforces the same contract + through its `yes` argument — no prompt exists there, so a bare real + refresh returns an immediate actionable error. `--dry_run` previews + the itemized advisory estimate and never prompts. A failed estimate + degrades to a "still charged the seal-time fee" warning and its + total is absent on the wire (explicit proto presence) — it never + blocks the flow and is never rendered as a zero fee. ## Deep Docs diff --git a/cmd/wavecli/waveclicommands/CLAUDE.md b/cmd/wavecli/waveclicommands/CLAUDE.md index de16e0581..2ee80fe6e 100644 --- a/cmd/wavecli/waveclicommands/CLAUDE.md +++ b/cmd/wavecli/waveclicommands/CLAUDE.md @@ -152,6 +152,16 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/cmd/wave - `recovery escalate` refuses to run on non-interactive stdin unless `--yes` is passed — it never blocks on a y/N prompt an agent can't answer. +- `ark vtxos refresh` is gated on fee consent: a real refresh fetches + the dry-run estimate and prompts with it on a TTY, and refuses on + non-interactive stdin without `--yes` (same posture as `leave --all` + and `recovery escalate`). The MCP tool enforces the same contract + through its `yes` argument — no prompt exists there, so a bare real + refresh returns an immediate actionable error. `--dry_run` previews + the itemized advisory estimate and never prompts. A failed estimate + degrades to a "still charged the seal-time fee" warning and its + total is absent on the wire (explicit proto presence) — it never + blocks the flow and is never rendered as a zero fee. ## Deep Docs diff --git a/cmd/wavecli/waveclicommands/client.go b/cmd/wavecli/waveclicommands/client.go index 6eb7d28f9..e60f7ea12 100644 --- a/cmd/wavecli/waveclicommands/client.go +++ b/cmd/wavecli/waveclicommands/client.go @@ -97,8 +97,15 @@ func getDaemonConn(cmd *cobra.Command) (*grpc.ClientConn, error) { // getDaemonClient establishes a gRPC connection to the daemon and returns a // DaemonServiceClient. The caller is responsible for closing the returned -// connection. -func getDaemonClient(cmd *cobra.Command) (waverpc.DaemonServiceClient, +// connection. Package-level indirection over defaultGetDaemonClient so +// command-wiring tests can substitute an in-process bufconn daemon +// (mirroring the stdinIsTTY indirection); production code never +// overrides it. +var getDaemonClient = defaultGetDaemonClient + +// defaultGetDaemonClient dials the daemon from the command's connection +// flags. +func defaultGetDaemonClient(cmd *cobra.Command) (waverpc.DaemonServiceClient, *grpc.ClientConn, error) { conn, err := getDaemonConn(cmd) diff --git a/cmd/wavecli/waveclicommands/cmd_fees.go b/cmd/wavecli/waveclicommands/cmd_fees.go index d533688e7..6df026f07 100644 --- a/cmd/wavecli/waveclicommands/cmd_fees.go +++ b/cmd/wavecli/waveclicommands/cmd_fees.go @@ -39,7 +39,13 @@ func newFeesEstimateCmd() *cobra.Command { "seal time and may differ from this estimate. " + "Shows liquidity fee, on-chain share, margin, " + "total fee, effective rate, and minimum " + - "viable VTXO.", + "viable VTXO.\n\n" + + "To preview the fee for refreshing specific " + + "VTXOs without looking up their amounts and " + + "remaining lifetimes by hand, use `ark vtxos " + + "refresh --dry_run` instead: it resolves each " + + "selected VTXO and returns a per-outpoint " + + "estimate.", RunE: feesEstimate, } diff --git a/cmd/wavecli/waveclicommands/cmd_mcp.go b/cmd/wavecli/waveclicommands/cmd_mcp.go index 0a2f93cb1..c5aeb02ef 100644 --- a/cmd/wavecli/waveclicommands/cmd_mcp.go +++ b/cmd/wavecli/waveclicommands/cmd_mcp.go @@ -91,6 +91,23 @@ func buildMCPServer(client waverpc.DaemonServiceClient, return server } +// checkMCPRefreshConsent enforces the refresh fee-consent contract on +// the MCP surface: a real (non-dry-run) refresh requires the explicit +// yes acknowledgement, because every refresh is charged an operator +// fee at seal time and MCP has no interactive prompt to fall back to. +// The returned error is immediate and actionable — an agent is never +// blocked, matching the CLI's non-interactive refusal. +func checkMCPRefreshConsent(dryRun, yes bool) error { + if dryRun || yes { + return nil + } + + return fmt.Errorf("a refresh is charged an operator fee at seal " + + "time; call with dry_run:true first to preview the itemized " + + "advisory estimate, then pass yes:true to acknowledge the " + + "fee and queue the refresh") +} + // mcpResult builds a CallToolResult from a proto message response. func mcpResult(msg proto.Message) (*mcp.CallToolResult, error) { opts := protojson.MarshalOptions{ @@ -256,31 +273,38 @@ func registerMCPTools(s *mcp.Server, client waverpc.DaemonServiceClient) { // vtxos_refresh — refresh VTXOs. type vtxosRefreshArgs struct { - Outpoints []string `json:"outpoints,omitempty" jsonschema:"VTXO outpoint(s) to refresh (txid:index)"` //nolint:ll - All bool `json:"all,omitempty" jsonschema:"refresh all live VTXOs"` //nolint:ll - DryRun bool `json:"dry_run,omitempty" jsonschema:"validate without queuing"` //nolint:ll + Outpoints []string `json:"outpoints,omitempty" jsonschema:"VTXO outpoint(s) to refresh (txid:index)"` //nolint:ll + All bool `json:"all,omitempty" jsonschema:"refresh all live VTXOs"` //nolint:ll + DryRun bool `json:"dry_run,omitempty" jsonschema:"validate without queuing and preview the estimated operator fee"` //nolint:ll + Yes bool `json:"yes,omitempty" jsonschema:"acknowledge the operator fee and queue the refresh (required unless dry_run)"` //nolint:ll } mcp.AddTool(s, &mcp.Tool{ - Name: "ark.vtxos.refresh", - Description: "Queue VTXOs for refresh in next round", + Name: "ark.vtxos.refresh", + Description: "Queue VTXOs for refresh in next round. A " + + "refresh is charged an operator fee at seal time: " + + "call with dry_run:true first for an itemized " + + "advisory estimate, then pass yes:true to " + + "acknowledge the fee and queue", }, func(ctx context.Context, req *mcp.CallToolRequest, args vtxosRefreshArgs) (*mcp.CallToolResult, any, error) { - rpcReq := &waverpc.RefreshVTXOsRequest{ - DryRun: args.DryRun, + // Same consent contract as the CLI gate: a real refresh + // needs explicit acknowledgement. The error returns + // immediately (nothing can block on MCP) and tells the + // agent exactly how to proceed. + if err := checkMCPRefreshConsent( + args.DryRun, args.Yes, + ); err != nil { + return nil, nil, err } - if args.All { - rpcReq.Selection = &waverpc.RefreshVTXOsRequest_All{ - All: true, - } - } else if len(args.Outpoints) > 0 { - sel := &waverpc.RefreshVTXOsRequest_Outpoints{ - Outpoints: &waverpc.OutpointSelection{ - Outpoints: args.Outpoints, - }, - } - rpcReq.Selection = sel + + rpcReq, err := buildRefreshVTXOsRequest( + args.Outpoints, args.All, args.DryRun, + ) + if err != nil { + return nil, nil, err } + resp, err := client.RefreshVTXOs(ctx, rpcReq) if err != nil { return nil, nil, err diff --git a/cmd/wavecli/waveclicommands/cmd_vtxos.go b/cmd/wavecli/waveclicommands/cmd_vtxos.go index e2cb840be..f31f4f493 100644 --- a/cmd/wavecli/waveclicommands/cmd_vtxos.go +++ b/cmd/wavecli/waveclicommands/cmd_vtxos.go @@ -9,6 +9,8 @@ import ( "github.com/lightninglabs/wavelength/waverpc" "github.com/spf13/cobra" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" ) @@ -140,6 +142,13 @@ func newVTXOsRefreshCmd() *cobra.Command { Long: "Queues one or more VTXOs for refresh and then " + "commits the queued intents to the next round, " + "extending the VTXOs' expiry.\n\n" + + "A refresh is charged an operator fee, set by the " + + "server-issued quote at seal time. Pass " + + "`--dry_run` to preview an itemized advisory " + + "estimate without queuing anything. An " + + "interactive refresh shows the estimate and asks " + + "for confirmation; pass `--yes` to skip the " + + "prompt (required on non-interactive stdin).\n\n" + "Under the hood the refresh intent first " + "accumulates in the round actor's " + "PendingRoundAssembly state. By default this " + @@ -161,7 +170,11 @@ func newVTXOsRefreshCmd() *cobra.Command { "refresh all live VTXOs") cmd.Flags().Bool("dry_run", false, - "validate without queuing") + "validate without queuing and preview the estimated "+ + "operator fee") + + cmd.Flags().Bool("yes", false, + "skip the interactive fee confirmation") cmd.Flags().Bool("no_join", false, "skip the implicit `ark rounds join` follow-up so this "+ @@ -170,7 +183,42 @@ func newVTXOsRefreshCmd() *cobra.Command { return cmd } -// vtxosRefresh executes the RefreshVTXOs RPC. +// buildRefreshVTXOsRequest translates the flag surface into a +// RefreshVTXOsRequest. Extracted so the MCP tool and the CLI share +// the same selection guards — divergence here would let one surface +// silently refresh a different VTXO set than the other. +func buildRefreshVTXOsRequest(outpoints []string, all, + dryRun bool) (*waverpc.RefreshVTXOsRequest, error) { + + if !all && len(outpoints) == 0 { + return nil, fmt.Errorf("either --outpoint or --all is required") + } + + if all && len(outpoints) > 0 { + return nil, fmt.Errorf("--outpoint and --all are mutually " + + "exclusive") + } + + req := &waverpc.RefreshVTXOsRequest{DryRun: dryRun} + + if all { + req.Selection = &waverpc.RefreshVTXOsRequest_All{ + All: true, + } + } else { + req.Selection = &waverpc.RefreshVTXOsRequest_Outpoints{ + Outpoints: &waverpc.OutpointSelection{ + Outpoints: outpoints, + }, + } + } + + return req, nil +} + +// vtxosRefresh executes the RefreshVTXOs RPC. Flag handling lives in +// buildRefreshVTXOsRequest so the same builder can be reused by the +// MCP tool and covered with a focused unit test. func vtxosRefresh(cmd *cobra.Command, _ []string) error { client, conn, err := getDaemonClient(cmd) if err != nil { @@ -186,30 +234,33 @@ func vtxosRefresh(cmd *cobra.Command, _ []string) error { all, _ := cmd.Flags().GetBool("all") dryRun, _ := cmd.Flags().GetBool("dry_run") - if !all && len(outpoints) == 0 { - return fmt.Errorf("either --outpoint or --all is " + - "required") + built, err := buildRefreshVTXOsRequest( + outpoints, all, dryRun, + ) + if err != nil { + return err } - if all { - req.Selection = &waverpc.RefreshVTXOsRequest_All{ - All: true, - } - } else { - sel := &waverpc.RefreshVTXOsRequest_Outpoints{ - Outpoints: &waverpc.OutpointSelection{ - Outpoints: outpoints, - }, - } - req.Selection = sel - } - req.DryRun = dryRun + // Proto pointer-receiver shims: copy built fields onto + // the outer req that parseRequest owns. + req.Selection = built.Selection + req.DryRun = built.DryRun return nil }); err != nil { return err } + if err := confirmRefreshIfNeeded( + cmd, req, func(preview *waverpc.RefreshVTXOsRequest) ( + *waverpc.RefreshVTXOsResponse, error) { + + return client.RefreshVTXOs(cmd.Context(), preview) + }, + ); err != nil { + return err + } + resp, err := client.RefreshVTXOs( cmd.Context(), req, ) @@ -221,11 +272,240 @@ func vtxosRefresh(cmd *cobra.Command, _ []string) error { return err } + // The itemized estimate is in the JSON body on stdout; the + // summary goes to stderr so a human reading the terminal sees + // the headline number without stdout consumers having to strip + // prose. + for _, line := range summarizeRefreshFeeEstimate( + resp.GetFeeEstimate(), + ) { + fmt.Fprintln(cmd.ErrOrStderr(), line) + } + + // A non-empty dry-run preview without an estimate can only come + // from a daemon that predates the fee preview: the daemon omits + // the estimate solely for empty selections. Warn rather than + // stay silent — the flag help promises a fee preview, and + // silence here would read as "free". + if req.DryRun && len(resp.GetQueuedOutpoints()) > 0 && + resp.GetFeeEstimate() == nil { + + fmt.Fprintln( + cmd.ErrOrStderr(), + "warning: the daemon returned no fee estimate "+ + "(older daemon?); a refresh is still "+ + "charged the binding seal-time fee", + ) + } + noJoin, _ := cmd.Flags().GetBool("no_join") return maybeJoinNextRound(cmd, client, req.DryRun, noJoin) } +// summarizeRefreshFeeEstimate renders the dry-run fee estimate as +// human-readable stderr lines. Split from the IO wrapper so the +// wording rules are covered by a focused unit test. A nil estimate +// (real refresh, empty selection) renders nothing. +// +// Every branch repeats that the number is advisory: the binding fee is +// set by the seal-time quote, and telling the user otherwise here +// would overpromise exactly the guarantee the refresh flow cannot +// make. +func summarizeRefreshFeeEstimate(est *waverpc.RefreshFeeEstimate) []string { + if est == nil { + return nil + } + + var lines []string + + count := len(est.Outpoints) + + switch { + case est.EstimateError != "": + lines = append( + lines, fmt.Sprintf("warning: refresh fee estimate "+ + "unavailable (%s); a refresh is still "+ + "charged the binding seal-time fee", + est.EstimateError), + ) + + if est.FreeRefreshEligible { + lines = append( + lines, fmt.Sprintf("expected free: all %d "+ + "selected VTXO(s) are inside the "+ + "operator's free-refresh window "+ + "(advisory)", count), + ) + } + + case est.FreeRefreshEligible: + lines = append( + lines, fmt.Sprintf("estimated refresh fee: 0 sat — "+ + "all %d selected VTXO(s) are inside the "+ + "operator's free-refresh window (advisory; "+ + "the binding fee is set at seal time)", count), + ) + + default: + lines = append( + lines, + fmt.Sprintf( + "estimated refresh fee: %d sat across %d "+ + "VTXO(s) (advisory; the binding fee "+ + "is set at seal time)", + est.GetEstimatedTotalFeeSat(), count, + ), + ) + } + + var belowDust int + for _, row := range est.Outpoints { + if row.BelowDustWarning { + belowDust++ + } + } + if belowDust > 0 { + lines = append( + lines, fmt.Sprintf("warning: %d selected VTXO(s) are "+ + "below the operator's minimum viable amount; "+ + "refreshing them pays fees on coins that "+ + "already cost more to exit than they hold", + belowDust), + ) + } + + return lines +} + +// confirmRefreshIfNeeded gates dispatch of a real refresh on explicit +// consent: every refresh is charged an operator fee at seal time, and +// before this gate the CLI queued the refresh without ever mentioning +// it (#986). The check runs after parseRequest returns so it covers +// both the flag-driven path and the --json path, mirroring +// confirmLeaveAllIfNeeded. Skip when --yes is set (explicit scripted +// consent) or when req.DryRun is set (previewing, not spending). +// +// When stdin is not a TTY (agents, CI, pipelines), the function +// refuses to prompt — the caller must pass --yes or --dry_run — so a +// non-interactive invocation is never blocked on a y/N it cannot +// answer. Only on a TTY does it fetch the advisory estimate (via the +// same RPC in dry-run form, through fetchPreview) and prompt, so the +// operator consents to a number rather than a mystery fee. A preview +// that fails with InvalidArgument aborts — the real refresh would +// reject the same request shape — while any other preview failure +// degrades to prompting with an "estimate unavailable" warning: a +// broken estimate path must not make refreshes unconfirmable. +func confirmRefreshIfNeeded(cmd *cobra.Command, + req *waverpc.RefreshVTXOsRequest, + fetchPreview func(*waverpc.RefreshVTXOsRequest) ( + *waverpc.RefreshVTXOsResponse, error)) error { + + if req.DryRun { + return nil + } + + confirmYes, _ := cmd.Flags().GetBool("yes") + if confirmYes { + return nil + } + + if !stdinIsTTY(cmd) { + return PrintError( + "INVALID_ARGS", "refresh is charged an operator "+ + "fee at seal time and requires --yes "+ + "(explicit consent) or --dry_run (fee "+ + "preview) on non-interactive stdin; "+ + "refusing to prompt because an agent "+ + "cannot respond to y/N", + ) + } + + cloned := proto.Clone(req) + preview, ok := cloned.(*waverpc.RefreshVTXOsRequest) + if !ok { + + // Unreachable: proto.Clone preserves the concrete type. + return fmt.Errorf("unexpected preview request type %T", cloned) + } + preview.DryRun = true + + var lines []string + resp, err := fetchPreview(preview) + switch { + case status.Code(err) == codes.InvalidArgument: + // The dry-run validates the same request shape the real + // dispatch would; a caller mistake (unknown or malformed + // outpoint) should abort here, not after a confirmed + // queue. + return fmt.Errorf("refresh preview rejected: %w", err) + + case err != nil: + lines = []string{ + "warning: refresh fee estimate unavailable " + + "(preview failed); a refresh is still " + + "charged the binding seal-time fee", + } + + default: + // An empty preview means the selection resolves to + // nothing (refresh --all with no live VTXOs): the real + // dispatch queues nothing and charges nothing, so there + // is no fee to consent to and warning about a charge + // would be false. + if resp.GetStatus() == "preview" && + len(resp.GetQueuedOutpoints()) == 0 { + + fmt.Fprintln( + cmd.ErrOrStderr(), + "no live VTXOs to refresh; nothing will be "+ + "queued", + ) + + return nil + } + + lines = summarizeRefreshFeeEstimate(resp.GetFeeEstimate()) + if len(lines) == 0 { + lines = []string{ + "warning: the daemon returned no fee " + + "estimate; a refresh is still " + + "charged the binding seal-time fee", + } + } + } + + return promptRefreshConfirmation(cmd, lines) +} + +// promptRefreshConfirmation shows the fee-estimate lines and asks the +// operator to confirm the refresh on stdin. Any answer other than "y" +// or "yes" (case-insensitive) aborts the command. Only called when +// stdin is known to be a TTY (see confirmRefreshIfNeeded). All prompt +// output goes to stderr: stdout is reserved for the JSON body, and a +// prompt swallowed by a shell pipe would read as a hung command. +func promptRefreshConfirmation(cmd *cobra.Command, lines []string) error { + out := cmd.ErrOrStderr() + + for _, line := range lines { + fmt.Fprintln(out, line) + } + fmt.Fprint(out, "Proceed with refresh? [y/N]: ") + + reader := bufio.NewReader(cmd.InOrStdin()) + answer, err := reader.ReadString('\n') + if err != nil { + return fmt.Errorf("read confirmation: %w", err) + } + + answer = strings.TrimSpace(strings.ToLower(answer)) + if answer != "y" && answer != "yes" { + return fmt.Errorf("aborted by user") + } + + return nil +} + // newVTXOsLeaveCmd creates the vtxos leave subcommand. func newVTXOsLeaveCmd() *cobra.Command { cmd := &cobra.Command{ @@ -609,9 +889,12 @@ func defaultStdinIsTTY(cmd *cobra.Command) bool { // promptLeaveAllConfirmation asks the operator to confirm a // --all invocation on stdin. Any answer other than "y" or "yes" // (case-insensitive) aborts the command. Only called when stdin is -// known to be a TTY (see confirmLeaveAllIfNeeded). +// known to be a TTY (see confirmLeaveAllIfNeeded). The prompt goes to +// stderr, matching promptRefreshConfirmation: stdout is reserved for +// the JSON body, and a prompt swallowed by a shell pipe would read as +// a hung command. func promptLeaveAllConfirmation(cmd *cobra.Command) error { - out := cmd.OutOrStdout() + out := cmd.ErrOrStderr() fmt.Fprintln( out, "About to queue ALL live VTXOs for cooperative leave. "+ diff --git a/cmd/wavecli/waveclicommands/cmd_vtxos_refresh_test.go b/cmd/wavecli/waveclicommands/cmd_vtxos_refresh_test.go new file mode 100644 index 000000000..ed9aaacb2 --- /dev/null +++ b/cmd/wavecli/waveclicommands/cmd_vtxos_refresh_test.go @@ -0,0 +1,702 @@ +package waveclicommands + +import ( + "bytes" + "context" + "net" + "strings" + "sync" + "testing" + + "github.com/lightninglabs/wavelength/waverpc" + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/status" + "google.golang.org/grpc/test/bufconn" + "google.golang.org/protobuf/proto" +) + +// newRefreshTestCmd returns a vtxos refresh cobra command with the +// supplied stdin plumbed in so the prompt path is testable, plus the +// combined output buffer for wording assertions. +func newRefreshTestCmd(t *testing.T, + stdin string) (*cobra.Command, *bytes.Buffer) { + + t.Helper() + + cmd := newVTXOsRefreshCmd() + cmd.SetContext(t.Context()) + cmd.SetIn(strings.NewReader(stdin)) + + out := &bytes.Buffer{} + cmd.SetOut(out) + cmd.SetErr(out) + + return cmd, out +} + +// explicitRefreshReq returns a well-formed single-outpoint refresh +// request for gate tests. +func explicitRefreshReq() *waverpc.RefreshVTXOsRequest { + return &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_Outpoints{ + Outpoints: &waverpc.OutpointSelection{ + Outpoints: []string{ + "aa:0", + }, + }, + }, + } +} + +// staticPreview returns a fetchPreview stub that yields a fixed +// response and records whether it was called. +func staticPreview(called *bool, resp *waverpc.RefreshVTXOsResponse, + err error, +) func(*waverpc.RefreshVTXOsRequest) (*waverpc.RefreshVTXOsResponse, error) { + + return func(req *waverpc.RefreshVTXOsRequest) ( + *waverpc.RefreshVTXOsResponse, error) { + + *called = true + + return resp, err + } +} + +// TestConfirmRefreshDryRunBypasses verifies a dry-run request is not +// gated: the user is previewing, not spending. +func TestConfirmRefreshDryRunBypasses(t *testing.T) { + t.Parallel() + + cmd, _ := newRefreshTestCmd(t, "" /* no stdin */) + + req := explicitRefreshReq() + req.DryRun = true + + var previewed bool + require.NoError( + t, + confirmRefreshIfNeeded( + cmd, req, staticPreview(&previewed, nil, nil), + ), + ) + require.False(t, previewed) +} + +// TestConfirmRefreshYesFlagBypasses verifies --yes short-circuits both +// the prompt and the extra preview round-trip for scripted use. +func TestConfirmRefreshYesFlagBypasses(t *testing.T) { + t.Parallel() + + cmd, _ := newRefreshTestCmd(t, "" /* no stdin */) + require.NoError(t, cmd.Flags().Set("yes", "true")) + + var previewed bool + require.NoError( + t, + confirmRefreshIfNeeded( + cmd, explicitRefreshReq(), + staticPreview(&previewed, nil, nil), + ), + ) + require.False(t, previewed, + "--yes must not spend a preview round-trip") +} + +// TestConfirmRefreshNonTTYRefusesPrompt is the agent-safety guard the +// issue's acceptance criteria require: a non-interactive invocation +// without --yes or --dry_run fails fast with an INVALID_ARGS envelope +// instead of blocking on a prompt an agent cannot answer. +func TestConfirmRefreshNonTTYRefusesPrompt(t *testing.T) { + // NOT t.Parallel() — overrides the package-level stdinIsTTY + // indirection. + + prev := stdinIsTTY + stdinIsTTY = func(*cobra.Command) bool { return false } + defer func() { + stdinIsTTY = prev + }() + + cmd := newVTXOsRefreshCmd() + cmd.SetContext(t.Context()) + + var previewed bool + err := confirmRefreshIfNeeded( + cmd, explicitRefreshReq(), staticPreview(&previewed, nil, nil), + ) + require.Error(t, err) + require.Contains(t, err.Error(), "--yes") + require.Contains(t, err.Error(), "--dry_run") + require.True( + t, ErrorWasPrinted(err), + "expected a printedError so main.go exits with the "+ + "INVALID_ARGS code", + ) + require.False(t, previewed) +} + +// TestConfirmRefreshPromptShowsEstimateAndAcceptsYes verifies the +// interactive path consents to a number: the preview estimate is +// printed before the prompt and "y" proceeds. +func TestConfirmRefreshPromptShowsEstimateAndAcceptsYes(t *testing.T) { + t.Parallel() + + cmd, out := newRefreshTestCmd(t, "y\n") + + var previewed bool + preview := staticPreview( + &previewed, &waverpc.RefreshVTXOsResponse{ + QueuedOutpoints: []string{"aa:0"}, + Status: "preview", + FeeEstimate: &waverpc.RefreshFeeEstimate{ + EstimatedTotalFeeSat: proto.Int64(777), + Outpoints: []*waverpc.OutpointFeeEstimate{ + {Outpoint: "aa:0"}, + }, + }, + }, nil, + ) + + require.NoError( + t, + confirmRefreshIfNeeded( + cmd, explicitRefreshReq(), preview, + ), + ) + require.True(t, previewed) + require.Contains(t, out.String(), "777 sat") + require.Contains(t, out.String(), "Proceed with refresh? [y/N]") +} + +// TestConfirmRefreshPromptRejectsNo verifies "n" aborts before any +// dispatch, default-N posture included. +func TestConfirmRefreshPromptRejectsNo(t *testing.T) { + t.Parallel() + + for _, answer := range []string{"n\n", "\n"} { + cmd, _ := newRefreshTestCmd(t, answer) + + var previewed bool + err := confirmRefreshIfNeeded( + cmd, explicitRefreshReq(), + staticPreview(&previewed, nil, nil), + ) + require.Error(t, err) + require.Contains(t, err.Error(), "aborted by user") + } +} + +// TestConfirmRefreshPreviewInvalidArgumentAborts verifies a preview +// rejected as InvalidArgument (unknown or malformed outpoint) aborts +// the flow: the real dispatch would reject the same request shape, so +// prompting the user to confirm it would be noise. +func TestConfirmRefreshPreviewInvalidArgumentAborts(t *testing.T) { + t.Parallel() + + cmd, _ := newRefreshTestCmd(t, "y\n") + + var previewed bool + err := confirmRefreshIfNeeded( + cmd, explicitRefreshReq(), + staticPreview( + &previewed, nil, status.Error( + codes.InvalidArgument, "unknown VTXO outpoint", + ), + ), + ) + require.Error(t, err) + require.Contains(t, err.Error(), "refresh preview rejected") +} + +// TestConfirmRefreshPreviewFailureStillPrompts verifies a broken +// estimate path (operator down, old daemon) degrades to prompting +// with a warning instead of making refreshes unconfirmable — and the +// warning says a fee still applies. +func TestConfirmRefreshPreviewFailureStillPrompts(t *testing.T) { + t.Parallel() + + cmd, out := newRefreshTestCmd(t, "y\n") + + var previewed bool + require.NoError( + t, + confirmRefreshIfNeeded( + cmd, explicitRefreshReq(), + staticPreview( + &previewed, nil, status.Error( + codes.Unavailable, "operator down", + ), + ), + ), + ) + require.Contains(t, out.String(), "estimate unavailable") + require.Contains(t, out.String(), "still charged") + require.Contains(t, out.String(), "Proceed with refresh? [y/N]") +} + +// TestConfirmRefreshPreviewWithoutEstimateStillPrompts verifies a +// daemon that returns a preview without a fee estimate (an older +// daemon behind a newer CLI) still yields a warning plus the prompt, +// never a silent zero. +func TestConfirmRefreshPreviewWithoutEstimateStillPrompts(t *testing.T) { + t.Parallel() + + cmd, out := newRefreshTestCmd(t, "y\n") + + var previewed bool + require.NoError( + t, + confirmRefreshIfNeeded( + cmd, explicitRefreshReq(), + staticPreview( + &previewed, &waverpc.RefreshVTXOsResponse{ + QueuedOutpoints: []string{"aa:0"}, + Status: "preview", + }, + nil, + ), + ), + ) + require.Contains(t, out.String(), "no fee estimate") + require.Contains(t, out.String(), "still charged") +} + +// TestBuildRefreshVTXOsRequestRejectsOutpointAndAll verifies that a +// caller can't combine --outpoint and --all: before the shared +// builder, --all silently won and the outpoints were dropped. +func TestBuildRefreshVTXOsRequestRejectsOutpointAndAll(t *testing.T) { + t.Parallel() + + _, err := buildRefreshVTXOsRequest( + []string{ + "aa:0", + }, + true, false, + ) + require.Error(t, err) + require.Contains(t, err.Error(), "mutually exclusive") +} + +// TestBuildRefreshVTXOsRequestRejectsNoSelection verifies that a +// caller with neither --outpoint nor --all gets a clean CLI-side +// error rather than hitting the daemon with an empty request. +func TestBuildRefreshVTXOsRequestRejectsNoSelection(t *testing.T) { + t.Parallel() + + _, err := buildRefreshVTXOsRequest(nil, false, false) + require.Error(t, err) + require.Contains(t, err.Error(), "either --outpoint or --all") +} + +// TestBuildRefreshVTXOsRequestBuildsSelections locks in the two +// valid selection shapes and the dry_run passthrough. +func TestBuildRefreshVTXOsRequestBuildsSelections(t *testing.T) { + t.Parallel() + + req, err := buildRefreshVTXOsRequest(nil, true, true) + require.NoError(t, err) + require.True(t, req.GetAll()) + require.True(t, req.DryRun) + + req, err = buildRefreshVTXOsRequest( + []string{ + "aa:0", "bb:1", + }, + false, false, + ) + require.NoError(t, err) + require.Equal( + t, []string{ + "aa:0", + "bb:1", + }, + req.GetOutpoints().GetOutpoints(), + ) + require.False(t, req.DryRun) +} + +// TestSummarizeRefreshFeeEstimateNil verifies a response without an +// estimate (real refresh, empty selection) renders no stderr lines. +func TestSummarizeRefreshFeeEstimateNil(t *testing.T) { + t.Parallel() + + require.Empty(t, summarizeRefreshFeeEstimate(nil)) +} + +// TestSummarizeRefreshFeeEstimatePaid verifies the ordinary paid +// preview renders the headline total, the selection size, and the +// advisory caveat. +func TestSummarizeRefreshFeeEstimatePaid(t *testing.T) { + t.Parallel() + + lines := summarizeRefreshFeeEstimate(&waverpc.RefreshFeeEstimate{ + EstimatedTotalFeeSat: proto.Int64(1_234), + Outpoints: []*waverpc.OutpointFeeEstimate{ + {Outpoint: "aa:0"}, {Outpoint: "bb:1"}, + }, + }) + require.Len(t, lines, 1) + require.Contains(t, lines[0], "1234 sat") + require.Contains(t, lines[0], "2 VTXO(s)") + require.Contains(t, lines[0], "advisory") + require.Contains(t, lines[0], "seal time") +} + +// TestSummarizeRefreshFeeEstimateFree verifies a waiver-eligible +// selection leads with the zero fee and names the free-refresh window +// as the reason. +func TestSummarizeRefreshFeeEstimateFree(t *testing.T) { + t.Parallel() + + lines := summarizeRefreshFeeEstimate(&waverpc.RefreshFeeEstimate{ + FreeRefreshEligible: true, + Outpoints: []*waverpc.OutpointFeeEstimate{ + {Outpoint: "aa:0", TotalFeeSat: 500}, + }, + }) + require.Len(t, lines, 1) + require.Contains(t, lines[0], "0 sat") + require.Contains(t, lines[0], "free-refresh window") + require.Contains(t, lines[0], "advisory") +} + +// TestSummarizeRefreshFeeEstimateError verifies a degraded estimate +// warns that the numbers are absent — and still says a fee applies — +// rather than printing a zero a user could misread as free. +func TestSummarizeRefreshFeeEstimateError(t *testing.T) { + t.Parallel() + + lines := summarizeRefreshFeeEstimate(&waverpc.RefreshFeeEstimate{ + EstimateError: "operator fee estimate unavailable", + Outpoints: []*waverpc.OutpointFeeEstimate{ + {Outpoint: "aa:0"}, + }, + }) + require.Len(t, lines, 1) + require.Contains(t, lines[0], "warning") + require.Contains(t, lines[0], "operator fee estimate unavailable") + require.Contains(t, lines[0], "still charged") + require.NotContains(t, lines[0], "0 sat") +} + +// TestSummarizeRefreshFeeEstimateErrorFreeWindow verifies the locally +// computed waiver still surfaces when the operator quote failed: the +// user learns the refresh is expected free even in degraded mode. +func TestSummarizeRefreshFeeEstimateErrorFreeWindow(t *testing.T) { + t.Parallel() + + lines := summarizeRefreshFeeEstimate(&waverpc.RefreshFeeEstimate{ + EstimateError: "operator fee estimate unavailable", + FreeRefreshEligible: true, + Outpoints: []*waverpc.OutpointFeeEstimate{ + {Outpoint: "aa:0"}, + }, + }) + require.Len(t, lines, 2) + require.Contains(t, lines[0], "warning") + require.Contains(t, lines[1], "expected free") +} + +// TestSummarizeRefreshFeeEstimateBelowDust verifies uneconomic VTXOs in +// the selection add a count-level warning on top of the fee headline. +func TestSummarizeRefreshFeeEstimateBelowDust(t *testing.T) { + t.Parallel() + + lines := summarizeRefreshFeeEstimate(&waverpc.RefreshFeeEstimate{ + EstimatedTotalFeeSat: proto.Int64(400), + Outpoints: []*waverpc.OutpointFeeEstimate{ + {Outpoint: "aa:0", BelowDustWarning: true}, + {Outpoint: "bb:1"}, + {Outpoint: "cc:2", BelowDustWarning: true}, + }, + }) + require.Len(t, lines, 2) + require.Contains(t, lines[1], "2 selected VTXO(s)") + require.Contains(t, lines[1], "minimum viable") +} + +// fakeRefreshDaemon is an in-process DaemonService recording refresh +// and join traffic, for wiring tests that drive the full vtxosRefresh +// command path. +type fakeRefreshDaemon struct { + waverpc.UnimplementedDaemonServiceServer + + mu sync.Mutex + refreshReqs []*waverpc.RefreshVTXOsRequest + joinCalls int + + previewResp *waverpc.RefreshVTXOsResponse + queuedResp *waverpc.RefreshVTXOsResponse +} + +func (f *fakeRefreshDaemon) RefreshVTXOs(_ context.Context, + req *waverpc.RefreshVTXOsRequest) (*waverpc.RefreshVTXOsResponse, + error) { + + f.mu.Lock() + defer f.mu.Unlock() + + reqCopy, ok := proto.Clone(req).(*waverpc.RefreshVTXOsRequest) + if !ok { + return nil, status.Error(codes.Internal, "clone failed") + } + f.refreshReqs = append(f.refreshReqs, reqCopy) + + if req.DryRun { + if f.previewResp != nil { + return f.previewResp, nil + } + + return &waverpc.RefreshVTXOsResponse{ + QueuedOutpoints: []string{ + "aa:0", + }, + Status: "preview", + }, nil + } + + if f.queuedResp != nil { + return f.queuedResp, nil + } + + return &waverpc.RefreshVTXOsResponse{ + QueuedOutpoints: []string{ + "aa:0", + }, + Status: "queued", + }, nil +} + +func (f *fakeRefreshDaemon) JoinNextRound(_ context.Context, + _ *waverpc.JoinNextRoundRequest) (*waverpc.JoinNextRoundResponse, + error) { + + f.mu.Lock() + defer f.mu.Unlock() + + f.joinCalls++ + + return &waverpc.JoinNextRoundResponse{}, nil +} + +// realRefreshCalls returns how many non-dry-run refresh dispatches the +// fake daemon received. +func (f *fakeRefreshDaemon) realRefreshCalls() int { + f.mu.Lock() + defer f.mu.Unlock() + + n := 0 + for _, req := range f.refreshReqs { + if !req.DryRun { + n++ + } + } + + return n +} + +// withFakeRefreshDaemon serves the fake over bufconn and points the +// getDaemonClient indirection at it for the duration of the test. +// Tests using it must NOT be parallel: it swaps package-level state. +func withFakeRefreshDaemon(t *testing.T, fake *fakeRefreshDaemon) { + t.Helper() + + const bufSize = 1024 * 1024 + lis := bufconn.Listen(bufSize) + + srv := grpc.NewServer() + waverpc.RegisterDaemonServiceServer(srv, fake) + + go func() { + _ = srv.Serve(lis) + }() + t.Cleanup(srv.Stop) + + dialer := func(context.Context, string) (net.Conn, error) { + return lis.Dial() + } + + prev := getDaemonClient + getDaemonClient = func(*cobra.Command) (waverpc.DaemonServiceClient, + *grpc.ClientConn, error) { + + conn, err := grpc.NewClient( + "passthrough:///bufconn", + grpc.WithContextDialer(dialer), + grpc.WithTransportCredentials( + insecure.NewCredentials(), + ), + ) + if err != nil { + return nil, nil, err + } + + return waverpc.NewDaemonServiceClient(conn), conn, nil + } + t.Cleanup(func() { + getDaemonClient = prev + }) +} + +// TestVTXOsRefreshWiringNonTTYNeverDispatches drives the full command +// path: a non-interactive real refresh without --yes must refuse +// before ANY RPC reaches the daemon — the gate is wired ahead of +// dispatch, not beside it. +func TestVTXOsRefreshWiringNonTTYNeverDispatches(t *testing.T) { + // NOT t.Parallel() — overrides getDaemonClient and stdinIsTTY. + + fake := &fakeRefreshDaemon{} + withFakeRefreshDaemon(t, fake) + + prev := stdinIsTTY + stdinIsTTY = func(*cobra.Command) bool { return false } + defer func() { + stdinIsTTY = prev + }() + + cmd := newVTXOsRefreshCmd() + cmd.SetContext(t.Context()) + require.NoError(t, cmd.Flags().Set("outpoint", "aa:0")) + + err := vtxosRefresh(cmd, nil) + require.Error(t, err) + require.True(t, ErrorWasPrinted(err)) + require.Empty( + t, fake.refreshReqs, "the refusal must fire before any RPC", + ) + require.Zero(t, fake.joinCalls) +} + +// TestVTXOsRefreshWiringYesDispatchesOnce verifies --yes skips the +// gate and performs exactly one real dispatch plus the auto-join — +// no preview round-trip is spent on scripted consent. +func TestVTXOsRefreshWiringYesDispatchesOnce(t *testing.T) { + // NOT t.Parallel() — overrides getDaemonClient. + + fake := &fakeRefreshDaemon{} + withFakeRefreshDaemon(t, fake) + + cmd, _ := newRefreshTestCmd(t, "" /* no stdin */) + require.NoError(t, cmd.Flags().Set("outpoint", "aa:0")) + require.NoError(t, cmd.Flags().Set("yes", "true")) + + require.NoError(t, vtxosRefresh(cmd, nil)) + require.Len(t, fake.refreshReqs, 1) + require.False(t, fake.refreshReqs[0].DryRun) + require.Equal(t, 1, fake.joinCalls) +} + +// TestVTXOsRefreshWiringDeclineNeverDispatches verifies an +// interactive "n" leaves the daemon untouched beyond the preview: +// one dry-run fetch, zero real dispatches, zero joins. +func TestVTXOsRefreshWiringDeclineNeverDispatches(t *testing.T) { + // NOT t.Parallel() — overrides getDaemonClient. + + fake := &fakeRefreshDaemon{} + withFakeRefreshDaemon(t, fake) + + cmd, _ := newRefreshTestCmd(t, "n\n") + require.NoError(t, cmd.Flags().Set("outpoint", "aa:0")) + + err := vtxosRefresh(cmd, nil) + require.Error(t, err) + require.Contains(t, err.Error(), "aborted by user") + require.Len(t, fake.refreshReqs, 1, + "exactly the preview fetch") + require.True(t, fake.refreshReqs[0].DryRun) + require.Zero(t, fake.realRefreshCalls()) + require.Zero(t, fake.joinCalls) +} + +// TestVTXOsRefreshWiringDryRunPrintsSummary verifies the --dry_run +// flag path end to end: one dry-run RPC, no join, no prompt, and the +// stderr summary carries the estimate headline. +func TestVTXOsRefreshWiringDryRunPrintsSummary(t *testing.T) { + // NOT t.Parallel() — overrides getDaemonClient. + + fake := &fakeRefreshDaemon{ + previewResp: &waverpc.RefreshVTXOsResponse{ + QueuedOutpoints: []string{ + "aa:0", + }, + Status: "preview", + FeeEstimate: &waverpc.RefreshFeeEstimate{ + EstimatedTotalFeeSat: proto.Int64(432), + Outpoints: []*waverpc.OutpointFeeEstimate{ + { + Outpoint: "aa:0", + }, + }, + }, + }, + } + withFakeRefreshDaemon(t, fake) + + cmd, out := newRefreshTestCmd(t, "" /* no stdin */) + require.NoError(t, cmd.Flags().Set("outpoint", "aa:0")) + require.NoError(t, cmd.Flags().Set("dry_run", "true")) + + require.NoError(t, vtxosRefresh(cmd, nil)) + require.Len(t, fake.refreshReqs, 1) + require.True(t, fake.refreshReqs[0].DryRun) + require.Zero(t, fake.joinCalls, "dry run must not auto-join") + require.Contains(t, out.String(), "432 sat") + require.NotContains(t, out.String(), "Proceed with refresh?") +} + +// TestVTXOsRefreshWiringOldDaemonWarns verifies the version-skew +// warning: a dry-run preview with outpoints but no fee estimate can +// only come from a pre-feature daemon, and the CLI must say so +// instead of silently dropping the promised preview. +func TestVTXOsRefreshWiringOldDaemonWarns(t *testing.T) { + // NOT t.Parallel() — overrides getDaemonClient. + + fake := &fakeRefreshDaemon{ + previewResp: &waverpc.RefreshVTXOsResponse{ + QueuedOutpoints: []string{ + "aa:0", + }, + Status: "preview", + }, + } + withFakeRefreshDaemon(t, fake) + + cmd, out := newRefreshTestCmd(t, "" /* no stdin */) + require.NoError(t, cmd.Flags().Set("outpoint", "aa:0")) + require.NoError(t, cmd.Flags().Set("dry_run", "true")) + + require.NoError(t, vtxosRefresh(cmd, nil)) + require.Contains(t, out.String(), "no fee estimate") + require.Contains(t, out.String(), "still charged") +} + +// TestVTXOsRefreshWiringEmptyAllSkipsPrompt verifies a TTY refresh +// --all over an empty wallet does not warn about a charge for a +// selection that queues nothing: the gate skips the prompt and says +// so. +func TestVTXOsRefreshWiringEmptyAllSkipsPrompt(t *testing.T) { + // NOT t.Parallel() — overrides getDaemonClient. + + fake := &fakeRefreshDaemon{ + previewResp: &waverpc.RefreshVTXOsResponse{ + Status: "preview", + }, + } + withFakeRefreshDaemon(t, fake) + + // No stdin: an unexpected prompt would fail loudly on EOF. + cmd, out := newRefreshTestCmd(t, "") + require.NoError(t, cmd.Flags().Set("all", "true")) + + require.NoError(t, vtxosRefresh(cmd, nil)) + require.Contains(t, out.String(), "no live VTXOs to refresh") + require.NotContains(t, out.String(), "Proceed with refresh?") + require.Equal( + t, 1, fake.realRefreshCalls(), + "the harmless empty dispatch still proceeds", + ) +} diff --git a/cmd/wavecli/waveclicommands/mcp_wallet_test.go b/cmd/wavecli/waveclicommands/mcp_wallet_test.go index 6e1ae944d..b40891033 100644 --- a/cmd/wavecli/waveclicommands/mcp_wallet_test.go +++ b/cmd/wavecli/waveclicommands/mcp_wallet_test.go @@ -136,3 +136,22 @@ func TestBuildWalletActivityRequestHappyPath(t *testing.T) { require.Equal(t, uint32(50), req.GetLimit()) require.Equal(t, "cursor-token", req.GetCursor()) } + +// TestCheckMCPRefreshConsent pins the MCP refresh fee-consent +// contract: a dry-run preview and an acknowledged refresh pass, a +// bare real refresh is refused with an actionable error that names +// both the preview and the acknowledgement path — matching the +// consent the schema registry promises for this method. +func TestCheckMCPRefreshConsent(t *testing.T) { + t.Parallel() + + require.NoError(t, checkMCPRefreshConsent(true, false)) + require.NoError(t, checkMCPRefreshConsent(false, true)) + require.NoError(t, checkMCPRefreshConsent(true, true)) + + err := checkMCPRefreshConsent(false, false) + require.Error(t, err) + require.Contains(t, err.Error(), "dry_run:true") + require.Contains(t, err.Error(), "yes:true") + require.Contains(t, err.Error(), "operator fee") +} diff --git a/cmd/wavecli/waveclicommands/schema_registry.go b/cmd/wavecli/waveclicommands/schema_registry.go index d8deb0439..3fded35de 100644 --- a/cmd/wavecli/waveclicommands/schema_registry.go +++ b/cmd/wavecli/waveclicommands/schema_registry.go @@ -638,7 +638,11 @@ func arkVTXOMethodRegistry() []schemaMethod { { Method: "ark.vtxos.refresh", Description: "Queue VTXOs for refresh and join the " + - "next round (auto-joins unless --no_join)", + "next round (auto-joins unless --no_join). " + + "Charged an operator fee at seal time; " + + "--dry_run previews an itemized estimate, " + + "and a real refresh requires --yes on " + + "non-interactive stdin", Params: []schemaParam{ { Name: "outpoint", @@ -651,6 +655,12 @@ func arkVTXOMethodRegistry() []schemaMethod { Type: "bool", Description: "refresh all live VTXOs", }, + { + Name: "yes", + Type: "bool", + Description: "skip the interactive " + + "fee confirmation", + }, { Name: "no_join", Type: "bool", diff --git a/docs/daemon_cli_guide.md b/docs/daemon_cli_guide.md index 514794505..2c22ac5f6 100644 --- a/docs/daemon_cli_guide.md +++ b/docs/daemon_cli_guide.md @@ -399,19 +399,43 @@ on later terms refreshes). When the cached boundary fires, it fetches a fresh An operator fee-schedule hot reload is not pushed proactively to already-connected wallets. +A refresh is charged an operator fee (on-chain share + liquidity + +margin), set by the server-issued quote at seal time and auto-accepted +up to the daemon's `maxoperatorfeesat` cap. `--dry_run` previews an +itemized advisory estimate for the selected VTXOs — per outpoint: +amount, remaining lifetime, liquidity / on-chain / margin components — +resolved entirely from the daemon's own view, with no manual amount or +remaining-blocks input. Selections fully inside the operator's +advertised free-refresh window preview with a zero total +(`free_refresh_eligible`), while the per-outpoint rows keep the +ordinary paid quote to show what the waiver saves. The estimate is +best-effort: if the operator or chain tip is unreachable the preview +still returns with `estimate_error` set. The binding fee remains the +seal-time quote and may differ from any estimate. + +An interactive real refresh shows the estimate and asks for +confirmation. On non-interactive stdin (agents, pipelines) the command +refuses to prompt: pass `--yes` (explicit consent) or `--dry_run` +(preview). This mirrors the `leave --all` consent gate. + | Flag | Type | Description | |------|------|-------------| | `--outpoint` | string[] | VTXO outpoint(s) to refresh (txid:index) | | `--all` | bool | Refresh all live VTXOs | -| `--dry_run` | bool | Validate without queuing | +| `--dry_run` | bool | Validate without queuing and preview the estimated fee | +| `--yes` | bool | Skip the interactive fee confirmation | | `--no_join` | bool | Skip the implicit `ark rounds join` follow-up | ```bash -# Explicit outpoints (auto-joins the next round) -wavecli ark vtxos refresh --outpoint +# Preview the itemized fee estimate without queuing anything +wavecli ark vtxos refresh --outpoint --dry_run + +# Explicit outpoints (auto-joins the next round; prompts with the +# estimate on a TTY, requires --yes when stdin is not interactive) +wavecli ark vtxos refresh --outpoint --yes # Batch with other intents — explicitly join later -wavecli ark vtxos refresh --outpoint --no_join +wavecli ark vtxos refresh --outpoint --yes --no_join wavecli ark vtxos leave --outpoint --no_join \ --address bcrt1p... wavecli ark rounds join diff --git a/waved/AGENTS.md b/waved/AGENTS.md index d377b0534..2aa26771e 100644 --- a/waved/AGENTS.md +++ b/waved/AGENTS.md @@ -97,6 +97,17 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/waved.= 1, and the + free-refresh waiver is computed locally from the cached operator + terms (the operator's EstimateFee does not apply it). Estimate + failures set `estimate_error` and never fail the preview. The real + refresh path still gates on wallet readiness. - `SendVTXO` enforces `maxRecipients = 256`, rejects per-recipient amounts outside `(0, MaxSatoshi]`, and uses overflow-safe summation; the wallet actor repeats these checks as defense-in-depth. diff --git a/waved/CLAUDE.md b/waved/CLAUDE.md index 2c6266894..391a38bce 100644 --- a/waved/CLAUDE.md +++ b/waved/CLAUDE.md @@ -97,6 +97,17 @@ For field-level detail, use `go doc github.com/lightninglabs/wavelength/waved.= 1, and the + free-refresh waiver is computed locally from the cached operator + terms (the operator's EstimateFee does not apply it). Estimate + failures set `estimate_error` and never fail the preview. The real + refresh path still gates on wallet readiness. - `SendVTXO` enforces `maxRecipients = 256`, rejects per-recipient amounts outside `(0, MaxSatoshi]`, and uses overflow-safe summation; the wallet actor repeats these checks as defense-in-depth. diff --git a/waved/congestion_roundtrip_test.go b/waved/congestion_roundtrip_test.go index 09753c24c..82e16a363 100644 --- a/waved/congestion_roundtrip_test.go +++ b/waved/congestion_roundtrip_test.go @@ -32,6 +32,22 @@ type fakeArkService struct { // fails the fee quote. estimateFeeErr error + // responseFn, when non-nil, computes the EstimateFee reply from + // the request instead of returning the canned response. Used by + // tests that quote several distinct (amount, remaining-blocks) + // pairs in one RPC and need per-pair replies. + responseFn func(*arkrpc.EstimateFeeRequest) *arkrpc.EstimateFeeResponse + + // errFn, when non-nil, lets a test fail EstimateFee selectively + // per request (e.g. only the second distinct quote of a + // selection). A nil return falls through to responseFn / the + // canned response. + errFn func(*arkrpc.EstimateFeeRequest) error + + // estimateFeeCalls counts EstimateFee invocations so tests can + // assert quote deduplication. + estimateFeeCalls int + // getInfoResponse is the canned GetInfo reply. Optional; // tests that don't exercise the fetchOperatorTerms path can // leave it nil, in which case GetInfo falls through to the @@ -54,11 +70,22 @@ func (f *fakeArkService) EstimateFee(_ context.Context, req *arkrpc.EstimateFeeRequest) (*arkrpc.EstimateFeeResponse, error) { f.lastRequest = req + f.estimateFeeCalls++ if f.estimateFeeErr != nil { return nil, f.estimateFeeErr } + if f.errFn != nil { + if err := f.errFn(req); err != nil { + return nil, err + } + } + + if f.responseFn != nil { + return f.responseFn(req), nil + } + return f.response, nil } diff --git a/waved/rpc_fees.go b/waved/rpc_fees.go index 229810aca..a35865293 100644 --- a/waved/rpc_fees.go +++ b/waved/rpc_fees.go @@ -49,9 +49,28 @@ const ( func (s *Server) quoteOperatorFee(ctx context.Context, amountSat int64, isBoarding bool, remainingBlocks uint32) (btcutil.Amount, error) { + resp, err := s.quoteOperatorFeeBreakdown( + ctx, amountSat, isBoarding, remainingBlocks, + ) + if err != nil { + return 0, err + } + + return btcutil.Amount(resp.TotalFeeSat), nil +} + +// quoteOperatorFeeBreakdown fetches the operator's full itemized +// EstimateFee response for one (amount, boarding, remaining-blocks) +// tuple. quoteOperatorFee wraps it for callers that only need the +// total; the refresh dry-run estimate consumes the component +// breakdown directly. +func (s *Server) quoteOperatorFeeBreakdown(ctx context.Context, amountSat int64, + isBoarding bool, remainingBlocks uint32) (*arkrpc.EstimateFeeResponse, + error) { + client := s.operatorArkClient() if client == nil { - return 0, status.Errorf(codes.Unavailable, "operator "+ + return nil, status.Errorf(codes.Unavailable, "operator "+ "connection not initialized") } @@ -63,7 +82,7 @@ func (s *Server) quoteOperatorFee(ctx context.Context, amountSat int64, }, ) if err != nil { - return 0, err + return nil, err } // Defensive nil-guard: well-behaved gRPC servers always return @@ -73,11 +92,11 @@ func (s *Server) quoteOperatorFee(ctx context.Context, amountSat int64, // clean codes.Internal so the caller can fall back instead of // crashing the daemon. if resp == nil { - return 0, status.Errorf(codes.Internal, "operator returned "+ + return nil, status.Errorf(codes.Internal, "operator returned "+ "empty fee response") } - return btcutil.Amount(resp.TotalFeeSat), nil + return resp, nil } // autoRefreshFeeQuoter builds the vtxo.RefreshFeeQuoter the VTXO diff --git a/waved/rpc_refresh_estimate.go b/waved/rpc_refresh_estimate.go new file mode 100644 index 000000000..8879685d9 --- /dev/null +++ b/waved/rpc_refresh_estimate.go @@ -0,0 +1,387 @@ +package waved + +import ( + "context" + "errors" + "fmt" + + "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/wire/v2" + "github.com/lightninglabs/wavelength/arkrpc" + "github.com/lightninglabs/wavelength/vtxo" + "github.com/lightninglabs/wavelength/waverpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// refreshQuoteKey dedupes operator EstimateFee calls across the selected +// VTXOs. The operator quote is a pure function of (amount, remaining +// blocks) — VTXOs from the same round share an expiry, so a wallet-wide +// dry run typically collapses to a handful of distinct quotes instead of +// one round-trip per VTXO. +type refreshQuoteKey struct { + amountSat int64 + remainingBlocks uint32 +} + +// refreshDryRunPreview services the dry_run branch of RefreshVTXOs: it +// resolves the selected VTXOs against the SQL store, echoes the preview +// outpoint list, and attaches the advisory fee estimate. It runs before +// the wallet-ready gate (mirroring the LeaveVTXOs dry-run ordering) so +// callers can preview a refresh — including its cost — without an +// unlocked wallet; everything it needs is the SQL VTXO view, the chain +// tip, and the operator connection. +func (r *RPCServer) refreshDryRunPreview(ctx context.Context, + explicit []wire.OutPoint, all bool) (*waverpc.RefreshVTXOsResponse, + error) { + + // A store-less daemon (partially wired setups, tests) cannot + // resolve amounts or lifetimes: keep the pre-existing preview + // echo for explicit outpoints and degrade the estimate instead + // of dropping the echo along with it. The selection=all echo is + // empty here by construction, matching the LeaveVTXOs nil-store + // tolerance. + if r.server.vtxoStore == nil { + outpointStrs := make([]string, 0, len(explicit)) + for _, op := range explicit { + outpointStrs = append( + outpointStrs, + fmt.Sprintf("%s:%d", op.Hash, op.Index), + ) + } + + resp := &waverpc.RefreshVTXOsResponse{ + QueuedOutpoints: outpointStrs, + Status: "preview", + } + if len(explicit) > 0 { + resp.FeeEstimate = &waverpc.RefreshFeeEstimate{ + EstimateError: "VTXO store unavailable; " + + "fee estimate not computed", + } + } + + return resp, nil + } + + descs, err := r.resolveRefreshPreviewTargets(ctx, explicit, all) + if err != nil { + return nil, err + } + + outpointStrs := make([]string, 0, len(descs)) + for _, desc := range descs { + outpointStrs = append( + outpointStrs, fmt.Sprintf("%s:%d", desc.Outpoint.Hash, + desc.Outpoint.Index), + ) + } + + resp := &waverpc.RefreshVTXOsResponse{ + QueuedOutpoints: outpointStrs, + Status: "preview", + } + + // An empty selection (refresh --all with no live VTXOs) has + // nothing to price: leave fee_estimate unset rather than + // attaching a vacuous zero-fee estimate a caller could misread + // as "free". + if len(descs) > 0 { + resp.FeeEstimate = r.estimateRefreshFees(ctx, descs) + } + + return resp, nil +} + +// resolveRefreshPreviewTargets maps the request selection onto full VTXO +// descriptors so the preview can price each target without the caller +// supplying amounts or lifetimes. Explicit outpoints are validated +// against the store: an unknown or non-live outpoint is a caller +// mistake and surfaces as InvalidArgument, because the real refresh can +// never execute it — a preview that echoed and priced it anyway would +// defeat dry_run's role as the validity probe behind the CLI's consent +// prompt. The caller guarantees a non-nil vtxoStore. +func (r *RPCServer) resolveRefreshPreviewTargets(ctx context.Context, + explicit []wire.OutPoint, all bool) ([]*vtxo.Descriptor, error) { + + if all { + liveVTXOs, err := r.server.vtxoStore.ListLiveVTXOs(ctx) + if err != nil { + return nil, status.Errorf(codes.Internal, "list live "+ + "VTXOs: %v", err) + } + + // Keep only VTXOs actually in LiveState, matching the + // real refresh path's filter: anything already on its + // way through a round must not be double-counted in the + // preview either. + descs := make([]*vtxo.Descriptor, 0, len(liveVTXOs)) + for _, desc := range liveVTXOs { + if desc.Status != vtxo.VTXOStatusLive { + continue + } + + descs = append(descs, desc) + } + + return descs, nil + } + + descs := make([]*vtxo.Descriptor, 0, len(explicit)) + for _, op := range explicit { + desc, err := r.server.vtxoStore.GetVTXO(ctx, op) + if err != nil { + if errors.Is(err, vtxo.ErrVTXONotFound) { + return nil, status.Errorf(codes.InvalidArgument, + "unknown VTXO outpoint %s:%d", op.Hash, + op.Index) + } + + return nil, status.Errorf(codes.Internal, "get VTXO "+ + "%s:%d: %v", op.Hash, op.Index, err) + } + + // GetVTXO resolves rows in any lifecycle state; the --all + // branch filters to LiveState, and the explicit branch + // must be as strict — a spent or in-flight VTXO would + // preview as refreshable (and be priced) while the real + // dispatch is guaranteed to fail on it. + if desc.Status != vtxo.VTXOStatusLive { + return nil, status.Errorf(codes.InvalidArgument, + "VTXO %s:%d is not refreshable (status %v)", + op.Hash, op.Index, desc.Status) + } + + descs = append(descs, desc) + } + + return descs, nil +} + +// degradeRefreshEstimate marks the estimate as degraded with the given +// caller-safe message. The locally computed waiver verdict survives +// degradation: a waiver-eligible selection keeps its explicit zero +// total, while any other degraded estimate leaves the total absent so a +// missing number can never be misread as a free refresh. +func degradeRefreshEstimate(est *waverpc.RefreshFeeEstimate, + msg string) *waverpc.RefreshFeeEstimate { + + est.EstimateError = msg + if est.FreeRefreshEligible { + est.EstimatedTotalFeeSat = proto.Int64(0) + } + + return est +} + +// refreshQuoteSane rejects operator quotes carrying negative, +// beyond-money-supply, or internally inconsistent values. Such a quote +// is operator nonsense; it is contained here so a broken (or hostile) +// operator can neither render negative components, poison the +// selection total, nor advertise an itemization that doesn't reconcile +// with the total the seal charges. +func refreshQuoteSane(quote *arkrpc.EstimateFeeResponse) bool { + // Bound every component individually before summing: three + // values each capped at MaxSatoshi cannot overflow int64, so + // the reconciliation below is wrap-free. + maxSat := int64(btcutil.MaxSatoshi) + components := []int64{ + quote.LiquidityFeeSat, + quote.OnchainShareSat, + quote.MarginSat, + } + var sum int64 + for _, c := range components { + if c < 0 || c > maxSat { + return false + } + sum += c + } + + // The operator's fee schedule defines total_fee_sat as exactly + // liquidity + on-chain share + margin; a quote whose parts + // don't sum to its total is as nonsensical as a negative one. + return quote.TotalFeeSat == sum && quote.TotalFeeSat <= maxSat +} + +// estimateRefreshFees builds the advisory RefreshFeeEstimate for the +// resolved targets. The estimate is strictly best-effort: any failure to +// reach the chain backend or the operator sets estimate_error and +// returns whatever facts are known locally (amounts, remaining +// lifetimes, free-window membership) instead of failing the preview — +// dry_run must keep working as a validity probe in degraded mode. +// +// The free-refresh waiver (lumos#675) is applied here rather than by the +// operator: the operator's EstimateFee prices every refresh as paid, so +// quoting a free late refresh through it alone would over-quote. The +// daemon knows the advertised window (cached operator terms), each +// VTXO's remaining lifetime, and that a RefreshVTXOs selection is +// exactly the pure one-for-one renewal shape the waiver requires, so +// eligibility is computable locally. Per-outpoint rows keep the +// ordinary paid quote (components sum to the row total) and only the +// selection-level total reflects the waiver, mirroring the operator's +// all-or-nothing seal-time rule. +func (r *RPCServer) estimateRefreshFees(ctx context.Context, + descs []*vtxo.Descriptor) *waverpc.RefreshFeeEstimate { + + est := &waverpc.RefreshFeeEstimate{} + + // The advertised free-refresh window rides on the cached + // operator terms; a cold cache reads as "no window", which only + // ever over-quotes (waiver missed, never invented). + var window uint32 + if terms := r.server.loadOperatorTerms(); terms != nil { + window = terms.FreeRefreshWindowBlocks + } + + height, err := r.currentBlockHeight(ctx) + if err != nil { + r.server.log.WarnS(ctx, "Refresh fee estimate: chain "+ + "height unavailable", err) + + // Without the chain tip neither remaining lifetimes nor + // window membership can be computed; return the + // amount-only rows so the caller still sees what was + // selected. + for _, desc := range descs { + est.Outpoints = append( + est.Outpoints, &waverpc.OutpointFeeEstimate{ + Outpoint: fmt.Sprintf( + "%s:%d", desc.Outpoint.Hash, + desc.Outpoint.Index, + ), + AmountSat: int64(desc.Amount), + }, + ) + } + + return degradeRefreshEstimate( + est, + "chain height unavailable; fee estimate not computed", + ) + } + + // First pass: per-outpoint facts the daemon knows locally. The + // quote's remaining-blocks figure is clamped to 1 because the + // operator treats zero as "use the full sweep-delay lifetime", + // which would massively over-quote an expiring VTXO; window + // membership uses the unclamped value (an expired VTXO is not + // waiver-eligible). + allInWindow := true + for _, desc := range descs { + remaining := vtxo.BlocksUntilExpiry(desc, height) + clamped := remaining + if clamped < 1 { + clamped = 1 + } + + inWindow := window > 0 && remaining > 0 && + uint32(remaining) <= window + if !inWindow { + allInWindow = false + } + + est.Outpoints = append( + est.Outpoints, &waverpc.OutpointFeeEstimate{ + Outpoint: fmt.Sprintf( + "%s:%d", desc.Outpoint.Hash, + desc.Outpoint.Index, + ), + AmountSat: int64(desc.Amount), + RemainingBlocks: uint32(clamped), + InFreeRefreshWindow: inWindow, + }, + ) + } + est.FreeRefreshEligible = allInWindow + + // Second pass: fetch and validate the operator quotes, deduped + // on (amount, remaining blocks), and pre-compute the selection + // total. Every quote is vetted before any component is written + // to a row, so a mid-selection failure degrades all-or-nothing — + // rows either all carry components or none do, which is exactly + // what estimate_error promises the caller. + quotes := make(map[refreshQuoteKey]*arkrpc.EstimateFeeResponse) + + var total int64 + for _, row := range est.Outpoints { + key := refreshQuoteKey{ + amountSat: row.AmountSat, + remainingBlocks: row.RemainingBlocks, + } + + quote, ok := quotes[key] + if !ok { + var err error + quote, err = r.server.quoteOperatorFeeBreakdown( + ctx, key.amountSat, false, /* isBoarding */ + key.remainingBlocks, + ) + if err != nil { + r.server.log.WarnS(ctx, "Refresh fee "+ + "estimate: operator quote "+ + "unavailable", err) + + // Match the EstimateFee proxy's + // sanitization stance: log the upstream + // detail, never echo operator-internal + // error text to the caller. + return degradeRefreshEstimate( + est, + "operator fee estimate unavailable", + ) + } + + if !refreshQuoteSane(quote) { + r.server.log.WarnS(ctx, "Refresh fee "+ + "estimate: operator returned "+ + "nonsense fee", + fmt.Errorf("total_fee_sat=%d for %s", + quote.TotalFeeSat, row.Outpoint)) + + return degradeRefreshEstimate( + est, "operator fee estimate invalid", + ) + } + + quotes[key] = quote + } + + // Each vetted quote is bounded by MaxSatoshi, but a large + // selection could still wrap the sum; a total beyond the + // money supply is as nonsensical as a negative quote. + total += quote.TotalFeeSat + if total > int64(btcutil.MaxSatoshi) { + r.server.log.WarnS(ctx, "Refresh fee estimate: "+ + "selection total overflows", + fmt.Errorf("total=%d", total)) + + return degradeRefreshEstimate( + est, "operator fee estimate invalid", + ) + } + } + + // Third pass: apply the fully vetted quotes to the rows. + for _, row := range est.Outpoints { + quote := quotes[refreshQuoteKey{ + amountSat: row.AmountSat, + remainingBlocks: row.RemainingBlocks, + }] + + row.LiquidityFeeSat = quote.LiquidityFeeSat + row.OnchainShareSat = quote.OnchainShareSat + row.MarginSat = quote.MarginSat + row.TotalFeeSat = quote.TotalFeeSat + row.BelowDustWarning = quote.BelowDustWarning + } + + if est.FreeRefreshEligible { + est.EstimatedTotalFeeSat = proto.Int64(0) + } else { + est.EstimatedTotalFeeSat = proto.Int64(total) + } + + return est +} diff --git a/waved/rpc_refresh_estimate_test.go b/waved/rpc_refresh_estimate_test.go new file mode 100644 index 000000000..cc753dace --- /dev/null +++ b/waved/rpc_refresh_estimate_test.go @@ -0,0 +1,1172 @@ +package waved + +import ( + "context" + "database/sql" + "fmt" + "sync" + "testing" + "time" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil/v2" + "github.com/btcsuite/btcd/chainhash/v2" + "github.com/btcsuite/btcd/wire/v2" + "github.com/btcsuite/btclog/v2" + "github.com/lightninglabs/wavelength/arkrpc" + "github.com/lightninglabs/wavelength/baselib/actor" + "github.com/lightninglabs/wavelength/chainsource" + "github.com/lightninglabs/wavelength/db" + "github.com/lightninglabs/wavelength/lib/arkscript" + "github.com/lightninglabs/wavelength/lib/types" + "github.com/lightninglabs/wavelength/vtxo" + "github.com/lightninglabs/wavelength/wallet" + "github.com/lightninglabs/wavelength/waverpc" + "github.com/lightningnetwork/lnd/clock" + fn "github.com/lightningnetwork/lnd/fn/v2" + "github.com/lightningnetwork/lnd/keychain" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// scaledEstimateFn prices a quote as a pure function of the request so +// tests covering several distinct (amount, remaining-blocks) pairs can +// assert each row was matched to its own quote: liquidity scales with +// amount, the other components are fixed. +func scaledEstimateFn( + req *arkrpc.EstimateFeeRequest) *arkrpc.EstimateFeeResponse { + + liquidity := req.AmountSat / 1_000 + + return &arkrpc.EstimateFeeResponse{ + LiquidityFeeSat: liquidity, + OnchainShareSat: 25, + MarginSat: 100, + TotalFeeSat: liquidity + 125, + } +} + +// newRefreshEstimateServer wires the minimal daemon surface the refresh +// dry-run preview needs — SQL VTXO store, chain tip, operator +// connection — and deliberately nothing wallet-related: the preview +// must work without a ready wallet. +func newRefreshEstimateServer(t *testing.T, svc *fakeArkService, + height int32) (*RPCServer, *db.VTXOPersistenceStore) { + + t.Helper() + + sqlDB := db.NewTestDB(t) + roundDB := db.NewTransactionExecutor( + sqlDB.BaseDB, + func(tx *sql.Tx) db.RoundStore { + return sqlDB.WithTx(tx) + }, + btclog.Disabled, + ) + vtxoStore := db.NewVTXOPersistenceStore( + roundDB, clock.NewDefaultClock(), + ) + + s := &Server{ + serverConn: newBufconnClient(t, svc), + log: btclog.Disabled, + chainBackend: &heightOnlyChainBackend{ + height: height, + }, + vtxoStore: vtxoStore, + } + + return &RPCServer{server: s}, vtxoStore +} + +// newRefreshEstimateVTXO builds a persistable live VTXO descriptor with +// the amount and batch expiry the estimate under test needs. +func newRefreshEstimateVTXO(t *testing.T, hashByte byte, amount btcutil.Amount, + batchExpiry int32) *vtxo.Descriptor { + + t.Helper() + + clientKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + operatorKey, err := btcec.NewPrivateKey() + require.NoError(t, err) + + const exitDelay uint32 = 10 + + policyTemplate, err := arkscript.EncodeStandardVTXOTemplate( + clientKey.PubKey(), operatorKey.PubKey(), exitDelay, + ) + require.NoError(t, err) + + template, err := arkscript.DecodePolicyTemplate(policyTemplate) + require.NoError(t, err) + + pkScript, err := template.PkScript() + require.NoError(t, err) + + tapScript, err := arkscript.VTXOTapScript( + clientKey.PubKey(), operatorKey.PubKey(), exitDelay, + ) + require.NoError(t, err) + + var outpointHash chainhash.Hash + outpointHash[0] = hashByte + + var commitmentTxID chainhash.Hash + commitmentTxID[0] = hashByte + commitmentTxID[1] = 0xc0 + + return &vtxo.Descriptor{ + Outpoint: wire.OutPoint{ + Hash: outpointHash, + Index: uint32(hashByte), + }, + Amount: amount, + PolicyTemplate: policyTemplate, + PkScript: pkScript, + ClientKey: keychain.KeyDescriptor{ + PubKey: clientKey.PubKey(), + KeyLocator: keychain.KeyLocator{ + Index: uint32(hashByte), + }, + }, + OperatorKey: operatorKey.PubKey(), + TapScript: tapScript, + RoundID: fmt.Sprintf("refresh-est-round-%x", hashByte), + CommitmentTxID: commitmentTxID, + BatchExpiry: batchExpiry, + RelativeExpiry: exitDelay, + CreatedHeight: 1, + Status: vtxo.VTXOStatusLive, + } +} + +// outpointStr renders a descriptor's outpoint in the "txid:index" wire +// format the RPC uses. +func outpointStr(desc *vtxo.Descriptor) string { + return fmt.Sprintf("%s:%d", desc.Outpoint.Hash, desc.Outpoint.Index) +} + +// TestRefreshDryRunEstimateAllPath covers the selection=all preview: the +// estimate must price every live VTXO from the daemon's own store view +// (no caller-supplied amounts or lifetimes) — and must do so without any +// wallet wiring, since the dry-run path short-circuits before the +// wallet-ready gate. +func TestRefreshDryRunEstimateAllPath(t *testing.T) { + t.Parallel() + + const height = int32(900) + + svc := &fakeArkService{responseFn: scaledEstimateFn} + r, vtxoStore := newRefreshEstimateServer(t, svc, height) + + descA := newRefreshEstimateVTXO(t, 0x01, 100_000, 1_000) + descB := newRefreshEstimateVTXO(t, 0x02, 250_000, 1_400) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), descA)) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), descB)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + require.Equal(t, "preview", resp.Status) + require.ElementsMatch( + t, []string{outpointStr(descA), outpointStr(descB)}, + resp.QueuedOutpoints, + ) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.Empty(t, est.EstimateError) + require.False( + t, est.FreeRefreshEligible, + "no operator terms cached, so no free window applies", + ) + require.Len(t, est.Outpoints, 2) + + rows := make(map[string]*waverpc.OutpointFeeEstimate) + for _, row := range est.Outpoints { + rows[row.Outpoint] = row + } + + rowA := rows[outpointStr(descA)] + require.NotNil(t, rowA) + require.Equal(t, int64(100_000), rowA.AmountSat) + require.Equal( + t, uint32(100), rowA.RemainingBlocks, + "remaining = batch expiry 1000 - height 900", + ) + require.Equal(t, int64(100), rowA.LiquidityFeeSat) + require.Equal(t, int64(25), rowA.OnchainShareSat) + require.Equal(t, int64(100), rowA.MarginSat) + require.Equal(t, int64(225), rowA.TotalFeeSat) + require.False(t, rowA.InFreeRefreshWindow) + + rowB := rows[outpointStr(descB)] + require.NotNil(t, rowB) + require.Equal(t, int64(250_000), rowB.AmountSat) + require.Equal(t, uint32(500), rowB.RemainingBlocks) + require.Equal(t, int64(375), rowB.TotalFeeSat) + + require.Equal( + t, int64(600), est.GetEstimatedTotalFeeSat(), + "total is the sum of the per-outpoint quotes", + ) + + // Distinct (amount, remaining) pairs require distinct quotes. + require.Equal(t, 2, svc.estimateFeeCalls) +} + +// TestRefreshDryRunEstimateExplicitOutpointLookup covers the explicit +// selection path: the daemon must resolve amount and remaining lifetime +// from its store — the manual amount / remaining-blocks inputs of the +// standalone fees estimate command must not be needed — and must ask +// the operator for a refresh (not boarding) quote. +func TestRefreshDryRunEstimateExplicitOutpointLookup(t *testing.T) { + t.Parallel() + + const height = int32(700) + + svc := &fakeArkService{responseFn: scaledEstimateFn} + r, vtxoStore := newRefreshEstimateServer(t, svc, height) + + desc := newRefreshEstimateVTXO(t, 0x03, 42_000, 1_000) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), desc)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_Outpoints{ + Outpoints: &waverpc.OutpointSelection{ + Outpoints: []string{ + outpointStr(desc), + }, + }, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + require.Equal(t, "preview", resp.Status) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.Len(t, est.Outpoints, 1) + require.Equal(t, int64(42_000), est.Outpoints[0].AmountSat) + require.Equal(t, uint32(300), est.Outpoints[0].RemainingBlocks) + + require.NotNil(t, svc.lastRequest) + require.False( + t, svc.lastRequest.IsBoarding, + "refresh quotes must not be priced as boarding", + ) + require.Equal(t, int64(42_000), svc.lastRequest.AmountSat) + require.Equal(t, uint32(300), svc.lastRequest.RemainingBlocks) +} + +// TestRefreshDryRunEstimateUnknownOutpoint verifies an explicit outpoint +// the store does not know is a caller mistake (InvalidArgument naming +// the outpoint), not a silent echo: the previous behavior echoed any +// well-formed outpoint, which let a typo'd preview read as valid. +func TestRefreshDryRunEstimateUnknownOutpoint(t *testing.T) { + t.Parallel() + + svc := &fakeArkService{responseFn: scaledEstimateFn} + r, _ := newRefreshEstimateServer(t, svc, 700) + + missing := newRefreshEstimateVTXO(t, 0x04, 10_000, 1_000) + + _, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_Outpoints{ + Outpoints: &waverpc.OutpointSelection{ + Outpoints: []string{ + outpointStr(missing), + }, + }, + }, + DryRun: true, + }, + ) + require.Error(t, err) + require.Equal(t, codes.InvalidArgument, status.Code(err)) + require.Contains(t, err.Error(), outpointStr(missing)) +} + +// TestRefreshDryRunEstimateDedupesQuotes verifies VTXOs sharing an +// (amount, remaining-blocks) pair share one operator round-trip: a +// wallet-wide dry run over a round's fan-out must not turn into one +// RPC per VTXO. +func TestRefreshDryRunEstimateDedupesQuotes(t *testing.T) { + t.Parallel() + + const height = int32(500) + + svc := &fakeArkService{responseFn: scaledEstimateFn} + r, vtxoStore := newRefreshEstimateServer(t, svc, height) + + // Two identical (amount, expiry) pairs plus one distinct. + descA := newRefreshEstimateVTXO(t, 0x05, 50_000, 1_000) + descB := newRefreshEstimateVTXO(t, 0x06, 50_000, 1_000) + descC := newRefreshEstimateVTXO(t, 0x07, 80_000, 1_000) + for _, desc := range []*vtxo.Descriptor{descA, descB, descC} { + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), desc)) + } + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.Len(t, est.Outpoints, 3) + require.Equal( + t, 2, svc.estimateFeeCalls, + "two distinct (amount, remaining) pairs, two quotes", + ) + + // The deduped pair still carries per-row components. + rows := make(map[string]*waverpc.OutpointFeeEstimate) + for _, row := range est.Outpoints { + rows[row.Outpoint] = row + } + require.Equal( + t, rows[outpointStr(descA)].TotalFeeSat, + rows[outpointStr(descB)].TotalFeeSat, + ) + require.Equal(t, int64(175), rows[outpointStr(descA)].TotalFeeSat) + require.Equal(t, int64(205), rows[outpointStr(descC)].TotalFeeSat) +} + +// TestRefreshDryRunEstimateFreeWindow covers the waiver-eligible +// selection: every VTXO inside the operator's advertised free-refresh +// window zeroes the selection total while the per-outpoint rows keep +// the ordinary paid quote, so the caller sees what the waiver saves. +func TestRefreshDryRunEstimateFreeWindow(t *testing.T) { + t.Parallel() + + const height = int32(950) + + svc := &fakeArkService{responseFn: scaledEstimateFn} + r, vtxoStore := newRefreshEstimateServer(t, svc, height) + r.server.storeOperatorTerms(&types.OperatorTerms{ + FreeRefreshWindowBlocks: 100, + }) + + // Remaining lifetimes 50 and 90: both inside the 100-block + // window. + descA := newRefreshEstimateVTXO(t, 0x08, 100_000, 1_000) + descB := newRefreshEstimateVTXO(t, 0x09, 200_000, 1_040) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), descA)) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), descB)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.True(t, est.FreeRefreshEligible) + require.NotNil( + t, est.EstimatedTotalFeeSat, + "the waiver's zero total is explicit, not absent", + ) + require.Zero( + t, est.GetEstimatedTotalFeeSat(), + "waiver-eligible selection previews as free", + ) + + for _, row := range est.Outpoints { + require.True(t, row.InFreeRefreshWindow, row.Outpoint) + require.Positive( + t, row.TotalFeeSat, "rows keep the ordinary paid "+ + "quote so the caller sees what the waiver "+ + "saves", + ) + } +} + +// TestRefreshDryRunEstimateWindowBrokenByOneOutpoint verifies the +// waiver's all-or-nothing rule: one out-of-window VTXO in the selection +// prices the whole refresh as paid, mirroring the operator's seal-time +// isFreeRefresh predicate. +func TestRefreshDryRunEstimateWindowBrokenByOneOutpoint(t *testing.T) { + t.Parallel() + + const height = int32(950) + + svc := &fakeArkService{responseFn: scaledEstimateFn} + r, vtxoStore := newRefreshEstimateServer(t, svc, height) + r.server.storeOperatorTerms(&types.OperatorTerms{ + FreeRefreshWindowBlocks: 100, + }) + + // Remaining lifetimes 50 (inside) and 250 (outside). + inside := newRefreshEstimateVTXO(t, 0x0a, 100_000, 1_000) + outside := newRefreshEstimateVTXO(t, 0x0b, 200_000, 1_200) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), inside)) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), outside)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.False(t, est.FreeRefreshEligible) + require.Positive(t, est.GetEstimatedTotalFeeSat()) + + rows := make(map[string]*waverpc.OutpointFeeEstimate) + for _, row := range est.Outpoints { + rows[row.Outpoint] = row + } + require.True(t, rows[outpointStr(inside)].InFreeRefreshWindow) + require.False(t, rows[outpointStr(outside)].InFreeRefreshWindow) +} + +// TestRefreshDryRunEstimateOperatorDown verifies the degraded mode the +// issue requires: with the operator unreachable the preview still +// returns (dry_run stays usable as a validity probe), the locally +// known facts stay populated, and estimate_error tells the caller the +// fee numbers are absent rather than zero. +func TestRefreshDryRunEstimateOperatorDown(t *testing.T) { + t.Parallel() + + const height = int32(900) + + svc := &fakeArkService{ + estimateFeeErr: status.Error( + codes.Unavailable, "operator down", + ), + } + r, vtxoStore := newRefreshEstimateServer(t, svc, height) + + desc := newRefreshEstimateVTXO(t, 0x0c, 100_000, 1_000) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), desc)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err, "estimate failure must not fail dry_run") + require.Equal(t, "preview", resp.Status) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.NotEmpty(t, est.EstimateError) + require.NotContains( + t, est.EstimateError, "operator down", + "upstream error text must not leak to the caller", + ) + require.Nil( + t, est.EstimatedTotalFeeSat, "a degraded paid estimate "+ + "leaves the total absent so it can not be misread "+ + "as free", + ) + require.Len(t, est.Outpoints, 1) + + // Locally known facts survive the degradation. + require.Equal(t, int64(100_000), est.Outpoints[0].AmountSat) + require.Equal(t, uint32(100), est.Outpoints[0].RemainingBlocks) + require.Zero(t, est.Outpoints[0].TotalFeeSat) +} + +// TestRefreshDryRunEstimateOperatorDownFreeWindow verifies the +// locally computed waiver survives operator unavailability: a +// waiver-eligible selection still previews as free (with +// estimate_error set for the missing per-outpoint quotes). +func TestRefreshDryRunEstimateOperatorDownFreeWindow(t *testing.T) { + t.Parallel() + + const height = int32(950) + + svc := &fakeArkService{ + estimateFeeErr: status.Error( + codes.Unavailable, "operator down", + ), + } + r, vtxoStore := newRefreshEstimateServer(t, svc, height) + r.server.storeOperatorTerms(&types.OperatorTerms{ + FreeRefreshWindowBlocks: 100, + }) + + desc := newRefreshEstimateVTXO(t, 0x0d, 100_000, 1_000) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), desc)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.True(t, est.FreeRefreshEligible) + require.NotNil(t, est.EstimatedTotalFeeSat) + require.Zero(t, est.GetEstimatedTotalFeeSat()) + require.NotEmpty(t, est.EstimateError) +} + +// TestRefreshDryRunEstimateExpiredClampsRemaining verifies an already +// expired VTXO quotes at a clamped remaining lifetime of 1 instead of +// 0: the operator treats zero as "use the full sweep-delay lifetime", +// which would massively over-quote. An expired VTXO is also never +// waiver-eligible. +func TestRefreshDryRunEstimateExpiredClampsRemaining(t *testing.T) { + t.Parallel() + + const height = int32(2_000) + + svc := &fakeArkService{responseFn: scaledEstimateFn} + r, vtxoStore := newRefreshEstimateServer(t, svc, height) + r.server.storeOperatorTerms(&types.OperatorTerms{ + FreeRefreshWindowBlocks: 100, + }) + + // Batch expiry far behind the tip. + desc := newRefreshEstimateVTXO(t, 0x0e, 100_000, 1_000) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), desc)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.Len(t, est.Outpoints, 1) + require.Equal(t, uint32(1), est.Outpoints[0].RemainingBlocks) + require.False(t, est.Outpoints[0].InFreeRefreshWindow) + require.False(t, est.FreeRefreshEligible) + + require.NotNil(t, svc.lastRequest) + require.Equal(t, uint32(1), svc.lastRequest.RemainingBlocks) +} + +// TestRefreshDryRunEstimateRejectsNonsenseQuote verifies a negative +// operator quote is treated like an unreachable operator instead of +// being summed into a misleading total. +func TestRefreshDryRunEstimateRejectsNonsenseQuote(t *testing.T) { + t.Parallel() + + svc := &fakeArkService{ + response: &arkrpc.EstimateFeeResponse{ + TotalFeeSat: -1, + }, + } + r, vtxoStore := newRefreshEstimateServer(t, svc, 900) + + desc := newRefreshEstimateVTXO(t, 0x0f, 100_000, 1_000) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), desc)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.NotEmpty(t, est.EstimateError) + require.Nil(t, est.EstimatedTotalFeeSat) + require.Len(t, est.Outpoints, 1) + require.Zero(t, est.Outpoints[0].TotalFeeSat) +} + +// TestRefreshDryRunEmptyAllReturnsPreview pins the reordered empty +// case: selection=all over an empty store previews as "preview" (it +// used to report "queued" because the empty-target early return ran +// before the dry-run branch) and attaches no vacuous fee estimate a +// caller could misread as "free". +func TestRefreshDryRunEmptyAllReturnsPreview(t *testing.T) { + t.Parallel() + + svc := &fakeArkService{responseFn: scaledEstimateFn} + r, _ := newRefreshEstimateServer(t, svc, 900) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + require.Equal(t, "preview", resp.Status) + require.Empty(t, resp.QueuedOutpoints) + require.Nil(t, resp.FeeEstimate) + require.Zero(t, svc.estimateFeeCalls) +} + +// TestRefreshVTXOsSelectionRequiredBeforeWalletGate verifies +// pure-argument validation outranks the wallet-ready gate, matching +// the LeaveVTXOs ordering rule: a request-shape bug must surface as +// InvalidArgument regardless of wallet state. +func TestRefreshVTXOsSelectionRequiredBeforeWalletGate(t *testing.T) { + t.Parallel() + + r := newTestRPCServer() + + _, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{}, + ) + require.Error(t, err) + require.Equal(t, codes.InvalidArgument, status.Code(err)) +} + +// TestRefreshVTXOsRealPathStillRequiresWallet pins that only the +// dry-run preview moved ahead of the wallet-ready gate: a real refresh +// on an unready wallet still fails before any queuing. +func TestRefreshVTXOsRealPathStillRequiresWallet(t *testing.T) { + t.Parallel() + + r := newTestRPCServer() + + _, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_Outpoints{ + Outpoints: &waverpc.OutpointSelection{ + Outpoints: []string{ + "deadbeefdeadbeefdeadbeef" + + "deadbeefdeadbeefdead" + + "beefdeadbeef00000000" + + ":0", + }, + }, + }, + }, + ) + require.Error(t, err) + require.NotEqual( + t, codes.InvalidArgument, status.Code(err), + "a well-formed real refresh must fail on wallet readiness, "+ + "not argument validation", + ) +} + +// errChainBackend is a chain backend whose BestBlock always fails, for +// exercising the chain-height-unavailable degrade path. +type errChainBackend struct { + chainsource.ChainBackend +} + +func (b *errChainBackend) BestBlock(context.Context) (int32, chainhash.Hash, + error) { + + return 0, chainhash.Hash{}, fmt.Errorf("backend still syncing") +} + +// TestRefreshDryRunEstimateChainHeightUnavailable verifies the first +// degrade mode: a failing chain backend yields amount-only rows, an +// estimate error, no operator calls, an absent total — and a preview +// that still succeeds, keeping dry_run usable as a validity probe while +// the backend syncs. +func TestRefreshDryRunEstimateChainHeightUnavailable(t *testing.T) { + t.Parallel() + + svc := &fakeArkService{responseFn: scaledEstimateFn} + r, vtxoStore := newRefreshEstimateServer(t, svc, 0) + r.server.chainBackend = &errChainBackend{} + + desc := newRefreshEstimateVTXO(t, 0x10, 100_000, 1_000) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), desc)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err, "chain failure must not fail dry_run") + require.Equal(t, "preview", resp.Status) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.Contains(t, est.EstimateError, "chain height") + require.Nil(t, est.EstimatedTotalFeeSat) + require.False(t, est.FreeRefreshEligible) + require.Len(t, est.Outpoints, 1) + require.Equal(t, int64(100_000), est.Outpoints[0].AmountSat) + require.Zero(t, est.Outpoints[0].RemainingBlocks) + require.Zero( + t, svc.estimateFeeCalls, + "no operator quote without a chain tip", + ) +} + +// TestRefreshDryRunEstimatePartialQuoteFailure verifies the +// all-or-nothing degrade when the operator fails partway through a +// multi-quote selection: no row may keep components from the quotes +// that succeeded before the failure, because estimate_error tells the +// caller the components are absent. +func TestRefreshDryRunEstimatePartialQuoteFailure(t *testing.T) { + t.Parallel() + + const height = int32(500) + + svc := &fakeArkService{ + responseFn: scaledEstimateFn, + errFn: func(req *arkrpc.EstimateFeeRequest) error { + if req.AmountSat == 80_000 { + return status.Error( + codes.Unavailable, "quote two down", + ) + } + + return nil + }, + } + r, vtxoStore := newRefreshEstimateServer(t, svc, height) + + descA := newRefreshEstimateVTXO(t, 0x11, 50_000, 1_000) + descB := newRefreshEstimateVTXO(t, 0x12, 80_000, 1_000) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), descA)) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), descB)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.NotEmpty(t, est.EstimateError) + require.Nil(t, est.EstimatedTotalFeeSat) + + // ListLiveVTXOs enumeration order is storage-dependent, so the + // failing quote may be attempted first (one call) or second + // (two calls); either way the failure must abort further + // quoting. + require.GreaterOrEqual(t, svc.estimateFeeCalls, 1) + require.LessOrEqual(t, svc.estimateFeeCalls, 2) + + require.Len(t, est.Outpoints, 2) + for _, row := range est.Outpoints { + require.Zero(t, row.LiquidityFeeSat, row.Outpoint) + require.Zero(t, row.OnchainShareSat, row.Outpoint) + require.Zero(t, row.MarginSat, row.Outpoint) + require.Zero(t, row.TotalFeeSat, row.Outpoint) + } +} + +// TestRefreshDryRunEstimateRejectsNegativeComponent verifies the quote +// sanity check covers the component fields, not just the total: a +// negative component would break the documented invariant that a row's +// components sum to its total. +func TestRefreshDryRunEstimateRejectsNegativeComponent(t *testing.T) { + t.Parallel() + + svc := &fakeArkService{ + response: &arkrpc.EstimateFeeResponse{ + LiquidityFeeSat: -50, + MarginSat: 100, + TotalFeeSat: 50, + }, + } + r, vtxoStore := newRefreshEstimateServer(t, svc, 900) + + desc := newRefreshEstimateVTXO(t, 0x13, 100_000, 1_000) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), desc)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.Contains(t, est.EstimateError, "invalid") + require.Nil(t, est.EstimatedTotalFeeSat) + require.Len(t, est.Outpoints, 1) + require.Zero(t, est.Outpoints[0].LiquidityFeeSat) + require.Zero(t, est.Outpoints[0].TotalFeeSat) +} + +// TestRefreshDryRunEstimateRejectsMismatchedComponents verifies the +// quote sanity check enforces the reconciliation the proto documents: +// components that don't sum to total_fee_sat degrade the estimate +// instead of echoing an itemization that contradicts its own total. +func TestRefreshDryRunEstimateRejectsMismatchedComponents(t *testing.T) { + t.Parallel() + + svc := &fakeArkService{ + response: &arkrpc.EstimateFeeResponse{ + LiquidityFeeSat: 50, + OnchainShareSat: 25, + MarginSat: 100, + TotalFeeSat: 999, + }, + } + r, vtxoStore := newRefreshEstimateServer(t, svc, 900) + + desc := newRefreshEstimateVTXO(t, 0x14, 100_000, 1_000) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), desc)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.Contains(t, est.EstimateError, "invalid") + require.Nil(t, est.EstimatedTotalFeeSat) + require.Len(t, est.Outpoints, 1) + require.Zero(t, est.Outpoints[0].TotalFeeSat) +} + +// TestRefreshDryRunEstimateWindowBoundary pins the inclusive window +// comparison against the operator's IsFreeRefreshWindow semantics: a +// VTXO at exactly the advertised window is waiver-eligible, one block +// beyond is not. +func TestRefreshDryRunEstimateWindowBoundary(t *testing.T) { + t.Parallel() + + const height = int32(900) + + cases := []struct { + name string + remaining int32 + eligible bool + }{ + { + name: "exactly at window", + remaining: 100, + eligible: true, + }, + { + name: "one beyond window", + remaining: 101, + eligible: false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + svc := &fakeArkService{ + responseFn: scaledEstimateFn, + } + r, vtxoStore := newRefreshEstimateServer( + t, svc, height, + ) + r.server.storeOperatorTerms(&types.OperatorTerms{ + FreeRefreshWindowBlocks: 100, + }) + + desc := newRefreshEstimateVTXO( + t, 0x14, 100_000, height+tc.remaining, + ) + require.NoError( + t, + vtxoStore.SaveVTXO( + t.Context(), desc, + ), + ) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc. + RefreshVTXOsRequest_All{ + All: true, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.Len(t, est.Outpoints, 1) + require.Equal( + t, tc.eligible, + est.Outpoints[0].InFreeRefreshWindow, + ) + require.Equal( + t, tc.eligible, est.FreeRefreshEligible, + ) + }) + } +} + +// TestRefreshDryRunEstimateDuplicateOutpointCollapsed verifies a +// repeated explicit outpoint is collapsed before resolution: one row, +// one echo, one quote — never a doubled total for a single VTXO. +func TestRefreshDryRunEstimateDuplicateOutpointCollapsed(t *testing.T) { + t.Parallel() + + const height = int32(700) + + svc := &fakeArkService{responseFn: scaledEstimateFn} + r, vtxoStore := newRefreshEstimateServer(t, svc, height) + + desc := newRefreshEstimateVTXO(t, 0x15, 50_000, 1_000) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), desc)) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_Outpoints{ + Outpoints: &waverpc.OutpointSelection{ + Outpoints: []string{ + outpointStr(desc), + outpointStr(desc), + }, + }, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + require.Equal(t, []string{outpointStr(desc)}, resp.QueuedOutpoints) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.Len(t, est.Outpoints, 1) + require.Equal( + t, int64(175), est.GetEstimatedTotalFeeSat(), + "one VTXO, one quote, never a doubled total", + ) + require.Equal(t, 1, svc.estimateFeeCalls) +} + +// TestRefreshDryRunEstimateRejectsNonLiveOutpoint verifies an explicit +// outpoint in a non-live state is a caller mistake, mirroring the +// --all branch's LiveState filter: the real refresh can never execute +// it, so a preview that echoed and priced it would overpromise +// validity right where the CLI consent prompt trusts it. +func TestRefreshDryRunEstimateRejectsNonLiveOutpoint(t *testing.T) { + t.Parallel() + + svc := &fakeArkService{responseFn: scaledEstimateFn} + r, vtxoStore := newRefreshEstimateServer(t, svc, 700) + + desc := newRefreshEstimateVTXO(t, 0x16, 50_000, 1_000) + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), desc)) + + // SaveVTXO persists rows as live; move the fixture into its + // non-live state explicitly. + require.NoError( + t, + vtxoStore.UpdateVTXOStatus( + t.Context(), desc.Outpoint, vtxo.VTXOStatusSpent, + ), + ) + + _, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_Outpoints{ + Outpoints: &waverpc.OutpointSelection{ + Outpoints: []string{ + outpointStr(desc), + }, + }, + }, + DryRun: true, + }, + ) + require.Error(t, err) + require.Equal(t, codes.InvalidArgument, status.Code(err)) + require.Contains(t, err.Error(), "not refreshable") + require.Zero(t, svc.estimateFeeCalls) +} + +// TestRefreshDryRunNilStoreEchoesExplicit pins the store-less degrade: +// the preview echo for explicit outpoints survives (as before this +// feature) and the estimate degrades explicitly instead of silently +// vanishing. +func TestRefreshDryRunNilStoreEchoesExplicit(t *testing.T) { + t.Parallel() + + r := newTestRPCServer() + + op := "1111111111111111111111111111111111111111111111111111111111" + + "111111:0" + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_Outpoints{ + Outpoints: &waverpc.OutpointSelection{ + Outpoints: []string{op}, + }, + }, + DryRun: true, + }, + ) + require.NoError(t, err) + require.Equal(t, "preview", resp.Status) + require.Len(t, resp.QueuedOutpoints, 1) + + est := resp.FeeEstimate + require.NotNil(t, est) + require.Contains(t, est.EstimateError, "store unavailable") + require.Nil(t, est.EstimatedTotalFeeSat) +} + +// refreshTestWallet is a minimal wallet actor recording the refresh +// requests it receives, for asserting what the real (non-dry-run) +// path dispatches. +type refreshTestWallet struct { + mu sync.Mutex + reqs []*wallet.RefreshVTXOsRequest +} + +func (w *refreshTestWallet) Receive(_ context.Context, + msg wallet.WalletMsg) fn.Result[wallet.WalletResp] { + + w.mu.Lock() + defer w.mu.Unlock() + + switch msg := msg.(type) { + case *wallet.RefreshVTXOsRequest: + reqCopy := *msg + w.reqs = append(w.reqs, &reqCopy) + + return fn.Ok[wallet.WalletResp]( + &wallet.RefreshVTXOsResponse{ + RefreshingCount: len(msg.TargetOutpoints), + }, + ) + + default: + return fn.Err[wallet.WalletResp]( + fmt.Errorf("unexpected wallet msg %T", msg), + ) + } +} + +// TestRefreshVTXOsAllQueuesLiveTargets pins the real (non-dry-run) +// --all path after the handler restructure: live VTXOs are expanded +// into explicit wallet targets, non-live rows are filtered, and the +// response echoes what the wallet actually queued. +func TestRefreshVTXOsAllQueuesLiveTargets(t *testing.T) { + t.Parallel() + + svc := &fakeArkService{responseFn: scaledEstimateFn} + r, vtxoStore := newRefreshEstimateServer(t, svc, 900) + + live1 := newRefreshEstimateVTXO(t, 0x17, 40_000, 1_000) + live2 := newRefreshEstimateVTXO(t, 0x18, 60_000, 1_000) + pending := newRefreshEstimateVTXO(t, 0x19, 80_000, 1_000) + for _, desc := range []*vtxo.Descriptor{live1, live2, pending} { + require.NoError(t, vtxoStore.SaveVTXO(t.Context(), desc)) + } + + // SaveVTXO persists rows as live; move the in-flight fixture + // into PendingForfeit explicitly so the filter has something + // to exclude. + require.NoError( + t, + vtxoStore.UpdateVTXOStatus( + t.Context(), pending.Outpoint, + vtxo.VTXOStatusPendingForfeit, + ), + ) + + system := actor.NewActorSystem() + t.Cleanup(func() { + shutdownCtx, cancel := context.WithTimeout( + context.Background(), 5*time.Second, + ) + defer cancel() + + require.NoError(t, system.Shutdown(shutdownCtx)) + }) + + testWallet := &refreshTestWallet{} + walletKey := actor.NewServiceKey[ + wallet.WalletMsg, wallet.WalletResp, + ]( + "refresh-all-test-wallet", + ) + walletRef := walletKey.Spawn( + system, "refresh-all-test-wallet", testWallet, + ) + + walletReady := make(chan struct{}) + close(walletReady) + r.server.walletReady = walletReady + r.server.walletRef = fn.Some(walletRef) + + resp, err := r.RefreshVTXOs( + t.Context(), &waverpc.RefreshVTXOsRequest{ + Selection: &waverpc.RefreshVTXOsRequest_All{ + All: true, + }, + }, + ) + require.NoError(t, err) + require.Equal(t, "queued", resp.Status) + require.ElementsMatch( + t, []string{outpointStr(live1), outpointStr(live2)}, + resp.QueuedOutpoints, + ) + require.Nil(t, resp.FeeEstimate, + "the real path attaches no estimate") + + require.Len(t, testWallet.reqs, 1) + require.ElementsMatch( + t, []wire.OutPoint{live1.Outpoint, live2.Outpoint}, + testWallet.reqs[0].TargetOutpoints, + "only live VTXOs reach the wallet actor", + ) + require.True(t, testWallet.reqs[0].ForceRefresh) +} diff --git a/waved/rpc_server.go b/waved/rpc_server.go index b070b8a60..e15dbd472 100644 --- a/waved/rpc_server.go +++ b/waved/rpc_server.go @@ -1533,6 +1533,68 @@ func (r *RPCServer) RefreshVTXOs(ctx context.Context, req *waverpc.RefreshVTXOsRequest) (*waverpc.RefreshVTXOsResponse, error) { + // Pure-argument validation (selection shape / outpoint syntax) + // runs before the wallet-ready gate so a malformed request + // surfaces InvalidArgument regardless of wallet state, matching + // the LeaveVTXOs ordering rule: client bugs should always look + // like client bugs. + var ( + explicit []wire.OutPoint + refreshAll bool + ) + switch sel := req.Selection.(type) { + case *waverpc.RefreshVTXOsRequest_All: + if !sel.All { + return nil, status.Errorf(codes.InvalidArgument, + "refresh selection 'all' must be true when set") + } + refreshAll = true + + case *waverpc.RefreshVTXOsRequest_Outpoints: + if sel.Outpoints == nil || + len(sel.Outpoints.Outpoints) == 0 { + return nil, status.Errorf(codes.InvalidArgument, + "outpoints list is empty") + } + + // Dedupe while preserving order so both the preview and + // the real path see one canonical selection: a repeated + // outpoint would double-count in the fee estimate and + // register a doomed duplicate forfeit pair in the wallet. + seen := make( + map[wire.OutPoint]struct{}, + len(sel.Outpoints.Outpoints), + ) + for _, opStr := range sel.Outpoints.Outpoints { + op, err := parseOutpointString(opStr) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, + "invalid outpoint %q: %v", opStr, err) + } + + if _, dup := seen[op]; dup { + continue + } + seen[op] = struct{}{} + + explicit = append(explicit, op) + } + + default: + return nil, status.Errorf(codes.InvalidArgument, "selection "+ + "is required (outpoints or all)") + } + + // The dry-run preview short-circuits before the wallet-ready + // gate (parity with the LeaveVTXOs dry-run ordering): resolving + // the selection and pricing it only needs the SQL VTXO view, the + // chain tip, and the operator connection, so callers can preview + // a refresh — including its advisory fee — without an unlocked + // wallet. + if req.DryRun { + return r.refreshDryRunPreview(ctx, explicit, refreshAll) + } + if err := r.requireWalletReady(); err != nil { return nil, err } @@ -1554,15 +1616,9 @@ func (r *RPCServer) RefreshVTXOs(ctx context.Context, // synthesised `queued_outpoints` from a separate ListLiveVTXOs // call below, which made the caller believe the refresh // succeeded. - var targets []wire.OutPoint - - switch sel := req.Selection.(type) { - case *waverpc.RefreshVTXOsRequest_All: - if !sel.All { - return nil, status.Errorf(codes.InvalidArgument, - "refresh selection 'all' must be true when set") - } + targets := explicit + if refreshAll { if r.server.vtxoStore == nil { return nil, status.Errorf(codes.Internal, "VTXO "+ "store not available for refresh --all") @@ -1589,27 +1645,6 @@ func (r *RPCServer) RefreshVTXOs(ctx context.Context, } targets = append(targets, v.Outpoint) } - - case *waverpc.RefreshVTXOsRequest_Outpoints: - if sel.Outpoints == nil || - len(sel.Outpoints.Outpoints) == 0 { - return nil, status.Errorf(codes.InvalidArgument, - "outpoints list is empty") - } - - for _, opStr := range sel.Outpoints.Outpoints { - op, err := parseOutpointString(opStr) - if err != nil { - return nil, status.Errorf(codes.InvalidArgument, - "invalid outpoint %q: %v", opStr, err) - } - - targets = append(targets, op) - } - - default: - return nil, status.Errorf(codes.InvalidArgument, "selection "+ - "is required (outpoints or all)") } // `--all` against a wallet with no live VTXOs is not an error — @@ -1622,23 +1657,6 @@ func (r *RPCServer) RefreshVTXOs(ctx context.Context, }, nil } - // For dry_run, validate inputs and return a preview without - // actually queuing anything. - if req.DryRun { - outpointStrs := make([]string, 0, len(targets)) - for _, op := range targets { - outpointStrs = append( - outpointStrs, - fmt.Sprintf("%s:%d", op.Hash, op.Index), - ) - } - - return &waverpc.RefreshVTXOsResponse{ - QueuedOutpoints: outpointStrs, - Status: "preview", - }, nil - } - // Send the refresh request to the wallet actor and await its // response. refreshReq := &wallet.RefreshVTXOsRequest{ diff --git a/waverpc/daemon.pb.go b/waverpc/daemon.pb.go index d4c5194ed..5234a6056 100644 --- a/waverpc/daemon.pb.go +++ b/waverpc/daemon.pb.go @@ -4528,7 +4528,12 @@ type RefreshVTXOsResponse struct { // for refresh. QueuedOutpoints []string `protobuf:"bytes,1,rep,name=queued_outpoints,json=queuedOutpoints,proto3" json:"queued_outpoints,omitempty"` // status is the result: "queued" on success, "preview" on dry_run. - Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + Status string `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"` + // fee_estimate carries the advisory operator-fee preview for the + // selected VTXOs. Only populated on dry_run previews with a + // non-empty selection. The binding fee is set by the server-issued + // JoinRoundQuote at seal time and may differ from this estimate. + FeeEstimate *RefreshFeeEstimate `protobuf:"bytes,3,opt,name=fee_estimate,json=feeEstimate,proto3" json:"fee_estimate,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -4577,6 +4582,239 @@ func (x *RefreshVTXOsResponse) GetStatus() string { return "" } +func (x *RefreshVTXOsResponse) GetFeeEstimate() *RefreshFeeEstimate { + if x != nil { + return x.FeeEstimate + } + return nil +} + +// RefreshFeeEstimate is the advisory operator-fee preview attached to a +// RefreshVTXOs dry_run response. It itemizes what refreshing the +// selected VTXOs is expected to cost at the operator's current rates, +// including whether the operator's free-refresh window applies. All +// values are advisory: the binding fee is set at seal time. +type RefreshFeeEstimate struct { + state protoimpl.MessageState `protogen:"open.v1"` + // estimated_total_fee_sat is the advisory total operator fee for + // refreshing the full selection. Explicit presence: the field is + // only set when the total is meaningful — either the per-outpoint + // quotes all succeeded, or free_refresh_eligible is true (the + // waiver is computed locally, so its zero total stays valid even + // when estimate_error is set). When the estimate degrades the + // field is absent, never zero, so a missing number can not be + // misread as a free refresh. + EstimatedTotalFeeSat *int64 `protobuf:"varint,1,opt,name=estimated_total_fee_sat,json=estimatedTotalFeeSat,proto3,oneof" json:"estimated_total_fee_sat,omitempty"` + // outpoints itemizes the advisory per-outpoint quotes at the + // operator's current rates. Each row is the ordinary paid price and + // is NOT waiver-adjusted: its components always sum to the row's + // total_fee_sat, so under a free-refresh-eligible selection the rows + // show what the waiver saves. + Outpoints []*OutpointFeeEstimate `protobuf:"bytes,2,rep,name=outpoints,proto3" json:"outpoints,omitempty"` + // free_refresh_eligible is true when every selected VTXO is inside + // the operator's advertised free-refresh window, so refreshing + // exactly this selection on its own is expected to seal with all + // fee components waived. The waiver is all-or-nothing per round: + // batching this selection with boarding, leave, or out-of-window + // inputs forfeits it. + FreeRefreshEligible bool `protobuf:"varint,3,opt,name=free_refresh_eligible,json=freeRefreshEligible,proto3" json:"free_refresh_eligible,omitempty"` + // estimate_error is non-empty when the per-outpoint operator quotes + // could not be fetched (operator unreachable, chain height + // unavailable). The preview is still returned so dry_run keeps + // working as a validity probe; when set, treat the per-outpoint fee + // components and (unless free_refresh_eligible) the total as absent + // rather than zero. + EstimateError string `protobuf:"bytes,4,opt,name=estimate_error,json=estimateError,proto3" json:"estimate_error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RefreshFeeEstimate) Reset() { + *x = RefreshFeeEstimate{} + mi := &file_daemon_proto_msgTypes[52] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RefreshFeeEstimate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RefreshFeeEstimate) ProtoMessage() {} + +func (x *RefreshFeeEstimate) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[52] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RefreshFeeEstimate.ProtoReflect.Descriptor instead. +func (*RefreshFeeEstimate) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{52} +} + +func (x *RefreshFeeEstimate) GetEstimatedTotalFeeSat() int64 { + if x != nil && x.EstimatedTotalFeeSat != nil { + return *x.EstimatedTotalFeeSat + } + return 0 +} + +func (x *RefreshFeeEstimate) GetOutpoints() []*OutpointFeeEstimate { + if x != nil { + return x.Outpoints + } + return nil +} + +func (x *RefreshFeeEstimate) GetFreeRefreshEligible() bool { + if x != nil { + return x.FreeRefreshEligible + } + return false +} + +func (x *RefreshFeeEstimate) GetEstimateError() string { + if x != nil { + return x.EstimateError + } + return "" +} + +// OutpointFeeEstimate is one selected VTXO's advisory refresh-fee +// quote, resolved from the daemon's own view of the VTXO (amount and +// remaining lifetime) so callers do not have to supply either. +type OutpointFeeEstimate struct { + state protoimpl.MessageState `protogen:"open.v1"` + // outpoint identifies the VTXO in "txid:index" format. + Outpoint string `protobuf:"bytes,1,opt,name=outpoint,proto3" json:"outpoint,omitempty"` + // amount_sat is the VTXO amount the quote is for. + AmountSat int64 `protobuf:"varint,2,opt,name=amount_sat,json=amountSat,proto3" json:"amount_sat,omitempty"` + // remaining_blocks is the remaining VTXO lifetime used for the + // quote: batch expiry minus the current chain height. An expired or + // same-block-expiring VTXO is clamped to 1 so the quote never falls + // back to the operator's full-lifetime default for a zero value. + RemainingBlocks uint32 `protobuf:"varint,3,opt,name=remaining_blocks,json=remainingBlocks,proto3" json:"remaining_blocks,omitempty"` + // liquidity_fee_sat is the time-value-of-money component. + LiquidityFeeSat int64 `protobuf:"varint,4,opt,name=liquidity_fee_sat,json=liquidityFeeSat,proto3" json:"liquidity_fee_sat,omitempty"` + // onchain_share_sat is the per-participant on-chain cost share. + OnchainShareSat int64 `protobuf:"varint,5,opt,name=onchain_share_sat,json=onchainShareSat,proto3" json:"onchain_share_sat,omitempty"` + // margin_sat is the fixed operator margin. + MarginSat int64 `protobuf:"varint,6,opt,name=margin_sat,json=marginSat,proto3" json:"margin_sat,omitempty"` + // total_fee_sat is the sum of the components: the ordinary paid + // price for refreshing this VTXO, before any free-refresh waiver. + TotalFeeSat int64 `protobuf:"varint,7,opt,name=total_fee_sat,json=totalFeeSat,proto3" json:"total_fee_sat,omitempty"` + // in_free_refresh_window is true when this VTXO's remaining + // lifetime is inside the operator's advertised free-refresh window. + InFreeRefreshWindow bool `protobuf:"varint,8,opt,name=in_free_refresh_window,json=inFreeRefreshWindow,proto3" json:"in_free_refresh_window,omitempty"` + // below_dust_warning mirrors the operator's warning that this + // VTXO's amount is below the minimum economically viable VTXO at + // current rates: refreshing it costs fees on a coin that already + // costs more to exit than it holds. + BelowDustWarning bool `protobuf:"varint,9,opt,name=below_dust_warning,json=belowDustWarning,proto3" json:"below_dust_warning,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *OutpointFeeEstimate) Reset() { + *x = OutpointFeeEstimate{} + mi := &file_daemon_proto_msgTypes[53] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *OutpointFeeEstimate) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*OutpointFeeEstimate) ProtoMessage() {} + +func (x *OutpointFeeEstimate) ProtoReflect() protoreflect.Message { + mi := &file_daemon_proto_msgTypes[53] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use OutpointFeeEstimate.ProtoReflect.Descriptor instead. +func (*OutpointFeeEstimate) Descriptor() ([]byte, []int) { + return file_daemon_proto_rawDescGZIP(), []int{53} +} + +func (x *OutpointFeeEstimate) GetOutpoint() string { + if x != nil { + return x.Outpoint + } + return "" +} + +func (x *OutpointFeeEstimate) GetAmountSat() int64 { + if x != nil { + return x.AmountSat + } + return 0 +} + +func (x *OutpointFeeEstimate) GetRemainingBlocks() uint32 { + if x != nil { + return x.RemainingBlocks + } + return 0 +} + +func (x *OutpointFeeEstimate) GetLiquidityFeeSat() int64 { + if x != nil { + return x.LiquidityFeeSat + } + return 0 +} + +func (x *OutpointFeeEstimate) GetOnchainShareSat() int64 { + if x != nil { + return x.OnchainShareSat + } + return 0 +} + +func (x *OutpointFeeEstimate) GetMarginSat() int64 { + if x != nil { + return x.MarginSat + } + return 0 +} + +func (x *OutpointFeeEstimate) GetTotalFeeSat() int64 { + if x != nil { + return x.TotalFeeSat + } + return 0 +} + +func (x *OutpointFeeEstimate) GetInFreeRefreshWindow() bool { + if x != nil { + return x.InFreeRefreshWindow + } + return false +} + +func (x *OutpointFeeEstimate) GetBelowDustWarning() bool { + if x != nil { + return x.BelowDustWarning + } + return false +} + // CustomRefreshVTXOInput specifies one custom-policy VTXO input to refresh in // a round. The input can be outside the wallet-managed live VTXO set. type CustomRefreshVTXOInput struct { @@ -4611,7 +4849,7 @@ type CustomRefreshVTXOInput struct { func (x *CustomRefreshVTXOInput) Reset() { *x = CustomRefreshVTXOInput{} - mi := &file_daemon_proto_msgTypes[52] + mi := &file_daemon_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4623,7 +4861,7 @@ func (x *CustomRefreshVTXOInput) String() string { func (*CustomRefreshVTXOInput) ProtoMessage() {} func (x *CustomRefreshVTXOInput) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[52] + mi := &file_daemon_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4636,7 +4874,7 @@ func (x *CustomRefreshVTXOInput) ProtoReflect() protoreflect.Message { // Deprecated: Use CustomRefreshVTXOInput.ProtoReflect.Descriptor instead. func (*CustomRefreshVTXOInput) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{52} + return file_daemon_proto_rawDescGZIP(), []int{54} } func (x *CustomRefreshVTXOInput) GetOutpoint() string { @@ -4710,7 +4948,7 @@ type CustomRefreshVTXOOutput struct { func (x *CustomRefreshVTXOOutput) Reset() { *x = CustomRefreshVTXOOutput{} - mi := &file_daemon_proto_msgTypes[53] + mi := &file_daemon_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4722,7 +4960,7 @@ func (x *CustomRefreshVTXOOutput) String() string { func (*CustomRefreshVTXOOutput) ProtoMessage() {} func (x *CustomRefreshVTXOOutput) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[53] + mi := &file_daemon_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4735,7 +4973,7 @@ func (x *CustomRefreshVTXOOutput) ProtoReflect() protoreflect.Message { // Deprecated: Use CustomRefreshVTXOOutput.ProtoReflect.Descriptor instead. func (*CustomRefreshVTXOOutput) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{53} + return file_daemon_proto_rawDescGZIP(), []int{55} } func (x *CustomRefreshVTXOOutput) GetAmountSat() int64 { @@ -4782,7 +5020,7 @@ type RefreshCustomVTXOsRequest struct { func (x *RefreshCustomVTXOsRequest) Reset() { *x = RefreshCustomVTXOsRequest{} - mi := &file_daemon_proto_msgTypes[54] + mi := &file_daemon_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4794,7 +5032,7 @@ func (x *RefreshCustomVTXOsRequest) String() string { func (*RefreshCustomVTXOsRequest) ProtoMessage() {} func (x *RefreshCustomVTXOsRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[54] + mi := &file_daemon_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4807,7 +5045,7 @@ func (x *RefreshCustomVTXOsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RefreshCustomVTXOsRequest.ProtoReflect.Descriptor instead. func (*RefreshCustomVTXOsRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{54} + return file_daemon_proto_rawDescGZIP(), []int{56} } func (x *RefreshCustomVTXOsRequest) GetInputs() []*CustomRefreshVTXOInput { @@ -4843,7 +5081,7 @@ type RefreshCustomVTXOsResponse struct { func (x *RefreshCustomVTXOsResponse) Reset() { *x = RefreshCustomVTXOsResponse{} - mi := &file_daemon_proto_msgTypes[55] + mi := &file_daemon_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4855,7 +5093,7 @@ func (x *RefreshCustomVTXOsResponse) String() string { func (*RefreshCustomVTXOsResponse) ProtoMessage() {} func (x *RefreshCustomVTXOsResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[55] + mi := &file_daemon_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4868,7 +5106,7 @@ func (x *RefreshCustomVTXOsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RefreshCustomVTXOsResponse.ProtoReflect.Descriptor instead. func (*RefreshCustomVTXOsResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{55} + return file_daemon_proto_rawDescGZIP(), []int{57} } func (x *RefreshCustomVTXOsResponse) GetQueuedOutpoints() []string { @@ -4937,7 +5175,7 @@ type PendingForfeitParticipantSignatureRequest struct { func (x *PendingForfeitParticipantSignatureRequest) Reset() { *x = PendingForfeitParticipantSignatureRequest{} - mi := &file_daemon_proto_msgTypes[56] + mi := &file_daemon_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4949,7 +5187,7 @@ func (x *PendingForfeitParticipantSignatureRequest) String() string { func (*PendingForfeitParticipantSignatureRequest) ProtoMessage() {} func (x *PendingForfeitParticipantSignatureRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[56] + mi := &file_daemon_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4962,7 +5200,7 @@ func (x *PendingForfeitParticipantSignatureRequest) ProtoReflect() protoreflect. // Deprecated: Use PendingForfeitParticipantSignatureRequest.ProtoReflect.Descriptor instead. func (*PendingForfeitParticipantSignatureRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{56} + return file_daemon_proto_rawDescGZIP(), []int{58} } func (x *PendingForfeitParticipantSignatureRequest) GetRequestId() []byte { @@ -5076,7 +5314,7 @@ type ListPendingForfeitParticipantSignatureRequestsRequest struct { func (x *ListPendingForfeitParticipantSignatureRequestsRequest) Reset() { *x = ListPendingForfeitParticipantSignatureRequestsRequest{} - mi := &file_daemon_proto_msgTypes[57] + mi := &file_daemon_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5088,7 +5326,7 @@ func (x *ListPendingForfeitParticipantSignatureRequestsRequest) String() string func (*ListPendingForfeitParticipantSignatureRequestsRequest) ProtoMessage() {} func (x *ListPendingForfeitParticipantSignatureRequestsRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[57] + mi := &file_daemon_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5101,7 +5339,7 @@ func (x *ListPendingForfeitParticipantSignatureRequestsRequest) ProtoReflect() p // Deprecated: Use ListPendingForfeitParticipantSignatureRequestsRequest.ProtoReflect.Descriptor instead. func (*ListPendingForfeitParticipantSignatureRequestsRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{57} + return file_daemon_proto_rawDescGZIP(), []int{59} } func (x *ListPendingForfeitParticipantSignatureRequestsRequest) GetAfterSequence() uint64 { @@ -5128,7 +5366,7 @@ type ListPendingForfeitParticipantSignatureRequestsResponse struct { func (x *ListPendingForfeitParticipantSignatureRequestsResponse) Reset() { *x = ListPendingForfeitParticipantSignatureRequestsResponse{} - mi := &file_daemon_proto_msgTypes[58] + mi := &file_daemon_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5140,7 +5378,7 @@ func (x *ListPendingForfeitParticipantSignatureRequestsResponse) String() string func (*ListPendingForfeitParticipantSignatureRequestsResponse) ProtoMessage() {} func (x *ListPendingForfeitParticipantSignatureRequestsResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[58] + mi := &file_daemon_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5153,7 +5391,7 @@ func (x *ListPendingForfeitParticipantSignatureRequestsResponse) ProtoReflect() // Deprecated: Use ListPendingForfeitParticipantSignatureRequestsResponse.ProtoReflect.Descriptor instead. func (*ListPendingForfeitParticipantSignatureRequestsResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{58} + return file_daemon_proto_rawDescGZIP(), []int{60} } func (x *ListPendingForfeitParticipantSignatureRequestsResponse) GetRequests() []*PendingForfeitParticipantSignatureRequest { @@ -5185,7 +5423,7 @@ type ForfeitParticipantSignature struct { func (x *ForfeitParticipantSignature) Reset() { *x = ForfeitParticipantSignature{} - mi := &file_daemon_proto_msgTypes[59] + mi := &file_daemon_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5197,7 +5435,7 @@ func (x *ForfeitParticipantSignature) String() string { func (*ForfeitParticipantSignature) ProtoMessage() {} func (x *ForfeitParticipantSignature) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[59] + mi := &file_daemon_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5210,7 +5448,7 @@ func (x *ForfeitParticipantSignature) ProtoReflect() protoreflect.Message { // Deprecated: Use ForfeitParticipantSignature.ProtoReflect.Descriptor instead. func (*ForfeitParticipantSignature) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{59} + return file_daemon_proto_rawDescGZIP(), []int{61} } func (x *ForfeitParticipantSignature) GetPubkey() []byte { @@ -5244,7 +5482,7 @@ type SubmitForfeitParticipantSignaturesRequest struct { func (x *SubmitForfeitParticipantSignaturesRequest) Reset() { *x = SubmitForfeitParticipantSignaturesRequest{} - mi := &file_daemon_proto_msgTypes[60] + mi := &file_daemon_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5256,7 +5494,7 @@ func (x *SubmitForfeitParticipantSignaturesRequest) String() string { func (*SubmitForfeitParticipantSignaturesRequest) ProtoMessage() {} func (x *SubmitForfeitParticipantSignaturesRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[60] + mi := &file_daemon_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5269,7 +5507,7 @@ func (x *SubmitForfeitParticipantSignaturesRequest) ProtoReflect() protoreflect. // Deprecated: Use SubmitForfeitParticipantSignaturesRequest.ProtoReflect.Descriptor instead. func (*SubmitForfeitParticipantSignaturesRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{60} + return file_daemon_proto_rawDescGZIP(), []int{62} } func (x *SubmitForfeitParticipantSignaturesRequest) GetRequestId() []byte { @@ -5294,7 +5532,7 @@ type SubmitForfeitParticipantSignaturesResponse struct { func (x *SubmitForfeitParticipantSignaturesResponse) Reset() { *x = SubmitForfeitParticipantSignaturesResponse{} - mi := &file_daemon_proto_msgTypes[61] + mi := &file_daemon_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5306,7 +5544,7 @@ func (x *SubmitForfeitParticipantSignaturesResponse) String() string { func (*SubmitForfeitParticipantSignaturesResponse) ProtoMessage() {} func (x *SubmitForfeitParticipantSignaturesResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[61] + mi := &file_daemon_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5319,7 +5557,7 @@ func (x *SubmitForfeitParticipantSignaturesResponse) ProtoReflect() protoreflect // Deprecated: Use SubmitForfeitParticipantSignaturesResponse.ProtoReflect.Descriptor instead. func (*SubmitForfeitParticipantSignaturesResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{61} + return file_daemon_proto_rawDescGZIP(), []int{63} } // LeaveDestination describes where a single leave output should land. @@ -5339,7 +5577,7 @@ type LeaveDestination struct { func (x *LeaveDestination) Reset() { *x = LeaveDestination{} - mi := &file_daemon_proto_msgTypes[62] + mi := &file_daemon_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5351,7 +5589,7 @@ func (x *LeaveDestination) String() string { func (*LeaveDestination) ProtoMessage() {} func (x *LeaveDestination) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[62] + mi := &file_daemon_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5364,7 +5602,7 @@ func (x *LeaveDestination) ProtoReflect() protoreflect.Message { // Deprecated: Use LeaveDestination.ProtoReflect.Descriptor instead. func (*LeaveDestination) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{62} + return file_daemon_proto_rawDescGZIP(), []int{64} } func (x *LeaveDestination) GetTarget() isLeaveDestination_Target { @@ -5445,7 +5683,7 @@ type LeaveVTXOsRequest struct { func (x *LeaveVTXOsRequest) Reset() { *x = LeaveVTXOsRequest{} - mi := &file_daemon_proto_msgTypes[63] + mi := &file_daemon_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5457,7 +5695,7 @@ func (x *LeaveVTXOsRequest) String() string { func (*LeaveVTXOsRequest) ProtoMessage() {} func (x *LeaveVTXOsRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[63] + mi := &file_daemon_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5470,7 +5708,7 @@ func (x *LeaveVTXOsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LeaveVTXOsRequest.ProtoReflect.Descriptor instead. func (*LeaveVTXOsRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{63} + return file_daemon_proto_rawDescGZIP(), []int{65} } func (x *LeaveVTXOsRequest) GetSelection() isLeaveVTXOsRequest_Selection { @@ -5553,7 +5791,7 @@ type LeaveVTXOsResponse struct { func (x *LeaveVTXOsResponse) Reset() { *x = LeaveVTXOsResponse{} - mi := &file_daemon_proto_msgTypes[64] + mi := &file_daemon_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5565,7 +5803,7 @@ func (x *LeaveVTXOsResponse) String() string { func (*LeaveVTXOsResponse) ProtoMessage() {} func (x *LeaveVTXOsResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[64] + mi := &file_daemon_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5578,7 +5816,7 @@ func (x *LeaveVTXOsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use LeaveVTXOsResponse.ProtoReflect.Descriptor instead. func (*LeaveVTXOsResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{64} + return file_daemon_proto_rawDescGZIP(), []int{66} } func (x *LeaveVTXOsResponse) GetQueuedOutpoints() []string { @@ -5616,7 +5854,7 @@ type SendOnChainRequest struct { func (x *SendOnChainRequest) Reset() { *x = SendOnChainRequest{} - mi := &file_daemon_proto_msgTypes[65] + mi := &file_daemon_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5628,7 +5866,7 @@ func (x *SendOnChainRequest) String() string { func (*SendOnChainRequest) ProtoMessage() {} func (x *SendOnChainRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[65] + mi := &file_daemon_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5641,7 +5879,7 @@ func (x *SendOnChainRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SendOnChainRequest.ProtoReflect.Descriptor instead. func (*SendOnChainRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{65} + return file_daemon_proto_rawDescGZIP(), []int{67} } func (x *SendOnChainRequest) GetDestination() *LeaveDestination { @@ -5749,7 +5987,7 @@ type SendOnChainResponse struct { func (x *SendOnChainResponse) Reset() { *x = SendOnChainResponse{} - mi := &file_daemon_proto_msgTypes[66] + mi := &file_daemon_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5761,7 +5999,7 @@ func (x *SendOnChainResponse) String() string { func (*SendOnChainResponse) ProtoMessage() {} func (x *SendOnChainResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[66] + mi := &file_daemon_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5774,7 +6012,7 @@ func (x *SendOnChainResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SendOnChainResponse.ProtoReflect.Descriptor instead. func (*SendOnChainResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{66} + return file_daemon_proto_rawDescGZIP(), []int{68} } func (x *SendOnChainResponse) GetActualAmountSat() int64 { @@ -5840,7 +6078,7 @@ type BoardRequest struct { func (x *BoardRequest) Reset() { *x = BoardRequest{} - mi := &file_daemon_proto_msgTypes[67] + mi := &file_daemon_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5852,7 +6090,7 @@ func (x *BoardRequest) String() string { func (*BoardRequest) ProtoMessage() {} func (x *BoardRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[67] + mi := &file_daemon_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5865,7 +6103,7 @@ func (x *BoardRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BoardRequest.ProtoReflect.Descriptor instead. func (*BoardRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{67} + return file_daemon_proto_rawDescGZIP(), []int{69} } func (x *BoardRequest) GetTargetVtxoCount() uint32 { @@ -5896,7 +6134,7 @@ type BoardResponse struct { func (x *BoardResponse) Reset() { *x = BoardResponse{} - mi := &file_daemon_proto_msgTypes[68] + mi := &file_daemon_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5908,7 +6146,7 @@ func (x *BoardResponse) String() string { func (*BoardResponse) ProtoMessage() {} func (x *BoardResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[68] + mi := &file_daemon_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5921,7 +6159,7 @@ func (x *BoardResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BoardResponse.ProtoReflect.Descriptor instead. func (*BoardResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{68} + return file_daemon_proto_rawDescGZIP(), []int{70} } func (x *BoardResponse) GetStatus() string { @@ -5946,7 +6184,7 @@ type JoinNextRoundRequest struct { func (x *JoinNextRoundRequest) Reset() { *x = JoinNextRoundRequest{} - mi := &file_daemon_proto_msgTypes[69] + mi := &file_daemon_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5958,7 +6196,7 @@ func (x *JoinNextRoundRequest) String() string { func (*JoinNextRoundRequest) ProtoMessage() {} func (x *JoinNextRoundRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[69] + mi := &file_daemon_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5971,7 +6209,7 @@ func (x *JoinNextRoundRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use JoinNextRoundRequest.ProtoReflect.Descriptor instead. func (*JoinNextRoundRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{69} + return file_daemon_proto_rawDescGZIP(), []int{71} } type JoinNextRoundResponse struct { @@ -5986,7 +6224,7 @@ type JoinNextRoundResponse struct { func (x *JoinNextRoundResponse) Reset() { *x = JoinNextRoundResponse{} - mi := &file_daemon_proto_msgTypes[70] + mi := &file_daemon_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5998,7 +6236,7 @@ func (x *JoinNextRoundResponse) String() string { func (*JoinNextRoundResponse) ProtoMessage() {} func (x *JoinNextRoundResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[70] + mi := &file_daemon_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6011,7 +6249,7 @@ func (x *JoinNextRoundResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use JoinNextRoundResponse.ProtoReflect.Descriptor instead. func (*JoinNextRoundResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{70} + return file_daemon_proto_rawDescGZIP(), []int{72} } func (x *JoinNextRoundResponse) GetStatus() string { @@ -6047,7 +6285,7 @@ type SweepBoardingUTXOsRequest struct { func (x *SweepBoardingUTXOsRequest) Reset() { *x = SweepBoardingUTXOsRequest{} - mi := &file_daemon_proto_msgTypes[71] + mi := &file_daemon_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6059,7 +6297,7 @@ func (x *SweepBoardingUTXOsRequest) String() string { func (*SweepBoardingUTXOsRequest) ProtoMessage() {} func (x *SweepBoardingUTXOsRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[71] + mi := &file_daemon_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6072,7 +6310,7 @@ func (x *SweepBoardingUTXOsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SweepBoardingUTXOsRequest.ProtoReflect.Descriptor instead. func (*SweepBoardingUTXOsRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{71} + return file_daemon_proto_rawDescGZIP(), []int{73} } func (x *SweepBoardingUTXOsRequest) GetOutpoints() []string { @@ -6125,7 +6363,7 @@ type BoardingSweepOutput struct { func (x *BoardingSweepOutput) Reset() { *x = BoardingSweepOutput{} - mi := &file_daemon_proto_msgTypes[72] + mi := &file_daemon_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6137,7 +6375,7 @@ func (x *BoardingSweepOutput) String() string { func (*BoardingSweepOutput) ProtoMessage() {} func (x *BoardingSweepOutput) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[72] + mi := &file_daemon_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6150,7 +6388,7 @@ func (x *BoardingSweepOutput) ProtoReflect() protoreflect.Message { // Deprecated: Use BoardingSweepOutput.ProtoReflect.Descriptor instead. func (*BoardingSweepOutput) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{72} + return file_daemon_proto_rawDescGZIP(), []int{74} } func (x *BoardingSweepOutput) GetOutpoint() string { @@ -6216,7 +6454,7 @@ type SweepBoardingUTXOsResponse struct { func (x *SweepBoardingUTXOsResponse) Reset() { *x = SweepBoardingUTXOsResponse{} - mi := &file_daemon_proto_msgTypes[73] + mi := &file_daemon_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6228,7 +6466,7 @@ func (x *SweepBoardingUTXOsResponse) String() string { func (*SweepBoardingUTXOsResponse) ProtoMessage() {} func (x *SweepBoardingUTXOsResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[73] + mi := &file_daemon_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6241,7 +6479,7 @@ func (x *SweepBoardingUTXOsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SweepBoardingUTXOsResponse.ProtoReflect.Descriptor instead. func (*SweepBoardingUTXOsResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{73} + return file_daemon_proto_rawDescGZIP(), []int{75} } func (x *SweepBoardingUTXOsResponse) GetStatus() string { @@ -6345,7 +6583,7 @@ type ListBoardingSweepsRequest struct { func (x *ListBoardingSweepsRequest) Reset() { *x = ListBoardingSweepsRequest{} - mi := &file_daemon_proto_msgTypes[74] + mi := &file_daemon_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6357,7 +6595,7 @@ func (x *ListBoardingSweepsRequest) String() string { func (*ListBoardingSweepsRequest) ProtoMessage() {} func (x *ListBoardingSweepsRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[74] + mi := &file_daemon_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6370,7 +6608,7 @@ func (x *ListBoardingSweepsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListBoardingSweepsRequest.ProtoReflect.Descriptor instead. func (*ListBoardingSweepsRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{74} + return file_daemon_proto_rawDescGZIP(), []int{76} } func (x *ListBoardingSweepsRequest) GetStatus() string { @@ -6413,7 +6651,7 @@ type BoardingSweepInput struct { func (x *BoardingSweepInput) Reset() { *x = BoardingSweepInput{} - mi := &file_daemon_proto_msgTypes[75] + mi := &file_daemon_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6425,7 +6663,7 @@ func (x *BoardingSweepInput) String() string { func (*BoardingSweepInput) ProtoMessage() {} func (x *BoardingSweepInput) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[75] + mi := &file_daemon_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6438,7 +6676,7 @@ func (x *BoardingSweepInput) ProtoReflect() protoreflect.Message { // Deprecated: Use BoardingSweepInput.ProtoReflect.Descriptor instead. func (*BoardingSweepInput) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{75} + return file_daemon_proto_rawDescGZIP(), []int{77} } func (x *BoardingSweepInput) GetOutpoint() string { @@ -6508,7 +6746,7 @@ type BoardingSweep struct { func (x *BoardingSweep) Reset() { *x = BoardingSweep{} - mi := &file_daemon_proto_msgTypes[76] + mi := &file_daemon_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6520,7 +6758,7 @@ func (x *BoardingSweep) String() string { func (*BoardingSweep) ProtoMessage() {} func (x *BoardingSweep) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[76] + mi := &file_daemon_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6533,7 +6771,7 @@ func (x *BoardingSweep) ProtoReflect() protoreflect.Message { // Deprecated: Use BoardingSweep.ProtoReflect.Descriptor instead. func (*BoardingSweep) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{76} + return file_daemon_proto_rawDescGZIP(), []int{78} } func (x *BoardingSweep) GetTxid() string { @@ -6625,7 +6863,7 @@ type ListBoardingSweepsResponse struct { func (x *ListBoardingSweepsResponse) Reset() { *x = ListBoardingSweepsResponse{} - mi := &file_daemon_proto_msgTypes[77] + mi := &file_daemon_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6637,7 +6875,7 @@ func (x *ListBoardingSweepsResponse) String() string { func (*ListBoardingSweepsResponse) ProtoMessage() {} func (x *ListBoardingSweepsResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[77] + mi := &file_daemon_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6650,7 +6888,7 @@ func (x *ListBoardingSweepsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListBoardingSweepsResponse.ProtoReflect.Descriptor instead. func (*ListBoardingSweepsResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{77} + return file_daemon_proto_rawDescGZIP(), []int{79} } func (x *ListBoardingSweepsResponse) GetSweeps() []*BoardingSweep { @@ -6680,7 +6918,7 @@ type RoundVTXOInfo struct { func (x *RoundVTXOInfo) Reset() { *x = RoundVTXOInfo{} - mi := &file_daemon_proto_msgTypes[78] + mi := &file_daemon_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6692,7 +6930,7 @@ func (x *RoundVTXOInfo) String() string { func (*RoundVTXOInfo) ProtoMessage() {} func (x *RoundVTXOInfo) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[78] + mi := &file_daemon_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6705,7 +6943,7 @@ func (x *RoundVTXOInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use RoundVTXOInfo.ProtoReflect.Descriptor instead. func (*RoundVTXOInfo) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{78} + return file_daemon_proto_rawDescGZIP(), []int{80} } func (x *RoundVTXOInfo) GetOutpoint() string { @@ -6773,7 +7011,7 @@ type RoundInfo struct { func (x *RoundInfo) Reset() { *x = RoundInfo{} - mi := &file_daemon_proto_msgTypes[79] + mi := &file_daemon_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6785,7 +7023,7 @@ func (x *RoundInfo) String() string { func (*RoundInfo) ProtoMessage() {} func (x *RoundInfo) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[79] + mi := &file_daemon_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6798,7 +7036,7 @@ func (x *RoundInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use RoundInfo.ProtoReflect.Descriptor instead. func (*RoundInfo) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{79} + return file_daemon_proto_rawDescGZIP(), []int{81} } func (x *RoundInfo) GetRoundId() string { @@ -6910,7 +7148,7 @@ type ListRoundsRequest struct { func (x *ListRoundsRequest) Reset() { *x = ListRoundsRequest{} - mi := &file_daemon_proto_msgTypes[80] + mi := &file_daemon_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6922,7 +7160,7 @@ func (x *ListRoundsRequest) String() string { func (*ListRoundsRequest) ProtoMessage() {} func (x *ListRoundsRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[80] + mi := &file_daemon_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6935,7 +7173,7 @@ func (x *ListRoundsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoundsRequest.ProtoReflect.Descriptor instead. func (*ListRoundsRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{80} + return file_daemon_proto_rawDescGZIP(), []int{82} } func (x *ListRoundsRequest) GetPageSize() int32 { @@ -6990,7 +7228,7 @@ type GetRoundRequest struct { func (x *GetRoundRequest) Reset() { *x = GetRoundRequest{} - mi := &file_daemon_proto_msgTypes[81] + mi := &file_daemon_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7002,7 +7240,7 @@ func (x *GetRoundRequest) String() string { func (*GetRoundRequest) ProtoMessage() {} func (x *GetRoundRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[81] + mi := &file_daemon_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7015,7 +7253,7 @@ func (x *GetRoundRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoundRequest.ProtoReflect.Descriptor instead. func (*GetRoundRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{81} + return file_daemon_proto_rawDescGZIP(), []int{83} } func (x *GetRoundRequest) GetRoundId() string { @@ -7035,7 +7273,7 @@ type GetRoundResponse struct { func (x *GetRoundResponse) Reset() { *x = GetRoundResponse{} - mi := &file_daemon_proto_msgTypes[82] + mi := &file_daemon_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7047,7 +7285,7 @@ func (x *GetRoundResponse) String() string { func (*GetRoundResponse) ProtoMessage() {} func (x *GetRoundResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[82] + mi := &file_daemon_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7060,7 +7298,7 @@ func (x *GetRoundResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetRoundResponse.ProtoReflect.Descriptor instead. func (*GetRoundResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{82} + return file_daemon_proto_rawDescGZIP(), []int{84} } func (x *GetRoundResponse) GetRound() *RoundInfo { @@ -7083,7 +7321,7 @@ type ListRoundsResponse struct { func (x *ListRoundsResponse) Reset() { *x = ListRoundsResponse{} - mi := &file_daemon_proto_msgTypes[83] + mi := &file_daemon_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7095,7 +7333,7 @@ func (x *ListRoundsResponse) String() string { func (*ListRoundsResponse) ProtoMessage() {} func (x *ListRoundsResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[83] + mi := &file_daemon_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7108,7 +7346,7 @@ func (x *ListRoundsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListRoundsResponse.ProtoReflect.Descriptor instead. func (*ListRoundsResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{83} + return file_daemon_proto_rawDescGZIP(), []int{85} } func (x *ListRoundsResponse) GetRounds() []*RoundInfo { @@ -7133,7 +7371,7 @@ type WatchRoundsRequest struct { func (x *WatchRoundsRequest) Reset() { *x = WatchRoundsRequest{} - mi := &file_daemon_proto_msgTypes[84] + mi := &file_daemon_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7145,7 +7383,7 @@ func (x *WatchRoundsRequest) String() string { func (*WatchRoundsRequest) ProtoMessage() {} func (x *WatchRoundsRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[84] + mi := &file_daemon_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7158,7 +7396,7 @@ func (x *WatchRoundsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchRoundsRequest.ProtoReflect.Descriptor instead. func (*WatchRoundsRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{84} + return file_daemon_proto_rawDescGZIP(), []int{86} } type WatchRoundsResponse struct { @@ -7172,7 +7410,7 @@ type WatchRoundsResponse struct { func (x *WatchRoundsResponse) Reset() { *x = WatchRoundsResponse{} - mi := &file_daemon_proto_msgTypes[85] + mi := &file_daemon_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7184,7 +7422,7 @@ func (x *WatchRoundsResponse) String() string { func (*WatchRoundsResponse) ProtoMessage() {} func (x *WatchRoundsResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[85] + mi := &file_daemon_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7197,7 +7435,7 @@ func (x *WatchRoundsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use WatchRoundsResponse.ProtoReflect.Descriptor instead. func (*WatchRoundsResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{85} + return file_daemon_proto_rawDescGZIP(), []int{87} } func (x *WatchRoundsResponse) GetRound() *RoundInfo { @@ -7235,7 +7473,7 @@ type OORSessionInfo struct { func (x *OORSessionInfo) Reset() { *x = OORSessionInfo{} - mi := &file_daemon_proto_msgTypes[86] + mi := &file_daemon_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7247,7 +7485,7 @@ func (x *OORSessionInfo) String() string { func (*OORSessionInfo) ProtoMessage() {} func (x *OORSessionInfo) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[86] + mi := &file_daemon_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7260,7 +7498,7 @@ func (x *OORSessionInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use OORSessionInfo.ProtoReflect.Descriptor instead. func (*OORSessionInfo) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{86} + return file_daemon_proto_rawDescGZIP(), []int{88} } func (x *OORSessionInfo) GetSessionId() string { @@ -7344,7 +7582,7 @@ type ListOORSessionsRequest struct { func (x *ListOORSessionsRequest) Reset() { *x = ListOORSessionsRequest{} - mi := &file_daemon_proto_msgTypes[87] + mi := &file_daemon_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7356,7 +7594,7 @@ func (x *ListOORSessionsRequest) String() string { func (*ListOORSessionsRequest) ProtoMessage() {} func (x *ListOORSessionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[87] + mi := &file_daemon_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7369,7 +7607,7 @@ func (x *ListOORSessionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListOORSessionsRequest.ProtoReflect.Descriptor instead. func (*ListOORSessionsRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{87} + return file_daemon_proto_rawDescGZIP(), []int{89} } func (x *ListOORSessionsRequest) GetPageSize() int32 { @@ -7413,7 +7651,7 @@ type ListOORSessionsResponse struct { func (x *ListOORSessionsResponse) Reset() { *x = ListOORSessionsResponse{} - mi := &file_daemon_proto_msgTypes[88] + mi := &file_daemon_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7425,7 +7663,7 @@ func (x *ListOORSessionsResponse) String() string { func (*ListOORSessionsResponse) ProtoMessage() {} func (x *ListOORSessionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[88] + mi := &file_daemon_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7438,7 +7676,7 @@ func (x *ListOORSessionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListOORSessionsResponse.ProtoReflect.Descriptor instead. func (*ListOORSessionsResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{88} + return file_daemon_proto_rawDescGZIP(), []int{90} } func (x *ListOORSessionsResponse) GetSessions() []*OORSessionInfo { @@ -7465,7 +7703,7 @@ type GetOORSessionRequest struct { func (x *GetOORSessionRequest) Reset() { *x = GetOORSessionRequest{} - mi := &file_daemon_proto_msgTypes[89] + mi := &file_daemon_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7477,7 +7715,7 @@ func (x *GetOORSessionRequest) String() string { func (*GetOORSessionRequest) ProtoMessage() {} func (x *GetOORSessionRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[89] + mi := &file_daemon_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7490,7 +7728,7 @@ func (x *GetOORSessionRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetOORSessionRequest.ProtoReflect.Descriptor instead. func (*GetOORSessionRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{89} + return file_daemon_proto_rawDescGZIP(), []int{91} } func (x *GetOORSessionRequest) GetSessionId() string { @@ -7510,7 +7748,7 @@ type GetOORSessionResponse struct { func (x *GetOORSessionResponse) Reset() { *x = GetOORSessionResponse{} - mi := &file_daemon_proto_msgTypes[90] + mi := &file_daemon_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7522,7 +7760,7 @@ func (x *GetOORSessionResponse) String() string { func (*GetOORSessionResponse) ProtoMessage() {} func (x *GetOORSessionResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[90] + mi := &file_daemon_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7535,7 +7773,7 @@ func (x *GetOORSessionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetOORSessionResponse.ProtoReflect.Descriptor instead. func (*GetOORSessionResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{90} + return file_daemon_proto_rawDescGZIP(), []int{92} } func (x *GetOORSessionResponse) GetSession() *OORSessionInfo { @@ -7563,7 +7801,7 @@ type EstimateFeeRequest struct { func (x *EstimateFeeRequest) Reset() { *x = EstimateFeeRequest{} - mi := &file_daemon_proto_msgTypes[91] + mi := &file_daemon_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7575,7 +7813,7 @@ func (x *EstimateFeeRequest) String() string { func (*EstimateFeeRequest) ProtoMessage() {} func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[91] + mi := &file_daemon_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7588,7 +7826,7 @@ func (x *EstimateFeeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EstimateFeeRequest.ProtoReflect.Descriptor instead. func (*EstimateFeeRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{91} + return file_daemon_proto_rawDescGZIP(), []int{93} } func (x *EstimateFeeRequest) GetAmountSat() int64 { @@ -7642,7 +7880,7 @@ type EstimateFeeResponse struct { func (x *EstimateFeeResponse) Reset() { *x = EstimateFeeResponse{} - mi := &file_daemon_proto_msgTypes[92] + mi := &file_daemon_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7654,7 +7892,7 @@ func (x *EstimateFeeResponse) String() string { func (*EstimateFeeResponse) ProtoMessage() {} func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[92] + mi := &file_daemon_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7667,7 +7905,7 @@ func (x *EstimateFeeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EstimateFeeResponse.ProtoReflect.Descriptor instead. func (*EstimateFeeResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{92} + return file_daemon_proto_rawDescGZIP(), []int{94} } func (x *EstimateFeeResponse) GetLiquidityFeeSat() int64 { @@ -7737,7 +7975,7 @@ type GetFeeHistoryRequest struct { func (x *GetFeeHistoryRequest) Reset() { *x = GetFeeHistoryRequest{} - mi := &file_daemon_proto_msgTypes[93] + mi := &file_daemon_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7749,7 +7987,7 @@ func (x *GetFeeHistoryRequest) String() string { func (*GetFeeHistoryRequest) ProtoMessage() {} func (x *GetFeeHistoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[93] + mi := &file_daemon_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7762,7 +8000,7 @@ func (x *GetFeeHistoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetFeeHistoryRequest.ProtoReflect.Descriptor instead. func (*GetFeeHistoryRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{93} + return file_daemon_proto_rawDescGZIP(), []int{95} } func (x *GetFeeHistoryRequest) GetLimit() uint32 { @@ -7837,7 +8075,7 @@ type FeeHistoryEntry struct { func (x *FeeHistoryEntry) Reset() { *x = FeeHistoryEntry{} - mi := &file_daemon_proto_msgTypes[94] + mi := &file_daemon_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7849,7 +8087,7 @@ func (x *FeeHistoryEntry) String() string { func (*FeeHistoryEntry) ProtoMessage() {} func (x *FeeHistoryEntry) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[94] + mi := &file_daemon_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7862,7 +8100,7 @@ func (x *FeeHistoryEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use FeeHistoryEntry.ProtoReflect.Descriptor instead. func (*FeeHistoryEntry) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{94} + return file_daemon_proto_rawDescGZIP(), []int{96} } func (x *FeeHistoryEntry) GetEntryId() int64 { @@ -7942,7 +8180,7 @@ type GetFeeHistoryResponse struct { func (x *GetFeeHistoryResponse) Reset() { *x = GetFeeHistoryResponse{} - mi := &file_daemon_proto_msgTypes[95] + mi := &file_daemon_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7954,7 +8192,7 @@ func (x *GetFeeHistoryResponse) String() string { func (*GetFeeHistoryResponse) ProtoMessage() {} func (x *GetFeeHistoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[95] + mi := &file_daemon_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7967,7 +8205,7 @@ func (x *GetFeeHistoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetFeeHistoryResponse.ProtoReflect.Descriptor instead. func (*GetFeeHistoryResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{95} + return file_daemon_proto_rawDescGZIP(), []int{97} } func (x *GetFeeHistoryResponse) GetEntries() []*FeeHistoryEntry { @@ -8008,7 +8246,7 @@ type ListTransactionsRequest struct { func (x *ListTransactionsRequest) Reset() { *x = ListTransactionsRequest{} - mi := &file_daemon_proto_msgTypes[96] + mi := &file_daemon_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8020,7 +8258,7 @@ func (x *ListTransactionsRequest) String() string { func (*ListTransactionsRequest) ProtoMessage() {} func (x *ListTransactionsRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[96] + mi := &file_daemon_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8033,7 +8271,7 @@ func (x *ListTransactionsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListTransactionsRequest.ProtoReflect.Descriptor instead. func (*ListTransactionsRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{96} + return file_daemon_proto_rawDescGZIP(), []int{98} } func (x *ListTransactionsRequest) GetFromUnixS() int64 { @@ -8124,7 +8362,7 @@ type TransactionHistoryEntry struct { func (x *TransactionHistoryEntry) Reset() { *x = TransactionHistoryEntry{} - mi := &file_daemon_proto_msgTypes[97] + mi := &file_daemon_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8136,7 +8374,7 @@ func (x *TransactionHistoryEntry) String() string { func (*TransactionHistoryEntry) ProtoMessage() {} func (x *TransactionHistoryEntry) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[97] + mi := &file_daemon_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8149,7 +8387,7 @@ func (x *TransactionHistoryEntry) ProtoReflect() protoreflect.Message { // Deprecated: Use TransactionHistoryEntry.ProtoReflect.Descriptor instead. func (*TransactionHistoryEntry) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{97} + return file_daemon_proto_rawDescGZIP(), []int{99} } func (x *TransactionHistoryEntry) GetSource() string { @@ -8286,7 +8524,7 @@ type ListTransactionsResponse struct { func (x *ListTransactionsResponse) Reset() { *x = ListTransactionsResponse{} - mi := &file_daemon_proto_msgTypes[98] + mi := &file_daemon_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8298,7 +8536,7 @@ func (x *ListTransactionsResponse) String() string { func (*ListTransactionsResponse) ProtoMessage() {} func (x *ListTransactionsResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[98] + mi := &file_daemon_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8311,7 +8549,7 @@ func (x *ListTransactionsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListTransactionsResponse.ProtoReflect.Descriptor instead. func (*ListTransactionsResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{98} + return file_daemon_proto_rawDescGZIP(), []int{100} } func (x *ListTransactionsResponse) GetTransactions() []*TransactionHistoryEntry { @@ -8346,7 +8584,7 @@ type UnrollRequest struct { func (x *UnrollRequest) Reset() { *x = UnrollRequest{} - mi := &file_daemon_proto_msgTypes[99] + mi := &file_daemon_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8358,7 +8596,7 @@ func (x *UnrollRequest) String() string { func (*UnrollRequest) ProtoMessage() {} func (x *UnrollRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[99] + mi := &file_daemon_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8371,7 +8609,7 @@ func (x *UnrollRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnrollRequest.ProtoReflect.Descriptor instead. func (*UnrollRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{99} + return file_daemon_proto_rawDescGZIP(), []int{101} } func (x *UnrollRequest) GetOutpoint() string { @@ -8394,7 +8632,7 @@ type UnrollResponse struct { func (x *UnrollResponse) Reset() { *x = UnrollResponse{} - mi := &file_daemon_proto_msgTypes[100] + mi := &file_daemon_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8406,7 +8644,7 @@ func (x *UnrollResponse) String() string { func (*UnrollResponse) ProtoMessage() {} func (x *UnrollResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[100] + mi := &file_daemon_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8419,7 +8657,7 @@ func (x *UnrollResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnrollResponse.ProtoReflect.Descriptor instead. func (*UnrollResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{100} + return file_daemon_proto_rawDescGZIP(), []int{102} } func (x *UnrollResponse) GetCreated() bool { @@ -8451,7 +8689,7 @@ type GetUnrollStatusRequest struct { func (x *GetUnrollStatusRequest) Reset() { *x = GetUnrollStatusRequest{} - mi := &file_daemon_proto_msgTypes[101] + mi := &file_daemon_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8463,7 +8701,7 @@ func (x *GetUnrollStatusRequest) String() string { func (*GetUnrollStatusRequest) ProtoMessage() {} func (x *GetUnrollStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[101] + mi := &file_daemon_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8476,7 +8714,7 @@ func (x *GetUnrollStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUnrollStatusRequest.ProtoReflect.Descriptor instead. func (*GetUnrollStatusRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{101} + return file_daemon_proto_rawDescGZIP(), []int{103} } func (x *GetUnrollStatusRequest) GetOutpoint() string { @@ -8528,7 +8766,7 @@ type UnrollProgress struct { func (x *UnrollProgress) Reset() { *x = UnrollProgress{} - mi := &file_daemon_proto_msgTypes[102] + mi := &file_daemon_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8540,7 +8778,7 @@ func (x *UnrollProgress) String() string { func (*UnrollProgress) ProtoMessage() {} func (x *UnrollProgress) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[102] + mi := &file_daemon_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8553,7 +8791,7 @@ func (x *UnrollProgress) ProtoReflect() protoreflect.Message { // Deprecated: Use UnrollProgress.ProtoReflect.Descriptor instead. func (*UnrollProgress) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{102} + return file_daemon_proto_rawDescGZIP(), []int{104} } func (x *UnrollProgress) GetConfirmedTxs() uint32 { @@ -8638,7 +8876,7 @@ type UnrollCSV struct { func (x *UnrollCSV) Reset() { *x = UnrollCSV{} - mi := &file_daemon_proto_msgTypes[103] + mi := &file_daemon_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8650,7 +8888,7 @@ func (x *UnrollCSV) String() string { func (*UnrollCSV) ProtoMessage() {} func (x *UnrollCSV) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[103] + mi := &file_daemon_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8663,7 +8901,7 @@ func (x *UnrollCSV) ProtoReflect() protoreflect.Message { // Deprecated: Use UnrollCSV.ProtoReflect.Descriptor instead. func (*UnrollCSV) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{103} + return file_daemon_proto_rawDescGZIP(), []int{105} } func (x *UnrollCSV) GetTargetConfirmHeight() int32 { @@ -8728,7 +8966,7 @@ type UnrollFees struct { func (x *UnrollFees) Reset() { *x = UnrollFees{} - mi := &file_daemon_proto_msgTypes[104] + mi := &file_daemon_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8740,7 +8978,7 @@ func (x *UnrollFees) String() string { func (*UnrollFees) ProtoMessage() {} func (x *UnrollFees) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[104] + mi := &file_daemon_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8753,7 +8991,7 @@ func (x *UnrollFees) ProtoReflect() protoreflect.Message { // Deprecated: Use UnrollFees.ProtoReflect.Descriptor instead. func (*UnrollFees) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{104} + return file_daemon_proto_rawDescGZIP(), []int{106} } func (x *UnrollFees) GetCpfpFeeSat() int64 { @@ -8849,7 +9087,7 @@ type GetUnrollStatusResponse struct { func (x *GetUnrollStatusResponse) Reset() { *x = GetUnrollStatusResponse{} - mi := &file_daemon_proto_msgTypes[105] + mi := &file_daemon_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -8861,7 +9099,7 @@ func (x *GetUnrollStatusResponse) String() string { func (*GetUnrollStatusResponse) ProtoMessage() {} func (x *GetUnrollStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[105] + mi := &file_daemon_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -8874,7 +9112,7 @@ func (x *GetUnrollStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetUnrollStatusResponse.ProtoReflect.Descriptor instead. func (*GetUnrollStatusResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{105} + return file_daemon_proto_rawDescGZIP(), []int{107} } func (x *GetUnrollStatusResponse) GetFound() bool { @@ -8998,7 +9236,7 @@ type ArmVHTLCRecoveryRequest struct { func (x *ArmVHTLCRecoveryRequest) Reset() { *x = ArmVHTLCRecoveryRequest{} - mi := &file_daemon_proto_msgTypes[106] + mi := &file_daemon_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9010,7 +9248,7 @@ func (x *ArmVHTLCRecoveryRequest) String() string { func (*ArmVHTLCRecoveryRequest) ProtoMessage() {} func (x *ArmVHTLCRecoveryRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[106] + mi := &file_daemon_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9023,7 +9261,7 @@ func (x *ArmVHTLCRecoveryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ArmVHTLCRecoveryRequest.ProtoReflect.Descriptor instead. func (*ArmVHTLCRecoveryRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{106} + return file_daemon_proto_rawDescGZIP(), []int{108} } func (x *ArmVHTLCRecoveryRequest) GetRequestId() string { @@ -9166,7 +9404,7 @@ type ArmVHTLCRecoveryResponse struct { func (x *ArmVHTLCRecoveryResponse) Reset() { *x = ArmVHTLCRecoveryResponse{} - mi := &file_daemon_proto_msgTypes[107] + mi := &file_daemon_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9178,7 +9416,7 @@ func (x *ArmVHTLCRecoveryResponse) String() string { func (*ArmVHTLCRecoveryResponse) ProtoMessage() {} func (x *ArmVHTLCRecoveryResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[107] + mi := &file_daemon_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9191,7 +9429,7 @@ func (x *ArmVHTLCRecoveryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ArmVHTLCRecoveryResponse.ProtoReflect.Descriptor instead. func (*ArmVHTLCRecoveryResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{107} + return file_daemon_proto_rawDescGZIP(), []int{109} } func (x *ArmVHTLCRecoveryResponse) GetRecoveryId() string { @@ -9231,7 +9469,7 @@ type EscalateVHTLCRecoveryRequest struct { func (x *EscalateVHTLCRecoveryRequest) Reset() { *x = EscalateVHTLCRecoveryRequest{} - mi := &file_daemon_proto_msgTypes[108] + mi := &file_daemon_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9243,7 +9481,7 @@ func (x *EscalateVHTLCRecoveryRequest) String() string { func (*EscalateVHTLCRecoveryRequest) ProtoMessage() {} func (x *EscalateVHTLCRecoveryRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[108] + mi := &file_daemon_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9256,7 +9494,7 @@ func (x *EscalateVHTLCRecoveryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use EscalateVHTLCRecoveryRequest.ProtoReflect.Descriptor instead. func (*EscalateVHTLCRecoveryRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{108} + return file_daemon_proto_rawDescGZIP(), []int{110} } func (x *EscalateVHTLCRecoveryRequest) GetRecoveryId() string { @@ -9290,7 +9528,7 @@ type EscalateVHTLCRecoveryResponse struct { func (x *EscalateVHTLCRecoveryResponse) Reset() { *x = EscalateVHTLCRecoveryResponse{} - mi := &file_daemon_proto_msgTypes[109] + mi := &file_daemon_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9302,7 +9540,7 @@ func (x *EscalateVHTLCRecoveryResponse) String() string { func (*EscalateVHTLCRecoveryResponse) ProtoMessage() {} func (x *EscalateVHTLCRecoveryResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[109] + mi := &file_daemon_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9315,7 +9553,7 @@ func (x *EscalateVHTLCRecoveryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use EscalateVHTLCRecoveryResponse.ProtoReflect.Descriptor instead. func (*EscalateVHTLCRecoveryResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{109} + return file_daemon_proto_rawDescGZIP(), []int{111} } func (x *EscalateVHTLCRecoveryResponse) GetStatus() *VHTLCRecoveryStatus { @@ -9339,7 +9577,7 @@ type CancelVHTLCRecoveryRequest struct { func (x *CancelVHTLCRecoveryRequest) Reset() { *x = CancelVHTLCRecoveryRequest{} - mi := &file_daemon_proto_msgTypes[110] + mi := &file_daemon_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9351,7 +9589,7 @@ func (x *CancelVHTLCRecoveryRequest) String() string { func (*CancelVHTLCRecoveryRequest) ProtoMessage() {} func (x *CancelVHTLCRecoveryRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[110] + mi := &file_daemon_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9364,7 +9602,7 @@ func (x *CancelVHTLCRecoveryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelVHTLCRecoveryRequest.ProtoReflect.Descriptor instead. func (*CancelVHTLCRecoveryRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{110} + return file_daemon_proto_rawDescGZIP(), []int{112} } func (x *CancelVHTLCRecoveryRequest) GetRecoveryId() string { @@ -9398,7 +9636,7 @@ type CancelVHTLCRecoveryResponse struct { func (x *CancelVHTLCRecoveryResponse) Reset() { *x = CancelVHTLCRecoveryResponse{} - mi := &file_daemon_proto_msgTypes[111] + mi := &file_daemon_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9410,7 +9648,7 @@ func (x *CancelVHTLCRecoveryResponse) String() string { func (*CancelVHTLCRecoveryResponse) ProtoMessage() {} func (x *CancelVHTLCRecoveryResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[111] + mi := &file_daemon_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9423,7 +9661,7 @@ func (x *CancelVHTLCRecoveryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CancelVHTLCRecoveryResponse.ProtoReflect.Descriptor instead. func (*CancelVHTLCRecoveryResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{111} + return file_daemon_proto_rawDescGZIP(), []int{113} } func (x *CancelVHTLCRecoveryResponse) GetStatus() *VHTLCRecoveryStatus { @@ -9443,7 +9681,7 @@ type GetVHTLCRecoveryStatusRequest struct { func (x *GetVHTLCRecoveryStatusRequest) Reset() { *x = GetVHTLCRecoveryStatusRequest{} - mi := &file_daemon_proto_msgTypes[112] + mi := &file_daemon_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9455,7 +9693,7 @@ func (x *GetVHTLCRecoveryStatusRequest) String() string { func (*GetVHTLCRecoveryStatusRequest) ProtoMessage() {} func (x *GetVHTLCRecoveryStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[112] + mi := &file_daemon_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9468,7 +9706,7 @@ func (x *GetVHTLCRecoveryStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetVHTLCRecoveryStatusRequest.ProtoReflect.Descriptor instead. func (*GetVHTLCRecoveryStatusRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{112} + return file_daemon_proto_rawDescGZIP(), []int{114} } func (x *GetVHTLCRecoveryStatusRequest) GetRecoveryId() string { @@ -9490,7 +9728,7 @@ type GetVHTLCRecoveryStatusResponse struct { func (x *GetVHTLCRecoveryStatusResponse) Reset() { *x = GetVHTLCRecoveryStatusResponse{} - mi := &file_daemon_proto_msgTypes[113] + mi := &file_daemon_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9502,7 +9740,7 @@ func (x *GetVHTLCRecoveryStatusResponse) String() string { func (*GetVHTLCRecoveryStatusResponse) ProtoMessage() {} func (x *GetVHTLCRecoveryStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[113] + mi := &file_daemon_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9515,7 +9753,7 @@ func (x *GetVHTLCRecoveryStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetVHTLCRecoveryStatusResponse.ProtoReflect.Descriptor instead. func (*GetVHTLCRecoveryStatusResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{113} + return file_daemon_proto_rawDescGZIP(), []int{115} } func (x *GetVHTLCRecoveryStatusResponse) GetFound() bool { @@ -9542,7 +9780,7 @@ type ListVHTLCRecoveriesRequest struct { func (x *ListVHTLCRecoveriesRequest) Reset() { *x = ListVHTLCRecoveriesRequest{} - mi := &file_daemon_proto_msgTypes[114] + mi := &file_daemon_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9554,7 +9792,7 @@ func (x *ListVHTLCRecoveriesRequest) String() string { func (*ListVHTLCRecoveriesRequest) ProtoMessage() {} func (x *ListVHTLCRecoveriesRequest) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[114] + mi := &file_daemon_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9567,7 +9805,7 @@ func (x *ListVHTLCRecoveriesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVHTLCRecoveriesRequest.ProtoReflect.Descriptor instead. func (*ListVHTLCRecoveriesRequest) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{114} + return file_daemon_proto_rawDescGZIP(), []int{116} } func (x *ListVHTLCRecoveriesRequest) GetIncludeTerminal() bool { @@ -9587,7 +9825,7 @@ type ListVHTLCRecoveriesResponse struct { func (x *ListVHTLCRecoveriesResponse) Reset() { *x = ListVHTLCRecoveriesResponse{} - mi := &file_daemon_proto_msgTypes[115] + mi := &file_daemon_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9599,7 +9837,7 @@ func (x *ListVHTLCRecoveriesResponse) String() string { func (*ListVHTLCRecoveriesResponse) ProtoMessage() {} func (x *ListVHTLCRecoveriesResponse) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[115] + mi := &file_daemon_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9612,7 +9850,7 @@ func (x *ListVHTLCRecoveriesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListVHTLCRecoveriesResponse.ProtoReflect.Descriptor instead. func (*ListVHTLCRecoveriesResponse) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{115} + return file_daemon_proto_rawDescGZIP(), []int{117} } func (x *ListVHTLCRecoveriesResponse) GetStatuses() []*VHTLCRecoveryStatus { @@ -9687,7 +9925,7 @@ type VHTLCRecoveryStatus struct { func (x *VHTLCRecoveryStatus) Reset() { *x = VHTLCRecoveryStatus{} - mi := &file_daemon_proto_msgTypes[116] + mi := &file_daemon_proto_msgTypes[118] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -9699,7 +9937,7 @@ func (x *VHTLCRecoveryStatus) String() string { func (*VHTLCRecoveryStatus) ProtoMessage() {} func (x *VHTLCRecoveryStatus) ProtoReflect() protoreflect.Message { - mi := &file_daemon_proto_msgTypes[116] + mi := &file_daemon_proto_msgTypes[118] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -9712,7 +9950,7 @@ func (x *VHTLCRecoveryStatus) ProtoReflect() protoreflect.Message { // Deprecated: Use VHTLCRecoveryStatus.ProtoReflect.Descriptor instead. func (*VHTLCRecoveryStatus) Descriptor() ([]byte, []int) { - return file_daemon_proto_rawDescGZIP(), []int{116} + return file_daemon_proto_rawDescGZIP(), []int{118} } func (x *VHTLCRecoveryStatus) GetRecoveryId() string { @@ -10150,10 +10388,29 @@ const file_daemon_proto_rawDesc = "" + "\toutpoints\x18\x01 \x01(\v2\x1a.waverpc.OutpointSelectionH\x00R\toutpoints\x12\x12\n" + "\x03all\x18\x02 \x01(\bH\x00R\x03all\x12\x17\n" + "\adry_run\x18\x03 \x01(\bR\x06dryRunB\v\n" + - "\tselection\"Y\n" + + "\tselection\"\x99\x01\n" + "\x14RefreshVTXOsResponse\x12)\n" + "\x10queued_outpoints\x18\x01 \x03(\tR\x0fqueuedOutpoints\x12\x16\n" + - "\x06status\x18\x02 \x01(\tR\x06status\"\xd0\x02\n" + + "\x06status\x18\x02 \x01(\tR\x06status\x12>\n" + + "\ffee_estimate\x18\x03 \x01(\v2\x1b.waverpc.RefreshFeeEstimateR\vfeeEstimate\"\x83\x02\n" + + "\x12RefreshFeeEstimate\x12:\n" + + "\x17estimated_total_fee_sat\x18\x01 \x01(\x03H\x00R\x14estimatedTotalFeeSat\x88\x01\x01\x12:\n" + + "\toutpoints\x18\x02 \x03(\v2\x1c.waverpc.OutpointFeeEstimateR\toutpoints\x122\n" + + "\x15free_refresh_eligible\x18\x03 \x01(\bR\x13freeRefreshEligible\x12%\n" + + "\x0eestimate_error\x18\x04 \x01(\tR\restimateErrorB\x1a\n" + + "\x18_estimated_total_fee_sat\"\xf9\x02\n" + + "\x13OutpointFeeEstimate\x12\x1a\n" + + "\boutpoint\x18\x01 \x01(\tR\boutpoint\x12\x1d\n" + + "\n" + + "amount_sat\x18\x02 \x01(\x03R\tamountSat\x12)\n" + + "\x10remaining_blocks\x18\x03 \x01(\rR\x0fremainingBlocks\x12*\n" + + "\x11liquidity_fee_sat\x18\x04 \x01(\x03R\x0fliquidityFeeSat\x12*\n" + + "\x11onchain_share_sat\x18\x05 \x01(\x03R\x0fonchainShareSat\x12\x1d\n" + + "\n" + + "margin_sat\x18\x06 \x01(\x03R\tmarginSat\x12\"\n" + + "\rtotal_fee_sat\x18\a \x01(\x03R\vtotalFeeSat\x123\n" + + "\x16in_free_refresh_window\x18\b \x01(\bR\x13inFreeRefreshWindow\x12,\n" + + "\x12below_dust_warning\x18\t \x01(\bR\x10belowDustWarning\"\xd0\x02\n" + "\x16CustomRefreshVTXOInput\x12\x1a\n" + "\boutpoint\x18\x01 \x01(\tR\boutpoint\x12\x1d\n" + "\n" + @@ -10720,7 +10977,7 @@ func file_daemon_proto_rawDescGZIP() []byte { } var file_daemon_proto_enumTypes = make([]protoimpl.EnumInfo, 11) -var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 118) +var file_daemon_proto_msgTypes = make([]protoimpl.MessageInfo, 120) var file_daemon_proto_goTypes = []any{ (WalletState)(0), // 0: waverpc.WalletState (VTXOStatus)(0), // 1: waverpc.VTXOStatus @@ -10785,72 +11042,74 @@ var file_daemon_proto_goTypes = []any{ (*OutpointSelection)(nil), // 60: waverpc.OutpointSelection (*RefreshVTXOsRequest)(nil), // 61: waverpc.RefreshVTXOsRequest (*RefreshVTXOsResponse)(nil), // 62: waverpc.RefreshVTXOsResponse - (*CustomRefreshVTXOInput)(nil), // 63: waverpc.CustomRefreshVTXOInput - (*CustomRefreshVTXOOutput)(nil), // 64: waverpc.CustomRefreshVTXOOutput - (*RefreshCustomVTXOsRequest)(nil), // 65: waverpc.RefreshCustomVTXOsRequest - (*RefreshCustomVTXOsResponse)(nil), // 66: waverpc.RefreshCustomVTXOsResponse - (*PendingForfeitParticipantSignatureRequest)(nil), // 67: waverpc.PendingForfeitParticipantSignatureRequest - (*ListPendingForfeitParticipantSignatureRequestsRequest)(nil), // 68: waverpc.ListPendingForfeitParticipantSignatureRequestsRequest - (*ListPendingForfeitParticipantSignatureRequestsResponse)(nil), // 69: waverpc.ListPendingForfeitParticipantSignatureRequestsResponse - (*ForfeitParticipantSignature)(nil), // 70: waverpc.ForfeitParticipantSignature - (*SubmitForfeitParticipantSignaturesRequest)(nil), // 71: waverpc.SubmitForfeitParticipantSignaturesRequest - (*SubmitForfeitParticipantSignaturesResponse)(nil), // 72: waverpc.SubmitForfeitParticipantSignaturesResponse - (*LeaveDestination)(nil), // 73: waverpc.LeaveDestination - (*LeaveVTXOsRequest)(nil), // 74: waverpc.LeaveVTXOsRequest - (*LeaveVTXOsResponse)(nil), // 75: waverpc.LeaveVTXOsResponse - (*SendOnChainRequest)(nil), // 76: waverpc.SendOnChainRequest - (*SendOnChainResponse)(nil), // 77: waverpc.SendOnChainResponse - (*BoardRequest)(nil), // 78: waverpc.BoardRequest - (*BoardResponse)(nil), // 79: waverpc.BoardResponse - (*JoinNextRoundRequest)(nil), // 80: waverpc.JoinNextRoundRequest - (*JoinNextRoundResponse)(nil), // 81: waverpc.JoinNextRoundResponse - (*SweepBoardingUTXOsRequest)(nil), // 82: waverpc.SweepBoardingUTXOsRequest - (*BoardingSweepOutput)(nil), // 83: waverpc.BoardingSweepOutput - (*SweepBoardingUTXOsResponse)(nil), // 84: waverpc.SweepBoardingUTXOsResponse - (*ListBoardingSweepsRequest)(nil), // 85: waverpc.ListBoardingSweepsRequest - (*BoardingSweepInput)(nil), // 86: waverpc.BoardingSweepInput - (*BoardingSweep)(nil), // 87: waverpc.BoardingSweep - (*ListBoardingSweepsResponse)(nil), // 88: waverpc.ListBoardingSweepsResponse - (*RoundVTXOInfo)(nil), // 89: waverpc.RoundVTXOInfo - (*RoundInfo)(nil), // 90: waverpc.RoundInfo - (*ListRoundsRequest)(nil), // 91: waverpc.ListRoundsRequest - (*GetRoundRequest)(nil), // 92: waverpc.GetRoundRequest - (*GetRoundResponse)(nil), // 93: waverpc.GetRoundResponse - (*ListRoundsResponse)(nil), // 94: waverpc.ListRoundsResponse - (*WatchRoundsRequest)(nil), // 95: waverpc.WatchRoundsRequest - (*WatchRoundsResponse)(nil), // 96: waverpc.WatchRoundsResponse - (*OORSessionInfo)(nil), // 97: waverpc.OORSessionInfo - (*ListOORSessionsRequest)(nil), // 98: waverpc.ListOORSessionsRequest - (*ListOORSessionsResponse)(nil), // 99: waverpc.ListOORSessionsResponse - (*GetOORSessionRequest)(nil), // 100: waverpc.GetOORSessionRequest - (*GetOORSessionResponse)(nil), // 101: waverpc.GetOORSessionResponse - (*EstimateFeeRequest)(nil), // 102: waverpc.EstimateFeeRequest - (*EstimateFeeResponse)(nil), // 103: waverpc.EstimateFeeResponse - (*GetFeeHistoryRequest)(nil), // 104: waverpc.GetFeeHistoryRequest - (*FeeHistoryEntry)(nil), // 105: waverpc.FeeHistoryEntry - (*GetFeeHistoryResponse)(nil), // 106: waverpc.GetFeeHistoryResponse - (*ListTransactionsRequest)(nil), // 107: waverpc.ListTransactionsRequest - (*TransactionHistoryEntry)(nil), // 108: waverpc.TransactionHistoryEntry - (*ListTransactionsResponse)(nil), // 109: waverpc.ListTransactionsResponse - (*UnrollRequest)(nil), // 110: waverpc.UnrollRequest - (*UnrollResponse)(nil), // 111: waverpc.UnrollResponse - (*GetUnrollStatusRequest)(nil), // 112: waverpc.GetUnrollStatusRequest - (*UnrollProgress)(nil), // 113: waverpc.UnrollProgress - (*UnrollCSV)(nil), // 114: waverpc.UnrollCSV - (*UnrollFees)(nil), // 115: waverpc.UnrollFees - (*GetUnrollStatusResponse)(nil), // 116: waverpc.GetUnrollStatusResponse - (*ArmVHTLCRecoveryRequest)(nil), // 117: waverpc.ArmVHTLCRecoveryRequest - (*ArmVHTLCRecoveryResponse)(nil), // 118: waverpc.ArmVHTLCRecoveryResponse - (*EscalateVHTLCRecoveryRequest)(nil), // 119: waverpc.EscalateVHTLCRecoveryRequest - (*EscalateVHTLCRecoveryResponse)(nil), // 120: waverpc.EscalateVHTLCRecoveryResponse - (*CancelVHTLCRecoveryRequest)(nil), // 121: waverpc.CancelVHTLCRecoveryRequest - (*CancelVHTLCRecoveryResponse)(nil), // 122: waverpc.CancelVHTLCRecoveryResponse - (*GetVHTLCRecoveryStatusRequest)(nil), // 123: waverpc.GetVHTLCRecoveryStatusRequest - (*GetVHTLCRecoveryStatusResponse)(nil), // 124: waverpc.GetVHTLCRecoveryStatusResponse - (*ListVHTLCRecoveriesRequest)(nil), // 125: waverpc.ListVHTLCRecoveriesRequest - (*ListVHTLCRecoveriesResponse)(nil), // 126: waverpc.ListVHTLCRecoveriesResponse - (*VHTLCRecoveryStatus)(nil), // 127: waverpc.VHTLCRecoveryStatus - nil, // 128: waverpc.LeaveVTXOsRequest.DestinationsEntry + (*RefreshFeeEstimate)(nil), // 63: waverpc.RefreshFeeEstimate + (*OutpointFeeEstimate)(nil), // 64: waverpc.OutpointFeeEstimate + (*CustomRefreshVTXOInput)(nil), // 65: waverpc.CustomRefreshVTXOInput + (*CustomRefreshVTXOOutput)(nil), // 66: waverpc.CustomRefreshVTXOOutput + (*RefreshCustomVTXOsRequest)(nil), // 67: waverpc.RefreshCustomVTXOsRequest + (*RefreshCustomVTXOsResponse)(nil), // 68: waverpc.RefreshCustomVTXOsResponse + (*PendingForfeitParticipantSignatureRequest)(nil), // 69: waverpc.PendingForfeitParticipantSignatureRequest + (*ListPendingForfeitParticipantSignatureRequestsRequest)(nil), // 70: waverpc.ListPendingForfeitParticipantSignatureRequestsRequest + (*ListPendingForfeitParticipantSignatureRequestsResponse)(nil), // 71: waverpc.ListPendingForfeitParticipantSignatureRequestsResponse + (*ForfeitParticipantSignature)(nil), // 72: waverpc.ForfeitParticipantSignature + (*SubmitForfeitParticipantSignaturesRequest)(nil), // 73: waverpc.SubmitForfeitParticipantSignaturesRequest + (*SubmitForfeitParticipantSignaturesResponse)(nil), // 74: waverpc.SubmitForfeitParticipantSignaturesResponse + (*LeaveDestination)(nil), // 75: waverpc.LeaveDestination + (*LeaveVTXOsRequest)(nil), // 76: waverpc.LeaveVTXOsRequest + (*LeaveVTXOsResponse)(nil), // 77: waverpc.LeaveVTXOsResponse + (*SendOnChainRequest)(nil), // 78: waverpc.SendOnChainRequest + (*SendOnChainResponse)(nil), // 79: waverpc.SendOnChainResponse + (*BoardRequest)(nil), // 80: waverpc.BoardRequest + (*BoardResponse)(nil), // 81: waverpc.BoardResponse + (*JoinNextRoundRequest)(nil), // 82: waverpc.JoinNextRoundRequest + (*JoinNextRoundResponse)(nil), // 83: waverpc.JoinNextRoundResponse + (*SweepBoardingUTXOsRequest)(nil), // 84: waverpc.SweepBoardingUTXOsRequest + (*BoardingSweepOutput)(nil), // 85: waverpc.BoardingSweepOutput + (*SweepBoardingUTXOsResponse)(nil), // 86: waverpc.SweepBoardingUTXOsResponse + (*ListBoardingSweepsRequest)(nil), // 87: waverpc.ListBoardingSweepsRequest + (*BoardingSweepInput)(nil), // 88: waverpc.BoardingSweepInput + (*BoardingSweep)(nil), // 89: waverpc.BoardingSweep + (*ListBoardingSweepsResponse)(nil), // 90: waverpc.ListBoardingSweepsResponse + (*RoundVTXOInfo)(nil), // 91: waverpc.RoundVTXOInfo + (*RoundInfo)(nil), // 92: waverpc.RoundInfo + (*ListRoundsRequest)(nil), // 93: waverpc.ListRoundsRequest + (*GetRoundRequest)(nil), // 94: waverpc.GetRoundRequest + (*GetRoundResponse)(nil), // 95: waverpc.GetRoundResponse + (*ListRoundsResponse)(nil), // 96: waverpc.ListRoundsResponse + (*WatchRoundsRequest)(nil), // 97: waverpc.WatchRoundsRequest + (*WatchRoundsResponse)(nil), // 98: waverpc.WatchRoundsResponse + (*OORSessionInfo)(nil), // 99: waverpc.OORSessionInfo + (*ListOORSessionsRequest)(nil), // 100: waverpc.ListOORSessionsRequest + (*ListOORSessionsResponse)(nil), // 101: waverpc.ListOORSessionsResponse + (*GetOORSessionRequest)(nil), // 102: waverpc.GetOORSessionRequest + (*GetOORSessionResponse)(nil), // 103: waverpc.GetOORSessionResponse + (*EstimateFeeRequest)(nil), // 104: waverpc.EstimateFeeRequest + (*EstimateFeeResponse)(nil), // 105: waverpc.EstimateFeeResponse + (*GetFeeHistoryRequest)(nil), // 106: waverpc.GetFeeHistoryRequest + (*FeeHistoryEntry)(nil), // 107: waverpc.FeeHistoryEntry + (*GetFeeHistoryResponse)(nil), // 108: waverpc.GetFeeHistoryResponse + (*ListTransactionsRequest)(nil), // 109: waverpc.ListTransactionsRequest + (*TransactionHistoryEntry)(nil), // 110: waverpc.TransactionHistoryEntry + (*ListTransactionsResponse)(nil), // 111: waverpc.ListTransactionsResponse + (*UnrollRequest)(nil), // 112: waverpc.UnrollRequest + (*UnrollResponse)(nil), // 113: waverpc.UnrollResponse + (*GetUnrollStatusRequest)(nil), // 114: waverpc.GetUnrollStatusRequest + (*UnrollProgress)(nil), // 115: waverpc.UnrollProgress + (*UnrollCSV)(nil), // 116: waverpc.UnrollCSV + (*UnrollFees)(nil), // 117: waverpc.UnrollFees + (*GetUnrollStatusResponse)(nil), // 118: waverpc.GetUnrollStatusResponse + (*ArmVHTLCRecoveryRequest)(nil), // 119: waverpc.ArmVHTLCRecoveryRequest + (*ArmVHTLCRecoveryResponse)(nil), // 120: waverpc.ArmVHTLCRecoveryResponse + (*EscalateVHTLCRecoveryRequest)(nil), // 121: waverpc.EscalateVHTLCRecoveryRequest + (*EscalateVHTLCRecoveryResponse)(nil), // 122: waverpc.EscalateVHTLCRecoveryResponse + (*CancelVHTLCRecoveryRequest)(nil), // 123: waverpc.CancelVHTLCRecoveryRequest + (*CancelVHTLCRecoveryResponse)(nil), // 124: waverpc.CancelVHTLCRecoveryResponse + (*GetVHTLCRecoveryStatusRequest)(nil), // 125: waverpc.GetVHTLCRecoveryStatusRequest + (*GetVHTLCRecoveryStatusResponse)(nil), // 126: waverpc.GetVHTLCRecoveryStatusResponse + (*ListVHTLCRecoveriesRequest)(nil), // 127: waverpc.ListVHTLCRecoveriesRequest + (*ListVHTLCRecoveriesResponse)(nil), // 128: waverpc.ListVHTLCRecoveriesResponse + (*VHTLCRecoveryStatus)(nil), // 129: waverpc.VHTLCRecoveryStatus + nil, // 130: waverpc.LeaveVTXOsRequest.DestinationsEntry } var file_daemon_proto_depIdxs = []int32{ 0, // 0: waverpc.GetInfoResponse.wallet_state:type_name -> waverpc.WalletState @@ -10877,144 +11136,146 @@ var file_daemon_proto_depIdxs = []int32{ 50, // 21: waverpc.SignOORCustomInputResponse.signature:type_name -> waverpc.TaprootScriptSignature 3, // 22: waverpc.ForfeitSigningContext.signing_route:type_name -> waverpc.ForfeitSigningRoute 60, // 23: waverpc.RefreshVTXOsRequest.outpoints:type_name -> waverpc.OutpointSelection - 59, // 24: waverpc.CustomRefreshVTXOInput.forfeit_signing_context:type_name -> waverpc.ForfeitSigningContext - 63, // 25: waverpc.RefreshCustomVTXOsRequest.inputs:type_name -> waverpc.CustomRefreshVTXOInput - 64, // 26: waverpc.RefreshCustomVTXOsRequest.outputs:type_name -> waverpc.CustomRefreshVTXOOutput - 3, // 27: waverpc.PendingForfeitParticipantSignatureRequest.signing_route:type_name -> waverpc.ForfeitSigningRoute - 67, // 28: waverpc.ListPendingForfeitParticipantSignatureRequestsResponse.requests:type_name -> waverpc.PendingForfeitParticipantSignatureRequest - 70, // 29: waverpc.SubmitForfeitParticipantSignaturesRequest.signatures:type_name -> waverpc.ForfeitParticipantSignature - 60, // 30: waverpc.LeaveVTXOsRequest.outpoints:type_name -> waverpc.OutpointSelection - 73, // 31: waverpc.LeaveVTXOsRequest.default_destination:type_name -> waverpc.LeaveDestination - 128, // 32: waverpc.LeaveVTXOsRequest.destinations:type_name -> waverpc.LeaveVTXOsRequest.DestinationsEntry - 73, // 33: waverpc.SendOnChainRequest.destination:type_name -> waverpc.LeaveDestination - 83, // 34: waverpc.SweepBoardingUTXOsResponse.sweepable_outputs:type_name -> waverpc.BoardingSweepOutput - 86, // 35: waverpc.BoardingSweep.inputs:type_name -> waverpc.BoardingSweepInput - 87, // 36: waverpc.ListBoardingSweepsResponse.sweeps:type_name -> waverpc.BoardingSweep - 4, // 37: waverpc.RoundInfo.state:type_name -> waverpc.RoundState - 89, // 38: waverpc.RoundInfo.vtxos:type_name -> waverpc.RoundVTXOInfo - 4, // 39: waverpc.ListRoundsRequest.state_filter:type_name -> waverpc.RoundState - 90, // 40: waverpc.GetRoundResponse.round:type_name -> waverpc.RoundInfo - 90, // 41: waverpc.ListRoundsResponse.rounds:type_name -> waverpc.RoundInfo - 90, // 42: waverpc.WatchRoundsResponse.round:type_name -> waverpc.RoundInfo - 5, // 43: waverpc.OORSessionInfo.direction:type_name -> waverpc.OORSessionDirection - 6, // 44: waverpc.OORSessionInfo.status:type_name -> waverpc.OORSessionStatus - 5, // 45: waverpc.ListOORSessionsRequest.direction_filter:type_name -> waverpc.OORSessionDirection - 6, // 46: waverpc.ListOORSessionsRequest.status_filter:type_name -> waverpc.OORSessionStatus - 97, // 47: waverpc.ListOORSessionsResponse.sessions:type_name -> waverpc.OORSessionInfo - 97, // 48: waverpc.GetOORSessionResponse.session:type_name -> waverpc.OORSessionInfo - 105, // 49: waverpc.GetFeeHistoryResponse.entries:type_name -> waverpc.FeeHistoryEntry - 108, // 50: waverpc.ListTransactionsResponse.transactions:type_name -> waverpc.TransactionHistoryEntry - 7, // 51: waverpc.GetUnrollStatusResponse.status:type_name -> waverpc.UnrollJobStatus - 113, // 52: waverpc.GetUnrollStatusResponse.progress:type_name -> waverpc.UnrollProgress - 114, // 53: waverpc.GetUnrollStatusResponse.csv:type_name -> waverpc.UnrollCSV - 115, // 54: waverpc.GetUnrollStatusResponse.fees:type_name -> waverpc.UnrollFees - 8, // 55: waverpc.ArmVHTLCRecoveryRequest.direction:type_name -> waverpc.VHTLCRecoveryDirection - 9, // 56: waverpc.ArmVHTLCRecoveryRequest.action:type_name -> waverpc.VHTLCRecoveryAction - 127, // 57: waverpc.ArmVHTLCRecoveryResponse.status:type_name -> waverpc.VHTLCRecoveryStatus - 127, // 58: waverpc.EscalateVHTLCRecoveryResponse.status:type_name -> waverpc.VHTLCRecoveryStatus - 127, // 59: waverpc.CancelVHTLCRecoveryResponse.status:type_name -> waverpc.VHTLCRecoveryStatus - 127, // 60: waverpc.GetVHTLCRecoveryStatusResponse.status:type_name -> waverpc.VHTLCRecoveryStatus - 127, // 61: waverpc.ListVHTLCRecoveriesResponse.statuses:type_name -> waverpc.VHTLCRecoveryStatus - 8, // 62: waverpc.VHTLCRecoveryStatus.direction:type_name -> waverpc.VHTLCRecoveryDirection - 9, // 63: waverpc.VHTLCRecoveryStatus.action:type_name -> waverpc.VHTLCRecoveryAction - 10, // 64: waverpc.VHTLCRecoveryStatus.state:type_name -> waverpc.VHTLCRecoveryState - 7, // 65: waverpc.VHTLCRecoveryStatus.unroll_status:type_name -> waverpc.UnrollJobStatus - 73, // 66: waverpc.LeaveVTXOsRequest.DestinationsEntry.value:type_name -> waverpc.LeaveDestination - 11, // 67: waverpc.DaemonService.GetInfo:input_type -> waverpc.GetInfoRequest - 14, // 68: waverpc.DaemonService.GenSeed:input_type -> waverpc.GenSeedRequest - 16, // 69: waverpc.DaemonService.InitWallet:input_type -> waverpc.InitWalletRequest - 18, // 70: waverpc.DaemonService.UnlockWallet:input_type -> waverpc.UnlockWalletRequest - 20, // 71: waverpc.DaemonService.GetBalance:input_type -> waverpc.GetBalanceRequest - 25, // 72: waverpc.DaemonService.ListVTXOs:input_type -> waverpc.ListVTXOsRequest - 27, // 73: waverpc.DaemonService.NewAddress:input_type -> waverpc.NewAddressRequest - 29, // 74: waverpc.DaemonService.NewReceiveScript:input_type -> waverpc.NewReceiveScriptRequest - 31, // 75: waverpc.DaemonService.ReceiveAuthKey:input_type -> waverpc.ReceiveAuthKeyRequest - 33, // 76: waverpc.DaemonService.SignReceiveAuthMessage:input_type -> waverpc.SignReceiveAuthMessageRequest - 35, // 77: waverpc.DaemonService.SignReceiveAuthMessageCompact:input_type -> waverpc.SignReceiveAuthMessageCompactRequest - 37, // 78: waverpc.DaemonService.ReceiveAuthECDH:input_type -> waverpc.ReceiveAuthECDHRequest - 39, // 79: waverpc.DaemonService.GetIndexedVTXOByPkScript:input_type -> waverpc.GetIndexedVTXOByPkScriptRequest - 41, // 80: waverpc.DaemonService.GetVTXOExpiryInfo:input_type -> waverpc.GetVTXOExpiryInfoRequest - 43, // 81: waverpc.DaemonService.GetIndexedOORSessionByTxid:input_type -> waverpc.GetIndexedOORSessionByTxidRequest - 46, // 82: waverpc.DaemonService.SendVTXO:input_type -> waverpc.SendVTXORequest - 48, // 83: waverpc.DaemonService.SendOOR:input_type -> waverpc.SendOORRequest - 52, // 84: waverpc.DaemonService.PrepareOOR:input_type -> waverpc.PrepareOORRequest - 55, // 85: waverpc.DaemonService.SignOORCustomInput:input_type -> waverpc.SignOORCustomInputRequest - 57, // 86: waverpc.DaemonService.SignVTXOForfeit:input_type -> waverpc.SignVTXOForfeitRequest - 61, // 87: waverpc.DaemonService.RefreshVTXOs:input_type -> waverpc.RefreshVTXOsRequest - 65, // 88: waverpc.DaemonService.RefreshCustomVTXOs:input_type -> waverpc.RefreshCustomVTXOsRequest - 68, // 89: waverpc.DaemonService.ListPendingForfeitParticipantSignatureRequests:input_type -> waverpc.ListPendingForfeitParticipantSignatureRequestsRequest - 71, // 90: waverpc.DaemonService.SubmitForfeitParticipantSignatures:input_type -> waverpc.SubmitForfeitParticipantSignaturesRequest - 74, // 91: waverpc.DaemonService.LeaveVTXOs:input_type -> waverpc.LeaveVTXOsRequest - 76, // 92: waverpc.DaemonService.SendOnChain:input_type -> waverpc.SendOnChainRequest - 78, // 93: waverpc.DaemonService.Board:input_type -> waverpc.BoardRequest - 80, // 94: waverpc.DaemonService.JoinNextRound:input_type -> waverpc.JoinNextRoundRequest - 82, // 95: waverpc.DaemonService.SweepBoardingUTXOs:input_type -> waverpc.SweepBoardingUTXOsRequest - 85, // 96: waverpc.DaemonService.ListBoardingSweeps:input_type -> waverpc.ListBoardingSweepsRequest - 91, // 97: waverpc.DaemonService.ListRounds:input_type -> waverpc.ListRoundsRequest - 92, // 98: waverpc.DaemonService.GetRound:input_type -> waverpc.GetRoundRequest - 95, // 99: waverpc.DaemonService.WatchRounds:input_type -> waverpc.WatchRoundsRequest - 98, // 100: waverpc.DaemonService.ListOORSessions:input_type -> waverpc.ListOORSessionsRequest - 100, // 101: waverpc.DaemonService.GetOORSession:input_type -> waverpc.GetOORSessionRequest - 102, // 102: waverpc.DaemonService.EstimateFee:input_type -> waverpc.EstimateFeeRequest - 104, // 103: waverpc.DaemonService.GetFeeHistory:input_type -> waverpc.GetFeeHistoryRequest - 107, // 104: waverpc.DaemonService.ListTransactions:input_type -> waverpc.ListTransactionsRequest - 110, // 105: waverpc.DaemonService.Unroll:input_type -> waverpc.UnrollRequest - 112, // 106: waverpc.DaemonService.GetUnrollStatus:input_type -> waverpc.GetUnrollStatusRequest - 117, // 107: waverpc.DaemonService.ArmVHTLCRecovery:input_type -> waverpc.ArmVHTLCRecoveryRequest - 119, // 108: waverpc.DaemonService.EscalateVHTLCRecovery:input_type -> waverpc.EscalateVHTLCRecoveryRequest - 121, // 109: waverpc.DaemonService.CancelVHTLCRecovery:input_type -> waverpc.CancelVHTLCRecoveryRequest - 123, // 110: waverpc.DaemonService.GetVHTLCRecoveryStatus:input_type -> waverpc.GetVHTLCRecoveryStatusRequest - 125, // 111: waverpc.DaemonService.ListVHTLCRecoveries:input_type -> waverpc.ListVHTLCRecoveriesRequest - 12, // 112: waverpc.DaemonService.GetInfo:output_type -> waverpc.GetInfoResponse - 15, // 113: waverpc.DaemonService.GenSeed:output_type -> waverpc.GenSeedResponse - 17, // 114: waverpc.DaemonService.InitWallet:output_type -> waverpc.InitWalletResponse - 19, // 115: waverpc.DaemonService.UnlockWallet:output_type -> waverpc.UnlockWalletResponse - 21, // 116: waverpc.DaemonService.GetBalance:output_type -> waverpc.GetBalanceResponse - 26, // 117: waverpc.DaemonService.ListVTXOs:output_type -> waverpc.ListVTXOsResponse - 28, // 118: waverpc.DaemonService.NewAddress:output_type -> waverpc.NewAddressResponse - 30, // 119: waverpc.DaemonService.NewReceiveScript:output_type -> waverpc.NewReceiveScriptResponse - 32, // 120: waverpc.DaemonService.ReceiveAuthKey:output_type -> waverpc.ReceiveAuthKeyResponse - 34, // 121: waverpc.DaemonService.SignReceiveAuthMessage:output_type -> waverpc.SignReceiveAuthMessageResponse - 36, // 122: waverpc.DaemonService.SignReceiveAuthMessageCompact:output_type -> waverpc.SignReceiveAuthMessageCompactResponse - 38, // 123: waverpc.DaemonService.ReceiveAuthECDH:output_type -> waverpc.ReceiveAuthECDHResponse - 40, // 124: waverpc.DaemonService.GetIndexedVTXOByPkScript:output_type -> waverpc.GetIndexedVTXOByPkScriptResponse - 42, // 125: waverpc.DaemonService.GetVTXOExpiryInfo:output_type -> waverpc.GetVTXOExpiryInfoResponse - 44, // 126: waverpc.DaemonService.GetIndexedOORSessionByTxid:output_type -> waverpc.GetIndexedOORSessionByTxidResponse - 47, // 127: waverpc.DaemonService.SendVTXO:output_type -> waverpc.SendVTXOResponse - 51, // 128: waverpc.DaemonService.SendOOR:output_type -> waverpc.SendOORResponse - 54, // 129: waverpc.DaemonService.PrepareOOR:output_type -> waverpc.PrepareOORResponse - 56, // 130: waverpc.DaemonService.SignOORCustomInput:output_type -> waverpc.SignOORCustomInputResponse - 58, // 131: waverpc.DaemonService.SignVTXOForfeit:output_type -> waverpc.SignVTXOForfeitResponse - 62, // 132: waverpc.DaemonService.RefreshVTXOs:output_type -> waverpc.RefreshVTXOsResponse - 66, // 133: waverpc.DaemonService.RefreshCustomVTXOs:output_type -> waverpc.RefreshCustomVTXOsResponse - 69, // 134: waverpc.DaemonService.ListPendingForfeitParticipantSignatureRequests:output_type -> waverpc.ListPendingForfeitParticipantSignatureRequestsResponse - 72, // 135: waverpc.DaemonService.SubmitForfeitParticipantSignatures:output_type -> waverpc.SubmitForfeitParticipantSignaturesResponse - 75, // 136: waverpc.DaemonService.LeaveVTXOs:output_type -> waverpc.LeaveVTXOsResponse - 77, // 137: waverpc.DaemonService.SendOnChain:output_type -> waverpc.SendOnChainResponse - 79, // 138: waverpc.DaemonService.Board:output_type -> waverpc.BoardResponse - 81, // 139: waverpc.DaemonService.JoinNextRound:output_type -> waverpc.JoinNextRoundResponse - 84, // 140: waverpc.DaemonService.SweepBoardingUTXOs:output_type -> waverpc.SweepBoardingUTXOsResponse - 88, // 141: waverpc.DaemonService.ListBoardingSweeps:output_type -> waverpc.ListBoardingSweepsResponse - 94, // 142: waverpc.DaemonService.ListRounds:output_type -> waverpc.ListRoundsResponse - 93, // 143: waverpc.DaemonService.GetRound:output_type -> waverpc.GetRoundResponse - 96, // 144: waverpc.DaemonService.WatchRounds:output_type -> waverpc.WatchRoundsResponse - 99, // 145: waverpc.DaemonService.ListOORSessions:output_type -> waverpc.ListOORSessionsResponse - 101, // 146: waverpc.DaemonService.GetOORSession:output_type -> waverpc.GetOORSessionResponse - 103, // 147: waverpc.DaemonService.EstimateFee:output_type -> waverpc.EstimateFeeResponse - 106, // 148: waverpc.DaemonService.GetFeeHistory:output_type -> waverpc.GetFeeHistoryResponse - 109, // 149: waverpc.DaemonService.ListTransactions:output_type -> waverpc.ListTransactionsResponse - 111, // 150: waverpc.DaemonService.Unroll:output_type -> waverpc.UnrollResponse - 116, // 151: waverpc.DaemonService.GetUnrollStatus:output_type -> waverpc.GetUnrollStatusResponse - 118, // 152: waverpc.DaemonService.ArmVHTLCRecovery:output_type -> waverpc.ArmVHTLCRecoveryResponse - 120, // 153: waverpc.DaemonService.EscalateVHTLCRecovery:output_type -> waverpc.EscalateVHTLCRecoveryResponse - 122, // 154: waverpc.DaemonService.CancelVHTLCRecovery:output_type -> waverpc.CancelVHTLCRecoveryResponse - 124, // 155: waverpc.DaemonService.GetVHTLCRecoveryStatus:output_type -> waverpc.GetVHTLCRecoveryStatusResponse - 126, // 156: waverpc.DaemonService.ListVHTLCRecoveries:output_type -> waverpc.ListVHTLCRecoveriesResponse - 112, // [112:157] is the sub-list for method output_type - 67, // [67:112] is the sub-list for method input_type - 67, // [67:67] is the sub-list for extension type_name - 67, // [67:67] is the sub-list for extension extendee - 0, // [0:67] is the sub-list for field type_name + 63, // 24: waverpc.RefreshVTXOsResponse.fee_estimate:type_name -> waverpc.RefreshFeeEstimate + 64, // 25: waverpc.RefreshFeeEstimate.outpoints:type_name -> waverpc.OutpointFeeEstimate + 59, // 26: waverpc.CustomRefreshVTXOInput.forfeit_signing_context:type_name -> waverpc.ForfeitSigningContext + 65, // 27: waverpc.RefreshCustomVTXOsRequest.inputs:type_name -> waverpc.CustomRefreshVTXOInput + 66, // 28: waverpc.RefreshCustomVTXOsRequest.outputs:type_name -> waverpc.CustomRefreshVTXOOutput + 3, // 29: waverpc.PendingForfeitParticipantSignatureRequest.signing_route:type_name -> waverpc.ForfeitSigningRoute + 69, // 30: waverpc.ListPendingForfeitParticipantSignatureRequestsResponse.requests:type_name -> waverpc.PendingForfeitParticipantSignatureRequest + 72, // 31: waverpc.SubmitForfeitParticipantSignaturesRequest.signatures:type_name -> waverpc.ForfeitParticipantSignature + 60, // 32: waverpc.LeaveVTXOsRequest.outpoints:type_name -> waverpc.OutpointSelection + 75, // 33: waverpc.LeaveVTXOsRequest.default_destination:type_name -> waverpc.LeaveDestination + 130, // 34: waverpc.LeaveVTXOsRequest.destinations:type_name -> waverpc.LeaveVTXOsRequest.DestinationsEntry + 75, // 35: waverpc.SendOnChainRequest.destination:type_name -> waverpc.LeaveDestination + 85, // 36: waverpc.SweepBoardingUTXOsResponse.sweepable_outputs:type_name -> waverpc.BoardingSweepOutput + 88, // 37: waverpc.BoardingSweep.inputs:type_name -> waverpc.BoardingSweepInput + 89, // 38: waverpc.ListBoardingSweepsResponse.sweeps:type_name -> waverpc.BoardingSweep + 4, // 39: waverpc.RoundInfo.state:type_name -> waverpc.RoundState + 91, // 40: waverpc.RoundInfo.vtxos:type_name -> waverpc.RoundVTXOInfo + 4, // 41: waverpc.ListRoundsRequest.state_filter:type_name -> waverpc.RoundState + 92, // 42: waverpc.GetRoundResponse.round:type_name -> waverpc.RoundInfo + 92, // 43: waverpc.ListRoundsResponse.rounds:type_name -> waverpc.RoundInfo + 92, // 44: waverpc.WatchRoundsResponse.round:type_name -> waverpc.RoundInfo + 5, // 45: waverpc.OORSessionInfo.direction:type_name -> waverpc.OORSessionDirection + 6, // 46: waverpc.OORSessionInfo.status:type_name -> waverpc.OORSessionStatus + 5, // 47: waverpc.ListOORSessionsRequest.direction_filter:type_name -> waverpc.OORSessionDirection + 6, // 48: waverpc.ListOORSessionsRequest.status_filter:type_name -> waverpc.OORSessionStatus + 99, // 49: waverpc.ListOORSessionsResponse.sessions:type_name -> waverpc.OORSessionInfo + 99, // 50: waverpc.GetOORSessionResponse.session:type_name -> waverpc.OORSessionInfo + 107, // 51: waverpc.GetFeeHistoryResponse.entries:type_name -> waverpc.FeeHistoryEntry + 110, // 52: waverpc.ListTransactionsResponse.transactions:type_name -> waverpc.TransactionHistoryEntry + 7, // 53: waverpc.GetUnrollStatusResponse.status:type_name -> waverpc.UnrollJobStatus + 115, // 54: waverpc.GetUnrollStatusResponse.progress:type_name -> waverpc.UnrollProgress + 116, // 55: waverpc.GetUnrollStatusResponse.csv:type_name -> waverpc.UnrollCSV + 117, // 56: waverpc.GetUnrollStatusResponse.fees:type_name -> waverpc.UnrollFees + 8, // 57: waverpc.ArmVHTLCRecoveryRequest.direction:type_name -> waverpc.VHTLCRecoveryDirection + 9, // 58: waverpc.ArmVHTLCRecoveryRequest.action:type_name -> waverpc.VHTLCRecoveryAction + 129, // 59: waverpc.ArmVHTLCRecoveryResponse.status:type_name -> waverpc.VHTLCRecoveryStatus + 129, // 60: waverpc.EscalateVHTLCRecoveryResponse.status:type_name -> waverpc.VHTLCRecoveryStatus + 129, // 61: waverpc.CancelVHTLCRecoveryResponse.status:type_name -> waverpc.VHTLCRecoveryStatus + 129, // 62: waverpc.GetVHTLCRecoveryStatusResponse.status:type_name -> waverpc.VHTLCRecoveryStatus + 129, // 63: waverpc.ListVHTLCRecoveriesResponse.statuses:type_name -> waverpc.VHTLCRecoveryStatus + 8, // 64: waverpc.VHTLCRecoveryStatus.direction:type_name -> waverpc.VHTLCRecoveryDirection + 9, // 65: waverpc.VHTLCRecoveryStatus.action:type_name -> waverpc.VHTLCRecoveryAction + 10, // 66: waverpc.VHTLCRecoveryStatus.state:type_name -> waverpc.VHTLCRecoveryState + 7, // 67: waverpc.VHTLCRecoveryStatus.unroll_status:type_name -> waverpc.UnrollJobStatus + 75, // 68: waverpc.LeaveVTXOsRequest.DestinationsEntry.value:type_name -> waverpc.LeaveDestination + 11, // 69: waverpc.DaemonService.GetInfo:input_type -> waverpc.GetInfoRequest + 14, // 70: waverpc.DaemonService.GenSeed:input_type -> waverpc.GenSeedRequest + 16, // 71: waverpc.DaemonService.InitWallet:input_type -> waverpc.InitWalletRequest + 18, // 72: waverpc.DaemonService.UnlockWallet:input_type -> waverpc.UnlockWalletRequest + 20, // 73: waverpc.DaemonService.GetBalance:input_type -> waverpc.GetBalanceRequest + 25, // 74: waverpc.DaemonService.ListVTXOs:input_type -> waverpc.ListVTXOsRequest + 27, // 75: waverpc.DaemonService.NewAddress:input_type -> waverpc.NewAddressRequest + 29, // 76: waverpc.DaemonService.NewReceiveScript:input_type -> waverpc.NewReceiveScriptRequest + 31, // 77: waverpc.DaemonService.ReceiveAuthKey:input_type -> waverpc.ReceiveAuthKeyRequest + 33, // 78: waverpc.DaemonService.SignReceiveAuthMessage:input_type -> waverpc.SignReceiveAuthMessageRequest + 35, // 79: waverpc.DaemonService.SignReceiveAuthMessageCompact:input_type -> waverpc.SignReceiveAuthMessageCompactRequest + 37, // 80: waverpc.DaemonService.ReceiveAuthECDH:input_type -> waverpc.ReceiveAuthECDHRequest + 39, // 81: waverpc.DaemonService.GetIndexedVTXOByPkScript:input_type -> waverpc.GetIndexedVTXOByPkScriptRequest + 41, // 82: waverpc.DaemonService.GetVTXOExpiryInfo:input_type -> waverpc.GetVTXOExpiryInfoRequest + 43, // 83: waverpc.DaemonService.GetIndexedOORSessionByTxid:input_type -> waverpc.GetIndexedOORSessionByTxidRequest + 46, // 84: waverpc.DaemonService.SendVTXO:input_type -> waverpc.SendVTXORequest + 48, // 85: waverpc.DaemonService.SendOOR:input_type -> waverpc.SendOORRequest + 52, // 86: waverpc.DaemonService.PrepareOOR:input_type -> waverpc.PrepareOORRequest + 55, // 87: waverpc.DaemonService.SignOORCustomInput:input_type -> waverpc.SignOORCustomInputRequest + 57, // 88: waverpc.DaemonService.SignVTXOForfeit:input_type -> waverpc.SignVTXOForfeitRequest + 61, // 89: waverpc.DaemonService.RefreshVTXOs:input_type -> waverpc.RefreshVTXOsRequest + 67, // 90: waverpc.DaemonService.RefreshCustomVTXOs:input_type -> waverpc.RefreshCustomVTXOsRequest + 70, // 91: waverpc.DaemonService.ListPendingForfeitParticipantSignatureRequests:input_type -> waverpc.ListPendingForfeitParticipantSignatureRequestsRequest + 73, // 92: waverpc.DaemonService.SubmitForfeitParticipantSignatures:input_type -> waverpc.SubmitForfeitParticipantSignaturesRequest + 76, // 93: waverpc.DaemonService.LeaveVTXOs:input_type -> waverpc.LeaveVTXOsRequest + 78, // 94: waverpc.DaemonService.SendOnChain:input_type -> waverpc.SendOnChainRequest + 80, // 95: waverpc.DaemonService.Board:input_type -> waverpc.BoardRequest + 82, // 96: waverpc.DaemonService.JoinNextRound:input_type -> waverpc.JoinNextRoundRequest + 84, // 97: waverpc.DaemonService.SweepBoardingUTXOs:input_type -> waverpc.SweepBoardingUTXOsRequest + 87, // 98: waverpc.DaemonService.ListBoardingSweeps:input_type -> waverpc.ListBoardingSweepsRequest + 93, // 99: waverpc.DaemonService.ListRounds:input_type -> waverpc.ListRoundsRequest + 94, // 100: waverpc.DaemonService.GetRound:input_type -> waverpc.GetRoundRequest + 97, // 101: waverpc.DaemonService.WatchRounds:input_type -> waverpc.WatchRoundsRequest + 100, // 102: waverpc.DaemonService.ListOORSessions:input_type -> waverpc.ListOORSessionsRequest + 102, // 103: waverpc.DaemonService.GetOORSession:input_type -> waverpc.GetOORSessionRequest + 104, // 104: waverpc.DaemonService.EstimateFee:input_type -> waverpc.EstimateFeeRequest + 106, // 105: waverpc.DaemonService.GetFeeHistory:input_type -> waverpc.GetFeeHistoryRequest + 109, // 106: waverpc.DaemonService.ListTransactions:input_type -> waverpc.ListTransactionsRequest + 112, // 107: waverpc.DaemonService.Unroll:input_type -> waverpc.UnrollRequest + 114, // 108: waverpc.DaemonService.GetUnrollStatus:input_type -> waverpc.GetUnrollStatusRequest + 119, // 109: waverpc.DaemonService.ArmVHTLCRecovery:input_type -> waverpc.ArmVHTLCRecoveryRequest + 121, // 110: waverpc.DaemonService.EscalateVHTLCRecovery:input_type -> waverpc.EscalateVHTLCRecoveryRequest + 123, // 111: waverpc.DaemonService.CancelVHTLCRecovery:input_type -> waverpc.CancelVHTLCRecoveryRequest + 125, // 112: waverpc.DaemonService.GetVHTLCRecoveryStatus:input_type -> waverpc.GetVHTLCRecoveryStatusRequest + 127, // 113: waverpc.DaemonService.ListVHTLCRecoveries:input_type -> waverpc.ListVHTLCRecoveriesRequest + 12, // 114: waverpc.DaemonService.GetInfo:output_type -> waverpc.GetInfoResponse + 15, // 115: waverpc.DaemonService.GenSeed:output_type -> waverpc.GenSeedResponse + 17, // 116: waverpc.DaemonService.InitWallet:output_type -> waverpc.InitWalletResponse + 19, // 117: waverpc.DaemonService.UnlockWallet:output_type -> waverpc.UnlockWalletResponse + 21, // 118: waverpc.DaemonService.GetBalance:output_type -> waverpc.GetBalanceResponse + 26, // 119: waverpc.DaemonService.ListVTXOs:output_type -> waverpc.ListVTXOsResponse + 28, // 120: waverpc.DaemonService.NewAddress:output_type -> waverpc.NewAddressResponse + 30, // 121: waverpc.DaemonService.NewReceiveScript:output_type -> waverpc.NewReceiveScriptResponse + 32, // 122: waverpc.DaemonService.ReceiveAuthKey:output_type -> waverpc.ReceiveAuthKeyResponse + 34, // 123: waverpc.DaemonService.SignReceiveAuthMessage:output_type -> waverpc.SignReceiveAuthMessageResponse + 36, // 124: waverpc.DaemonService.SignReceiveAuthMessageCompact:output_type -> waverpc.SignReceiveAuthMessageCompactResponse + 38, // 125: waverpc.DaemonService.ReceiveAuthECDH:output_type -> waverpc.ReceiveAuthECDHResponse + 40, // 126: waverpc.DaemonService.GetIndexedVTXOByPkScript:output_type -> waverpc.GetIndexedVTXOByPkScriptResponse + 42, // 127: waverpc.DaemonService.GetVTXOExpiryInfo:output_type -> waverpc.GetVTXOExpiryInfoResponse + 44, // 128: waverpc.DaemonService.GetIndexedOORSessionByTxid:output_type -> waverpc.GetIndexedOORSessionByTxidResponse + 47, // 129: waverpc.DaemonService.SendVTXO:output_type -> waverpc.SendVTXOResponse + 51, // 130: waverpc.DaemonService.SendOOR:output_type -> waverpc.SendOORResponse + 54, // 131: waverpc.DaemonService.PrepareOOR:output_type -> waverpc.PrepareOORResponse + 56, // 132: waverpc.DaemonService.SignOORCustomInput:output_type -> waverpc.SignOORCustomInputResponse + 58, // 133: waverpc.DaemonService.SignVTXOForfeit:output_type -> waverpc.SignVTXOForfeitResponse + 62, // 134: waverpc.DaemonService.RefreshVTXOs:output_type -> waverpc.RefreshVTXOsResponse + 68, // 135: waverpc.DaemonService.RefreshCustomVTXOs:output_type -> waverpc.RefreshCustomVTXOsResponse + 71, // 136: waverpc.DaemonService.ListPendingForfeitParticipantSignatureRequests:output_type -> waverpc.ListPendingForfeitParticipantSignatureRequestsResponse + 74, // 137: waverpc.DaemonService.SubmitForfeitParticipantSignatures:output_type -> waverpc.SubmitForfeitParticipantSignaturesResponse + 77, // 138: waverpc.DaemonService.LeaveVTXOs:output_type -> waverpc.LeaveVTXOsResponse + 79, // 139: waverpc.DaemonService.SendOnChain:output_type -> waverpc.SendOnChainResponse + 81, // 140: waverpc.DaemonService.Board:output_type -> waverpc.BoardResponse + 83, // 141: waverpc.DaemonService.JoinNextRound:output_type -> waverpc.JoinNextRoundResponse + 86, // 142: waverpc.DaemonService.SweepBoardingUTXOs:output_type -> waverpc.SweepBoardingUTXOsResponse + 90, // 143: waverpc.DaemonService.ListBoardingSweeps:output_type -> waverpc.ListBoardingSweepsResponse + 96, // 144: waverpc.DaemonService.ListRounds:output_type -> waverpc.ListRoundsResponse + 95, // 145: waverpc.DaemonService.GetRound:output_type -> waverpc.GetRoundResponse + 98, // 146: waverpc.DaemonService.WatchRounds:output_type -> waverpc.WatchRoundsResponse + 101, // 147: waverpc.DaemonService.ListOORSessions:output_type -> waverpc.ListOORSessionsResponse + 103, // 148: waverpc.DaemonService.GetOORSession:output_type -> waverpc.GetOORSessionResponse + 105, // 149: waverpc.DaemonService.EstimateFee:output_type -> waverpc.EstimateFeeResponse + 108, // 150: waverpc.DaemonService.GetFeeHistory:output_type -> waverpc.GetFeeHistoryResponse + 111, // 151: waverpc.DaemonService.ListTransactions:output_type -> waverpc.ListTransactionsResponse + 113, // 152: waverpc.DaemonService.Unroll:output_type -> waverpc.UnrollResponse + 118, // 153: waverpc.DaemonService.GetUnrollStatus:output_type -> waverpc.GetUnrollStatusResponse + 120, // 154: waverpc.DaemonService.ArmVHTLCRecovery:output_type -> waverpc.ArmVHTLCRecoveryResponse + 122, // 155: waverpc.DaemonService.EscalateVHTLCRecovery:output_type -> waverpc.EscalateVHTLCRecoveryResponse + 124, // 156: waverpc.DaemonService.CancelVHTLCRecovery:output_type -> waverpc.CancelVHTLCRecoveryResponse + 126, // 157: waverpc.DaemonService.GetVHTLCRecoveryStatus:output_type -> waverpc.GetVHTLCRecoveryStatusResponse + 128, // 158: waverpc.DaemonService.ListVHTLCRecoveries:output_type -> waverpc.ListVHTLCRecoveriesResponse + 114, // [114:159] is the sub-list for method output_type + 69, // [69:114] is the sub-list for method input_type + 69, // [69:69] is the sub-list for extension type_name + 69, // [69:69] is the sub-list for extension extendee + 0, // [0:69] is the sub-list for field type_name } func init() { file_daemon_proto_init() } @@ -11035,15 +11296,16 @@ func file_daemon_proto_init() { (*RefreshVTXOsRequest_Outpoints)(nil), (*RefreshVTXOsRequest_All)(nil), } - file_daemon_proto_msgTypes[62].OneofWrappers = []any{ + file_daemon_proto_msgTypes[52].OneofWrappers = []any{} + file_daemon_proto_msgTypes[64].OneofWrappers = []any{ (*LeaveDestination_Address)(nil), (*LeaveDestination_PkScript)(nil), } - file_daemon_proto_msgTypes[63].OneofWrappers = []any{ + file_daemon_proto_msgTypes[65].OneofWrappers = []any{ (*LeaveVTXOsRequest_Outpoints)(nil), (*LeaveVTXOsRequest_All)(nil), } - file_daemon_proto_msgTypes[65].OneofWrappers = []any{ + file_daemon_proto_msgTypes[67].OneofWrappers = []any{ (*SendOnChainRequest_AmountSat)(nil), (*SendOnChainRequest_SweepAll)(nil), } @@ -11053,7 +11315,7 @@ func file_daemon_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_daemon_proto_rawDesc), len(file_daemon_proto_rawDesc)), NumEnums: 11, - NumMessages: 118, + NumMessages: 120, NumExtensions: 0, NumServices: 1, }, diff --git a/waverpc/daemon.proto b/waverpc/daemon.proto index 4ccc2c00b..237c611b0 100644 --- a/waverpc/daemon.proto +++ b/waverpc/daemon.proto @@ -1244,6 +1244,92 @@ message RefreshVTXOsResponse { // status is the result: "queued" on success, "preview" on dry_run. string status = 2; + + // fee_estimate carries the advisory operator-fee preview for the + // selected VTXOs. Only populated on dry_run previews with a + // non-empty selection. The binding fee is set by the server-issued + // JoinRoundQuote at seal time and may differ from this estimate. + RefreshFeeEstimate fee_estimate = 3; +} + +// RefreshFeeEstimate is the advisory operator-fee preview attached to a +// RefreshVTXOs dry_run response. It itemizes what refreshing the +// selected VTXOs is expected to cost at the operator's current rates, +// including whether the operator's free-refresh window applies. All +// values are advisory: the binding fee is set at seal time. +message RefreshFeeEstimate { + // estimated_total_fee_sat is the advisory total operator fee for + // refreshing the full selection. Explicit presence: the field is + // only set when the total is meaningful — either the per-outpoint + // quotes all succeeded, or free_refresh_eligible is true (the + // waiver is computed locally, so its zero total stays valid even + // when estimate_error is set). When the estimate degrades the + // field is absent, never zero, so a missing number can not be + // misread as a free refresh. + optional int64 estimated_total_fee_sat = 1; + + // outpoints itemizes the advisory per-outpoint quotes at the + // operator's current rates. Each row is the ordinary paid price and + // is NOT waiver-adjusted: its components always sum to the row's + // total_fee_sat, so under a free-refresh-eligible selection the rows + // show what the waiver saves. + repeated OutpointFeeEstimate outpoints = 2; + + // free_refresh_eligible is true when every selected VTXO is inside + // the operator's advertised free-refresh window, so refreshing + // exactly this selection on its own is expected to seal with all + // fee components waived. The waiver is all-or-nothing per round: + // batching this selection with boarding, leave, or out-of-window + // inputs forfeits it. + bool free_refresh_eligible = 3; + + // estimate_error is non-empty when the per-outpoint operator quotes + // could not be fetched (operator unreachable, chain height + // unavailable). The preview is still returned so dry_run keeps + // working as a validity probe; when set, treat the per-outpoint fee + // components and (unless free_refresh_eligible) the total as absent + // rather than zero. + string estimate_error = 4; +} + +// OutpointFeeEstimate is one selected VTXO's advisory refresh-fee +// quote, resolved from the daemon's own view of the VTXO (amount and +// remaining lifetime) so callers do not have to supply either. +message OutpointFeeEstimate { + // outpoint identifies the VTXO in "txid:index" format. + string outpoint = 1; + + // amount_sat is the VTXO amount the quote is for. + int64 amount_sat = 2; + + // remaining_blocks is the remaining VTXO lifetime used for the + // quote: batch expiry minus the current chain height. An expired or + // same-block-expiring VTXO is clamped to 1 so the quote never falls + // back to the operator's full-lifetime default for a zero value. + uint32 remaining_blocks = 3; + + // liquidity_fee_sat is the time-value-of-money component. + int64 liquidity_fee_sat = 4; + + // onchain_share_sat is the per-participant on-chain cost share. + int64 onchain_share_sat = 5; + + // margin_sat is the fixed operator margin. + int64 margin_sat = 6; + + // total_fee_sat is the sum of the components: the ordinary paid + // price for refreshing this VTXO, before any free-refresh waiver. + int64 total_fee_sat = 7; + + // in_free_refresh_window is true when this VTXO's remaining + // lifetime is inside the operator's advertised free-refresh window. + bool in_free_refresh_window = 8; + + // below_dust_warning mirrors the operator's warning that this + // VTXO's amount is below the minimum economically viable VTXO at + // current rates: refreshing it costs fees on a coin that already + // costs more to exit than it holds. + bool below_dust_warning = 9; } // CustomRefreshVTXOInput specifies one custom-policy VTXO input to refresh in