diff --git a/justfile b/justfile
index fd8801d9..8c5dc41e 100644
--- a/justfile
+++ b/justfile
@@ -80,7 +80,7 @@ swap:
# Run the asb on testnet
asb-testnet:
- ASB_DEV_ADDR_OUTPUT_PATH="$(pwd)/src-gui/.env.development" cargo run -p swap-asb --bin asb -- --testnet start --rpc-bind-port 9944 --rpc-bind-host 0.0.0.0
+ ASB_DEV_ADDR_OUTPUT_PATH="$(pwd)/src-gui/.env.development" cargo run -p swap-asb --bin asb -- --testnet --trace start --rpc-bind-port 9944 --rpc-bind-host 0.0.0.0
# Launch the ASB controller REPL against a local testnet ASB instance
asb-testnet-controller:
@@ -140,3 +140,4 @@ code2prompt_single_crate crate:
prepare-windows-build:
cd dev-scripts && ./ubuntu_build_x86_86-w64-mingw32-gcc.sh
+
diff --git a/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx b/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx
index 03741cb2..04118063 100644
--- a/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx
+++ b/src-gui/src/renderer/components/pages/swap/swap/SwapWidget.tsx
@@ -52,7 +52,16 @@ export default function SwapWidget() {
flex: 1,
}}
>
-
+
+
+
{swap.state !== null && (
<>
diff --git a/swap-p2p/Cargo.toml b/swap-p2p/Cargo.toml
index 54a0c095..2ef5b3cd 100644
--- a/swap-p2p/Cargo.toml
+++ b/swap-p2p/Cargo.toml
@@ -13,7 +13,7 @@ swap-machine = { path = "../swap-machine" }
swap-serde = { path = "../swap-serde" }
# Networking
-libp2p = { workspace = true, features = ["serde", "request-response", "rendezvous", "cbor", "json", "ping", "identify"] }
+libp2p = { workspace = true, features = ["serde", "request-response", "rendezvous", "cbor", "json", "identify", "ping"] }
# Serialization
asynchronous-codec = "0.7.0"
diff --git a/swap-p2p/src/futures_util.rs b/swap-p2p/src/futures_util.rs
new file mode 100644
index 00000000..ca222632
--- /dev/null
+++ b/swap-p2p/src/futures_util.rs
@@ -0,0 +1,71 @@
+use libp2p::futures::future::BoxFuture;
+use libp2p::futures::stream::{FuturesUnordered, StreamExt};
+use std::collections::HashSet;
+use std::hash::Hash;
+use std::task::{Context, Poll};
+
+/// A collection of futures with associated keys that can be checked for presence
+/// before completion.
+///
+/// This combines a HashSet for key tracking with FuturesUnordered for efficient polling.
+/// The key is provided during insertion; the future only needs to yield the value.
+pub struct FuturesHashSet {
+ keys: HashSet,
+ futures: FuturesUnordered>,
+}
+
+impl FuturesHashSet {
+ pub fn new() -> Self {
+ Self {
+ keys: HashSet::new(),
+ futures: FuturesUnordered::new(),
+ }
+ }
+
+ /// Check if a future with the given key is already pending
+ pub fn contains_key(&self, key: &K) -> bool {
+ self.keys.contains(key)
+ }
+
+ /// Insert a new future with the given key.
+ /// The future should yield V; the key will be paired with it when it completes.
+ /// Returns true if the key was newly inserted, false if it was already present.
+ /// If false is returned, the future is not added.
+ pub fn insert(&mut self, key: K, future: BoxFuture<'static, V>) -> bool {
+ if self.keys.insert(key.clone()) {
+ let key_clone = key;
+ let wrapped = async move {
+ let value = future.await;
+ (key_clone, value)
+ };
+ self.futures.push(Box::pin(wrapped));
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Poll for the next completed future.
+ /// When a future completes, its key is automatically removed from the tracking set.
+ pub fn poll_next_unpin(&mut self, cx: &mut Context) -> Poll