mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-03-03 10:46:42 +01:00
Add an ability to set custom payout and change addresses (#4101)
* Add an ability to set custom payout and change addresses * config changes * formatting * respond to the comments
This commit is contained in:
parent
5b1b1ee149
commit
5777ec1c31
19 changed files with 596 additions and 297 deletions
|
@ -952,18 +952,26 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
||||||
)
|
)
|
||||||
|
|
||||||
"create a dlc offer" in {
|
"create a dlc offer" in {
|
||||||
(mockWalletApi
|
(
|
||||||
.createDLCOffer(_: ContractInfoTLV,
|
mockWalletApi
|
||||||
_: Satoshis,
|
.createDLCOffer(
|
||||||
_: Option[SatoshisPerVirtualByte],
|
_: ContractInfoTLV,
|
||||||
_: UInt32,
|
_: Satoshis,
|
||||||
_: UInt32))
|
_: Option[SatoshisPerVirtualByte],
|
||||||
|
_: UInt32,
|
||||||
|
_: UInt32,
|
||||||
|
_: Option[BitcoinAddress],
|
||||||
|
_: Option[BitcoinAddress]
|
||||||
|
)
|
||||||
|
)
|
||||||
.expects(
|
.expects(
|
||||||
contractInfoTLV,
|
contractInfoTLV,
|
||||||
Satoshis(2500),
|
Satoshis(2500),
|
||||||
Some(SatoshisPerVirtualByte(Satoshis.one)),
|
Some(SatoshisPerVirtualByte(Satoshis.one)),
|
||||||
UInt32(contractMaturity),
|
UInt32(contractMaturity),
|
||||||
UInt32(contractTimeout)
|
UInt32(contractTimeout),
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
.returning(Future.successful(offer))
|
.returning(Future.successful(offer))
|
||||||
|
|
||||||
|
@ -1021,8 +1029,10 @@ class RoutesSpec extends AnyWordSpec with ScalatestRouteTest with MockFactory {
|
||||||
|
|
||||||
"accept a dlc offer" in {
|
"accept a dlc offer" in {
|
||||||
(mockWalletApi
|
(mockWalletApi
|
||||||
.acceptDLCOffer(_: DLCOfferTLV))
|
.acceptDLCOffer(_: DLCOfferTLV,
|
||||||
.expects(offer.toTLV)
|
_: Option[BitcoinAddress],
|
||||||
|
_: Option[BitcoinAddress]))
|
||||||
|
.expects(offer.toTLV, None, None)
|
||||||
.returning(Future.successful(accept))
|
.returning(Future.successful(accept))
|
||||||
|
|
||||||
val route = walletRoutes.handleCommand(
|
val route = walletRoutes.handleCommand(
|
||||||
|
|
|
@ -2,7 +2,7 @@ package org.bitcoins.server
|
||||||
|
|
||||||
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.LockUnspentOutputParameter
|
import org.bitcoins.commons.jsonmodels.bitcoind.RpcOpts.LockUnspentOutputParameter
|
||||||
import org.bitcoins.commons.jsonmodels.cli.ContractDescriptorParser
|
import org.bitcoins.commons.jsonmodels.cli.ContractDescriptorParser
|
||||||
import org.bitcoins.commons.serializers.{JsonReaders}
|
import org.bitcoins.commons.serializers.JsonReaders
|
||||||
import org.bitcoins.core.api.wallet.CoinSelectionAlgo
|
import org.bitcoins.core.api.wallet.CoinSelectionAlgo
|
||||||
import org.bitcoins.core.crypto._
|
import org.bitcoins.core.crypto._
|
||||||
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
|
import org.bitcoins.core.currency.{Bitcoins, Satoshis}
|
||||||
|
@ -655,26 +655,67 @@ case class CreateDLCOffer(
|
||||||
collateral: Satoshis,
|
collateral: Satoshis,
|
||||||
feeRateOpt: Option[SatoshisPerVirtualByte],
|
feeRateOpt: Option[SatoshisPerVirtualByte],
|
||||||
locktime: UInt32,
|
locktime: UInt32,
|
||||||
refundLocktime: UInt32)
|
refundLocktime: UInt32,
|
||||||
|
externalPayoutAddressOpt: Option[BitcoinAddress],
|
||||||
|
externalChangeAddressOpt: Option[BitcoinAddress])
|
||||||
|
|
||||||
object CreateDLCOffer extends ServerJsonModels {
|
object CreateDLCOffer extends ServerJsonModels {
|
||||||
|
|
||||||
def fromJsArr(jsArr: ujson.Arr): Try[CreateDLCOffer] = {
|
def fromJsArr(jsArr: ujson.Arr): Try[CreateDLCOffer] = {
|
||||||
|
|
||||||
|
def parseParameters(
|
||||||
|
contractInfoJs: Value,
|
||||||
|
collateralJs: Value,
|
||||||
|
feeRateOptJs: Value,
|
||||||
|
locktimeJs: Value,
|
||||||
|
refundLTJs: Value,
|
||||||
|
payoutAddressJs: Value,
|
||||||
|
changeAddressJs: Value) = Try {
|
||||||
|
val contractInfoTLV = jsToContractInfoTLV(contractInfoJs)
|
||||||
|
val collateral = jsToSatoshis(collateralJs)
|
||||||
|
val feeRate = jsToSatoshisPerVirtualByteOpt(feeRateOptJs)
|
||||||
|
val locktime = jsToUInt32(locktimeJs)
|
||||||
|
val refundLT = jsToUInt32(refundLTJs)
|
||||||
|
val payoutAddressJsOpt = nullToOpt(payoutAddressJs)
|
||||||
|
val payoutAddressOpt =
|
||||||
|
payoutAddressJsOpt.map(js => jsToBitcoinAddress(js))
|
||||||
|
val changeAddressJsOpt = nullToOpt(changeAddressJs)
|
||||||
|
val changeAddressOpt =
|
||||||
|
changeAddressJsOpt.map(js => jsToBitcoinAddress(js))
|
||||||
|
CreateDLCOffer(contractInfoTLV,
|
||||||
|
collateral,
|
||||||
|
feeRate,
|
||||||
|
locktime,
|
||||||
|
refundLT,
|
||||||
|
payoutAddressOpt,
|
||||||
|
changeAddressOpt)
|
||||||
|
}
|
||||||
|
|
||||||
jsArr.arr.toList match {
|
jsArr.arr.toList match {
|
||||||
case contractInfoJs :: collateralJs :: feeRateOptJs :: locktimeJs :: refundLTJs :: Nil =>
|
case contractInfoJs :: collateralJs :: feeRateOptJs :: locktimeJs :: refundLTJs :: Nil =>
|
||||||
Try {
|
parseParameters(contractInfoJs,
|
||||||
val contractInfoTLV = jsToContractInfoTLV(contractInfoJs)
|
collateralJs,
|
||||||
val collateral = jsToSatoshis(collateralJs)
|
feeRateOptJs,
|
||||||
val feeRate = jsToSatoshisPerVirtualByteOpt(feeRateOptJs)
|
locktimeJs,
|
||||||
val locktime = jsToUInt32(locktimeJs)
|
refundLTJs,
|
||||||
val refundLT = jsToUInt32(refundLTJs)
|
Null,
|
||||||
CreateDLCOffer(contractInfoTLV,
|
Null)
|
||||||
collateral,
|
case contractInfoJs :: collateralJs :: feeRateOptJs :: locktimeJs :: refundLTJs :: payoutAddressJs :: Nil =>
|
||||||
feeRate,
|
parseParameters(contractInfoJs,
|
||||||
locktime,
|
collateralJs,
|
||||||
refundLT)
|
feeRateOptJs,
|
||||||
}
|
locktimeJs,
|
||||||
|
refundLTJs,
|
||||||
|
payoutAddressJs,
|
||||||
|
Null)
|
||||||
|
case contractInfoJs :: collateralJs :: feeRateOptJs :: locktimeJs :: refundLTJs :: payoutAddressJs :: changeAddressJs :: Nil =>
|
||||||
|
parseParameters(contractInfoJs,
|
||||||
|
collateralJs,
|
||||||
|
feeRateOptJs,
|
||||||
|
locktimeJs,
|
||||||
|
refundLTJs,
|
||||||
|
payoutAddressJs,
|
||||||
|
changeAddressJs)
|
||||||
case other =>
|
case other =>
|
||||||
Failure(
|
Failure(
|
||||||
new IllegalArgumentException(
|
new IllegalArgumentException(
|
||||||
|
@ -839,17 +880,35 @@ object DecodeAttestations extends ServerJsonModels {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case class AcceptDLCOffer(offer: LnMessage[DLCOfferTLV])
|
case class AcceptDLCOffer(
|
||||||
|
offer: LnMessage[DLCOfferTLV],
|
||||||
|
externalPayoutAddressOpt: Option[BitcoinAddress],
|
||||||
|
externalChangeAddressOpt: Option[BitcoinAddress])
|
||||||
|
|
||||||
object AcceptDLCOffer extends ServerJsonModels {
|
object AcceptDLCOffer extends ServerJsonModels {
|
||||||
|
|
||||||
def fromJsArr(jsArr: ujson.Arr): Try[AcceptDLCOffer] = {
|
def fromJsArr(jsArr: ujson.Arr): Try[AcceptDLCOffer] = {
|
||||||
|
def parseParameters(
|
||||||
|
offerJs: Value,
|
||||||
|
payoutAddressJs: Value,
|
||||||
|
changeAddressJs: Value) = Try {
|
||||||
|
val offer = LnMessageFactory(DLCOfferTLV).fromHex(offerJs.str)
|
||||||
|
val payoutAddressJsOpt = nullToOpt(payoutAddressJs)
|
||||||
|
val payoutAddressOpt =
|
||||||
|
payoutAddressJsOpt.map(js => jsToBitcoinAddress(js))
|
||||||
|
val changeAddressJsOpt = nullToOpt(changeAddressJs)
|
||||||
|
val changeAddressOpt =
|
||||||
|
changeAddressJsOpt.map(js => jsToBitcoinAddress(js))
|
||||||
|
AcceptDLCOffer(offer, payoutAddressOpt, changeAddressOpt)
|
||||||
|
}
|
||||||
|
|
||||||
jsArr.arr.toList match {
|
jsArr.arr.toList match {
|
||||||
case offerJs :: Nil =>
|
case offerJs :: Nil =>
|
||||||
Try {
|
parseParameters(offerJs, Null, Null)
|
||||||
val offer = LnMessageFactory(DLCOfferTLV).fromHex(offerJs.str)
|
case offerJs :: payoutAddressJs :: Nil =>
|
||||||
AcceptDLCOffer(offer)
|
parseParameters(offerJs, payoutAddressJs, Null)
|
||||||
}
|
case offerJs :: payoutAddressJs :: changeAddressJs :: Nil =>
|
||||||
|
parseParameters(offerJs, payoutAddressJs, changeAddressJs)
|
||||||
case Nil =>
|
case Nil =>
|
||||||
Failure(new IllegalArgumentException("Missing offer argument"))
|
Failure(new IllegalArgumentException("Missing offer argument"))
|
||||||
|
|
||||||
|
@ -930,24 +989,42 @@ object AddDLCSigs extends ServerJsonModels {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case class DLCDataFromFile(path: Path, destinationOpt: Option[Path])
|
case class DLCDataFromFile(
|
||||||
|
path: Path,
|
||||||
|
destinationOpt: Option[Path],
|
||||||
|
externalPayoutAddressOpt: Option[BitcoinAddress],
|
||||||
|
externalChangeAddressOpt: Option[BitcoinAddress])
|
||||||
|
|
||||||
object DLCDataFromFile extends ServerJsonModels {
|
object DLCDataFromFile extends ServerJsonModels {
|
||||||
|
|
||||||
def fromJsArr(jsArr: ujson.Arr): Try[DLCDataFromFile] = {
|
def fromJsArr(jsArr: ujson.Arr): Try[DLCDataFromFile] = {
|
||||||
|
def parseParameters(
|
||||||
|
pathJs: Value,
|
||||||
|
destJs: Value,
|
||||||
|
payoutAddressJs: Value,
|
||||||
|
changeAddressJs: Value) = Try {
|
||||||
|
val path = new File(pathJs.str).toPath
|
||||||
|
val destJsOpt = nullToOpt(destJs)
|
||||||
|
val destOpt = destJsOpt.map(js => new File(js.str).toPath)
|
||||||
|
val payoutAddressJsOpt = nullToOpt(payoutAddressJs)
|
||||||
|
val payoutAddressOpt =
|
||||||
|
payoutAddressJsOpt.map(js => jsToBitcoinAddress(js))
|
||||||
|
val changeAddressJsOpt = nullToOpt(changeAddressJs)
|
||||||
|
val changeAddressOpt =
|
||||||
|
changeAddressJsOpt.map(js => jsToBitcoinAddress(js))
|
||||||
|
|
||||||
|
DLCDataFromFile(path, destOpt, payoutAddressOpt, changeAddressOpt)
|
||||||
|
}
|
||||||
|
|
||||||
jsArr.arr.toList match {
|
jsArr.arr.toList match {
|
||||||
case pathJs :: Nil =>
|
case pathJs :: Nil =>
|
||||||
Try {
|
parseParameters(pathJs, Null, Null, Null)
|
||||||
val path = new File(pathJs.str).toPath
|
|
||||||
DLCDataFromFile(path, None)
|
|
||||||
}
|
|
||||||
case pathJs :: destJs :: Nil =>
|
case pathJs :: destJs :: Nil =>
|
||||||
Try {
|
parseParameters(pathJs, destJs, Null, Null)
|
||||||
val path = new File(pathJs.str).toPath
|
case pathJs :: destJs :: payoutAddressJs :: Nil =>
|
||||||
val destJsOpt = nullToOpt(destJs)
|
parseParameters(pathJs, destJs, payoutAddressJs, Null)
|
||||||
val destOpt = destJsOpt.map(js => new File(js.str).toPath)
|
case pathJs :: destJs :: payoutAddressJs :: changeAddressJs :: Nil =>
|
||||||
DLCDataFromFile(path, destOpt)
|
parseParameters(pathJs, destJs, payoutAddressJs, changeAddressJs)
|
||||||
}
|
|
||||||
case Nil =>
|
case Nil =>
|
||||||
Failure(new IllegalArgumentException("Missing path argument"))
|
Failure(new IllegalArgumentException("Missing path argument"))
|
||||||
case other =>
|
case other =>
|
||||||
|
|
|
@ -6,14 +6,14 @@ import akka.http.scaladsl.server._
|
||||||
import akka.stream.Materializer
|
import akka.stream.Materializer
|
||||||
import grizzled.slf4j.Logging
|
import grizzled.slf4j.Logging
|
||||||
import org.bitcoins.commons.serializers.Picklers._
|
import org.bitcoins.commons.serializers.Picklers._
|
||||||
|
import org.bitcoins.core.api.dlc.wallet.AnyDLCHDWalletApi
|
||||||
import org.bitcoins.core.api.wallet.db.SpendingInfoDb
|
import org.bitcoins.core.api.wallet.db.SpendingInfoDb
|
||||||
import org.bitcoins.core.currency._
|
import org.bitcoins.core.currency._
|
||||||
import org.bitcoins.core.protocol.tlv._
|
import org.bitcoins.core.protocol.tlv._
|
||||||
import org.bitcoins.core.protocol.transaction.Transaction
|
import org.bitcoins.core.protocol.transaction.Transaction
|
||||||
|
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
||||||
import org.bitcoins.core.wallet.utxo.{AddressLabelTagType, TxoState}
|
import org.bitcoins.core.wallet.utxo.{AddressLabelTagType, TxoState}
|
||||||
import org.bitcoins.crypto.NetworkElement
|
import org.bitcoins.crypto.NetworkElement
|
||||||
import org.bitcoins.core.api.dlc.wallet.AnyDLCHDWalletApi
|
|
||||||
import org.bitcoins.core.wallet.fee.SatoshisPerVirtualByte
|
|
||||||
import org.bitcoins.keymanager._
|
import org.bitcoins.keymanager._
|
||||||
import org.bitcoins.keymanager.config.KeyManagerAppConfig
|
import org.bitcoins.keymanager.config.KeyManagerAppConfig
|
||||||
import org.bitcoins.server.routes.{Server, ServerCommand, ServerRoute}
|
import org.bitcoins.server.routes.{Server, ServerCommand, ServerRoute}
|
||||||
|
@ -298,7 +298,9 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
|
||||||
collateral,
|
collateral,
|
||||||
feeRateOpt,
|
feeRateOpt,
|
||||||
locktime,
|
locktime,
|
||||||
refundLT)) =>
|
refundLT,
|
||||||
|
payoutAddressOpt,
|
||||||
|
changeAddressOpt)) =>
|
||||||
complete {
|
complete {
|
||||||
val announcements = contractInfo.oracleInfo match {
|
val announcements = contractInfo.oracleInfo match {
|
||||||
case OracleInfoV0TLV(announcement) => Vector(announcement)
|
case OracleInfoV0TLV(announcement) => Vector(announcement)
|
||||||
|
@ -316,7 +318,9 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
|
||||||
collateral,
|
collateral,
|
||||||
feeRateOpt,
|
feeRateOpt,
|
||||||
locktime,
|
locktime,
|
||||||
refundLT)
|
refundLT,
|
||||||
|
payoutAddressOpt,
|
||||||
|
changeAddressOpt)
|
||||||
.map { offer =>
|
.map { offer =>
|
||||||
Server.httpSuccess(offer.toMessage.hex)
|
Server.httpSuccess(offer.toMessage.hex)
|
||||||
}
|
}
|
||||||
|
@ -327,10 +331,11 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
|
||||||
AcceptDLCOffer.fromJsArr(arr) match {
|
AcceptDLCOffer.fromJsArr(arr) match {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
complete(Server.httpBadRequest(exception))
|
complete(Server.httpBadRequest(exception))
|
||||||
case Success(AcceptDLCOffer(offer)) =>
|
case Success(
|
||||||
|
AcceptDLCOffer(offer, payoutAddressOpt, changeAddressOpt)) =>
|
||||||
complete {
|
complete {
|
||||||
wallet
|
wallet
|
||||||
.acceptDLCOffer(offer.tlv)
|
.acceptDLCOffer(offer.tlv, payoutAddressOpt, changeAddressOpt)
|
||||||
.map { accept =>
|
.map { accept =>
|
||||||
Server.httpSuccess(accept.toMessage.hex)
|
Server.httpSuccess(accept.toMessage.hex)
|
||||||
}
|
}
|
||||||
|
@ -341,7 +346,11 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
|
||||||
DLCDataFromFile.fromJsArr(arr) match {
|
DLCDataFromFile.fromJsArr(arr) match {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
complete(Server.httpBadRequest(exception))
|
complete(Server.httpBadRequest(exception))
|
||||||
case Success(DLCDataFromFile(path, destOpt)) =>
|
case Success(
|
||||||
|
DLCDataFromFile(path,
|
||||||
|
destOpt,
|
||||||
|
payoutAddressOpt,
|
||||||
|
changeAddressOpt)) =>
|
||||||
complete {
|
complete {
|
||||||
|
|
||||||
val hex = Files.readAllLines(path).get(0)
|
val hex = Files.readAllLines(path).get(0)
|
||||||
|
@ -349,7 +358,9 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
|
||||||
val offerMessage = LnMessageFactory(DLCOfferTLV).fromHex(hex)
|
val offerMessage = LnMessageFactory(DLCOfferTLV).fromHex(hex)
|
||||||
|
|
||||||
wallet
|
wallet
|
||||||
.acceptDLCOffer(offerMessage.tlv)
|
.acceptDLCOffer(offerMessage.tlv,
|
||||||
|
payoutAddressOpt,
|
||||||
|
changeAddressOpt)
|
||||||
.map { accept =>
|
.map { accept =>
|
||||||
val ret = handleDestinationOpt(accept.toMessage.hex, destOpt)
|
val ret = handleDestinationOpt(accept.toMessage.hex, destOpt)
|
||||||
Server.httpSuccess(ret)
|
Server.httpSuccess(ret)
|
||||||
|
@ -375,7 +386,7 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
|
||||||
DLCDataFromFile.fromJsArr(arr) match {
|
DLCDataFromFile.fromJsArr(arr) match {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
complete(Server.httpBadRequest(exception))
|
complete(Server.httpBadRequest(exception))
|
||||||
case Success(DLCDataFromFile(path, destOpt)) =>
|
case Success(DLCDataFromFile(path, destOpt, _, _)) =>
|
||||||
complete {
|
complete {
|
||||||
|
|
||||||
val hex = Files.readAllLines(path).get(0)
|
val hex = Files.readAllLines(path).get(0)
|
||||||
|
@ -408,7 +419,7 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
|
||||||
DLCDataFromFile.fromJsArr(arr) match {
|
DLCDataFromFile.fromJsArr(arr) match {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
complete(Server.httpBadRequest(exception))
|
complete(Server.httpBadRequest(exception))
|
||||||
case Success(DLCDataFromFile(path, _)) =>
|
case Success(DLCDataFromFile(path, _, _, _)) =>
|
||||||
complete {
|
complete {
|
||||||
|
|
||||||
val hex = Files.readAllLines(path).get(0)
|
val hex = Files.readAllLines(path).get(0)
|
||||||
|
@ -439,7 +450,7 @@ case class WalletRoutes(wallet: AnyDLCHDWalletApi)(implicit
|
||||||
DLCDataFromFile.fromJsArr(arr) match {
|
DLCDataFromFile.fromJsArr(arr) match {
|
||||||
case Failure(exception) =>
|
case Failure(exception) =>
|
||||||
complete(Server.httpBadRequest(exception))
|
complete(Server.httpBadRequest(exception))
|
||||||
case Success(DLCDataFromFile(path, _)) =>
|
case Success(DLCDataFromFile(path, _, _, _)) =>
|
||||||
val hex = Files.readAllLines(path).get(0)
|
val hex = Files.readAllLines(path).get(0)
|
||||||
|
|
||||||
val signMessage = LnMessageFactory(DLCSignTLV).fromHex(hex)
|
val signMessage = LnMessageFactory(DLCSignTLV).fromHex(hex)
|
||||||
|
|
|
@ -32,5 +32,8 @@ bitcoin-s.dlcnode.proxy.socks5 = ${?BITCOIN_S_DLCNODE_PROXY_SOCKS5}
|
||||||
bitcoin-s.dlcnode.tor.enabled = ${?BITCOIN_S_DLCNODE_TOR_ENABLED}
|
bitcoin-s.dlcnode.tor.enabled = ${?BITCOIN_S_DLCNODE_TOR_ENABLED}
|
||||||
bitcoin-s.dlcnode.external-ip = ${?BITCOIN_S_DLCNODE_EXTERNAL_IP}
|
bitcoin-s.dlcnode.external-ip = ${?BITCOIN_S_DLCNODE_EXTERNAL_IP}
|
||||||
|
|
||||||
|
bitcoin-s.wallet.allowExternalDLCAddresses = false
|
||||||
|
bitcoin-s.wallet.allowExternalDLCAddresses = ${?BITCOIN_S_ALLOW_EXT_DLC_ADDRESSES}
|
||||||
|
|
||||||
bitcoin-s.tor.enabled = ${?BITCOIN_S_TOR_ENABLED}
|
bitcoin-s.tor.enabled = ${?BITCOIN_S_TOR_ENABLED}
|
||||||
bitcoin-s.tor.provided = ${?BITCOIN_S_TOR_PROVIDED}
|
bitcoin-s.tor.provided = ${?BITCOIN_S_TOR_PROVIDED}
|
|
@ -5,6 +5,7 @@ import org.bitcoins.core.api.wallet._
|
||||||
import org.bitcoins.core.currency.Satoshis
|
import org.bitcoins.core.currency.Satoshis
|
||||||
import org.bitcoins.core.dlc.accounting._
|
import org.bitcoins.core.dlc.accounting._
|
||||||
import org.bitcoins.core.number.UInt32
|
import org.bitcoins.core.number.UInt32
|
||||||
|
import org.bitcoins.core.protocol.BitcoinAddress
|
||||||
import org.bitcoins.core.protocol.dlc.models.DLCMessage._
|
import org.bitcoins.core.protocol.dlc.models.DLCMessage._
|
||||||
import org.bitcoins.core.protocol.dlc.models._
|
import org.bitcoins.core.protocol.dlc.models._
|
||||||
import org.bitcoins.core.protocol.tlv._
|
import org.bitcoins.core.protocol.tlv._
|
||||||
|
@ -22,9 +23,17 @@ trait DLCWalletApi { self: WalletApi =>
|
||||||
collateral: Satoshis,
|
collateral: Satoshis,
|
||||||
feeRateOpt: Option[SatoshisPerVirtualByte],
|
feeRateOpt: Option[SatoshisPerVirtualByte],
|
||||||
locktime: UInt32,
|
locktime: UInt32,
|
||||||
refundLT: UInt32): Future[DLCOffer] = {
|
refundLT: UInt32,
|
||||||
|
externalPayoutAddressOpt: Option[BitcoinAddress],
|
||||||
|
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCOffer] = {
|
||||||
val contractInfo = ContractInfo.fromTLV(contractInfoTLV)
|
val contractInfo = ContractInfo.fromTLV(contractInfoTLV)
|
||||||
createDLCOffer(contractInfo, collateral, feeRateOpt, locktime, refundLT)
|
createDLCOffer(contractInfo,
|
||||||
|
collateral,
|
||||||
|
feeRateOpt,
|
||||||
|
locktime,
|
||||||
|
refundLT,
|
||||||
|
externalPayoutAddressOpt,
|
||||||
|
externalChangeAddressOpt)
|
||||||
}
|
}
|
||||||
|
|
||||||
def createDLCOffer(
|
def createDLCOffer(
|
||||||
|
@ -32,7 +41,9 @@ trait DLCWalletApi { self: WalletApi =>
|
||||||
collateral: Satoshis,
|
collateral: Satoshis,
|
||||||
feeRateOpt: Option[SatoshisPerVirtualByte],
|
feeRateOpt: Option[SatoshisPerVirtualByte],
|
||||||
locktime: UInt32,
|
locktime: UInt32,
|
||||||
refundLT: UInt32): Future[DLCOffer]
|
refundLT: UInt32,
|
||||||
|
externalPayoutAddressOpt: Option[BitcoinAddress],
|
||||||
|
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCOffer]
|
||||||
|
|
||||||
def registerDLCOffer(dlcOffer: DLCOffer): Future[DLCOffer] = {
|
def registerDLCOffer(dlcOffer: DLCOffer): Future[DLCOffer] = {
|
||||||
createDLCOffer(
|
createDLCOffer(
|
||||||
|
@ -40,15 +51,25 @@ trait DLCWalletApi { self: WalletApi =>
|
||||||
dlcOffer.totalCollateral,
|
dlcOffer.totalCollateral,
|
||||||
Some(dlcOffer.feeRate),
|
Some(dlcOffer.feeRate),
|
||||||
dlcOffer.timeouts.contractMaturity.toUInt32,
|
dlcOffer.timeouts.contractMaturity.toUInt32,
|
||||||
dlcOffer.timeouts.contractTimeout.toUInt32
|
dlcOffer.timeouts.contractTimeout.toUInt32,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
def acceptDLCOffer(dlcOfferTLV: DLCOfferTLV): Future[DLCAccept] = {
|
def acceptDLCOffer(
|
||||||
acceptDLCOffer(DLCOffer.fromTLV(dlcOfferTLV))
|
dlcOfferTLV: DLCOfferTLV,
|
||||||
|
externalPayoutAddressOpt: Option[BitcoinAddress],
|
||||||
|
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCAccept] = {
|
||||||
|
acceptDLCOffer(DLCOffer.fromTLV(dlcOfferTLV),
|
||||||
|
externalPayoutAddressOpt,
|
||||||
|
externalChangeAddressOpt)
|
||||||
}
|
}
|
||||||
|
|
||||||
def acceptDLCOffer(dlcOffer: DLCOffer): Future[DLCAccept]
|
def acceptDLCOffer(
|
||||||
|
dlcOffer: DLCOffer,
|
||||||
|
externalPayoutAddressOpt: Option[BitcoinAddress],
|
||||||
|
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCAccept]
|
||||||
|
|
||||||
def signDLC(acceptTLV: DLCAcceptTLV): Future[DLCSign]
|
def signDLC(acceptTLV: DLCAcceptTLV): Future[DLCSign]
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.bitcoins.core.crypto.ExtPublicKey
|
||||||
import org.bitcoins.core.hd.{BIP32Path, HDChainType}
|
import org.bitcoins.core.hd.{BIP32Path, HDChainType}
|
||||||
import org.bitcoins.core.number.UInt16
|
import org.bitcoins.core.number.UInt16
|
||||||
import org.bitcoins.core.policy.Policy
|
import org.bitcoins.core.policy.Policy
|
||||||
|
import org.bitcoins.core.protocol.BitcoinAddress
|
||||||
import org.bitcoins.core.protocol.dlc.build.DLCTxBuilder
|
import org.bitcoins.core.protocol.dlc.build.DLCTxBuilder
|
||||||
import org.bitcoins.core.protocol.dlc.models.DLCMessage.{
|
import org.bitcoins.core.protocol.dlc.models.DLCMessage.{
|
||||||
DLCAccept,
|
DLCAccept,
|
||||||
|
@ -324,7 +325,8 @@ object DLCUtil {
|
||||||
xpub: ExtPublicKey,
|
xpub: ExtPublicKey,
|
||||||
chainType: HDChainType,
|
chainType: HDChainType,
|
||||||
keyIndex: Int,
|
keyIndex: Int,
|
||||||
networkParameters: NetworkParameters): DLCPublicKeys = {
|
networkParameters: NetworkParameters,
|
||||||
|
externalPayoutAddressOpt: Option[BitcoinAddress]): DLCPublicKeys = {
|
||||||
val chainIndex = chainType.index
|
val chainIndex = chainType.index
|
||||||
val fundingKey =
|
val fundingKey =
|
||||||
xpub
|
xpub
|
||||||
|
@ -332,16 +334,20 @@ object DLCUtil {
|
||||||
.get
|
.get
|
||||||
.key
|
.key
|
||||||
|
|
||||||
val payoutKey =
|
|
||||||
xpub
|
|
||||||
.deriveChildPubKey(
|
|
||||||
BIP32Path.fromString(s"m/$chainIndex/${keyIndex + 1}"))
|
|
||||||
.get
|
|
||||||
.key
|
|
||||||
|
|
||||||
networkParameters match {
|
networkParameters match {
|
||||||
case bitcoinNetwork: BitcoinNetwork =>
|
case bitcoinNetwork: BitcoinNetwork =>
|
||||||
DLCPublicKeys.fromPubKeys(fundingKey, payoutKey, bitcoinNetwork)
|
externalPayoutAddressOpt match {
|
||||||
|
case None =>
|
||||||
|
val payoutKey =
|
||||||
|
xpub
|
||||||
|
.deriveChildPubKey(
|
||||||
|
BIP32Path.fromString(s"m/$chainIndex/${keyIndex + 1}"))
|
||||||
|
.get
|
||||||
|
.key
|
||||||
|
DLCPublicKeys.fromPubKeys(fundingKey, payoutKey, bitcoinNetwork)
|
||||||
|
case Some(externalPayoutAddress) =>
|
||||||
|
DLCPublicKeys(fundingKey, externalPayoutAddress)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,6 +134,10 @@ bitcoin-s {
|
||||||
# before we timeout
|
# before we timeout
|
||||||
addressQueueTimeout = 5 seconds
|
addressQueueTimeout = 5 seconds
|
||||||
|
|
||||||
|
# Allow external payout and change addresses in DLCs
|
||||||
|
# By default all DLC addresses are generated by the wallet itself
|
||||||
|
allowExternalDLCAddresses = false
|
||||||
|
|
||||||
# this config key is read by Slick
|
# this config key is read by Slick
|
||||||
db {
|
db {
|
||||||
name = walletdb
|
name = walletdb
|
||||||
|
|
|
@ -57,8 +57,10 @@ class DLCNegotiationTest extends BitcoinSDualWalletTest {
|
||||||
half,
|
half,
|
||||||
Some(SatoshisPerVirtualByte.one),
|
Some(SatoshisPerVirtualByte.one),
|
||||||
UInt32.zero,
|
UInt32.zero,
|
||||||
UInt32.one)
|
UInt32.one,
|
||||||
accept <- walletA.acceptDLCOffer(offer)
|
None,
|
||||||
|
None)
|
||||||
|
accept <- walletA.acceptDLCOffer(offer, None, None)
|
||||||
|
|
||||||
// Send accept message to begin p2p
|
// Send accept message to begin p2p
|
||||||
_ = handler ! accept.toMessage
|
_ = handler ! accept.toMessage
|
||||||
|
|
|
@ -35,7 +35,9 @@ class DLCNodeTest extends BitcoinSDLCNodeTest {
|
||||||
half,
|
half,
|
||||||
Some(SatoshisPerVirtualByte.one),
|
Some(SatoshisPerVirtualByte.one),
|
||||||
UInt32.zero,
|
UInt32.zero,
|
||||||
UInt32.one)
|
UInt32.one,
|
||||||
|
None,
|
||||||
|
None)
|
||||||
|
|
||||||
_ <- nodeB.acceptDLCOffer(addrA, offer.toMessage)
|
_ <- nodeB.acceptDLCOffer(addrA, offer.toMessage)
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ class DLCDataHandler(dlcWalletApi: DLCWalletApi, connectionHandler: ActorRef)
|
||||||
Future.unit
|
Future.unit
|
||||||
case dlcOffer: DLCOfferTLV =>
|
case dlcOffer: DLCOfferTLV =>
|
||||||
val f = for {
|
val f = for {
|
||||||
accept <- dlcWalletApi.acceptDLCOffer(dlcOffer)
|
accept <- dlcWalletApi.acceptDLCOffer(dlcOffer, None, None)
|
||||||
_ = connectionHandler ! accept.toMessage
|
_ = connectionHandler ! accept.toMessage
|
||||||
} yield ()
|
} yield ()
|
||||||
f
|
f
|
||||||
|
|
|
@ -362,11 +362,15 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
|
||||||
|
|
||||||
//helper method to make an offer
|
//helper method to make an offer
|
||||||
def makeOffer(): Future[DLCOffer] = {
|
def makeOffer(): Future[DLCOffer] = {
|
||||||
walletA.createDLCOffer(contractInfoTLV = contractInfo,
|
walletA.createDLCOffer(
|
||||||
collateral = totalCollateral,
|
contractInfoTLV = contractInfo,
|
||||||
feeRateOpt = feeRateOpt,
|
collateral = totalCollateral,
|
||||||
locktime = UInt32.zero,
|
feeRateOpt = feeRateOpt,
|
||||||
refundLT = UInt32.one)
|
locktime = UInt32.zero,
|
||||||
|
refundLT = UInt32.one,
|
||||||
|
externalPayoutAddressOpt = None,
|
||||||
|
externalChangeAddressOpt = None
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
//simply try to make 2 offers with the same contract info
|
//simply try to make 2 offers with the same contract info
|
||||||
|
@ -414,7 +418,9 @@ class DLCExecutionTest extends BitcoinSDualWalletTest {
|
||||||
status.localCollateral.satoshis,
|
status.localCollateral.satoshis,
|
||||||
None,
|
None,
|
||||||
UInt32.zero,
|
UInt32.zero,
|
||||||
UInt32.one)
|
UInt32.one,
|
||||||
|
None,
|
||||||
|
None)
|
||||||
|
|
||||||
_ <- walletA.listDLCs()
|
_ <- walletA.listDLCs()
|
||||||
} yield succeed
|
} yield succeed
|
||||||
|
|
|
@ -51,7 +51,9 @@ class MultiWalletDLCTest extends BitcoinSWalletTest {
|
||||||
half,
|
half,
|
||||||
Some(SatoshisPerVirtualByte.one),
|
Some(SatoshisPerVirtualByte.one),
|
||||||
UInt32.zero,
|
UInt32.zero,
|
||||||
UInt32.one)
|
UInt32.one,
|
||||||
|
None,
|
||||||
|
None)
|
||||||
dlcsA <- walletA.listDLCs()
|
dlcsA <- walletA.listDLCs()
|
||||||
dlcsB <- walletB.listDLCs()
|
dlcsB <- walletB.listDLCs()
|
||||||
|
|
||||||
|
@ -68,12 +70,15 @@ class MultiWalletDLCTest extends BitcoinSWalletTest {
|
||||||
fundedWallet: FundedDLCWallet =>
|
fundedWallet: FundedDLCWallet =>
|
||||||
//see: https://github.com/bitcoin-s/bitcoin-s/issues/3813#issue-1051117559
|
//see: https://github.com/bitcoin-s/bitcoin-s/issues/3813#issue-1051117559
|
||||||
val wallet = fundedWallet.wallet
|
val wallet = fundedWallet.wallet
|
||||||
val offerF = wallet.createDLCOffer(contractInfo = sampleContractInfo,
|
val offerF = wallet.createDLCOffer(
|
||||||
collateral = half,
|
contractInfo = sampleContractInfo,
|
||||||
feeRateOpt =
|
collateral = half,
|
||||||
Some(SatoshisPerVirtualByte.one),
|
feeRateOpt = Some(SatoshisPerVirtualByte.one),
|
||||||
locktime = UInt32.zero,
|
locktime = UInt32.zero,
|
||||||
refundLocktime = UInt32.one)
|
refundLocktime = UInt32.one,
|
||||||
|
externalPayoutAddressOpt = None,
|
||||||
|
externalChangeAddressOpt = None
|
||||||
|
)
|
||||||
|
|
||||||
//now unreserve the utxo
|
//now unreserve the utxo
|
||||||
val reservedUtxoF = for {
|
val reservedUtxoF = for {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.bitcoins.dlc.wallet
|
||||||
|
|
||||||
import org.bitcoins.core.currency._
|
import org.bitcoins.core.currency._
|
||||||
import org.bitcoins.core.number.{UInt32, UInt64}
|
import org.bitcoins.core.number.{UInt32, UInt64}
|
||||||
|
import org.bitcoins.core.protocol.BitcoinAddress
|
||||||
import org.bitcoins.core.protocol.dlc.models.DLCMessage._
|
import org.bitcoins.core.protocol.dlc.models.DLCMessage._
|
||||||
import org.bitcoins.core.protocol.dlc.models._
|
import org.bitcoins.core.protocol.dlc.models._
|
||||||
import org.bitcoins.core.protocol.script.P2WPKHWitnessV0
|
import org.bitcoins.core.protocol.script.P2WPKHWitnessV0
|
||||||
|
@ -44,7 +45,9 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
offerData.timeouts.contractTimeout.toUInt32
|
offerData.timeouts.contractTimeout.toUInt32,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
||||||
dlcA1Opt <- walletA.dlcDAO.read(dlcId)
|
dlcA1Opt <- walletA.dlcDAO.read(dlcId)
|
||||||
|
@ -61,7 +64,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
assert(offer.changeAddress.value.nonEmpty)
|
assert(offer.changeAddress.value.nonEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
accept <- walletB.acceptDLCOffer(offer)
|
accept <- walletB.acceptDLCOffer(offer, None, None)
|
||||||
dlcB1Opt <- walletB.dlcDAO.read(dlcId)
|
dlcB1Opt <- walletB.dlcDAO.read(dlcId)
|
||||||
_ = {
|
_ = {
|
||||||
assert(dlcB1Opt.isDefined)
|
assert(dlcB1Opt.isDefined)
|
||||||
|
@ -185,11 +188,13 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
offerData.timeouts.contractTimeout.toUInt32
|
offerData.timeouts.contractTimeout.toUInt32,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
||||||
|
|
||||||
accept <- walletB.acceptDLCOffer(offer)
|
accept <- walletB.acceptDLCOffer(offer, None, None)
|
||||||
|
|
||||||
// reorder dlc inputs in wallets
|
// reorder dlc inputs in wallets
|
||||||
_ <- reorderInputDbs(walletA, dlcId)
|
_ <- reorderInputDbs(walletA, dlcId)
|
||||||
|
@ -242,11 +247,13 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
offerData.timeouts.contractTimeout.toUInt32
|
offerData.timeouts.contractTimeout.toUInt32,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
||||||
|
|
||||||
accept <- walletB.acceptDLCOffer(offer.toTLV)
|
accept <- walletB.acceptDLCOffer(offer.toTLV, None, None)
|
||||||
|
|
||||||
// reorder dlc inputs in wallets
|
// reorder dlc inputs in wallets
|
||||||
_ <- reorderInputDbs(walletA, dlcId)
|
_ <- reorderInputDbs(walletA, dlcId)
|
||||||
|
@ -271,7 +278,9 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
offerData.timeouts.contractTimeout.toUInt32
|
offerData.timeouts.contractTimeout.toUInt32,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
||||||
dlcA1Opt <- walletA.dlcDAO.read(dlcId)
|
dlcA1Opt <- walletA.dlcDAO.read(dlcId)
|
||||||
|
@ -287,7 +296,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
assert(offer.changeAddress.value.nonEmpty)
|
assert(offer.changeAddress.value.nonEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
accept <- walletB.acceptDLCOffer(offer.toTLV)
|
accept <- walletB.acceptDLCOffer(offer.toTLV, None, None)
|
||||||
dlcB1Opt <- walletB.dlcDAO.read(dlcId)
|
dlcB1Opt <- walletB.dlcDAO.read(dlcId)
|
||||||
_ = {
|
_ = {
|
||||||
assert(dlcB1Opt.isDefined)
|
assert(dlcB1Opt.isDefined)
|
||||||
|
@ -365,10 +374,12 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
offerData.timeouts.contractTimeout.toUInt32
|
offerData.timeouts.contractTimeout.toUInt32,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
accept <- walletB.acceptDLCOffer(offer)
|
accept <- walletB.acceptDLCOffer(offer, None, None)
|
||||||
} yield accept
|
} yield accept
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -509,7 +520,9 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
offerData.timeouts.contractTimeout.toUInt32
|
offerData.timeouts.contractTimeout.toUInt32,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
|
|
||||||
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
||||||
|
@ -552,9 +565,11 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
offerData.timeouts.contractTimeout.toUInt32
|
offerData.timeouts.contractTimeout.toUInt32,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
_ <- walletB.acceptDLCOffer(offer)
|
_ <- walletB.acceptDLCOffer(offer, None, None)
|
||||||
|
|
||||||
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
||||||
|
|
||||||
|
@ -593,9 +608,11 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
offerData.timeouts.contractTimeout.toUInt32
|
offerData.timeouts.contractTimeout.toUInt32,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
accept <- walletB.acceptDLCOffer(offer)
|
accept <- walletB.acceptDLCOffer(offer, None, None)
|
||||||
sign <- walletA.signDLC(accept)
|
sign <- walletA.signDLC(accept)
|
||||||
_ <- walletB.addDLCSigs(sign)
|
_ <- walletB.addDLCSigs(sign)
|
||||||
|
|
||||||
|
@ -635,9 +652,11 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
offerData.timeouts.contractTimeout.toUInt32
|
offerData.timeouts.contractTimeout.toUInt32,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
accept <- walletB.acceptDLCOffer(offer)
|
accept <- walletB.acceptDLCOffer(offer, None, None)
|
||||||
sign <- walletA.signDLC(accept)
|
sign <- walletA.signDLC(accept)
|
||||||
_ <- walletB.addDLCSigs(sign)
|
_ <- walletB.addDLCSigs(sign)
|
||||||
|
|
||||||
|
@ -668,9 +687,11 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
UInt32.max
|
UInt32.max,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
accept <- walletB.acceptDLCOffer(offer)
|
accept <- walletB.acceptDLCOffer(offer, None, None)
|
||||||
sign <- walletA.signDLC(accept)
|
sign <- walletA.signDLC(accept)
|
||||||
_ <- walletB.addDLCSigs(sign)
|
_ <- walletB.addDLCSigs(sign)
|
||||||
|
|
||||||
|
@ -733,7 +754,9 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
offerData.timeouts.contractTimeout.toUInt32
|
offerData.timeouts.contractTimeout.toUInt32,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
_ = {
|
_ = {
|
||||||
assert(offer.oracleInfos == offerData.oracleInfos)
|
assert(offer.oracleInfos == offerData.oracleInfos)
|
||||||
|
@ -747,7 +770,7 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
|
|
||||||
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
dlcId = calcDLCId(offer.fundingInputs.map(_.outPoint))
|
||||||
|
|
||||||
accept <- walletB.acceptDLCOffer(offer)
|
accept <- walletB.acceptDLCOffer(offer, None, None)
|
||||||
_ = {
|
_ = {
|
||||||
assert(accept.fundingInputs.nonEmpty)
|
assert(accept.fundingInputs.nonEmpty)
|
||||||
assert(
|
assert(
|
||||||
|
@ -824,19 +847,23 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
val totalCollateral = Satoshis(5000)
|
val totalCollateral = Satoshis(5000)
|
||||||
|
|
||||||
def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = {
|
def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = {
|
||||||
walletA.createDLCOffer(contractInfoTLV = contractInfo,
|
walletA.createDLCOffer(
|
||||||
collateral = totalCollateral,
|
contractInfoTLV = contractInfo,
|
||||||
feeRateOpt = feeRateOpt,
|
collateral = totalCollateral,
|
||||||
locktime = UInt32.zero,
|
feeRateOpt = feeRateOpt,
|
||||||
refundLT = UInt32.one)
|
locktime = UInt32.zero,
|
||||||
|
refundLT = UInt32.one,
|
||||||
|
externalPayoutAddressOpt = None,
|
||||||
|
externalChangeAddressOpt = None
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
offerA <- makeOffer(contractInfoA)
|
offerA <- makeOffer(contractInfoA)
|
||||||
offerB <- makeOffer(contractInfoB)
|
offerB <- makeOffer(contractInfoB)
|
||||||
|
|
||||||
_ <- walletB.acceptDLCOffer(offerA)
|
_ <- walletB.acceptDLCOffer(offerA, None, None)
|
||||||
_ <- walletB.acceptDLCOffer(offerB)
|
_ <- walletB.acceptDLCOffer(offerB, None, None)
|
||||||
} yield succeed
|
} yield succeed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -868,17 +895,21 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
val totalCollateral = Satoshis(100000)
|
val totalCollateral = Satoshis(100000)
|
||||||
|
|
||||||
def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = {
|
def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = {
|
||||||
walletA.createDLCOffer(contractInfoTLV = contractInfo,
|
walletA.createDLCOffer(
|
||||||
collateral = totalCollateral,
|
contractInfoTLV = contractInfo,
|
||||||
feeRateOpt = feeRateOpt,
|
collateral = totalCollateral,
|
||||||
locktime = UInt32.zero,
|
feeRateOpt = feeRateOpt,
|
||||||
refundLT = UInt32.one)
|
locktime = UInt32.zero,
|
||||||
|
refundLT = UInt32.one,
|
||||||
|
externalPayoutAddressOpt = None,
|
||||||
|
externalChangeAddressOpt = None
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
offer <- makeOffer(contractInfoA)
|
offer <- makeOffer(contractInfoA)
|
||||||
accept1F = walletB.acceptDLCOffer(offer)
|
accept1F = walletB.acceptDLCOffer(offer, None, None)
|
||||||
accept2F = walletB.acceptDLCOffer(offer)
|
accept2F = walletB.acceptDLCOffer(offer, None, None)
|
||||||
_ <- recoverToSucceededIf[DuplicateOfferException](
|
_ <- recoverToSucceededIf[DuplicateOfferException](
|
||||||
Future.sequence(Seq(accept1F, accept2F)))
|
Future.sequence(Seq(accept1F, accept2F)))
|
||||||
} yield {
|
} yield {
|
||||||
|
@ -895,17 +926,21 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
val totalCollateral = Satoshis(100000)
|
val totalCollateral = Satoshis(100000)
|
||||||
|
|
||||||
def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = {
|
def makeOffer(contractInfo: ContractInfoV0TLV): Future[DLCOffer] = {
|
||||||
walletA.createDLCOffer(contractInfoTLV = contractInfo,
|
walletA.createDLCOffer(
|
||||||
collateral = totalCollateral,
|
contractInfoTLV = contractInfo,
|
||||||
feeRateOpt = feeRateOpt,
|
collateral = totalCollateral,
|
||||||
locktime = UInt32.zero,
|
feeRateOpt = feeRateOpt,
|
||||||
refundLT = UInt32.one)
|
locktime = UInt32.zero,
|
||||||
|
refundLT = UInt32.one,
|
||||||
|
externalPayoutAddressOpt = None,
|
||||||
|
externalChangeAddressOpt = None
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
offer <- makeOffer(contractInfoA)
|
offer <- makeOffer(contractInfoA)
|
||||||
accept1 <- walletB.acceptDLCOffer(offer)
|
accept1 <- walletB.acceptDLCOffer(offer, None, None)
|
||||||
accept2 <- walletB.acceptDLCOffer(offer)
|
accept2 <- walletB.acceptDLCOffer(offer, None, None)
|
||||||
} yield {
|
} yield {
|
||||||
assert(accept1 == accept2)
|
assert(accept1 == accept2)
|
||||||
}
|
}
|
||||||
|
@ -923,9 +958,11 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
UInt32.max
|
UInt32.max,
|
||||||
|
None,
|
||||||
|
None
|
||||||
)
|
)
|
||||||
accept <- walletB.acceptDLCOffer(offer)
|
accept <- walletB.acceptDLCOffer(offer, None, None)
|
||||||
res <- recoverToSucceededIf[IllegalArgumentException](
|
res <- recoverToSucceededIf[IllegalArgumentException](
|
||||||
walletB.signDLC(accept))
|
walletB.signDLC(accept))
|
||||||
} yield res
|
} yield res
|
||||||
|
@ -944,7 +981,9 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
offerData.totalCollateral,
|
offerData.totalCollateral,
|
||||||
Some(offerData.feeRate),
|
Some(offerData.feeRate),
|
||||||
offerData.timeouts.contractMaturity.toUInt32,
|
offerData.timeouts.contractMaturity.toUInt32,
|
||||||
UInt32.max
|
UInt32.max,
|
||||||
|
None,
|
||||||
|
None
|
||||||
))
|
))
|
||||||
} yield {
|
} yield {
|
||||||
res
|
res
|
||||||
|
@ -963,18 +1002,82 @@ class WalletDLCSetupTest extends BitcoinSDualWalletTest {
|
||||||
val totalCollateral = Satoshis(5000)
|
val totalCollateral = Satoshis(5000)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
offer <- walletA.createDLCOffer(contractInfoTLV = contractInfo,
|
offer <- walletA.createDLCOffer(
|
||||||
collateral = totalCollateral,
|
contractInfoTLV = contractInfo,
|
||||||
feeRateOpt = feeRateOpt,
|
collateral = totalCollateral,
|
||||||
locktime = UInt32.zero,
|
feeRateOpt = feeRateOpt,
|
||||||
refundLT = UInt32.one)
|
locktime = UInt32.zero,
|
||||||
|
refundLT = UInt32.one,
|
||||||
|
externalPayoutAddressOpt = None,
|
||||||
|
externalChangeAddressOpt = None
|
||||||
|
)
|
||||||
invalidOffer = offer.copy(contractInfo = invalidContractInfo)
|
invalidOffer = offer.copy(contractInfo = invalidContractInfo)
|
||||||
res <- recoverToSucceededIf[InvalidAnnouncementSignature](
|
res <- recoverToSucceededIf[InvalidAnnouncementSignature](
|
||||||
walletB.acceptDLCOffer(invalidOffer))
|
walletB.acceptDLCOffer(invalidOffer, None, None))
|
||||||
} yield {
|
} yield {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it must "use external payout and change addresses when they are provided" in {
|
||||||
|
wallets =>
|
||||||
|
val walletA = wallets._1.wallet
|
||||||
|
val walletB = wallets._2.wallet
|
||||||
|
|
||||||
|
//https://test.oracle.suredbits.com/contract/enum/75b08299654dca23b80cf359db6afb6cfd6e55bc898b5397d3c0fe796dfc13f0/12fb3e5f091086329ed0d2a12c3fcfa80111a36ef3fc1ac9c2567076a57d6a73
|
||||||
|
val contractInfo = ContractInfoV0TLV.fromHex(
|
||||||
|
"fdd82eeb00000000000186a0fda71026030359455300000000000186a0024e4f0000000000000000056f746865720000000000000000fda712b5fdd824b1596ec40d0dae3fdf54d9795ad51ec069970c6863a02d244663d39fd6bedadc0070349e1ba2e17583ee2d1cb3ae6fffaaa1c45039b61c5c4f1d0d864221c461745d1bcfab252c6dd9edd7aea4c5eeeef138f7ff7346061ea40143a9f5ae80baa9fdd8224d0001fa5b84283852400b21a840d5d5ca1cc31867c37326ad521aa50bebf3df4eea1a60b03280fdd8060f000303594553024e4f056f74686572135465746865722d52657365727665732d363342")
|
||||||
|
val contractInfo1 = DLCWalletUtil.sampleDLCOffer.contractInfo.toTLV
|
||||||
|
|
||||||
|
val feeRateOpt = Some(SatoshisPerVirtualByte(Satoshis.one))
|
||||||
|
val totalCollateral = Satoshis(5000)
|
||||||
|
val feeRateOpt1 = Some(SatoshisPerVirtualByte(Satoshis(2)))
|
||||||
|
val totalCollateral1 = Satoshis(10000)
|
||||||
|
|
||||||
|
// random testnet addresses
|
||||||
|
val payoutAddressAOpt = Some(
|
||||||
|
BitcoinAddress.fromString("tb1qw98mrsxpqtz25xe332khnvlapvl09ejnzk7c3f"))
|
||||||
|
val changeAddressAOpt = Some(
|
||||||
|
BitcoinAddress.fromString("tb1qkfaglsvpcwe5pm9ktqs80u9d9jd0qzgqjqd240"))
|
||||||
|
val payoutAddressBOpt =
|
||||||
|
Some(BitcoinAddress.fromString("2MsM67NLa71fHvTUBqNENW15P68nHB2vVXb"))
|
||||||
|
val changeAddressBOpt =
|
||||||
|
Some(BitcoinAddress.fromString("2N4YXTxKEso3yeYXNn5h42Vqu3FzTTQ8Lq5"))
|
||||||
|
|
||||||
|
for {
|
||||||
|
offer <- walletA.createDLCOffer(
|
||||||
|
contractInfoTLV = contractInfo,
|
||||||
|
collateral = totalCollateral,
|
||||||
|
feeRateOpt = feeRateOpt,
|
||||||
|
locktime = UInt32.zero,
|
||||||
|
refundLT = UInt32.one,
|
||||||
|
externalPayoutAddressOpt = payoutAddressAOpt,
|
||||||
|
externalChangeAddressOpt = changeAddressAOpt
|
||||||
|
)
|
||||||
|
accept <- walletB.acceptDLCOffer(offer,
|
||||||
|
payoutAddressBOpt,
|
||||||
|
changeAddressBOpt)
|
||||||
|
offer1 <- walletA.createDLCOffer(
|
||||||
|
contractInfoTLV = contractInfo1,
|
||||||
|
collateral = totalCollateral1,
|
||||||
|
feeRateOpt = feeRateOpt1,
|
||||||
|
locktime = UInt32.zero,
|
||||||
|
refundLT = UInt32.one,
|
||||||
|
externalPayoutAddressOpt = None,
|
||||||
|
externalChangeAddressOpt = None
|
||||||
|
)
|
||||||
|
accept1 <- walletB.acceptDLCOffer(offer1, None, None)
|
||||||
|
} yield {
|
||||||
|
assert(offer.pubKeys.payoutAddress == payoutAddressAOpt.get)
|
||||||
|
assert(offer.changeAddress == changeAddressAOpt.get)
|
||||||
|
assert(accept.pubKeys.payoutAddress == payoutAddressBOpt.get)
|
||||||
|
assert(accept.changeAddress == changeAddressBOpt.get)
|
||||||
|
assert(offer1.pubKeys.payoutAddress != payoutAddressAOpt.get)
|
||||||
|
assert(offer1.changeAddress != changeAddressAOpt.get)
|
||||||
|
assert(accept1.pubKeys.payoutAddress != payoutAddressBOpt.get)
|
||||||
|
assert(accept1.changeAddress != changeAddressBOpt.get)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -268,8 +268,16 @@ abstract class DLCWallet
|
||||||
collateral: Satoshis,
|
collateral: Satoshis,
|
||||||
feeRateOpt: Option[SatoshisPerVirtualByte],
|
feeRateOpt: Option[SatoshisPerVirtualByte],
|
||||||
locktime: UInt32,
|
locktime: UInt32,
|
||||||
refundLocktime: UInt32): Future[DLCOffer] = {
|
refundLocktime: UInt32,
|
||||||
|
externalPayoutAddressOpt: Option[BitcoinAddress],
|
||||||
|
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCOffer] = {
|
||||||
logger.info("Creating DLC Offer")
|
logger.info("Creating DLC Offer")
|
||||||
|
if (
|
||||||
|
!walletConfig.allowExternalDLCAddresses && (externalPayoutAddressOpt.nonEmpty || externalChangeAddressOpt.nonEmpty)
|
||||||
|
) {
|
||||||
|
return Future.failed(
|
||||||
|
new IllegalArgumentException("External DLC addresses are not allowed"))
|
||||||
|
}
|
||||||
if (!validateAnnouncementSignatures(contractInfo.oracleInfos)) {
|
if (!validateAnnouncementSignatures(contractInfo.oracleInfos)) {
|
||||||
return Future.failed(
|
return Future.failed(
|
||||||
InvalidAnnouncementSignature(
|
InvalidAnnouncementSignature(
|
||||||
|
@ -279,19 +287,15 @@ abstract class DLCWallet
|
||||||
val announcements =
|
val announcements =
|
||||||
contractInfo.oracleInfos.head.singleOracleInfos.map(_.announcement)
|
contractInfo.oracleInfos.head.singleOracleInfos.map(_.announcement)
|
||||||
|
|
||||||
//hack for now to get around https://github.com/bitcoin-s/bitcoin-s/issues/3127
|
|
||||||
//filter announcements that we already have in the db
|
|
||||||
val groupedAnnouncementsF: Future[AnnouncementGrouping] = {
|
|
||||||
groupByExistingAnnouncements(announcements)
|
|
||||||
}
|
|
||||||
|
|
||||||
val feeRateF = determineFeeRate(feeRateOpt).map { fee =>
|
val feeRateF = determineFeeRate(feeRateOpt).map { fee =>
|
||||||
SatoshisPerVirtualByte(fee.currencyUnit)
|
SatoshisPerVirtualByte(fee.currencyUnit)
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
feeRate <- feeRateF
|
feeRate <- feeRateF
|
||||||
groupedAnnouncements <- groupedAnnouncementsF
|
//hack for now to get around https://github.com/bitcoin-s/bitcoin-s/issues/3127
|
||||||
|
//filter announcements that we already have in the db
|
||||||
|
groupedAnnouncements <- groupByExistingAnnouncements(announcements)
|
||||||
announcementDataDbs <- announcementDAO.createAll(
|
announcementDataDbs <- announcementDAO.createAll(
|
||||||
groupedAnnouncements.newAnnouncements)
|
groupedAnnouncements.newAnnouncements)
|
||||||
allAnnouncementDbs =
|
allAnnouncementDbs =
|
||||||
|
@ -341,14 +345,17 @@ abstract class DLCWallet
|
||||||
used = false)
|
used = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
changeSPK =
|
changeAddr = externalChangeAddressOpt.getOrElse {
|
||||||
txBuilder.finalizer.changeSPK
|
val changeSPK = txBuilder.finalizer.changeSPK
|
||||||
changeAddr = BitcoinAddress.fromScriptPubKey(changeSPK, networkParameters)
|
BitcoinAddress.fromScriptPubKey(changeSPK, networkParameters)
|
||||||
|
}
|
||||||
|
|
||||||
dlcPubKeys = DLCUtil.calcDLCPubKeys(xpub = account.xpub,
|
dlcPubKeys = DLCUtil.calcDLCPubKeys(xpub = account.xpub,
|
||||||
chainType = chainType,
|
chainType = chainType,
|
||||||
keyIndex = nextIndex,
|
keyIndex = nextIndex,
|
||||||
networkParameters = networkParameters)
|
networkParameters = networkParameters,
|
||||||
|
externalPayoutAddressOpt =
|
||||||
|
externalPayoutAddressOpt)
|
||||||
|
|
||||||
_ = logger.debug(
|
_ = logger.debug(
|
||||||
s"DLC Offer data collected, creating database entry, ${dlcId.hex}")
|
s"DLC Offer data collected, creating database entry, ${dlcId.hex}")
|
||||||
|
@ -552,8 +559,17 @@ abstract class DLCWallet
|
||||||
*
|
*
|
||||||
* This is the first step of the recipient
|
* This is the first step of the recipient
|
||||||
*/
|
*/
|
||||||
override def acceptDLCOffer(offer: DLCOffer): Future[DLCAccept] = {
|
override def acceptDLCOffer(
|
||||||
|
offer: DLCOffer,
|
||||||
|
externalPayoutAddressOpt: Option[BitcoinAddress],
|
||||||
|
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCAccept] = {
|
||||||
logger.debug("Calculating relevant wallet data for DLC Accept")
|
logger.debug("Calculating relevant wallet data for DLC Accept")
|
||||||
|
if (
|
||||||
|
!walletConfig.allowExternalDLCAddresses && (externalPayoutAddressOpt.nonEmpty || externalChangeAddressOpt.nonEmpty)
|
||||||
|
) {
|
||||||
|
return Future.failed(
|
||||||
|
new IllegalArgumentException("External DLC addresses are not allowed"))
|
||||||
|
}
|
||||||
if (!validateAnnouncementSignatures(offer.oracleInfos)) {
|
if (!validateAnnouncementSignatures(offer.oracleInfos)) {
|
||||||
return Future.failed(InvalidAnnouncementSignature(
|
return Future.failed(InvalidAnnouncementSignature(
|
||||||
s"Offer ${offer.tempContractId.hex} contains invalid announcement signature(s)"))
|
s"Offer ${offer.tempContractId.hex} contains invalid announcement signature(s)"))
|
||||||
|
@ -573,7 +589,11 @@ abstract class DLCWallet
|
||||||
dlcAccept <- {
|
dlcAccept <- {
|
||||||
dlcAcceptOpt match {
|
dlcAcceptOpt match {
|
||||||
case Some(accept) => Future.successful(accept)
|
case Some(accept) => Future.successful(accept)
|
||||||
case None => createNewDLCAccept(collateral, offer)
|
case None =>
|
||||||
|
createNewDLCAccept(collateral,
|
||||||
|
offer,
|
||||||
|
externalPayoutAddressOpt,
|
||||||
|
externalChangeAddressOpt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
status <- findDLC(dlcId)
|
status <- findDLC(dlcId)
|
||||||
|
@ -612,155 +632,161 @@ abstract class DLCWallet
|
||||||
|
|
||||||
private def createNewDLCAccept(
|
private def createNewDLCAccept(
|
||||||
collateral: CurrencyUnit,
|
collateral: CurrencyUnit,
|
||||||
offer: DLCOffer): Future[DLCAccept] = Future {
|
offer: DLCOffer,
|
||||||
DLCWallet.AcceptingOffersLatch.startAccepting(offer.tempContractId)
|
externalPayoutAddressOpt: Option[BitcoinAddress],
|
||||||
|
externalChangeAddressOpt: Option[BitcoinAddress]): Future[DLCAccept] =
|
||||||
|
Future {
|
||||||
|
DLCWallet.AcceptingOffersLatch.startAccepting(offer.tempContractId)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
s"Creating DLC Accept for tempContractId ${offer.tempContractId.hex}")
|
s"Creating DLC Accept for tempContractId ${offer.tempContractId.hex}")
|
||||||
|
|
||||||
def getFundingPrivKey(account: AccountDb, dlc: DLCDb): AdaptorSign = {
|
def getFundingPrivKey(account: AccountDb, dlc: DLCDb): AdaptorSign = {
|
||||||
val bip32Path = BIP32Path(
|
val bip32Path = BIP32Path(
|
||||||
account.hdAccount.path ++ Vector(BIP32Node(0, hardened = false),
|
account.hdAccount.path ++ Vector(BIP32Node(0, hardened = false),
|
||||||
BIP32Node(dlc.keyIndex,
|
BIP32Node(dlc.keyIndex,
|
||||||
hardened = false)))
|
hardened = false)))
|
||||||
val privKeyPath = HDPath.fromString(bip32Path.toString)
|
val privKeyPath = HDPath.fromString(bip32Path.toString)
|
||||||
keyManager.toSign(privKeyPath)
|
keyManager.toSign(privKeyPath)
|
||||||
}
|
|
||||||
|
|
||||||
val result = for {
|
|
||||||
account <- getDefaultAccountForType(AddressType.SegWit)
|
|
||||||
(dlc, offerDb, contractDataDb) <- initDLCForAccept(offer, account)
|
|
||||||
(txBuilder, spendingInfos) <- fundDLCAcceptMsg(offer = offer,
|
|
||||||
collateral = collateral,
|
|
||||||
account = account)
|
|
||||||
fundingPrivKey = getFundingPrivKey(account, dlc)
|
|
||||||
(acceptWithoutSigs, dlcPubKeys) = DLCAcceptUtil.buildAcceptWithoutSigs(
|
|
||||||
dlc = dlc,
|
|
||||||
offer = offer,
|
|
||||||
txBuilder = txBuilder,
|
|
||||||
spendingInfos = spendingInfos,
|
|
||||||
account = account,
|
|
||||||
fundingPrivKey = fundingPrivKey,
|
|
||||||
collateral = collateral,
|
|
||||||
networkParameters = networkParameters
|
|
||||||
)
|
|
||||||
builder = DLCTxBuilder(offer, acceptWithoutSigs)
|
|
||||||
|
|
||||||
contractId = builder.calcContractId
|
|
||||||
|
|
||||||
dlcDbWithContractId = dlc.copy(contractIdOpt = Some(contractId))
|
|
||||||
|
|
||||||
signer = DLCTxSigner(builder = builder,
|
|
||||||
isInitiator = false,
|
|
||||||
fundingKey = fundingPrivKey,
|
|
||||||
finalAddress = dlcPubKeys.payoutAddress,
|
|
||||||
fundingUtxos = spendingInfos)
|
|
||||||
|
|
||||||
spkDb = ScriptPubKeyDb(builder.fundingSPK)
|
|
||||||
// only update spk db if we don't have it
|
|
||||||
_ <- scriptPubKeyDAO.createIfNotExists(spkDb)
|
|
||||||
|
|
||||||
_ = logger.info(s"Creating CET Sigs for ${contractId.toHex}")
|
|
||||||
//emit websocket event that we are now computing adaptor signatures
|
|
||||||
status = DLCStatusBuilder.buildInProgressDLCStatus(
|
|
||||||
dlcDb = dlcDbWithContractId,
|
|
||||||
contractInfo = offer.contractInfo,
|
|
||||||
contractData = contractDataDb,
|
|
||||||
offerDb = offerDb)
|
|
||||||
_ = dlcConfig.walletCallbacks.executeOnDLCStateChange(logger, status)
|
|
||||||
cetSigs <- signer.createCETSigsAsync()
|
|
||||||
refundSig = signer.signRefundTx
|
|
||||||
_ = logger.debug(
|
|
||||||
s"DLC Accept data collected, creating database entry, ${dlc.dlcId.hex}")
|
|
||||||
|
|
||||||
dlcAcceptDb = DLCAcceptDb(
|
|
||||||
dlcId = dlc.dlcId,
|
|
||||||
fundingKey = dlcPubKeys.fundingKey,
|
|
||||||
payoutAddress = dlcPubKeys.payoutAddress,
|
|
||||||
payoutSerialId = acceptWithoutSigs.payoutSerialId,
|
|
||||||
collateral = collateral,
|
|
||||||
changeAddress = acceptWithoutSigs.changeAddress,
|
|
||||||
changeSerialId = acceptWithoutSigs.changeSerialId,
|
|
||||||
negotiationFieldsTLV = NoNegotiationFields.toTLV
|
|
||||||
)
|
|
||||||
|
|
||||||
sigsDbs = cetSigs.outcomeSigs.zipWithIndex.map { case (sig, index) =>
|
|
||||||
DLCCETSignaturesDb(dlc.dlcId, index = index, sig._1, sig._2, None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
refundSigsDb =
|
val result = for {
|
||||||
DLCRefundSigsDb(dlc.dlcId, refundSig, None)
|
account <- getDefaultAccountForType(AddressType.SegWit)
|
||||||
|
(dlc, offerDb, contractDataDb) <- initDLCForAccept(offer, account)
|
||||||
|
(txBuilder, spendingInfos) <- fundDLCAcceptMsg(offer = offer,
|
||||||
|
collateral = collateral,
|
||||||
|
account = account)
|
||||||
|
fundingPrivKey = getFundingPrivKey(account, dlc)
|
||||||
|
(acceptWithoutSigs, dlcPubKeys) = DLCAcceptUtil.buildAcceptWithoutSigs(
|
||||||
|
dlc = dlc,
|
||||||
|
offer = offer,
|
||||||
|
txBuilder = txBuilder,
|
||||||
|
spendingInfos = spendingInfos,
|
||||||
|
account = account,
|
||||||
|
fundingPrivKey = fundingPrivKey,
|
||||||
|
collateral = collateral,
|
||||||
|
networkParameters = networkParameters,
|
||||||
|
externalPayoutAddressOpt = externalPayoutAddressOpt,
|
||||||
|
externalChangeAddressOpt = externalChangeAddressOpt
|
||||||
|
)
|
||||||
|
builder = DLCTxBuilder(offer, acceptWithoutSigs)
|
||||||
|
|
||||||
offerInputs = offer.fundingInputs.zipWithIndex.map {
|
contractId = builder.calcContractId
|
||||||
case (funding, idx) =>
|
|
||||||
DLCFundingInputDb(
|
|
||||||
dlcId = dlc.dlcId,
|
|
||||||
isInitiator = true,
|
|
||||||
index = idx,
|
|
||||||
inputSerialId = funding.inputSerialId,
|
|
||||||
outPoint = funding.outPoint,
|
|
||||||
output = funding.output,
|
|
||||||
nSequence = funding.sequence,
|
|
||||||
maxWitnessLength = funding.maxWitnessLen.toLong,
|
|
||||||
redeemScriptOpt = funding.redeemScriptOpt,
|
|
||||||
witnessScriptOpt = None
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
offerPrevTxs = offer.fundingInputs.map(funding =>
|
dlcDbWithContractId = dlc.copy(contractIdOpt = Some(contractId))
|
||||||
TransactionDbHelper.fromTransaction(funding.prevTx,
|
|
||||||
blockHashOpt = None))
|
|
||||||
|
|
||||||
acceptInputs = spendingInfos
|
signer = DLCTxSigner(builder = builder,
|
||||||
.zip(acceptWithoutSigs.fundingInputs)
|
isInitiator = false,
|
||||||
.zipWithIndex
|
fundingKey = fundingPrivKey,
|
||||||
.map { case ((utxo, fundingInput), idx) =>
|
finalAddress = dlcPubKeys.payoutAddress,
|
||||||
DLCFundingInputDb(
|
fundingUtxos = spendingInfos)
|
||||||
dlcId = dlc.dlcId,
|
|
||||||
isInitiator = false,
|
spkDb = ScriptPubKeyDb(builder.fundingSPK)
|
||||||
index = idx,
|
// only update spk db if we don't have it
|
||||||
inputSerialId = fundingInput.inputSerialId,
|
_ <- scriptPubKeyDAO.createIfNotExists(spkDb)
|
||||||
outPoint = utxo.outPoint,
|
|
||||||
output = utxo.output,
|
_ = logger.info(s"Creating CET Sigs for ${contractId.toHex}")
|
||||||
nSequence = fundingInput.sequence,
|
//emit websocket event that we are now computing adaptor signatures
|
||||||
maxWitnessLength = fundingInput.maxWitnessLen.toLong,
|
status = DLCStatusBuilder.buildInProgressDLCStatus(
|
||||||
redeemScriptOpt = InputInfo.getRedeemScript(utxo.inputInfo),
|
dlcDb = dlcDbWithContractId,
|
||||||
witnessScriptOpt = InputInfo.getScriptWitness(utxo.inputInfo)
|
contractInfo = offer.contractInfo,
|
||||||
)
|
contractData = contractDataDb,
|
||||||
|
offerDb = offerDb)
|
||||||
|
_ = dlcConfig.walletCallbacks.executeOnDLCStateChange(logger, status)
|
||||||
|
cetSigs <- signer.createCETSigsAsync()
|
||||||
|
refundSig = signer.signRefundTx
|
||||||
|
_ = logger.debug(
|
||||||
|
s"DLC Accept data collected, creating database entry, ${dlc.dlcId.hex}")
|
||||||
|
|
||||||
|
dlcAcceptDb = DLCAcceptDb(
|
||||||
|
dlcId = dlc.dlcId,
|
||||||
|
fundingKey = dlcPubKeys.fundingKey,
|
||||||
|
payoutAddress = dlcPubKeys.payoutAddress,
|
||||||
|
payoutSerialId = acceptWithoutSigs.payoutSerialId,
|
||||||
|
collateral = collateral,
|
||||||
|
changeAddress = acceptWithoutSigs.changeAddress,
|
||||||
|
changeSerialId = acceptWithoutSigs.changeSerialId,
|
||||||
|
negotiationFieldsTLV = NoNegotiationFields.toTLV
|
||||||
|
)
|
||||||
|
|
||||||
|
sigsDbs = cetSigs.outcomeSigs.zipWithIndex.map { case (sig, index) =>
|
||||||
|
DLCCETSignaturesDb(dlc.dlcId, index = index, sig._1, sig._2, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
accept =
|
refundSigsDb =
|
||||||
dlcAcceptDb.toDLCAccept(tempContractId = dlc.tempContractId,
|
DLCRefundSigsDb(dlc.dlcId, refundSig, None)
|
||||||
fundingInputs = acceptWithoutSigs.fundingInputs,
|
|
||||||
outcomeSigs = cetSigs.outcomeSigs,
|
|
||||||
refundSig = refundSig)
|
|
||||||
|
|
||||||
_ = require(accept.tempContractId == offer.tempContractId,
|
offerInputs = offer.fundingInputs.zipWithIndex.map {
|
||||||
"Offer and Accept have differing tempContractIds!")
|
case (funding, idx) =>
|
||||||
|
DLCFundingInputDb(
|
||||||
|
dlcId = dlc.dlcId,
|
||||||
|
isInitiator = true,
|
||||||
|
index = idx,
|
||||||
|
inputSerialId = funding.inputSerialId,
|
||||||
|
outPoint = funding.outPoint,
|
||||||
|
output = funding.output,
|
||||||
|
nSequence = funding.sequence,
|
||||||
|
maxWitnessLength = funding.maxWitnessLen.toLong,
|
||||||
|
redeemScriptOpt = funding.redeemScriptOpt,
|
||||||
|
witnessScriptOpt = None
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
_ <- remoteTxDAO.upsertAll(offerPrevTxs)
|
offerPrevTxs = offer.fundingInputs.map(funding =>
|
||||||
actions = actionBuilder.buildCreateAcceptAction(
|
TransactionDbHelper.fromTransaction(funding.prevTx,
|
||||||
dlcDb = dlcDbWithContractId.updateState(DLCState.Accepted),
|
blockHashOpt = None))
|
||||||
dlcAcceptDb = dlcAcceptDb,
|
|
||||||
offerInputs = offerInputs,
|
|
||||||
acceptInputs = acceptInputs,
|
|
||||||
cetSigsDb = sigsDbs,
|
|
||||||
refundSigsDb = refundSigsDb
|
|
||||||
)
|
|
||||||
_ <- safeDatabase.run(actions)
|
|
||||||
dlcDb <- updateDLCContractIds(offer, accept)
|
|
||||||
_ = logger.info(
|
|
||||||
s"Created DLCAccept for tempContractId ${offer.tempContractId.hex} with contract Id ${contractId.toHex}")
|
|
||||||
|
|
||||||
fundingTx = builder.buildFundingTx
|
acceptInputs = spendingInfos
|
||||||
outPoint = TransactionOutPoint(fundingTx.txId,
|
.zip(acceptWithoutSigs.fundingInputs)
|
||||||
UInt32(builder.fundOutputIndex))
|
.zipWithIndex
|
||||||
_ <- updateFundingOutPoint(dlcDb.contractIdOpt.get, outPoint)
|
.map { case ((utxo, fundingInput), idx) =>
|
||||||
} yield accept
|
DLCFundingInputDb(
|
||||||
result.onComplete(_ =>
|
dlcId = dlc.dlcId,
|
||||||
DLCWallet.AcceptingOffersLatch.doneAccepting(offer.tempContractId))
|
isInitiator = false,
|
||||||
result
|
index = idx,
|
||||||
}.flatten
|
inputSerialId = fundingInput.inputSerialId,
|
||||||
|
outPoint = utxo.outPoint,
|
||||||
|
output = utxo.output,
|
||||||
|
nSequence = fundingInput.sequence,
|
||||||
|
maxWitnessLength = fundingInput.maxWitnessLen.toLong,
|
||||||
|
redeemScriptOpt = InputInfo.getRedeemScript(utxo.inputInfo),
|
||||||
|
witnessScriptOpt = InputInfo.getScriptWitness(utxo.inputInfo)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
accept =
|
||||||
|
dlcAcceptDb.toDLCAccept(tempContractId = dlc.tempContractId,
|
||||||
|
fundingInputs =
|
||||||
|
acceptWithoutSigs.fundingInputs,
|
||||||
|
outcomeSigs = cetSigs.outcomeSigs,
|
||||||
|
refundSig = refundSig)
|
||||||
|
|
||||||
|
_ = require(accept.tempContractId == offer.tempContractId,
|
||||||
|
"Offer and Accept have differing tempContractIds!")
|
||||||
|
|
||||||
|
_ <- remoteTxDAO.upsertAll(offerPrevTxs)
|
||||||
|
actions = actionBuilder.buildCreateAcceptAction(
|
||||||
|
dlcDb = dlcDbWithContractId.updateState(DLCState.Accepted),
|
||||||
|
dlcAcceptDb = dlcAcceptDb,
|
||||||
|
offerInputs = offerInputs,
|
||||||
|
acceptInputs = acceptInputs,
|
||||||
|
cetSigsDb = sigsDbs,
|
||||||
|
refundSigsDb = refundSigsDb
|
||||||
|
)
|
||||||
|
_ <- safeDatabase.run(actions)
|
||||||
|
dlcDb <- updateDLCContractIds(offer, accept)
|
||||||
|
_ = logger.info(
|
||||||
|
s"Created DLCAccept for tempContractId ${offer.tempContractId.hex} with contract Id ${contractId.toHex}")
|
||||||
|
|
||||||
|
fundingTx = builder.buildFundingTx
|
||||||
|
outPoint = TransactionOutPoint(fundingTx.txId,
|
||||||
|
UInt32(builder.fundOutputIndex))
|
||||||
|
_ <- updateFundingOutPoint(dlcDb.contractIdOpt.get, outPoint)
|
||||||
|
} yield accept
|
||||||
|
result.onComplete(_ =>
|
||||||
|
DLCWallet.AcceptingOffersLatch.doneAccepting(offer.tempContractId))
|
||||||
|
result
|
||||||
|
}.flatten
|
||||||
|
|
||||||
def registerDLCAccept(
|
def registerDLCAccept(
|
||||||
accept: DLCAccept): Future[(DLCDb, Vector[DLCCETSignaturesDb])] = {
|
accept: DLCAccept): Future[(DLCDb, Vector[DLCCETSignaturesDb])] = {
|
||||||
|
|
|
@ -38,7 +38,9 @@ object DLCAcceptUtil extends Logging {
|
||||||
account: AccountDb,
|
account: AccountDb,
|
||||||
fundingPrivKey: AdaptorSign,
|
fundingPrivKey: AdaptorSign,
|
||||||
collateral: CurrencyUnit,
|
collateral: CurrencyUnit,
|
||||||
networkParameters: NetworkParameters): (
|
networkParameters: NetworkParameters,
|
||||||
|
externalPayoutAddressOpt: Option[BitcoinAddress],
|
||||||
|
externalChangeAddressOpt: Option[BitcoinAddress]): (
|
||||||
DLCAcceptWithoutSigs,
|
DLCAcceptWithoutSigs,
|
||||||
DLCPublicKeys) = {
|
DLCPublicKeys) = {
|
||||||
val serialIds = DLCMessage.genSerialIds(
|
val serialIds = DLCMessage.genSerialIds(
|
||||||
|
@ -49,15 +51,18 @@ object DLCAcceptUtil extends Logging {
|
||||||
.fromInputSigningInfo(utxo, id, TransactionConstants.enableRBFSequence)
|
.fromInputSigningInfo(utxo, id, TransactionConstants.enableRBFSequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
val changeSPK = txBuilder.finalizer.changeSPK
|
val changeAddr = externalChangeAddressOpt.getOrElse {
|
||||||
val changeAddr =
|
val changeSPK = txBuilder.finalizer.changeSPK
|
||||||
BitcoinAddress.fromScriptPubKey(changeSPK, networkParameters)
|
BitcoinAddress.fromScriptPubKey(changeSPK, networkParameters)
|
||||||
|
}
|
||||||
|
|
||||||
val dlcPubKeys = DLCUtil.calcDLCPubKeys(xpub = account.xpub,
|
val dlcPubKeys = DLCUtil.calcDLCPubKeys(
|
||||||
chainType = dlc.changeIndex,
|
xpub = account.xpub,
|
||||||
keyIndex = dlc.keyIndex,
|
chainType = dlc.changeIndex,
|
||||||
networkParameters =
|
keyIndex = dlc.keyIndex,
|
||||||
networkParameters)
|
networkParameters = networkParameters,
|
||||||
|
externalPayoutAddressOpt = externalPayoutAddressOpt
|
||||||
|
)
|
||||||
|
|
||||||
require(dlcPubKeys.fundingKey == fundingPrivKey.publicKey,
|
require(dlcPubKeys.fundingKey == fundingPrivKey.publicKey,
|
||||||
"Did not derive the same funding private and public key")
|
"Did not derive the same funding private and public key")
|
||||||
|
|
|
@ -242,6 +242,10 @@ bitcoin-s {
|
||||||
# before we timeout
|
# before we timeout
|
||||||
addressQueueTimeout = 5 seconds
|
addressQueueTimeout = 5 seconds
|
||||||
|
|
||||||
|
# Allow external payout and change addresses in DLCs
|
||||||
|
# By default all DLC addresses are generated by the wallet itself
|
||||||
|
allowExternalDLCAddresses = false
|
||||||
|
|
||||||
# How often the wallet will rebroadcast unconfirmed transactions
|
# How often the wallet will rebroadcast unconfirmed transactions
|
||||||
rebroadcastFrequency = 4 hours
|
rebroadcastFrequency = 4 hours
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,9 @@ object BitcoinSTestAppConfig {
|
||||||
| node {
|
| node {
|
||||||
| mode = spv
|
| mode = spv
|
||||||
| }
|
| }
|
||||||
|
|
| wallet {
|
||||||
|
| allowExternalDLCAddresses = true
|
||||||
|
| }
|
||||||
| proxy.enabled = $torEnabled
|
| proxy.enabled = $torEnabled
|
||||||
| tor.enabled = $torEnabled
|
| tor.enabled = $torEnabled
|
||||||
| tor.use-random-ports = false
|
| tor.use-random-ports = false
|
||||||
|
|
|
@ -274,7 +274,12 @@ object DLCWalletUtil extends Logging {
|
||||||
def initDLC(
|
def initDLC(
|
||||||
fundedWalletA: FundedDLCWallet,
|
fundedWalletA: FundedDLCWallet,
|
||||||
fundedWalletB: FundedDLCWallet,
|
fundedWalletB: FundedDLCWallet,
|
||||||
contractInfo: ContractInfo)(implicit ec: ExecutionContext): Future[
|
contractInfo: ContractInfo,
|
||||||
|
payoutAddressAOpt: Option[BitcoinAddress] = None,
|
||||||
|
changeAddressAOpt: Option[BitcoinAddress] = None,
|
||||||
|
payoutAddressBOpt: Option[BitcoinAddress] = None,
|
||||||
|
changeAddressBOpt: Option[BitcoinAddress] = None)(implicit
|
||||||
|
ec: ExecutionContext): Future[
|
||||||
(InitializedDLCWallet, InitializedDLCWallet)] = {
|
(InitializedDLCWallet, InitializedDLCWallet)] = {
|
||||||
val walletA = fundedWalletA.wallet
|
val walletA = fundedWalletA.wallet
|
||||||
val walletB = fundedWalletB.wallet
|
val walletB = fundedWalletB.wallet
|
||||||
|
@ -285,9 +290,13 @@ object DLCWalletUtil extends Logging {
|
||||||
collateral = half,
|
collateral = half,
|
||||||
feeRateOpt = Some(SatoshisPerVirtualByte.fromLong(10)),
|
feeRateOpt = Some(SatoshisPerVirtualByte.fromLong(10)),
|
||||||
locktime = dummyTimeouts.contractMaturity.toUInt32,
|
locktime = dummyTimeouts.contractMaturity.toUInt32,
|
||||||
refundLocktime = dummyTimeouts.contractTimeout.toUInt32
|
refundLocktime = dummyTimeouts.contractTimeout.toUInt32,
|
||||||
|
externalPayoutAddressOpt = payoutAddressAOpt,
|
||||||
|
externalChangeAddressOpt = changeAddressAOpt
|
||||||
)
|
)
|
||||||
accept <- walletB.acceptDLCOffer(offer)
|
accept <- walletB.acceptDLCOffer(offer,
|
||||||
|
payoutAddressBOpt,
|
||||||
|
changeAddressBOpt)
|
||||||
sigs <- walletA.signDLC(accept)
|
sigs <- walletA.signDLC(accept)
|
||||||
_ <- walletB.addDLCSigs(sigs)
|
_ <- walletB.addDLCSigs(sigs)
|
||||||
tx <- walletB.broadcastDLCFundingTx(sigs.contractId)
|
tx <- walletB.broadcastDLCFundingTx(sigs.contractId)
|
||||||
|
|
|
@ -145,6 +145,9 @@ case class WalletAppConfig(baseDatadir: Path, configOverrides: Vector[Config])(
|
||||||
lazy val feeProviderTargetOpt: Option[Int] =
|
lazy val feeProviderTargetOpt: Option[Int] =
|
||||||
config.getIntOpt("bitcoin-s.fee-provider.target")
|
config.getIntOpt("bitcoin-s.fee-provider.target")
|
||||||
|
|
||||||
|
lazy val allowExternalDLCAddresses: Boolean =
|
||||||
|
config.getBoolean("bitcoin-s.wallet.allowExternalDLCAddresses")
|
||||||
|
|
||||||
lazy val bip39PasswordOpt: Option[String] = kmConf.bip39PasswordOpt
|
lazy val bip39PasswordOpt: Option[String] = kmConf.bip39PasswordOpt
|
||||||
|
|
||||||
lazy val aesPasswordOpt: Option[AesPassword] = kmConf.aesPasswordOpt
|
lazy val aesPasswordOpt: Option[AesPassword] = kmConf.aesPasswordOpt
|
||||||
|
|
Loading…
Add table
Reference in a new issue