Release Vexta Wallet v0.6.8 hotfix

This commit is contained in:
Vexta Project 2026-09-01 09:00:40 +00:00
commit 45d9b111fb
6 changed files with 348 additions and 57 deletions

View file

@ -22,8 +22,8 @@ android {
applicationId = "org.vextaproject.wallet" applicationId = "org.vextaproject.wallet"
minSdk = 26 minSdk = 26
targetSdk = 36 targetSdk = 36
versionCode = 37 versionCode = 38
versionName = "0.6.7" versionName = "0.6.8"
} }
signingConfigs { signingConfigs {

View file

@ -37,6 +37,7 @@ object BlockScanner {
val height: Int, val height: Int,
val netSatoshis: Long, val netSatoshis: Long,
val blockTime: Long, val blockTime: Long,
val isCoinbase: Boolean = false,
val isPending: Boolean = false, val isPending: Boolean = false,
val feeSatoshis: Long? = null, val feeSatoshis: Long? = null,
val recipientAddress: String? = null val recipientAddress: String? = null
@ -263,7 +264,8 @@ object BlockScanner {
.toHex(), .toHex(),
height = height, height = height,
netSatoshis = netSatoshis, netSatoshis = netSatoshis,
blockTime = blockTime blockTime = blockTime,
isCoinbase = transaction.isCoinbase
) )
) )
} }

View file

@ -136,6 +136,7 @@ object HeaderSync {
var versionInfo: VersionInfo? = null var versionInfo: VersionInfo? = null
var gotVerack = false var gotVerack = false
var requested = false var requested = false
var totalReceived = 0
val cachedBefore = chain.last().height val cachedBefore = chain.last().height
repeat(200) { repeat(200) {
@ -153,6 +154,8 @@ object HeaderSync {
"headers" -> { "headers" -> {
val received = parseHeaders(message.second, chain) val received = parseHeaders(message.second, chain)
totalReceived += received
val remote = versionInfo val remote = versionInfo
?: throw IllegalStateException("missing node version") ?: throw IllegalStateException("missing node version")
@ -162,8 +165,25 @@ object HeaderSync {
) )
} }
if (chain.last().height < remote.height) {
if (received == 0) {
throw IllegalStateException(
"peer advertises height ${remote.height} " +
"but returned no additional headers"
)
}
sendMessage(
output,
"getheaders",
createGetHeadersPayload(chain)
)
return@repeat
}
return SyncResult( return SyncResult(
received = received, received = totalReceived,
cachedBefore = cachedBefore, cachedBefore = cachedBefore,
chainHeight = chain.last().height, chainHeight = chain.last().height,
lastHash = chain.last().hashDisplay lastHash = chain.last().hashDisplay
@ -175,7 +195,7 @@ object HeaderSync {
sendMessage( sendMessage(
output, output,
"getheaders", "getheaders",
createGetHeadersPayload(chain.last().hashWire) createGetHeadersPayload(chain)
) )
requested = true requested = true
} }
@ -201,6 +221,8 @@ object HeaderSync {
) )
} }
val receivedHeaders = mutableListOf<ByteArray>()
repeat(count) { repeat(count) {
if (buffer.remaining() < 81) { if (buffer.remaining() < 81) {
throw IllegalStateException("truncated headers message") throw IllegalStateException("truncated headers message")
@ -217,7 +239,33 @@ object HeaderSync {
) )
} }
appendVerifiedHeader(chain, raw) receivedHeaders.add(raw)
}
if (receivedHeaders.isNotEmpty()) {
val firstPreviousHash =
receivedHeaders.first().copyOfRange(4, 36)
if (!firstPreviousHash.contentEquals(chain.last().hashWire)) {
val ancestorIndex =
chain.indexOfLast {
it.hashWire.contentEquals(firstPreviousHash)
}
if (ancestorIndex < 0) {
throw IllegalStateException(
"peer returned headers with no common ancestor"
)
}
while (chain.lastIndex > ancestorIndex) {
chain.removeAt(chain.lastIndex)
}
}
receivedHeaders.forEach { raw ->
appendVerifiedHeader(chain, raw)
}
} }
return count return count
@ -375,12 +423,38 @@ object HeaderSync {
return compact return compact
} }
fun createGetHeadersPayload(locatorHash: ByteArray): ByteArray { fun createGetHeadersPayload(chain: List<ChainHeader>): ByteArray {
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
val locator = mutableListOf<ByteArray>()
var index = chain.lastIndex
var step = 1
while (index >= 0) {
locator.add(chain[index].hashWire)
if (index == 0) {
break
}
index = (index - step).coerceAtLeast(0)
if (locator.size >= 10) {
step *= 2
}
}
if (!locator.last().contentEquals(chain.first().hashWire)) {
locator.add(chain.first().hashWire)
}
writeInt32LE(out, PROTOCOL_VERSION) writeInt32LE(out, PROTOCOL_VERSION)
writeCompactSize(out, 1) writeCompactSize(out, locator.size)
out.write(locatorHash)
locator.forEach { hash ->
out.write(hash)
}
out.write(ByteArray(32)) out.write(ByteArray(32))
return out.toByteArray() return out.toByteArray()

View file

@ -119,6 +119,7 @@ class MainActivity : Activity() {
) )
val results = mutableListOf<String>() val results = mutableListOf<String>()
var successfulPeerSyncs = 0
for ((index, peer) in peers.withIndex()) { for ((index, peer) in peers.withIndex()) {
try { try {
@ -128,6 +129,8 @@ class MainActivity : Activity() {
saveChain(chain) saveChain(chain)
} }
successfulPeerSyncs++
results.add( results.add(
"$peer:$PORT\n" + "$peer:$PORT\n" +
"Handshake: successful\n" + "Handshake: successful\n" +
@ -148,7 +151,10 @@ class MainActivity : Activity() {
runOnUiThread { runOnUiThread {
if (intent.getBooleanExtra("sync_only", false)) { if (intent.getBooleanExtra("sync_only", false)) {
if (chain.last().height > 0) { if (
successfulPeerSyncs > 0 &&
chain.last().height > 0
) {
setResult( setResult(
RESULT_OK, RESULT_OK,
android.content.Intent().putExtra( android.content.Intent().putExtra(
@ -271,6 +277,7 @@ class MainActivity : Activity() {
var versionInfo: VersionInfo? = null var versionInfo: VersionInfo? = null
var gotVerack = false var gotVerack = false
var requested = false var requested = false
var totalReceived = 0
val cachedBefore = chain.last().height val cachedBefore = chain.last().height
repeat(200) { repeat(200) {
@ -288,6 +295,8 @@ class MainActivity : Activity() {
"headers" -> { "headers" -> {
val received = parseHeaders(message.second, chain) val received = parseHeaders(message.second, chain)
totalReceived += received
val remote = versionInfo val remote = versionInfo
?: throw IllegalStateException("missing node version") ?: throw IllegalStateException("missing node version")
@ -297,8 +306,25 @@ class MainActivity : Activity() {
) )
} }
if (chain.last().height < remote.height) {
if (received == 0) {
throw IllegalStateException(
"peer advertises height ${remote.height} " +
"but returned no additional headers"
)
}
sendMessage(
output,
"getheaders",
createGetHeadersPayload(chain)
)
return@repeat
}
return SyncResult( return SyncResult(
received = received, received = totalReceived,
cachedBefore = cachedBefore, cachedBefore = cachedBefore,
chainHeight = chain.last().height, chainHeight = chain.last().height,
lastHash = chain.last().hashDisplay lastHash = chain.last().hashDisplay
@ -310,7 +336,7 @@ class MainActivity : Activity() {
sendMessage( sendMessage(
output, output,
"getheaders", "getheaders",
createGetHeadersPayload(chain.last().hashWire) createGetHeadersPayload(chain)
) )
requested = true requested = true
} }
@ -336,6 +362,8 @@ class MainActivity : Activity() {
) )
} }
val receivedHeaders = mutableListOf<ByteArray>()
repeat(count) { repeat(count) {
if (buffer.remaining() < 81) { if (buffer.remaining() < 81) {
throw IllegalStateException("truncated headers message") throw IllegalStateException("truncated headers message")
@ -352,7 +380,33 @@ class MainActivity : Activity() {
) )
} }
appendVerifiedHeader(chain, raw) receivedHeaders.add(raw)
}
if (receivedHeaders.isNotEmpty()) {
val firstPreviousHash =
receivedHeaders.first().copyOfRange(4, 36)
if (!firstPreviousHash.contentEquals(chain.last().hashWire)) {
val ancestorIndex =
chain.indexOfLast {
it.hashWire.contentEquals(firstPreviousHash)
}
if (ancestorIndex < 0) {
throw IllegalStateException(
"peer returned headers with no common ancestor"
)
}
while (chain.lastIndex > ancestorIndex) {
chain.removeAt(chain.lastIndex)
}
}
receivedHeaders.forEach { raw ->
appendVerifiedHeader(chain, raw)
}
} }
return count return count
@ -510,12 +564,38 @@ class MainActivity : Activity() {
return compact return compact
} }
private fun createGetHeadersPayload(locatorHash: ByteArray): ByteArray { private fun createGetHeadersPayload(chain: List<ChainHeader>): ByteArray {
val out = ByteArrayOutputStream() val out = ByteArrayOutputStream()
val locator = mutableListOf<ByteArray>()
var index = chain.lastIndex
var step = 1
while (index >= 0) {
locator.add(chain[index].hashWire)
if (index == 0) {
break
}
index = (index - step).coerceAtLeast(0)
if (locator.size >= 10) {
step *= 2
}
}
if (!locator.last().contentEquals(chain.first().hashWire)) {
locator.add(chain.first().hashWire)
}
writeInt32LE(out, PROTOCOL_VERSION) writeInt32LE(out, PROTOCOL_VERSION)
writeCompactSize(out, 1) writeCompactSize(out, locator.size)
out.write(locatorHash)
locator.forEach { hash ->
out.write(hash)
}
out.write(ByteArray(32)) out.write(ByteArray(32))
return out.toByteArray() return out.toByteArray()

View file

@ -110,6 +110,11 @@ class WalletActivity : FragmentActivity() {
"background_transaction_history" "background_transaction_history"
private const val PREF_LAST_BACKGROUND_SCAN_HEIGHT = private const val PREF_LAST_BACKGROUND_SCAN_HEIGHT =
"last_background_scan_height" "last_background_scan_height"
private const val PREF_LAST_HEADER_HEIGHT =
"last_header_height"
private const val PREF_BACKGROUND_TX_HISTORY_VERSION =
"background_transaction_history_version"
private const val BACKGROUND_TX_HISTORY_VERSION = 4
private const val PREF_FIAT_CURRENCY = private const val PREF_FIAT_CURRENCY =
"preferred_fiat_currency" "preferred_fiat_currency"
@ -147,6 +152,7 @@ class WalletActivity : FragmentActivity() {
private var latestSpendableUtxos = private var latestSpendableUtxos =
emptyList<BlockScanner.SpendableUtxo>() emptyList<BlockScanner.SpendableUtxo>()
private var latestChainHeight = 0 private var latestChainHeight = 0
private var walletFiatRefresh: ((Long) -> Unit)? = null
private fun spendableUtxos( private fun spendableUtxos(
utxos: List<BlockScanner.SpendableUtxo>, utxos: List<BlockScanner.SpendableUtxo>,
@ -193,7 +199,7 @@ class WalletActivity : FragmentActivity() {
latestChainHeight = latestChainHeight =
getSharedPreferences(PREFS, MODE_PRIVATE) getSharedPreferences(PREFS, MODE_PRIVATE)
.getInt( .getInt(
PREF_LAST_BACKGROUND_SCAN_HEIGHT, PREF_LAST_HEADER_HEIGHT,
0 0
) )
.coerceAtLeast(0) .coerceAtLeast(0)
@ -217,6 +223,10 @@ class WalletActivity : FragmentActivity() {
100_000_000.0 100_000_000.0
) )
walletFiatRefresh?.invoke(
balanceSatoshis
)
walletHistoryView?.text = walletHistoryView?.text =
formatTransactionHistory( formatTransactionHistory(
cachedTransactions, cachedTransactions,
@ -429,7 +439,7 @@ class WalletActivity : FragmentActivity() {
mainWalletVisible && mainWalletVisible &&
!blockchainScanRunning !blockchainScanRunning
) { ) {
startHeaderSyncAndScan() startBackgroundHeaderSyncAndScan()
} }
}, 400L) }, 400L)
} }
@ -870,12 +880,21 @@ class WalletActivity : FragmentActivity() {
} }
blockchainScanRunning = true blockchainScanRunning = true
blockchainScanStatus?.text =
"Synchronizing block headers..."
Thread { Thread {
try { try {
val chain = HeaderSync.loadCachedChain(this) val chain = HeaderSync.loadCachedChain(this)
runOnUiThread {
blockchainScanStatus?.text =
"Synchronizing block headers...\n" +
"Cached height: ${chain.last().height}"
}
val syncErrors = mutableListOf<String>() val syncErrors = mutableListOf<String>()
var successfulPeerSyncs = 0
for (peer in listOf("87.106.99.23", "74.208.53.160")) { for (peer in listOf("87.106.99.23", "74.208.53.160")) {
try { try {
@ -884,6 +903,14 @@ class WalletActivity : FragmentActivity() {
if (result.received > 0) { if (result.received > 0) {
HeaderSync.saveChain(this, chain) HeaderSync.saveChain(this, chain)
} }
successfulPeerSyncs++
runOnUiThread {
blockchainScanStatus?.text =
"Header sync OK: ${result.chainHeight}\n" +
"Peer: $peer"
}
} catch (e: Exception) { } catch (e: Exception) {
syncErrors.add( syncErrors.add(
"$peer: ${e.message ?: e.javaClass.simpleName}" "$peer: ${e.message ?: e.javaClass.simpleName}"
@ -891,7 +918,10 @@ class WalletActivity : FragmentActivity() {
} }
} }
if (chain.last().height <= 0) { if (
successfulPeerSyncs == 0 ||
chain.last().height <= 0
) {
runOnUiThread { runOnUiThread {
blockchainScanRunning = false blockchainScanRunning = false
blockchainScanStatus?.text = blockchainScanStatus?.text =
@ -904,9 +934,12 @@ class WalletActivity : FragmentActivity() {
runOnUiThread { runOnUiThread {
runBlockchainScan() runBlockchainScan()
} }
} catch (_: Exception) { } catch (e: Exception) {
runOnUiThread { runOnUiThread {
blockchainScanRunning = false blockchainScanRunning = false
blockchainScanStatus?.text =
"Background synchronization failed:\n" +
(e.message ?: e.javaClass.simpleName)
} }
} }
}.start() }.start()
@ -947,6 +980,14 @@ class WalletActivity : FragmentActivity() {
-1 -1
) ?: -1 ) ?: -1
if (height >= 0) {
latestChainHeight = height
getSharedPreferences(PREFS, MODE_PRIVATE)
.edit()
.putInt(PREF_LAST_HEADER_HEIGHT, height)
.apply()
}
blockchainScanStatus?.text = if (height >= 0) { blockchainScanStatus?.text = if (height >= 0) {
"Headers synchronized to block $height.\n" + "Headers synchronized to block $height.\n" +
"Scanning compact filters..." "Scanning compact filters..."
@ -1073,7 +1114,7 @@ class WalletActivity : FragmentActivity() {
try { try {
val parts = value.split("|") val parts = value.split("|")
if (parts.size != 4) { if (parts.size != 5) {
return@mapNotNull null return@mapNotNull null
} }
@ -1090,6 +1131,7 @@ class WalletActivity : FragmentActivity() {
parts[2].toLong() parts[2].toLong()
}, },
blockTime = parts[3].toLong(), blockTime = parts[3].toLong(),
isCoinbase = parts[4].toBooleanStrict(),
feeSatoshis = outgoing?.feeSatoshis, feeSatoshis = outgoing?.feeSatoshis,
recipientAddress = recipientAddress =
outgoing?.recipientAddress outgoing?.recipientAddress
@ -1135,11 +1177,23 @@ class WalletActivity : FragmentActivity() {
} }
private fun hasCachedWalletState(): Boolean { private fun hasCachedWalletState(): Boolean {
return getSharedPreferences(PREFS, MODE_PRIVATE) val preferences =
.getInt( getSharedPreferences(PREFS, MODE_PRIVATE)
val scanHeight =
preferences.getInt(
PREF_LAST_BACKGROUND_SCAN_HEIGHT, PREF_LAST_BACKGROUND_SCAN_HEIGHT,
0 0
) > 0 )
val cacheVersion =
preferences.getInt(
PREF_BACKGROUND_TX_HISTORY_VERSION,
0
)
return scanHeight > 0 &&
cacheVersion == BACKGROUND_TX_HISTORY_VERSION
} }
private fun preferredFiatCurrency(): String { private fun preferredFiatCurrency(): String {
@ -1395,6 +1449,9 @@ class WalletActivity : FragmentActivity() {
} }
}, },
blockTime = newest.blockTime, blockTime = newest.blockTime,
isCoinbase = transactions.any {
it.isCoinbase
},
feeSatoshis = feeSatoshis =
outgoing?.feeSatoshis, outgoing?.feeSatoshis,
recipientAddress = recipientAddress =
@ -1586,8 +1643,22 @@ class WalletActivity : FragmentActivity() {
1 1
).coerceAtLeast(1) ).coerceAtLeast(1)
"Block ${transaction.height}\n" + if (transaction.isCoinbase) {
"Confirmations: $confirmations" val maturityStatus =
if (confirmations >= COINBASE_MATURITY) {
"Coinbase reward — Mature"
} else {
"Coinbase reward — Immature"
}
"Block ${transaction.height}\n" +
"$maturityStatus\n" +
"Confirmations: $confirmations / " +
"$COINBASE_MATURITY"
} else {
"Block ${transaction.height}\n" +
"Confirmations: $confirmations"
}
} }
"$direction $sign$amount VTX\n" + "$direction $sign$amount VTX\n" +
@ -1637,7 +1708,14 @@ class WalletActivity : FragmentActivity() {
val details = StringBuilder() val details = StringBuilder()
details.append("Type: $direction\n") details.append(
"Type: " +
if (transaction.isCoinbase) {
"Coinbase reward\n"
} else {
"$direction\n"
}
)
details.append("Amount: $amount VTX\n") details.append("Amount: $amount VTX\n")
details.append("Date & time: $dateTime\n") details.append("Date & time: $dateTime\n")
@ -1645,9 +1723,30 @@ class WalletActivity : FragmentActivity() {
details.append("Status: Pending\n") details.append("Status: Pending\n")
details.append("Confirmations: 0\n") details.append("Confirmations: 0\n")
} else { } else {
details.append("Status: Confirmed\n") if (transaction.isCoinbase) {
details.append("Block: ${transaction.height}\n") val mature =
details.append("Confirmations: $confirmations\n") confirmations >= COINBASE_MATURITY
details.append(
"Status: " +
if (mature) {
"Mature\n"
} else {
"Immature\n"
}
)
details.append("Block: ${transaction.height}\n")
details.append(
"Confirmations: $confirmations / " +
"$COINBASE_MATURITY\n"
)
} else {
details.append("Status: Confirmed\n")
details.append("Block: ${transaction.height}\n")
details.append(
"Confirmations: $confirmations\n"
)
}
} }
transaction.feeSatoshis?.let { fee -> transaction.feeSatoshis?.let { fee ->
@ -2144,7 +2243,7 @@ class WalletActivity : FragmentActivity() {
latestChainHeight = latestChainHeight =
getSharedPreferences(PREFS, MODE_PRIVATE) getSharedPreferences(PREFS, MODE_PRIVATE)
.getInt( .getInt(
PREF_LAST_BACKGROUND_SCAN_HEIGHT, PREF_LAST_HEADER_HEIGHT,
0 0
) )
.coerceAtLeast(0) .coerceAtLeast(0)
@ -2337,30 +2436,37 @@ class WalletActivity : FragmentActivity() {
addView(alternativesContainer) addView(alternativesContainer)
loadFiatValues( walletFiatRefresh = {
cachedBalance balanceSatoshis ->
) { values -> loadFiatValues(
fiatValues = values balanceSatoshis
) { values ->
fiatValues = values
preferredFiatView.text = preferredFiatView.text =
preferredFiatText( preferredFiatText(
alternativesContainer.visibility == alternativesContainer.visibility ==
View.VISIBLE View.VISIBLE
) )
alternativeViews.forEach { alternativeViews.forEach {
(currency, view) -> (currency, view) ->
values[currency]?.let { values[currency]?.let {
value -> value ->
view.text = view.text =
formatFiatValue( formatFiatValue(
currency, currency,
value value
) )
}
} }
} }
} }
walletFiatRefresh?.invoke(
cachedBalance
)
addView(space(8)) addView(space(8))
addView( addView(
TextView(this@WalletActivity).apply { TextView(this@WalletActivity).apply {
@ -2422,7 +2528,7 @@ class WalletActivity : FragmentActivity() {
symbol = "↻", symbol = "↻",
label = "Scan" label = "Scan"
) { ) {
startHeaderSyncAndScan() startBackgroundHeaderSyncAndScan()
} }
) )
@ -2527,7 +2633,7 @@ class WalletActivity : FragmentActivity() {
mainWalletVisible && mainWalletVisible &&
!blockchainScanRunning !blockchainScanRunning
) { ) {
startHeaderSyncAndScan() startBackgroundHeaderSyncAndScan()
} }
}, 500L) }, 500L)
} }

View file

@ -43,13 +43,15 @@ class WalletMonitoringService : Service() {
"tx_notifications_initialized" "tx_notifications_initialized"
private const val PREF_LAST_BACKGROUND_SCAN_HEIGHT = private const val PREF_LAST_BACKGROUND_SCAN_HEIGHT =
"last_background_scan_height" "last_background_scan_height"
private const val PREF_LAST_HEADER_HEIGHT =
"last_header_height"
private const val PREF_BACKGROUND_UTXO_PREFIX = private const val PREF_BACKGROUND_UTXO_PREFIX =
"background_utxos_" "background_utxos_"
private const val PREF_BACKGROUND_TX_HISTORY = private const val PREF_BACKGROUND_TX_HISTORY =
"background_transaction_history" "background_transaction_history"
private const val PREF_BACKGROUND_TX_HISTORY_VERSION = private const val PREF_BACKGROUND_TX_HISTORY_VERSION =
"background_transaction_history_version" "background_transaction_history_version"
private const val BACKGROUND_TX_HISTORY_VERSION = 2 private const val BACKGROUND_TX_HISTORY_VERSION = 4
} }
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
@ -97,10 +99,24 @@ class WalletMonitoringService : Service() {
try { try {
val chain = HeaderSync.loadCachedChain(this) val chain = HeaderSync.loadCachedChain(this)
val cachedTipHeightBeforeSync = chain.last().height
val cachedTipHashBeforeSync = chain.last().hashDisplay
var reorgDetected = false
for (peer in listOf("87.106.99.23", "74.208.53.160")) { for (peer in listOf("87.106.99.23", "74.208.53.160")) {
try { try {
val result = HeaderSync.syncFromPeer(peer, chain) val result = HeaderSync.syncFromPeer(peer, chain)
if (cachedTipHeightBeforeSync > 0) {
val originalTipStillCanonical =
chain.getOrNull(cachedTipHeightBeforeSync)
?.hashDisplay == cachedTipHashBeforeSync
if (!originalTipStillCanonical) {
reorgDetected = true
}
}
if (result.received > 0) { if (result.received > 0) {
HeaderSync.saveChain(this, chain) HeaderSync.saveChain(this, chain)
} }
@ -122,6 +138,13 @@ class WalletMonitoringService : Service() {
val localHeight = chain.last().height val localHeight = chain.last().height
preferences.edit()
.putInt(
PREF_LAST_HEADER_HEIGHT,
localHeight
)
.apply()
val previousHeight = val previousHeight =
preferences preferences
.getInt( .getInt(
@ -145,7 +168,8 @@ class WalletMonitoringService : Service() {
) == BACKGROUND_TX_HISTORY_VERSION ) == BACKGROUND_TX_HISTORY_VERSION
val fullScanRequired = val fullScanRequired =
previousHeight <= 0 || reorgDetected ||
previousHeight <= 0 ||
previousHeight > localHeight || previousHeight > localHeight ||
!backgroundCacheAvailable || !backgroundCacheAvailable ||
!transactionHistoryCurrent !transactionHistoryCurrent
@ -226,7 +250,10 @@ class WalletMonitoringService : Service() {
netSatoshis = transactions.sumOf { netSatoshis = transactions.sumOf {
it.netSatoshis it.netSatoshis
}, },
blockTime = newest.blockTime blockTime = newest.blockTime,
isCoinbase = transactions.any {
it.isCoinbase
}
) )
} }
.filter { it.netSatoshis != 0L } .filter { it.netSatoshis != 0L }
@ -308,7 +335,8 @@ class WalletMonitoringService : Service() {
transaction.txid, transaction.txid,
transaction.height.toString(), transaction.height.toString(),
transaction.netSatoshis.toString(), transaction.netSatoshis.toString(),
transaction.blockTime.toString() transaction.blockTime.toString(),
transaction.isCoinbase.toString()
).joinToString("|") ).joinToString("|")
}.toSet() }.toSet()
@ -325,7 +353,7 @@ class WalletMonitoringService : Service() {
try { try {
val parts = value.split("|") val parts = value.split("|")
if (parts.size != 4) { if (parts.size != 5) {
return@mapNotNull null return@mapNotNull null
} }
@ -333,7 +361,8 @@ class WalletMonitoringService : Service() {
txid = parts[0], txid = parts[0],
height = parts[1].toInt(), height = parts[1].toInt(),
netSatoshis = parts[2].toLong(), netSatoshis = parts[2].toLong(),
blockTime = parts[3].toLong() blockTime = parts[3].toLong(),
isCoinbase = parts[4].toBooleanStrict()
) )
} catch (_: Exception) { } catch (_: Exception) {
null null