Merge pull request #1150 from hieblmi/show-static-details
Some checks failed
CI / RPC compilation check (push) Has been cancelled
CI / SQL compilation check (push) Has been cancelled
CI / go mod check (push) Has been cancelled
CI / build and lint code (push) Has been cancelled
CI / verify that auto-generated documentation is up-to-date (push) Has been cancelled
CI / run unit-test sqlite3 race (push) Has been cancelled
CI / run unit-test postgres race (push) Has been cancelled
CI / run LiT itests (push) Has been cancelled
CI / run LiT unit tests (push) Has been cancelled

looprpc: populate static address swap timing and cost fields
This commit is contained in:
Slyghtning 2026-06-03 20:48:18 +02:00 committed by GitHub
commit f879c52e65
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 413 additions and 12 deletions

View file

@ -349,7 +349,7 @@ func listWithdrawals(ctx context.Context, cmd *cli.Command) error {
var listStaticAddressSwapsCommand = &cli.Command{
Name: "listswaps",
Usage: "Shows a list of finalized static address swaps.",
Usage: "Shows a list of static address swaps.",
Description: `
`,
Action: listStaticAddressSwaps,

View file

@ -28,7 +28,7 @@
" listunspent, l List unspent static address outputs.\n",
" listdeposits Displays static address deposits. A filter can be applied to only show deposits in a specific state.\n",
" listwithdrawals Display a summary of past withdrawals.\n",
" listswaps Shows a list of finalized static address swaps.\n",
" listswaps Shows a list of static address swaps.\n",
" withdraw, w Withdraw from static address deposits.\n",
" summary, s Display a summary of static address related information.\n",
" in Loop in funds from static address deposits.\n",

View file

@ -511,7 +511,7 @@ Display a summary of past withdrawals.
.SS listswaps
.PP
Shows a list of finalized static address swaps.
Shows a list of static address swaps.
.PP
\fB--help, -h\fP: show help

View file

@ -601,7 +601,7 @@ The following flags are supported:
### `static listswaps` subcommand
Shows a list of finalized static address swaps.
Shows a list of static address swaps.
Usage:

4
go.mod
View file

@ -238,4 +238,8 @@ replace gonum.org/v1/gonum => github.com/gonum/gonum v0.11.0
replace gonum.org/v1/plot => github.com/gonum/plot v0.10.1
// the replaced domain disappeared and package moved to new location. Worth
// checking later if the domain reappears and replace can be removed.
replace dario.cat/mergo => github.com/darccio/mergo v1.0.1
go 1.25.5

4
go.sum
View file

@ -592,8 +592,6 @@ cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoIS
cloud.google.com/go/workflows v1.8.0/go.mod h1:ysGhmEajwZxGn1OhGOGKsTXc5PyxOc0vfKf5Af+to4M=
cloud.google.com/go/workflows v1.9.0/go.mod h1:ZGkj1aFIOd9c8Gerkjjq7OW7I5+l6cSvT3ujaO/WwSA=
cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8=
git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc=
@ -747,6 +745,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/darccio/mergo v1.0.1 h1:xYD7+aWQRjjQuN2qd0DjhRavXPuEnm8pMIRJMY1AAiU=
github.com/darccio/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=

View file

@ -1966,6 +1966,10 @@ func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
var clientSwaps []*looprpc.StaticAddressLoopInSwap
for _, swp := range swaps {
if swp == nil {
continue
}
chainParams, err := s.network.ChainParams()
if err != nil {
return nil, fmt.Errorf("error getting chain params")
@ -2005,6 +2009,9 @@ func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
if swp.SelectedAmount > 0 {
swapAmount = swp.SelectedAmount
}
costServer := staticAddressLoopInSwapServerCost(swp)
initiationTime := staticAddressLoopInTimestamp(swp.InitiationTime)
lastUpdateTime := staticAddressLoopInTimestamp(swp.LastUpdateTime)
swap := &looprpc.StaticAddressLoopInSwap{
SwapHash: swp.SwapHash[:],
DepositOutpoints: swp.DepositOutpoints,
@ -2012,6 +2019,9 @@ func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
SwapAmountSatoshis: int64(swapAmount),
PaymentRequestAmountSatoshis: payReqAmount,
Deposits: protoDeposits,
InitiationTime: initiationTime,
LastUpdateTime: lastUpdateTime,
CostServer: costServer,
}
clientSwaps = append(clientSwaps, swap)
@ -2022,6 +2032,31 @@ func (s *swapClientServer) ListStaticAddressSwaps(ctx context.Context,
}, nil
}
func staticAddressLoopInTimestamp(t time.Time) int64 {
if t.IsZero() {
return 0
}
return t.UnixNano()
}
// staticAddressLoopInSwapServerCost returns the paid server cost using the
// legacy ListSwaps cost semantics. Static loop-ins currently only persist the
// accepted quote fee, and that fee is paid once the swap invoice settles.
// Timeout-path miner fees are not persisted, so cost_onchain and cost_offchain
// remain zero instead of returning an estimate as an actual cost.
func staticAddressLoopInSwapServerCost(swp *loopin.StaticAddressLoopIn) int64 {
switch swp.GetState() {
case loopin.PaymentReceived, loopin.Succeeded,
loopin.SucceededTransitioningFailed:
return int64(swp.QuotedSwapFee)
default:
return 0
}
}
// GetStaticAddressSummary returns a summary of static address-related
// information. Amongst deposits and withdrawals and their total values, it also
// includes a list of detailed deposit information filtered by their state.

View file

@ -20,9 +20,11 @@ import (
"github.com/lightninglabs/loop/looprpc"
"github.com/lightninglabs/loop/staticaddr/address"
"github.com/lightninglabs/loop/staticaddr/deposit"
"github.com/lightninglabs/loop/staticaddr/loopin"
"github.com/lightninglabs/loop/staticaddr/script"
"github.com/lightninglabs/loop/swap"
mock_lnd "github.com/lightninglabs/loop/test"
"github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
"github.com/lightningnetwork/lnd/lntypes"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire"
@ -306,6 +308,254 @@ func TestSetLiquidityParamsRejectsStaticAutoloopWithoutExperimental(
require.ErrorContains(t, err, "--experimental")
}
// TestStaticAddressLoopInTimestamp verifies that zero timestamps are omitted
// from static loop-in responses instead of passing a zero time to UnixNano.
func TestStaticAddressLoopInTimestamp(t *testing.T) {
require.Zero(t, staticAddressLoopInTimestamp(time.Time{}))
timestamp := time.Unix(1_234, 567).UTC()
require.Equal(
t, timestamp.UnixNano(),
staticAddressLoopInTimestamp(timestamp),
)
}
// TestStaticAddressLoopInSwapServerCost verifies that static loop-in server
// costs are only reported once the invoice payment was received. Timeout path
// costs are not persisted today, so they are intentionally not estimated here.
func TestStaticAddressLoopInSwapServerCost(t *testing.T) {
const quoteFee = btcutil.Amount(1_234)
tests := []struct {
name string
state fsm.StateType
wantServer int64
}{
{
name: "pending before payment",
state: loopin.SignHtlcTx,
},
{
name: "payment received",
state: loopin.PaymentReceived,
wantServer: int64(quoteFee),
},
{
name: "succeeded",
state: loopin.Succeeded,
wantServer: int64(quoteFee),
},
{
name: "succeeded transition failed",
state: loopin.SucceededTransitioningFailed,
wantServer: int64(quoteFee),
},
{
name: "timeout swept",
state: loopin.HtlcTimeoutSwept,
},
{
name: "failed",
state: loopin.Failed,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
swap := &loopin.StaticAddressLoopIn{
QuotedSwapFee: quoteFee,
}
swap.SetState(test.state)
costServer := staticAddressLoopInSwapServerCost(swap)
require.Equal(t, test.wantServer, costServer)
})
}
}
// TestListStaticAddressSwapsPopulatesTimingAndCosts verifies that the RPC
// response maps stored static loop-in timing fields and cost fields.
func TestListStaticAddressSwapsPopulatesTimingAndCosts(t *testing.T) {
ctx := t.Context()
lnd := mock_lnd.NewMockLnd()
const (
paymentRequestAmount = btcutil.Amount(50_000)
quotedSwapFee = btcutil.Amount(1_234)
depositValue = btcutil.Amount(51_234)
depositConfHeight = int64(590)
staticAddressExpiry = uint32(25)
)
_, swapInvoice, err := lnd.Client.AddInvoice(
ctx, &invoicesrpc.AddInvoiceData{
Value: lnwire.NewMSatFromSatoshis(paymentRequestAmount),
},
)
require.NoError(t, err)
swapHash := lntypes.Hash{1, 2, 3}
depositID := deposit.ID{4, 5, 6}
depositOutpoint := wire.OutPoint{
Hash: chainhash.Hash{7, 8, 9},
Index: 1,
}
testDeposit := &deposit.Deposit{
ID: depositID,
OutPoint: depositOutpoint,
Value: depositValue,
ConfirmationHeight: depositConfHeight,
SwapHash: &swapHash,
}
testDeposit.SetState(deposit.LoopedIn)
initiationTime := time.Unix(1_234, 567).UTC()
lastUpdateTime := time.Unix(2_345, 678).UTC()
staticLoopIn := &loopin.StaticAddressLoopIn{
SwapHash: swapHash,
SwapInvoice: swapInvoice,
InitiationTime: initiationTime,
LastUpdateTime: lastUpdateTime,
QuotedSwapFee: quotedSwapFee,
DepositOutpoints: []string{depositOutpoint.String()},
Deposits: []*deposit.Deposit{testDeposit},
}
staticLoopIn.SetState(loopin.Succeeded)
depositStore := &mockDepositStore{
byOutpoint: map[string]*deposit.Deposit{
depositOutpoint.String(): testDeposit,
},
}
depositMgr := deposit.NewManager(&deposit.ManagerConfig{
Store: depositStore,
})
staticLoopInMgr, err := loopin.NewManager(&loopin.Config{
Store: &mockStaticAddressLoopInStore{
swaps: []*loopin.StaticAddressLoopIn{staticLoopIn},
},
DepositManager: depositMgr,
}, 1)
require.NoError(t, err)
_, clientPubkey := mock_lnd.CreateKey(1)
_, serverPubkey := mock_lnd.CreateKey(2)
addrStore := &mockAddressStore{
params: []*script.Parameters{{
ClientPubkey: clientPubkey,
ServerPubkey: serverPubkey,
Expiry: staticAddressExpiry,
PkScript: []byte("pkscript"),
}},
}
addrMgr, err := address.NewManager(&address.ManagerConfig{
Store: addrStore,
WalletKit: lnd.WalletKit,
ChainParams: lnd.ChainParams,
}, 1)
require.NoError(t, err)
server := &swapClientServer{
network: lndclient.NetworkTestnet,
lnd: &lnd.LndServices,
staticAddressManager: addrMgr,
depositManager: depositMgr,
staticLoopInManager: staticLoopInMgr,
}
resp, err := server.ListStaticAddressSwaps(
ctx, &looprpc.ListStaticAddressSwapsRequest{},
)
require.NoError(t, err)
require.Len(t, resp.Swaps, 1)
swap := resp.Swaps[0]
require.Equal(t, swapHash[:], swap.SwapHash)
require.Equal(t, []string{depositOutpoint.String()}, swap.DepositOutpoints)
require.Equal(
t, looprpc.StaticAddressLoopInSwapState_SUCCEEDED, swap.State,
)
require.Equal(t, int64(depositValue), swap.SwapAmountSatoshis)
require.Equal(
t, int64(paymentRequestAmount), swap.PaymentRequestAmountSatoshis,
)
require.Equal(t, initiationTime.UnixNano(), swap.InitiationTime)
require.Equal(t, lastUpdateTime.UnixNano(), swap.LastUpdateTime)
require.Equal(t, int64(quotedSwapFee), swap.CostServer)
require.Zero(t, swap.CostOnchain)
require.Zero(t, swap.CostOffchain)
require.Len(t, swap.Deposits, 1)
rpcDeposit := swap.Deposits[0]
require.Equal(t, depositID[:], rpcDeposit.Id)
require.Equal(t, depositOutpoint.String(), rpcDeposit.Outpoint)
require.Equal(t, int64(depositValue), rpcDeposit.Value)
require.Equal(t, depositConfHeight, rpcDeposit.ConfirmationHeight)
require.Equal(t, swapHash[:], rpcDeposit.SwapHash)
require.Equal(t, looprpc.DepositState_LOOPED_IN, rpcDeposit.State)
require.Equal(
t, depositConfHeight+int64(staticAddressExpiry)-600,
rpcDeposit.BlocksUntilExpiry,
)
}
// mockStaticAddressLoopInStore is a minimal in-memory loop-in store for RPC
// response mapping tests.
type mockStaticAddressLoopInStore struct {
swaps []*loopin.StaticAddressLoopIn
}
// CreateLoopIn satisfies the static loop-in store interface.
func (s *mockStaticAddressLoopInStore) CreateLoopIn(_ context.Context,
_ *loopin.StaticAddressLoopIn) error {
return nil
}
// UpdateLoopIn satisfies the static loop-in store interface.
func (s *mockStaticAddressLoopInStore) UpdateLoopIn(_ context.Context,
_ *loopin.StaticAddressLoopIn) error {
return nil
}
// GetStaticAddressLoopInSwapsByStates returns the configured loop-ins.
func (s *mockStaticAddressLoopInStore) GetStaticAddressLoopInSwapsByStates(
_ context.Context, _ []fsm.StateType) ([]*loopin.StaticAddressLoopIn,
error) {
return s.swaps, nil
}
// IsStored satisfies the static loop-in store interface.
func (s *mockStaticAddressLoopInStore) IsStored(_ context.Context,
_ lntypes.Hash) (bool, error) {
return false, nil
}
// GetLoopInByHash returns the configured loop-in with the given hash.
func (s *mockStaticAddressLoopInStore) GetLoopInByHash(_ context.Context,
swapHash lntypes.Hash) (*loopin.StaticAddressLoopIn, error) {
for _, swp := range s.swaps {
if swp.SwapHash == swapHash {
return swp, nil
}
}
return nil, nil
}
// SwapHashesForDepositIDs satisfies the static loop-in store interface.
func (s *mockStaticAddressLoopInStore) SwapHashesForDepositIDs(
_ context.Context, _ []deposit.ID) (map[lntypes.Hash][]deposit.ID,
error) {
return nil, nil
}
// TestRPCAutoloopReasonStaticLoopInNoCandidate verifies that the new planner
// reason is exposed over rpc.
func TestRPCAutoloopReasonStaticLoopInNoCandidate(t *testing.T) {
@ -1073,10 +1323,16 @@ func (s *mockDepositStore) DepositForOutpoint(_ context.Context,
}
return nil, nil
}
func (s *mockDepositStore) AllDeposits(_ context.Context) ([]*deposit.Deposit,
error) {
return nil, nil
deposits := make([]*deposit.Deposit, 0, len(s.byOutpoint))
for _, d := range s.byOutpoint {
deposits = append(deposits, d)
}
return deposits, nil
}
// TestListUnspentDeposits tests filtering behavior of ListUnspentDeposits.

View file

@ -5874,7 +5874,17 @@ type StaticAddressLoopInSwap struct {
// fees.
PaymentRequestAmountSatoshis int64 `protobuf:"varint,5,opt,name=payment_request_amount_satoshis,json=paymentRequestAmountSatoshis,proto3" json:"payment_request_amount_satoshis,omitempty"`
// The deposits that were used for this swap.
Deposits []*Deposit `protobuf:"bytes,6,rep,name=deposits,proto3" json:"deposits,omitempty"`
Deposits []*Deposit `protobuf:"bytes,6,rep,name=deposits,proto3" json:"deposits,omitempty"`
// Initiation time of the swap.
InitiationTime int64 `protobuf:"varint,7,opt,name=initiation_time,json=initiationTime,proto3" json:"initiation_time,omitempty"`
// Last update time of the swap.
LastUpdateTime int64 `protobuf:"varint,8,opt,name=last_update_time,json=lastUpdateTime,proto3" json:"last_update_time,omitempty"`
// Swap server cost.
CostServer int64 `protobuf:"varint,9,opt,name=cost_server,json=costServer,proto3" json:"cost_server,omitempty"`
// On-chain transaction cost.
CostOnchain int64 `protobuf:"varint,10,opt,name=cost_onchain,json=costOnchain,proto3" json:"cost_onchain,omitempty"`
// Off-chain routing fees.
CostOffchain int64 `protobuf:"varint,11,opt,name=cost_offchain,json=costOffchain,proto3" json:"cost_offchain,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -5951,6 +5961,41 @@ func (x *StaticAddressLoopInSwap) GetDeposits() []*Deposit {
return nil
}
func (x *StaticAddressLoopInSwap) GetInitiationTime() int64 {
if x != nil {
return x.InitiationTime
}
return 0
}
func (x *StaticAddressLoopInSwap) GetLastUpdateTime() int64 {
if x != nil {
return x.LastUpdateTime
}
return 0
}
func (x *StaticAddressLoopInSwap) GetCostServer() int64 {
if x != nil {
return x.CostServer
}
return 0
}
func (x *StaticAddressLoopInSwap) GetCostOnchain() int64 {
if x != nil {
return x.CostOnchain
}
return 0
}
func (x *StaticAddressLoopInSwap) GetCostOffchain() int64 {
if x != nil {
return x.CostOffchain
}
return 0
}
type StaticAddressLoopInRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The outpoints of the deposits to loop-in.
@ -6972,14 +7017,21 @@ const file_client_proto_rawDesc = "" +
"\x1dtotal_deposit_amount_satoshis\x18\x03 \x01(\x03R\x1atotalDepositAmountSatoshis\x12:\n" +
"\x19withdrawn_amount_satoshis\x18\x04 \x01(\x03R\x17withdrawnAmountSatoshis\x124\n" +
"\x16change_amount_satoshis\x18\x05 \x01(\x03R\x14changeAmountSatoshis\x12/\n" +
"\x13confirmation_height\x18\x06 \x01(\rR\x12confirmationHeight\"\xc7\x02\n" +
"\x13confirmation_height\x18\x06 \x01(\rR\x12confirmationHeight\"\x83\x04\n" +
"\x17StaticAddressLoopInSwap\x12\x1b\n" +
"\tswap_hash\x18\x01 \x01(\fR\bswapHash\x12+\n" +
"\x11deposit_outpoints\x18\x02 \x03(\tR\x10depositOutpoints\x12;\n" +
"\x05state\x18\x03 \x01(\x0e2%.looprpc.StaticAddressLoopInSwapStateR\x05state\x120\n" +
"\x14swap_amount_satoshis\x18\x04 \x01(\x03R\x12swapAmountSatoshis\x12E\n" +
"\x1fpayment_request_amount_satoshis\x18\x05 \x01(\x03R\x1cpaymentRequestAmountSatoshis\x12,\n" +
"\bdeposits\x18\x06 \x03(\v2\x10.looprpc.DepositR\bdeposits\"\xef\x02\n" +
"\bdeposits\x18\x06 \x03(\v2\x10.looprpc.DepositR\bdeposits\x12'\n" +
"\x0finitiation_time\x18\a \x01(\x03R\x0einitiationTime\x12(\n" +
"\x10last_update_time\x18\b \x01(\x03R\x0elastUpdateTime\x12\x1f\n" +
"\vcost_server\x18\t \x01(\x03R\n" +
"costServer\x12!\n" +
"\fcost_onchain\x18\n" +
" \x01(\x03R\vcostOnchain\x12#\n" +
"\rcost_offchain\x18\v \x01(\x03R\fcostOffchain\"\xef\x02\n" +
"\x1aStaticAddressLoopInRequest\x12\x1c\n" +
"\toutpoints\x18\x01 \x03(\tR\toutpoints\x121\n" +
"\x15max_swap_fee_satoshis\x18\x02 \x01(\x03R\x12maxSwapFeeSatoshis\x12\x19\n" +

View file

@ -2143,6 +2143,31 @@ message StaticAddressLoopInSwap {
The deposits that were used for this swap.
*/
repeated Deposit deposits = 6;
/*
Initiation time of the swap.
*/
int64 initiation_time = 7;
/*
Last update time of the swap.
*/
int64 last_update_time = 8;
/*
Swap server cost.
*/
int64 cost_server = 9;
/*
On-chain transaction cost.
*/
int64 cost_onchain = 10;
/*
Off-chain routing fees.
*/
int64 cost_offchain = 11;
}
enum StaticAddressLoopInSwapState {

View file

@ -2807,6 +2807,31 @@
"$ref": "#/definitions/looprpcDeposit"
},
"description": "The deposits that were used for this swap."
},
"initiation_time": {
"type": "string",
"format": "int64",
"description": "Initiation time of the swap."
},
"last_update_time": {
"type": "string",
"format": "int64",
"description": "Last update time of the swap."
},
"cost_server": {
"type": "string",
"format": "int64",
"description": "Swap server cost."
},
"cost_onchain": {
"type": "string",
"format": "int64",
"description": "On-chain transaction cost."
},
"cost_offchain": {
"type": "string",
"format": "int64",
"description": "Off-chain routing fees."
}
}
},

View file

@ -199,3 +199,7 @@ replace gonum.org/v1/gonum => github.com/gonum/gonum v0.11.0
replace github.com/golang-migrate/migrate/v4 => github.com/lightninglabs/migrate/v4 v4.18.2-9023d66a-fork-pr-2
replace lukechampine.com/uint128 => github.com/lukechampine/uint128 v1.2.0
// the replaced domain disappeared and package moved to new location. Worth
// checking later if the domain reappears and replace can be removed.
replace dario.cat/mergo => github.com/darccio/mergo v1.0.1

View file

@ -1,7 +1,5 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@ -103,6 +101,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/darccio/mergo v1.0.1 h1:xYD7+aWQRjjQuN2qd0DjhRavXPuEnm8pMIRJMY1AAiU=
github.com/darccio/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=