diff --git a/rpcserver.go b/rpcserver.go index 20fdf56db..d073a56c1 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -1067,7 +1067,8 @@ func allowCORS(handler http.Handler, origins []string) http.Handler { // address to a specified output value to be sent to that address. func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64, feeRate chainfee.SatPerKWeight, minConfs int32, label string, - strategy wallet.CoinSelectionStrategy) (*chainhash.Hash, error) { + strategy wallet.CoinSelectionStrategy, + selectedUtxos fn.Set[wire.OutPoint]) (*chainhash.Hash, error) { outputs, err := addrPairsToOutputs(paymentMap, r.cfg.ActiveNetParams.Params) if err != nil { @@ -1077,7 +1078,7 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64, // We first do a dry run, to sanity check we won't spend our wallet // balance below the reserved amount. authoredTx, err := r.server.cc.Wallet.CreateSimpleTx( - nil, outputs, feeRate, minConfs, strategy, true, + selectedUtxos, outputs, feeRate, minConfs, strategy, true, ) if err != nil { return nil, err @@ -1098,7 +1099,7 @@ func (r *rpcServer) sendCoinsOnChain(paymentMap map[string]int64, // If that checks out, we're fairly confident that creating sending to // these outputs will keep the wallet balance above the reserve. tx, err := r.server.cc.Wallet.SendOutputs( - nil, outputs, feeRate, minConfs, label, strategy, + selectedUtxos, outputs, feeRate, minConfs, label, strategy, ) if err != nil { return nil, err @@ -1290,9 +1291,9 @@ func (r *rpcServer) SendCoins(ctx context.Context, } rpcsLog.Infof("[sendcoins] addr=%v, amt=%v, sat/kw=%v, min_confs=%v, "+ - "send_all=%v", + "send_all=%v, select_outpoints=%v", in.Addr, btcutil.Amount(in.Amount), int64(feePerKw), minConfs, - in.SendAll) + in.SendAll, len(in.Outpoints)) // Decode the address receiving the coins, we need to check whether the // address is valid for this network. @@ -1337,6 +1338,22 @@ func (r *rpcServer) SendCoins(ctx context.Context, wallet := r.server.cc.Wallet maxFeeRate := r.cfg.Sweeper.MaxFeeRate.FeePerKWeight() + var selectOutpoints fn.Set[wire.OutPoint] + if len(in.Outpoints) != 0 { + wireOutpoints, err := toWireOutpoints(in.Outpoints) + if err != nil { + return nil, fmt.Errorf("can't create outpoints "+ + "%w", err) + } + + if fn.HasDuplicates(wireOutpoints) { + return nil, fmt.Errorf("selected outpoints contain " + + "duplicate values") + } + + selectOutpoints = fn.NewSet(wireOutpoints...) + } + // If the send all flag is active, then we'll attempt to sweep all the // coins in the wallet in a single transaction (if possible), // otherwise, we'll respect the amount, and attempt a regular 2-output @@ -1363,7 +1380,7 @@ func (r *rpcServer) SendCoins(ctx context.Context, sweepTxPkg, err := sweep.CraftSweepAllTx( feePerKw, maxFeeRate, uint32(bestHeight), nil, targetAddr, wallet, wallet, wallet.WalletController, - r.server.cc.Signer, minConfs, nil, + r.server.cc.Signer, minConfs, selectOutpoints, ) if err != nil { return nil, err @@ -1417,7 +1434,7 @@ func (r *rpcServer) SendCoins(ctx context.Context, feePerKw, maxFeeRate, uint32(bestHeight), outputs, targetAddr, wallet, wallet, wallet.WalletController, - r.server.cc.Signer, minConfs, nil, + r.server.cc.Signer, minConfs, selectOutpoints, ) if err != nil { return nil, err @@ -1443,7 +1460,7 @@ func (r *rpcServer) SendCoins(ctx context.Context, return nil, err } - rpcsLog.Debugf("Sweeping all coins from wallet to addr=%v, "+ + rpcsLog.Debugf("Sweeping coins from wallet to addr=%v, "+ "with tx=%v", in.Addr, spew.Sdump(sweepTxPkg.SweepTx)) // As our sweep transaction was created, successfully, we'll @@ -1468,8 +1485,8 @@ func (r *rpcServer) SendCoins(ctx context.Context, paymentMap := map[string]int64{targetAddr.String(): in.Amount} err := wallet.WithCoinSelectLock(func() error { newTXID, err := r.sendCoinsOnChain( - paymentMap, feePerKw, minConfs, - label, coinSelectionStrategy, + paymentMap, feePerKw, minConfs, label, + coinSelectionStrategy, selectOutpoints, ) if err != nil { return err @@ -1540,8 +1557,8 @@ func (r *rpcServer) SendMany(ctx context.Context, wallet := r.server.cc.Wallet err = wallet.WithCoinSelectLock(func() error { sendManyTXID, err := r.sendCoinsOnChain( - in.AddrToAmount, feePerKw, minConfs, - label, coinSelectionStrategy, + in.AddrToAmount, feePerKw, minConfs, label, + coinSelectionStrategy, nil, ) if err != nil { return err