Skip to content

Commit 754a1bd

Browse files
committed
Add evicted transaction event
1 parent 9dd71eb commit 754a1bd

File tree

17 files changed

+1051
-278
lines changed

17 files changed

+1051
-278
lines changed

CHANGELOG.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,27 @@
66
- `OnchainTransactionReceived`: Emitted when a new unconfirmed transaction is
77
first detected in the mempool (instant notification for incoming payments!)
88
- `OnchainTransactionConfirmed`: Emitted when a transaction receives confirmations
9-
- `OnchainTransactionUnconfirmed`: Emitted when a previously confirmed transaction
10-
becomes unconfirmed (blockchain reorg)
9+
- `OnchainTransactionReplaced`: Emitted when a transaction is replaced (via RBF or different transaction using a commmon input)
10+
- `OnchainTransactionReorged`: Emitted when a previously confirmed transaction
11+
becomes unconfirmed due to a blockchain reorg
12+
- `OnchainTransactionEvicted`: Emitted when a transaction is evicted from the mempool
1113
- **Sync Completion Event** (fully implemented):
1214
- `SyncCompleted`: Emitted when onchain wallet sync finishes successfully
1315
- **Balance Change Event** (fully implemented):
1416
- `BalanceChanged`: Emitted when onchain or Lightning balances change, allowing
1517
applications to update balance displays immediately without polling
16-
- Added `TransactionContext` enum to onchain transaction events, which provides
17-
information about whether a transaction is related to channel funding, channel
18-
closure, or regular wallet activity. Applications can cross-reference with
19-
`ChannelPending` and `ChannelClosed` events to identify channel-related
20-
transactions.
18+
- Added `TransactionDetails`, `TxInput`, and `TxOutput` structs to provide comprehensive
19+
transaction information in onchain events, including inputs and outputs. This enables
20+
applications to analyze transaction data themselves to detect channel funding, closures,
21+
and other transaction types.
2122
- Added `SyncType` enum to distinguish between onchain wallet sync, Lightning
2223
wallet sync, and fee rate cache updates.
2324
- Balance tracking is now persisted in `NodeMetrics` to detect changes across restarts.
2425

26+
## Bug Fixes and Improvements
27+
- Fixed bug in `BitcoindRpc` eviction detection where transactions were incorrectly
28+
filtered (using `contains_key` instead of `!contains_key`).
29+
2530
# 0.6.2 - Aug. 14, 2025
2631
This patch release fixes a panic that could have been hit when syncing to a
2732
TLS-enabled Electrum server, as well as some minor issues when shutting down

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ldk-node"
3-
version = "0.6.2-rc.4"
3+
version = "0.6.2-rc.6"
44
authors = ["Elias Rohrer <[email protected]>"]
55
homepage = "https://lightningdevkit.org/"
66
license = "MIT OR Apache-2.0"

MOBILE_DEVELOPER_GUIDE.md

Lines changed: 184 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ These events notify you about Bitcoin transactions affecting your onchain wallet
2525
- **Use Case**: Show "Payment incoming!" notification immediately
2626
- **Fields**:
2727
- `txid`: Transaction ID
28-
- `amountSats`: Net amount (positive for incoming, negative for outgoing)
29-
- `context`: Type of transaction (channel-related or regular wallet)
28+
- `details`: `TransactionDetails` object with comprehensive transaction information
3029

3130
#### `OnchainTransactionConfirmed`
3231
- **When**: Transaction receives blockchain confirmations
@@ -36,14 +35,27 @@ These events notify you about Bitcoin transactions affecting your onchain wallet
3635
- `blockHash`: Block hash where confirmed
3736
- `blockHeight`: Block height
3837
- `confirmationTime`: Unix timestamp
39-
- `context`: Transaction type
38+
- `details`: `TransactionDetails` object with comprehensive transaction information
4039

41-
#### `OnchainTransactionUnconfirmed`
42-
- **When**: Previously confirmed transaction becomes unconfirmed (blockchain reorg)
40+
#### `OnchainTransactionReplaced`
41+
- **When**: Transaction is replaced via Replace-By-Fee (RBF)
42+
- **Use Case**: Update UI to show the transaction was replaced
43+
- **Fields**:
44+
- `txid`: Transaction ID of the replacement transaction
45+
46+
#### `OnchainTransactionReorged`
47+
- **When**: Previously confirmed transaction becomes unconfirmed due to blockchain reorg
4348
- **Use Case**: Mark transaction as pending again
4449
- **Fields**:
4550
- `txid`: Transaction ID
4651

52+
#### `OnchainTransactionEvicted`
53+
- **When**: Transaction is evicted from the mempool (no longer unconfirmed and not confirmed)
54+
- **Use Case**: Mark transaction as evicted, potentially allow user to rebroadcast
55+
- **Fields**:
56+
- `txid`: Transaction ID
57+
- **Note**: Works with all chain sources (Esplora, Electrum, and BitcoindRpc)
58+
4759
### 2. Sync Events
4860

4961
Track synchronization progress and completion:
@@ -77,13 +89,30 @@ Track synchronization progress and completion:
7789
- `oldTotalLightningBalanceSats`: Previous Lightning balance
7890
- `newTotalLightningBalanceSats`: New Lightning balance
7991

80-
### 4. Transaction Context
92+
### 4. Transaction Details
8193

82-
The `TransactionContext` enum helps identify what type of transaction you're dealing with:
94+
The `TransactionDetails` struct provides comprehensive information about transactions, allowing you to analyze transaction purposes yourself:
8395

84-
- `RegularWallet`: Normal Bitcoin transaction
85-
- `ChannelFunding`: Opening a Lightning channel
86-
- `ChannelClosure`: Closing a Lightning channel
96+
#### `TransactionDetails`
97+
- `amountSats`: Net amount in satoshis (positive for incoming, negative for outgoing)
98+
- `inputs`: Array of `TxInput` objects
99+
- `outputs`: Array of `TxOutput` objects
100+
101+
#### `TxInput`
102+
- `txid`: Previous transaction ID being spent
103+
- `vout`: Output index being spent
104+
- `scriptsig`: Script signature (hex-encoded)
105+
- `witness`: Witness stack (array of hex-encoded strings)
106+
- `sequence`: Sequence number
107+
108+
#### `TxOutput`
109+
- `scriptpubkey`: Script public key (hex-encoded)
110+
- `scriptpubkeyType`: Script type (e.g., "p2wpkh", "p2wsh", "p2tr")
111+
- `scriptpubkeyAddress`: Bitcoin address (if decodable)
112+
- `value`: Value in satoshis
113+
- `n`: Output index
114+
115+
**Note**: You can analyze transaction inputs and outputs to detect channel funding, channel closures, and other transaction types. This provides more flexibility than the previous `TransactionContext` enum.
87116

88117
---
89118

@@ -119,14 +148,20 @@ class WalletEventHandler {
119148

120149
func handleEvent(_ event: Event) {
121150
switch event {
122-
case .onchainTransactionReceived(let txid, let amountSats, let context):
123-
handleIncomingTransaction(txid: txid, amount: amountSats, context: context)
151+
case .onchainTransactionReceived(let txid, let details):
152+
handleIncomingTransaction(txid: txid, details: details)
153+
154+
case .onchainTransactionConfirmed(let txid, let blockHash, let blockHeight, let confirmationTime, let details):
155+
handleConfirmedTransaction(txid: txid, height: blockHeight, details: details)
124156

125-
case .onchainTransactionConfirmed(let txid, let blockHash, let blockHeight, let confirmationTime, let context):
126-
handleConfirmedTransaction(txid: txid, height: blockHeight, context: context)
157+
case .onchainTransactionReplaced(let txid):
158+
handleReplacedTransaction(txid: txid)
127159

128-
case .onchainTransactionUnconfirmed(let txid):
129-
handleUnconfirmedTransaction(txid: txid)
160+
case .onchainTransactionReorged(let txid):
161+
handleReorgedTransaction(txid: txid)
162+
163+
case .onchainTransactionEvicted(let txid):
164+
handleEvictedTransaction(txid: txid)
130165

131166
case .syncProgress(let syncType, let progressPercent, let currentBlock, let targetBlock):
132167
updateSyncProgress(type: syncType, percent: progressPercent)
@@ -150,18 +185,18 @@ class WalletEventHandler {
150185
#### Example 1: Real-time Transaction Notifications
151186

152187
```swift
153-
func handleIncomingTransaction(txid: String, amount: Int64, context: TransactionContext) {
188+
func handleIncomingTransaction(txid: String, details: TransactionDetails) {
154189
DispatchQueue.main.async {
155-
if amount > 0 {
190+
if details.amountSats > 0 {
156191
// Incoming payment
157192
self.showNotification(
158193
title: "Payment Received!",
159-
body: "Incoming payment of \(amount) sats (unconfirmed)",
194+
body: "Incoming payment of \(details.amountSats) sats (unconfirmed)",
160195
txid: txid
161196
)
162197

163198
// Update UI to show pending transaction
164-
self.addPendingTransaction(txid: txid, amount: amount)
199+
self.addPendingTransaction(txid: txid, amount: details.amountSats)
165200

166201
// Play sound or haptic feedback
167202
self.playPaymentReceivedSound()
@@ -170,19 +205,16 @@ func handleIncomingTransaction(txid: String, amount: Int64, context: Transaction
170205
self.updateTransactionStatus(txid: txid, status: .broadcasting)
171206
}
172207

173-
// Check if this is channel-related
174-
switch context {
175-
case .channelFunding(let channelId, _, let counterpartyNodeId):
176-
print("Channel \(channelId) funding transaction detected")
177-
case .channelClosure(let channelId, _, _):
178-
print("Channel \(channelId) closing transaction detected")
179-
case .regularWallet:
180-
print("Regular wallet transaction")
208+
// Analyze transaction to detect channel-related transactions
209+
if self.isChannelFundingTransaction(details: details) {
210+
print("Channel funding transaction detected")
211+
} else if self.isChannelClosureTransaction(details: details) {
212+
print("Channel closure transaction detected")
181213
}
182214
}
183215
}
184216

185-
func handleConfirmedTransaction(txid: String, height: UInt32, context: TransactionContext) {
217+
func handleConfirmedTransaction(txid: String, height: UInt32, details: TransactionDetails) {
186218
DispatchQueue.main.async {
187219
// Update transaction status
188220
self.updateTransactionStatus(txid: txid, status: .confirmed(height: height))
@@ -198,6 +230,54 @@ func handleConfirmedTransaction(txid: String, height: UInt32, context: Transacti
198230
self.refreshTransactionList()
199231
}
200232
}
233+
234+
func handleReplacedTransaction(txid: String) {
235+
DispatchQueue.main.async {
236+
self.updateTransactionStatus(txid: txid, status: .replaced)
237+
self.showNotification(
238+
title: "Transaction Replaced",
239+
body: "Transaction was replaced via RBF",
240+
txid: txid
241+
)
242+
}
243+
}
244+
245+
func handleReorgedTransaction(txid: String) {
246+
DispatchQueue.main.async {
247+
self.updateTransactionStatus(txid: txid, status: .pending)
248+
self.showNotification(
249+
title: "Transaction Unconfirmed",
250+
body: "Transaction became unconfirmed due to reorg",
251+
txid: txid
252+
)
253+
}
254+
}
255+
256+
func handleEvictedTransaction(txid: String) {
257+
DispatchQueue.main.async {
258+
self.updateTransactionStatus(txid: txid, status: .evicted)
259+
self.showNotification(
260+
title: "Transaction Evicted",
261+
body: "Transaction was evicted from mempool",
262+
txid: txid
263+
)
264+
// Optionally allow user to rebroadcast
265+
self.promptRebroadcast(txid: txid)
266+
}
267+
}
268+
269+
// Helper functions to analyze transaction details
270+
func isChannelFundingTransaction(details: TransactionDetails) -> Bool {
271+
// Analyze inputs/outputs to detect channel funding
272+
// This is a simplified example - implement based on your needs
273+
return details.outputs.count == 2 && details.amountSats > 0
274+
}
275+
276+
func isChannelClosureTransaction(details: TransactionDetails) -> Bool {
277+
// Analyze inputs/outputs to detect channel closure
278+
// This is a simplified example - implement based on your needs
279+
return details.inputs.count > 0 && details.outputs.count >= 2
280+
}
201281
```
202282

203283
#### Example 2: Sync Progress Bar
@@ -325,23 +405,23 @@ class WalletEventHandler(private val node: Node) {
325405
private fun handleEvent(event: Event) {
326406
when (event) {
327407
is Event.OnchainTransactionReceived -> {
328-
handleIncomingTransaction(
329-
event.txid,
330-
event.amountSats,
331-
event.context
332-
)
408+
handleIncomingTransaction(event.txid, event.details)
333409
}
334410

335411
is Event.OnchainTransactionConfirmed -> {
336-
handleConfirmedTransaction(
337-
event.txid,
338-
event.blockHeight,
339-
event.context
340-
)
412+
handleConfirmedTransaction(event.txid, event.blockHeight, event.details)
413+
}
414+
415+
is Event.OnchainTransactionReplaced -> {
416+
handleReplacedTransaction(event.txid)
417+
}
418+
419+
is Event.OnchainTransactionReorged -> {
420+
handleReorgedTransaction(event.txid)
341421
}
342422

343-
is Event.OnchainTransactionUnconfirmed -> {
344-
handleUnconfirmedTransaction(event.txid)
423+
is Event.OnchainTransactionEvicted -> {
424+
handleEvictedTransaction(event.txid)
345425
}
346426

347427
is Event.SyncProgress -> {
@@ -380,18 +460,18 @@ class WalletEventHandler(private val node: Node) {
380460
```kotlin
381461
class TransactionNotificationManager(private val context: Context) {
382462

383-
fun handleIncomingTransaction(txid: String, amountSats: Long, context: TransactionContext) {
463+
fun handleIncomingTransaction(txid: String, details: TransactionDetails) {
384464
GlobalScope.launch(Dispatchers.Main) {
385-
if (amountSats > 0) {
465+
if (details.amountSats > 0) {
386466
// Incoming payment
387467
showNotification(
388468
title = "Payment Received!",
389-
message = "Incoming payment of $amountSats sats (unconfirmed)",
469+
message = "Incoming payment of ${details.amountSats} sats (unconfirmed)",
390470
txid = txid
391471
)
392472

393473
// Update transaction list
394-
addPendingTransaction(txid, amountSats)
474+
addPendingTransaction(txid, details.amountSats)
395475

396476
// Play sound
397477
playPaymentSound()
@@ -400,22 +480,16 @@ class TransactionNotificationManager(private val context: Context) {
400480
updateTransactionStatus(txid, TransactionStatus.BROADCASTING)
401481
}
402482

403-
// Check transaction type
404-
when (context) {
405-
is TransactionContext.ChannelFunding -> {
406-
Log.d("Wallet", "Channel ${context.channelId} funding transaction")
407-
}
408-
is TransactionContext.ChannelClosure -> {
409-
Log.d("Wallet", "Channel ${context.channelId} closing transaction")
410-
}
411-
is TransactionContext.RegularWallet -> {
412-
Log.d("Wallet", "Regular wallet transaction")
413-
}
483+
// Analyze transaction to detect channel-related transactions
484+
if (isChannelFundingTransaction(details)) {
485+
Log.d("Wallet", "Channel funding transaction detected")
486+
} else if (isChannelClosureTransaction(details)) {
487+
Log.d("Wallet", "Channel closure transaction detected")
414488
}
415489
}
416490
}
417491

418-
fun handleConfirmedTransaction(txid: String, height: UInt, context: TransactionContext) {
492+
fun handleConfirmedTransaction(txid: String, height: UInt, details: TransactionDetails) {
419493
GlobalScope.launch(Dispatchers.Main) {
420494
// Update status
421495
updateTransactionStatus(txid, TransactionStatus.CONFIRMED)
@@ -429,6 +503,59 @@ class TransactionNotificationManager(private val context: Context) {
429503

430504
// Vibrate
431505
vibrate()
506+
507+
// Refresh transaction list
508+
refreshTransactionList()
509+
}
510+
}
511+
512+
fun handleReplacedTransaction(txid: String) {
513+
GlobalScope.launch(Dispatchers.Main) {
514+
updateTransactionStatus(txid, TransactionStatus.REPLACED)
515+
showNotification(
516+
title = "Transaction Replaced",
517+
message = "Transaction was replaced via RBF",
518+
txid = txid
519+
)
520+
}
521+
}
522+
523+
fun handleReorgedTransaction(txid: String) {
524+
GlobalScope.launch(Dispatchers.Main) {
525+
updateTransactionStatus(txid, TransactionStatus.PENDING)
526+
showNotification(
527+
title = "Transaction Unconfirmed",
528+
message = "Transaction became unconfirmed due to reorg",
529+
txid = txid
530+
)
531+
}
532+
}
533+
534+
fun handleEvictedTransaction(txid: String) {
535+
GlobalScope.launch(Dispatchers.Main) {
536+
updateTransactionStatus(txid, TransactionStatus.EVICTED)
537+
showNotification(
538+
title = "Transaction Evicted",
539+
message = "Transaction was evicted from mempool",
540+
txid = txid
541+
)
542+
// Optionally allow user to rebroadcast
543+
promptRebroadcast(txid)
544+
}
545+
}
546+
547+
// Helper functions to analyze transaction details
548+
private fun isChannelFundingTransaction(details: TransactionDetails): Boolean {
549+
// Analyze inputs/outputs to detect channel funding
550+
// This is a simplified example - implement based on your needs
551+
return details.outputs.size == 2 && details.amountSats > 0
552+
}
553+
554+
private fun isChannelClosureTransaction(details: TransactionDetails): Boolean {
555+
// Analyze inputs/outputs to detect channel closure
556+
// This is a simplified example - implement based on your needs
557+
return details.inputs.isNotEmpty() && details.outputs.size >= 2
558+
}
432559
}
433560
}
434561

0 commit comments

Comments
 (0)