What changed, and why it matters
This commit adds a new notification event called ChannelFundingCreated to the Eclair Lightning node. It tells external listeners when a channel funding or splice transaction has been signed and is ready to be published. The change is purely additive: it emits an event at existing points in the code and exposes it over the WebSocket API. It does not alter security-critical logic such as transaction signing, validation, or channel state transitions. The release notes explicitly say the event is handy for detecting peers that use black-listed inputs so operators can close the channel early, but that is a monitoring/operational use case, not a vulnerability fix.
No security action required. Treat as a normal feature addition. Operators who want to detect black-listed inputs can subscribe to the new channel-funding-created WebSocket event and implement their own closure policy.
Security signals we found
New event emission only; no change to validation or signing logic
Event data is already internally available at the publish sites
Release notes describe operational monitoring use case (black-listed inputs), not a security bug
Minor pattern-match simplification in dual-funding abort path with no observable behavior change
Evidence from the diff
The patch introduces a ChannelFundingCreated case class and publishes it on the Akka event stream in four places: single-funded channel open (both funder and fundee sides), dual-funded channel open (both sides), and splice completion in normal channels. It also adds JSON serialization and forwards the event to WebSocket clients. A minor cleanup changes a collect pattern in ChannelOpenDualFunded from matching purchase to wildcard. No cryptographic checks, funding validation, or state-machine rules are changed. The event carries the channel id, remote node id, funding transaction id/index, and current commitments, which are already known at these code points.
Changed components
eclair-core channel event streameclair-node WebSocket APIchannel open FSMs (single-funded, dual-funded, splicing)Inspect captured patch +57 / −6
diff --git a/docs/release-notes/eclair-vnext.md b/docs/release-notes/eclair-vnext.md
index 8abe2c1..006d8a9 100644
--- a/docs/release-notes/eclair-vnext.md
+++ b/docs/release-notes/eclair-vnext.md
@@ -15,9 +15,13 @@ Eclair emits several events during a channel lifecycle, which can be received by
We reworked these events to be compatible with splicing and consistent with 0-conf:
- we removed the `channel-opened` event
+- we introduced a `channel-funding-created` event
- we introduced a `channel-confirmed` event
- we introduced a `channel-ready` event
+The `channel-funding-created` event is emitted when the funding transaction or a splice transaction has been signed and can be published.
+Listeners can use the `fundingTxIndex` to detect whether this is the initial channel funding (`fundingTxIndex = 0`) or a splice (`fundingTxIndex > 0`).
+
The `channel-confirmed` event is emitted when the funding transaction or a splice transaction has enough confirmations.
Listeners can use the `fundingTxIndex` to detect whether this is the initial channel funding (`fundingTxIndex = 0`) or a splice (`fundingTxIndex > 0`).
@@ -46,7 +50,7 @@ eclair.relay.reserved-for-accountable = 0.0
### API changes
- `findroute`, `findroutetonode` and `findroutebetweennodes` now include a `maxCltvExpiryDelta` parameter (#3234)
-- `channel-opened` was removed from the websocket in favor of `channel-confirmed` and `channel-ready` (#3237)
+- `channel-opened` was removed from the websocket in favor of `channel-funding-created`, `channel-confirmed` and `channel-ready` (#3237 and #3256)
### Miscellaneous improvements and bug fixes
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala
index cee5e99..971e315 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/ChannelEvents.scala
@@ -52,6 +52,11 @@ case class ShortChannelIdAssigned(channel: ActorRef, channelId: ByteVector32, an
/** This event will be sent if a channel was aborted before completing the opening flow. */
case class ChannelAborted(channel: ActorRef, remoteNodeId: PublicKey, channelId: ByteVector32) extends ChannelEvent
+/** This event is sent once a funding transaction (channel creation or splice) is ready to be published. */
+case class ChannelFundingCreated(channel: ActorRef, channelId: ByteVector32, remoteNodeId: PublicKey, fundingTx: Either[TxId, Transaction], fundingTxIndex: Long, commitments: Commitments) extends ChannelEvent {
+ val fundingTxId: TxId = fundingTx.fold(txId => txId, tx => tx.txid)
+}
+
/** This event is sent once a funding transaction (channel creation or splice) has been confirmed. */
case class ChannelFundingConfirmed(channel: ActorRef, channelId: ByteVector32, remoteNodeId: PublicKey, fundingTxId: TxId, fundingTxIndex: Long, blockHeight: BlockHeight, commitments: Commitments) extends ChannelEvent
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala
index 28ae5b4..5ab4177 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/Channel.scala
@@ -685,6 +685,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
val minDepth_opt = d.commitments.channelParams.minDepth(nodeParams.channelConf.minDepth)
watchFundingConfirmed(signingSession.fundingTx.txId, minDepth_opt, delay_opt = None)
val commitments1 = d.commitments.add(signingSession1.commitment)
+ context.system.eventStream.publish(ChannelFundingCreated(self, d.channelId, remoteNodeId, Right(signingSession1.fundingTx.signedTx_opt.getOrElse(signingSession1.fundingTx.sharedTx.tx.buildUnsignedTx())), signingSession1.commitment.fundingTxIndex, commitments1))
val d1 = d.copy(commitments = commitments1, spliceStatus = SpliceStatus.NoSplice)
stay() using d1 storing() sending signingSession1.localSigs calling endQuiescence(d1)
}
@@ -1457,6 +1458,7 @@ class Channel(val nodeParams: NodeParams, val channelKeys: ChannelKeys, val wall
val minDepth_opt = d.commitments.channelParams.minDepth(nodeParams.channelConf.minDepth)
watchFundingConfirmed(signingSession.fundingTx.txId, minDepth_opt, delay_opt = None)
val commitments1 = d.commitments.add(signingSession1.commitment)
+ context.system.eventStream.publish(ChannelFundingCreated(self, d.channelId, remoteNodeId, Right(signingSession1.fundingTx.signedTx_opt.getOrElse(signingSession1.fundingTx.sharedTx.tx.buildUnsignedTx())), signingSession1.commitment.fundingTxIndex, commitments1))
val d1 = d.copy(commitments = commitments1, spliceStatus = SpliceStatus.NoSplice)
log.info("publishing funding tx for channelId={} fundingTxId={}", d.channelId, signingSession1.fundingTx.sharedTx.txId)
Metrics.recordSplice(signingSession1.fundingTx.fundingParams, signingSession1.fundingTx.sharedTx.tx)
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala
index b051e08..5ec2f91 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenDualFunded.scala
@@ -383,6 +383,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers {
remotePerCommitmentSecrets = ShaChain.init,
originChannels = Map.empty
)
+ context.system.eventStream.publish(ChannelFundingCreated(self, d.channelId, remoteNodeId, Right(signingSession1.fundingTx.signedTx_opt.getOrElse(signingSession1.fundingTx.sharedTx.tx.buildUnsignedTx())), signingSession1.commitment.fundingTxIndex, commitments))
val d1 = DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED(commitments, d.localPushAmount, d.remotePushAmount, nodeParams.currentBlockHeight, nodeParams.currentBlockHeight, DualFundingStatus.WaitingForConfirmations, None)
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) using d1 storing() sending signingSession1.localSigs
}
@@ -406,6 +407,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers {
remotePerCommitmentSecrets = ShaChain.init,
originChannels = Map.empty
)
+ context.system.eventStream.publish(ChannelFundingCreated(self, d.channelId, remoteNodeId, Right(signingSession.fundingTx.signedTx_opt.getOrElse(signingSession.fundingTx.sharedTx.tx.buildUnsignedTx())), signingSession.commitment.fundingTxIndex, commitments))
val d1 = DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED(commitments, d.localPushAmount, d.remotePushAmount, nodeParams.currentBlockHeight, nodeParams.currentBlockHeight, DualFundingStatus.WaitingForConfirmations, None)
goto(WAIT_FOR_DUAL_FUNDING_CONFIRMED) using d1 storing() sending signingSession.localSigs calling publishFundingTx(signingSession.fundingTx)
}
@@ -413,7 +415,7 @@ trait ChannelOpenDualFunded extends DualFundingHandlers with ErrorHandlers {
log.info("our peer aborted the dual funding flow: ascii='{}' bin={}", msg.toAscii, msg.data)
rollbackFundingAttempt(d.signingSession.fundingTx.tx, Nil)
d.signingSession.liquidityPurchase_opt.collect {
- case purchase if !d.signingSession.fundingParams.isInitiator => peer ! LiquidityPurchaseAborted(d.channelId, d.signingSession.fundingTx.txId, d.signingSession.fundingTxIndex)
+ case _ if !d.signingSession.fundingParams.isInitiator => peer ! LiquidityPurchaseAborted(d.channelId, d.signingSession.fundingTx.txId, d.signingSession.fundingTxIndex)
}
goto(CLOSED) using IgnoreClosedData(d) sending TxAbort(d.channelId, DualFundingAborted(d.channelId).getMessage)
case msg: InteractiveTxConstructionMessage =>
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala
index 1f10caa..ba82d37 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/channel/fsm/ChannelOpenSingleFunded.scala
@@ -334,6 +334,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers {
txPublisher ! SetChannelId(remoteNodeId, channelId)
context.system.eventStream.publish(ChannelIdAssigned(self, remoteNodeId, temporaryChannelId, channelId))
context.system.eventStream.publish(ChannelSignatureReceived(self, commitments))
+ context.system.eventStream.publish(ChannelFundingCreated(self, channelId, remoteNodeId, Left(commitment.fundingTxId), commitment.fundingTxIndex, commitments))
// NB: we don't send a ChannelSignatureSent for the first commit
log.info("waiting for them to publish the funding tx for channelId={} fundingTxid={}", channelId, commitment.fundingTxId)
watchFundingConfirmed(commitment.fundingTxId, d.channelParams.minDepth(nodeParams.channelConf.minDepth), delay_opt = None)
@@ -391,6 +392,7 @@ trait ChannelOpenSingleFunded extends SingleFundingHandlers with ErrorHandlers {
originChannels = Map.empty)
val blockHeight = nodeParams.currentBlockHeight
context.system.eventStream.publish(ChannelSignatureReceived(self, commitments))
+ context.system.eventStream.publish(ChannelFundingCreated(self, d.channelId, remoteNodeId, Right(d.fundingTx), commitment.fundingTxIndex, commitments))
log.info("publishing funding tx fundingTxId={}", commitment.fundingTxId)
watchFundingConfirmed(commitment.fundingTxId, d.channelParams.minDepth(nodeParams.channelConf.minDepth), delay_opt = None)
// we will publish the funding tx only after the channel state has been written to disk because we want to
diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala
index bea4040..11bb739 100644
--- a/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala
+++ b/eclair-core/src/main/scala/fr/acinq/eclair/json/JsonSerializers.scala
@@ -560,6 +560,13 @@ object ChannelEventSerializer extends MinimalSerializer({
JField("commitTxFeeratePerKw", JLong(e.commitTxFeerate.toLong)),
JField("fundingTxFeeratePerKw", e.fundingTxFeerate.map(f => JLong(f.toLong)).getOrElse(JNothing))
)
+ case e: ChannelFundingCreated => JObject(
+ JField("type", JString("channel-funding-created")),
+ JField("remoteNodeId", JString(e.remoteNodeId.toString())),
+ JField("channelId", JString(e.channelId.toHex)),
+ JField("fundingTxId", JString(e.fundingTxId.value.toHex)),
+ JField("fundingTxIndex", JLong(e.fundingTxIndex)),
+ )
case e: ChannelFundingConfirmed => JObject(
JField("type", JString("channel-confirmed")),
JField("remoteNodeId", JString(e.remoteNodeId.toString())),
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala
index 05b0909..7c2efb1 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForDualFundingSignedStateSpec.scala
@@ -97,8 +97,11 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
test("complete interactive-tx protocol", Tag(ChannelStateTestsTags.DualFunding)) { f =>
import f._
- val listener = TestProbe()
- alice.underlyingActor.context.system.eventStream.subscribe(listener.ref, classOf[TransactionPublished])
+ val listenerA = TestProbe()
+ alice.underlyingActor.context.system.eventStream.subscribe(listenerA.ref, classOf[TransactionPublished])
+ alice.underlyingActor.context.system.eventStream.subscribe(listenerA.ref, classOf[ChannelFundingCreated])
+ val listenerB = TestProbe()
+ bob.underlyingActor.context.system.eventStream.subscribe(listenerB.ref, classOf[ChannelFundingCreated])
bob2alice.expectMsgType[CommitSig]
bob2alice.forward(alice)
@@ -106,17 +109,19 @@ class WaitForDualFundingSignedStateSpec extends TestKitBaseClass with FixtureAny
alice2bob.forward(bob)
// Bob sends its signatures first as he contributed less than Alice.
- bob2alice.expectMsgType[TxSignatures]
+ bob2alice.expectMsgType[TxSignatures].txId
awaitCond(bob.stateName == WAIT_FOR_DUAL_FUNDING_CONFIRMED)
val bobData = bob.stateData.asInstanceOf[DATA_WAIT_FOR_DUAL_FUNDING_CONFIRMED]
assert(bobData.commitments.channelParams.channelFeatures.hasFeature(Features.DualFunding))
assert(bobData.latestFundingTx.sharedTx.isInstanceOf[PartiallySignedSharedTransaction])
val fundingTxId = bobData.latestFundingTx.sharedTx.asInstanceOf[PartiallySignedSharedTransaction].txId
assert(bob2blockchain.expectMsgType[WatchFundingConfirmed].txId == fundingTxId)
+ assert(listenerB.expectMsgType[ChannelFundingCreated].fundingTxId == fundingTxId)
// Alice receives Bob's signatures and sends her own signatures.
bob2alice.forward(alice)
- assert(listener.expectMsgType[TransactionPublished].tx.txid == fundingTxId)
+ assert(listenerA.expectMsgType[ChannelFundingCreated].fundingTxId == fundingTxId)
+ assert(listenerA.expectMsgType[TransactionPublished].tx.txid == fundingTxId)
assert(alice2blockchain.expectMsgType[WatchFundingConfirmed].txId == fundingTxId)
alice2bob.expectMsgType[TxSignatures]
awaitCond(alice.stateName == WAIT_FOR_DUAL_FUNDING_CONFIRMED)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala
index c4a77c0..b551d9c 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingCreatedStateSpec.scala
@@ -70,6 +70,7 @@ class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFun
test("recv FundingCreated") { f =>
import f._
+ bob.underlying.system.eventStream.subscribe(listener.ref, classOf[ChannelFundingCreated])
alice2bob.expectMsgType[FundingCreated]
alice2bob.forward(bob)
awaitCond(bob.stateName == WAIT_FOR_FUNDING_CONFIRMED)
@@ -77,6 +78,8 @@ class WaitForFundingCreatedStateSpec extends TestKitBaseClass with FixtureAnyFun
bob2blockchain.expectMsgType[TxPublisher.SetChannelId]
val watchConfirmed = bob2blockchain.expectMsgType[WatchFundingConfirmed]
assert(watchConfirmed.minDepth == Bob.nodeParams.channelConf.minDepth)
+ val fundingCreated = listener.expectMsgType[ChannelFundingCreated]
+ assert(fundingCreated.fundingTx == Left(watchConfirmed.txId))
}
test("recv FundingCreated (funder can't pay fees)", Tag(FunderBelowCommitFees)) { f =>
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala
index 08fc290..8f31571 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/b/WaitForFundingSignedStateSpec.scala
@@ -80,12 +80,16 @@ class WaitForFundingSignedStateSpec extends TestKitBaseClass with FixtureAnyFunS
import f._
val listener = TestProbe()
alice.underlying.system.eventStream.subscribe(listener.ref, classOf[TransactionPublished])
+ alice.underlying.system.eventStream.subscribe(listener.ref, classOf[ChannelFundingCreated])
bob2alice.expectMsgType[FundingSigned]
bob2alice.forward(alice)
awaitCond(alice.stateName == WAIT_FOR_FUNDING_CONFIRMED)
val watchConfirmed = alice2blockchain.expectMsgType[WatchFundingConfirmed]
val fundingTxId = watchConfirmed.txId
assert(watchConfirmed.minDepth == 6)
+ val fundingCreated = listener.expectMsgType[ChannelFundingCreated]
+ assert(fundingCreated.fundingTx.isRight)
+ assert(fundingCreated.fundingTx.toOption.map(_.txid).contains(fundingTxId))
val txPublished = listener.expectMsgType[TransactionPublished]
assert(txPublished.tx.txid == fundingTxId)
assert(txPublished.miningFee > 0.sat)
diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala
index f9bd932..17e2aeb 100644
--- a/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala
+++ b/eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalSplicesStateSpec.scala
@@ -1394,6 +1394,7 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
systemA.eventStream.subscribe(aliceEvents.ref, classOf[AvailableBalanceChanged])
systemA.eventStream.subscribe(aliceEvents.ref, classOf[LocalChannelUpdate])
systemA.eventStream.subscribe(aliceEvents.ref, classOf[LocalChannelDown])
+ systemA.eventStream.subscribe(aliceEvents.ref, classOf[ChannelFundingCreated])
systemB.eventStream.subscribe(bobEvents.ref, classOf[ForgetHtlcInfos])
systemB.eventStream.subscribe(bobEvents.ref, classOf[AvailableBalanceChanged])
systemB.eventStream.subscribe(bobEvents.ref, classOf[LocalChannelUpdate])
@@ -1402,6 +1403,10 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
val fundingInput = alice.commitments.latest.fundingInput
val fundingTx1 = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)))
checkWatchConfirmed(f, fundingTx1)
+ inside(aliceEvents.expectMsgType[ChannelFundingCreated]) { e =>
+ assert(e.fundingTx == Right(fundingTx1))
+ assert(e.fundingTxIndex == 1)
+ }
bob ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx1)
bob2blockchain.expectMsgTypeHaving[WatchFundingSpent](_.txId == fundingTx1.txid)
@@ -1413,6 +1418,10 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
val fundingTx2 = initiateSplice(f, spliceIn_opt = Some(SpliceIn(500_000 sat)))
checkWatchConfirmed(f, fundingTx2)
+ inside(aliceEvents.expectMsgType[ChannelFundingCreated]) { e =>
+ assert(e.fundingTx == Right(fundingTx2))
+ assert(e.fundingTxIndex == 2)
+ }
alice ! WatchFundingConfirmedTriggered(BlockHeight(400000), 42, fundingTx1)
alice2bob.expectMsgType[SpliceLocked]
diff --git a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/WebSocket.scala b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/WebSocket.scala
index 1698e67..e5a771d 100644
--- a/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/WebSocket.scala
+++ b/eclair-node/src/main/scala/fr/acinq/eclair/api/handlers/WebSocket.scala
@@ -52,6 +52,7 @@ trait WebSocket {
override def preStart(): Unit = {
context.system.eventStream.subscribe(self, classOf[PaymentEvent])
context.system.eventStream.subscribe(self, classOf[ChannelCreated])
+ context.system.eventStream.subscribe(self, classOf[ChannelFundingCreated])
context.system.eventStream.subscribe(self, classOf[ChannelFundingConfirmed])
context.system.eventStream.subscribe(self, classOf[ChannelReadyForPayments])
context.system.eventStream.subscribe(self, classOf[ChannelStateChanged])
@@ -62,6 +63,7 @@ trait WebSocket {
def receive: Receive = {
case message: PaymentEvent => flowInput.offer(serialization.write(message))
case message: ChannelCreated => flowInput.offer(serialization.write(message))
+ case message: ChannelFundingCreated => flowInput.offer(serialization.write(message))
case message: ChannelFundingConfirmed => flowInput.offer(serialization.write(message))
case message: ChannelReadyForPayments => flowInput.offer(serialization.write(message))
case message: ChannelStateChanged =>
diff --git a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala
index fb1a12c..2456d87 100644
--- a/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala
+++ b/eclair-node/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala
@@ -1160,6 +1160,12 @@ class ApiServiceSpec extends AnyFunSuite with ScalatestRouteTest with IdiomaticM
system.eventStream.publish(chcr)
wsClient.expectMessage(expectedSerializedChcr)
+ val chfcr = ChannelFundingCreated(system.deadLetters, ByteVector32.One, bobNodeId, Left(fundingTxId), 0, null)
+ val expectedSerializedChfcr = """{"type":"channel-funding-created","remoteNodeId":"039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a3585","channelId":"0100000000000000000000000000000000000000000000000000000000000000","fundingTxId":"9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692","fundingTxIndex":0}"""
+ assert(serialization.write(chfcr) == expectedSerializedChfcr)
+ system.eventStream.publish(chfcr)
+ wsClient.expectMessage(expectedSerializedChfcr)
+
val chfc = ChannelFundingConfirmed(system.deadLetters, ByteVector32.One, bobNodeId, fundingTxId, 0, BlockHeight(900000), null)
val expectedSerializedChfc = """{"type":"channel-confirmed","remoteNodeId":"039dc0e0b1d25905e44fdf6f8e89755a5e219685840d0bc1d28d3308f9628a3585","channelId":"0100000000000000000000000000000000000000000000000000000000000000","fundingTxId":"9fcd45bbaa09c60c991ac0425704163c3f3d2d683c789fa409455b9c97792692","fundingTxIndex":0,"blockHeight":900000}"""
assert(serialization.write(chfc) == expectedSerializedChfc)
Why this scored 20/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.