mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 10:46:42 +01:00
Wallet send from outpoints (#1405)
This commit is contained in:
parent
583da51958
commit
c571585b3b
10 changed files with 347 additions and 50 deletions
|
@ -2,7 +2,7 @@ package org.bitcoins.commons.serializers
|
|||
|
||||
import org.bitcoins.core.crypto.ExtPublicKey
|
||||
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutPoint}
|
||||
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
|
@ -38,4 +38,7 @@ object Picklers {
|
|||
|
||||
implicit val extPubKeyPickler: ReadWriter[ExtPublicKey] =
|
||||
readwriter[String].bimap(_.toString, ExtPublicKey.fromString(_).get)
|
||||
|
||||
implicit val transactionOutPointPickler: ReadWriter[TransactionOutPoint] =
|
||||
readwriter[String].bimap(_.hex, TransactionOutPoint.fromHex)
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import org.bitcoins.core.config.{NetworkParameters, Networks}
|
|||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.protocol.BlockStamp.BlockTime
|
||||
import org.bitcoins.core.protocol._
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutPoint}
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
import scopt._
|
||||
|
@ -86,4 +86,11 @@ object CliReaders {
|
|||
|
||||
val reads: String => Transaction = Transaction.fromHex
|
||||
}
|
||||
|
||||
implicit val outPointsRead: Read[TransactionOutPoint] =
|
||||
new Read[TransactionOutPoint] {
|
||||
val arity: Int = 1
|
||||
|
||||
val reads: String => TransactionOutPoint = TransactionOutPoint.fromHex
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,11 @@ import org.bitcoins.cli.CliCommand._
|
|||
import org.bitcoins.cli.CliReaders._
|
||||
import org.bitcoins.core.config.NetworkParameters
|
||||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.protocol.transaction.{EmptyTransaction, Transaction}
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
EmptyTransaction,
|
||||
Transaction,
|
||||
TransactionOutPoint
|
||||
}
|
||||
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
|
@ -31,8 +35,8 @@ object ConsoleCli {
|
|||
.action((_, conf) => conf.copy(debug = true))
|
||||
.text("Print debugging information"),
|
||||
opt[Int]("rpcport")
|
||||
.action((port,conf) => conf.copy(rpcPort = port))
|
||||
.text(s"The port to send our rpc request to on the server"),
|
||||
.action((port, conf) => conf.copy(rpcPort = port))
|
||||
.text(s"The port to send our rpc request to on the server"),
|
||||
help('h', "help").text("Display this help message and exit"),
|
||||
note(sys.props("line.separator") + "Commands:"),
|
||||
note(sys.props("line.separator") + "===Blockchain ==="),
|
||||
|
@ -218,6 +222,49 @@ object ConsoleCli {
|
|||
case other => other
|
||||
}))
|
||||
),
|
||||
cmd("sendfromoutpoints")
|
||||
.action((_, conf) =>
|
||||
conf.copy(
|
||||
command = SendFromOutPoints(Vector.empty, null, 0.bitcoin, None)))
|
||||
.text("Send money to the given address")
|
||||
.children(
|
||||
arg[Seq[TransactionOutPoint]]("outpoints")
|
||||
.text("Out Points to send from")
|
||||
.required()
|
||||
.action((outPoints, conf) =>
|
||||
conf.copy(command = conf.command match {
|
||||
case send: SendFromOutPoints =>
|
||||
send.copy(outPoints = outPoints.toVector)
|
||||
case other => other
|
||||
})),
|
||||
arg[BitcoinAddress]("address")
|
||||
.text("Address to send to")
|
||||
.required()
|
||||
.action((addr, conf) =>
|
||||
conf.copy(command = conf.command match {
|
||||
case send: SendFromOutPoints =>
|
||||
send.copy(destination = addr)
|
||||
case other => other
|
||||
})),
|
||||
arg[Bitcoins]("amount")
|
||||
.text("amount to send in BTC")
|
||||
.required()
|
||||
.action((btc, conf) =>
|
||||
conf.copy(command = conf.command match {
|
||||
case send: SendFromOutPoints =>
|
||||
send.copy(amount = btc)
|
||||
case other => other
|
||||
})),
|
||||
opt[SatoshisPerVirtualByte]("feerate")
|
||||
.text("Fee rate in sats per virtual byte")
|
||||
.optional()
|
||||
.action((feeRate, conf) =>
|
||||
conf.copy(command = conf.command match {
|
||||
case send: SendFromOutPoints =>
|
||||
send.copy(satoshisPerVirtualByte = Some(feeRate))
|
||||
case other => other
|
||||
}))
|
||||
),
|
||||
note(sys.props("line.separator") + "=== Network ==="),
|
||||
cmd("getpeers")
|
||||
.action((_, conf) => conf.copy(command = GetPeers))
|
||||
|
@ -382,6 +429,15 @@ object ConsoleCli {
|
|||
Seq(up.writeJs(address),
|
||||
up.writeJs(bitcoins),
|
||||
up.writeJs(satoshisPerVirtualByte)))
|
||||
case SendFromOutPoints(outPoints,
|
||||
address,
|
||||
bitcoins,
|
||||
satoshisPerVirtualByte) =>
|
||||
RequestParam("sendfromoutpoints",
|
||||
Seq(up.writeJs(outPoints),
|
||||
up.writeJs(address),
|
||||
up.writeJs(bitcoins),
|
||||
up.writeJs(satoshisPerVirtualByte)))
|
||||
// height
|
||||
case GetBlockCount => RequestParam("getblockcount")
|
||||
// filter count
|
||||
|
@ -505,6 +561,12 @@ object CliCommand {
|
|||
amount: Bitcoins,
|
||||
satoshisPerVirtualByte: Option[SatoshisPerVirtualByte])
|
||||
extends CliCommand
|
||||
case class SendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
destination: BitcoinAddress,
|
||||
amount: Bitcoins,
|
||||
satoshisPerVirtualByte: Option[SatoshisPerVirtualByte])
|
||||
extends CliCommand
|
||||
case object GetNewAddress extends CliCommand
|
||||
case object GetUtxos extends CliCommand
|
||||
case object GetAddresses extends CliCommand
|
||||
|
|
|
@ -17,12 +17,7 @@ import org.bitcoins.core.protocol.BlockStamp.{
|
|||
InvalidBlockStamp
|
||||
}
|
||||
import org.bitcoins.core.protocol.script.EmptyScriptWitness
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
EmptyTransaction,
|
||||
EmptyTransactionOutPoint,
|
||||
EmptyTransactionOutput,
|
||||
Transaction
|
||||
}
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp, P2PKHAddress}
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.core.util.FutureUtil
|
||||
|
@ -477,6 +472,85 @@ class RoutesSpec
|
|||
|
||||
}
|
||||
|
||||
"send from outpoints" in {
|
||||
// positive cases
|
||||
|
||||
(mockWalletApi
|
||||
.sendFromOutPoints(_: Vector[TransactionOutPoint],
|
||||
_: BitcoinAddress,
|
||||
_: CurrencyUnit,
|
||||
_: FeeUnit))
|
||||
.expects(Vector.empty[TransactionOutPoint],
|
||||
testAddress,
|
||||
Bitcoins(100),
|
||||
*)
|
||||
.returning(Future.successful(EmptyTransaction))
|
||||
|
||||
(mockNode.broadcastTransaction _)
|
||||
.expects(EmptyTransaction)
|
||||
.returning(FutureUtil.unit)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
val route = walletRoutes.handleCommand(
|
||||
ServerCommand("sendfromoutpoints",
|
||||
Arr(Arr(), Str(testAddressStr), Num(100), Num(4))))
|
||||
|
||||
Post() ~> route ~> check {
|
||||
contentType shouldEqual `application/json`
|
||||
responseAs[String] shouldEqual """{"result":"0000000000000000000000000000000000000000000000000000000000000000","error":null}"""
|
||||
}
|
||||
|
||||
// negative cases
|
||||
|
||||
val route1 = walletRoutes.handleCommand(
|
||||
ServerCommand("sendfromoutpoints", Arr(Arr(), Null, Null, Null)))
|
||||
|
||||
Post() ~> route1 ~> check {
|
||||
rejection shouldEqual ValidationRejection(
|
||||
"failure",
|
||||
Some(InvalidData(Null, "Expected ujson.Str")))
|
||||
}
|
||||
|
||||
val route2 = walletRoutes.handleCommand(
|
||||
ServerCommand("sendfromoutpoints", Arr(Arr(), "Null", Null, Null)))
|
||||
|
||||
Post() ~> route2 ~> check {
|
||||
rejection shouldEqual ValidationRejection(
|
||||
"failure",
|
||||
Some(InvalidData("Null", "Expected a valid address")))
|
||||
}
|
||||
|
||||
val route3 = walletRoutes.handleCommand(
|
||||
ServerCommand("sendfromoutpoints",
|
||||
Arr(Arr(), Str(testAddressStr), Null, Null)))
|
||||
|
||||
Post() ~> route3 ~> check {
|
||||
rejection shouldEqual ValidationRejection(
|
||||
"failure",
|
||||
Some(InvalidData(Null, "Expected ujson.Num")))
|
||||
}
|
||||
|
||||
val route4 = walletRoutes.handleCommand(
|
||||
ServerCommand("sendfromoutpoints",
|
||||
Arr(Arr(), Str(testAddressStr), Str("abc"), Null)))
|
||||
|
||||
Post() ~> route4 ~> check {
|
||||
rejection shouldEqual ValidationRejection(
|
||||
"failure",
|
||||
Some(InvalidData("abc", "Expected ujson.Num")))
|
||||
}
|
||||
|
||||
val route5 = walletRoutes.handleCommand(
|
||||
ServerCommand("sendfromoutpoints",
|
||||
Arr(Null, Str(testAddressStr), Num(100), Num(4))))
|
||||
|
||||
Post() ~> route5 ~> check {
|
||||
rejection shouldEqual ValidationRejection(
|
||||
"failure",
|
||||
Some(InvalidData(Null, "Expected ujson.Arr")))
|
||||
}
|
||||
}
|
||||
|
||||
"return the peer list" in {
|
||||
val route =
|
||||
nodeRoutes.handleCommand(ServerCommand("getpeers", Arr()))
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.bitcoins.server
|
|||
|
||||
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
|
||||
import org.bitcoins.core.protocol.BlockStamp.BlockHeight
|
||||
import org.bitcoins.core.protocol.transaction.Transaction
|
||||
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutPoint}
|
||||
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||
|
@ -235,6 +235,43 @@ object SendToAddress extends ServerJsonModels {
|
|||
|
||||
}
|
||||
|
||||
case class SendFromOutpoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: Bitcoins,
|
||||
satoshisPerVirtualByte: Option[SatoshisPerVirtualByte])
|
||||
|
||||
object SendFromOutpoints extends ServerJsonModels {
|
||||
|
||||
def fromJsArr(jsArr: ujson.Arr): Try[SendFromOutpoints] = {
|
||||
jsArr.arr.toList match {
|
||||
case outPointsJs :: addrJs :: bitcoinsJs :: satsPerVBytesJs :: Nil =>
|
||||
Try {
|
||||
val outPoints = jsToTransactionOutPointSeq(outPointsJs).toVector
|
||||
val address = jsToBitcoinAddress(addrJs)
|
||||
val bitcoins = Bitcoins(bitcoinsJs.num)
|
||||
val satoshisPerVirtualByte =
|
||||
nullToOpt(satsPerVBytesJs).map(satsPerVBytes =>
|
||||
SatoshisPerVirtualByte(Satoshis(satsPerVBytes.num.toLong)))
|
||||
SendFromOutpoints(outPoints,
|
||||
address,
|
||||
bitcoins,
|
||||
satoshisPerVirtualByte)
|
||||
}
|
||||
case Nil =>
|
||||
Failure(
|
||||
new IllegalArgumentException(
|
||||
"Missing outPoints, address, amount, and fee rate arguments"))
|
||||
|
||||
case other =>
|
||||
Failure(
|
||||
new IllegalArgumentException(
|
||||
s"Bad number of arguments: ${other.length}. Expected: 3"))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
trait ServerJsonModels {
|
||||
|
||||
def jsToBitcoinAddress(js: Value): BitcoinAddress = {
|
||||
|
@ -252,6 +289,14 @@ trait ServerJsonModels {
|
|||
|
||||
def jsToPSBT(js: Value): PSBT = PSBT.fromString(js.str)
|
||||
|
||||
def jsToTransactionOutPointSeq(js: Value): Seq[TransactionOutPoint] = {
|
||||
js.arr.foldLeft(Seq.empty[TransactionOutPoint])((seq, outPoint) =>
|
||||
seq :+ jsToTransactionOutPoint(outPoint))
|
||||
}
|
||||
|
||||
def jsToTransactionOutPoint(js: Value): TransactionOutPoint =
|
||||
TransactionOutPoint(js.str)
|
||||
|
||||
def jsToTx(js: Value): Transaction = Transaction.fromHex(js.str)
|
||||
|
||||
def nullToOpt(value: Value): Option[Value] = value match {
|
||||
|
|
|
@ -107,6 +107,30 @@ case class WalletRoutes(wallet: WalletApi, node: Node)(
|
|||
}
|
||||
}
|
||||
|
||||
case ServerCommand("sendfromoutpoints", arr) =>
|
||||
SendFromOutpoints.fromJsArr(arr) match {
|
||||
case Failure(exception) =>
|
||||
reject(ValidationRejection("failure", Some(exception)))
|
||||
case Success(
|
||||
SendFromOutpoints(outPoints,
|
||||
address,
|
||||
bitcoins,
|
||||
satoshisPerVirtualByteOpt)) =>
|
||||
complete {
|
||||
// TODO dynamic fees based off mempool and recent blocks
|
||||
val feeRate =
|
||||
satoshisPerVirtualByteOpt.getOrElse(SatoshisPerByte(100.satoshis))
|
||||
|
||||
for {
|
||||
tx <- wallet.sendFromOutPoints(outPoints,
|
||||
address,
|
||||
bitcoins,
|
||||
feeRate)
|
||||
_ <- node.broadcastTransaction(tx)
|
||||
} yield Server.httpSuccess(tx.txIdBE)
|
||||
}
|
||||
}
|
||||
|
||||
case ServerCommand("rescan", arr) =>
|
||||
Rescan.fromJsArr(arr) match {
|
||||
case Failure(exception) =>
|
||||
|
|
|
@ -17,15 +17,16 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
|
||||
behavior of "Wallet"
|
||||
|
||||
val testAddress: BitcoinAddress =
|
||||
BitcoinAddress("bcrt1qlhctylgvdsvaanv539rg7hyn0sjkdm23y70kgq").get
|
||||
|
||||
val amountToSend: Bitcoins = Bitcoins(0.5)
|
||||
|
||||
val feeRate: SatoshisPerByte = SatoshisPerByte(Satoshis.one)
|
||||
|
||||
it should "correctly send to an address" in { fundedWallet =>
|
||||
val wallet = fundedWallet.wallet
|
||||
|
||||
val testAddress =
|
||||
BitcoinAddress("bcrt1qlhctylgvdsvaanv539rg7hyn0sjkdm23y70kgq").get
|
||||
val amountToSend: Bitcoins = Bitcoins(0.5)
|
||||
|
||||
for {
|
||||
tx <- wallet.sendToAddress(testAddress, amountToSend, feeRate)
|
||||
} yield {
|
||||
|
@ -140,4 +141,27 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
sendToAddressesF
|
||||
}
|
||||
}
|
||||
|
||||
it should "correctly send from outpoints" in { fundedWallet =>
|
||||
val wallet = fundedWallet.wallet
|
||||
for {
|
||||
allOutPoints <- wallet.spendingInfoDAO.findAllOutpoints()
|
||||
// use half of them
|
||||
outPoints = allOutPoints.drop(allOutPoints.size / 2)
|
||||
tx <- wallet.sendFromOutPoints(outPoints,
|
||||
testAddress,
|
||||
amountToSend,
|
||||
feeRate)
|
||||
} yield {
|
||||
assert(outPoints.forall(outPoint =>
|
||||
tx.inputs.exists(_.previousOutput == outPoint)),
|
||||
"Every outpoint was not included included")
|
||||
assert(tx.inputs.size == outPoints.size, "An extra input was added")
|
||||
|
||||
val expectedOutput =
|
||||
TransactionOutput(amountToSend, testAddress.scriptPubKey)
|
||||
assert(tx.outputs.contains(expectedOutput),
|
||||
"Did not contain expected output")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,14 @@ import java.time.Instant
|
|||
|
||||
import org.bitcoins.core.api.{ChainQueryApi, NodeApi}
|
||||
import org.bitcoins.core.bloom.{BloomFilter, BloomUpdateAll}
|
||||
import org.bitcoins.core.config.BitcoinNetwork
|
||||
import org.bitcoins.core.crypto.ExtPublicKey
|
||||
import org.bitcoins.core.currency._
|
||||
import org.bitcoins.core.hd.{HDAccount, HDCoin, HDPurposes}
|
||||
import org.bitcoins.core.protocol.BitcoinAddress
|
||||
import org.bitcoins.core.protocol.blockchain.BlockHeader
|
||||
import org.bitcoins.core.protocol.transaction._
|
||||
import org.bitcoins.core.wallet.builder.BitcoinTxBuilder
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
import org.bitcoins.core.wallet.utxo.TxoState
|
||||
import org.bitcoins.core.wallet.utxo.TxoState.{
|
||||
|
@ -205,6 +207,62 @@ abstract class Wallet
|
|||
} yield updatedInfos
|
||||
}
|
||||
|
||||
/** Takes a [[BitcoinTxBuilder]] for a transaction to be sent, and completes it by:
|
||||
* signing the transaction, then correctly processing the it and logging it
|
||||
*/
|
||||
private def finishSend(txBuilder: BitcoinTxBuilder): Future[Transaction] = {
|
||||
for {
|
||||
signed <- txBuilder.sign
|
||||
ourOuts <- findOurOuts(signed)
|
||||
_ <- processOurTransaction(transaction = signed,
|
||||
feeRate = txBuilder.feeRate,
|
||||
inputAmount = txBuilder.creditingAmount,
|
||||
sentAmount = txBuilder.destinationAmount,
|
||||
blockHashOpt = None)
|
||||
} yield {
|
||||
logger.debug(
|
||||
s"Signed transaction=${signed.txIdBE.hex} with outputs=${signed.outputs.length}, inputs=${signed.inputs.length}")
|
||||
|
||||
logger.trace(s"Change output(s) for transaction=${signed.txIdBE.hex}")
|
||||
ourOuts.foreach { out =>
|
||||
logger.trace(s" $out")
|
||||
}
|
||||
signed
|
||||
}
|
||||
}
|
||||
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb): Future[Transaction] = {
|
||||
require(
|
||||
address.networkParameters.isSameNetworkBytes(networkParameters),
|
||||
s"Cannot send to address on other network, got ${address.networkParameters}"
|
||||
)
|
||||
logger.info(s"Sending $amount to $address at feerate $feeRate")
|
||||
for {
|
||||
utxoDbs <- spendingInfoDAO.findByOutPoints(outPoints)
|
||||
diff = utxoDbs.map(_.outPoint).diff(outPoints)
|
||||
_ = require(diff.isEmpty,
|
||||
s"Not all OutPoints belong to this wallet, diff $diff")
|
||||
|
||||
utxos = utxoDbs.map(_.toUTXOSpendingInfo(keyManager))
|
||||
|
||||
changeAddr <- getNewChangeAddress(fromAccount.hdAccount)
|
||||
|
||||
output = TransactionOutput(amount, address.scriptPubKey)
|
||||
txBuilder <- BitcoinTxBuilder(
|
||||
Vector(output),
|
||||
utxos,
|
||||
feeRate,
|
||||
changeAddr.scriptPubKey,
|
||||
networkParameters.asInstanceOf[BitcoinNetwork])
|
||||
tx <- finishSend(txBuilder)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
|
@ -222,23 +280,9 @@ abstract class Wallet
|
|||
feeRate = feeRate,
|
||||
fromAccount = fromAccount,
|
||||
keyManagerOpt = Some(keyManager))
|
||||
signed <- txBuilder.sign
|
||||
ourOuts <- findOurOuts(signed)
|
||||
_ <- processOurTransaction(transaction = signed,
|
||||
feeRate = feeRate,
|
||||
inputAmount = txBuilder.creditingAmount,
|
||||
sentAmount = txBuilder.destinationAmount,
|
||||
blockHashOpt = None)
|
||||
} yield {
|
||||
logger.debug(
|
||||
s"Signed transaction=${signed.txIdBE.hex} with outputs=${signed.outputs.length}, inputs=${signed.inputs.length}")
|
||||
|
||||
logger.trace(s"Change output(s) for transaction=${signed.txIdBE.hex}")
|
||||
ourOuts.foreach { out =>
|
||||
logger.trace(s" $out")
|
||||
}
|
||||
signed
|
||||
}
|
||||
tx <- finishSend(txBuilder)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendToAddresses(
|
||||
|
@ -273,23 +317,8 @@ abstract class Wallet
|
|||
fromAccount = fromAccount,
|
||||
keyManagerOpt = Some(keyManager),
|
||||
markAsReserved = reserveUtxos)
|
||||
signed <- txBuilder.sign
|
||||
ourOuts <- findOurOuts(signed)
|
||||
_ <- processOurTransaction(transaction = signed,
|
||||
feeRate = feeRate,
|
||||
inputAmount = txBuilder.creditingAmount,
|
||||
sentAmount = txBuilder.destinationAmount,
|
||||
blockHashOpt = None)
|
||||
} yield {
|
||||
logger.debug(
|
||||
s"Signed transaction=${signed.txIdBE.hex} with outputs=${signed.outputs.length}, inputs=${signed.inputs.length}")
|
||||
|
||||
logger.trace(s"Change output(s) for transaction=${signed.txIdBE.hex}")
|
||||
ourOuts.foreach { out =>
|
||||
logger.trace(s" $out")
|
||||
}
|
||||
signed
|
||||
}
|
||||
tx <- finishSend(txBuilder)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** Creates a new account my reading from our account database, finding the last account,
|
||||
|
|
|
@ -10,7 +10,11 @@ import org.bitcoins.core.gcs.{GolombFilter, SimpleFilterMatcher}
|
|||
import org.bitcoins.core.hd.{AddressType, HDAccount, HDChainType, HDPurpose}
|
||||
import org.bitcoins.core.protocol.blockchain.{Block, BlockHeader, ChainParams}
|
||||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||
import org.bitcoins.core.protocol.transaction.{Transaction, TransactionOutput}
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
Transaction,
|
||||
TransactionOutPoint,
|
||||
TransactionOutput
|
||||
}
|
||||
import org.bitcoins.core.protocol.{BitcoinAddress, BlockStamp}
|
||||
import org.bitcoins.core.util.FutureUtil
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
|
@ -415,6 +419,24 @@ trait WalletApi extends WalletLogger {
|
|||
|
||||
def keyManager: BIP39KeyManager
|
||||
|
||||
def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb): Future[Transaction]
|
||||
|
||||
def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
tx <- sendFromOutPoints(outPoints, address, amount, feeRate, account)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Sends money from the specified account
|
||||
|
|
|
@ -163,6 +163,13 @@ case class SpendingInfoDAO()(
|
|||
safeDatabase.runVec(query.result).map(_.toVector)
|
||||
}
|
||||
|
||||
/** Enumerates all TX outpoints in the wallet */
|
||||
def findByOutPoints(outPoints: Vector[TransactionOutPoint]): Future[
|
||||
Vector[SpendingInfoDb]] = {
|
||||
val query = table.filter(_.outPoint.inSet(outPoints))
|
||||
safeDatabase.runVec(query.result).map(_.toVector)
|
||||
}
|
||||
|
||||
/**
|
||||
* This table stores the necessary information to spend
|
||||
* a transaction output (TXO) at a later point in time. It
|
||||
|
|
Loading…
Add table
Reference in a new issue