From c3cda161c293d2ddd04de13260253d7e5c74ed35 Mon Sep 17 00:00:00 2001
From: woodser <13068859+woodser@users.noreply.github.com>
Date: Fri, 28 Aug 2026 08:16:01 -0400
Subject: [PATCH 1/5] test: update offline wallet expectations for ungated
daemon calls
getDaemonHeight surfaces wallet2's natural connection error and
startSyncing succeeds offline like monero-wallet-rpc's auto_refresh.
---
src/test/java/TestMoneroWalletFull.java | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/src/test/java/TestMoneroWalletFull.java b/src/test/java/TestMoneroWalletFull.java
index 4275b2ba..8c77fc2a 100644
--- a/src/test/java/TestMoneroWalletFull.java
+++ b/src/test/java/TestMoneroWalletFull.java
@@ -251,8 +251,9 @@ public void testCreateWalletRandomFull() {
// cannot get daemon chain height
try {
wallet.getDaemonHeight();
+ fail("Should have thrown exception");
} catch (MoneroError e) {
- assertEquals("Wallet is not connected to daemon", e.getMessage());
+ assertEquals("daemon error", e.getMessage()); // wallet2 masks errors from untrusted daemons
}
// set daemon connection and check chain height
@@ -307,7 +308,7 @@ public void testCreateWalletFromSeedFull() {
assertFalse(wallet.isSynced());
assertEquals(1, wallet.getHeight());
assertEquals(0, wallet.getRestoreHeight());
- try { wallet.startSyncing(); } catch (MoneroError e) { assertEquals("Wallet is not connected to daemon", e.getMessage()); }
+ wallet.startSyncing(); // succeeds while offline, syncing when a daemon becomes reachable
wallet.close();
// create wallet without restore height
@@ -771,9 +772,7 @@ public void testStartStopSyncing() {
assertNotNull(wallet.getSeed());
assertEquals(1, wallet.getHeight());
assertEquals(BigInteger.valueOf(0), wallet.getBalance());
- wallet.startSyncing();
- } catch (MoneroError e) {
- assertEquals("Wallet is not connected to daemon", e.getMessage());
+ wallet.startSyncing(); // succeeds while offline, syncing when a daemon becomes reachable
} finally {
wallet.close();
}
From a6cd6890f46a4e20af47f2cef9f15b49b4073d5b Mon Sep 17 00:00:00 2001
From: woodser <13068859+woodser@users.noreply.github.com>
Date: Fri, 28 Aug 2026 10:39:22 -0400
Subject: [PATCH 2/5] common: exit task looper under lock so restarts cannot
reuse a dead loop
A restart while a loop drained its final task or died interrupted could
see it as alive and not spawn a new loop, leaving the looper dead.
---
src/main/java/monero/common/TaskLooper.java | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/src/main/java/monero/common/TaskLooper.java b/src/main/java/monero/common/TaskLooper.java
index 0afd82f8..a2d24c3a 100644
--- a/src/main/java/monero/common/TaskLooper.java
+++ b/src/main/java/monero/common/TaskLooper.java
@@ -54,15 +54,23 @@ public synchronized TaskLooper start(long periodInMs, boolean targetFixedPeriod)
if (isStarted) return this;
isStarted = true;
- // start looping
+ // reuse a live loop, which observes isStarted under lock and continues
if (isLooping) return this;
isLooping = true;
TaskLooper that = this;
Thread loop = new Thread(new Runnable() {
@Override
public void run() {
- while (isStarted && !Thread.currentThread().isInterrupted()) {
-
+ while (true) {
+
+ // decide to exit and clear isLooping atomically so a restart cannot reuse a dead loop
+ synchronized (that) {
+ if (!isStarted || Thread.currentThread().isInterrupted()) {
+ isLooping = false;
+ return;
+ }
+ }
+
// run the task
long startTime = System.currentTimeMillis();
task.run();
@@ -71,12 +79,12 @@ public void run() {
if (isStarted) {
try { TimeUnit.MILLISECONDS.sleep(that.periodInMs - (targetFixedPeriod ? System.currentTimeMillis() - startTime : 0)); } // target fixed period by accounting for run time
catch (Exception e) {
- isLooping = false;
+ synchronized (that) { isLooping = false; }
if (isStarted) throw new RuntimeException(e);
+ return;
}
}
}
- isLooping = false;
}
});
loop.start();
From cc87ba6c32e5cb719aab5f3ae0bffbbe96f0a6d2 Mon Sep 17 00:00:00 2001
From: woodser <13068859+woodser@users.noreply.github.com>
Date: Fri, 28 Aug 2026 20:49:45 -0400
Subject: [PATCH 3/5] build: bump version to 0.8.56
---
README.md | 4 ++--
pom.xml | 2 +-
src/main/java/monero/common/MoneroUtils.java | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index c076744f..391493eb 100644
--- a/README.md
+++ b/README.md
@@ -97,7 +97,7 @@ walletFull.close(true);
#### For Gradle, add to build.gradle:
-`compile 'io.github.woodser:monero-java:0.8.55'`
+`compile 'io.github.woodser:monero-java:0.8.56'`
#### For Maven, add to pom.xml:
@@ -105,7 +105,7 @@ walletFull.close(true);
io.github.woodser
monero-java
- 0.8.55
+ 0.8.56
```
diff --git a/pom.xml b/pom.xml
index 51a31d74..387b765a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
io.github.woodser
monero-java
- 0.8.55
+ 0.8.56
Monero Java Library
A Java library for using Monero
https://github.com/woodser/monero-java
diff --git a/src/main/java/monero/common/MoneroUtils.java b/src/main/java/monero/common/MoneroUtils.java
index ad397f0c..e8593ac5 100644
--- a/src/main/java/monero/common/MoneroUtils.java
+++ b/src/main/java/monero/common/MoneroUtils.java
@@ -42,7 +42,7 @@ public class MoneroUtils {
* @return the version of this monero-java library
*/
public static String getVersion() {
- return "0.8.55";
+ return "0.8.56";
}
/**
From 226604a1731f65666806313a29c1ae4da459e1f5 Mon Sep 17 00:00:00 2001
From: woodser <13068859+woodser@users.noreply.github.com>
Date: Sat, 29 Aug 2026 07:17:48 -0400
Subject: [PATCH 4/5] wallet: announce missed confirm transition when tx
unlocks between polls
The confirm notification was silently skipped when a tx confirmed and
unlocked within one poll period, such as during fast mining.
---
src/main/java/monero/wallet/MoneroWalletRpc.java | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/main/java/monero/wallet/MoneroWalletRpc.java b/src/main/java/monero/wallet/MoneroWalletRpc.java
index 26e87934..cb6b2604 100644
--- a/src/main/java/monero/wallet/MoneroWalletRpc.java
+++ b/src/main/java/monero/wallet/MoneroWalletRpc.java
@@ -2421,8 +2421,14 @@ public void poll() {
// announce new unlocked outputs
for (MoneroTxWallet unlockedTx : unlockedTxs) {
+ boolean missedConfirm = Boolean.TRUE.equals(unlockedTx.isConfirmed()) && !prevConfirmedNotifications.contains(unlockedTx.getHash());
prevUnconfirmedNotifications.remove(unlockedTx.getHash()); // stop tracking tx notifications
prevConfirmedNotifications.remove(unlockedTx.getHash());
+ if (missedConfirm) { // announce missed confirm transition if tx unlocked between polls
+ MoneroTxWallet confirmedTx = unlockedTx.copy().setIsLocked(true);
+ confirmedTx.setBlock(unlockedTx.getBlock().copy().setTxs(confirmedTx));
+ notifyOutputs(confirmedTx);
+ }
notifyOutputs(unlockedTx);
}
From 2fac5f002bf8cc97fd9915d8b0edb37f6cce4e5d Mon Sep 17 00:00:00 2001
From: woodser <13068859+woodser@users.noreply.github.com>
Date: Thu, 27 Aug 2026 12:38:38 -0400
Subject: [PATCH 5/5] build: update monero-cpp submodule
Adapt JNI bridge to monero_tx_set::deserialize returning a shared pointer.
---
external/monero-cpp | 2 +-
src/main/cpp/monero_jni_bridge.cpp | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/external/monero-cpp b/external/monero-cpp
index 8db4afab..6927f7bf 160000
--- a/external/monero-cpp
+++ b/external/monero-cpp
@@ -1 +1 @@
-Subproject commit 8db4afab28f394f3dc78de6b4c2d3473484ce1b9
+Subproject commit 6927f7bf52dabad2179dfe2a0a0e1a95e12290c1
diff --git a/src/main/cpp/monero_jni_bridge.cpp b/src/main/cpp/monero_jni_bridge.cpp
index d7d22623..a2bed5a1 100644
--- a/src/main/cpp/monero_jni_bridge.cpp
+++ b/src/main/cpp/monero_jni_bridge.cpp
@@ -1428,10 +1428,10 @@ JNIEXPORT jstring JNICALL Java_monero_wallet_MoneroWalletFull_describeTxSetJni(J
try {
// deserialize tx set to describe
- monero_tx_set tx_set = monero_tx_set::deserialize(tx_set_json);
+ shared_ptr tx_set = monero_tx_set::deserialize(tx_set_json);
// describe tx set
- monero_tx_set described_tx_set = wallet->describe_tx_set(tx_set);
+ monero_tx_set described_tx_set = wallet->describe_tx_set(*tx_set);
// serialize, free, and return
std::string monero_tx_set_json = described_tx_set.serialize();