diff --git a/app-commons/src/main/scala/org/bitcoins/commons/serializers/Picklers.scala b/app-commons/src/main/scala/org/bitcoins/commons/serializers/Picklers.scala index fc30cd0f94..371a46f362 100644 --- a/app-commons/src/main/scala/org/bitcoins/commons/serializers/Picklers.scala +++ b/app-commons/src/main/scala/org/bitcoins/commons/serializers/Picklers.scala @@ -118,9 +118,6 @@ object Picklers { implicit val partialSignaturePickler: ReadWriter[PartialSignature] = readwriter[String].bimap(_.hex, PartialSignature.fromHex) - implicit val dlcOfferTLVPickler: ReadWriter[DLCOfferTLV] = - readwriter[String].bimap(_.hex, DLCOfferTLV.fromHex) - implicit val lnMessageDLCOfferTLVPickler: ReadWriter[LnMessage[DLCOfferTLV]] = readwriter[String].bimap(_.hex, LnMessageFactory(DLCOfferTLV).fromHex) @@ -162,6 +159,213 @@ object Picklers { LockUnspentOutputParameter] = readwriter[Value].bimap(_.toJson, LockUnspentOutputParameter.fromJson) + // can't make implicit because it will overlap with ones needed for cli + val announcementV0JsonWriter: Writer[OracleAnnouncementV0TLV] = + writer[Obj].comap { announcement => + val noncesJson = announcement.eventTLV.nonces.map { nonce => + Str(nonce.hex) + } + + val descriptorJson = announcement.eventTLV.eventDescriptor match { + case EnumEventDescriptorV0TLV(outcomes) => + Obj("outcomes" -> outcomes.map(Str(_))) + case numeric: NumericEventDescriptorTLV => + Obj( + "base" -> Num(numeric.base.toLong.toDouble), + "isSigned" -> Bool(numeric.isSigned), + "unit" -> Str(numeric.unit), + "precision" -> Num(numeric.precision.toLong.toDouble) + ) + } + + val maturityStr = + TimeUtil.iso8601ToString(Date.from(announcement.eventTLV.maturation)) + + val eventJson = Obj("nonces" -> noncesJson, + "maturity" -> Str(maturityStr), + "descriptor" -> descriptorJson, + "eventId" -> Str(announcement.eventTLV.eventId)) + + Obj( + "announcementSignature" -> Str(announcement.announcementSignature.hex), + "publicKey" -> Str(announcement.publicKey.hex), + "event" -> eventJson) + } + + // can't make implicit because it will overlap with ones needed for cli + val oracleAnnouncementTLVJsonWriter: Writer[OracleAnnouncementTLV] = + writer[Value].comap { case v0: OracleAnnouncementV0TLV => + writeJs(v0)(announcementV0JsonWriter) + } + + // can't make implicit because it will overlap with ones needed for cli + val oracleAttestmentV0Writer: Writer[OracleAttestmentV0TLV] = + writer[Obj].comap { attestments => + val sigsJson = attestments.sigs.map(sig => Str(sig.hex)) + val valuesJson = attestments.outcomes.map(Str(_)) + + Obj("eventId" -> Str(attestments.eventId), + "signatures" -> sigsJson, + "values" -> valuesJson) + } + + implicit val fundingInputV0Writer: Writer[FundingInputTLV] = + writer[Value].comap { case v0: FundingInputV0TLV => + writeJs(v0)(fundingInputWriter) + } + + implicit val fundingInputWriter: Writer[FundingInputV0TLV] = + writer[Obj].comap { input => + import input._ + + val redeemScriptJson = redeemScriptOpt match { + case Some(rs) => Str(rs.hex) + case None => ujson.Null + } + + Obj( + "inputSerialId" -> Num(inputSerialId.toBigInt.toDouble), + "prevTx" -> Str(prevTx.hex), + "prevTxVout" -> Num(prevTxVout.toLong.toDouble), + "sequence" -> Num(sequence.toLong.toDouble), + "maxWitnessLen" -> Num(maxWitnessLen.toLong.toDouble), + "redeemScript" -> redeemScriptJson + ) + } + + implicit val contractDescriptorV0Writer: Writer[ContractDescriptorV0TLV] = + writer[Obj].comap { v0 => + import v0._ + + val outcomesJs = outcomes.map { case (outcome, payout) => + Obj("outcome" -> Str(outcome), + "localPayout" -> Num(payout.toLong.toDouble)) + } + + Obj("outcomes" -> outcomesJs) + } + + implicit val payoutFunctionV0TLVWriter: Writer[PayoutFunctionV0TLV] = + writer[Obj].comap { payoutFunc => + import payoutFunc._ + + val pointsJs = points.map { point => + Obj( + "outcome" -> Num(point.outcome.toDouble), + "payout" -> Num(point.value.toLong.toDouble), + "extraPrecision" -> Num(point.extraPrecision.toDouble), + "isEndpoint" -> Bool(point.isEndpoint) + ) + } + + Obj("points" -> pointsJs) + } + + implicit val roundingIntervalsV0TLVWriter: Writer[RoundingIntervalsV0TLV] = + writer[Obj].comap { roundingIntervals => + import roundingIntervals._ + + val intervalsJs = intervalStarts.map { i => + Obj("beginInterval" -> Num(i._1.toDouble), + "roundingMod" -> Num(i._2.toLong.toDouble)) + } + + Obj("intervals" -> intervalsJs) + } + + implicit val contractDescriptorV1Writer: Writer[ContractDescriptorV1TLV] = + writer[Obj].comap { v1 => + import v1._ + + Obj("numDigits" -> Num(numDigits.toDouble), + "payoutFunction" -> writeJs(payoutFunction), + "roundingIntervals" -> writeJs(roundingIntervals)) + } + + implicit val contractDescriptorWriter: Writer[ContractDescriptorTLV] = + writer[Value].comap { + case v0: ContractDescriptorV0TLV => + writeJs(v0)(contractDescriptorV0Writer) + case v1: ContractDescriptorV1TLV => + writeJs(v1)(contractDescriptorV1Writer) + } + + implicit val oracleInfoV0TLVWriter: Writer[OracleInfoV0TLV] = + writer[Obj].comap { oracleInfo => + Obj( + "announcement" -> writeJs(oracleInfo.announcement)( + oracleAnnouncementTLVJsonWriter)) + } + + implicit val oracleInfoV1TLVWriter: Writer[OracleInfoV1TLV] = + writer[Obj].comap { oracleInfo => + import oracleInfo._ + Obj("threshold" -> Num(threshold.toDouble), + "announcements" -> oracles.map(o => + writeJs(o)(oracleAnnouncementTLVJsonWriter))) + } + + implicit val oracleParamsV0TLVWriter: Writer[OracleParamsV0TLV] = + writer[Obj].comap { params => + import params._ + Obj("maxErrorExp" -> Num(maxErrorExp.toDouble), + "minFailExp" -> Num(minFailExp.toDouble), + "maximizeCoverage" -> Bool(maximizeCoverage)) + } + + implicit val oracleParamsTLVWriter: Writer[OracleParamsTLV] = + writer[Value].comap { case v0: OracleParamsV0TLV => + writeJs(v0) + } + + implicit val oracleInfoV2TLVWriter: Writer[OracleInfoV2TLV] = + writer[Obj].comap { oracleInfo => + import oracleInfo._ + Obj("threshold" -> Num(threshold.toDouble), + "announcements" -> oracles.map(o => + writeJs(o)(oracleAnnouncementTLVJsonWriter)), + "params" -> writeJs(params)) + } + + implicit val oracleInfoTLVWriter: Writer[OracleInfoTLV] = + writer[Value].comap { + case v0: OracleInfoV0TLV => writeJs(v0) + case v1: OracleInfoV1TLV => writeJs(v1) + case v2: OracleInfoV2TLV => writeJs(v2) + } + + // can't make implicit because it will overlap with ones needed for cli + val contractInfoV0TLVJsonWriter: Writer[ContractInfoV0TLV] = + writer[Obj].comap { contractInfo => + import contractInfo._ + + Obj("totalCollateral" -> Num(totalCollateral.toLong.toDouble), + "contractDescriptor" -> writeJs(contractDescriptor), + "oracleInfo" -> writeJs(oracleInfo)) + } + + implicit val offerTLVWriter: Writer[DLCOfferTLV] = + writer[Obj].comap { offer => + import offer._ + Obj( + "contractFlags" -> Str(contractFlags.toHexString), + "chainHash" -> Str(chainHash.hex), + "contractInfo" -> writeJs(contractInfo)(contractInfoV0TLVJsonWriter), + "fundingPubKey" -> Str(fundingPubKey.hex), + "payoutSPK" -> Str(payoutSPK.hex), + "payoutSerialId" -> Num(payoutSerialId.toBigInt.toDouble), + "offerCollateralSatoshis" -> Num( + totalCollateralSatoshis.toLong.toDouble), + "fundingInputs" -> fundingInputs.map(i => writeJs(i)), + "changeSPK" -> Str(changeSPK.hex), + "changeSerialId" -> Num(changeSerialId.toBigInt.toDouble), + "fundOutputSerialId" -> Num(fundOutputSerialId.toBigInt.toDouble), + "feeRatePerVb" -> Num(feeRate.toLong.toDouble), + "cetLocktime" -> Num(contractMaturityBound.toUInt32.toLong.toDouble), + "refundLocktime" -> Num(contractTimeout.toUInt32.toLong.toDouble) + ) + } + implicit val offeredW: Writer[Offered] = writer[Obj].comap { offered => import offered._ @@ -614,9 +818,15 @@ object Picklers { implicit val extPrivateKeyPickler: ReadWriter[ExtPrivateKey] = readwriter[String].bimap(ExtKey.toString, ExtPrivateKey.fromString) + implicit val oracleAnnouncementTLV: ReadWriter[OracleAnnouncementV0TLV] = + readwriter[String].bimap(_.hex, OracleAnnouncementV0TLV.fromHex) + implicit val oracleAttestmentTLV: ReadWriter[OracleAttestmentTLV] = readwriter[String].bimap(_.hex, OracleAttestmentTLV.fromHex) + implicit val oracleAttestmentV0TLV: ReadWriter[OracleAttestmentV0TLV] = + readwriter[String].bimap(_.hex, OracleAttestmentV0TLV.fromHex) + implicit val ecPublicKeyPickler: ReadWriter[ECPublicKey] = readwriter[String].bimap(_.hex, ECPublicKey.fromHex) diff --git a/app/cli/src/main/scala/org/bitcoins/cli/CliReaders.scala b/app/cli/src/main/scala/org/bitcoins/cli/CliReaders.scala index 74f832c2bb..5eeab3693c 100644 --- a/app/cli/src/main/scala/org/bitcoins/cli/CliReaders.scala +++ b/app/cli/src/main/scala/org/bitcoins/cli/CliReaders.scala @@ -196,6 +196,22 @@ object CliReaders { val reads: String => ContractInfoV0TLV = ContractInfoV0TLV.fromHex } + implicit val announcementReads: Read[OracleAnnouncementV0TLV] = + new Read[OracleAnnouncementV0TLV] { + val arity: Int = 1 + + val reads: String => OracleAnnouncementV0TLV = + OracleAnnouncementV0TLV.fromHex + } + + implicit val attestmentReads: Read[OracleAttestmentV0TLV] = + new Read[OracleAttestmentV0TLV] { + val arity: Int = 1 + + val reads: String => OracleAttestmentV0TLV = + OracleAttestmentV0TLV.fromHex + } + implicit val blockStampReads: Read[BlockStamp] = new Read[BlockStamp] { val arity: Int = 1 diff --git a/app/cli/src/main/scala/org/bitcoins/cli/ConsoleCli.scala b/app/cli/src/main/scala/org/bitcoins/cli/ConsoleCli.scala index 4fddefe0c0..a107d32fec 100644 --- a/app/cli/src/main/scala/org/bitcoins/cli/ConsoleCli.scala +++ b/app/cli/src/main/scala/org/bitcoins/cli/ConsoleCli.scala @@ -734,6 +734,58 @@ object ConsoleCli { })) ), note(sys.props("line.separator") + "=== DLC ==="), + cmd("decodecontractinfo") + .action((_, conf) => conf.copy(command = DecodeContractInfo(null))) + .text("Decodes a contract info into json") + .children( + arg[ContractInfoV0TLV]("contractinfo") + .text("Hex encoded contract info") + .required() + .action((contractInfo, conf) => + conf.copy(command = conf.command match { + case decode: DecodeContractInfo => + decode.copy(contractInfo = contractInfo) + case other => other + }))), + cmd("decodeoffer") + .action((_, conf) => conf.copy(command = DecodeOffer(null))) + .text("Decodes an offer message into json") + .children( + arg[LnMessage[DLCOfferTLV]]("offer") + .text("Hex encoded dlc offer message") + .required() + .action((offer, conf) => + conf.copy(command = conf.command match { + case decode: DecodeOffer => + decode.copy(offer = offer) + case other => other + }))), + cmd("decodeannouncement") + .action((_, conf) => conf.copy(command = DecodeAnnouncement(null))) + .text("Decodes an oracle announcement message into json") + .children( + arg[OracleAnnouncementV0TLV]("announcement") + .text("Hex encoded oracle announcement message") + .required() + .action((ann, conf) => + conf.copy(command = conf.command match { + case decode: DecodeAnnouncement => + decode.copy(announcement = ann) + case other => other + }))), + cmd("decodeattestments") + .action((_, conf) => conf.copy(command = DecodeAttestments(null))) + .text("Decodes an oracle attestments message into json") + .children( + arg[OracleAttestmentV0TLV]("attestments") + .text("Hex encoded oracle attestments message") + .required() + .action((attestments, conf) => + conf.copy(command = conf.command match { + case decode: DecodeAttestments => + decode.copy(sigs = attestments) + case other => other + }))), cmd("getdlchostaddress") .action((_, conf) => conf.copy(command = GetDLCHostAddress)) .text("Returns the public listening address of the DLC Node"), @@ -1569,7 +1621,19 @@ object ConsoleCli { RequestParam("walletinfo") // DLCs case GetDLCHostAddress => RequestParam("getdlchostaddress") - case GetDLCs => RequestParam("getdlcs") + + case DecodeContractInfo(contractInfo) => + RequestParam("decodecontractinfo", Seq(up.writeJs(contractInfo))) + + case DecodeOffer(offer) => + RequestParam("decodeoffer", Seq(up.writeJs(offer))) + case DecodeAnnouncement(announcement) => + RequestParam("decodeannouncement", Seq(up.writeJs(announcement))) + + case DecodeAttestments(attestments) => + RequestParam("decodeattestments", Seq(up.writeJs(attestments))) + + case GetDLCs => RequestParam("getdlcs") case GetDLC(dlcId) => RequestParam("getdlc", Seq(up.writeJs(dlcId))) case CreateDLCOffer(contractInfo, @@ -1954,6 +2018,18 @@ object CliCommand { // DLC case object GetDLCHostAddress extends AppServerCliCommand + case class DecodeContractInfo(contractInfo: ContractInfoV0TLV) + extends AppServerCliCommand + + case class DecodeOffer(offer: LnMessage[DLCOfferTLV]) + extends AppServerCliCommand + + case class DecodeAnnouncement(announcement: OracleAnnouncementV0TLV) + extends AppServerCliCommand + + case class DecodeAttestments(sigs: OracleAttestmentV0TLV) + extends AppServerCliCommand + case class CreateDLCOffer( contractInfo: ContractInfoV0TLV, collateral: Satoshis, diff --git a/app/server/src/main/scala/org/bitcoins/server/CoreRoutes.scala b/app/server/src/main/scala/org/bitcoins/server/CoreRoutes.scala index 9026c48b6e..ceca12cf46 100644 --- a/app/server/src/main/scala/org/bitcoins/server/CoreRoutes.scala +++ b/app/server/src/main/scala/org/bitcoins/server/CoreRoutes.scala @@ -4,6 +4,7 @@ import akka.actor.ActorSystem import akka.http.scaladsl.server.Directives._ import akka.http.scaladsl.server._ import org.bitcoins.commons.jsonmodels.{SerializedPSBT, SerializedTransaction} +import org.bitcoins.commons.serializers.Picklers._ import org.bitcoins.core.hd.AddressType import org.bitcoins.core.protocol.script.{ MultiSignatureScriptPubKey, @@ -15,6 +16,7 @@ import org.bitcoins.core.psbt.PSBT import org.bitcoins.server.BitcoinSAppConfig.toChainConf import org.bitcoins.server.routes.{Server, ServerCommand, ServerRoute} import ujson._ +import upickle.default._ import scala.collection.mutable import scala.concurrent.Future @@ -145,6 +147,40 @@ case class CoreRoutes()(implicit system: ActorSystem, config: BitcoinSAppConfig) } } + case ServerCommand("decodeoffer", arr) => + withValidServerCommand(DecodeOffer.fromJsArr(arr)) { + case DecodeOffer(offerTLV) => + complete { + Server.httpSuccess(writeJs(offerTLV)) + } + } + + case ServerCommand("decodecontractinfo", arr) => + withValidServerCommand(DecodeContractInfo.fromJsArr(arr)) { + case DecodeContractInfo(contractInfo) => + complete { + Server.httpSuccess( + writeJs(contractInfo)(contractInfoV0TLVJsonWriter)) + } + } + + case ServerCommand("decodeannouncement", arr) => + withValidServerCommand(DecodeAnnouncement.fromJsArr(arr)) { + case DecodeAnnouncement(announcement) => + complete { + Server.httpSuccess( + writeJs(announcement)(oracleAnnouncementTLVJsonWriter)) + } + } + + case ServerCommand("decodeattestments", arr) => + withValidServerCommand(DecodeAttestations.fromJsArr(arr)) { + case DecodeAttestations(attestments) => + complete { + Server.httpSuccess(writeJs(attestments)(oracleAttestmentV0Writer)) + } + } + case ServerCommand("createmultisig", arr) => withValidServerCommand(CreateMultisig.fromJsArr(arr)) { case CreateMultisig(requiredKeys, keys, addressType) => diff --git a/app/server/src/main/scala/org/bitcoins/server/ServerJsonModels.scala b/app/server/src/main/scala/org/bitcoins/server/ServerJsonModels.scala index 94a1cb1ed7..b9362194cb 100644 --- a/app/server/src/main/scala/org/bitcoins/server/ServerJsonModels.scala +++ b/app/server/src/main/scala/org/bitcoins/server/ServerJsonModels.scala @@ -21,7 +21,7 @@ import ujson._ import java.io.File import java.net.{InetSocketAddress, URI} import java.nio.file.Path -import scala.util.{Failure, Try} +import scala.util._ case class GetNewAddress(labelOpt: Option[AddressLabelTag]) @@ -677,6 +677,102 @@ object CreateDLCOffer extends ServerJsonModels { } } +case class DecodeContractInfo(contractInfo: ContractInfoV0TLV) + +object DecodeContractInfo extends ServerJsonModels { + + def fromJsArr(jsArr: ujson.Arr): Try[DecodeContractInfo] = { + jsArr.arr.toList match { + case contractInfoJs :: Nil => + Try { + val contractInfo = ContractInfoV0TLV(contractInfoJs.str) + DecodeContractInfo(contractInfo) + } + case Nil => + Failure(new IllegalArgumentException("Missing contractInfo argument")) + + case other => + Failure( + new IllegalArgumentException( + s"Bad number of arguments: ${other.length}. Expected: 1")) + } + } +} + +case class DecodeOffer(offer: DLCOfferTLV) + +object DecodeOffer extends ServerJsonModels { + + def fromJsArr(jsArr: ujson.Arr): Try[DecodeOffer] = { + jsArr.arr.toList match { + case offerJs :: Nil => + Try { + val offer: LnMessage[DLCOfferTLV] = + LnMessageFactory(DLCOfferTLV).fromHex(offerJs.str) + DecodeOffer(offer.tlv) + } match { + case Success(value) => Success(value) + case Failure(_) => + Try { + val offer = DLCOfferTLV.fromHex(offerJs.str) + DecodeOffer(offer) + } + } + case Nil => + Failure(new IllegalArgumentException("Missing offer argument")) + + case other => + Failure( + new IllegalArgumentException( + s"Bad number of arguments: ${other.length}. Expected: 1")) + } + } +} + +case class DecodeAnnouncement(announcement: OracleAnnouncementTLV) + +object DecodeAnnouncement extends ServerJsonModels { + + def fromJsArr(jsArr: ujson.Arr): Try[DecodeAnnouncement] = { + jsArr.arr.toList match { + case annJs :: Nil => + Try { + val announcementTLV = OracleAnnouncementTLV(annJs.str) + DecodeAnnouncement(announcementTLV) + } + case Nil => + Failure(new IllegalArgumentException("Missing announcement argument")) + + case other => + Failure( + new IllegalArgumentException( + s"Bad number of arguments: ${other.length}. Expected: 1")) + } + } +} + +case class DecodeAttestations(announcement: OracleAttestmentV0TLV) + +object DecodeAttestations extends ServerJsonModels { + + def fromJsArr(jsArr: ujson.Arr): Try[DecodeAttestations] = { + jsArr.arr.toList match { + case attestmentJs :: Nil => + Try { + val attestments = OracleAttestmentV0TLV(attestmentJs.str) + DecodeAttestations(attestments) + } + case Nil => + Failure(new IllegalArgumentException("Missing attestment argument")) + + case other => + Failure( + new IllegalArgumentException( + s"Bad number of arguments: ${other.length}. Expected: 1")) + } + } +} + case class AcceptDLCOffer(offer: LnMessage[DLCOfferTLV]) object AcceptDLCOffer extends ServerJsonModels { diff --git a/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala b/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala index c0cf1f2eec..23864025c5 100644 --- a/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala +++ b/core/src/main/scala/org/bitcoins/core/protocol/tlv/TLV.scala @@ -627,6 +627,9 @@ sealed trait NumericEventDescriptorTLV extends EventDescriptorTLV { case None => false } } + + /** If the Descriptor contains negative values */ + def isSigned: Boolean } /** Describes a large range event using numerical decomposition */ @@ -705,7 +708,10 @@ case class SignedDigitDecompositionEventDescriptor( numDigits: UInt16, unit: NormalizedString, precision: Int32) - extends DigitDecompositionEventDescriptorV0TLV + extends DigitDecompositionEventDescriptorV0TLV { + + override val isSigned: Boolean = true +} /** Represents a large range event that is unsigned */ case class UnsignedDigitDecompositionEventDescriptor( @@ -713,7 +719,9 @@ case class UnsignedDigitDecompositionEventDescriptor( numDigits: UInt16, unit: NormalizedString, precision: Int32) - extends DigitDecompositionEventDescriptorV0TLV + extends DigitDecompositionEventDescriptorV0TLV { + override val isSigned: Boolean = false +} object DigitDecompositionEventDescriptorV0TLV extends TLVFactory[DigitDecompositionEventDescriptorV0TLV] { diff --git a/docs/applications/server.md b/docs/applications/server.md index af050a953a..1d7f74e4f9 100644 --- a/docs/applications/server.md +++ b/docs/applications/server.md @@ -226,6 +226,14 @@ the `-p 9999:9999` port mapping on the docker container to adjust for this. - `passphrase` - The passphrase to encrypt the wallet with ### DLC + - `decodecontractinfo` `contractinfo` - Decodes a contract info into json + - `contractinfo` - Hex encoded contract info + - `decodeoffer` `offer` - Decodes an offer message into json + - `offer` - Hex encoded dlc offer message + - `decodeannouncement` `announcement` - Decodes an oracle announcement message into json + - `announcement` - Hex encoded oracle announcement message + - `decodeattestments` `attestments` - Decodes an oracle attestments message into json + - `attestments` - Hex encoded oracle attestments message - `getdlchostaddress` - Returns the public listening address of the DLC Node - `createdlcoffer` `contractInfo` `collateral` `[feerate]` `locktime` `refundlocktime` - Creates a DLC offer that another party can accept - `contractInfo` - Hex encoded contractInfo message diff --git a/docs/wallet/dlc.md b/docs/wallet/dlc.md index 349d127a57..bf6f0f4a1d 100644 --- a/docs/wallet/dlc.md +++ b/docs/wallet/dlc.md @@ -37,7 +37,151 @@ Both parties must agree on all fields from the table below: | refundlocktime | Locktime of the Refund Transaction | | feerate | Fee rate in sats/vbyte | -> Note: if you wish to set up your own oracle for testing, you can do so by checking out our [oracle rpc server](../oracle/oracle-server.md) or [Krystal Bull](https://github.com/benthecarman/krystal-bull) +> Note: if you wish to set up your own oracle for testing, you can do so by checking out our [oracle rpc server](../oracle/oracle-server.md) or [Krystal Bull](https://github.com/bitcoin-s/krystal-bull) + +### Decoding DLC Messages + +With the cli tool you can decode dlc offers, contract infos, oracle announcements, and oracle attestations. +This can be helpful for when developing on top of bitcoin-s and not having the proper DLC tooling to understand the messages. + +Examples: + +Decoding Offer: +```bash +bitcoin-s-cli decodeoffer a71a006fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000fdd82ee40000000000010f2cfda7101802035965730000000000010f2c024e6f0000000000000000fda712bcfdd824b8d89fe4d84bdf96299d2e3664acda7e14001297f341afcaca91d3909f12988cb308f309625d89c078bf2268b174faf1c082f1f10bef55834af50b91d9a241ffb2b8b005b07acf849ad2cec22107331dedbf5a607654fad4eafe39c278e27dde68fdd822540001ef8bd9f4445543c63923dde8c00f60d649c445a7772ac4693136b2fd234fcc2f60dfa880fdd80609000203596573024e6f20446f657320746865206d656d706f6f6c20636c656172206f6e20372f322f323102279cf17c387d7101b08d90b2aeef46231957d9d3558f5871e62781a68e7d75c9001600148dc81a3097bf28c6020eaf89cefae69c5f31aceb5d655d867008d23e00000000000090880001fda714fd01b3627231e09b7948c3019d02000000000102368af13baeef5a313d3ef12e895b232f1e76212cb76957d7e4b8a664915d15960100000000fdfffffffaec4ef43ae2365d0abc53336188053da4078a5b7489928602bcc8c9a4b75f0d0000000000fdffffff030f0b000000000000160014107eb2463ad25843ed01fd33656c83bdd11db3554a8701000000000022002002265c41f299b3c7f0dda5b9d7bc4135d25c2d8aed286837aa9e0954d70894d606961c000000000016001482a7ecd4624e6856d1b09c27e9f3a82323f49c2d0247304402200911162e99e23e4a26e0219a4bdaaf7a5f790be63a8376516640dcc0860ca4d602203a078371904842e2e6adfbebebcac138db23514b3396851569f5f2bf82c3197a012103cd2d0a4eace5993ebb0a75da85d9070627e64f1a5783cf5217e9bd82d20d1c470247304402200fa8515323410ca2b14b08bf22c618b6ced77b9289cb1dfa7ac10548e2e1b2e002206a407bcafdfb64009182fb5838db9671968abdd902e1194f6883cd0e5413ad36012103d3864eb755690e2b4ff139047419373e36a05630429da76fef5de25eeffb4ffc0000000000000002fffffffd006b000000160014d13be5f330b1ea9bbf61f68002b4465e02c341d9df27e1460161e002825311ec94d1e91b000000000000000a0000000061316580 +{ + "contractFlags": "0", + "chainHash": "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000", + "contractInfo": { + "totalCollateral": 69420, + "contractDescriptor": { + "outcomes": [ + { + "outcome": "Yes", + "localPayout": 69420 + }, + { + "outcome": "No", + "localPayout": 0 + } + ] + }, + "oracleInfo": { + "announcement": { + "announcementSignature": "d89fe4d84bdf96299d2e3664acda7e14001297f341afcaca91d3909f12988cb308f309625d89c078bf2268b174faf1c082f1f10bef55834af50b91d9a241ffb2", + "publicKey": "b8b005b07acf849ad2cec22107331dedbf5a607654fad4eafe39c278e27dde68", + "event": { + "nonces": [ + "ef8bd9f4445543c63923dde8c00f60d649c445a7772ac4693136b2fd234fcc2f" + ], + "maturity": "2021-07-03T00:00:00Z", + "descriptor": { + "outcomes": [ + "Yes", + "No" + ] + }, + "eventId": "Does the mempool clear on 7/2/21" + } + } + } + }, + "fundingPubKey": "02279cf17c387d7101b08d90b2aeef46231957d9d3558f5871e62781a68e7d75c9", + "payoutSPK": "1600148dc81a3097bf28c6020eaf89cefae69c5f31aceb", + "payoutSerialId": 6729888050161701888, + "offerCollateralSatoshis": 37000, + "fundingInputs": [ + { + "inputSerialId": 7093787203812804608, + "prevTx": "02000000000102368af13baeef5a313d3ef12e895b232f1e76212cb76957d7e4b8a664915d15960100000000fdfffffffaec4ef43ae2365d0abc53336188053da4078a5b7489928602bcc8c9a4b75f0d0000000000fdffffff030f0b000000000000160014107eb2463ad25843ed01fd33656c83bdd11db3554a8701000000000022002002265c41f299b3c7f0dda5b9d7bc4135d25c2d8aed286837aa9e0954d70894d606961c000000000016001482a7ecd4624e6856d1b09c27e9f3a82323f49c2d0247304402200911162e99e23e4a26e0219a4bdaaf7a5f790be63a8376516640dcc0860ca4d602203a078371904842e2e6adfbebebcac138db23514b3396851569f5f2bf82c3197a012103cd2d0a4eace5993ebb0a75da85d9070627e64f1a5783cf5217e9bd82d20d1c470247304402200fa8515323410ca2b14b08bf22c618b6ced77b9289cb1dfa7ac10548e2e1b2e002206a407bcafdfb64009182fb5838db9671968abdd902e1194f6883cd0e5413ad36012103d3864eb755690e2b4ff139047419373e36a05630429da76fef5de25eeffb4ffc00000000", + "prevTxVout": 2, + "sequence": 4294967293, + "maxWitnessLen": 107, + "redeemScript": null + } + ], + "changeSPK": "160014d13be5f330b1ea9bbf61f68002b4465e02c341d9", + "changeSerialId": 1.6080068685336797E19, + "fundOutputSerialId": 9.390869355804355E18, + "feeRatePerVb": 10, + "cetLocktime": 0, + "refundLocktime": 1630627200 +} +``` + +Decoding Contract Info: +```bash +bitcoin-s-cli decodecontractinfo fdd82ee40000000000010f2cfda7101802035965730000000000010f2c024e6f0000000000000000fda712bcfdd824b8d89fe4d84bdf96299d2e3664acda7e14001297f341afcaca91d3909f12988cb308f309625d89c078bf2268b174faf1c082f1f10bef55834af50b91d9a241ffb2b8b005b07acf849ad2cec22107331dedbf5a607654fad4eafe39c278e27dde68fdd822540001ef8bd9f4445543c63923dde8c00f60d649c445a7772ac4693136b2fd234fcc2f60dfa880fdd80609000203596573024e6f20446f657320746865206d656d706f6f6c20636c656172206f6e20372f322f3231 +{ + "totalCollateral": 69420, + "contractDescriptor": { + "outcomes": [ + { + "outcome": "Yes", + "localPayout": 69420 + }, + { + "outcome": "No", + "localPayout": 0 + } + ] + }, + "oracleInfo": { + "announcement": { + "announcementSignature": "d89fe4d84bdf96299d2e3664acda7e14001297f341afcaca91d3909f12988cb308f309625d89c078bf2268b174faf1c082f1f10bef55834af50b91d9a241ffb2", + "publicKey": "b8b005b07acf849ad2cec22107331dedbf5a607654fad4eafe39c278e27dde68", + "event": { + "nonces": [ + "ef8bd9f4445543c63923dde8c00f60d649c445a7772ac4693136b2fd234fcc2f" + ], + "maturity": "2021-07-03T00:00:00Z", + "descriptor": { + "outcomes": [ + "Yes", + "No" + ] + }, + "eventId": "Does the mempool clear on 7/2/21" + } + } + } +``` + +Decoding Oracle Announcement: +```bash +bitcoin-s-cli decodeannouncement fdd824b1585e18c3bc1d922854329fdb5a402713b161b7baebb6b6f3249936ef79c0a6671027afd7a1126d79e589811042eb4885c0cb7c150b4c4863a1e35a2ac432f7c81d5dcdba2e64cb116cc0c375a0856298f0058b778f46bfe625ac6576204889e4fdd8224d0001424c11a44c2e522f90bbe4abab6ec1bc8ab44c9b29316ce6e1d0d7d08385a474603c2e80fdd80609000203594553024e4f194254432d5553442d4f5645522d35304b2d434f494e42415345 +{ + "announcementSignature": "585e18c3bc1d922854329fdb5a402713b161b7baebb6b6f3249936ef79c0a6671027afd7a1126d79e589811042eb4885c0cb7c150b4c4863a1e35a2ac432f7c8", + "publicKey": "1d5dcdba2e64cb116cc0c375a0856298f0058b778f46bfe625ac6576204889e4", + "event": { + "nonces": [ + "424c11a44c2e522f90bbe4abab6ec1bc8ab44c9b29316ce6e1d0d7d08385a474" + ], + "maturity": "2021-03-01T00:00:00Z", + "descriptor": { + "outcomes": [ + "YES", + "NO" + ] + }, + "eventId": "BTC-USD-OVER-50K-COINBASE" + } +} +``` + +Decoding Oracle Attestations +```bash +bitcoin-s-cli decodeattestments fdd8687f194254432d5553442d4f5645522d35304b2d434f494e424153451d5dcdba2e64cb116cc0c375a0856298f0058b778f46bfe625ac6576204889e40001424c11a44c2e522f90bbe4abab6ec1bc8ab44c9b29316ce6e1d0d7d08385a474de6b75f1da183a2a4f9ad144b48bf1026cee9687221df58f04128db79ca17e2a024e4f +{ + "eventId": "BTC-USD-OVER-50K-COINBASE", + "signatures": [ + "424c11a44c2e522f90bbe4abab6ec1bc8ab44c9b29316ce6e1d0d7d08385a474de6b75f1da183a2a4f9ad144b48bf1026cee9687221df58f04128db79ca17e2a" + ], + "values": [ + "NO" + ] +} +``` ## Step 3: Set up The DLC @@ -175,4 +319,3 @@ If the `refundlocktime` for the DLC has been reached, you can get the fully-sign ```bashrc ./app/cli/target/universal/stage/bitcoin-s-cli executedlcrefund [contractId] ``` -