Skip to content

Commit d7ab72a

Browse files
committed
Support bumping fees via anchor outputs
1 parent 146babe commit d7ab72a

File tree

13 files changed

+122
-43
lines changed

13 files changed

+122
-43
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/blockchain/bitcoind/rpc/BitcoinCoreClient.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ class BitcoinCoreClient(val rpcClient: BitcoinJsonRPCClient, val lockUtxos: Bool
305305
})
306306
}
307307

308-
private def utxoUpdatePsbt(psbt: Psbt)(implicit ec: ExecutionContext): Future[Psbt] = {
308+
def utxoUpdatePsbt(psbt: Psbt)(implicit ec: ExecutionContext): Future[Psbt] = {
309309
val encoded = Base64.getEncoder.encodeToString(Psbt.write(psbt).toByteArray)
310310
rpcClient.invoke("utxoupdatepsbt", encoded).map(json => {
311311
val JString(base64) = json

eclair-core/src/main/scala/fr/acinq/eclair/channel/Helpers.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,10 +1152,10 @@ object Helpers {
11521152
(localPaymentPubkey, remoteDelayedPaymentPubkey)
11531153
}
11541154
val claimAnchorTxs = List(
1155-
withTxGenerationLog("local-anchor") {
1156-
Transactions.makeClaimLocalAnchorOutputTx(rcp.commitTx, localAnchorKey, confirmationTarget)
1155+
withTxGenerationLog("local-anchor-from-remote-commit-tx") {
1156+
Transactions.makeClaimLocalAnchorOutputTx(rcp.commitTx, localAnchorKey, confirmationTarget).map(_.copy(fromRemoteCommitTx = true))
11571157
},
1158-
withTxGenerationLog("remote-anchor") {
1158+
withTxGenerationLog("remote-anchor-from-remote-commit-tx") {
11591159
Transactions.makeClaimRemoteAnchorOutputTx(rcp.commitTx, remoteAnchorKey)
11601160
}
11611161
).flatten

eclair-core/src/main/scala/fr/acinq/eclair/channel/publish/ReplaceableTxFunder.scala

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import fr.acinq.eclair.transactions.Transactions._
3232
import fr.acinq.eclair.{NodeParams, NotificationsLogger}
3333

3434
import scala.concurrent.{ExecutionContext, Future}
35+
import scala.jdk.CollectionConverters.ListHasAsScala
3536
import scala.util.{Failure, Success}
3637

3738
/**
@@ -49,7 +50,7 @@ object ReplaceableTxFunder {
4950
sealed trait Command
5051
case class FundTransaction(replyTo: ActorRef[FundingResult], cmd: TxPublisher.PublishReplaceableTx, tx: Either[FundedTx, ReplaceableTxWithWitnessData], targetFeerate: FeeratePerKw) extends Command
5152

52-
private case class AddInputsOk(tx: ReplaceableTxWithWitnessData, totalAmountIn: Satoshi) extends Command
53+
private case class AddInputsOk(tx: ReplaceableTxWithWitnessData, totalAmountIn: Satoshi, spentUtxos: Seq[TxOut]) extends Command
5354
private case class AddInputsFailed(reason: Throwable) extends Command
5455
private case class SignWalletInputsOk(signedTx: Transaction) extends Command
5556
private case class SignWalletInputsFailed(reason: Throwable) extends Command
@@ -237,7 +238,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
237238
private val log = context.log
238239

239240
def fund(txWithWitnessData: ReplaceableTxWithWitnessData, targetFeerate: FeeratePerKw): Behavior[Command] = {
240-
log.info("funding {} tx (targetFeerate={})", txWithWitnessData.txInfo.desc, targetFeerate)
241+
log.info("funding {} tx (targetFeerate={}) txId={}", txWithWitnessData.txInfo.desc, targetFeerate, txWithWitnessData.txInfo.tx.txid)
241242
txWithWitnessData match {
242243
case claimLocalAnchor: ClaimLocalAnchorWithWitnessData =>
243244
val commitFeerate = cmd.commitment.localCommit.spec.commitTxFeerate
@@ -254,7 +255,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
254255
val htlcFeerate = cmd.commitment.localCommit.spec.htlcTxFeerate(cmd.commitment.params.commitmentFormat)
255256
if (targetFeerate <= htlcFeerate) {
256257
log.debug("publishing {} without adding inputs: txid={}", cmd.desc, htlcTx.txInfo.tx.txid)
257-
sign(txWithWitnessData, htlcFeerate, htlcTx.txInfo.amountIn)
258+
sign(txWithWitnessData, htlcFeerate, htlcTx.txInfo.amountIn, None)
258259
} else {
259260
addWalletInputs(htlcTx, targetFeerate)
260261
}
@@ -266,7 +267,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
266267
replyTo ! FundingFailed(TxPublisher.TxRejectedReason.TxSkipped(retryNextBlock = true))
267268
Behaviors.stopped
268269
case Right(updatedClaimHtlcTx) =>
269-
sign(updatedClaimHtlcTx, targetFeerate, updatedClaimHtlcTx.txInfo.amountIn)
270+
sign(updatedClaimHtlcTx, targetFeerate, updatedClaimHtlcTx.txInfo.amountIn, None)
270271
}
271272
}
272273
}
@@ -280,7 +281,7 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
280281
Behaviors.stopped
281282
case AdjustPreviousTxOutputResult.TxOutputAdjusted(updatedTx) =>
282283
log.debug("bumping {} fees without adding new inputs: txid={}", cmd.desc, updatedTx.txInfo.tx.txid)
283-
sign(updatedTx, targetFeerate, previousTx.totalAmountIn)
284+
sign(updatedTx, targetFeerate, previousTx.totalAmountIn, None)
284285
case AdjustPreviousTxOutputResult.AddWalletInputs(tx) =>
285286
log.debug("bumping {} fees requires adding new inputs (feerate={})", cmd.desc, targetFeerate)
286287
// We restore the original transaction (remove previous attempt's wallet inputs).
@@ -291,13 +292,13 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
291292

292293
private def addWalletInputs(txWithWitnessData: ReplaceableTxWithWalletInputs, targetFeerate: FeeratePerKw): Behavior[Command] = {
293294
context.pipeToSelf(addInputs(txWithWitnessData, targetFeerate, cmd.commitment)) {
294-
case Success((fundedTx, totalAmountIn)) => AddInputsOk(fundedTx, totalAmountIn)
295+
case Success((fundedTx, totalAmountIn, psbt)) => AddInputsOk(fundedTx, totalAmountIn, psbt)
295296
case Failure(reason) => AddInputsFailed(reason)
296297
}
297298
Behaviors.receiveMessagePartial {
298-
case AddInputsOk(fundedTx, totalAmountIn) =>
299+
case AddInputsOk(fundedTx, totalAmountIn, spentUtxos) =>
299300
log.debug("added {} wallet input(s) and {} wallet output(s) to {}", fundedTx.txInfo.tx.txIn.length - 1, fundedTx.txInfo.tx.txOut.length - 1, cmd.desc)
300-
sign(fundedTx, targetFeerate, totalAmountIn)
301+
sign(fundedTx, targetFeerate, totalAmountIn, Some(spentUtxos))
301302
case AddInputsFailed(reason) =>
302303
if (reason.getMessage.contains("Insufficient funds")) {
303304
val nodeOperatorMessage =
@@ -315,17 +316,33 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
315316
}
316317
}
317318

318-
private def sign(fundedTx: ReplaceableTxWithWitnessData, txFeerate: FeeratePerKw, amountIn: Satoshi): Behavior[Command] = {
319+
private def sign(fundedTx: ReplaceableTxWithWitnessData, txFeerate: FeeratePerKw, amountIn: Satoshi, spentUtxos: Option[Seq[TxOut]]): Behavior[Command] = {
319320
val channelKeyPath = keyManager.keyPath(cmd.commitment.localParams, cmd.commitment.params.channelConfig)
320321
fundedTx match {
321322
case claimAnchorTx: ClaimLocalAnchorWithWitnessData =>
322-
val localSig = keyManager.sign(claimAnchorTx.txInfo, keyManager.fundingPublicKey(cmd.commitment.localParams.fundingKeyPath, cmd.commitment.fundingTxIndex), TxOwner.Local, cmd.commitment.params.commitmentFormat)
323+
log.info(s"signing ${claimAnchorTx.txInfo.tx} with spentUtxos $spentUtxos")
324+
val localSig = fundedTx.txInfo.input match {
325+
case _: InputInfo.SegwitInput =>
326+
keyManager.sign(claimAnchorTx.txInfo, keyManager.fundingPublicKey(cmd.commitment.localParams.fundingKeyPath, cmd.commitment.fundingTxIndex), TxOwner.Local, cmd.commitment.params.commitmentFormat)
327+
case _: InputInfo.TaprootInput =>
328+
if (claimAnchorTx.txInfo.fromRemoteCommitTx) {
329+
keyManager.sign(claimAnchorTx.txInfo, keyManager.paymentPoint(channelKeyPath), TxOwner.Local, cmd.commitment.params.commitmentFormat, spentUtxos)
330+
} else {
331+
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, cmd.commitment.localCommit.index)
332+
keyManager.sign(claimAnchorTx.txInfo, keyManager.delayedPaymentPoint(channelKeyPath), localPerCommitmentPoint, TxOwner.Local, cmd.commitment.params.commitmentFormat, spentUtxos)
333+
}
334+
}
323335
val signedTx = claimAnchorTx.copy(txInfo = addSigs(claimAnchorTx.txInfo, localSig))
324336
signWalletInputs(signedTx, txFeerate, amountIn)
325337
case htlcTx: HtlcWithWitnessData =>
326338
val localPerCommitmentPoint = keyManager.commitmentPoint(channelKeyPath, cmd.commitment.localCommit.index)
327339
val localHtlcBasepoint = keyManager.htlcPoint(channelKeyPath)
328-
val localSig = keyManager.sign(htlcTx.txInfo, localHtlcBasepoint, localPerCommitmentPoint, TxOwner.Local, cmd.commitment.params.commitmentFormat)
340+
val localSig = htlcTx.txInfo.input match {
341+
case _: InputInfo.SegwitInput =>
342+
keyManager.sign(htlcTx.txInfo, localHtlcBasepoint, localPerCommitmentPoint, TxOwner.Local, cmd.commitment.params.commitmentFormat)
343+
case _: InputInfo.TaprootInput =>
344+
keyManager.sign(htlcTx.txInfo, localHtlcBasepoint, localPerCommitmentPoint, TxOwner.Local, cmd.commitment.params.commitmentFormat, spentUtxos)
345+
}
329346
val signedTx = htlcTx match {
330347
case htlcSuccess: HtlcSuccessWithWitnessData => htlcSuccess.copy(txInfo = addSigs(htlcSuccess.txInfo, localSig, htlcSuccess.remoteSig, htlcSuccess.preimage, cmd.commitment.params.commitmentFormat))
331348
case htlcTimeout: HtlcTimeoutWithWitnessData => htlcTimeout.copy(txInfo = addSigs(htlcTimeout.txInfo, localSig, htlcTimeout.remoteSig, cmd.commitment.params.commitmentFormat))
@@ -361,13 +378,17 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
361378
case InputInfo.SegwitInput(_, _, redeemScript) => fr.acinq.bitcoin.Script.parse(redeemScript)
362379
case _: InputInfo.TaprootInput => null
363380
}
381+
val sigHash = locallySignedTx.txInfo.input match {
382+
case _: InputInfo.TaprootInput => fr.acinq.bitcoin.SigHash.SIGHASH_DEFAULT
383+
case _: InputInfo.SegwitInput => fr.acinq.bitcoin.SigHash.SIGHASH_ALL
384+
}
364385
val psbt = new Psbt(locallySignedTx.txInfo.tx)
365386
.updateWitnessInput(
366387
locallySignedTx.txInfo.input.outPoint,
367388
locallySignedTx.txInfo.input.txOut,
368389
null,
369390
witnessScript,
370-
fr.acinq.bitcoin.SigHash.SIGHASH_ALL,
391+
sigHash,
371392
java.util.Map.of(),
372393
null,
373394
null,
@@ -431,10 +452,26 @@ private class ReplaceableTxFunder(nodeParams: NodeParams,
431452
}
432453
}
433454

434-
private def addInputs(tx: ReplaceableTxWithWalletInputs, targetFeerate: FeeratePerKw, commitment: FullCommitment): Future[(ReplaceableTxWithWalletInputs, Satoshi)] = {
455+
private def addInputs(tx: ReplaceableTxWithWalletInputs, targetFeerate: FeeratePerKw, commitment: FullCommitment): Future[(ReplaceableTxWithWalletInputs, Satoshi, Seq[TxOut])] = {
456+
import fr.acinq.bitcoin.scalacompat.KotlinUtils._
457+
458+
def getSpentUtxos(txInfo: TransactionWithInputInfo): Future[Seq[TxOut]] = {
459+
val psbt = new Psbt(txInfo.tx)
460+
bitcoinClient.utxoUpdatePsbt(psbt).map(updatedPsbt => {
461+
// we get the first spent input from txInfo.input, additional inputs are wallet inputs and have been updated by bitcoin core
462+
txInfo.input.txOut +: updatedPsbt.inputs.asScala.tail.map(_.getWitnessUtxo).map(kmp2scala).toSeq
463+
})
464+
}
465+
435466
tx match {
436-
case anchorTx: ClaimLocalAnchorWithWitnessData => addInputs(anchorTx, targetFeerate, commitment)
437-
case htlcTx: HtlcWithWitnessData => addInputs(htlcTx, targetFeerate, commitment)
467+
case anchorTx: ClaimLocalAnchorWithWitnessData => for {
468+
(fundedTx, amountIn) <- addInputs(anchorTx, targetFeerate, commitment)
469+
spentUtxos <- getSpentUtxos(fundedTx.txInfo)
470+
} yield (fundedTx, amountIn, spentUtxos)
471+
case htlcTx: HtlcWithWitnessData => for {
472+
(fundedTx, amountIn) <- addInputs(htlcTx, targetFeerate, commitment)
473+
spentUtxos <- getSpentUtxos(fundedTx.txInfo)
474+
} yield (fundedTx, amountIn, spentUtxos)
438475
}
439476
}
440477

eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/ChannelKeyManager.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,9 @@ trait ChannelKeyManager {
8888
* @param commitmentFormat format of the commitment tx
8989
* @return a signature generated with the private key that matches the input extended public key
9090
*/
91-
def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64
91+
def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat, spentUtxos: Option[Seq[TxOut]]): ByteVector64
92+
93+
def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = sign(tx, publicKey, txOwner, commitmentFormat, None)
9294

9395
def partialSign(tx: TransactionWithInputInfo, localPublicKey: ExtendedPublicKey, remotePublicKey: PublicKey, txOwner: TxOwner, localNonce: (SecretNonce, IndividualNonce), remoteNextLocalNonce: IndividualNonce): Either[Throwable, ByteVector32] = {
9496
partialSign(tx.tx, tx.tx.txIn.indexWhere(_.outPoint == tx.input.outPoint), Seq(tx.input.txOut), localPublicKey, remotePublicKey, txOwner, localNonce, remoteNextLocalNonce)
@@ -106,7 +108,7 @@ trait ChannelKeyManager {
106108
* @param commitmentFormat format of the commitment tx
107109
* @return a signature generated with a private key generated from the input key's matching private key and the remote point.
108110
*/
109-
def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64
111+
def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat, spentUtxos: Option[Seq[TxOut]] = None): ByteVector64
110112

111113
/**
112114
* Ths method is used to spend revoked transactions, with the corresponding revocation key

eclair-core/src/main/scala/fr/acinq/eclair/crypto/keymanager/LocalChannelKeyManager.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,13 @@ class LocalChannelKeyManager(seed: ByteVector, chainHash: BlockHash) extends Cha
130130
* @param commitmentFormat format of the commitment tx
131131
* @return a signature generated with the private key that matches the input extended public key
132132
*/
133-
override def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = {
133+
override def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat, spentUtxos: Option[Seq[TxOut]]): ByteVector64 = {
134134
// NB: not all those transactions are actually commit txs (especially during closing), but this is good enough for monitoring purposes
135135
val tags = TagSet.Empty.withTag(Tags.TxOwner, txOwner.toString).withTag(Tags.TxType, Tags.TxTypes.CommitTx)
136136
Metrics.SignTxCount.withTags(tags).increment()
137137
KamonExt.time(Metrics.SignTxDuration.withTags(tags)) {
138138
val privateKey = privateKeys.get(publicKey.path)
139-
tx.sign(privateKey.privateKey, txOwner, commitmentFormat)
139+
tx.sign(privateKey.privateKey, txOwner, commitmentFormat, spentUtxos)
140140
}
141141
}
142142

@@ -160,14 +160,14 @@ class LocalChannelKeyManager(seed: ByteVector, chainHash: BlockHash) extends Cha
160160
* @param commitmentFormat format of the commitment tx
161161
* @return a signature generated with a private key generated from the input key's matching private key and the remote point.
162162
*/
163-
override def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = {
163+
override def sign(tx: TransactionWithInputInfo, publicKey: ExtendedPublicKey, remotePoint: PublicKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat, spentUtxos: Option[Seq[TxOut]] = None): ByteVector64 = {
164164
// NB: not all those transactions are actually htlc txs (especially during closing), but this is good enough for monitoring purposes
165165
val tags = TagSet.Empty.withTag(Tags.TxOwner, txOwner.toString).withTag(Tags.TxType, Tags.TxTypes.HtlcTx)
166166
Metrics.SignTxCount.withTags(tags).increment()
167167
KamonExt.time(Metrics.SignTxDuration.withTags(tags)) {
168168
val privateKey = privateKeys.get(publicKey.path)
169169
val currentKey = Generators.derivePrivKey(privateKey.privateKey, remotePoint)
170-
tx.sign(currentKey, txOwner, commitmentFormat)
170+
tx.sign(currentKey, txOwner, commitmentFormat, spentUtxos)
171171
}
172172
}
173173

eclair-core/src/main/scala/fr/acinq/eclair/transactions/Transactions.scala

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -173,15 +173,17 @@ object Transactions {
173173
case DefaultCommitmentFormat | _:AnchorOutputsCommitmentFormat => SIGHASH_ALL
174174
}
175175

176-
def sign(key: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat): ByteVector64 = {
177-
sign(key, sighash(txOwner, commitmentFormat))
176+
def sign(key: PrivateKey, txOwner: TxOwner, commitmentFormat: CommitmentFormat, spentUtxos: Option[Seq[TxOut]] = None): ByteVector64 = {
177+
sign(key, sighash(txOwner, commitmentFormat), spentUtxos)
178178
}
179179

180-
def sign(key: PrivateKey, sighashType: Int): ByteVector64 = input match {
180+
def sign(key: PrivateKey, sighashType: Int): ByteVector64 = sign(key, sighashType, None)
181+
182+
def sign(key: PrivateKey, sighashType: Int, spentUtxos: Option[Seq[TxOut]]): ByteVector64 = input match {
181183
case t: InputInfo.TaprootInput =>
182184
t.redeemPath match {
183-
case k: RedeemPath.KeyPath => Transaction.signInputTaprootKeyPath(key, tx, 0, Seq(input.txOut), sighashType, k.scriptTree_opt)
184-
case s: RedeemPath.ScriptPath => Transaction.signInputTaprootScriptPath(key, tx, 0, Seq(input.txOut), sighashType, s.leafHash)
185+
case k: RedeemPath.KeyPath => Transaction.signInputTaprootKeyPath(key, tx, 0, spentUtxos getOrElse Seq(input.txOut), sighashType, k.scriptTree_opt)
186+
case s: RedeemPath.ScriptPath => Transaction.signInputTaprootScriptPath(key, tx, 0, spentUtxos getOrElse Seq(input.txOut), sighashType, s.leafHash)
185187
}
186188
case InputInfo.SegwitInput(outPoint, txOut, redeemScript) =>
187189
// NB: the tx may have multiple inputs, we will only sign the one provided in txinfo.input. Bear in mind that the
@@ -287,7 +289,7 @@ object Transactions {
287289
}
288290

289291
sealed trait ClaimAnchorOutputTx extends TransactionWithInputInfo
290-
case class ClaimLocalAnchorOutputTx(input: InputInfo, tx: Transaction, confirmationTarget: ConfirmationTarget) extends ClaimAnchorOutputTx with ReplaceableTransactionWithInputInfo {
292+
case class ClaimLocalAnchorOutputTx(input: InputInfo, tx: Transaction, confirmationTarget: ConfirmationTarget, fromRemoteCommitTx: Boolean = false) extends ClaimAnchorOutputTx with ReplaceableTransactionWithInputInfo {
291293
override def desc: String = "local-anchor"
292294
}
293295

eclair-core/src/main/scala/fr/acinq/eclair/wire/internal/channel/version2/ChannelCodecs2.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ private[channel] object ChannelCodecs2 {
124124
val mainPenaltyTxCodec: Codec[MainPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[MainPenaltyTx]
125125
val htlcPenaltyTxCodec: Codec[HtlcPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[HtlcPenaltyTx]
126126
val claimHtlcDelayedOutputPenaltyTxCodec: Codec[ClaimHtlcDelayedOutputPenaltyTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimHtlcDelayedOutputPenaltyTx]
127-
val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget.upcast[ConfirmationTarget])).as[ClaimLocalAnchorOutputTx]
127+
val claimLocalAnchorOutputTxCodec: Codec[ClaimLocalAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("confirmationTarget" | defaultConfirmationTarget.upcast[ConfirmationTarget]) :: provide(false)).as[ClaimLocalAnchorOutputTx]
128128
val claimRemoteAnchorOutputTxCodec: Codec[ClaimRemoteAnchorOutputTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec)).as[ClaimRemoteAnchorOutputTx]
129129
val closingTxCodec: Codec[ClosingTx] = (("inputInfo" | inputInfoCodec) :: ("tx" | txCodec) :: ("outputIndex" | optional(bool8, outputInfoCodec))).as[ClosingTx]
130130

0 commit comments

Comments
 (0)