mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-26 01:29:20 +01:00
Add SendFundsHandlingApi
, remove HDWalletApi
(#5680)
* Begin moving methods out of HDWalletApi * Add SendFundsHandlingApi, remove HDWalletApi * Fix docs * Move makeOpReturnCommitment() to SendFundsHandlingApi * Remove MockWalletApi * Cleanup * Fix RoutesSpec * Revert logback-test.xml
This commit is contained in:
parent
287bf984a0
commit
d17934f17f
46 changed files with 1325 additions and 1707 deletions
|
@ -2,6 +2,7 @@ package org.bitcoins.server
|
|||
|
||||
import org.apache.pekko.http.scaladsl.model.ContentTypes
|
||||
import org.apache.pekko.http.scaladsl.testkit.ScalatestRouteTest
|
||||
import org.bitcoins.core.api.dlc.wallet.DLCNeutrinoHDWalletApi
|
||||
import org.bitcoins.core.api.dlc.wallet.db.DLCContactDb
|
||||
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
|
||||
import org.bitcoins.core.protocol.dlc.models.ContractInfo
|
||||
|
@ -11,7 +12,6 @@ import org.bitcoins.crypto.Sha256Digest
|
|||
import org.bitcoins.dlc.node.DLCNode
|
||||
import org.bitcoins.server.routes.ServerCommand
|
||||
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||
import org.bitcoins.wallet.MockWalletApi
|
||||
import org.scalamock.scalatest.MockFactory
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
|
@ -26,7 +26,7 @@ class DLCRoutesSpec
|
|||
implicit val conf: BitcoinSAppConfig =
|
||||
BitcoinSTestAppConfig.getNeutrinoTestConfig()
|
||||
|
||||
val mockWallet = mock[MockWalletApi]
|
||||
val mockWallet = mock[DLCNeutrinoHDWalletApi]
|
||||
|
||||
val mockNodeApi = DLCNode(mockWallet)(system, conf.dlcNodeConf)
|
||||
|
||||
|
|
|
@ -8,13 +8,17 @@ import org.apache.pekko.http.scaladsl.testkit.{
|
|||
}
|
||||
import org.bitcoins.core.api.chain.ChainApi
|
||||
import org.bitcoins.core.api.chain.db.*
|
||||
import org.bitcoins.core.api.dlc.wallet.DLCNeutrinoHDWalletApi
|
||||
import org.bitcoins.core.api.wallet.db.*
|
||||
import org.bitcoins.core.api.wallet.{
|
||||
AccountHandlingApi,
|
||||
AddressHandlingApi,
|
||||
AddressInfo,
|
||||
CoinSelectionAlgo,
|
||||
RescanHandlingApi
|
||||
FundTransactionHandlingApi,
|
||||
RescanHandlingApi,
|
||||
SendFundsHandlingApi,
|
||||
UtxoHandlingApi
|
||||
}
|
||||
import org.bitcoins.core.config.RegTest
|
||||
import org.bitcoins.core.crypto.ExtPublicKey
|
||||
|
@ -49,7 +53,7 @@ import org.bitcoins.server.routes.{CommonRoutes, ServerCommand}
|
|||
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||
import org.bitcoins.testkit.wallet.DLCWalletUtil
|
||||
import org.bitcoins.testkitcore.util.TransactionTestUtil
|
||||
import org.bitcoins.wallet.{MockWalletApi, WalletHolder}
|
||||
import org.bitcoins.wallet.WalletHolder
|
||||
import org.scalamock.scalatest.MockFactory
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
import scodec.bits.ByteVector
|
||||
|
@ -73,6 +77,17 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
val testAddress: BitcoinAddress = BitcoinAddress.fromString(testAddressStr)
|
||||
val testLabel: AddressLabelTag = AddressLabelTag("test")
|
||||
|
||||
val xpub = ExtPublicKey
|
||||
.fromString(
|
||||
"xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
|
||||
)
|
||||
|
||||
val accountDb =
|
||||
AccountDb(
|
||||
xpub = xpub,
|
||||
hdAccount = HDAccount(HDCoin(HDPurpose.Legacy, HDCoinType.Testnet), 0)
|
||||
)
|
||||
|
||||
val mockChainApi: ChainApi = mock[ChainApi]
|
||||
|
||||
val mockNode: Node = mock[Node]
|
||||
|
@ -81,7 +96,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
|
||||
val nodeRoutes: NodeRoutes = NodeRoutes(mockNode)
|
||||
|
||||
val mockWalletApi: MockWalletApi = mock[MockWalletApi]
|
||||
val mockWalletApi: DLCNeutrinoHDWalletApi = mock[DLCNeutrinoHDWalletApi]
|
||||
|
||||
val mockRescanHandlingApi: RescanHandlingApi = mock[RescanHandlingApi]
|
||||
|
||||
|
@ -89,10 +104,19 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
|
||||
val mockAddressHandlingApi: AddressHandlingApi = mock[AddressHandlingApi]
|
||||
|
||||
val mockFundTxHandlingApi: FundTransactionHandlingApi =
|
||||
mock[FundTransactionHandlingApi]
|
||||
|
||||
val mockSendFundsHandlingApi: SendFundsHandlingApi =
|
||||
mock[SendFundsHandlingApi]
|
||||
|
||||
val mockUtxoHandlingApi: UtxoHandlingApi = mock[UtxoHandlingApi]
|
||||
|
||||
val walletHolder = new WalletHolder(Some(mockWalletApi))
|
||||
|
||||
val feeRateApi = ConstantFeeRateProvider(SatoshisPerVirtualByte.one)
|
||||
|
||||
val fee = SatoshisPerVirtualByte(Satoshis(4))
|
||||
val walletLoader: DLCWalletNeutrinoBackendLoader = {
|
||||
DLCWalletNeutrinoBackendLoader(
|
||||
walletHolder,
|
||||
|
@ -671,16 +695,6 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
}
|
||||
|
||||
"return the wallet accounts" in {
|
||||
val xpub = ExtPublicKey
|
||||
.fromString(
|
||||
"xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
|
||||
)
|
||||
|
||||
val accountDb =
|
||||
AccountDb(
|
||||
xpub = xpub,
|
||||
hdAccount = HDAccount(HDCoin(HDPurpose.Legacy, HDCoinType.Testnet), 0)
|
||||
)
|
||||
|
||||
(() => mockWalletApi.accountHandling)
|
||||
.expects()
|
||||
|
@ -1617,11 +1631,35 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
"send to an address" in {
|
||||
// positive cases
|
||||
|
||||
(mockWalletApi
|
||||
.sendToAddress(_: BitcoinAddress, _: CurrencyUnit, _: Option[FeeUnit])(
|
||||
_: ExecutionContext
|
||||
))
|
||||
.expects(testAddress, Bitcoins(100), *, executor)
|
||||
(() => mockWalletApi.sendFundsHandling)
|
||||
.expects()
|
||||
.returning(mockSendFundsHandlingApi)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(() => mockWalletApi.sendFundsHandling.accountHandling)
|
||||
.expects()
|
||||
.returning(mockAccountHandlingApi)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(() =>
|
||||
mockWalletApi.sendFundsHandling.accountHandling.getDefaultAccount())
|
||||
.expects()
|
||||
.returning(Future.successful(accountDb))
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(mockWalletApi.sendFundsHandling
|
||||
.sendWithAlgo(_: BitcoinAddress,
|
||||
_: CurrencyUnit,
|
||||
_: FeeUnit,
|
||||
_: CoinSelectionAlgo,
|
||||
_: AccountDb,
|
||||
_: Vector[AddressTag]))
|
||||
.expects(testAddress,
|
||||
Bitcoins(100),
|
||||
fee,
|
||||
CoinSelectionAlgo.LeastWaste,
|
||||
accountDb,
|
||||
Vector.empty)
|
||||
.returning(Future.successful(EmptyTransaction))
|
||||
|
||||
(mockWalletApi.broadcastTransaction _)
|
||||
|
@ -1708,19 +1746,38 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
"send from outpoints" in {
|
||||
// positive cases
|
||||
|
||||
(mockWalletApi
|
||||
(() => mockWalletApi.sendFundsHandling)
|
||||
.expects()
|
||||
.returning(mockSendFundsHandlingApi)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(() => mockWalletApi.sendFundsHandling.accountHandling)
|
||||
.expects()
|
||||
.returning(mockAccountHandlingApi)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(() =>
|
||||
mockWalletApi.sendFundsHandling.accountHandling.getDefaultAccount())
|
||||
.expects()
|
||||
.returning(Future.successful(accountDb))
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(mockWalletApi.sendFundsHandling
|
||||
.sendFromOutPoints(
|
||||
_: Vector[TransactionOutPoint],
|
||||
_: BitcoinAddress,
|
||||
_: CurrencyUnit,
|
||||
_: Option[FeeUnit]
|
||||
)(_: ExecutionContext))
|
||||
_: FeeUnit,
|
||||
_: AccountDb,
|
||||
_: Vector[AddressTag]
|
||||
))
|
||||
.expects(
|
||||
Vector.empty[TransactionOutPoint],
|
||||
testAddress,
|
||||
Bitcoins(100),
|
||||
*,
|
||||
executor
|
||||
fee,
|
||||
accountDb,
|
||||
Vector.empty
|
||||
)
|
||||
.returning(Future.successful(EmptyTransaction))
|
||||
|
||||
|
@ -1822,11 +1879,25 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
}
|
||||
|
||||
"sweep wallet" in {
|
||||
(mockWalletApi
|
||||
.sweepWallet(_: BitcoinAddress, _: Option[FeeUnit])(
|
||||
_: ExecutionContext
|
||||
))
|
||||
.expects(testAddress, *, executor)
|
||||
(() => mockWalletApi.sendFundsHandling)
|
||||
.expects()
|
||||
.returning(mockSendFundsHandlingApi)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(() => mockWalletApi.sendFundsHandling.utxoHandling)
|
||||
.expects()
|
||||
.returning(mockUtxoHandlingApi)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(() => mockWalletApi.sendFundsHandling.utxoHandling.listUtxos())
|
||||
.expects()
|
||||
.returning(Future.successful(Vector.empty))
|
||||
|
||||
(mockWalletApi.sendFundsHandling
|
||||
.sendFromOutPoints(_: Vector[TransactionOutPoint],
|
||||
_: BitcoinAddress,
|
||||
_: FeeUnit))
|
||||
.expects(Vector.empty, testAddress, fee)
|
||||
.returning(Future.successful(EmptyTransaction))
|
||||
|
||||
(mockWalletApi.broadcastTransaction _)
|
||||
|
@ -1849,7 +1920,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
"send with algo" in {
|
||||
// positive cases
|
||||
|
||||
(mockWalletApi
|
||||
(() => mockWalletApi.sendFundsHandling)
|
||||
.expects()
|
||||
.returning(mockSendFundsHandlingApi)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(mockWalletApi.sendFundsHandling
|
||||
.sendWithAlgo(
|
||||
_: BitcoinAddress,
|
||||
_: CurrencyUnit,
|
||||
|
@ -1859,7 +1935,7 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
.expects(
|
||||
testAddress,
|
||||
Bitcoins(100),
|
||||
Some(SatoshisPerVirtualByte(Satoshis(4))),
|
||||
Some(fee),
|
||||
CoinSelectionAlgo.AccumulateSmallestViable,
|
||||
executor
|
||||
)
|
||||
|
@ -1972,7 +2048,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
"020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000"
|
||||
)
|
||||
|
||||
(mockWalletApi
|
||||
(() => mockWalletApi.sendFundsHandling)
|
||||
.expects()
|
||||
.returning(mockSendFundsHandlingApi)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(mockWalletApi.sendFundsHandling
|
||||
.signPSBT(_: PSBT)(_: ExecutionContext))
|
||||
.expects(PSBT.empty, executor)
|
||||
.returning(Future.successful(PSBT.fromUnsignedTx(tx)))
|
||||
|
@ -1995,11 +2076,14 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
|
||||
val message = "Never gonna give you up, never gonna let you down"
|
||||
|
||||
(mockWalletApi
|
||||
.makeOpReturnCommitment(_: String, _: Boolean, _: Option[FeeUnit])(
|
||||
_: ExecutionContext
|
||||
))
|
||||
.expects(message, false, *, executor)
|
||||
(() => mockWalletApi.sendFundsHandling)
|
||||
.expects()
|
||||
.returning(mockSendFundsHandlingApi)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(mockWalletApi.sendFundsHandling
|
||||
.makeOpReturnCommitment(_: String, _: Boolean, _: Option[FeeUnit]))
|
||||
.expects(message, false, *)
|
||||
.returning(Future.successful(EmptyTransaction))
|
||||
|
||||
(mockWalletApi.broadcastTransaction _)
|
||||
|
@ -2020,7 +2104,13 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
}
|
||||
|
||||
"bump fee with rbf" in {
|
||||
(mockWalletApi
|
||||
|
||||
(() => mockWalletApi.sendFundsHandling)
|
||||
.expects()
|
||||
.returning(mockSendFundsHandlingApi)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(mockWalletApi.sendFundsHandling
|
||||
.bumpFeeRBF(_: DoubleSha256DigestBE, _: FeeUnit))
|
||||
.expects(DoubleSha256DigestBE.empty, SatoshisPerVirtualByte.one)
|
||||
.returning(Future.successful(EmptyTransaction))
|
||||
|
@ -2046,7 +2136,12 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
|||
}
|
||||
|
||||
"bump fee with CPFP" in {
|
||||
(mockWalletApi
|
||||
(() => mockWalletApi.sendFundsHandling)
|
||||
.expects()
|
||||
.returning(mockSendFundsHandlingApi)
|
||||
.anyNumberOfTimes()
|
||||
|
||||
(mockWalletApi.sendFundsHandling
|
||||
.bumpFeeCPFP(_: DoubleSha256DigestBE, _: FeeUnit))
|
||||
.expects(DoubleSha256DigestBE.empty, SatoshisPerVirtualByte.one)
|
||||
.returning(Future.successful(EmptyTransaction))
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.apache.pekko.http.scaladsl.model.ContentTypes.*
|
|||
import org.apache.pekko.http.scaladsl.testkit.ScalatestRouteTest
|
||||
import org.bitcoins.commons.serializers.Picklers
|
||||
import org.bitcoins.core.api.chain.ChainApi
|
||||
import org.bitcoins.core.api.dlc.wallet.DLCNeutrinoHDWalletApi
|
||||
import org.bitcoins.core.api.wallet.AccountHandlingApi
|
||||
import org.bitcoins.core.api.wallet.db.AccountDb
|
||||
import org.bitcoins.core.crypto.ExtKeyVersion.SegWitMainNetPriv
|
||||
|
@ -21,7 +22,7 @@ import org.bitcoins.server.routes.ServerCommand
|
|||
import org.bitcoins.testkit.BitcoinSTestAppConfig
|
||||
import org.bitcoins.testkitcore.Implicits.GeneratorOps
|
||||
import org.bitcoins.testkitcore.gen.TLVGen
|
||||
import org.bitcoins.wallet.{MockWalletApi, WalletHolder}
|
||||
import org.bitcoins.wallet.WalletHolder
|
||||
import org.scalamock.scalatest.MockFactory
|
||||
import org.scalatest.wordspec.AnyWordSpec
|
||||
|
||||
|
@ -39,7 +40,7 @@ class WalletRoutesSpec
|
|||
val mockChainApi: ChainApi = mock[ChainApi]
|
||||
|
||||
val mockNode: Node = mock[Node]
|
||||
val mockWalletApi: MockWalletApi = mock[MockWalletApi]
|
||||
val mockWalletApi: DLCNeutrinoHDWalletApi = mock[DLCNeutrinoHDWalletApi]
|
||||
val mockAccountHandlingApi: AccountHandlingApi = mock[AccountHandlingApi]
|
||||
|
||||
val walletHolder = new WalletHolder(Some(mockWalletApi))
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
package org.bitcoins.wallet
|
||||
|
||||
import org.bitcoins.core.api.dlc.wallet.DLCNeutrinoHDWalletApi
|
||||
import org.bitcoins.core.api.wallet.db.AccountDb
|
||||
import org.bitcoins.core.hd.AddressType
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
/** ScalaMock cannot stub traits with protected methods, so we need to stub them
|
||||
* manually.
|
||||
*/
|
||||
abstract class MockWalletApi extends DLCNeutrinoHDWalletApi {
|
||||
|
||||
override def getDefaultAccount(): Future[AccountDb] = stub
|
||||
|
||||
override def getDefaultAccountForType(
|
||||
addressType: AddressType
|
||||
): Future[AccountDb] = stub
|
||||
|
||||
private def stub[T] =
|
||||
Future.failed[T](new RuntimeException("Not implemented"))
|
||||
|
||||
}
|
|
@ -442,7 +442,7 @@ class BitcoinSServerMain(override val serverArgParser: ServerArgParser)(implicit
|
|||
bitcoind <- bitcoindF
|
||||
walletHolder = result._1
|
||||
callback = BitcoindCallbacks.onBlockReceived(
|
||||
walletHolder.processBlock(_).map(_ => ()))
|
||||
walletHolder.transactionProcessing.processBlock(_).map(_ => ()))
|
||||
_ = bitcoind.bitcoindRpcAppConfig.addCallbacks(callback)
|
||||
} yield result
|
||||
}
|
||||
|
|
|
@ -244,7 +244,7 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
|
|||
val rawTxListener: Option[Transaction => Unit] = Some {
|
||||
{ (tx: Transaction) =>
|
||||
logger.debug(s"Received tx ${tx.txIdBE.hex}, processing")
|
||||
val f = wallet.processTransaction(tx, None)
|
||||
val f = wallet.transactionProcessing.processTransaction(tx, None)
|
||||
f.failed.foreach { err =>
|
||||
logger.error("failed to process raw tx zmq message", err)
|
||||
}
|
||||
|
@ -267,7 +267,7 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
|
|||
logger.info(
|
||||
s"Received block ${block.blockHeader.hashBE.hex}, processing"
|
||||
)
|
||||
val f = wallet.processBlock(block)
|
||||
val f = wallet.transactionProcessing.processBlock(block)
|
||||
f.failed.foreach { err =>
|
||||
logger.error("failed to process raw block zmq message", err)
|
||||
}
|
||||
|
@ -364,7 +364,8 @@ object BitcoindRpcBackendUtil extends BitcoinSLogger {
|
|||
walletF.map { wallet =>
|
||||
Sink.foreachAsync(1) {
|
||||
case (block: Block, blockHeaderResult: GetBlockHeaderResult) =>
|
||||
val blockProcessedF = wallet.processBlock(block)
|
||||
val blockProcessedF =
|
||||
wallet.transactionProcessing.processBlock(block)
|
||||
val executeCallbackF: Future[Unit] = {
|
||||
for {
|
||||
wallet <- blockProcessedF
|
||||
|
|
|
@ -669,7 +669,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
|
|||
) =>
|
||||
complete {
|
||||
for {
|
||||
tx <- wallet.sendToAddress(
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(
|
||||
address,
|
||||
bitcoins,
|
||||
satoshisPerVirtualByteOpt
|
||||
|
@ -690,7 +690,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
|
|||
) =>
|
||||
complete {
|
||||
for {
|
||||
tx <- wallet.sendFromOutPoints(
|
||||
tx <- wallet.sendFundsHandling.sendFromOutPoints(
|
||||
outPoints,
|
||||
address,
|
||||
bitcoins,
|
||||
|
@ -705,10 +705,13 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
|
|||
withValidServerCommand(SweepWallet.fromJsArr(arr)) {
|
||||
case SweepWallet(address, feeRateOpt) =>
|
||||
complete {
|
||||
for {
|
||||
tx <- wallet.sweepWallet(address, feeRateOpt)
|
||||
val f = for {
|
||||
tx <- wallet.sendFundsHandling.sweepWallet(address, feeRateOpt)
|
||||
_ <- wallet.broadcastTransaction(tx)
|
||||
} yield Server.httpSuccess(tx.txIdBE)
|
||||
|
||||
f.failed.foreach(err => logger.error(s"f.err", err))
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -717,7 +720,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
|
|||
case SendWithAlgo(address, bitcoins, satoshisPerVirtualByteOpt, algo) =>
|
||||
complete {
|
||||
for {
|
||||
tx <- wallet.sendWithAlgo(
|
||||
tx <- wallet.sendFundsHandling.sendWithAlgo(
|
||||
address,
|
||||
bitcoins,
|
||||
satoshisPerVirtualByteOpt,
|
||||
|
@ -731,7 +734,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
|
|||
case ServerCommand("signpsbt", arr) =>
|
||||
withValidServerCommand(SignPSBT.fromJsArr(arr)) { case SignPSBT(psbt) =>
|
||||
complete {
|
||||
wallet.signPSBT(psbt).map { signed =>
|
||||
wallet.sendFundsHandling.signPSBT(psbt).map { signed =>
|
||||
Server.httpSuccess(signed.base64)
|
||||
}
|
||||
}
|
||||
|
@ -742,7 +745,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
|
|||
case OpReturnCommit(message, hashMessage, satoshisPerVirtualByteOpt) =>
|
||||
complete {
|
||||
for {
|
||||
tx <- wallet.makeOpReturnCommitment(
|
||||
tx <- wallet.sendFundsHandling.makeOpReturnCommitment(
|
||||
message,
|
||||
hashMessage,
|
||||
satoshisPerVirtualByteOpt
|
||||
|
@ -759,7 +762,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
|
|||
case BumpFee(txId, feeRate) =>
|
||||
complete {
|
||||
for {
|
||||
tx <- wallet.bumpFeeRBF(txId, feeRate)
|
||||
tx <- wallet.sendFundsHandling.bumpFeeRBF(txId, feeRate)
|
||||
_ <- wallet.broadcastTransaction(tx)
|
||||
} yield Server.httpSuccess(tx.txIdBE)
|
||||
}
|
||||
|
@ -770,7 +773,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
|
|||
case BumpFee(txId, feeRate) =>
|
||||
complete {
|
||||
for {
|
||||
tx <- wallet.bumpFeeCPFP(txId, feeRate)
|
||||
tx <- wallet.sendFundsHandling.bumpFeeCPFP(txId, feeRate)
|
||||
_ <- wallet.broadcastTransaction(tx)
|
||||
} yield Server.httpSuccess(tx.txIdBE)
|
||||
}
|
||||
|
@ -845,7 +848,7 @@ case class WalletRoutes(loadWalletApi: DLCWalletLoaderApi)(implicit
|
|||
|
||||
case ServerCommand("getaccounts", _) =>
|
||||
complete {
|
||||
wallet.listAccounts().map { accounts =>
|
||||
wallet.accountHandling.listAccounts().map { accounts =>
|
||||
val xpubs = accounts.map(_.xpub)
|
||||
Server.httpSuccess(xpubs)
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ object CallbackUtil extends BitcoinSLogger {
|
|||
import system.dispatcher
|
||||
val txSink = Sink.foreachAsync[Transaction](1) { case tx: Transaction =>
|
||||
logger.debug(s"Receiving transaction txid=${tx.txIdBE.hex} as a callback")
|
||||
wallet
|
||||
wallet.transactionProcessing
|
||||
.processTransaction(tx, blockHashOpt = None)
|
||||
.map(_ => ())
|
||||
}
|
||||
|
@ -46,7 +46,9 @@ object CallbackUtil extends BitcoinSLogger {
|
|||
logger.debug(
|
||||
s"Executing onBlock callback=${block.blockHeader.hashBE.hex}"
|
||||
)
|
||||
wallet.processBlock(block).map(_ => ())
|
||||
wallet.transactionProcessing
|
||||
.processBlock(block)
|
||||
.map(_ => ())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,7 +108,7 @@ object CallbackUtil extends BitcoinSLogger {
|
|||
import system.dispatcher
|
||||
val txSink = Sink.foreachAsync[Transaction](1) { case tx: Transaction =>
|
||||
logger.debug(s"Receiving transaction txid=${tx.txIdBE.hex} as a callback")
|
||||
wallet
|
||||
wallet.transactionProcessing
|
||||
.processTransaction(tx, blockHashOpt = None)
|
||||
.map(_ => ())
|
||||
}
|
||||
|
@ -118,7 +120,7 @@ object CallbackUtil extends BitcoinSLogger {
|
|||
}
|
||||
|
||||
val blockSink = Sink.foreachAsync[Block](1) { block =>
|
||||
wallet
|
||||
wallet.transactionProcessing
|
||||
.processBlock(block)
|
||||
.map(_ => ())
|
||||
}
|
||||
|
|
|
@ -23,8 +23,29 @@ trait AccountHandlingApi {
|
|||
|
||||
def createNewAccount(purpose: HDPurpose): Future[ExtPublicKey]
|
||||
|
||||
/** Gets the balance of the given account */
|
||||
def getBalance(account: HDAccount)(implicit
|
||||
ec: ExecutionContext): Future[CurrencyUnit] = {
|
||||
val confirmedF = getConfirmedBalance(account)
|
||||
val unconfirmedF = getUnconfirmedBalance(account)
|
||||
for {
|
||||
confirmed <- confirmedF
|
||||
unconfirmed <- unconfirmedF
|
||||
} yield {
|
||||
confirmed + unconfirmed
|
||||
}
|
||||
}
|
||||
|
||||
def getConfirmedBalance(account: HDAccount): Future[CurrencyUnit]
|
||||
|
||||
def getUnconfirmedBalance(account: HDAccount): Future[CurrencyUnit]
|
||||
|
||||
def getDefaultAccount(): Future[AccountDb]
|
||||
def listAccounts(): Future[Vector[AccountDb]]
|
||||
def listAccounts(purpose: HDPurpose)(implicit
|
||||
ec: ExecutionContext): Future[Vector[AccountDb]] = {
|
||||
listAccounts().map(_.filter(_.hdAccount.purpose == purpose))
|
||||
}
|
||||
def getDefaultAccountForType(addressType: AddressType): Future[AccountDb]
|
||||
def getNewAddress(
|
||||
account: AccountDb,
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package org.bitcoins.core.api.wallet
|
||||
|
||||
import org.bitcoins.core.api.keymanager.BIP39KeyManagerApi
|
||||
import org.bitcoins.core.api.wallet.db.{AccountDb, SpendingInfoDb}
|
||||
import org.bitcoins.core.api.feeprovider.FeeRateApi
|
||||
import org.bitcoins.core.api.wallet.db.AccountDb
|
||||
import org.bitcoins.core.currency.CurrencyUnit
|
||||
import org.bitcoins.core.hd.{AddressType, HDAccount, HDPurpose}
|
||||
import org.bitcoins.core.protocol.BitcoinAddress
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
Transaction,
|
||||
|
@ -11,58 +10,48 @@ import org.bitcoins.core.protocol.transaction.{
|
|||
TransactionOutput
|
||||
}
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.core.wallet.builder.{
|
||||
FundRawTxHelper,
|
||||
ShufflingNonInteractiveFinalizer
|
||||
}
|
||||
import org.bitcoins.core.wallet.fee.FeeUnit
|
||||
import org.bitcoins.core.wallet.utxo.AddressTag
|
||||
import org.bitcoins.crypto.DoubleSha256DigestBE
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
|
||||
/** API for the wallet project.
|
||||
*
|
||||
* This wallet API is BIP44 compliant.
|
||||
*
|
||||
* @see
|
||||
* [[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP44]]
|
||||
*/
|
||||
trait HDWalletApi extends WalletApi {
|
||||
trait SendFundsHandlingApi {
|
||||
|
||||
override def keyManager: BIP39KeyManagerApi
|
||||
def accountHandling: AccountHandlingApi
|
||||
def fundTxHandling: FundTransactionHandlingApi
|
||||
def rescanHandling: RescanHandlingApi
|
||||
def addressHandling: AddressHandlingApi
|
||||
def feeRateApi: FeeRateApi
|
||||
def utxoHandling: UtxoHandlingApi
|
||||
|
||||
/** Gets the balance of the given account */
|
||||
def getBalance(account: HDAccount)(implicit
|
||||
ec: ExecutionContext): Future[CurrencyUnit] = {
|
||||
val confirmedF = getConfirmedBalance(account)
|
||||
val unconfirmedF = getUnconfirmedBalance(account)
|
||||
for {
|
||||
confirmed <- confirmedF
|
||||
unconfirmed <- unconfirmedF
|
||||
} yield {
|
||||
confirmed + unconfirmed
|
||||
}
|
||||
}
|
||||
def bumpFeeRBF(
|
||||
txId: DoubleSha256DigestBE,
|
||||
newFeeRate: FeeUnit
|
||||
): Future[Transaction]
|
||||
def bumpFeeCPFP(
|
||||
txId: DoubleSha256DigestBE,
|
||||
feeRate: FeeUnit): Future[Transaction]
|
||||
|
||||
def getConfirmedBalance(account: HDAccount): Future[CurrencyUnit]
|
||||
def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb
|
||||
): Future[Transaction]
|
||||
|
||||
def getUnconfirmedBalance(account: HDAccount): Future[CurrencyUnit]
|
||||
def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
fromAccount: AccountDb): Future[Transaction]
|
||||
|
||||
/** Fetches the default account from the DB
|
||||
* @return
|
||||
* Future[AccountDb]
|
||||
*/
|
||||
def getDefaultAccount(): Future[AccountDb] =
|
||||
accountHandling.getDefaultAccount()
|
||||
def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRate: FeeUnit): Future[Transaction]
|
||||
|
||||
/** Fetches the default account for the given address/account kind
|
||||
* @param addressType
|
||||
*/
|
||||
def getDefaultAccountForType(addressType: AddressType): Future[AccountDb]
|
||||
def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRateOpt: Option[FeeUnit]): Future[Transaction]
|
||||
|
||||
def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
|
@ -70,16 +59,14 @@ trait HDWalletApi extends WalletApi {
|
|||
feeRate: FeeUnit,
|
||||
algo: CoinSelectionAlgo,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag])(implicit
|
||||
ec: ExecutionContext): Future[Transaction]
|
||||
newTags: Vector[AddressTag]): Future[Transaction]
|
||||
|
||||
def sendWithAlgo(
|
||||
final def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
algo: CoinSelectionAlgo,
|
||||
fromAccount: AccountDb)(implicit
|
||||
ec: ExecutionContext): Future[Transaction] =
|
||||
fromAccount: AccountDb): Future[Transaction] =
|
||||
sendWithAlgo(address, amount, feeRate, algo, fromAccount, Vector.empty)
|
||||
|
||||
def sendWithAlgo(
|
||||
|
@ -95,26 +82,26 @@ trait HDWalletApi extends WalletApi {
|
|||
} yield tx
|
||||
}
|
||||
|
||||
override def sendWithAlgo(
|
||||
def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
algo: CoinSelectionAlgo)(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendWithAlgo(address, amount, feeRateOpt, algo, account)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendWithAlgo(
|
||||
def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
algo: CoinSelectionAlgo)(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendWithAlgo(address, amount, feeRate, algo, account)
|
||||
} yield tx
|
||||
}
|
||||
|
@ -127,7 +114,7 @@ trait HDWalletApi extends WalletApi {
|
|||
newTags: Vector[AddressTag])(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendWithAlgo(address, amount, feeRate, algo, account, newTags)
|
||||
} yield tx
|
||||
}
|
||||
|
@ -142,16 +129,30 @@ trait HDWalletApi extends WalletApi {
|
|||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag])(implicit
|
||||
ec: ExecutionContext): Future[Transaction]
|
||||
newTags: Vector[AddressTag]): Future[Transaction]
|
||||
|
||||
def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
feeRate: FeeUnit): Future[Transaction]
|
||||
|
||||
final def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
feeRateOpt: Option[FeeUnit])(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt)
|
||||
tx <- sendFromOutPoints(outPoints, address, feeRate)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
final def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb)(implicit
|
||||
ec: ExecutionContext): Future[Transaction] =
|
||||
fromAccount: AccountDb): Future[Transaction] =
|
||||
sendFromOutPoints(outPoints,
|
||||
address,
|
||||
amount,
|
||||
|
@ -159,7 +160,7 @@ trait HDWalletApi extends WalletApi {
|
|||
fromAccount,
|
||||
Vector.empty)
|
||||
|
||||
def sendFromOutPoints(
|
||||
final def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
|
@ -172,30 +173,30 @@ trait HDWalletApi extends WalletApi {
|
|||
} yield tx
|
||||
}
|
||||
|
||||
override def sendFromOutPoints(
|
||||
final def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendFromOutPoints(outPoints, address, amount, feeRateOpt, account)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendFromOutPoints(
|
||||
final def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendFromOutPoints(outPoints, address, amount, feeRate, account)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
def sendFromOutPoints(
|
||||
final def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
|
@ -203,33 +204,36 @@ trait HDWalletApi extends WalletApi {
|
|||
newTags: Vector[AddressTag])(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <-
|
||||
sendFromOutPoints(outPoints, address, amount, feeRate, account, newTags)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** Sends money from the specified account
|
||||
*
|
||||
* todo: add error handling to signature
|
||||
*/
|
||||
def sendToAddress(
|
||||
final def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag])(implicit
|
||||
ec: ExecutionContext): Future[Transaction]
|
||||
newTags: Vector[AddressTag]
|
||||
): Future[Transaction] =
|
||||
sendWithAlgo(
|
||||
address,
|
||||
amount,
|
||||
feeRate,
|
||||
CoinSelectionAlgo.LeastWaste,
|
||||
fromAccount,
|
||||
newTags
|
||||
)
|
||||
|
||||
def sendToAddress(
|
||||
final def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb)(implicit
|
||||
ec: ExecutionContext): Future[Transaction] =
|
||||
fromAccount: AccountDb): Future[Transaction] =
|
||||
sendToAddress(address, amount, feeRate, fromAccount, Vector.empty)
|
||||
|
||||
def sendToAddress(
|
||||
final def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
|
@ -241,35 +245,35 @@ trait HDWalletApi extends WalletApi {
|
|||
} yield tx
|
||||
}
|
||||
|
||||
override def sendToAddress(
|
||||
final def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendToAddress(address, amount, feeRateOpt, account)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendToAddress(
|
||||
final def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendToAddress(address, amount, feeRate, account)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
def sendToAddress(
|
||||
final def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
newTags: Vector[AddressTag])(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendToAddress(address, amount, feeRate, account, newTags)
|
||||
} yield tx
|
||||
}
|
||||
|
@ -283,18 +287,16 @@ trait HDWalletApi extends WalletApi {
|
|||
amounts: Vector[CurrencyUnit],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag])(implicit
|
||||
ec: ExecutionContext): Future[Transaction]
|
||||
newTags: Vector[AddressTag]): Future[Transaction]
|
||||
|
||||
def sendToAddresses(
|
||||
final def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb)(implicit
|
||||
ec: ExecutionContext): Future[Transaction] =
|
||||
fromAccount: AccountDb): Future[Transaction] =
|
||||
sendToAddresses(addresses, amounts, feeRate, fromAccount, Vector.empty)
|
||||
|
||||
def sendToAddresses(
|
||||
final def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
|
@ -306,35 +308,35 @@ trait HDWalletApi extends WalletApi {
|
|||
} yield tx
|
||||
}
|
||||
|
||||
override def sendToAddresses(
|
||||
final def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendToAddresses(addresses, amounts, feeRateOpt, account)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendToAddresses(
|
||||
final def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendToAddresses(addresses, amounts, feeRate, account)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
def sendToAddresses(
|
||||
final def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRate: FeeUnit,
|
||||
newTags: Vector[AddressTag])(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendToAddresses(addresses, amounts, feeRate, account, newTags)
|
||||
} yield tx
|
||||
}
|
||||
|
@ -347,17 +349,15 @@ trait HDWalletApi extends WalletApi {
|
|||
outputs: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag])(implicit
|
||||
ec: ExecutionContext): Future[Transaction]
|
||||
newTags: Vector[AddressTag]): Future[Transaction]
|
||||
|
||||
def sendToOutputs(
|
||||
final def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb)(implicit
|
||||
ec: ExecutionContext): Future[Transaction] =
|
||||
fromAccount: AccountDb): Future[Transaction] =
|
||||
sendToOutputs(outputs, feeRate, fromAccount, Vector.empty)
|
||||
|
||||
def sendToOutputs(
|
||||
final def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
fromAccount: AccountDb)(implicit
|
||||
|
@ -368,96 +368,59 @@ trait HDWalletApi extends WalletApi {
|
|||
} yield tx
|
||||
}
|
||||
|
||||
def sendToOutputs(
|
||||
final def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit,
|
||||
newTags: Vector[AddressTag])(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendToOutputs(outputs, feeRate, account, newTags)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendToOutputs(
|
||||
final def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendToOutputs(outputs, feeRateOpt, account)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
final def sendToOutputs(outputs: Vector[TransactionOutput], feeRate: FeeUnit)(
|
||||
implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- sendToOutputs(outputs, feeRate, account)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** Sends the entire wallet balance to the given address */
|
||||
final def sweepWallet(address: BitcoinAddress, feeRate: FeeUnit)(implicit
|
||||
ec: ExecutionContext
|
||||
): Future[Transaction] = {
|
||||
for {
|
||||
utxos <- utxoHandling.listUtxos()
|
||||
outpoints = utxos.map(_.outPoint)
|
||||
tx <- sendFromOutPoints(outpoints, address, feeRate)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
final def sweepWallet(address: BitcoinAddress, feeRateOpt: Option[FeeUnit])(
|
||||
implicit ec: ExecutionContext
|
||||
): Future[Transaction] = {
|
||||
determineFeeRate(feeRateOpt).flatMap(sweepWallet(address, _))
|
||||
}
|
||||
|
||||
def signPSBT(psbt: PSBT)(implicit ec: ExecutionContext): Future[PSBT]
|
||||
|
||||
def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb)(implicit
|
||||
ec: ExecutionContext): Future[Transaction]
|
||||
|
||||
def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
fromAccount: AccountDb)(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt)
|
||||
tx <- makeOpReturnCommitment(message, hashMessage, feeRate, fromAccount)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
account <- getDefaultAccount()
|
||||
tx <- makeOpReturnCommitment(message, hashMessage, feeRate, account)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
def listDefaultAccountUtxos(): Future[Vector[SpendingInfoDb]]
|
||||
|
||||
def listUtxos(hdAccount: HDAccount): Future[Vector[SpendingInfoDb]]
|
||||
|
||||
override def clearAllUtxos(): Future[HDWalletApi]
|
||||
|
||||
def listAccounts(): Future[Vector[AccountDb]] = {
|
||||
accountHandling.listAccounts()
|
||||
}
|
||||
|
||||
/** Lists all wallet accounts with the given type
|
||||
* @param purpose
|
||||
* @return
|
||||
* [[Future[Vector[AccountDb]]
|
||||
*/
|
||||
def listAccounts(purpose: HDPurpose)(implicit
|
||||
ec: ExecutionContext): Future[Vector[AccountDb]] = {
|
||||
accountHandling.listAccounts().map(_.filter(_.hdAccount.purpose == purpose))
|
||||
}
|
||||
|
||||
def fundRawTransaction(
|
||||
destinations: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
markAsReserved: Boolean)
|
||||
: Future[FundRawTxHelper[ShufflingNonInteractiveFinalizer]] = {
|
||||
fundTxHandling.fundRawTransaction(destinations,
|
||||
feeRate,
|
||||
fromAccount,
|
||||
markAsReserved)
|
||||
}
|
||||
private def determineFeeRate(feeRateOpt: Option[FeeUnit]): Future[FeeUnit] =
|
||||
feeRateOpt match {
|
||||
case None =>
|
||||
feeRateApi.getFeeRate()
|
||||
case Some(feeRate) =>
|
||||
Future.successful(feeRate)
|
||||
}
|
||||
}
|
|
@ -9,6 +9,15 @@ import scala.concurrent.Future
|
|||
|
||||
trait UtxoHandlingApi {
|
||||
|
||||
/** Removes all utxos from the wallet. Don't call this unless you are sure you
|
||||
* can recover your wallet
|
||||
*/
|
||||
def clearAllUtxos(): Future[Unit]
|
||||
|
||||
def clearAllAddresses(): Future[Unit]
|
||||
|
||||
def listDefaultAccountUtxos(): Future[Vector[SpendingInfoDb]]
|
||||
|
||||
/** Lists unspent transaction outputs in the wallet
|
||||
* @return
|
||||
* Vector[SpendingInfoDb]
|
||||
|
@ -19,6 +28,8 @@ trait UtxoHandlingApi {
|
|||
|
||||
def listUtxos(state: TxoState): Future[Vector[SpendingInfoDb]]
|
||||
|
||||
def listUtxos(account: HDAccount): Future[Vector[SpendingInfoDb]]
|
||||
|
||||
def listUtxos(
|
||||
hdAccount: HDAccount,
|
||||
tag: AddressTag): Future[Vector[SpendingInfoDb]]
|
||||
|
|
|
@ -2,14 +2,12 @@ package org.bitcoins.core.api.wallet
|
|||
|
||||
import org.bitcoins.core.api.chain.ChainQueryApi
|
||||
import org.bitcoins.core.api.feeprovider.FeeRateApi
|
||||
import org.bitcoins.core.api.keymanager.KeyManagerApi
|
||||
import org.bitcoins.core.api.node.NodeApi
|
||||
import org.bitcoins.core.api.wallet.db.*
|
||||
import org.bitcoins.core.crypto.ExtPublicKey
|
||||
import org.bitcoins.core.currency.CurrencyUnit
|
||||
import org.bitcoins.core.hd.HDAccount
|
||||
import org.bitcoins.core.protocol.BitcoinAddress
|
||||
import org.bitcoins.core.protocol.blockchain.Block
|
||||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
Transaction,
|
||||
|
@ -32,6 +30,13 @@ import scala.concurrent.{ExecutionContext, Future}
|
|||
* [[https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki BIP44]]
|
||||
*/
|
||||
trait WalletApi extends StartStopAsync[WalletApi] {
|
||||
def accountHandling: AccountHandlingApi
|
||||
def fundTxHandling: FundTransactionHandlingApi
|
||||
def rescanHandling: RescanHandlingApi
|
||||
def addressHandling: AddressHandlingApi
|
||||
def utxoHandling: UtxoHandlingApi
|
||||
def transactionProcessing: TransactionProcessingApi
|
||||
def sendFundsHandling: SendFundsHandlingApi
|
||||
|
||||
val nodeApi: NodeApi
|
||||
val chainQueryApi: ChainQueryApi
|
||||
|
@ -49,30 +54,6 @@ trait WalletApi extends StartStopAsync[WalletApi] {
|
|||
|
||||
def stop(): Future[WalletApi]
|
||||
|
||||
/** Processes the give block, updating our DB state if it's relevant to us.
|
||||
*
|
||||
* @param block
|
||||
* The block we're processing
|
||||
*/
|
||||
def processBlock(block: Block): Future[Unit]
|
||||
|
||||
def processTransaction(
|
||||
transaction: Transaction,
|
||||
blockHashOpt: Option[DoubleSha256DigestBE]
|
||||
): Future[Unit]
|
||||
|
||||
/** Processes TXs originating from our wallet. This is called right after
|
||||
* we've signed a TX, updating our UTXO state.
|
||||
*/
|
||||
def processOurTransaction(
|
||||
transaction: Transaction,
|
||||
feeRate: FeeUnit,
|
||||
inputAmount: CurrencyUnit,
|
||||
sentAmount: CurrencyUnit,
|
||||
blockHashOpt: Option[DoubleSha256DigestBE],
|
||||
newTags: Vector[AddressTag]
|
||||
): Future[ProcessTxResult]
|
||||
|
||||
/** Gets the sum of all UTXOs in this wallet */
|
||||
def getBalance()(implicit ec: ExecutionContext): Future[CurrencyUnit] = {
|
||||
val confirmedF = getConfirmedBalance()
|
||||
|
@ -121,15 +102,6 @@ trait WalletApi extends StartStopAsync[WalletApi] {
|
|||
/** Checks if the wallet contains any data */
|
||||
def isEmpty(): Future[Boolean]
|
||||
|
||||
/** Removes all utxos from the wallet. Don't call this unless you are sure you
|
||||
* can recover your wallet
|
||||
*/
|
||||
def clearAllUtxos(): Future[WalletApi]
|
||||
|
||||
def clearAllAddresses(): Future[WalletApi]
|
||||
|
||||
def keyManager: KeyManagerApi
|
||||
|
||||
protected def determineFeeRate(feeRateOpt: Option[FeeUnit]): Future[FeeUnit] =
|
||||
feeRateOpt match {
|
||||
case None =>
|
||||
|
@ -138,158 +110,6 @@ trait WalletApi extends StartStopAsync[WalletApi] {
|
|||
Future.successful(feeRate)
|
||||
}
|
||||
|
||||
def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction]
|
||||
|
||||
def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt)
|
||||
tx <- sendFromOutPoints(outPoints, address, amount, feeRate)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction]
|
||||
|
||||
def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt)
|
||||
tx <- sendFromOutPoints(outPoints, address, feeRate)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** Sends the entire wallet balance to the given address */
|
||||
def sweepWallet(address: BitcoinAddress)(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = sweepWallet(address, None)
|
||||
|
||||
/** Sends the entire wallet balance to the given address */
|
||||
def sweepWallet(address: BitcoinAddress, feeRateOpt: Option[FeeUnit])(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt)
|
||||
tx <- sweepWallet(address, feeRate)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** Sends the entire wallet balance to the given address */
|
||||
def sweepWallet(address: BitcoinAddress, feeRate: FeeUnit)(implicit
|
||||
ec: ExecutionContext): Future[Transaction]
|
||||
|
||||
def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
algo: CoinSelectionAlgo)(implicit
|
||||
ec: ExecutionContext): Future[Transaction]
|
||||
|
||||
def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
algo: CoinSelectionAlgo
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt)
|
||||
tx <- sendWithAlgo(address, amount, feeRate, algo)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** Sends money to the address
|
||||
*
|
||||
* todo: add error handling to signature
|
||||
*/
|
||||
def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction]
|
||||
|
||||
def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt)
|
||||
tx <- sendToAddress(address, amount, feeRate)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** Sends funds using the specified outputs
|
||||
*
|
||||
* todo: add error handling to signature
|
||||
*/
|
||||
def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRateOpt: Option[FeeUnit])(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt)
|
||||
tx <- sendToOutputs(outputs, feeRate)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
def sendToOutputs(outputs: Vector[TransactionOutput], feeRate: FeeUnit)(
|
||||
implicit ec: ExecutionContext): Future[Transaction]
|
||||
|
||||
/** Sends funds to each address
|
||||
*/
|
||||
def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRateOpt: Option[FeeUnit])(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt)
|
||||
tx <- sendToAddresses(addresses, amounts, feeRate)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction]
|
||||
|
||||
def bumpFeeRBF(
|
||||
txId: DoubleSha256DigestBE,
|
||||
newFeeRate: FeeUnit): Future[Transaction]
|
||||
|
||||
/** Bumps the fee of the parent transaction with a new child transaction with
|
||||
* the given fee rate
|
||||
*/
|
||||
def bumpFeeCPFP(
|
||||
txId: DoubleSha256DigestBE,
|
||||
feeRate: FeeUnit): Future[Transaction]
|
||||
|
||||
def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRate: FeeUnit)(implicit ec: ExecutionContext): Future[Transaction]
|
||||
|
||||
def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRateOpt: Option[FeeUnit])(implicit
|
||||
ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt)
|
||||
tx <- makeOpReturnCommitment(message, hashMessage, feeRate)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** Determines if the given output is from this wallet and is a change output
|
||||
* from this wallet
|
||||
*/
|
||||
|
@ -365,4 +185,4 @@ case class WalletInfo(
|
|||
imported: Boolean)
|
||||
|
||||
/** An HDWallet that uses Neutrino to sync */
|
||||
trait NeutrinoHDWalletApi extends HDWalletApi with NeutrinoWalletApi
|
||||
trait NeutrinoHDWalletApi extends WalletApi with NeutrinoWalletApi
|
||||
|
|
|
@ -96,7 +96,7 @@ class DLCWalletCallbackTest extends BitcoinSDualWalletTest {
|
|||
_ <- initF
|
||||
contractId <- DLCWalletUtil.getContractId(wallets._1.wallet)
|
||||
fundingTx <- walletA.getDLCFundingTx(contractId)
|
||||
_ <- walletA.processTransaction(
|
||||
_ <- walletA.transactionProcessing.processTransaction(
|
||||
transaction = fundingTx,
|
||||
blockHashOpt = Some(CryptoGenerators.doubleSha256DigestBE.sample.get)
|
||||
)
|
||||
|
@ -107,7 +107,7 @@ class DLCWalletCallbackTest extends BitcoinSDualWalletTest {
|
|||
}
|
||||
}
|
||||
transaction <- walletA.executeDLC(contractId, sigs._1).map(_.get)
|
||||
_ <- walletB.processTransaction(transaction, None)
|
||||
_ <- walletB.transactionProcessing.processTransaction(transaction, None)
|
||||
} yield ()
|
||||
|
||||
for {
|
||||
|
@ -207,12 +207,12 @@ class DLCWalletCallbackTest extends BitcoinSDualWalletTest {
|
|||
_ <- initF
|
||||
contractId <- DLCWalletUtil.getContractId(wallets._1.wallet)
|
||||
fundingTx <- walletA.getDLCFundingTx(contractId)
|
||||
_ <- walletA.processTransaction(
|
||||
_ <- walletA.transactionProcessing.processTransaction(
|
||||
transaction = fundingTx,
|
||||
blockHashOpt = Some(CryptoGenerators.doubleSha256DigestBE.sample.get)
|
||||
)
|
||||
transaction <- walletA.executeDLCRefund(contractId)
|
||||
_ <- walletB.processTransaction(transaction, None)
|
||||
_ <- walletB.transactionProcessing.processTransaction(transaction, None)
|
||||
} yield ()
|
||||
|
||||
for {
|
||||
|
|
|
@ -45,10 +45,10 @@ class MultiWalletDLCTest extends BitcoinSWalletTest {
|
|||
)(configB, system)
|
||||
|
||||
for {
|
||||
accountA <- walletA.getDefaultAccount()
|
||||
accountA <- walletA.accountHandling.getDefaultAccount()
|
||||
|
||||
walletB <- walletBF
|
||||
accountB <- walletB.getDefaultAccount()
|
||||
accountB <- walletB.accountHandling.getDefaultAccount()
|
||||
|
||||
_ = assert(accountA.xpub != accountB.xpub)
|
||||
|
||||
|
|
|
@ -714,7 +714,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
|||
|
||||
tx <- walletB.broadcastDLCFundingTx(sign.contractId)
|
||||
// make sure other wallet sees it
|
||||
_ <- walletA.processTransaction(tx, None)
|
||||
_ <- walletA.transactionProcessing.processTransaction(tx, None)
|
||||
|
||||
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
||||
|
||||
|
@ -752,7 +752,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
|||
|
||||
tx <- walletB.broadcastDLCFundingTx(sign.contractId)
|
||||
// make sure other wallet sees it
|
||||
_ <- walletA.processTransaction(tx, None)
|
||||
_ <- walletA.transactionProcessing.processTransaction(tx, None)
|
||||
|
||||
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
||||
|
||||
|
@ -884,7 +884,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
|||
}
|
||||
|
||||
tx <- walletB.broadcastDLCFundingTx(sign.contractId)
|
||||
_ <- walletA.processTransaction(tx, None)
|
||||
_ <- walletA.transactionProcessing.processTransaction(tx, None)
|
||||
|
||||
func = (wallet: DLCWallet) =>
|
||||
wallet.executeDLC(sign.contractId, sig).map(_.get)
|
||||
|
|
|
@ -113,8 +113,7 @@ abstract class DLCWallet
|
|||
DLCActionBuilder(dlcWalletDAOs)
|
||||
}
|
||||
|
||||
override protected lazy val transactionProcessing
|
||||
: DLCTransactionProcessing = {
|
||||
override lazy val transactionProcessing: DLCTransactionProcessing = {
|
||||
val txProcessing = TransactionProcessing(
|
||||
walletApi = this,
|
||||
chainQueryApi = chainQueryApi,
|
||||
|
@ -431,7 +430,7 @@ abstract class DLCWallet
|
|||
_ <- oracleNonceDAO.createAll(nonceDbs)
|
||||
chainType = HDChainType.External
|
||||
|
||||
account <- getDefaultAccountForType(AddressType.SegWit)
|
||||
account <- accountHandling.getDefaultAccountForType(AddressType.SegWit)
|
||||
nextIndex <- addressHandling.getNextAvailableIndex(account, chainType)
|
||||
_ <- writeDLCKeysToAddressDb(account, chainType, nextIndex)
|
||||
|
||||
|
@ -880,7 +879,7 @@ abstract class DLCWallet
|
|||
s"Creating DLC Accept for tempContractId ${offer.tempContractId.hex}"
|
||||
)
|
||||
val result = for {
|
||||
account <- getDefaultAccountForType(AddressType.SegWit)
|
||||
account <- accountHandling.getDefaultAccountForType(AddressType.SegWit)
|
||||
fundRawTxHelper <- fundDLCAcceptMsg(
|
||||
offer = offer,
|
||||
collateral = collateral,
|
||||
|
|
|
@ -79,6 +79,6 @@ walletConf.addCallbacks(exampleCallbacks)
|
|||
// Then to trigger the event we can run
|
||||
val exampleTx = Transaction(
|
||||
"0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000")
|
||||
wallet.processTransaction(exampleTx, None)
|
||||
wallet.transactionProcessing.processTransaction(exampleTx, None)
|
||||
|
||||
```
|
||||
|
|
|
@ -90,11 +90,11 @@ val initBalanceF = for {
|
|||
val clearedWalletF = for {
|
||||
w <- walletF
|
||||
_ <- initBalanceF
|
||||
clearedWallet <- w.clearAllUtxos()
|
||||
zeroBalance <- clearedWallet.getBalance()
|
||||
_<- w.utxoHandling.clearAllUtxos()
|
||||
zeroBalance <- w.getBalance()
|
||||
} yield {
|
||||
println(s"Balance after clearing utxos: ${zeroBalance}")
|
||||
clearedWallet
|
||||
w
|
||||
}
|
||||
|
||||
//we need to pick how many addresses we want to generate off of our keychain
|
||||
|
|
|
@ -170,7 +170,7 @@ val transactionF: Future[(Transaction, Option[DoubleSha256DigestBE])] = for {
|
|||
val balanceF: Future[CurrencyUnit] = for {
|
||||
wallet <- walletF
|
||||
(tx, blockhash) <- transactionF
|
||||
_ <- wallet.processTransaction(tx, blockhash)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, blockhash)
|
||||
balance <- wallet.getBalance()
|
||||
} yield balance
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
|
|||
for {
|
||||
balance <- wallet.getBalance()
|
||||
addresses <- wallet.addressHandling.listAddresses()
|
||||
utxos <- wallet.listDefaultAccountUtxos()
|
||||
utxos <- wallet.utxoHandling.listDefaultAccountUtxos()
|
||||
} yield {
|
||||
// +- fee rate because signatures could vary in size
|
||||
(expectedBalance === balance +- FeeRate.currencyUnit) &&
|
||||
|
@ -97,7 +97,9 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
|
|||
for {
|
||||
// send
|
||||
addr <- bitcoind.getNewAddress
|
||||
_ <- wallet.sendToAddress(addr, TestAmount, Some(FeeRate))
|
||||
_ <- wallet.sendFundsHandling.sendToAddress(addr,
|
||||
TestAmount,
|
||||
Some(FeeRate))
|
||||
|
||||
_ <- wallet.getConfirmedBalance()
|
||||
_ <- wallet.getUnconfirmedBalance()
|
||||
|
@ -148,7 +150,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
|
|||
_ <- wallet.addressHandling.watchScriptPubKey(spk)
|
||||
|
||||
// send
|
||||
txSent <- wallet.sendToOutputs(Vector(output), FeeRate)
|
||||
txSent <- wallet.sendFundsHandling.sendToOutputs(Vector(output), FeeRate)
|
||||
_ <- node.broadcastTransaction(txSent)
|
||||
|
||||
// confirm
|
||||
|
@ -169,7 +171,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
|
|||
rescan <- wallet.isRescanning()
|
||||
balance <- wallet.getBalance()
|
||||
addresses <- wallet.addressHandling.listAddresses()
|
||||
utxos <- wallet.listDefaultAccountUtxos()
|
||||
utxos <- wallet.utxoHandling.listDefaultAccountUtxos()
|
||||
spks = utxos
|
||||
.map(_.output.scriptPubKey)
|
||||
} yield {
|
||||
|
@ -182,7 +184,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
|
|||
|
||||
for {
|
||||
addresses <- wallet.addressHandling.listAddresses()
|
||||
utxos <- wallet.listDefaultAccountUtxos()
|
||||
utxos <- wallet.utxoHandling.listDefaultAccountUtxos()
|
||||
_ = assert(addresses.size == 6)
|
||||
_ = assert(utxos.size == 3)
|
||||
|
||||
|
@ -192,7 +194,7 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
|
|||
.sendToAddress(address, TestAmount)
|
||||
|
||||
addresses <- wallet.addressHandling.listAddresses()
|
||||
utxos <- wallet.listDefaultAccountUtxos()
|
||||
utxos <- wallet.utxoHandling.listDefaultAccountUtxos()
|
||||
_ = assert(addresses.size == 7)
|
||||
_ = assert(utxos.size == 3)
|
||||
_ <-
|
||||
|
@ -206,9 +208,9 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
|
|||
.getSyncDescriptorOpt()
|
||||
.map(_.get.height == bitcoindHeight)
|
||||
})
|
||||
_ <- wallet.clearAllUtxos()
|
||||
_ <- wallet.utxoHandling.clearAllUtxos()
|
||||
addresses <- wallet.addressHandling.listAddresses()
|
||||
utxos <- wallet.listDefaultAccountUtxos()
|
||||
utxos <- wallet.utxoHandling.listDefaultAccountUtxos()
|
||||
_ = assert(addresses.nonEmpty)
|
||||
_ = assert(utxos.isEmpty)
|
||||
|
||||
|
@ -282,7 +284,9 @@ class NeutrinoNodeWithWalletTest extends NodeTestWithCachedBitcoindNewest {
|
|||
stoppedNode <- stopF
|
||||
|
||||
// create a transaction that spends to bitcoind with our wallet
|
||||
tx <- wallet.sendToAddress(bitcoindAddr, sendAmt, SatoshisPerByte.one)
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(bitcoindAddr,
|
||||
sendAmt,
|
||||
SatoshisPerByte.one)
|
||||
// broadcast tx
|
||||
_ <- bitcoind.sendRawTransaction(tx)
|
||||
_ <- bitcoind.generateToAddress(1, bitcoindAddr)
|
||||
|
|
|
@ -599,8 +599,9 @@ object BitcoinSWalletTest extends WalletLogger {
|
|||
val hdAccount1 = WalletTestUtil.getHdAccount1(config)
|
||||
val expectedDefaultAmt = BitcoinSWalletTest.expectedDefaultAmt
|
||||
val expectedAccount1Amt = BitcoinSWalletTest.expectedAccount1Amt
|
||||
val defaultBalanceF = fundedWallet.wallet.getBalance(defaultAccount)
|
||||
val account1BalanceF = fundedWallet.wallet.getBalance(hdAccount1)
|
||||
val accountHandling = fundedWallet.wallet.accountHandling
|
||||
val defaultBalanceF = accountHandling.getBalance(defaultAccount)
|
||||
val account1BalanceF = accountHandling.getBalance(hdAccount1)
|
||||
for {
|
||||
balance <- defaultBalanceF
|
||||
account1Balance <- account1BalanceF
|
||||
|
|
|
@ -401,7 +401,7 @@ object DLCWalletUtil extends BitcoinSLogger {
|
|||
sigs <- walletA.signDLC(accept)
|
||||
_ <- walletB.addDLCSigs(sigs)
|
||||
tx <- walletB.broadcastDLCFundingTx(sigs.contractId)
|
||||
_ <- walletA.processTransaction(tx, None)
|
||||
_ <- walletA.transactionProcessing.processTransaction(tx, None)
|
||||
} yield {
|
||||
(
|
||||
InitializedDLCWallet(FundedDLCWallet(walletA)),
|
||||
|
@ -478,8 +478,8 @@ object DLCWalletUtil extends BitcoinSLogger {
|
|||
func(dlcB)
|
||||
}
|
||||
_ <- {
|
||||
if (asInitiator) dlcB.processTransaction(tx, None)
|
||||
else dlcA.processTransaction(tx, None)
|
||||
if (asInitiator) dlcB.transactionProcessing.processTransaction(tx, None)
|
||||
else dlcA.transactionProcessing.processTransaction(tx, None)
|
||||
}
|
||||
_ <- dlcA.broadcastTransaction(tx)
|
||||
dlcDb <- dlcA.dlcDAO.findByContractId(contractId)
|
||||
|
|
|
@ -4,7 +4,7 @@ import org.apache.pekko.actor.ActorSystem
|
|||
import org.bitcoins.commons.util.BitcoinSLogger
|
||||
import org.bitcoins.core.api.chain.ChainQueryApi
|
||||
import org.bitcoins.core.api.node.NodeApi
|
||||
import org.bitcoins.core.api.wallet.HDWalletApi
|
||||
import org.bitcoins.core.api.wallet.NeutrinoHDWalletApi
|
||||
import org.bitcoins.core.currency.CurrencyUnit
|
||||
import org.bitcoins.core.hd.HDAccount
|
||||
import org.bitcoins.core.protocol.BitcoinAddress
|
||||
|
@ -87,7 +87,8 @@ trait FundWalletUtil extends BitcoinSLogger {
|
|||
|
||||
val fundedWalletF =
|
||||
txsF.flatMap(txs =>
|
||||
FutureUtil.sequentially(txs)(tx => wallet.processTransaction(tx, None)))
|
||||
FutureUtil.sequentially(txs)(tx =>
|
||||
wallet.transactionProcessing.processTransaction(tx, None)))
|
||||
|
||||
fundedWalletF.map(_ => wallet)
|
||||
}
|
||||
|
@ -95,9 +96,9 @@ trait FundWalletUtil extends BitcoinSLogger {
|
|||
def fundAccountForWalletWithBitcoind(
|
||||
amts: Vector[CurrencyUnit],
|
||||
account: HDAccount,
|
||||
wallet: HDWalletApi,
|
||||
wallet: NeutrinoHDWalletApi,
|
||||
bitcoind: BitcoindRpcClient
|
||||
)(implicit ec: ExecutionContext): Future[HDWalletApi] = {
|
||||
)(implicit ec: ExecutionContext): Future[NeutrinoHDWalletApi] = {
|
||||
|
||||
val addressesF: Future[Vector[BitcoinAddress]] = Future.sequence {
|
||||
Vector.fill(3)(wallet.accountHandling.getNewAddress(account))
|
||||
|
@ -107,7 +108,7 @@ trait FundWalletUtil extends BitcoinSLogger {
|
|||
addresses <- addressesF
|
||||
addressAmountMap = addresses.zip(amts).toMap
|
||||
(tx, blockHash) <- fundAddressesWithBitcoind(addressAmountMap, bitcoind)
|
||||
_ <- wallet.processTransaction(tx, Some(blockHash))
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, Some(blockHash))
|
||||
} yield (tx, blockHash)
|
||||
|
||||
txAndHashF.map(_ => wallet)
|
||||
|
@ -154,13 +155,14 @@ trait FundWalletUtil extends BitcoinSLogger {
|
|||
// sanity check to make sure we have money
|
||||
for {
|
||||
fundedWallet <- fundedAccount1WalletF
|
||||
balance <- fundedWallet.getBalance(defaultAccount)
|
||||
accountHandling = fundedWallet.accountHandling
|
||||
balance <- accountHandling.getBalance(defaultAccount)
|
||||
_ = require(
|
||||
balance == BitcoinSWalletTest.expectedDefaultAmt,
|
||||
s"Funding wallet fixture failed to fund the wallet, got balance=${balance} expected=${BitcoinSWalletTest.expectedDefaultAmt}"
|
||||
)
|
||||
|
||||
account1Balance <- fundedWallet.getBalance(hdAccount1)
|
||||
account1Balance <- accountHandling.getBalance(hdAccount1)
|
||||
_ = require(
|
||||
account1Balance == BitcoinSWalletTest.expectedAccount1Amt,
|
||||
s"Funding wallet fixture failed to fund account 1, " +
|
||||
|
|
|
@ -73,7 +73,9 @@ class AddressHandlingTest extends BitcoinSWalletTest {
|
|||
_ = assert(exists, s"Wallet must contain address after generating it")
|
||||
address2 <- wallet.addressHandling.getUnusedAddress
|
||||
_ = assert(address1 == address2, "Must generate same address")
|
||||
_ <- wallet.sendToAddress(address1, Satoshis(10000), None)
|
||||
_ <- wallet.sendFundsHandling.sendToAddress(address1,
|
||||
Satoshis(10000),
|
||||
None)
|
||||
address3 <- wallet.addressHandling.getUnusedAddress
|
||||
} yield {
|
||||
assert(address1 != address3, "Must generate a new address")
|
||||
|
@ -111,7 +113,9 @@ class AddressHandlingTest extends BitcoinSWalletTest {
|
|||
)
|
||||
|
||||
tempAddress <- wallet.addressHandling.getNewAddress()
|
||||
tx <- wallet.sendToAddress(tempAddress, Bitcoins(1), None)
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(tempAddress,
|
||||
Bitcoins(1),
|
||||
None)
|
||||
spentDbs <- wallet.spendingInfoDAO.findOutputsBeingSpent(tx)
|
||||
spentAddresses <- wallet.addressHandling.listSpentAddresses()
|
||||
} yield {
|
||||
|
|
|
@ -56,7 +56,7 @@ class AddressTagIntegrationTest extends BitcoinSWalletTest {
|
|||
.map(unconfirmed => assert(unconfirmed == 0.bitcoin))
|
||||
|
||||
// after this, tx is unconfirmed in wallet
|
||||
_ <- wallet.processTransaction(tx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, None)
|
||||
|
||||
// we should now have one UTXO in the wallet
|
||||
// it should not be confirmed
|
||||
|
@ -93,7 +93,7 @@ class AddressTagIntegrationTest extends BitcoinSWalletTest {
|
|||
)
|
||||
}
|
||||
signedTx = rawTxHelper.signedTx
|
||||
_ <- wallet.processTransaction(signedTx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(signedTx, None)
|
||||
|
||||
utxos <- wallet.listUtxos()
|
||||
balancePostSend <- wallet.getBalance()
|
||||
|
@ -137,15 +137,15 @@ class AddressTagIntegrationTest extends BitcoinSWalletTest {
|
|||
walletAddr1 <- walletAddr1F
|
||||
txid <- bitcoind.sendToAddress(walletAddr1, Bitcoins.two)
|
||||
tx <- bitcoind.getRawTransaction(txid)
|
||||
_ <- wallet.processTransaction(tx.hex, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx.hex, None)
|
||||
taggedAddress <- taggedAddrF
|
||||
tx <- wallet.sendToAddress(
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(
|
||||
taggedAddress,
|
||||
Satoshis(100000),
|
||||
SatoshisPerVirtualByte.one,
|
||||
Vector(exampleTag)
|
||||
)
|
||||
_ <- wallet.processTransaction(tx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, None)
|
||||
_ <- bitcoind.sendRawTransaction(tx)
|
||||
bitcoindAddr <- bitcoindAddrF
|
||||
_ <- PekkoUtil.nonBlockingSleep(1.second)
|
||||
|
|
|
@ -51,7 +51,7 @@ class BitcoindBlockPollingTest
|
|||
wallet,
|
||||
bitcoind,
|
||||
None
|
||||
)(wallet.processBlock(_).map(_ => ()))
|
||||
)(wallet.transactionProcessing.processBlock(_).map(_ => ()))
|
||||
_ <- bitcoind.generateToAddress(6, bech32Address)
|
||||
|
||||
// Wait for it to process
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package org.bitcoins.wallet
|
||||
|
||||
import org.bitcoins.core.api.wallet.HDWalletApi
|
||||
import org.bitcoins.core.api.wallet.NeutrinoHDWalletApi
|
||||
import org.bitcoins.core.currency.Bitcoins
|
||||
import org.bitcoins.core.protocol.transaction.TransactionOutput
|
||||
import org.bitcoins.core.wallet.utxo.StorageLocationTag.HotStorage
|
||||
import org.bitcoins.core.wallet.utxo._
|
||||
import org.bitcoins.core.wallet.utxo.*
|
||||
import org.bitcoins.testkit.wallet.{
|
||||
BitcoinSWalletTestCachedBitcoindNewest,
|
||||
WalletTestUtil,
|
||||
|
@ -173,7 +173,7 @@ class FundTransactionHandlingTest
|
|||
for {
|
||||
feeRate <- wallet.getFeeRate()
|
||||
account1DbOpt <- account1DbF
|
||||
fundRawTxHelper <- wallet.fundRawTransaction(
|
||||
fundRawTxHelper <- wallet.fundTxHandling.fundRawTransaction(
|
||||
Vector(newDestination),
|
||||
feeRate,
|
||||
account1DbOpt.get,
|
||||
|
@ -199,7 +199,7 @@ class FundTransactionHandlingTest
|
|||
val fundedTxF = for {
|
||||
feeRate <- wallet.getFeeRate()
|
||||
account1DbOpt <- account1DbF
|
||||
fundedTx <- wallet.fundRawTransaction(
|
||||
fundedTx <- wallet.fundTxHandling.fundRawTransaction(
|
||||
destinations = Vector(newDestination),
|
||||
feeRate = feeRate,
|
||||
fromAccount = account1DbOpt.get,
|
||||
|
@ -218,22 +218,20 @@ class FundTransactionHandlingTest
|
|||
val bitcoind = fundedWallet.bitcoind
|
||||
val fundedTxF = for {
|
||||
feeRate <- wallet.getFeeRate()
|
||||
_ <- wallet.accountHandling.createNewAccount(
|
||||
wallet.keyManager.kmParams.purpose)
|
||||
accounts <- wallet.listAccounts()
|
||||
accounts <- wallet.accountHandling.listAccounts()
|
||||
account2 = accounts.find(_.hdAccount.index == 2).get
|
||||
|
||||
addr <- wallet.accountHandling.getNewAddress(account2)
|
||||
|
||||
hash <- bitcoind.generateToAddress(1, addr).map(_.head)
|
||||
block <- bitcoind.getBlockRaw(hash)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
|
||||
utxos <- wallet.listUtxos(account2.hdAccount)
|
||||
utxos <- wallet.utxoHandling.listUtxos(account2.hdAccount)
|
||||
_ = assert(utxos.size == 1)
|
||||
|
||||
fundedTx <-
|
||||
wallet.fundRawTransaction(
|
||||
wallet.fundTxHandling.fundRawTransaction(
|
||||
destinations = Vector(destination),
|
||||
feeRate = feeRate,
|
||||
fromAccount = account2,
|
||||
|
@ -269,14 +267,16 @@ class FundTransactionHandlingTest
|
|||
}
|
||||
|
||||
def testAddressTagFunding(
|
||||
wallet: HDWalletApi,
|
||||
wallet: NeutrinoHDWalletApi,
|
||||
tag: AddressTag
|
||||
): Future[Assertion] = {
|
||||
for {
|
||||
feeRate <- wallet.getFeeRate()
|
||||
taggedAddr <- wallet.addressHandling.getNewAddress(Vector(tag))
|
||||
_ <-
|
||||
wallet.sendToAddress(taggedAddr, destination.value * 2, Some(feeRate))
|
||||
wallet.sendFundsHandling.sendToAddress(taggedAddr,
|
||||
destination.value * 2,
|
||||
Some(feeRate))
|
||||
taggedBalance <- wallet.getBalance(tag)
|
||||
_ = assert(taggedBalance == destination.value * 2)
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ class LegacyWalletTest extends BitcoinSWalletTest {
|
|||
it should "generate legacy addresses" in { (wallet: Wallet) =>
|
||||
for {
|
||||
addr <- wallet.getNewAddress()
|
||||
account <- wallet.getDefaultAccount()
|
||||
account <- wallet.accountHandling.getDefaultAccount()
|
||||
otherAddr <- wallet.getNewAddress()
|
||||
thirdAddr <- wallet.addressHandling.getNewAddress(AddressType.Legacy)
|
||||
allAddrs <- wallet.addressHandling.listAddresses()
|
||||
|
@ -31,7 +31,8 @@ class LegacyWalletTest extends BitcoinSWalletTest {
|
|||
|
||||
it should "generate segwit addresses" in { wallet =>
|
||||
for {
|
||||
account <- wallet.getDefaultAccountForType(AddressType.SegWit)
|
||||
account <- wallet.accountHandling.getDefaultAccountForType(
|
||||
AddressType.SegWit)
|
||||
addr <- wallet.addressHandling.getNewAddress(AddressType.SegWit)
|
||||
} yield {
|
||||
assert(account.hdAccount.purpose == HDPurpose.SegWit)
|
||||
|
|
|
@ -41,10 +41,10 @@ class MultiWalletTest extends BitcoinSAsyncTest with EmbeddedPg {
|
|||
|
||||
val assertionF: Future[Assertion] = for {
|
||||
walletA <- walletAF
|
||||
accountA <- walletA.getDefaultAccount()
|
||||
accountA <- walletA.accountHandling.getDefaultAccount()
|
||||
|
||||
walletB <- walletBF
|
||||
accountB <- walletB.getDefaultAccount()
|
||||
accountB <- walletB.accountHandling.getDefaultAccount()
|
||||
_ <- walletA.walletConfig.stop()
|
||||
_ <- walletB.walletConfig.stop()
|
||||
} yield assert(accountA.xpub != accountB.xpub)
|
||||
|
|
|
@ -50,7 +50,7 @@ class ProcessBlockTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
.map(_.head)
|
||||
block <- bitcoind.getBlockRaw(hash)
|
||||
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
utxos <- wallet.listUtxos()
|
||||
height <- bitcoind.getBlockCount()
|
||||
bestHash <- bitcoind.getBestBlockHash()
|
||||
|
@ -78,7 +78,8 @@ class ProcessBlockTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
addr <- wallet.getNewAddress()
|
||||
hashes <- bitcoind.generateToAddress(101, addr)
|
||||
blocks <- FutureUtil.sequentially(hashes)(bitcoind.getBlockRaw)
|
||||
_ <- FutureUtil.sequentially(blocks)(wallet.processBlock)
|
||||
_ <- FutureUtil.sequentially(blocks)(
|
||||
wallet.transactionProcessing.processBlock)
|
||||
coinbaseUtxos <- wallet.listUtxos(TxoState.ImmatureCoinbase)
|
||||
confirmedUtxos <- wallet.listUtxos(TxoState.ConfirmedReceived)
|
||||
balance <- wallet.getConfirmedBalance()
|
||||
|
@ -203,7 +204,7 @@ class ProcessBlockTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
.addUTXOToInput(recvTx, 0)
|
||||
.addKeyPathToInput(accountDb.xpub, bip32Path, addrDb.pubkey, 0)
|
||||
|
||||
signed <- wallet.signPSBT(psbt)
|
||||
signed <- wallet.sendFundsHandling.signPSBT(psbt)
|
||||
tx <- Future.fromTry(
|
||||
signed.finalizePSBT.flatMap(_.extractTransactionAndValidate)
|
||||
)
|
||||
|
@ -212,7 +213,7 @@ class ProcessBlockTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
hash <- bitcoind.generateToAddress(1, bitcoindAddr).map(_.head)
|
||||
block <- bitcoind.getBlockRaw(hash)
|
||||
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
|
||||
balance <- wallet.getBalance()
|
||||
} yield assert(balance == output0.value)
|
||||
|
|
|
@ -64,16 +64,16 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
|
|||
tx =
|
||||
TransactionTestUtil.buildTransactionTo(output, outPoint)
|
||||
|
||||
_ <- wallet.processTransaction(tx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, None)
|
||||
oldConfirmed <- wallet.getConfirmedBalance()
|
||||
oldUnconfirmed <- wallet.getUnconfirmedBalance()
|
||||
|
||||
// repeating the action should not make a difference
|
||||
_ <- checkUtxosAndBalance(wallet) {
|
||||
wallet.processTransaction(tx, None)
|
||||
wallet.transactionProcessing.processTransaction(tx, None)
|
||||
}
|
||||
|
||||
_ <- wallet.processTransaction(
|
||||
_ <- wallet.transactionProcessing.processTransaction(
|
||||
tx,
|
||||
Some(MockChainQueryApi.testBlockHash)
|
||||
)
|
||||
|
@ -83,7 +83,9 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
|
|||
|
||||
// repeating the action should not make a difference
|
||||
_ <- checkUtxosAndBalance(wallet) {
|
||||
wallet.processTransaction(tx, Some(MockChainQueryApi.testBlockHash))
|
||||
wallet.transactionProcessing.processTransaction(
|
||||
tx,
|
||||
Some(MockChainQueryApi.testBlockHash))
|
||||
}
|
||||
} yield {
|
||||
val ourOutputs =
|
||||
|
@ -104,23 +106,23 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
|
|||
.transactionTo(address.scriptPubKey)
|
||||
.sampleSome
|
||||
|
||||
_ <- wallet.processTransaction(tx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, None)
|
||||
oldConfirmed <- wallet.getConfirmedBalance()
|
||||
oldUnconfirmed <- wallet.getUnconfirmedBalance()
|
||||
|
||||
// repeating the action should not make a difference
|
||||
_ <- checkUtxosAndBalance(wallet) {
|
||||
wallet.processTransaction(tx, None)
|
||||
wallet.transactionProcessing.processTransaction(tx, None)
|
||||
}
|
||||
|
||||
_ <- wallet.processTransaction(tx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, None)
|
||||
newConfirmed <- wallet.getConfirmedBalance()
|
||||
newUnconfirmed <- wallet.getUnconfirmedBalance()
|
||||
utxosPostAdd <- wallet.listUtxos()
|
||||
|
||||
// repeating the action should not make a difference
|
||||
_ <- checkUtxosAndBalance(wallet) {
|
||||
wallet.processTransaction(tx, None)
|
||||
wallet.transactionProcessing.processTransaction(tx, None)
|
||||
}
|
||||
} yield {
|
||||
val ourOutputs =
|
||||
|
@ -137,7 +139,7 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
|
|||
val unrelated = TransactionGenerators.transaction.sampleSome
|
||||
for {
|
||||
_ <- checkUtxosAndBalance(wallet) {
|
||||
wallet.processTransaction(unrelated, None)
|
||||
wallet.transactionProcessing.processTransaction(unrelated, None)
|
||||
}
|
||||
|
||||
balance <- wallet.getBalance()
|
||||
|
@ -173,7 +175,8 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
|
|||
// make sure wallet is empty
|
||||
balance <- wallet.getBalance()
|
||||
_ = assert(balance == Bitcoins.zero)
|
||||
processed <- wallet.processTransaction(fundingTx, None)
|
||||
processed <- wallet.transactionProcessing.processTransaction(fundingTx,
|
||||
None)
|
||||
balance <- wallet.getBalance()
|
||||
_ = assert(balance == amtWithFee)
|
||||
} yield processed
|
||||
|
@ -191,7 +194,7 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
|
|||
fromTagOpt = None,
|
||||
markAsReserved = true
|
||||
)
|
||||
_ <- wallet.processTransaction(
|
||||
_ <- wallet.transactionProcessing.processTransaction(
|
||||
transaction = rawTxHelper.signedTx,
|
||||
blockHashOpt = None
|
||||
)
|
||||
|
@ -210,8 +213,8 @@ class ProcessTransactionTest extends BitcoinSWalletTest {
|
|||
.transactionTo(address.scriptPubKey)
|
||||
.sampleSome
|
||||
|
||||
_ <- wallet.processTransaction(tx, None)
|
||||
accountBalance <- wallet.getUnconfirmedBalance(account1)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, None)
|
||||
accountBalance <- wallet.accountHandling.getUnconfirmedBalance(account1)
|
||||
} yield {
|
||||
assert(accountBalance == CurrencyUnits.zero)
|
||||
}
|
||||
|
|
|
@ -24,9 +24,9 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
val wallet = fixture.wallet
|
||||
|
||||
for {
|
||||
accountDb <- wallet.getDefaultAccount()
|
||||
accountDb <- wallet.accountHandling.getDefaultAccount()
|
||||
account = accountDb.hdAccount
|
||||
utxos <- wallet.listUtxos(account)
|
||||
utxos <- wallet.utxoHandling.listUtxos(account)
|
||||
_ = assert(utxos.nonEmpty)
|
||||
|
||||
addresses <- wallet.accountHandling.listAddresses(account)
|
||||
|
@ -34,7 +34,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
|
||||
_ <- wallet.accountHandling.clearUtxos(account)
|
||||
|
||||
clearedUtxos <- wallet.listUtxos(account)
|
||||
clearedUtxos <- wallet.utxoHandling.listUtxos(account)
|
||||
clearedAddresses <- wallet.accountHandling.listAddresses(account)
|
||||
} yield {
|
||||
assert(clearedUtxos.isEmpty)
|
||||
|
@ -55,7 +55,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
addresses <- wallet.addressHandling.listAddresses()
|
||||
_ = assert(addresses.nonEmpty)
|
||||
|
||||
_ <- wallet.clearAllUtxos()
|
||||
_ <- wallet.utxoHandling.clearAllUtxos()
|
||||
|
||||
clearedUtxos <- wallet.listUtxos()
|
||||
clearedAddresses <- wallet.addressHandling.listAddresses()
|
||||
|
@ -110,7 +110,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
bitcoindAddr <- bitcoindAddrF
|
||||
blockHashes <-
|
||||
bitcoind.generateToAddress(blocks = numBlocks, address = bitcoindAddr)
|
||||
_ <- wallet.processTransaction(
|
||||
_ <- wallet.transactionProcessing.processTransaction(
|
||||
transaction = tx,
|
||||
blockHashOpt = blockHashes.headOption
|
||||
)
|
||||
|
@ -131,7 +131,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
initBlockHeight <- initBlockHeightF
|
||||
txInBlockHeight = initBlockHeight + numBlocks
|
||||
txInBlockHeightOpt = Some(BlockStamp.BlockHeight(txInBlockHeight))
|
||||
_ <- wallet.clearAllUtxos()
|
||||
_ <- wallet.utxoHandling.clearAllUtxos()
|
||||
zeroBalance <- wallet.getBalance()
|
||||
_ = assert(zeroBalance == Satoshis.zero)
|
||||
rescanState <- wallet.rescanHandling.rescanNeutrinoWallet(
|
||||
|
@ -159,7 +159,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
val numBlocks = 1
|
||||
val initBalanceF = wallet.getBalance()
|
||||
|
||||
val defaultAccountF = wallet.getDefaultAccount()
|
||||
val defaultAccountF = wallet.accountHandling.getDefaultAccount()
|
||||
// send funds to a fresh wallet address
|
||||
val addrF = wallet.getNewAddress()
|
||||
val bitcoindAddrF = bitcoind.getNewAddress
|
||||
|
@ -171,7 +171,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
bitcoindAddr <- bitcoindAddrF
|
||||
blockHashes <-
|
||||
bitcoind.generateToAddress(blocks = numBlocks, address = bitcoindAddr)
|
||||
_ <- wallet.processTransaction(
|
||||
_ <- wallet.transactionProcessing.processTransaction(
|
||||
transaction = tx,
|
||||
blockHashOpt = blockHashes.headOption
|
||||
)
|
||||
|
@ -191,15 +191,15 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
|
||||
account <- defaultAccountF
|
||||
txIds <-
|
||||
wallet
|
||||
wallet.utxoHandling
|
||||
.listUtxos(account.hdAccount)
|
||||
.map(_.map(_.txid))
|
||||
_ <- wallet
|
||||
.findByTxIds(txIds)
|
||||
.map(_.flatMap(_.blockHashOpt))
|
||||
|
||||
_ <- wallet.clearAllUtxos()
|
||||
_ <- wallet.clearAllAddresses()
|
||||
_ <- wallet.utxoHandling.clearAllUtxos()
|
||||
_ <- wallet.utxoHandling.clearAllAddresses()
|
||||
balanceAfterClear <- wallet.getBalance()
|
||||
rescanState <- wallet.rescanHandling.fullRescanNeutrinoWallet(
|
||||
addressBatchSize = 1,
|
||||
|
@ -235,7 +235,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
bitcoindAddr <- bitcoindAddrF
|
||||
blockHashes <-
|
||||
bitcoind.generateToAddress(blocks = numBlocks, address = bitcoindAddr)
|
||||
_ <- wallet.processTransaction(
|
||||
_ <- wallet.transactionProcessing.processTransaction(
|
||||
transaction = tx,
|
||||
blockHashOpt = blockHashes.headOption
|
||||
)
|
||||
|
@ -371,7 +371,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
// now send a payment to our wallet
|
||||
hashes <- bitcoind.generateToAddress(1, address)
|
||||
block <- bitcoind.getBlockRaw(hashes.head)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
fundedAddresses <- wallet.addressHandling.listFundedAddresses()
|
||||
utxos <- wallet.listUtxos(TxoState.ImmatureCoinbase)
|
||||
} yield {
|
||||
|
@ -412,7 +412,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
)
|
||||
txid <- bitcoind.sendToAddress(addressNoFunds, amt)
|
||||
tx <- bitcoind.getRawTransactionRaw(txid)
|
||||
_ <- wallet.processTransaction(tx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, None)
|
||||
unconfirmedBalance <- wallet.getUnconfirmedBalance()
|
||||
} yield {
|
||||
assert(unconfirmedBalance == amt)
|
||||
|
@ -505,7 +505,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
val numBlocks = 1
|
||||
val initBalanceF = wallet.getBalance()
|
||||
|
||||
val defaultAccountF = wallet.getDefaultAccount()
|
||||
val defaultAccountF = wallet.accountHandling.getDefaultAccount()
|
||||
// send funds to a fresh wallet address
|
||||
val addr1F = wallet.getNewAddress()
|
||||
val addr2F = wallet.getNewAddress()
|
||||
|
@ -523,7 +523,7 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
bitcoindAddr <- bitcoindAddrF
|
||||
blockHashes <-
|
||||
bitcoind.generateToAddress(blocks = numBlocks, address = bitcoindAddr)
|
||||
_ <- wallet.processTransaction(
|
||||
_ <- wallet.transactionProcessing.processTransaction(
|
||||
transaction = tx,
|
||||
blockHashOpt = blockHashes.headOption
|
||||
)
|
||||
|
@ -543,15 +543,15 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
|
||||
account <- defaultAccountF
|
||||
txIds <-
|
||||
wallet
|
||||
wallet.utxoHandling
|
||||
.listUtxos(account.hdAccount)
|
||||
.map(_.map(_.txid))
|
||||
_ <- wallet
|
||||
.findByTxIds(txIds)
|
||||
.map(_.flatMap(_.blockHashOpt))
|
||||
|
||||
_ <- wallet.clearAllUtxos()
|
||||
_ <- wallet.clearAllAddresses()
|
||||
_ <- wallet.utxoHandling.clearAllUtxos()
|
||||
_ <- wallet.utxoHandling.clearAllAddresses()
|
||||
balanceAfterClear <- wallet.getBalance()
|
||||
rescanState <- wallet.rescanHandling.fullRescanNeutrinoWallet(1, true)
|
||||
_ <- RescanState.awaitRescanDone(rescanState)
|
||||
|
@ -574,8 +574,8 @@ class RescanHandlingTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
// need to clear all utxos / addresses to test this
|
||||
// otherwise the wallet will see we already have addresses and not generate
|
||||
// a fresh pool of addresses
|
||||
_ <- wallet.clearAllUtxos()
|
||||
_ <- wallet.clearAllAddresses()
|
||||
_ <- wallet.utxoHandling.clearAllUtxos()
|
||||
_ <- wallet.utxoHandling.clearAllAddresses()
|
||||
rescanState <- wallet.rescanHandling.fullRescanNeutrinoWallet(
|
||||
DEFAULT_ADDR_BATCH_SIZE)
|
||||
_ = assert(rescanState.isInstanceOf[RescanState.RescanStarted])
|
||||
|
|
|
@ -16,7 +16,7 @@ class SegwitWalletTest extends BitcoinSWalletTest {
|
|||
it should "generate segwit addresses" in { wallet =>
|
||||
for {
|
||||
addr <- wallet.getNewAddress()
|
||||
account <- wallet.getDefaultAccount()
|
||||
account <- wallet.accountHandling.getDefaultAccount()
|
||||
otherAddr <- wallet.getNewAddress()
|
||||
thirdAddr <- wallet.addressHandling.getNewAddress(AddressType.SegWit)
|
||||
allAddrs <- wallet.addressHandling.listAddresses()
|
||||
|
@ -32,7 +32,8 @@ class SegwitWalletTest extends BitcoinSWalletTest {
|
|||
|
||||
it should "generate legacy addresses" in { wallet =>
|
||||
for {
|
||||
account <- wallet.getDefaultAccountForType(AddressType.Legacy)
|
||||
account <- wallet.accountHandling.getDefaultAccountForType(
|
||||
AddressType.Legacy)
|
||||
addr <- wallet.addressHandling.getNewAddress(AddressType.Legacy)
|
||||
} yield {
|
||||
assert(account.hdAccount.purpose == HDPurpose.Legacy)
|
||||
|
|
|
@ -246,14 +246,14 @@ class TrezorAddressTest extends BitcoinSWalletTest with EmptyFixture {
|
|||
|
||||
val assertionsF: Future[Seq[Assertion]] = for {
|
||||
wallet <- getWallet(conf)
|
||||
existingAccounts <- wallet.listAccounts(purpose)
|
||||
existingAccounts <- wallet.accountHandling.listAccounts(purpose)
|
||||
_ <- createNeededAccounts(
|
||||
wallet,
|
||||
existingAccounts,
|
||||
conf.kmParams,
|
||||
testVectors
|
||||
)
|
||||
accounts <- wallet.listAccounts(purpose)
|
||||
accounts <- wallet.accountHandling.listAccounts(purpose)
|
||||
// we want to find all accounts for the given account type,
|
||||
// and match it with its corresponding test vector
|
||||
accountsWithVectors = {
|
||||
|
|
|
@ -51,7 +51,9 @@ class UTXOLifeCycleTest
|
|||
|
||||
for {
|
||||
oldTransactions <- wallet.listTransactions()
|
||||
tx <- wallet.sendToAddress(testAddr, Satoshis(3000), None)
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(testAddr,
|
||||
Satoshis(3000),
|
||||
None)
|
||||
|
||||
updatedCoins <- wallet.findOutputsBeingSpent(tx)
|
||||
newTransactions <- wallet.listTransactions()
|
||||
|
@ -69,7 +71,9 @@ class UTXOLifeCycleTest
|
|||
val walletConfig = param.walletConfig
|
||||
for {
|
||||
oldTransactions <- wallet.listTransactions()
|
||||
tx <- wallet.sendToAddress(testAddr, Satoshis(3000), None)
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(testAddr,
|
||||
Satoshis(3000),
|
||||
None)
|
||||
|
||||
updatedCoins <- wallet.findOutputsBeingSpent(tx)
|
||||
newTransactions <- wallet.listTransactions()
|
||||
|
@ -79,7 +83,7 @@ class UTXOLifeCycleTest
|
|||
|
||||
// Give tx a fake hash so it can appear as it's in a block
|
||||
hash <- bitcoind.getBestBlockHash()
|
||||
_ <- wallet.processTransaction(tx, Some(hash))
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, Some(hash))
|
||||
|
||||
_ <- wallet.updateUtxoPendingStates()
|
||||
pendingCoins <- wallet.findOutputsBeingSpent(tx)
|
||||
|
@ -150,9 +154,9 @@ class UTXOLifeCycleTest
|
|||
_ = assert(oldUtxos == utxos)
|
||||
|
||||
// process the transactions from mempool
|
||||
_ <- wallet.processTransaction(tx1.get, None)
|
||||
_ <- wallet.processTransaction(tx2.get, None)
|
||||
_ <- wallet.processTransaction(tx3.get, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx1.get, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx2.get, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx3.get, None)
|
||||
|
||||
utxos <- wallet.listUtxos()
|
||||
_ = assert(oldUtxos.size + 3 == utxos.size)
|
||||
|
@ -166,7 +170,7 @@ class UTXOLifeCycleTest
|
|||
_ = assert(blockHashes.size == 1)
|
||||
blockHash = blockHashes.head
|
||||
block <- bitcoind.getBlockRaw(blockHash)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
_ <- wallet.updateUtxoPendingStates()
|
||||
|
||||
utxos <- wallet.listUtxos()
|
||||
|
@ -177,7 +181,7 @@ class UTXOLifeCycleTest
|
|||
_ = assert(blockHashes.size == 1)
|
||||
blockHash = blockHashes.head
|
||||
block <- bitcoind.getBlockRaw(blockHash)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
_ <- wallet.updateUtxoPendingStates()
|
||||
|
||||
utxos <- wallet.listUtxos()
|
||||
|
@ -196,7 +200,7 @@ class UTXOLifeCycleTest
|
|||
_ = assert(blockHashes.size == 1)
|
||||
blockHash = blockHashes.head
|
||||
block <- bitcoind.getBlockRaw(blockHash)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
_ <- wallet.updateUtxoPendingStates()
|
||||
|
||||
utxos <- wallet.listUtxos()
|
||||
|
@ -215,7 +219,7 @@ class UTXOLifeCycleTest
|
|||
_ = assert(blockHashes.size == 1)
|
||||
blockHash = blockHashes.head
|
||||
block <- bitcoind.getBlockRaw(blockHash)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
_ <- wallet.updateUtxoPendingStates()
|
||||
|
||||
utxos <- wallet.listUtxos()
|
||||
|
@ -234,7 +238,7 @@ class UTXOLifeCycleTest
|
|||
_ = assert(blockHashes.size == 1)
|
||||
blockHash = blockHashes.head
|
||||
block <- bitcoind.getBlockRaw(blockHash)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
_ <- wallet.updateUtxoPendingStates()
|
||||
|
||||
utxos <- wallet.listUtxos()
|
||||
|
@ -253,7 +257,7 @@ class UTXOLifeCycleTest
|
|||
_ = assert(blockHashes.size == 1)
|
||||
blockHash = blockHashes.head
|
||||
block <- bitcoind.getBlockRaw(blockHash)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
_ <- wallet.updateUtxoPendingStates()
|
||||
|
||||
utxos <- wallet.listUtxos()
|
||||
|
@ -274,7 +278,7 @@ class UTXOLifeCycleTest
|
|||
val wallet = param.wallet
|
||||
|
||||
for {
|
||||
tx <- wallet.sendToAddress(
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(
|
||||
testAddr,
|
||||
Satoshis(3000),
|
||||
Some(SatoshisPerByte.one)
|
||||
|
@ -284,8 +288,9 @@ class UTXOLifeCycleTest
|
|||
_ = assert(coins.forall(_.state == BroadcastSpent))
|
||||
_ = assert(coins.forall(_.spendingTxIdOpt.contains(tx.txIdBE)))
|
||||
|
||||
rbf <- wallet.bumpFeeRBF(tx.txIdBE, SatoshisPerByte.fromLong(3))
|
||||
_ <- wallet.processTransaction(rbf, None)
|
||||
rbf <- wallet.sendFundsHandling.bumpFeeRBF(tx.txIdBE,
|
||||
SatoshisPerByte.fromLong(3))
|
||||
_ <- wallet.transactionProcessing.processTransaction(rbf, None)
|
||||
rbfCoins <- wallet.findOutputsBeingSpent(rbf)
|
||||
} yield {
|
||||
assert(rbfCoins.forall(_.state == BroadcastSpent))
|
||||
|
@ -299,7 +304,9 @@ class UTXOLifeCycleTest
|
|||
val spendingInfoDAO =
|
||||
SpendingInfoDAO()(system.dispatcher, param.walletConfig)
|
||||
for {
|
||||
tx <- wallet.sendToAddress(testAddr, Satoshis(3000), None)
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(testAddr,
|
||||
Satoshis(3000),
|
||||
None)
|
||||
|
||||
coins <- wallet.findOutputsBeingSpent(tx)
|
||||
|
||||
|
@ -315,7 +322,7 @@ class UTXOLifeCycleTest
|
|||
}
|
||||
|
||||
res <- recoverToSucceededIf[RuntimeException](
|
||||
wallet.processTransaction(newTx, None)
|
||||
wallet.transactionProcessing.processTransaction(newTx, None)
|
||||
)
|
||||
} yield res
|
||||
}
|
||||
|
@ -326,7 +333,9 @@ class UTXOLifeCycleTest
|
|||
val walletConfig = param.walletConfig
|
||||
for {
|
||||
oldTransactions <- wallet.listTransactions()
|
||||
tx <- wallet.sendToAddress(testAddr, Satoshis(3000), None)
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(testAddr,
|
||||
Satoshis(3000),
|
||||
None)
|
||||
|
||||
updatedCoins <- wallet.findOutputsBeingSpent(tx)
|
||||
newTransactions <- wallet.listTransactions()
|
||||
|
@ -336,7 +345,7 @@ class UTXOLifeCycleTest
|
|||
|
||||
// Give tx a fake hash so it can appear as it's in a block
|
||||
hash <- bitcoind.getBestBlockHash()
|
||||
_ <- wallet.processTransaction(tx, Some(hash))
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, Some(hash))
|
||||
|
||||
_ <- wallet.updateUtxoPendingStates()
|
||||
pendingCoins <- wallet.findOutputsBeingSpent(tx)
|
||||
|
@ -361,7 +370,7 @@ class UTXOLifeCycleTest
|
|||
}
|
||||
|
||||
res <- recoverToSucceededIf[RuntimeException](
|
||||
wallet.processTransaction(newTx, None)
|
||||
wallet.transactionProcessing.processTransaction(newTx, None)
|
||||
)
|
||||
} yield res
|
||||
}
|
||||
|
@ -376,7 +385,7 @@ class UTXOLifeCycleTest
|
|||
|
||||
txId <- bitcoind.sendToAddress(addr, Satoshis(3000))
|
||||
tx <- bitcoind.getRawTransactionRaw(txId)
|
||||
_ <- wallet.processOurTransaction(
|
||||
_ <- wallet.transactionProcessing.processOurTransaction(
|
||||
transaction = tx,
|
||||
feeRate = SatoshisPerByte(Satoshis(3)),
|
||||
inputAmount = Satoshis(4000),
|
||||
|
@ -405,7 +414,7 @@ class UTXOLifeCycleTest
|
|||
|
||||
txId <- bitcoind.sendToAddress(addr, Satoshis(3000))
|
||||
tx <- bitcoind.getRawTransactionRaw(txId)
|
||||
_ <- wallet.processOurTransaction(
|
||||
_ <- wallet.transactionProcessing.processOurTransaction(
|
||||
transaction = tx,
|
||||
feeRate = SatoshisPerByte(Satoshis(3)),
|
||||
inputAmount = Satoshis(4000),
|
||||
|
@ -422,7 +431,7 @@ class UTXOLifeCycleTest
|
|||
hash <- bitcoind.getNewAddress
|
||||
.flatMap(bitcoind.generateToAddress(1, _))
|
||||
.map(_.head)
|
||||
_ <- wallet.processTransaction(tx, Some(hash))
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, Some(hash))
|
||||
|
||||
pendingCoins <-
|
||||
wallet.findByScriptPubKey(addr.scriptPubKey)
|
||||
|
@ -447,7 +456,7 @@ class UTXOLifeCycleTest
|
|||
|
||||
txId <- bitcoind.sendToAddress(addr, Satoshis(3000))
|
||||
tx <- bitcoind.getRawTransactionRaw(txId)
|
||||
_ <- wallet.processOurTransaction(
|
||||
_ <- wallet.transactionProcessing.processOurTransaction(
|
||||
transaction = tx,
|
||||
feeRate = SatoshisPerByte(Satoshis(3)),
|
||||
inputAmount = Satoshis(4000),
|
||||
|
@ -575,11 +584,11 @@ class UTXOLifeCycleTest
|
|||
Satoshis(100000),
|
||||
P2PKHScriptPubKey(ECPublicKey.freshPublicKey)
|
||||
)
|
||||
val accountF = wallet.getDefaultAccount()
|
||||
val accountF = wallet.accountHandling.getDefaultAccount()
|
||||
for {
|
||||
oldTransactions <- wallet.listTransactions()
|
||||
account <- accountF
|
||||
rawTxHelper <- wallet.fundRawTransaction(
|
||||
rawTxHelper <- wallet.fundTxHandling.fundRawTransaction(
|
||||
destinations = Vector(dummyOutput),
|
||||
feeRate = SatoshisPerVirtualByte.one,
|
||||
fromAccount = account,
|
||||
|
@ -604,7 +613,7 @@ class UTXOLifeCycleTest
|
|||
.flatMap(bitcoind.generateToAddress(1, _))
|
||||
.map(_.head)
|
||||
block <- bitcoind.getBlockRaw(hash)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
|
||||
newReserved <- wallet.listUtxos(TxoState.Reserved)
|
||||
newTransactions <- wallet.listTransactions()
|
||||
|
@ -646,7 +655,7 @@ class UTXOLifeCycleTest
|
|||
PSBT.fromUnsignedTx(tx)
|
||||
}
|
||||
|
||||
psbt <- wallet.signPSBT(unsignedPSBT)
|
||||
psbt <- wallet.sendFundsHandling.signPSBT(unsignedPSBT)
|
||||
|
||||
tx <- Future.fromTry(
|
||||
psbt.finalizePSBT.flatMap(_.extractTransactionAndValidate)
|
||||
|
@ -656,7 +665,7 @@ class UTXOLifeCycleTest
|
|||
_ <- bitcoind.sendRawTransaction(tx)
|
||||
hash <- bitcoind.generateToAddress(1, testAddr).map(_.head)
|
||||
block <- bitcoind.getBlockRaw(hash)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
|
||||
updatedCoins <- wallet.findOutputsBeingSpent(tx)
|
||||
} yield {
|
||||
|
@ -714,7 +723,7 @@ class UTXOLifeCycleTest
|
|||
throwAwayAddr <- throwAwayAddrF
|
||||
hashes <- bitcoind.generateToAddress(blocks = 1, throwAwayAddr)
|
||||
block <- bitcoind.getBlockRaw(hashes.head)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
|
||||
// make sure the utxo is pending confirmations received
|
||||
utxos <- wallet.listUtxos(TxoState.PendingConfirmationsReceived)
|
||||
|
@ -733,7 +742,7 @@ class UTXOLifeCycleTest
|
|||
// now process another block
|
||||
hashes2 <- bitcoind.generateToAddress(blocks = 1, throwAwayAddr)
|
||||
block2 <- bitcoind.getBlockRaw(hashes2.head)
|
||||
_ <- wallet.processBlock(block2)
|
||||
_ <- wallet.transactionProcessing.processBlock(block2)
|
||||
|
||||
// the utxo should still be reserved
|
||||
reservedUtxos <- wallet.listUtxos(TxoState.Reserved)
|
||||
|
@ -755,7 +764,9 @@ class UTXOLifeCycleTest
|
|||
bitcoindAdr <- bitcoindAddrF
|
||||
utxoCount <- utxoCountF
|
||||
// build a spending transaction
|
||||
tx <- wallet.sendToAddress(bitcoindAdr, amt, SatoshisPerVirtualByte.one)
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(bitcoindAdr,
|
||||
amt,
|
||||
SatoshisPerVirtualByte.one)
|
||||
c <- wallet.listUtxos()
|
||||
_ = assert(c.length == utxoCount.length)
|
||||
txIdBE <- bitcoind.sendRawTransaction(tx)
|
||||
|
@ -775,7 +786,7 @@ class UTXOLifeCycleTest
|
|||
_ = assert(newReservedUtxos.length == utxoCount.length)
|
||||
blockHash <- bitcoind.generateToAddress(1, bitcoindAdr).map(_.head)
|
||||
block <- bitcoind.getBlockRaw(blockHash)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
broadcastSpentUtxo <- wallet.listUtxos(
|
||||
TxoState.PendingConfirmationsSpent
|
||||
)
|
||||
|
@ -807,7 +818,7 @@ class UTXOLifeCycleTest
|
|||
walletAddress <- wallet.getNewAddress()
|
||||
txId <- bitcoind.sendToAddress(walletAddress, receiveValue)
|
||||
receiveTx <- bitcoind.getRawTransactionRaw(txId)
|
||||
_ <- wallet.processTransaction(receiveTx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(receiveTx, None)
|
||||
receiveOutPointPair = receiveTx.outputs.zipWithIndex
|
||||
.find(_._1.value == receiveValue)
|
||||
.map(out => (receiveTx.txId, out._2))
|
||||
|
@ -824,7 +835,7 @@ class UTXOLifeCycleTest
|
|||
|
||||
// spend and broadcast unconfirmed
|
||||
feeRate <- wallet.feeRateApi.getFeeRate()
|
||||
sendTx <- wallet.sendWithAlgo(
|
||||
sendTx <- wallet.sendFundsHandling.sendWithAlgo(
|
||||
testAddr,
|
||||
sendValue,
|
||||
feeRate,
|
||||
|
@ -839,7 +850,7 @@ class UTXOLifeCycleTest
|
|||
// confirm receive and spend
|
||||
blockHashes <- bitcoind.generate(1)
|
||||
block <- bitcoind.getBlockRaw(blockHashes.head)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
|
||||
receivedUtxo <- wallet.findByOutPoints(Vector(receiveOutPoint))
|
||||
} yield {
|
||||
|
@ -861,7 +872,7 @@ class UTXOLifeCycleTest
|
|||
walletAddress <- wallet.getNewAddress()
|
||||
txId <- bitcoind.sendToAddress(walletAddress, receiveValue)
|
||||
receiveTx <- bitcoind.getRawTransactionRaw(txId)
|
||||
_ <- wallet.processTransaction(receiveTx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(receiveTx, None)
|
||||
receiveOutPointPair = receiveTx.outputs.zipWithIndex
|
||||
.find(_._1.value == receiveValue)
|
||||
.map(out => (receiveTx.txId, out._2))
|
||||
|
@ -878,7 +889,7 @@ class UTXOLifeCycleTest
|
|||
|
||||
// spend unconfirmed
|
||||
feeRate <- wallet.feeRateApi.getFeeRate()
|
||||
sendTx <- wallet.sendWithAlgo(
|
||||
sendTx <- wallet.sendFundsHandling.sendWithAlgo(
|
||||
testAddr,
|
||||
sendValue,
|
||||
feeRate,
|
||||
|
@ -892,7 +903,7 @@ class UTXOLifeCycleTest
|
|||
// confirm receive
|
||||
blockHashes <- bitcoind.generate(1)
|
||||
block <- bitcoind.getBlockRaw(blockHashes.head)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
|
||||
receivedUtxo <- wallet.findByOutPoints(Vector(receiveOutPoint))
|
||||
_ = assert(receivedUtxo.size == 1)
|
||||
|
@ -902,7 +913,7 @@ class UTXOLifeCycleTest
|
|||
_ <- wallet.broadcastTransaction(sendTx)
|
||||
blockHashes <- bitcoind.generate(1)
|
||||
block <- bitcoind.getBlockRaw(blockHashes.head)
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
|
||||
receivedUtxo <- wallet.findByOutPoints(Vector(receiveOutPoint))
|
||||
} yield {
|
||||
|
|
|
@ -74,7 +74,9 @@ class WalletCallbackTest extends BitcoinSWalletTest {
|
|||
|
||||
for {
|
||||
address <- wallet.getNewAddress()
|
||||
tx <- wallet.sendToAddress(address, Satoshis(1000), None)
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(address,
|
||||
Satoshis(1000),
|
||||
None)
|
||||
result <- resultP.future
|
||||
} yield assert(result == tx)
|
||||
}
|
||||
|
@ -108,7 +110,7 @@ class WalletCallbackTest extends BitcoinSWalletTest {
|
|||
|
||||
for {
|
||||
txno <- wallet.listTransactions().map(_.size)
|
||||
_ <- wallet.processTransaction(tx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, None)
|
||||
_ <- AsyncUtil.nonBlockingSleep(50.millis)
|
||||
txs <- wallet.listTransactions()
|
||||
} yield {
|
||||
|
@ -211,7 +213,7 @@ class WalletCallbackTest extends BitcoinSWalletTest {
|
|||
val wallet = fundedWallet.wallet
|
||||
|
||||
for {
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
result <- resultP.future
|
||||
} yield assert(result == block)
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
.map(unconfirmed => assert(unconfirmed == 0.bitcoin))
|
||||
|
||||
// after this, tx is unconfirmed in wallet
|
||||
_ <- wallet.processTransaction(tx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, None)
|
||||
|
||||
// we should now have one UTXO in the wallet
|
||||
// it should not be confirmed
|
||||
|
@ -92,7 +92,7 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
rawTx <- bitcoind.getRawTransaction(txId)
|
||||
|
||||
// after this, tx should be confirmed
|
||||
_ <- wallet.processTransaction(tx, rawTx.blockhash)
|
||||
_ <- wallet.transactionProcessing.processTransaction(tx, rawTx.blockhash)
|
||||
_ <-
|
||||
wallet
|
||||
.listUtxos()
|
||||
|
@ -116,7 +116,7 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
.map(unconfirmed => assert(unconfirmed == 0.satoshis))
|
||||
|
||||
signedTx <- bitcoind.getNewAddress.flatMap {
|
||||
wallet.sendToAddress(_, valueToBitcoind, None)
|
||||
wallet.sendFundsHandling.sendToAddress(_, valueToBitcoind, None)
|
||||
}
|
||||
|
||||
feeRate =
|
||||
|
@ -176,7 +176,8 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
txId <- bitcoind.sendToAddress(addr, valueFromBitcoind)
|
||||
rawTx <- bitcoind.getRawTransaction(txId)
|
||||
_ <- bitcoind.generate(6)
|
||||
_ <- wallet.processTransaction(rawTx.hex, rawTx.blockhash)
|
||||
_ <- wallet.transactionProcessing.processTransaction(rawTx.hex,
|
||||
rawTx.blockhash)
|
||||
|
||||
// Verify we funded the wallet
|
||||
balance <- wallet.getBalance()
|
||||
|
@ -184,7 +185,9 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
|
||||
// Create first tx
|
||||
tx1 <- bitcoind.getNewAddress.flatMap {
|
||||
wallet.sendToAddress(_, valueToBitcoind, SatoshisPerVirtualByte.one)
|
||||
wallet.sendFundsHandling.sendToAddress(_,
|
||||
valueToBitcoind,
|
||||
SatoshisPerVirtualByte.one)
|
||||
}
|
||||
_ <- bitcoind.sendRawTransaction(tx1)
|
||||
|
||||
|
@ -196,7 +199,8 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
|
||||
// Create replacement tx
|
||||
newFeeRate = SatoshisPerVirtualByte.fromLong(20)
|
||||
replacementTx <- wallet.bumpFeeRBF(tx1.txIdBE, newFeeRate)
|
||||
replacementTx <- wallet.sendFundsHandling.bumpFeeRBF(tx1.txIdBE,
|
||||
newFeeRate)
|
||||
|
||||
// Check tx being replaced exists
|
||||
tx1Info <- bitcoind.getRawTransaction(tx1.txIdBE)
|
||||
|
@ -239,7 +243,8 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
txId <- bitcoind.sendToAddress(addr, valueFromBitcoind)
|
||||
_ <- bitcoind.generate(6)
|
||||
rawTx <- bitcoind.getRawTransaction(txId)
|
||||
_ <- wallet.processTransaction(rawTx.hex, rawTx.blockhash)
|
||||
_ <- wallet.transactionProcessing.processTransaction(rawTx.hex,
|
||||
rawTx.blockhash)
|
||||
|
||||
// Verify we funded the wallet
|
||||
balance <- wallet.getBalance()
|
||||
|
@ -247,7 +252,9 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
|
||||
// Create rbf tx
|
||||
rbf <- bitcoind.getNewAddress.flatMap {
|
||||
wallet.sendToAddress(_, valueToBitcoind, SatoshisPerVirtualByte.one)
|
||||
wallet.sendFundsHandling.sendToAddress(_,
|
||||
valueToBitcoind,
|
||||
SatoshisPerVirtualByte.one)
|
||||
}
|
||||
_ <- bitcoind.sendRawTransaction(rbf)
|
||||
|
||||
|
@ -255,11 +262,13 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
_ <- bitcoind.generate(1)
|
||||
rawTx1 <- bitcoind.getRawTransaction(rbf.txIdBE)
|
||||
_ = require(rawTx1.blockhash.isDefined)
|
||||
_ <- wallet.processTransaction(rbf, rawTx1.blockhash)
|
||||
_ <- wallet.transactionProcessing.processTransaction(rbf,
|
||||
rawTx1.blockhash)
|
||||
|
||||
// fail to RBF confirmed tx
|
||||
res <- recoverToSucceededIf[IllegalArgumentException] {
|
||||
wallet.bumpFeeRBF(rbf.txIdBE, SatoshisPerVirtualByte.fromLong(20))
|
||||
wallet.sendFundsHandling.bumpFeeRBF(rbf.txIdBE,
|
||||
SatoshisPerVirtualByte.fromLong(20))
|
||||
}
|
||||
} yield res
|
||||
}
|
||||
|
@ -274,7 +283,8 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
txId <- bitcoind.sendToAddress(addr, valueFromBitcoind)
|
||||
rawTx <- bitcoind.getRawTransaction(txId)
|
||||
_ <- bitcoind.generate(6)
|
||||
_ <- wallet.processTransaction(rawTx.hex, rawTx.blockhash)
|
||||
_ <- wallet.transactionProcessing.processTransaction(rawTx.hex,
|
||||
rawTx.blockhash)
|
||||
|
||||
// Verify we funded the wallet
|
||||
balance <- wallet.getBalance()
|
||||
|
@ -282,7 +292,7 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
|
||||
// Create parent tx
|
||||
parentTx <- bitcoind.getNewAddress.flatMap {
|
||||
wallet.sendToAddress(_, valueToBitcoind, None)
|
||||
wallet.sendFundsHandling.sendToAddress(_, valueToBitcoind, None)
|
||||
}
|
||||
_ <- bitcoind.sendRawTransaction(parentTx)
|
||||
|
||||
|
@ -294,7 +304,8 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
|
||||
// Create child tx
|
||||
childFeeRate <- wallet.feeRateApi.getFeeRate()
|
||||
childTx <- wallet.bumpFeeCPFP(parentTx.txIdBE, childFeeRate)
|
||||
childTx <- wallet.sendFundsHandling.bumpFeeCPFP(parentTx.txIdBE,
|
||||
childFeeRate)
|
||||
_ <- bitcoind.sendRawTransaction(childTx)
|
||||
|
||||
// Check we didn't send again to bitcoind
|
||||
|
@ -325,7 +336,7 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
coinbaseTx.outputs.exists(_.scriptPubKey == addr.scriptPubKey)
|
||||
)
|
||||
|
||||
_ <- wallet.processBlock(block)
|
||||
_ <- wallet.transactionProcessing.processBlock(block)
|
||||
|
||||
// Verify we funded the wallet
|
||||
allUtxos <- wallet.listUtxos()
|
||||
|
@ -337,7 +348,9 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
|
||||
// Attempt to spend utxo
|
||||
_ <- recoverToSucceededIf[RuntimeException](
|
||||
wallet.sendToAddress(bitcoindAddr, valueToBitcoind, None)
|
||||
wallet.sendFundsHandling.sendToAddress(bitcoindAddr,
|
||||
valueToBitcoind,
|
||||
None)
|
||||
)
|
||||
|
||||
spendingTx = {
|
||||
|
@ -350,7 +363,7 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
}
|
||||
|
||||
_ <- recoverToSucceededIf[RuntimeException](
|
||||
wallet.processTransaction(spendingTx, None)
|
||||
wallet.transactionProcessing.processTransaction(spendingTx, None)
|
||||
)
|
||||
|
||||
// Make coinbase mature
|
||||
|
@ -359,13 +372,13 @@ class WalletIntegrationTest extends BitcoinSWalletTestCachedBitcoindNewest {
|
|||
|
||||
// Create valid spending tx
|
||||
psbt = PSBT.fromUnsignedTx(spendingTx)
|
||||
signedPSBT <- wallet.signPSBT(psbt)
|
||||
signedPSBT <- wallet.sendFundsHandling.signPSBT(psbt)
|
||||
signedTx = signedPSBT.finalizePSBT
|
||||
.flatMap(_.extractTransactionAndValidate)
|
||||
.get
|
||||
|
||||
// Process tx, validate correctly moved to
|
||||
_ <- wallet.processTransaction(signedTx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(signedTx, None)
|
||||
newCoinbaseUtxos <- wallet.listUtxos(TxoState.ImmatureCoinbase)
|
||||
_ = assert(newCoinbaseUtxos.isEmpty)
|
||||
spentUtxos <- wallet.listUtxos(TxoState.BroadcastSpent)
|
||||
|
|
|
@ -39,7 +39,9 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
it should "correctly send to an address" in { fundedWallet =>
|
||||
val wallet = fundedWallet.wallet
|
||||
for {
|
||||
tx <- wallet.sendToAddress(testAddress, amountToSend, None)
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(testAddress,
|
||||
amountToSend,
|
||||
None)
|
||||
} yield {
|
||||
assert(
|
||||
tx.outputs.contains(
|
||||
|
@ -66,7 +68,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
it should "correctly send to multiple addresses" in { fundedWallet =>
|
||||
val wallet = fundedWallet.wallet
|
||||
for {
|
||||
tx <- wallet.sendToAddresses(addresses, amounts, None)
|
||||
tx <- wallet.sendFundsHandling.sendToAddresses(addresses, amounts, None)
|
||||
} yield {
|
||||
val expectedOutputs = addresses.zip(amounts).map { case (addr, amount) =>
|
||||
TransactionOutput(amount, addr.scriptPubKey)
|
||||
|
@ -79,7 +81,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
fundedWallet =>
|
||||
val wallet = fundedWallet.wallet
|
||||
val sendToAddressesF =
|
||||
wallet.sendToAddresses(addresses, amounts.tail, None)
|
||||
wallet.sendFundsHandling.sendToAddresses(addresses, amounts.tail, None)
|
||||
|
||||
recoverToSucceededIf[IllegalArgumentException] {
|
||||
sendToAddressesF
|
||||
|
@ -90,7 +92,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
fundedWallet =>
|
||||
val wallet = fundedWallet.wallet
|
||||
val sendToAddressesF =
|
||||
wallet.sendToAddresses(addresses.tail, amounts, None)
|
||||
wallet.sendFundsHandling.sendToAddresses(addresses.tail, amounts, None)
|
||||
|
||||
recoverToSucceededIf[IllegalArgumentException] {
|
||||
sendToAddressesF
|
||||
|
@ -104,7 +106,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
}
|
||||
|
||||
for {
|
||||
tx <- wallet.sendToOutputs(expectedOutputs, None)
|
||||
tx <- wallet.sendFundsHandling.sendToOutputs(expectedOutputs, None)
|
||||
} yield {
|
||||
assert(expectedOutputs.diff(tx.outputs).isEmpty)
|
||||
}
|
||||
|
@ -116,7 +118,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
): Future[Assertion] = {
|
||||
val message = "ben was here"
|
||||
for {
|
||||
tx <- wallet.makeOpReturnCommitment(
|
||||
tx <- wallet.sendFundsHandling.makeOpReturnCommitment(
|
||||
message = message,
|
||||
hashMessage = hashMessage,
|
||||
feeRateOpt = None
|
||||
|
@ -167,7 +169,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
fundedWallet =>
|
||||
val wallet = fundedWallet.wallet
|
||||
recoverToSucceededIf[IllegalArgumentException] {
|
||||
wallet.makeOpReturnCommitment(
|
||||
wallet.sendFundsHandling.makeOpReturnCommitment(
|
||||
"This message is much too long and is over 80 bytes, the limit for OP_RETURN. It should cause an error.",
|
||||
hashMessage = false,
|
||||
None
|
||||
|
@ -178,7 +180,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
it should "fail to send to a different network address" in { fundedWallet =>
|
||||
val wallet = fundedWallet.wallet
|
||||
val sendToAddressesF =
|
||||
wallet.sendToAddress(
|
||||
wallet.sendFundsHandling.sendToAddress(
|
||||
BitcoinAddress("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"),
|
||||
Satoshis(1000),
|
||||
None
|
||||
|
@ -199,7 +201,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
)
|
||||
|
||||
val sendToAddressesF =
|
||||
wallet.sendToAddresses(addrs, amounts, None)
|
||||
wallet.sendFundsHandling.sendToAddresses(addrs, amounts, None)
|
||||
|
||||
recoverToSucceededIf[IllegalArgumentException] {
|
||||
sendToAddressesF
|
||||
|
@ -212,7 +214,10 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
allOutPoints <- wallet.spendingInfoDAO.findAllOutpoints()
|
||||
// use half of them
|
||||
outPoints = allOutPoints.drop(allOutPoints.size / 2)
|
||||
tx <- wallet.sendFromOutPoints(outPoints, testAddress, amountToSend, None)
|
||||
tx <- wallet.sendFundsHandling.sendFromOutPoints(outPoints,
|
||||
testAddress,
|
||||
amountToSend,
|
||||
None)
|
||||
} yield {
|
||||
assert(
|
||||
outPoints.forall(outPoint =>
|
||||
|
@ -237,7 +242,9 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
// use half of them
|
||||
utxos = allUtxos.drop(allUtxos.size / 2)
|
||||
outPoints = utxos.map(_.outPoint)
|
||||
tx <- wallet.sendFromOutPoints(outPoints, testAddress, None)
|
||||
tx <- wallet.sendFundsHandling.sendFromOutPoints(outPoints,
|
||||
testAddress,
|
||||
None)
|
||||
} yield {
|
||||
val expectedFeeRate =
|
||||
wallet.feeRateApi.asInstanceOf[RandomFeeProvider].lastFeeRate.get
|
||||
|
@ -281,7 +288,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
val wallet = fundedWallet.wallet
|
||||
for {
|
||||
utxos <- wallet.listUtxos()
|
||||
tx <- wallet.sweepWallet(testAddress, None)
|
||||
tx <- wallet.sendFundsHandling.sweepWallet(testAddress, None)
|
||||
balance <- wallet.getBalance()
|
||||
} yield {
|
||||
assert(balance == Satoshis.zero)
|
||||
|
@ -331,12 +338,14 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
val feeRate = FeeUnitGen.satsPerByte.sampleSome
|
||||
|
||||
for {
|
||||
tx <- wallet.sendToAddress(testAddress, amountToSend, feeRate)
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(testAddress,
|
||||
amountToSend,
|
||||
feeRate)
|
||||
|
||||
firstBal <- wallet.getBalance()
|
||||
|
||||
newFeeRate = SatoshisPerByte(feeRate.currencyUnit + Satoshis(50))
|
||||
bumpedTx <- wallet.bumpFeeRBF(tx.txIdBE, newFeeRate)
|
||||
bumpedTx <- wallet.sendFundsHandling.bumpFeeRBF(tx.txIdBE, newFeeRate)
|
||||
|
||||
txDb1Opt <- wallet.outgoingTxDAO.findByTxId(tx.txIdBE)
|
||||
txDb2Opt <- wallet.outgoingTxDAO.findByTxId(bumpedTx.txIdBE)
|
||||
|
@ -360,11 +369,15 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
val newFeeRate = SatoshisPerByte(feeRate.currencyUnit + Satoshis.one)
|
||||
|
||||
for {
|
||||
tx <- wallet.sendToAddress(testAddress, amountToSend, feeRate)
|
||||
_ <- wallet.processTransaction(tx, Some(DoubleSha256DigestBE.empty))
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(testAddress,
|
||||
amountToSend,
|
||||
feeRate)
|
||||
_ <- wallet.transactionProcessing.processTransaction(
|
||||
tx,
|
||||
Some(DoubleSha256DigestBE.empty))
|
||||
|
||||
res <- recoverToSucceededIf[IllegalArgumentException] {
|
||||
wallet.bumpFeeRBF(tx.txIdBE, newFeeRate)
|
||||
wallet.sendFundsHandling.bumpFeeRBF(tx.txIdBE, newFeeRate)
|
||||
}
|
||||
} yield res
|
||||
}
|
||||
|
@ -389,12 +402,14 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
psbt = PSBT.fromUnsignedTx(tx)
|
||||
|
||||
// Have wallet sign and process transaction
|
||||
signedPSBT <- wallet.signPSBT(psbt)
|
||||
signedPSBT <- wallet.sendFundsHandling.signPSBT(psbt)
|
||||
signedTx = signedPSBT.finalizePSBT.get.extractTransactionAndValidate.get
|
||||
_ <- wallet.processTransaction(signedTx, None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(signedTx, None)
|
||||
|
||||
res <- recoverToSucceededIf[IllegalArgumentException] {
|
||||
wallet.bumpFeeRBF(signedTx.txIdBE, SatoshisPerVirtualByte.fromLong(100))
|
||||
wallet.sendFundsHandling.bumpFeeRBF(
|
||||
signedTx.txIdBE,
|
||||
SatoshisPerVirtualByte.fromLong(100))
|
||||
}
|
||||
} yield res
|
||||
}
|
||||
|
@ -402,9 +417,11 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
it should "correctly CPFP a transaction" in { fundedWallet =>
|
||||
val wallet = fundedWallet.wallet
|
||||
for {
|
||||
parent <- wallet.sendToAddress(testAddress, amountToSend, None)
|
||||
parent <- wallet.sendFundsHandling.sendToAddress(testAddress,
|
||||
amountToSend,
|
||||
None)
|
||||
bumpRate <- wallet.feeRateApi.getFeeRate()
|
||||
child <- wallet.bumpFeeCPFP(parent.txIdBE, bumpRate)
|
||||
child <- wallet.sendFundsHandling.bumpFeeCPFP(parent.txIdBE, bumpRate)
|
||||
|
||||
received <- wallet.spendingInfoDAO.findTx(child).map(_.nonEmpty)
|
||||
} yield {
|
||||
|
@ -442,11 +459,15 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
val feeRate = FeeUnitGen.satsPerByte.sampleSome
|
||||
|
||||
for {
|
||||
tx <- wallet.sendToAddress(testAddress, amountToSend, feeRate)
|
||||
_ <- wallet.processTransaction(tx, Some(DoubleSha256DigestBE.empty))
|
||||
tx <- wallet.sendFundsHandling.sendToAddress(testAddress,
|
||||
amountToSend,
|
||||
feeRate)
|
||||
_ <- wallet.transactionProcessing.processTransaction(
|
||||
tx,
|
||||
Some(DoubleSha256DigestBE.empty))
|
||||
|
||||
res <- recoverToSucceededIf[IllegalArgumentException] {
|
||||
wallet.bumpFeeCPFP(tx.txIdBE, feeRate)
|
||||
wallet.sendFundsHandling.bumpFeeCPFP(tx.txIdBE, feeRate)
|
||||
}
|
||||
} yield res
|
||||
}
|
||||
|
@ -455,7 +476,8 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
val wallet = fundedWallet.wallet
|
||||
|
||||
recoverToSucceededIf[RuntimeException](
|
||||
wallet.bumpFeeCPFP(EmptyTransaction.txIdBE, SatoshisPerByte.one)
|
||||
wallet.sendFundsHandling.bumpFeeCPFP(EmptyTransaction.txIdBE,
|
||||
SatoshisPerByte.one)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -472,7 +494,7 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
.copyWithState(TxoState.PendingConfirmationsSpent)
|
||||
_ <- wallet.spendingInfoDAO.update(spent)
|
||||
test <- recoverToSucceededIf[IllegalArgumentException](
|
||||
wallet.sendFromOutPoints(
|
||||
wallet.sendFundsHandling.sendFromOutPoints(
|
||||
allUtxos.map(_.outPoint),
|
||||
testAddress,
|
||||
amountToSend,
|
||||
|
@ -487,16 +509,19 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
algo: CoinSelectionAlgo
|
||||
): Future[Assertion] = {
|
||||
for {
|
||||
account <- wallet.getDefaultAccount()
|
||||
account <- wallet.accountHandling.getDefaultAccount()
|
||||
feeRate <- wallet.getFeeRate()
|
||||
allUtxos <- wallet
|
||||
allUtxos <- wallet.utxoHandling
|
||||
.listUtxos(account.hdAccount)
|
||||
.map(_.map(CoinSelectorUtxo.fromSpendingInfoDb))
|
||||
|
||||
output = TransactionOutput(amountToSend, testAddress.scriptPubKey)
|
||||
expectedUtxos =
|
||||
CoinSelector.selectByAlgo(algo, allUtxos, Vector(output), feeRate)
|
||||
tx <- wallet.sendWithAlgo(testAddress, amountToSend, feeRate, algo)
|
||||
tx <- wallet.sendFundsHandling.sendWithAlgo(testAddress,
|
||||
amountToSend,
|
||||
feeRate,
|
||||
algo)
|
||||
} yield {
|
||||
val diff =
|
||||
expectedUtxos.map(_.outPoint).diff(tx.inputs.map(_.previousOutput))
|
||||
|
@ -543,8 +568,12 @@ class WalletSendingTest extends BitcoinSWalletTest {
|
|||
) // for fee, fee rates are random so we might need a lot
|
||||
|
||||
// build these transactions in parallel intentionally
|
||||
tx1F = fundedWallet.wallet.sendToAddress(addr1, amt, None)
|
||||
tx2F = fundedWallet.wallet.sendToAddress(addr2, amt, None)
|
||||
tx1F = fundedWallet.wallet.sendFundsHandling.sendToAddress(addr1,
|
||||
amt,
|
||||
None)
|
||||
tx2F = fundedWallet.wallet.sendFundsHandling.sendToAddress(addr2,
|
||||
amt,
|
||||
None)
|
||||
// one of these should fail because we don't have enough money
|
||||
_ <- tx1F
|
||||
_ <- tx2F
|
||||
|
|
|
@ -36,7 +36,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
|
|||
|
||||
it should "create a new wallet" in { (wallet: Wallet) =>
|
||||
for {
|
||||
accounts <- wallet.listAccounts()
|
||||
accounts <- wallet.accountHandling.listAccounts()
|
||||
addresses <- wallet.addressHandling.listAddresses()
|
||||
} yield {
|
||||
assert(accounts.length == 3) // legacy, segwit and nested segwit
|
||||
|
@ -125,7 +125,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
|
|||
}
|
||||
|
||||
for {
|
||||
account <- wallet.getDefaultAccount()
|
||||
account <- wallet.accountHandling.getDefaultAccount()
|
||||
_ <- testChain(hdAccount = account.hdAccount, External)
|
||||
res <- testChain(hdAccount = account.hdAccount, Change)
|
||||
} yield res
|
||||
|
@ -234,7 +234,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
|
|||
psbt = dummyPSBT(prevTxId = dummyPrevTx.txId)
|
||||
.addKeyPathToInput(accountDb.xpub, walletPath, walletKey, 0)
|
||||
|
||||
signed <- wallet.signPSBT(psbt)
|
||||
signed <- wallet.sendFundsHandling.signPSBT(psbt)
|
||||
} yield {
|
||||
assert(signed != psbt)
|
||||
assert(
|
||||
|
@ -254,11 +254,13 @@ class WalletUnitTest extends BitcoinSWalletTest {
|
|||
spk = addr.scriptPubKey
|
||||
_ = assert(spk == P2PKHScriptPubKey(walletKey))
|
||||
dummyPrevTx = dummyTx(spk = spk)
|
||||
_ <- wallet.processTransaction(dummyPrevTx, blockHashOpt = None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(dummyPrevTx,
|
||||
blockHashOpt =
|
||||
None)
|
||||
|
||||
psbt = dummyPSBT(prevTxId = dummyPrevTx.txId)
|
||||
|
||||
signed <- wallet.signPSBT(psbt)
|
||||
signed <- wallet.sendFundsHandling.signPSBT(psbt)
|
||||
} yield {
|
||||
assert(signed != psbt)
|
||||
assert(
|
||||
|
@ -278,11 +280,13 @@ class WalletUnitTest extends BitcoinSWalletTest {
|
|||
spk = addr.scriptPubKey
|
||||
_ = assert(spk == P2SHScriptPubKey(P2WPKHWitnessSPKV0(walletKey)))
|
||||
dummyPrevTx = dummyTx(spk = spk)
|
||||
_ <- wallet.processTransaction(dummyPrevTx, blockHashOpt = None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(dummyPrevTx,
|
||||
blockHashOpt =
|
||||
None)
|
||||
|
||||
psbt = dummyPSBT(prevTxId = dummyPrevTx.txId)
|
||||
|
||||
signed <- wallet.signPSBT(psbt)
|
||||
signed <- wallet.sendFundsHandling.signPSBT(psbt)
|
||||
} yield {
|
||||
assert(signed != psbt)
|
||||
assert(
|
||||
|
@ -302,12 +306,14 @@ class WalletUnitTest extends BitcoinSWalletTest {
|
|||
spk = addr.scriptPubKey
|
||||
_ = assert(spk == P2WPKHWitnessSPKV0(walletKey))
|
||||
dummyPrevTx = dummyTx(spk = spk)
|
||||
_ <- wallet.processTransaction(dummyPrevTx, blockHashOpt = None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(dummyPrevTx,
|
||||
blockHashOpt =
|
||||
None)
|
||||
|
||||
psbt = dummyPSBT(prevTxId = dummyPrevTx.txId)
|
||||
.addUTXOToInput(dummyPrevTx, 0)
|
||||
|
||||
signed <- wallet.signPSBT(psbt)
|
||||
signed <- wallet.sendFundsHandling.signPSBT(psbt)
|
||||
} yield {
|
||||
assert(signed != psbt)
|
||||
assert(
|
||||
|
@ -320,7 +326,7 @@ class WalletUnitTest extends BitcoinSWalletTest {
|
|||
it must "be able to sign a psbt with no wallet utxos" in { (wallet: Wallet) =>
|
||||
val psbt = dummyPSBT()
|
||||
for {
|
||||
signed <- wallet.signPSBT(psbt)
|
||||
signed <- wallet.sendFundsHandling.signPSBT(psbt)
|
||||
} yield assert(signed == psbt)
|
||||
}
|
||||
|
||||
|
@ -333,10 +339,12 @@ class WalletUnitTest extends BitcoinSWalletTest {
|
|||
spk = addr.scriptPubKey
|
||||
_ = assert(spk == P2WPKHWitnessSPKV0(walletKey))
|
||||
dummyPrevTx = dummyTx(spk = spk)
|
||||
_ <- wallet.processTransaction(dummyPrevTx, blockHashOpt = None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(dummyPrevTx,
|
||||
blockHashOpt = None)
|
||||
|
||||
dummyPrevTx1 = dummyTx(prevTxId = dummyPrevTx.txId, spk = spk)
|
||||
_ <- wallet.processTransaction(dummyPrevTx1, blockHashOpt = None)
|
||||
_ <- wallet.transactionProcessing.processTransaction(dummyPrevTx1,
|
||||
blockHashOpt = None)
|
||||
|
||||
toBroadcast <- wallet.getTransactionsToBroadcast
|
||||
} yield assert(toBroadcast.map(_.txIdBE) == Vector(dummyPrevTx1.txIdBE))
|
||||
|
|
|
@ -15,16 +15,11 @@ import org.bitcoins.core.crypto.ExtPublicKey
|
|||
import org.bitcoins.core.currency.*
|
||||
import org.bitcoins.core.gcs.{GolombFilter, SimpleFilterMatcher}
|
||||
import org.bitcoins.core.hd.*
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.BitcoinAddress
|
||||
import org.bitcoins.core.protocol.blockchain.{Block, ChainParams}
|
||||
import org.bitcoins.core.protocol.blockchain.ChainParams
|
||||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||
import org.bitcoins.core.protocol.transaction.*
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.core.script.constant.ScriptConstant
|
||||
import org.bitcoins.core.script.control.OP_RETURN
|
||||
import org.bitcoins.core.util.{BitcoinScriptUtil, FutureUtil, HDUtil}
|
||||
import org.bitcoins.core.wallet.builder.*
|
||||
import org.bitcoins.core.util.{FutureUtil, HDUtil}
|
||||
import org.bitcoins.core.wallet.fee.*
|
||||
import org.bitcoins.core.wallet.utxo.*
|
||||
import org.bitcoins.core.wallet.utxo.TxoState.*
|
||||
|
@ -36,7 +31,6 @@ import org.bitcoins.wallet.callback.WalletCallbacks
|
|||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
import org.bitcoins.wallet.internal.*
|
||||
import org.bitcoins.wallet.models.*
|
||||
import scodec.bits.ByteVector
|
||||
import slick.dbio.{DBIOAction, Effect, NoStream}
|
||||
|
||||
import java.time.Instant
|
||||
|
@ -44,11 +38,10 @@ import java.time.temporal.ChronoUnit
|
|||
import java.util.concurrent.TimeUnit
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import scala.util.control.NonFatal
|
||||
import scala.util.Random
|
||||
|
||||
abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
|
||||
|
||||
override def keyManager: BIP39KeyManager = {
|
||||
def keyManager: BIP39KeyManager = {
|
||||
walletConfig.kmConf.toBip39KeyManager
|
||||
}
|
||||
implicit val walletConfig: WalletAppConfig
|
||||
|
@ -96,15 +89,17 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
|
|||
val creationTime: Instant = keyManager.creationTime
|
||||
|
||||
def utxoHandling: UtxoHandling =
|
||||
UtxoHandling(spendingInfoDAO, transactionDAO, chainQueryApi)
|
||||
UtxoHandling(spendingInfoDAO, transactionDAO, addressDAO, chainQueryApi)
|
||||
|
||||
def fundTxHandling: FundTransactionHandling = FundTransactionHandling(
|
||||
accountHandling = accountHandling,
|
||||
utxoHandling = utxoHandling,
|
||||
addressHandling = addressHandling,
|
||||
transactionProcessing = transactionProcessing,
|
||||
spendingInfoDAO = spendingInfoDAO,
|
||||
transactionDAO = transactionDAO,
|
||||
keyManager = keyManager
|
||||
keyManager = keyManager,
|
||||
feeRateApi = feeRateApi
|
||||
)
|
||||
def accountHandling: AccountHandling =
|
||||
AccountHandling(walletDAOs, keyManager)
|
||||
|
@ -112,7 +107,7 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
|
|||
def addressHandling: AddressHandling =
|
||||
AddressHandling(accountHandling, walletDAOs)
|
||||
|
||||
protected lazy val transactionProcessing: TransactionProcessingApi = {
|
||||
override lazy val transactionProcessing: TransactionProcessingApi = {
|
||||
TransactionProcessing(
|
||||
walletApi = this,
|
||||
chainQueryApi = chainQueryApi,
|
||||
|
@ -131,6 +126,19 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
|
|||
)
|
||||
}
|
||||
|
||||
override def sendFundsHandling: SendFundsHandlingApi = {
|
||||
SendFundsHandlingHandling(
|
||||
accountHandling = accountHandling,
|
||||
feeRateApi = feeRateApi,
|
||||
fundTxHandling = fundTxHandling,
|
||||
addressHandling = addressHandling,
|
||||
transactionProcessing = transactionProcessing,
|
||||
utxoHandling = utxoHandling,
|
||||
keyManager = keyManager,
|
||||
walletDAOs = walletDAOs
|
||||
)
|
||||
}
|
||||
|
||||
override def isRescanning(): Future[Boolean] = rescanHandling.isRescanning()
|
||||
|
||||
def walletCallbacks: WalletCallbacks = walletConfig.callBacks
|
||||
|
@ -194,35 +202,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
|
|||
}
|
||||
}
|
||||
|
||||
override def processBlock(block: Block): Future[Unit] = {
|
||||
transactionProcessing.processBlock(block)
|
||||
}
|
||||
|
||||
override def processTransaction(
|
||||
transaction: Transaction,
|
||||
blockHashOpt: Option[DoubleSha256DigestBE]): Future[Unit] = {
|
||||
transactionProcessing.processTransaction(transaction, blockHashOpt)
|
||||
}
|
||||
|
||||
/** Processes TXs originating from our wallet. This is called right after
|
||||
* we've signed a TX, updating our UTXO state.
|
||||
*/
|
||||
override def processOurTransaction(
|
||||
transaction: Transaction,
|
||||
feeRate: FeeUnit,
|
||||
inputAmount: CurrencyUnit,
|
||||
sentAmount: CurrencyUnit,
|
||||
blockHashOpt: Option[DoubleSha256DigestBE],
|
||||
newTags: Vector[AddressTag]
|
||||
): Future[ProcessTxResult] = {
|
||||
transactionProcessing.processOurTransaction(transaction,
|
||||
feeRate,
|
||||
inputAmount,
|
||||
sentAmount,
|
||||
blockHashOpt,
|
||||
newTags)
|
||||
}
|
||||
|
||||
override def processCompactFilters(
|
||||
blockFilters: Vector[(DoubleSha256DigestBE, GolombFilter)]
|
||||
): Future[Wallet] = {
|
||||
|
@ -321,30 +300,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
|
|||
spendingInfoCount <- spendingInfoDAO.count()
|
||||
} yield addressCount == 0 && spendingInfoCount == 0
|
||||
|
||||
override def clearAllUtxos(): Future[Wallet] = {
|
||||
val aggregatedActions
|
||||
: DBIOAction[Unit, NoStream, Effect.Write with Effect.Transactional] =
|
||||
spendingInfoDAO.deleteAllAction().map(_ => ())
|
||||
|
||||
val resultedF = safeDatabase.run(aggregatedActions)
|
||||
resultedF.failed.foreach(err =>
|
||||
logger.error(
|
||||
s"Failed to clear utxos, addresses and scripts from the database",
|
||||
err
|
||||
))
|
||||
|
||||
resultedF.map(_ => this)
|
||||
}
|
||||
|
||||
override def clearAllAddresses(): Future[Wallet] = {
|
||||
val action = addressDAO
|
||||
.deleteAllAction()
|
||||
.map(_ => ())
|
||||
safeDatabase
|
||||
.run(action)
|
||||
.map(_ => this)
|
||||
}
|
||||
|
||||
override def getBalance()(implicit
|
||||
ec: ExecutionContext
|
||||
): Future[CurrencyUnit] = {
|
||||
|
@ -354,11 +309,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
|
|||
override def getConfirmedBalance(): Future[CurrencyUnit] = {
|
||||
safeDatabase.run(spendingInfoDAO.getConfirmedBalanceAction())
|
||||
}
|
||||
|
||||
override def getConfirmedBalance(account: HDAccount): Future[CurrencyUnit] = {
|
||||
safeDatabase.run(spendingInfoDAO.getConfirmedBalanceAction(Some(account)))
|
||||
}
|
||||
|
||||
override def getConfirmedBalance(tag: AddressTag): Future[CurrencyUnit] = {
|
||||
spendingInfoDAO.findAllUnspentForTag(tag).map { allUnspent =>
|
||||
val confirmed = allUnspent.filter(_.state == ConfirmedReceived)
|
||||
|
@ -370,12 +320,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
|
|||
safeDatabase.run(spendingInfoDAO.getUnconfirmedBalanceAction())
|
||||
}
|
||||
|
||||
override def getUnconfirmedBalance(
|
||||
account: HDAccount
|
||||
): Future[CurrencyUnit] = {
|
||||
safeDatabase.run(spendingInfoDAO.getUnconfirmedBalanceAction(Some(account)))
|
||||
}
|
||||
|
||||
override def getUnconfirmedBalance(tag: AddressTag): Future[CurrencyUnit] = {
|
||||
spendingInfoDAO.findAllUnspentForTag(tag).map { allUnspent =>
|
||||
val confirmed = allUnspent
|
||||
|
@ -402,424 +346,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
|
|||
spendingInfoDAO.findOutputsBeingSpent(tx)
|
||||
}
|
||||
|
||||
/** Enumerates all the TX outpoints in the wallet */
|
||||
protected[wallet] def listOutpoints(): Future[Vector[TransactionOutPoint]] =
|
||||
spendingInfoDAO.findAllOutpoints()
|
||||
|
||||
/** Takes a [[RawTxBuilderWithFinalizer]] for a transaction to be sent, and
|
||||
* completes it by: finalizing and signing the transaction, then correctly
|
||||
* processing and logging it
|
||||
*/
|
||||
private def finishSend[F <: RawTxFinalizer](
|
||||
rawTxHelper: FundRawTxHelper[F],
|
||||
sentAmount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
newTags: Vector[AddressTag]
|
||||
): Future[Transaction] = {
|
||||
val signed = rawTxHelper.signedTx
|
||||
|
||||
val processedTxF = for {
|
||||
ourOuts <- addressHandling.findOurOutputs(signed)
|
||||
creditingAmount = rawTxHelper.scriptSigParams.foldLeft(
|
||||
CurrencyUnits.zero
|
||||
)(_ + _.amount)
|
||||
_ <- transactionProcessing.processOurTransaction(
|
||||
transaction = signed,
|
||||
feeRate = feeRate,
|
||||
inputAmount = creditingAmount,
|
||||
sentAmount = sentAmount,
|
||||
blockHashOpt = None,
|
||||
newTags = newTags
|
||||
)
|
||||
} 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
|
||||
}
|
||||
|
||||
processedTxF.recoverWith { case _ =>
|
||||
// if something fails, we need to unreserve the utxos associated with this tx
|
||||
// and then propogate the failed future upwards
|
||||
utxoHandling.unmarkUTXOsAsReserved(signed).flatMap(_ => processedTxF)
|
||||
}
|
||||
}
|
||||
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
feeRate: FeeUnit
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
require(
|
||||
address.networkParameters.isSameNetworkBytes(networkParameters),
|
||||
s"Cannot send to address on other network, got ${address.networkParameters}"
|
||||
)
|
||||
logger.info(s"Sending 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"
|
||||
)
|
||||
spentUtxos =
|
||||
utxoDbs.filterNot(utxo => TxoState.receivedStates.contains(utxo.state))
|
||||
_ = require(
|
||||
spentUtxos.isEmpty,
|
||||
s"Some out points given have already been spent, ${spentUtxos.map(_.outPoint)}"
|
||||
)
|
||||
|
||||
utxos <- Future.sequence {
|
||||
utxoDbs.map(utxo =>
|
||||
transactionDAO
|
||||
.findByOutPoint(utxo.outPoint)
|
||||
.map(txDb => utxo.toUTXOInfo(keyManager, txDb.get.transaction)))
|
||||
}
|
||||
inputInfos = utxos.map(_.inputInfo)
|
||||
|
||||
utxoAmount = utxoDbs.map(_.output.value).sum
|
||||
dummyOutput = TransactionOutput(utxoAmount, address.scriptPubKey)
|
||||
inputs = InputUtil.calcSequenceForInputs(utxos)
|
||||
|
||||
txBuilder = RawTxBuilder() ++= inputs += dummyOutput
|
||||
finalizer = SubtractFeeFromOutputsFinalizer(
|
||||
inputInfos,
|
||||
feeRate,
|
||||
Vector(address.scriptPubKey)
|
||||
)
|
||||
.andThen(ShuffleFinalizer)
|
||||
.andThen(AddWitnessDataFinalizer(inputInfos))
|
||||
|
||||
withFinalizer = txBuilder.setFinalizer(finalizer)
|
||||
|
||||
tmp = withFinalizer.buildTx()
|
||||
|
||||
_ = require(
|
||||
tmp.outputs.size == 1,
|
||||
s"Created tx is not as expected, does not have 1 output, got $tmp"
|
||||
)
|
||||
rawTxHelper = FundRawTxHelper(withFinalizer, utxos, feeRate, Future.unit)
|
||||
tx <- finishSend(
|
||||
rawTxHelper,
|
||||
tmp.outputs.head.value,
|
||||
feeRate,
|
||||
Vector.empty
|
||||
)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): 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"
|
||||
)
|
||||
spentUtxos =
|
||||
utxoDbs.filterNot(utxo => TxoState.receivedStates.contains(utxo.state))
|
||||
_ = require(
|
||||
spentUtxos.isEmpty,
|
||||
s"Some out points given have already been spent, ${spentUtxos.map(_.outPoint)}"
|
||||
)
|
||||
|
||||
prevTxFs = utxoDbs.map(utxo =>
|
||||
transactionDAO.findByOutPoint(utxo.outPoint).map(_.get.transaction))
|
||||
prevTxs <- FutureUtil.collect(prevTxFs)
|
||||
utxos =
|
||||
utxoDbs
|
||||
.zip(prevTxs)
|
||||
.map(info => info._1.toUTXOInfo(keyManager, info._2))
|
||||
|
||||
changeAddr <- accountHandling.getNewChangeAddress(fromAccount.hdAccount)
|
||||
|
||||
output = TransactionOutput(amount, address.scriptPubKey)
|
||||
txBuilder = ShufflingNonInteractiveFinalizer.txBuilderFrom(
|
||||
Vector(output),
|
||||
utxos,
|
||||
feeRate,
|
||||
changeAddr.scriptPubKey
|
||||
)
|
||||
rawTxHelper = FundRawTxHelper(txBuilder, utxos, feeRate, Future.unit)
|
||||
tx <- finishSend(rawTxHelper, amount, feeRate, newTags)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** Sends the entire wallet balance to the given address */
|
||||
override def sweepWallet(address: BitcoinAddress, feeRate: FeeUnit)(implicit
|
||||
ec: ExecutionContext
|
||||
): Future[Transaction] = {
|
||||
for {
|
||||
utxos <- utxoHandling.listUtxos()
|
||||
balance = utxos.foldLeft(CurrencyUnits.zero)(_ + _.output.value)
|
||||
_ = logger.info(s"Sweeping wallet balance=$balance to address=$address")
|
||||
outpoints = utxos.map(_.outPoint)
|
||||
tx <- sendFromOutPoints(outpoints, address, feeRate)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def bumpFeeRBF(
|
||||
txId: DoubleSha256DigestBE,
|
||||
newFeeRate: FeeUnit
|
||||
): Future[Transaction] = {
|
||||
for {
|
||||
txDbOpt <- transactionDAO.findByTxId(txId)
|
||||
txDb <- txDbOpt match {
|
||||
case Some(db) => Future.successful(db)
|
||||
case None =>
|
||||
Future.failed(
|
||||
new RuntimeException(s"Unable to find transaction ${txId.hex}")
|
||||
)
|
||||
}
|
||||
tx = txDb.transaction
|
||||
|
||||
_ = require(TxUtil.isRBFEnabled(tx), "Transaction is not signaling RBF")
|
||||
|
||||
outPoints = tx.inputs.map(_.previousOutput).toVector
|
||||
spks = tx.outputs.map(_.scriptPubKey).toVector
|
||||
|
||||
utxos <- spendingInfoDAO.findByOutPoints(outPoints)
|
||||
_ = require(utxos.nonEmpty, "Can only bump fee for our own transaction")
|
||||
_ = require(
|
||||
utxos.size == tx.inputs.size,
|
||||
"Can only bump fee for a transaction we own all the inputs"
|
||||
)
|
||||
|
||||
_ = require(
|
||||
txDb.blockHashOpt.isEmpty,
|
||||
s"Cannot replace a confirmed transaction, ${txDb.blockHashOpt.get.hex}"
|
||||
)
|
||||
|
||||
spendingInfos <- FutureUtil.sequentially(utxos) { utxo =>
|
||||
transactionDAO
|
||||
.findByOutPoint(utxo.outPoint)
|
||||
.map(txDbOpt =>
|
||||
utxo.toUTXOInfo(keyManager = keyManager, txDbOpt.get.transaction))
|
||||
}
|
||||
|
||||
_ = {
|
||||
val inputAmount = utxos.foldLeft(CurrencyUnits.zero)(_ + _.output.value)
|
||||
|
||||
val oldFeeRate = newFeeRate match {
|
||||
case _: SatoshisPerByte =>
|
||||
SatoshisPerByte.calc(inputAmount, tx)
|
||||
case _: SatoshisPerKiloByte =>
|
||||
SatoshisPerKiloByte.calc(inputAmount, tx)
|
||||
case _: SatoshisPerVirtualByte =>
|
||||
SatoshisPerVirtualByte.calc(inputAmount, tx)
|
||||
case _: SatoshisPerKW =>
|
||||
SatoshisPerKW.calc(inputAmount, tx)
|
||||
}
|
||||
|
||||
require(
|
||||
oldFeeRate.currencyUnit < newFeeRate.currencyUnit,
|
||||
s"Cannot bump to a lower fee ${oldFeeRate.currencyUnit} < ${newFeeRate.currencyUnit}"
|
||||
)
|
||||
}
|
||||
|
||||
myAddrs <- addressDAO.findByScriptPubKeys(spks)
|
||||
_ = require(myAddrs.nonEmpty, "Must have an output we own")
|
||||
|
||||
changeSpks = myAddrs.flatMap { db =>
|
||||
if (db.isChange) {
|
||||
Some(db.scriptPubKey)
|
||||
} else None
|
||||
}
|
||||
|
||||
changeSpk =
|
||||
if (changeSpks.nonEmpty) {
|
||||
// Pick a random change spk
|
||||
Random.shuffle(changeSpks).head
|
||||
} else {
|
||||
// If none are explicit change, pick a random one we own
|
||||
Random.shuffle(myAddrs.map(_.scriptPubKey)).head
|
||||
}
|
||||
|
||||
oldOutputs <- spendingInfoDAO.findDbsForTx(txId)
|
||||
// delete old outputs
|
||||
_ <- spendingInfoDAO.deleteAll(oldOutputs)
|
||||
|
||||
sequence = tx.inputs.head.sequence + UInt32.one
|
||||
outputs = tx.outputs.filterNot(_.scriptPubKey == changeSpk)
|
||||
txBuilder = StandardNonInteractiveFinalizer.txBuilderFrom(
|
||||
outputs,
|
||||
spendingInfos,
|
||||
newFeeRate,
|
||||
changeSpk,
|
||||
sequence
|
||||
)
|
||||
|
||||
amount = outputs.foldLeft(CurrencyUnits.zero)(_ + _.value)
|
||||
rawTxHelper = FundRawTxHelper(
|
||||
txBuilder,
|
||||
spendingInfos,
|
||||
newFeeRate,
|
||||
Future.unit
|
||||
)
|
||||
tx <-
|
||||
finishSend(rawTxHelper, amount, newFeeRate, Vector.empty)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
algo: CoinSelectionAlgo,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): 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")
|
||||
val destination = TransactionOutput(amount, address.scriptPubKey)
|
||||
for {
|
||||
rawTxHelper <- fundTxHandling.fundRawTransactionInternal(
|
||||
destinations = Vector(destination),
|
||||
feeRate = feeRate,
|
||||
fromAccount = fromAccount,
|
||||
coinSelectionAlgo = algo,
|
||||
fromTagOpt = None,
|
||||
markAsReserved = true
|
||||
)
|
||||
tx <- finishSend(rawTxHelper, amount, feeRate, newTags)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
sendWithAlgo(
|
||||
address,
|
||||
amount,
|
||||
feeRate,
|
||||
CoinSelectionAlgo.LeastWaste,
|
||||
fromAccount
|
||||
)
|
||||
|
||||
override def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
sendWithAlgo(
|
||||
address,
|
||||
amount,
|
||||
feeRate,
|
||||
CoinSelectionAlgo.LeastWaste,
|
||||
fromAccount,
|
||||
newTags
|
||||
)
|
||||
|
||||
override def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
require(
|
||||
amounts.size == addresses.size,
|
||||
"Must have an amount for every address"
|
||||
)
|
||||
require(
|
||||
addresses.forall(
|
||||
_.networkParameters.isSameNetworkBytes(networkParameters)
|
||||
),
|
||||
s"Cannot send to address on other network, got ${addresses.map(_.networkParameters)}"
|
||||
)
|
||||
val destinations = addresses.zip(amounts).map { case (address, amount) =>
|
||||
logger.info(s"Sending $amount to $address at feerate $feeRate")
|
||||
TransactionOutput(amount, address.scriptPubKey)
|
||||
}
|
||||
sendToOutputs(destinations, feeRate, fromAccount, newTags)
|
||||
}
|
||||
|
||||
override def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
val messageToUse = if (hashMessage) {
|
||||
CryptoUtil.sha256(ByteVector(message.getBytes)).bytes
|
||||
} else {
|
||||
if (message.length > 80) {
|
||||
throw new IllegalArgumentException(
|
||||
s"Message cannot be greater than 80 characters, it should be hashed, got $message"
|
||||
)
|
||||
} else ByteVector(message.getBytes)
|
||||
}
|
||||
|
||||
val asm = Seq(OP_RETURN) ++ BitcoinScriptUtil.calculatePushOp(
|
||||
messageToUse
|
||||
) :+ ScriptConstant(messageToUse)
|
||||
|
||||
val scriptPubKey = ScriptPubKey(asm)
|
||||
|
||||
val output = TransactionOutput(0.satoshis, scriptPubKey)
|
||||
|
||||
for {
|
||||
fundRawTxHelper <- fundTxHandling.fundRawTransactionInternal(
|
||||
destinations = Vector(output),
|
||||
feeRate = feeRate,
|
||||
fromAccount = fromAccount,
|
||||
coinSelectionAlgo = CoinSelectionAlgo.RandomSelection,
|
||||
fromTagOpt = None,
|
||||
markAsReserved = true
|
||||
)
|
||||
tx <- finishSend(
|
||||
fundRawTxHelper,
|
||||
CurrencyUnits.zero,
|
||||
feeRate,
|
||||
Vector.empty
|
||||
)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = {
|
||||
for {
|
||||
fundRawTxHelper <- fundTxHandling.fundRawTransactionInternal(
|
||||
destinations = outputs,
|
||||
feeRate = feeRate,
|
||||
fromAccount = fromAccount,
|
||||
fromTagOpt = None,
|
||||
markAsReserved = true
|
||||
)
|
||||
sentAmount = outputs.foldLeft(CurrencyUnits.zero)(_ + _.value)
|
||||
tx <- finishSend(fundRawTxHelper, sentAmount, feeRate, newTags)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def isChange(output: TransactionOutput): Future[Boolean] = {
|
||||
addressDAO.findByScriptPubKey(output.scriptPubKey).map {
|
||||
|
@ -828,135 +354,13 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
|
|||
}
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def bumpFeeCPFP(
|
||||
txId: DoubleSha256DigestBE,
|
||||
feeRate: FeeUnit
|
||||
): Future[Transaction] = {
|
||||
for {
|
||||
txDbOpt <- transactionDAO.findByTxId(txId)
|
||||
txDb <- txDbOpt match {
|
||||
case Some(db) => Future.successful(db)
|
||||
case None =>
|
||||
Future.failed(
|
||||
new RuntimeException(s"Unable to find transaction ${txId.hex}")
|
||||
)
|
||||
}
|
||||
tx = txDb.transaction
|
||||
|
||||
spendingInfos <- spendingInfoDAO.findTx(tx)
|
||||
_ = require(
|
||||
spendingInfos.nonEmpty,
|
||||
s"Transaction ${txId.hex} must have an output we own"
|
||||
)
|
||||
|
||||
_ = require(
|
||||
txDb.blockHashOpt.isEmpty,
|
||||
s"Cannot replace a confirmed transaction, ${txDb.blockHashOpt.get.hex}"
|
||||
)
|
||||
|
||||
changeSpendingInfos = spendingInfos.flatMap { db =>
|
||||
if (db.isChange) {
|
||||
Some(db)
|
||||
} else None
|
||||
}
|
||||
|
||||
spendingInfo =
|
||||
if (changeSpendingInfos.nonEmpty) {
|
||||
// Pick a random change spendingInfo
|
||||
Random.shuffle(changeSpendingInfos).head
|
||||
} else {
|
||||
// If none are explicit change, pick a random one we own
|
||||
Random.shuffle(spendingInfos).head
|
||||
}
|
||||
|
||||
addr <- addressHandling.getNewChangeAddress()
|
||||
childTx <- sendFromOutPoints(Vector(spendingInfo.outPoint), addr, feeRate)
|
||||
} yield childTx
|
||||
}
|
||||
|
||||
override def signPSBT(
|
||||
psbt: PSBT
|
||||
)(implicit ec: ExecutionContext): Future[PSBT] = {
|
||||
val inputTxIds = psbt.transaction.inputs.zipWithIndex.map {
|
||||
case (input, index) =>
|
||||
input.previousOutput.txIdBE -> index
|
||||
}.toMap
|
||||
for {
|
||||
accountDbs <- accountDAO.findAll()
|
||||
ourXpubs = accountDbs.map(_.xpub)
|
||||
utxos <- spendingInfoDAO.findAll()
|
||||
txs <- transactionDAO.findByTxIds(inputTxIds.keys.toVector)
|
||||
} yield {
|
||||
val updated = txs.foldLeft(psbt) { (accum, tx) =>
|
||||
val index = inputTxIds(tx.txIdBE)
|
||||
accum.addUTXOToInput(tx.transaction, index)
|
||||
}
|
||||
|
||||
val signed =
|
||||
updated.inputMaps.zipWithIndex.foldLeft(updated) {
|
||||
case (unsigned, (input, index)) =>
|
||||
val xpubKeyPaths = input.BIP32DerivationPaths
|
||||
.filter { path =>
|
||||
ourXpubs.exists(_.fingerprint == path.masterFingerprint)
|
||||
}
|
||||
.map(bip32Path =>
|
||||
HDPath.fromString(
|
||||
bip32Path.path.toString
|
||||
)) // TODO add a way to get a HDPath from a BIP32 Path
|
||||
|
||||
val (utxoPath, withData) = {
|
||||
val outPoint = unsigned.transaction.inputs(index).previousOutput
|
||||
utxos.find(_.outpoint == outPoint) match {
|
||||
case Some(utxo) =>
|
||||
val psbtWithUtxoData = utxo.redeemScript match {
|
||||
case Some(redeemScript) =>
|
||||
unsigned.addRedeemOrWitnessScriptToInput(
|
||||
redeemScript,
|
||||
index
|
||||
)
|
||||
case None => unsigned
|
||||
}
|
||||
|
||||
(Vector(utxo.path), psbtWithUtxoData)
|
||||
case None => (Vector.empty, unsigned)
|
||||
}
|
||||
}
|
||||
|
||||
val keyPaths = xpubKeyPaths ++ utxoPath
|
||||
|
||||
keyPaths.foldLeft(withData) { (accum, hdPath) =>
|
||||
val sign = keyManager.toSign(hdPath)
|
||||
// Only sign if that key doesn't have a signature yet
|
||||
if (
|
||||
!input.partialSignatures.exists(
|
||||
_.pubKey.toPublicKey == sign.publicKey
|
||||
)
|
||||
) {
|
||||
logger.debug(
|
||||
s"Signing input $index with key ${sign.publicKey.hex}"
|
||||
)
|
||||
accum.sign(index, sign)
|
||||
} else {
|
||||
accum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (updated == signed) {
|
||||
logger.warn("Did not find any keys or utxos that belong to this wallet")
|
||||
}
|
||||
signed
|
||||
}
|
||||
}
|
||||
|
||||
override def getWalletName(): Future[String] = {
|
||||
Future.successful(walletConfig.walletName)
|
||||
}
|
||||
|
||||
override def getInfo(): Future[WalletInfo] = {
|
||||
for {
|
||||
accountDb <- getDefaultAccount()
|
||||
accountDb <- accountHandling.getDefaultAccount()
|
||||
walletState <- getSyncState()
|
||||
rescan <- rescanHandling.isRescanning()
|
||||
} yield {
|
||||
|
@ -1017,18 +421,10 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
|
|||
override def listUtxos(state: TxoState): Future[Vector[SpendingInfoDb]] =
|
||||
utxoHandling.listUtxos(state)
|
||||
|
||||
override def listUtxos(
|
||||
hdAccount: HDAccount): Future[Vector[SpendingInfoDb]] = {
|
||||
utxoHandling.listUtxos(hdAccount)
|
||||
}
|
||||
|
||||
override def listUtxos(tag: AddressTag): Future[Vector[SpendingInfoDb]] = {
|
||||
utxoHandling.listUtxos(tag)
|
||||
}
|
||||
|
||||
override def listDefaultAccountUtxos(): Future[Vector[SpendingInfoDb]] = {
|
||||
utxoHandling.listDefaultAccountUtxos()
|
||||
}
|
||||
def markUTXOsAsReserved(
|
||||
utxos: Vector[SpendingInfoDb]): Future[Vector[SpendingInfoDb]] = {
|
||||
utxoHandling.markUTXOsAsReserved(utxos)
|
||||
|
@ -1052,11 +448,6 @@ abstract class Wallet extends NeutrinoHDWalletApi with WalletLogger {
|
|||
tx: Transaction): Future[Vector[SpendingInfoDb]] = {
|
||||
utxoHandling.unmarkUTXOsAsReserved(tx)
|
||||
}
|
||||
|
||||
override def getDefaultAccountForType(
|
||||
addressType: AddressType): Future[AccountDb] = {
|
||||
accountHandling.getDefaultAccountForType(addressType)
|
||||
}
|
||||
}
|
||||
|
||||
// todo: create multiple wallets, need to maintain multiple databases
|
||||
|
|
|
@ -9,16 +9,14 @@ import org.bitcoins.core.api.dlc.wallet.db.{
|
|||
IncomingDLCOfferDb
|
||||
}
|
||||
import org.bitcoins.core.api.feeprovider.FeeRateApi
|
||||
import org.bitcoins.core.api.keymanager.BIP39KeyManagerApi
|
||||
import org.bitcoins.core.api.node.NodeApi
|
||||
import org.bitcoins.core.api.wallet.*
|
||||
import org.bitcoins.core.api.wallet.db.*
|
||||
import org.bitcoins.core.currency.{CurrencyUnit, Satoshis}
|
||||
import org.bitcoins.core.dlc.accounting.DLCWalletAccounting
|
||||
import org.bitcoins.core.gcs.GolombFilter
|
||||
import org.bitcoins.core.hd.{AddressType, HDAccount, HDPurpose}
|
||||
import org.bitcoins.core.hd.HDAccount
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.blockchain.Block
|
||||
import org.bitcoins.core.protocol.dlc.models.*
|
||||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||
import org.bitcoins.core.protocol.tlv.*
|
||||
|
@ -28,11 +26,6 @@ import org.bitcoins.core.protocol.transaction.{
|
|||
TransactionOutput
|
||||
}
|
||||
import org.bitcoins.core.protocol.BitcoinAddress
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.core.wallet.builder.{
|
||||
FundRawTxHelper,
|
||||
ShufflingNonInteractiveFinalizer
|
||||
}
|
||||
import org.bitcoins.core.wallet.fee.{FeeUnit, SatoshisPerVirtualByte}
|
||||
import org.bitcoins.core.wallet.utxo.{AddressTag, TxoState}
|
||||
import org.bitcoins.crypto.{DoubleSha256DigestBE, Sha256Digest}
|
||||
|
@ -67,7 +60,15 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
|
|||
override def fundTxHandling: FundTransactionHandlingApi =
|
||||
wallet.fundTxHandling
|
||||
|
||||
override def utxoHandling: UtxoHandlingApi = wallet.utxoHandling
|
||||
|
||||
override def addressHandling: AddressHandlingApi = wallet.addressHandling
|
||||
|
||||
override def transactionProcessing: TransactionProcessingApi =
|
||||
wallet.transactionProcessing
|
||||
|
||||
override def sendFundsHandling: SendFundsHandlingApi =
|
||||
wallet.sendFundsHandling
|
||||
def isInitialized: Boolean = synchronized {
|
||||
walletOpt.isDefined
|
||||
}
|
||||
|
@ -108,34 +109,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
|
|||
|
||||
override def getNewChangeAddress(): Future[BitcoinAddress] = delegate(
|
||||
_.getNewChangeAddress())
|
||||
override def processBlock(block: Block): Future[Unit] =
|
||||
delegate(_.processBlock(block))
|
||||
|
||||
override def processTransaction(
|
||||
transaction: Transaction,
|
||||
blockHashOpt: Option[DoubleSha256DigestBE]): Future[Unit] = {
|
||||
delegate(_.processTransaction(transaction, blockHashOpt))
|
||||
}
|
||||
|
||||
/** Processes TXs originating from our wallet. This is called right after
|
||||
* we've signed a TX, updating our UTXO state.
|
||||
*/
|
||||
override def processOurTransaction(
|
||||
transaction: Transaction,
|
||||
feeRate: FeeUnit,
|
||||
inputAmount: CurrencyUnit,
|
||||
sentAmount: CurrencyUnit,
|
||||
blockHashOpt: Option[DoubleSha256DigestBE],
|
||||
newTags: Vector[AddressTag]
|
||||
): Future[ProcessTxResult] = {
|
||||
delegate(
|
||||
_.processOurTransaction(transaction,
|
||||
feeRate,
|
||||
inputAmount,
|
||||
sentAmount,
|
||||
blockHashOpt,
|
||||
newTags))
|
||||
}
|
||||
|
||||
override def processCompactFilters(
|
||||
blockFilters: Vector[(DoubleSha256DigestBE, GolombFilter)]
|
||||
|
@ -164,17 +137,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
|
|||
res
|
||||
}
|
||||
|
||||
override def fundRawTransaction(
|
||||
destinations: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
markAsReserved: Boolean
|
||||
): Future[FundRawTxHelper[ShufflingNonInteractiveFinalizer]] = {
|
||||
delegate(
|
||||
_.fundRawTransaction(destinations, feeRate, fromAccount, markAsReserved)
|
||||
)
|
||||
}
|
||||
|
||||
override def updateUtxoPendingStates(): Future[Vector[SpendingInfoDb]] =
|
||||
delegate(_.updateUtxoPendingStates())
|
||||
|
||||
|
@ -192,10 +154,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
|
|||
override def getUnconfirmedBalance(tag: AddressTag): Future[CurrencyUnit] =
|
||||
delegate(_.getUnconfirmedBalance(tag))
|
||||
|
||||
override def listDefaultAccountUtxos(): Future[Vector[SpendingInfoDb]] = {
|
||||
delegate(_.listDefaultAccountUtxos())
|
||||
}
|
||||
|
||||
override def listTransactions(): Future[Vector[TransactionDb]] = {
|
||||
delegate(_.listTransactions())
|
||||
}
|
||||
|
@ -203,11 +161,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
|
|||
_.listUtxos()
|
||||
)
|
||||
|
||||
override def listUtxos(
|
||||
hdAccount: HDAccount): Future[Vector[SpendingInfoDb]] = {
|
||||
delegate(_.listUtxos(hdAccount))
|
||||
}
|
||||
|
||||
override def listUtxos(state: TxoState): Future[Vector[SpendingInfoDb]] =
|
||||
delegate(_.listUtxos(state))
|
||||
|
||||
|
@ -233,27 +186,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
|
|||
|
||||
override def isEmpty(): Future[Boolean] = delegate(_.isEmpty())
|
||||
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
feeRate: FeeUnit
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendFromOutPoints(outPoints, address, feeRate))
|
||||
|
||||
override def sweepWallet(address: BitcoinAddress, feeRate: FeeUnit)(implicit
|
||||
ec: ExecutionContext
|
||||
): Future[Transaction] = delegate(_.sweepWallet(address, feeRate))
|
||||
|
||||
override def bumpFeeRBF(
|
||||
txId: DoubleSha256DigestBE,
|
||||
newFeeRate: FeeUnit
|
||||
): Future[Transaction] = delegate(_.bumpFeeRBF(txId, newFeeRate))
|
||||
|
||||
override def bumpFeeCPFP(
|
||||
txId: DoubleSha256DigestBE,
|
||||
feeRate: FeeUnit
|
||||
): Future[Transaction] = delegate(_.bumpFeeCPFP(txId, feeRate))
|
||||
|
||||
override def isChange(output: TransactionOutput): Future[Boolean] = delegate(
|
||||
_.isChange(output)
|
||||
)
|
||||
|
@ -427,100 +359,11 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
|
|||
address: InetSocketAddress
|
||||
): Future[Vector[DLCStatus]] = delegate(_.listDLCsByContact(address))
|
||||
|
||||
override def keyManager: BIP39KeyManagerApi = wallet.keyManager
|
||||
def getConfirmedBalance(account: HDAccount): Future[CurrencyUnit] =
|
||||
delegate(_.accountHandling.getConfirmedBalance(account))
|
||||
|
||||
override def getConfirmedBalance(account: HDAccount): Future[CurrencyUnit] =
|
||||
delegate(_.getConfirmedBalance(account))
|
||||
|
||||
override def getUnconfirmedBalance(account: HDAccount): Future[CurrencyUnit] =
|
||||
delegate(_.getUnconfirmedBalance(account))
|
||||
|
||||
override def getDefaultAccount(): Future[AccountDb] = delegate(
|
||||
_.getDefaultAccount()
|
||||
)
|
||||
|
||||
override def getDefaultAccountForType(
|
||||
addressType: AddressType
|
||||
): Future[AccountDb] = delegate(_.getDefaultAccountForType(addressType))
|
||||
|
||||
override def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
algo: CoinSelectionAlgo,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.sendWithAlgo(address, amount, feeRate, algo, fromAccount, newTags)
|
||||
)
|
||||
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.sendFromOutPoints(
|
||||
outPoints,
|
||||
address,
|
||||
amount,
|
||||
feeRate,
|
||||
fromAccount,
|
||||
newTags
|
||||
)
|
||||
)
|
||||
|
||||
override def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.sendToAddress(address, amount, feeRate, fromAccount, newTags)
|
||||
)
|
||||
|
||||
override def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.sendToAddresses(addresses, amounts, feeRate, fromAccount, newTags)
|
||||
)
|
||||
|
||||
override def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.sendToOutputs(outputs, feeRate, fromAccount, newTags)
|
||||
)
|
||||
|
||||
override def signPSBT(psbt: PSBT)(implicit
|
||||
ec: ExecutionContext
|
||||
): Future[PSBT] = delegate(_.signPSBT(psbt))
|
||||
|
||||
override def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.makeOpReturnCommitment(message, hashMessage, feeRate, fromAccount)
|
||||
)
|
||||
|
||||
override def clearAllUtxos(): Future[HDWalletApi] = delegate(
|
||||
_.clearAllUtxos()
|
||||
)
|
||||
|
||||
override def clearAllAddresses(): Future[WalletApi] = {
|
||||
delegate(_.clearAllAddresses())
|
||||
}
|
||||
def getUnconfirmedBalance(account: HDAccount): Future[CurrencyUnit] =
|
||||
delegate(_.accountHandling.getUnconfirmedBalance(account))
|
||||
|
||||
override def getSyncDescriptorOpt(): Future[Option[SyncHeightDescriptor]] =
|
||||
delegate(_.getSyncDescriptorOpt())
|
||||
|
@ -546,77 +389,9 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
|
|||
ec: ExecutionContext
|
||||
): Future[CurrencyUnit] = delegate(_.getBalance(tag))
|
||||
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.sendFromOutPoints(outPoints, address, amount, feeRateOpt)
|
||||
)
|
||||
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.sendFromOutPoints(outPoints, address, feeRateOpt)
|
||||
)
|
||||
|
||||
override def sweepWallet(address: BitcoinAddress)(implicit
|
||||
def getBalance(account: HDAccount)(implicit
|
||||
ec: ExecutionContext
|
||||
): Future[Transaction] = delegate(_.sweepWallet(address))
|
||||
|
||||
override def sweepWallet(
|
||||
address: BitcoinAddress,
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.sweepWallet(address, feeRateOpt)
|
||||
)
|
||||
|
||||
override def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
algo: CoinSelectionAlgo
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.sendWithAlgo(address, amount, feeRateOpt, algo)
|
||||
)
|
||||
|
||||
override def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.sendToAddress(address, amount, feeRateOpt)
|
||||
)
|
||||
|
||||
override def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.sendToOutputs(outputs, feeRateOpt)
|
||||
)
|
||||
|
||||
override def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.sendToAddresses(addresses, amounts, feeRateOpt)
|
||||
)
|
||||
|
||||
override def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRateOpt: Option[FeeUnit]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] = delegate(
|
||||
_.makeOpReturnCommitment(message, hashMessage, feeRateOpt)
|
||||
)
|
||||
|
||||
override def getBalance(account: HDAccount)(implicit
|
||||
ec: ExecutionContext
|
||||
): Future[CurrencyUnit] = delegate(_.getBalance(account))
|
||||
): Future[CurrencyUnit] = delegate(_.accountHandling.getBalance(account))
|
||||
|
||||
override def processCompactFilter(
|
||||
blockHash: DoubleSha256DigestBE,
|
||||
|
@ -624,191 +399,6 @@ class WalletHolder(initWalletOpt: Option[DLCNeutrinoHDWalletApi])(implicit
|
|||
): Future[NeutrinoHDWalletApi] =
|
||||
delegate(_.processCompactFilter(blockHash, blockFilter))
|
||||
|
||||
override def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
algo: CoinSelectionAlgo,
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendWithAlgo(address, amount, feeRate, algo, fromAccount))
|
||||
|
||||
override def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
algo: CoinSelectionAlgo,
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendWithAlgo(address, amount, feeRateOpt, algo, fromAccount))
|
||||
|
||||
override def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
algo: CoinSelectionAlgo
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendWithAlgo(address, amount, feeRate, algo))
|
||||
|
||||
override def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
algo: CoinSelectionAlgo,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendWithAlgo(address, amount, feeRate, algo, newTags))
|
||||
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(
|
||||
_.sendFromOutPoints(outPoints, address, amount, feeRate, fromAccount)
|
||||
)
|
||||
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(
|
||||
_.sendFromOutPoints(outPoints, address, amount, feeRateOpt, fromAccount)
|
||||
)
|
||||
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendFromOutPoints(outPoints, address, amount, feeRate))
|
||||
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendFromOutPoints(outPoints, address, amount, feeRate, newTags))
|
||||
|
||||
override def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendToAddress(address, amount, feeRate, fromAccount))
|
||||
|
||||
override def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendToAddress(address, amount, feeRateOpt, fromAccount))
|
||||
|
||||
override def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendToAddress(address, amount, feeRate))
|
||||
|
||||
override def sendToAddress(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendToAddress(address, amount, feeRate, newTags))
|
||||
|
||||
override def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendToAddresses(addresses, amounts, feeRate, fromAccount))
|
||||
|
||||
override def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendToAddresses(addresses, amounts, feeRateOpt, fromAccount))
|
||||
|
||||
override def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRate: FeeUnit
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendToAddresses(addresses, amounts, feeRate))
|
||||
|
||||
override def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRate: FeeUnit,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendToAddresses(addresses, amounts, feeRate, newTags))
|
||||
|
||||
override def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendToOutputs(outputs, feeRate, fromAccount))
|
||||
|
||||
override def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendToOutputs(outputs, feeRateOpt, fromAccount))
|
||||
|
||||
override def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit,
|
||||
newTags: Vector[AddressTag]
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendToOutputs(outputs, feeRate, newTags))
|
||||
|
||||
override def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.sendToOutputs(outputs, feeRate))
|
||||
|
||||
override def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
fromAccount: AccountDb
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(
|
||||
_.makeOpReturnCommitment(message, hashMessage, feeRateOpt, fromAccount)
|
||||
)
|
||||
|
||||
override def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRate: FeeUnit
|
||||
)(implicit ec: ExecutionContext): Future[Transaction] =
|
||||
delegate(_.makeOpReturnCommitment(message, hashMessage, feeRate))
|
||||
|
||||
override def listAccounts(purpose: HDPurpose)(implicit
|
||||
ec: ExecutionContext
|
||||
): Future[Vector[AccountDb]] =
|
||||
delegate(_.listAccounts(purpose))
|
||||
|
||||
override def createDLCOffer(
|
||||
contractInfoTLV: ContractInfoTLV,
|
||||
collateral: Satoshis,
|
||||
|
|
|
@ -93,6 +93,16 @@ case class AccountHandling(
|
|||
}
|
||||
}
|
||||
|
||||
override def getUnconfirmedBalance(
|
||||
account: HDAccount
|
||||
): Future[CurrencyUnit] = {
|
||||
safeDatabase.run(spendingInfoDAO.getUnconfirmedBalanceAction(Some(account)))
|
||||
}
|
||||
|
||||
override def getConfirmedBalance(account: HDAccount): Future[CurrencyUnit] = {
|
||||
safeDatabase.run(spendingInfoDAO.getConfirmedBalanceAction(Some(account)))
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def listAccounts(): Future[Vector[AccountDb]] =
|
||||
accountDAO.findAll()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.bitcoins.wallet.internal
|
||||
|
||||
import org.apache.pekko.actor.ActorSystem
|
||||
import org.bitcoins.core.api.feeprovider.FeeRateApi
|
||||
import org.bitcoins.core.api.keymanager.BIP39KeyManagerApi
|
||||
import org.bitcoins.core.api.wallet.*
|
||||
import org.bitcoins.core.api.wallet.db.AccountDb
|
||||
|
@ -22,9 +23,11 @@ case class FundTransactionHandling(
|
|||
accountHandling: AccountHandling,
|
||||
utxoHandling: UtxoHandling,
|
||||
addressHandling: AddressHandlingApi,
|
||||
transactionProcessing: TransactionProcessingApi,
|
||||
spendingInfoDAO: SpendingInfoDAO,
|
||||
transactionDAO: TransactionDAO,
|
||||
keyManager: BIP39KeyManagerApi)(implicit
|
||||
keyManager: BIP39KeyManagerApi,
|
||||
feeRateApi: FeeRateApi)(implicit
|
||||
walletConfig: WalletAppConfig,
|
||||
system: ActorSystem)
|
||||
extends FundTransactionHandlingApi
|
||||
|
|
|
@ -0,0 +1,626 @@
|
|||
package org.bitcoins.wallet.internal
|
||||
|
||||
import org.bitcoins.commons.util.BitcoinSLogger
|
||||
import org.bitcoins.core.api.feeprovider.FeeRateApi
|
||||
import org.bitcoins.core.api.keymanager.BIP39KeyManagerApi
|
||||
import org.bitcoins.core.api.wallet.db.AccountDb
|
||||
import org.bitcoins.core.api.wallet.{
|
||||
AccountHandlingApi,
|
||||
AddressHandlingApi,
|
||||
CoinSelectionAlgo,
|
||||
SendFundsHandlingApi,
|
||||
TransactionProcessingApi,
|
||||
UtxoHandlingApi
|
||||
}
|
||||
import org.bitcoins.core.config.NetworkParameters
|
||||
import org.bitcoins.core.currency.{CurrencyUnit, CurrencyUnits, Satoshis}
|
||||
import org.bitcoins.core.hd.HDPath
|
||||
import org.bitcoins.core.number.UInt32
|
||||
import org.bitcoins.core.protocol.BitcoinAddress
|
||||
import org.bitcoins.core.protocol.script.ScriptPubKey
|
||||
import org.bitcoins.core.protocol.transaction.{
|
||||
InputUtil,
|
||||
Transaction,
|
||||
TransactionOutPoint,
|
||||
TransactionOutput,
|
||||
TxUtil
|
||||
}
|
||||
import org.bitcoins.core.psbt.PSBT
|
||||
import org.bitcoins.core.script.constant.ScriptConstant
|
||||
import org.bitcoins.core.script.control.OP_RETURN
|
||||
import org.bitcoins.core.util.{BitcoinScriptUtil, FutureUtil}
|
||||
import org.bitcoins.core.wallet.builder.{
|
||||
AddWitnessDataFinalizer,
|
||||
FundRawTxHelper,
|
||||
RawTxBuilder,
|
||||
RawTxFinalizer,
|
||||
ShuffleFinalizer,
|
||||
ShufflingNonInteractiveFinalizer,
|
||||
StandardNonInteractiveFinalizer,
|
||||
SubtractFeeFromOutputsFinalizer
|
||||
}
|
||||
import org.bitcoins.core.wallet.fee.{
|
||||
FeeUnit,
|
||||
SatoshisPerByte,
|
||||
SatoshisPerKW,
|
||||
SatoshisPerKiloByte,
|
||||
SatoshisPerVirtualByte
|
||||
}
|
||||
import org.bitcoins.core.wallet.utxo.{AddressTag, TxoState}
|
||||
import org.bitcoins.crypto.{CryptoUtil, DoubleSha256DigestBE}
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
import org.bitcoins.wallet.models.{
|
||||
AccountDAO,
|
||||
AddressDAO,
|
||||
SpendingInfoDAO,
|
||||
TransactionDAO,
|
||||
WalletDAOs
|
||||
}
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import scala.util.Random
|
||||
|
||||
case class SendFundsHandlingHandling(
|
||||
accountHandling: AccountHandlingApi,
|
||||
feeRateApi: FeeRateApi,
|
||||
fundTxHandling: FundTransactionHandling,
|
||||
addressHandling: AddressHandlingApi,
|
||||
transactionProcessing: TransactionProcessingApi,
|
||||
utxoHandling: UtxoHandlingApi,
|
||||
keyManager: BIP39KeyManagerApi,
|
||||
walletDAOs: WalletDAOs)(implicit walletConfig: WalletAppConfig)
|
||||
extends SendFundsHandlingApi
|
||||
with BitcoinSLogger {
|
||||
import walletConfig.ec
|
||||
import org.bitcoins.core.currency.currencyUnitNumeric
|
||||
private val networkParameters: NetworkParameters = walletConfig.network
|
||||
private val addressDAO: AddressDAO = walletDAOs.addressDAO
|
||||
private val spendingInfoDAO: SpendingInfoDAO = walletDAOs.utxoDAO
|
||||
private val transactionDAO: TransactionDAO = walletDAOs.transactionDAO
|
||||
private val accountDAO: AccountDAO = walletDAOs.accountDAO
|
||||
|
||||
override def bumpFeeRBF(
|
||||
txId: DoubleSha256DigestBE,
|
||||
newFeeRate: FeeUnit
|
||||
): Future[Transaction] = {
|
||||
for {
|
||||
txDbOpt <- transactionDAO.findByTxId(txId)
|
||||
txDb <- txDbOpt match {
|
||||
case Some(db) => Future.successful(db)
|
||||
case None =>
|
||||
Future.failed(
|
||||
new RuntimeException(s"Unable to find transaction ${txId.hex}")
|
||||
)
|
||||
}
|
||||
tx = txDb.transaction
|
||||
|
||||
_ = require(TxUtil.isRBFEnabled(tx), "Transaction is not signaling RBF")
|
||||
|
||||
outPoints = tx.inputs.map(_.previousOutput).toVector
|
||||
spks = tx.outputs.map(_.scriptPubKey).toVector
|
||||
|
||||
utxos <- spendingInfoDAO.findByOutPoints(outPoints)
|
||||
_ = require(utxos.nonEmpty, "Can only bump fee for our own transaction")
|
||||
_ = require(
|
||||
utxos.size == tx.inputs.size,
|
||||
"Can only bump fee for a transaction we own all the inputs"
|
||||
)
|
||||
|
||||
_ = require(
|
||||
txDb.blockHashOpt.isEmpty,
|
||||
s"Cannot replace a confirmed transaction, ${txDb.blockHashOpt.get.hex}"
|
||||
)
|
||||
|
||||
spendingInfos <- FutureUtil.sequentially(utxos) { utxo =>
|
||||
transactionDAO
|
||||
.findByOutPoint(utxo.outPoint)
|
||||
.map(txDbOpt =>
|
||||
utxo.toUTXOInfo(keyManager = keyManager, txDbOpt.get.transaction))
|
||||
}
|
||||
|
||||
_ = {
|
||||
val inputAmount = utxos.foldLeft(CurrencyUnits.zero)(_ + _.output.value)
|
||||
|
||||
val oldFeeRate = newFeeRate match {
|
||||
case _: SatoshisPerByte =>
|
||||
SatoshisPerByte.calc(inputAmount, tx)
|
||||
case _: SatoshisPerKiloByte =>
|
||||
SatoshisPerKiloByte.calc(inputAmount, tx)
|
||||
case _: SatoshisPerVirtualByte =>
|
||||
SatoshisPerVirtualByte.calc(inputAmount, tx)
|
||||
case _: SatoshisPerKW =>
|
||||
SatoshisPerKW.calc(inputAmount, tx)
|
||||
}
|
||||
|
||||
require(
|
||||
oldFeeRate.currencyUnit < newFeeRate.currencyUnit,
|
||||
s"Cannot bump to a lower fee ${oldFeeRate.currencyUnit} < ${newFeeRate.currencyUnit}"
|
||||
)
|
||||
}
|
||||
|
||||
myAddrs <- addressDAO.findByScriptPubKeys(spks)
|
||||
_ = require(myAddrs.nonEmpty, "Must have an output we own")
|
||||
|
||||
changeSpks = myAddrs.flatMap { db =>
|
||||
if (db.isChange) {
|
||||
Some(db.scriptPubKey)
|
||||
} else None
|
||||
}
|
||||
|
||||
changeSpk =
|
||||
if (changeSpks.nonEmpty) {
|
||||
// Pick a random change spk
|
||||
Random.shuffle(changeSpks).head
|
||||
} else {
|
||||
// If none are explicit change, pick a random one we own
|
||||
Random.shuffle(myAddrs.map(_.scriptPubKey)).head
|
||||
}
|
||||
|
||||
oldOutputs <- spendingInfoDAO.findDbsForTx(txId)
|
||||
// delete old outputs
|
||||
_ <- spendingInfoDAO.deleteAll(oldOutputs)
|
||||
|
||||
sequence = tx.inputs.head.sequence + UInt32.one
|
||||
outputs = tx.outputs.filterNot(_.scriptPubKey == changeSpk)
|
||||
txBuilder = StandardNonInteractiveFinalizer.txBuilderFrom(
|
||||
outputs,
|
||||
spendingInfos,
|
||||
newFeeRate,
|
||||
changeSpk,
|
||||
sequence
|
||||
)
|
||||
|
||||
amount = outputs.foldLeft(CurrencyUnits.zero)(_ + _.value)
|
||||
rawTxHelper = FundRawTxHelper(
|
||||
txBuilder,
|
||||
spendingInfos,
|
||||
newFeeRate,
|
||||
Future.unit
|
||||
)
|
||||
tx <-
|
||||
finishSend(rawTxHelper, amount, newFeeRate, Vector.empty)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def bumpFeeCPFP(
|
||||
txId: DoubleSha256DigestBE,
|
||||
feeRate: FeeUnit
|
||||
): Future[Transaction] = {
|
||||
for {
|
||||
txDbOpt <- transactionDAO.findByTxId(txId)
|
||||
txDb <- txDbOpt match {
|
||||
case Some(db) => Future.successful(db)
|
||||
case None =>
|
||||
Future.failed(
|
||||
new RuntimeException(s"Unable to find transaction ${txId.hex}")
|
||||
)
|
||||
}
|
||||
tx = txDb.transaction
|
||||
|
||||
spendingInfos <- spendingInfoDAO.findTx(tx)
|
||||
_ = require(
|
||||
spendingInfos.nonEmpty,
|
||||
s"Transaction ${txId.hex} must have an output we own"
|
||||
)
|
||||
|
||||
_ = require(
|
||||
txDb.blockHashOpt.isEmpty,
|
||||
s"Cannot replace a confirmed transaction, ${txDb.blockHashOpt.get.hex}"
|
||||
)
|
||||
|
||||
changeSpendingInfos = spendingInfos.flatMap { db =>
|
||||
if (db.isChange) {
|
||||
Some(db)
|
||||
} else None
|
||||
}
|
||||
|
||||
spendingInfo =
|
||||
if (changeSpendingInfos.nonEmpty) {
|
||||
// Pick a random change spendingInfo
|
||||
Random.shuffle(changeSpendingInfos).head
|
||||
} else {
|
||||
// If none are explicit change, pick a random one we own
|
||||
Random.shuffle(spendingInfos).head
|
||||
}
|
||||
|
||||
addr <- addressHandling.getNewChangeAddress()
|
||||
childTx <- sendFromOutPoints(Vector(spendingInfo.outPoint), addr, feeRate)
|
||||
} yield childTx
|
||||
}
|
||||
|
||||
override def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb
|
||||
): Future[Transaction] = {
|
||||
val messageToUse = if (hashMessage) {
|
||||
CryptoUtil.sha256(ByteVector(message.getBytes)).bytes
|
||||
} else {
|
||||
if (message.length > 80) {
|
||||
throw new IllegalArgumentException(
|
||||
s"Message cannot be greater than 80 characters, it should be hashed, got $message"
|
||||
)
|
||||
} else ByteVector(message.getBytes)
|
||||
}
|
||||
|
||||
val asm = Seq(OP_RETURN) ++ BitcoinScriptUtil.calculatePushOp(
|
||||
messageToUse
|
||||
) :+ ScriptConstant(messageToUse)
|
||||
|
||||
val scriptPubKey = ScriptPubKey(asm)
|
||||
|
||||
val output = TransactionOutput(Satoshis.zero, scriptPubKey)
|
||||
|
||||
for {
|
||||
fundRawTxHelper <- fundTxHandling.fundRawTransactionInternal(
|
||||
destinations = Vector(output),
|
||||
feeRate = feeRate,
|
||||
fromAccount = fromAccount,
|
||||
coinSelectionAlgo = CoinSelectionAlgo.RandomSelection,
|
||||
fromTagOpt = None,
|
||||
markAsReserved = true
|
||||
)
|
||||
tx <- finishSend(
|
||||
fundRawTxHelper,
|
||||
CurrencyUnits.zero,
|
||||
feeRate,
|
||||
Vector.empty
|
||||
)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRateOpt: Option[FeeUnit],
|
||||
fromAccount: AccountDb): Future[Transaction] = {
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt)
|
||||
tx <- makeOpReturnCommitment(message, hashMessage, feeRate, fromAccount)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRate: FeeUnit): Future[Transaction] = {
|
||||
for {
|
||||
account <- accountHandling.getDefaultAccount()
|
||||
tx <- makeOpReturnCommitment(message, hashMessage, feeRate, account)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def makeOpReturnCommitment(
|
||||
message: String,
|
||||
hashMessage: Boolean,
|
||||
feeRateOpt: Option[FeeUnit]): Future[Transaction] = {
|
||||
for {
|
||||
feeRate <- determineFeeRate(feeRateOpt)
|
||||
tx <- makeOpReturnCommitment(message, hashMessage, feeRate)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendWithAlgo(
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
algo: CoinSelectionAlgo,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
): 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")
|
||||
val destination = TransactionOutput(amount, address.scriptPubKey)
|
||||
for {
|
||||
rawTxHelper <- fundTxHandling.fundRawTransactionInternal(
|
||||
destinations = Vector(destination),
|
||||
feeRate = feeRate,
|
||||
fromAccount = fromAccount,
|
||||
coinSelectionAlgo = algo,
|
||||
fromTagOpt = None,
|
||||
markAsReserved = true
|
||||
)
|
||||
tx <- finishSend(rawTxHelper, amount, feeRate, newTags)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** Sends money from the specified account
|
||||
*
|
||||
* todo: add error handling to signature
|
||||
*/
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
amount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
): 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"
|
||||
)
|
||||
spentUtxos =
|
||||
utxoDbs.filterNot(utxo => TxoState.receivedStates.contains(utxo.state))
|
||||
_ = require(
|
||||
spentUtxos.isEmpty,
|
||||
s"Some out points given have already been spent, ${spentUtxos.map(_.outPoint)}"
|
||||
)
|
||||
|
||||
prevTxFs = utxoDbs.map(utxo =>
|
||||
transactionDAO.findByOutPoint(utxo.outPoint).map(_.get.transaction))
|
||||
prevTxs <- FutureUtil.collect(prevTxFs)
|
||||
utxos =
|
||||
utxoDbs
|
||||
.zip(prevTxs)
|
||||
.map(info => info._1.toUTXOInfo(keyManager, info._2))
|
||||
|
||||
changeAddr <- accountHandling.getNewChangeAddress(fromAccount.hdAccount)
|
||||
|
||||
output = TransactionOutput(amount, address.scriptPubKey)
|
||||
txBuilder = ShufflingNonInteractiveFinalizer.txBuilderFrom(
|
||||
Vector(output),
|
||||
utxos,
|
||||
feeRate,
|
||||
changeAddr.scriptPubKey
|
||||
)
|
||||
rawTxHelper = FundRawTxHelper(txBuilder, utxos, feeRate, Future.unit)
|
||||
tx <- finishSend(rawTxHelper, amount, feeRate, newTags)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
/** Sends money from the specified account
|
||||
*
|
||||
* todo: add error handling to signature
|
||||
*/
|
||||
override def sendToAddresses(
|
||||
addresses: Vector[BitcoinAddress],
|
||||
amounts: Vector[CurrencyUnit],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
): Future[Transaction] = {
|
||||
require(
|
||||
amounts.size == addresses.size,
|
||||
"Must have an amount for every address"
|
||||
)
|
||||
require(
|
||||
addresses.forall(
|
||||
_.networkParameters.isSameNetworkBytes(networkParameters)
|
||||
),
|
||||
s"Cannot send to address on other network, got ${addresses.map(_.networkParameters)}"
|
||||
)
|
||||
val destinations = addresses.zip(amounts).map { case (address, amount) =>
|
||||
logger.info(s"Sending $amount to $address at feerate $feeRate")
|
||||
TransactionOutput(amount, address.scriptPubKey)
|
||||
}
|
||||
sendToOutputs(destinations, feeRate, fromAccount, newTags)
|
||||
}
|
||||
|
||||
/** Sends money from the specified account
|
||||
*
|
||||
* todo: add error handling to signature
|
||||
*/
|
||||
override def sendToOutputs(
|
||||
outputs: Vector[TransactionOutput],
|
||||
feeRate: FeeUnit,
|
||||
fromAccount: AccountDb,
|
||||
newTags: Vector[AddressTag]
|
||||
): Future[Transaction] = {
|
||||
for {
|
||||
fundRawTxHelper <- fundTxHandling.fundRawTransactionInternal(
|
||||
destinations = outputs,
|
||||
feeRate = feeRate,
|
||||
fromAccount = fromAccount,
|
||||
fromTagOpt = None,
|
||||
markAsReserved = true
|
||||
)
|
||||
sentAmount = outputs.foldLeft(CurrencyUnits.zero)(_ + _.value)
|
||||
tx <- finishSend(fundRawTxHelper, sentAmount, feeRate, newTags)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def sendFromOutPoints(
|
||||
outPoints: Vector[TransactionOutPoint],
|
||||
address: BitcoinAddress,
|
||||
feeRate: FeeUnit
|
||||
): Future[Transaction] = {
|
||||
require(
|
||||
address.networkParameters.isSameNetworkBytes(networkParameters),
|
||||
s"Cannot send to address on other network, got ${address.networkParameters}"
|
||||
)
|
||||
logger.info(s"Sending 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"
|
||||
)
|
||||
spentUtxos =
|
||||
utxoDbs.filterNot(utxo => TxoState.receivedStates.contains(utxo.state))
|
||||
_ = require(
|
||||
spentUtxos.isEmpty,
|
||||
s"Some out points given have already been spent, ${spentUtxos.map(_.outPoint)}"
|
||||
)
|
||||
|
||||
utxos <- Future.sequence {
|
||||
utxoDbs.map(utxo =>
|
||||
transactionDAO
|
||||
.findByOutPoint(utxo.outPoint)
|
||||
.map(txDb => utxo.toUTXOInfo(keyManager, txDb.get.transaction)))
|
||||
}
|
||||
inputInfos = utxos.map(_.inputInfo)
|
||||
|
||||
utxoAmount = utxoDbs.map(_.output.value).sum
|
||||
dummyOutput = TransactionOutput(utxoAmount, address.scriptPubKey)
|
||||
inputs = InputUtil.calcSequenceForInputs(utxos)
|
||||
|
||||
txBuilder = RawTxBuilder() ++= inputs += dummyOutput
|
||||
finalizer = SubtractFeeFromOutputsFinalizer(
|
||||
inputInfos,
|
||||
feeRate,
|
||||
Vector(address.scriptPubKey)
|
||||
)
|
||||
.andThen(ShuffleFinalizer)
|
||||
.andThen(AddWitnessDataFinalizer(inputInfos))
|
||||
|
||||
withFinalizer = txBuilder.setFinalizer(finalizer)
|
||||
|
||||
tmp = withFinalizer.buildTx()
|
||||
|
||||
_ = require(
|
||||
tmp.outputs.size == 1,
|
||||
s"Created tx is not as expected, does not have 1 output, got $tmp"
|
||||
)
|
||||
rawTxHelper = FundRawTxHelper(withFinalizer, utxos, feeRate, Future.unit)
|
||||
tx <- finishSend(
|
||||
rawTxHelper,
|
||||
tmp.outputs.head.value,
|
||||
feeRate,
|
||||
Vector.empty
|
||||
)
|
||||
} yield tx
|
||||
}
|
||||
|
||||
override def signPSBT(
|
||||
psbt: PSBT
|
||||
)(implicit ec: ExecutionContext): Future[PSBT] = {
|
||||
val inputTxIds = psbt.transaction.inputs.zipWithIndex.map {
|
||||
case (input, index) =>
|
||||
input.previousOutput.txIdBE -> index
|
||||
}.toMap
|
||||
for {
|
||||
accountDbs <- accountDAO.findAll()
|
||||
ourXpubs = accountDbs.map(_.xpub)
|
||||
utxos <- spendingInfoDAO.findAll()
|
||||
txs <- transactionDAO.findByTxIds(inputTxIds.keys.toVector)
|
||||
} yield {
|
||||
val updated = txs.foldLeft(psbt) { (accum, tx) =>
|
||||
val index = inputTxIds(tx.txIdBE)
|
||||
accum.addUTXOToInput(tx.transaction, index)
|
||||
}
|
||||
|
||||
val signed =
|
||||
updated.inputMaps.zipWithIndex.foldLeft(updated) {
|
||||
case (unsigned, (input, index)) =>
|
||||
val xpubKeyPaths = input.BIP32DerivationPaths
|
||||
.filter { path =>
|
||||
ourXpubs.exists(_.fingerprint == path.masterFingerprint)
|
||||
}
|
||||
.map(bip32Path =>
|
||||
HDPath.fromString(
|
||||
bip32Path.path.toString
|
||||
)) // TODO add a way to get a HDPath from a BIP32 Path
|
||||
|
||||
val (utxoPath, withData) = {
|
||||
val outPoint = unsigned.transaction.inputs(index).previousOutput
|
||||
utxos.find(_.outpoint == outPoint) match {
|
||||
case Some(utxo) =>
|
||||
val psbtWithUtxoData = utxo.redeemScript match {
|
||||
case Some(redeemScript) =>
|
||||
unsigned.addRedeemOrWitnessScriptToInput(
|
||||
redeemScript,
|
||||
index
|
||||
)
|
||||
case None => unsigned
|
||||
}
|
||||
|
||||
(Vector(utxo.path), psbtWithUtxoData)
|
||||
case None => (Vector.empty, unsigned)
|
||||
}
|
||||
}
|
||||
|
||||
val keyPaths = xpubKeyPaths ++ utxoPath
|
||||
|
||||
keyPaths.foldLeft(withData) { (accum, hdPath) =>
|
||||
val sign = keyManager.toSign(hdPath)
|
||||
// Only sign if that key doesn't have a signature yet
|
||||
if (
|
||||
!input.partialSignatures.exists(
|
||||
_.pubKey.toPublicKey == sign.publicKey
|
||||
)
|
||||
) {
|
||||
logger.debug(
|
||||
s"Signing input $index with key ${sign.publicKey.hex}"
|
||||
)
|
||||
accum.sign(index, sign)
|
||||
} else {
|
||||
accum
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (updated == signed) {
|
||||
logger.warn("Did not find any keys or utxos that belong to this wallet")
|
||||
}
|
||||
signed
|
||||
}
|
||||
}
|
||||
|
||||
/** Takes a [[RawTxBuilderWithFinalizer]] for a transaction to be sent, and
|
||||
* completes it by: finalizing and signing the transaction, then correctly
|
||||
* processing and logging it
|
||||
*/
|
||||
private def finishSend[F <: RawTxFinalizer](
|
||||
rawTxHelper: FundRawTxHelper[F],
|
||||
sentAmount: CurrencyUnit,
|
||||
feeRate: FeeUnit,
|
||||
newTags: Vector[AddressTag]
|
||||
): Future[Transaction] = {
|
||||
val signed = rawTxHelper.signedTx
|
||||
|
||||
val processedTxF = for {
|
||||
ourOuts <- addressHandling.findOurOutputs(signed)
|
||||
creditingAmount = rawTxHelper.scriptSigParams.foldLeft(
|
||||
CurrencyUnits.zero
|
||||
)(_ + _.amount)
|
||||
_ <- transactionProcessing.processOurTransaction(
|
||||
transaction = signed,
|
||||
feeRate = feeRate,
|
||||
inputAmount = creditingAmount,
|
||||
sentAmount = sentAmount,
|
||||
blockHashOpt = None,
|
||||
newTags = newTags
|
||||
)
|
||||
} 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
|
||||
}
|
||||
|
||||
processedTxF.recoverWith { case _ =>
|
||||
// if something fails, we need to unreserve the utxos associated with this tx
|
||||
// and then propogate the failed future upwards
|
||||
utxoHandling.unmarkUTXOsAsReserved(signed).flatMap(_ => processedTxF)
|
||||
}
|
||||
}
|
||||
|
||||
private def determineFeeRate(feeRateOpt: Option[FeeUnit]): Future[FeeUnit] =
|
||||
feeRateOpt match {
|
||||
case None =>
|
||||
feeRateApi.getFeeRate()
|
||||
case Some(feeRate) =>
|
||||
Future.successful(feeRate)
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ import org.bitcoins.crypto.DoubleSha256DigestBE
|
|||
import org.bitcoins.db.SafeDatabase
|
||||
import org.bitcoins.wallet.callback.WalletCallbacks
|
||||
import org.bitcoins.wallet.config.WalletAppConfig
|
||||
import org.bitcoins.wallet.models.{SpendingInfoDAO, TransactionDAO}
|
||||
import org.bitcoins.wallet.models.{AddressDAO, SpendingInfoDAO, TransactionDAO}
|
||||
import slick.dbio.{DBIOAction, Effect, NoStream}
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
@ -30,6 +30,7 @@ import scala.concurrent.Future
|
|||
case class UtxoHandling(
|
||||
spendingInfoDAO: SpendingInfoDAO,
|
||||
transactionDAO: TransactionDAO,
|
||||
addressDAO: AddressDAO,
|
||||
chainQueryApi: ChainQueryApi)(implicit
|
||||
walletConfig: WalletAppConfig,
|
||||
system: ActorSystem)
|
||||
|
@ -40,6 +41,30 @@ case class UtxoHandling(
|
|||
private val walletCallbacks: WalletCallbacks = walletConfig.callBacks
|
||||
private val safeDatabase: SafeDatabase = spendingInfoDAO.safeDatabase
|
||||
|
||||
override def clearAllUtxos(): Future[Unit] = {
|
||||
val aggregatedActions
|
||||
: DBIOAction[Unit, NoStream, Effect.Write with Effect.Transactional] =
|
||||
spendingInfoDAO.deleteAllAction().map(_ => ())
|
||||
|
||||
val resultedF = safeDatabase.run(aggregatedActions)
|
||||
resultedF.failed.foreach(err =>
|
||||
logger.error(
|
||||
s"Failed to clear utxos, addresses and scripts from the database",
|
||||
err
|
||||
))
|
||||
|
||||
resultedF.map(_ => ())
|
||||
}
|
||||
|
||||
override def clearAllAddresses(): Future[Unit] = {
|
||||
val action = addressDAO
|
||||
.deleteAllAction()
|
||||
.map(_ => ())
|
||||
safeDatabase
|
||||
.run(action)
|
||||
.map(_ => ())
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
override def listUtxos(): Future[Vector[SpendingInfoDb]] = {
|
||||
spendingInfoDAO.findAllUnspent()
|
||||
|
|
|
@ -44,7 +44,7 @@ trait WalletSync extends BitcoinSLogger {
|
|||
blocksToSync <- blocksToSyncF
|
||||
syncedWallet <- FutureUtil.foldLeftAsync(wallet, blocksToSync) {
|
||||
case (wallet, nextBlock) =>
|
||||
wallet
|
||||
wallet.transactionProcessing
|
||||
.processBlock(nextBlock)
|
||||
.map(_ => wallet)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue