Repository: farcaster-project/farcaster-node
Pull request #158 (2021-11-01 14:01:01)

Use correct aggregated monero keys
Builds on top of #154 


Patch:
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..97c7dedf
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,3 @@
+# These are supported funding model platforms
+
+github: [dr-orlovsky]
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 00000000..5f5c0300
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,45 @@
+---
+name: Bug report
+about: Let's improve software together
+title: ''
+labels: bug
+assignees: dr-orlovsky
+
+---
+
+**Checklist**
+- [ ] Make sure you are using the latest code (`git checkout master && git pull origin`)
+- [ ] Compile only with rust nightly: `rustup default nightly && rustup update nightly && cargo test --all --all-features -v`
+- [ ] Try to do `cargo update`
+- [ ] Try to remove `target` directory
+
+**Classify the bug**
+Put `x` in the boxes below:
+- [ ] Build issue
+- [ ] Test failing
+- [ ] Runtime panic
+- [ ] Incorrect results
+- [ ] Unexpected/undocumented behaviour / 
+
+**Describe the problem**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Operating system
+2. Rust compiler version
+3. Did you 
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Logs**
+Please copy and paste content of `rustup default nightly && cargo test --all --all-features -v` in a block below right after "console" line:
+```console
+```
+
+**Additional context**
+Add any other context about the problem here.
+
+**Other links**
+Please provide links and references to the affected repositories, code samples etc.
diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
new file mode 100644
index 00000000..7802b8c3
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md
@@ -0,0 +1,23 @@
+Closes #
+
+## Checklist
+- [ ] Must pass CI, i.e.
+  - tests
+  - doc tests
+  - formatting lints
+- [ ] Must increase test coverage, i.e. all new features must be covered by tests. Use [Codecov](https://codecov.io/gh/LNP-BP/rust-amplify) to navigate the code and see which parts lack coverage.
+
+## Requirements
+
+Please do not introduce new dependencies and compiler feature requirements without good rationale. PRs adding new non-dev dependencies are likely to be rejected.
+
+## Pull request changes:
+* 
+*
+* 
+
+## Rationale
+* 
+* 
+
+Request for review: @dr_orlovsky
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
deleted file mode 100644
index dd9ad17a..00000000
--- a/.github/dependabot.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-# To get started with Dependabot version updates, you'll need to specify which
-# package ecosystems to update and where the package manifests are located.
-# Please see the documentation for all configuration options:
-# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
-
-version: 2
-updates:
-  - package-ecosystem: "github-actions"
-    directory: "/"
-    schedule:
-      interval: "daily"
diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml
index 57ab58d3..a653405b 100644
--- a/.github/workflows/binaries.yml
+++ b/.github/workflows/binaries.yml
@@ -1,62 +1,33 @@
-name: Build binaries
+name: Rust
 
 on:
   push:
-    branches: [ main ]
+    branches: [ master ]
   pull_request:
-    branches: [ main ]
+    branches: [ master ]
 
 env:
   CARGO_TERM_COLOR: always
 
 jobs:
   binaries:
-    name: Build binaries
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
-
       - name: Install dependencies
-        run: sudo apt-get install -y libsqlite3-dev libssl-dev libzmq3-dev pkg-config build-essential cmake
-
+        run: sudo apt-get install -y libzmq3-dev
       - name: Install latest nightly
         uses: actions-rs/toolchain@v1
         with:
           toolchain: nightly
           override: true
-
-      - name: Build farcaster daemon
-        uses: actions-rs/cargo@v1
-        with:
-          command: build
-          args: --bin farcasterd --features server
-
-      - name: Build peer daemon
+      - name: Build daemon
         uses: actions-rs/cargo@v1
         with:
           command: build
           args: --bin peerd --features server
-
-      - name: Build swap daemon
-        uses: actions-rs/cargo@v1
-        with:
-          command: build
-          args: --bin swapd --features server
-
-      - name: Build wallet daemon
-        uses: actions-rs/cargo@v1
-        with:
-          command: build
-          args: --bin walletd --features server
-
-      - name: Build syncer daemon
-        uses: actions-rs/cargo@v1
-        with:
-          command: build
-          args: --bin syncerd --features server
-
-      - name: Build swap CLI
+      - name: Build CLI
         uses: actions-rs/cargo@v1
         with:
           command: build
-          args: --bin swap-cli --features cli
+          args: --bin lnp-cli --features cli
diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml
deleted file mode 100644
index 55322c28..00000000
--- a/.github/workflows/create-release.yml
+++ /dev/null
@@ -1,60 +0,0 @@
-name: Create and publish release
-
-on:
-  pull_request:
-    types:
-      - closed
-
-jobs:
-  produce_binaries:
-    name: Compile released binaries
-    if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/')
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-
-      - name: Install dependencies
-        run: sudo apt-get install -y libsqlite3-dev libssl-dev libzmq3-dev pkg-config build-essential cmake
-
-      - name: Install latest stable
-        uses: actions-rs/toolchain@v1
-        with:
-          toolchain: stable
-          override: true
-
-      - name: Build released binaries
-        uses: actions-rs/cargo@v1
-        with:
-          command: build
-          args: --bins --all-features --release
-
-      - name: Prepare zip file
-        uses: papeloto/action-zip@v1
-        with:
-          files: target/release/address target/release/farcasterd target/release/peerd target/release/swap-cli target/release/swapd target/release/syncerd target/release/walletd
-          recursive: false
-          dest: bins.zip
-
-      - name: Archive release bins
-        uses: actions/upload-artifact@v2
-        with:
-          name: release-bins
-          path: bins.zip
-          retention-days: 7
-
-  create_release:
-    name: Create from merged release branch
-    uses: farcaster-project/workflows/.github/workflows/create-release.yml@v1.0.2
-    # Do not run before uploading the binaries
-    needs: produce_binaries
-    with:
-      artifact_name: release-bins
-      files: bins.zip
-
-  release_to_crates:
-    name: Publish the new release to crates.io
-    uses: farcaster-project/workflows/.github/workflows/release-to-crates-io.yml@v1.0.2
-    # Do not run before creating the release is done
-    needs: create_release
-    secrets:
-      cratesio_token: ${{ secrets.H4SH3D_CARGO_REGISTRY_TOKEN }}
diff --git a/.github/workflows/draft-new-release.yml b/.github/workflows/draft-new-release.yml
deleted file mode 100644
index 132bb297..00000000
--- a/.github/workflows/draft-new-release.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-name: Draft new release
-
-on:
-  workflow_dispatch:
-    inputs:
-      version:
-        description: 'The new version in major.minor.patch format.'
-        required: true
-
-jobs:
-  draft-new-release:
-    name: Draft a new release
-    uses: farcaster-project/workflows/.github/workflows/draft-new-release.yml@v1.0.2
-    with:
-      version: ${{ github.event.inputs.version }}
diff --git a/.github/workflows/library.yml b/.github/workflows/library.yml
new file mode 100644
index 00000000..2dfcdbb9
--- /dev/null
+++ b/.github/workflows/library.yml
@@ -0,0 +1,88 @@
+name: Features & Rust ver
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    branches: [ master ]
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  features:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        feature: [ embedded, client, server, cli, serde ]
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install dependencies
+        run: sudo apt-get install -y libzmq3-dev
+      - name: Install rust nightly
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: nightly
+          override: true
+      - name: Feature ${{matrix.feature}}
+        uses: actions-rs/cargo@v1
+        with:
+          command: build
+          args: --lib --features=${{matrix.feature}} --verbose
+  no-features:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install dependencies
+        run: sudo apt-get install -y libzmq3-dev
+      - name: Install latest nightly
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: nightly
+          override: true
+      - name: No features
+        uses: actions-rs/cargo@v1
+        with:
+          command: check
+          args: --lib --no-default-features --verbose
+  default-features:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install dependencies
+        run: sudo apt-get install -y libzmq3-dev
+      - name: Install latest nightly
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: nightly
+          override: true
+      - name: Default features
+        uses: actions-rs/cargo@v1
+        with:
+          command: check
+          args: --lib --verbose
+  dependency:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - name: Install dependencies
+        run: sudo apt-get install -y libzmq3-dev
+      - name: Install latest nightly
+        uses: actions-rs/toolchain@v1
+        with:
+          toolchain: nightly
+          override: true
+      - name: Create dependency
+        run: |
+          cargo new dep_test
+          cd dep_test
+          printf 'lnp_node = { path = "..", default-features = false, features = ["embedded"] }\n\n[workspace]' >> Cargo.toml
+      - name: Build dependency
+        uses: actions-rs/cargo@v1
+        with:
+          command: build
+          args: --verbose
+      - name: Clean up
+        run: |
+          cd ..
+          rm -rf dep_test
diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml
deleted file mode 100644
index a83da0b9..00000000
--- a/.github/workflows/package.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-name: Build and Publish GitHub Packages
-
-on:
-  push:
-    branches: [ main ]
-
-jobs:
-  faracsterd_container:
-    runs-on: ubuntu-latest
-
-    steps:
-    - uses: actions/checkout@v2
-
-    - name: Login to GitHub Container Registry
-      uses: docker/login-action@v1
-      with:
-        registry: ghcr.io
-        username: ${{ github.actor }}
-        password: ${{ secrets.GITHUB_TOKEN }}
-
-    - name: Docker build
-      run: docker build -f $GITHUB_WORKSPACE/Dockerfile -t ghcr.io/farcaster-project/farcaster-node/farcasterd:latest $GITHUB_WORKSPACE
-
-    - name: Docker push to GitHub Packages
-      run: docker push ghcr.io/farcaster-project/farcaster-node/farcasterd:latest
diff --git a/.github/workflows/publication.yml b/.github/workflows/publication.yml
new file mode 100644
index 00000000..58c84436
--- /dev/null
+++ b/.github/workflows/publication.yml
@@ -0,0 +1,17 @@
+name: Publication
+
+on:
+  push:
+    tags:
+      - 'v*'
+
+env:
+  CARGO_TERM_COLOR: always
+
+jobs:
+  publication:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v2
+      - run: cargo login ${{ secrets.CRATES_IO_TOKEN }}
+      - run: cargo publish --all-features
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 668ac5fe..a5901f1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,61 @@
-# Changelog
+Change Log
+==========
 
-All notable changes to this project will be documented in this file.
+v0.0.0
+-------------
+- Farcaster fork for Bitcoin-Monero atomic swaps
 
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
-## [Unreleased]
+lnp-node pre-fork
+-------------
+v0.1.0-beta.3
+-------------
+- Support for lightning peer message encoding (before used custom strict-encding)
+- Better representation of log messages
+- Migration on v0.3 of LNP/BP Core libraries after their complete refactoring
+
+v0.1.0-beta.2
+-------------
+- Fix balances consistency in channeld
+- README & build instructions improvements
+- Modernizing for LNP/BP Core v0.2 release & RGB Node v0.2 RC
+
+v0.1.0-beta.1
+--------------
+- Channel funding
+- Channel operations, HTLCs
+- RGB node integration
+- Adding assets to the channel (funding with assets)
+- Transfers of RGB assets
+
+v0.1.0-alpha.4
+--------------
+- Daemon management with LNPd
+- Connection initialisation cycle
+- Much better reporting in console tool
+- Completed set of command-line management & information commands
+- Improved channel creation lifecycle
+- Multiple improvements to debugging information representation
+
+v0.1.0-alpha.3
+--------------
+- Channel negotiation between nodes
+- Reworked service buses; added inter-daemon routing
+- Separated general service runtime functionality
+
+v0.1.0-alpha.2
+--------------
+- Skeleton for lnpd, channeld, gossipd and routed services/daemons
+- Ping/pong interplay betweeen nodes
+- Completed implementatino of enterprise service buses (CTL, MSG, BRIDGE)
+- Basic channel creating workflow
+
+v0.1.0-alpha.1
+--------------
+Initial pre-release:
+- Multithreaded concurrent architecture
+- Connection daemon
+- Command-line tool
+- LN peer connectivity
+- ZMQ RPC command buses: for LN messages and control
 
-[Unreleased]: https://github.com/farcaster-project/farcaster-node/compare/d409eec53263836d9dc036fb62527d3e6fa17bfc...HEAD
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index dd424962..d0104866 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,5 +1,5 @@
 # Code of Conduct
 
 Out code of conduct is not to have a code of conduct. Restriction is the word
-of sin: free people writing free code take their own decisions and act in a
+of sin: free people writing free code take their own decisions and act in a 
 way they like; taking all responsibility for the consequences.
diff --git a/Cargo.lock b/Cargo.lock
index 17abd9c7..d971da7f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,5 +1,7 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+version = 3
+
 [[package]]
 name = "adler32"
 version = "1.2.0"
@@ -1730,7 +1732,7 @@ dependencies = [
 [[package]]
 name = "monero-rpc"
 version = "0.1.0"
-source = "git+https://github.com/TheCharlatan/monero-rpc-rs?branch=changes#5b311c820d3420f66a4aa7b546662be65c6df316"
+source = "git+https://github.com/TheCharlatan/monero-rpc-rs?branch=changes#467cc0c66b906abfc4c4f5ecb88eb477c75f2f9e"
 dependencies = [
  "anyhow",
  "chrono",
diff --git a/Cargo.toml b/Cargo.toml
index ddfbaa1e..7365e341 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,14 +1,14 @@
 [package]
 name = "farcaster_node"
+description = "Farcaster node"
 version = "0.0.2"
-authors = ["Farcaster Devs", "Dr. Maxim Orlovsky <orlovsky@pandoracore.com>"]
-build = "build.rs"
-edition = "2018"
-exclude = [".github", "/test", "Dockerfile"]
-keywords = ["bitcoin", "monero", "node", "atomic swaps"]
+authors = ["Farcaster devs", "Dr. Maxim Orlovsky <orlovsky@pandoracore.com>"]
 license = "MIT"
+keywords = ["bitcoin", "monero", "node", "atomic swaps" ]
+edition = "2018"
 readme = "README.md"
-description = "Farcaster node"
+build = "build.rs"
+exclude = [".github", "/test", "Dockerfile"]
 
 [lib]
 name = "farcaster_node"
@@ -42,54 +42,54 @@ required-features = ["server"]
 # farcaster_core = { path = "../farcaster-core", features = ["serde"] }
 farcaster_core = { git = "https://github.com/farcaster-project/farcaster-core", branch = "main", features = ["serde"] }
 
-anyhow = "1"
 hex = "^0.4.3"
+anyhow = "1"
 
 # LNP/BP crates
 amplify = "3"
 amplify_derive = "2"
-base64 = { version = "0.12", optional = true }
-bech32 = { version = "0.7", optional = true }
-bitcoin = "0.27"
-bitcoincore-rpc = "0.14.0"
-chrono = "0.4"
-clap = { version = "3.0.0-beta.4", optional = true }
-colored = { version = "2", optional = true }
-configure_me = { version = "0.4", optional = true }
-dotenv = { version = "0.15", optional = true }
-# Coin clients
-electrum-client = { git = "https://github.com/Lederstrumpf/rust-electrum-client", branch = "verboseTransactionGetRebased" }
-env_logger = "0.7"
-internet2 = "0.5.0-alpha.2"
-# Rust language
-lazy_static = "1.4"
 lightning_encoding = "0.5.0-beta.3"
+lnpbp = {version = "0.5.0-beta.3", features = ["all"]}
+slip132 = "0.3"
 # lnp-core =  { path = "../ext/lnp-core" }
 # internet2 = { path = "../ext/rust-internet2" }
 lnp-core = "0.4.0-beta.3"
-lnpbp = { version = "0.5.0-beta.3", features = ["all"] }
-# Congig & logging
-log = { version = "0.4", features = ["max_level_trace", "release_max_level_debug"] }
+strict_encoding = "1.7.4"
+strict_encoding_derive = "1.7.4"
+internet2 = "0.5.0-alpha.2"
+bitcoin = "0.27"
 monero = { version = "0.15", features = ["strict_encoding_support"] }
-monero-rpc = { git = "https://github.com/TheCharlatan/monero-rpc-rs", branch = "changes" }
+# Rust language
+lazy_static = "1.4"
+chrono = "0.4"
 nix = { version = "0.19", optional = true }
 regex = { version = "1.5", optional = true }
 # Serialization & parsing
 serde_crate = { package = "serde", version = "1", features = ["derive"], optional = true }
-serde_json = { version = "1", optional = true }
 serde_with = { version = "1.8", optional = true }
+serde_json = { version = "1", optional = true }
 serde_yaml = { version = "0.8", optional = true }
-settings = { version = "0.10", package = "config", optional = true }
-shellexpand = { version = "2", optional = true }
-slip132 = "0.3"
-strict_encoding = "1.7.4"
-strict_encoding_derive = "1.7.4"
 sysinfo = { version = "0.18.2" }
-# Async
-tokio = { version = "1.12.0", features = ["full"] }
 toml = { version = "0.5", optional = true }
+bech32 = { version = "0.7", optional = true }
+base64 = { version = "0.12", optional = true }
+# Congig & logging
+log = { version = "0.4", features = ["max_level_trace", "release_max_level_debug"] }
+env_logger = "0.7"
+clap = { version = "3.0.0-beta.4", optional = true }
+settings = { version = "0.10", package = "config", optional = true }
+configure_me = { version = "0.4", optional = true }
+dotenv = { version = "0.15", optional = true }
+colored = { version = "2", optional = true }
+shellexpand = { version = "2", optional = true }
 # IPC
 zmq = "0.9"
+# Async
+tokio = { version = "1.12.0", features = ["full"] }
+# Coin clients
+electrum-client = { git = "https://github.com/Lederstrumpf/rust-electrum-client", branch = "verboseTransactionGetRebased" }
+bitcoincore-rpc = "0.14.0"
+monero-rpc = { git = "https://github.com/TheCharlatan/monero-rpc-rs", branch = "changes" }
 # monero-rpc = { path = "../monero-rpc-rs/" }
 
 [build-dependencies]
@@ -156,62 +156,29 @@ embedded = ["client", "node", "microservices/embedded"]
 # Server node can be run as a part of mobile app and other types of clients;
 # thus `server` != `node`.
 # This feature results in building with features not required for command-line
-node = [
-  "serde",
-  "internet2/keygen",
-  "bitcoin/rand",
-  "internet2/zmq",
-  "microservices/node",
-  "internet2/url",
-  "base64",
-  # Required for storing config and cache
-  "_config",
-  "_rpc",
-]
+node = ["serde", "internet2/keygen", "bitcoin/rand", "internet2/zmq", "microservices/node",
+    "internet2/url", "base64",
+    # Required for storing config and cache
+    "_config", "_rpc"]
 # Feature is required for any applications that talks to daemon processes
-client = [
-  "internet2/zmq",
-  "microservices/client",
-  "microservices/node",
-  "bitcoin/rand",
-  "base64",
-  "internet2/url",
-  "clap",
-  "_rpc",
-]
+client = ["internet2/zmq", "microservices/client", "microservices/node", "bitcoin/rand",
+    "base64",
+    "internet2/url", "clap", "_rpc"]
 # Required for all apps that can be launched from command-line shell as binaries
 # (i.e. both servers and cli)
 shell = [
-  "dotenv",
-  "clap",
-  "settings",
-  "configure_me",
-  "serde",
-  "amplify/parse_arg",
-  "microservices/shell",
-  "shellexpand",
-  "colored",
+    "dotenv", "clap", "settings", "configure_me", "serde",
+    "amplify/parse_arg", "microservices/shell", "shellexpand", "colored"
 ]
 
 # Internally used features for convenience
 _config = ["serde_yaml", "toml"]
 _rpc = []
 
-serde = [
-  "serde_crate",
-  "serde_with",
-  "serde_yaml",
-  "serde_json",
-  "toml",
-  "chrono/serde",
-  "bitcoin/use-serde",
-  "slip132/serde",
-  "amplify/serde",
-  "internet2/serde",
-  "microservices/serde",
-  "lnpbp/serde",
-  "lnp-core/serde",
-]
+serde = ["serde_crate", "serde_with", "serde_yaml", "serde_json", "toml",
+    "chrono/serde", "bitcoin/use-serde", "slip132/serde",
+    "amplify/serde", "internet2/serde", "microservices/serde",
+    "lnpbp/serde", "lnp-core/serde" ]
 tor = ["microservices/tor", "internet2/tor"]
 
 integration_test = ["regex"]
diff --git a/Dockerfile b/Dockerfile
index 8971d796..14dd2fff 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,7 +1,7 @@
 ARG BUILDER_DIR=/srv/farcaster
 
 
-FROM rust:1.54.0-slim-buster as builder
+FROM rust:1.53.0-slim-buster as builder
 
 ARG SRC_DIR=/usr/local/src/farcaster
 ARG BUILDER_DIR
@@ -21,7 +21,7 @@ COPY doc ${SRC_DIR}/doc
 COPY shell ${SRC_DIR}/shell
 COPY src ${SRC_DIR}/src
 COPY build.rs Cargo.lock Cargo.toml codecov.yml config_spec.toml \
-     LICENSE README.md ${SRC_DIR}/
+     LICENSE license_header.txt README.md ${SRC_DIR}/
 
 WORKDIR ${SRC_DIR}
 
@@ -56,6 +56,6 @@ VOLUME "$DATA_DIR"
 
 EXPOSE 9735 9981
 
-ENTRYPOINT ["farcasterd", "--data-dir", "/var/lib/farcaster", "-x", "lnpz://0.0.0.0:9981/?api=esb"]
+ENTRYPOINT ["farcasterd"]
 
-CMD ["--help"]
+CMD ["-vvv", "--data-dir", "/var/lib/farcaster", "-x", "lnpz://0.0.0.0:9981/?api=esb"]
diff --git a/LICENSE b/LICENSE
index 00a912b7..597e214d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2021 Farcaster Devs & LNP/BP Standards Association
+Copyright (c) 2021 LNP/BP Standards Association
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 07354ac2..a25d493a 100644
--- a/README.md
+++ b/README.md
@@ -1,161 +1,371 @@
-[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/farcaster-project/farcaster-node/Build%20binaries)](https://github.com/farcaster-project/farcaster-node/actions/workflows/binaries.yml)
-[![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance)
-[![Crates.io](https://img.shields.io/crates/v/farcaster_node.svg)](https://crates.io/crates/farcaster_node)
-[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
-[![MSRV](https://img.shields.io/badge/MSRV-1.54.0-blue)](https://blog.rust-lang.org/2021/07/29/Rust-1.54.0.html)
+# Farcaster-node: Atomic swap node
+:warning: THIS IS UNFINISHED, EXPERIMENTAL TECH AND YOU WILL LOSE YOUR MONEY IF YOU TRY IT ON MAINNET :warning:
 
-# Farcaster: cross-chain atomic swaps
-
-:warning: **THIS IS UNFINISHED, EXPERIMENTAL TECH AND YOU WILL LOSE YOUR MONEY IF YOU TRY IT ON MAINNET** :warning:
-
-This project is called the **Farcaster Node**, it is _a collection of microservices for running cross-chain atomic swaps_. Currently the node is focused on Bitcoin-Monero atomic swaps, but is designed to be flexible and integrate new crypto-pairs in the future.
+## Running the node
+### Clone and build the project
+```
+git clone https://github.com/farcaster-project/farcaster-node.git
+cd farcaster-node
+cargo build --all-features
+```
 
-Farcaster Node is build on atomic swap primitives described in the [RFCs](https://github.com/farcaster-project/RFCs) and implemented in [Farcaster Core](https://github.com/farcaster-project/farcaster-core).
+### Launch full nodes, electrum, monero-wallet-rpc
+#### Launch bitcoind
+``` sh
+bitcoind -server -testnet
+```
 
-:information_source: This work is based on LNP/BP work, this project is a fork from [LNP-BP/lnp-node](https://github.com/LNP-BP/lnp-node) since [acbb4c](https://github.com/farcaster-project/farcaster-node/commit/acbb4c467695dc3d1c02b88be97e9a6e2d434435).
+#### Launch electrs (electrum server in rust)
+``` sh
+electrs -vvvv --network testnet
+```
 
-## Build and run
+#### Launch monerod
+``` sh
+monerod --stagenet
+```
 
-### Local
+#### Launch wallet-rpc
+``` sh
+monero-wallet-rpc --stagenet --rpc-bind-port 18083 --disable-rpc-login --trusted-daemon --password "pw" --wallet-dir ~/.monero_wallets
+```
 
-To compile the node, please install [cargo](https://doc.rust-lang.org/cargo/), then run the following commands:
+### Launch two farcaster nodes for trading
 
-```bash
-sudo apt install -y libsqlite3-dev libssl-dev libzmq3-dev pkg-config build-essential cmake
-git clone https://github.com/farcaster-project/farcaster-node.git && cd farcaster-node
-cargo install --path . --bins --all-features --locked
+On one terminal launch the farcasterd node `node0` with `data_dir_0`
+```
+ ./target/debug/farcasterd -vv -d .data_dir_0 --electrum-server localhost:60001 --monero-daemon http://stagenet.melo.tools:38081 --monero-rpc-wallet http://localhost:18083
+ 
+```
+On a second terminal launch a second farcasterd node `node1` with `data_dir_1`
+```
+ ./target/debug/farcasterd -vv -d .data_dir_1 --electrum-server localhost:60001 --monero-daemon http://stagenet.melo.tools:38081 --monero-rpc-wallet http://localhost:18083
 ```
 
-Farcaster needs to connect to tree services to do actions on-chain and track on-chain events. Needed services are: an `electrum server`, a `monero daemon`, and a `monero rpc wallet`.
+### Client
 
-You can install and launch all the needed services locally by running the following commands:
+On a third terminal
 
-```sh
-bitcoind -server -testnet
-electrs --network testnet
-monerod --stagenet
-monero-wallet-rpc --stagenet --rpc-bind-port 18083\
-    --disable-rpc-login\
-    --trusted-daemon\
-    --password "pw"\
-    --wallet-dir ~/.fc_monero_wallets
+Create aliases for nodes client. You will use them to make and take offers.
+```
+# client for node0
+alias swap0-cli="./target/debug/swap-cli -d .data_dir_0" 
+# client for node1
+alias swap1-cli="./target/debug/swap-cli -d .data_dir_1"
 ```
 
-Then start the node with the following command:
-
+### Make and take offer
+### Make
+Maker creates offer and start listening. Command used to to print a hex representation of the offer that shall be shared with Taker. Additionally it spins up the listener awaiting for connection related to this offer. params of make:
 ```
-farcasterd -vv\
-    --electrum-server {ip:port}\
-    --monero-daemon http://{ip:port}\
-    --monero-rpc-wallet http://{ip:port}
+swap0-cli make --help
 ```
+```
+ARGS:
+    <ARBITRATING_ADDR>
+            bitcoin address used as destination or refund address
 
-### With docker
+    <NETWORK>
+            Type of offer and network to use [default: Testnet]
 
-You can follow the documentation on [how to use `farcasterd` with Docker](./doc/docker-stack.md).
+    <ARBITRATING_BLOCKCHAIN>
+            The chosen arbitrating blockchain [default: ECDSA]
 
-### Connect a client
+    <ACCORDANT_BLOCKCHAIN>
+            The chosen accordant blockchain [default: Monero]
 
-Once `farcasterd` is up & running you can issue commands to control its actions with a client. For the time being, only one client is provided within this repo: `swap-cli`.
+    <ARBITRATING_AMOUNT>
+            Amount of arbitrating assets to exchanged [default: "0.15 BTC"]
 
-If you launched `farcasterd` with the default paramters (the `--data-dir` argument), `swap-cli` will be able to connect to `farcasterd` without further configuration. You can get informations about the node with (this require `swap-cli` to be installed on your host):
+    <ACCORDANT_AMOUNT>
+            Amount of accordant assets to exchanged [default: "100 XMR"]
 
-```
-swap-cli info
-```
+    <MAKER_ROLE>
+            The future maker swap role [default: Alice] [possible values: Alice, Bob]
 
-Run `help` command for more details about available commands.
+    <CANCEL_TIMELOCK>
+            The cancel timelock parameter of the arbitrating blockchain [default: 10]
 
-### Remote client usage
+    <PUNISH_TIMELOCK>
+            The punish timelock parameter of the arbitrating blockchain [default: 30]
 
-Example from using other URLs supported by crate `internet2` `node_addr.rs`, besides the default inter process communication (also used by the Docker image to expose the control bus).
+    <FEE_STRATEGY>
+            The chosen fee strategy for the arbitrating transactions [default: "2 satoshi/vByte"]
 
-The daemon is controlled though ZMQ _ctl_ socket, an internal interface for control PRC protocol communications. Another ZMQ socket is used to forward all incoming protocol messages, the _msg_ socket. Both are node internal communication channels. Message from counterparty come through `peerd` services.
+    <PUBLIC_IP_ADDR>
+            public IPv4 or IPv6 address for public offer [default: 127.0.0.1]
 
-**Farcasterd**
+    <BIND_IP_ADDR>
+            IPv4 or IPv6 address to bind to [default: 0.0.0.0]
 
-To launch `farcasterd` with network binded _control_ (`-x`) bus and _message_ bus (`-m`) instead of `ctl.rpc` and `msg.rpc` files:
+    <PORT>
+            Port to use; defaults to the native LN port [default: 9735]
 
+    <OVERLAY>
+            Use overlay protocol (http, websocket etc) [default: tcp]
 ```
-farcasterd -vv -x "lnpz://127.0.0.1:9981/?api=esb" -m "lnpz://127.0.0.1:9982/?api=esb"
-```
-
-**Client**
 
-The following client can instruct the above `farcasterd` to return general informations as follows:
+The `ECDSA` below is a temporary hack, but it represents `Bitcoin<ECDSA>`, as Bitcoin can take many forms:
 
 ```
-swap-cli -x "lnpz://127.0.0.1:9981/?api=esb" -m "lnpz://127.0.0.1:9982/?api=esb" info
+swap0-cli make tb1q4gj53tuew3e6u4a32kdtle2q72su8te39dpceq Testnet ECDSA Monero "0.00001350 BTC" "0.00000001 XMR" Alice 10 30 "1 satoshi/vByte" "127.0.0.1" "0.0.0.0" 9745
 ```
 
-## Usage
-
-:rotating_light: **The following section focus on how to use the Farcaster Node to propose and run atomic swaps. Keep in mind that this software remains experimental and should not be used on mainnet or with any valuable assets.**
-
-When `farcasterd` is up & running and `swap-cli` is configured to connect and control it, you can make offers and/or take offers. An offer encapsulate informations about a trade of Bitcoin and Monero. One will make :hammer: an offer, e.g. a market maker, and one will try to take :moneybag: the offer. Below are the commands to use to either `make` an offer or `take` one.
-
-### :hammer: Make an offer
+This will produce the following hex encoded offer: 
 
-When listening for other peers to connect, e.g. for executing a swap, a `peerd` instance is spawned and binds to the specified `address:port` in arguments, counterparty `farcasterd` can then launch its own `peerd` that connects to the listening `peerd`, the communication is then established between two nodes.
+`4643535741500100020000008080000080080046050000000000000800102700000000000004000a00000004001e000000010800010000000000000001210002904c04c85a9d027fc44f5474438a1a98fd69bf64cef96608cb7d97a3b33b25670000000000000000000000000000000000000000000000000000000000007f000001261200`
 
-:mag_right: This requires for the time being some notions about the network topology the maker node is running in, this requirement will be lifted off later when integrating Tor by default.
+This public offer should be shared by maker with taker. It also contains information on how to connect to maker.
 
-To create an offer and spawn a listening `peerd` accepting incoming connections, run the following command:
+Additionally, it adds the public offers to the set of public offers in farcasterd that will be later used to initiate the swap upon takers message
+ 
+### Take
+Taker accepts offer and connects to Maker's daemon
 
+arguments of `take`
 ```
-swap-cli make tb1q935eq5fl2a3ajpqp0e3d7z36g7vctcgv05f5lf\
-    Testnet ECDSA Monero\
-    "0.00001350 BTC" "0.00000001 XMR"\
-    Alice 4 5 "1 satoshi/vByte"\
-    1.2.3.4\
-    0.0.0.0\
-    9735
-```
+    <BITCOIN_ADDRESS>
+            bitcoin address used as destination or refund address
 
-The first argument is the Bitcoin address used to get the bitcoins (as a refund or when the swap completes depending on the role).
+    <PUBLIC_OFFER>
+            Hex encoded offer
+```
 
-Then the network and assets are specified with the amounts. The `ECDSA` below is a temporary hack, but it represents `Bitcoin<ECDSA>`, as Bitcoin can take many forms.
+flag of interest: `--without-validation` or `-w`, for externally validated automated setups
 
-The role for the maker is specified in the offer: `Alice`, sells moneros for bitcoins, or `Bob`, sells bitcoins for moneros, with the timelock parameters for cancel and punish and the transaction fee that must be applied. Here the maker will send moneros and will receive bitcoin in his `tb1q935eq5fl2a3ajpqp0e3d7z36g7vctcgv05f5lf` address if the swap is successful, 4 and 5 blocks are used for the timelocks and 1 satoshi per virtual byte must be used for the Bitcoin transaction fee.
 
-Then the last three arguments in this example are: `public_ip_addr`, `bind_id_address` (default to `0.0.0.0`), and `port` (default to `9735`).
+Example of taking the offer above produced by maker. 
+```
+swap1-cli take tb1qt3r3t6yultt8ne88ffgvgyym0sstj4apwsz05j 4643535741500100020000008080000080080046050000000000000800102700000000000004000a00000004001e000000010800010000000000000001210002414dbe27712feb696a5f9f7d86eb37cb1317acc49a8a78e051dfe0c88efdff500000000000000000000000000000000000000000000000000000000000007f000001261200
+```
 
-:mag_right: To be able for a taker to connect and take the offer the `public_ip_addr:port` must be accessible and answered by the `peerd` binded to `bind_id_address:port`.
+## Remote client use
+Example from using other URLs supported by crate `internet2` `node_addr.rs`, besides the default inter process communication. 
 
-**The public offer result**
+### Farcasterd (Server)
+Here the node public key of farcasterd is derived from its secretkey included in `key.dat` file. You can launch the node first WITHOUT `-x` and `-m` to retrieve the printed pubkey, and later again on the same `-d data_dir` passing Ctl bus `-x` and Msg bus `-m` arguments:
+``` sh
+./target/debug/farcasterd -vvvv -x "lnpz://02c21ee2baf368b389059d4d2b75b734526aec8cc629481d981d4f628844f2f114@127.0.0.1:9981/?api=esb" -m "lnpz://02c21ee2baf368b389059d4d2b75b734526aec8cc629481d981d4f628844f2f114@127.0.0.1:9982/?api=esb" -d .data_dir_1
+```
 
-The command will ouput a hex encoded **public offer** that must be shared to anyone susceptible to take it and your `farcasterd` will register this public offer in its list, waiting for someone to connect and take it.
 
-Follow your `farcasterd` log (**with a log level set at `-vv`**) and fund the swap with the bitcoins or moneros when it asks so, at the end you should receive the counterparty assets.
+### Client
+And the following client can instruct the above `farcasterd` to make a offer as follows:
+``` sh
+./target/debug/swap-cli -x "lnpz://02c21ee2baf368b389059d4d2b75b734526aec8cc629481d981d4f628844f2f114@127.0.0.1:9981/?api=esb" -m "lnpz://02c21ee2baf368b389059d4d2b75b734526aec8cc629481d981d4f628844f2f114@127.0.0.1:9982/?api=esb" make
+```
 
-### :moneybag: Take the offer
+## Docker usage
 
-Taking a public offer is a much simpler process, all you need is a running node (doesn't require to know your network topology), an hex encoded public offer, and a bitcoin address to receive bitcoins (again, as a refund or if the swap completes depending on your swap role).
+Build the node container with `docker build -t farcaster:latest .` inside the project folder and then run the node with
 
 ```
-swap-cli take tb1qmcku4ht3tq53tvdl5hj03rajpdkdatd4w4mswx {hex encoded offer}
+docker run --rm -t -p 9735:9735 -p 9981:9981 --name farcaster_node farcaster
 ```
 
-The cli will ask you to validate the offer informations (amounts, assets, etc.), you can use the flag of interest `--without-validation` or `-w` for externally validated automated setups.
-
-Then follow your `farcasterd` log (**with a log level set at `-vv`**) and fund the swap with the bitcoins or moneros when it asks so, at the end you should receive the counterparty assets.
-
-### Run a swap locally
-
-If you want to test a swap with yourself locally you can follow the instructions [here](./doc/local-swap.md).
-
-## Releases and Changelog
+The container will be removed after execution (`--rm`), allocate a pseudo-TTY (`-t`), and publish exposed ports `9735` and `9981` on the host.
+
+It is then possible to command `farcasterd` with `swap-cli -x "lnpz://127.0.0.1:9981/?api=esb" info` from the host.
+
+Stop the container with `docker stop farcaster_node` (ctrl+c does not work yet).
+
+:warning: this exposes the control bus on the host, only intended for debug or on a trusted network.
+
+## LNP-node original readme
+This node was forked from `lnp-node` on 2021-03-12.
+
+Below the original for `lnp-node` README.
+
+
+`lnp-node` is a new Lightning Network node written from scratch in Rust. 
+Actually, it's a suite of daemons/microservices able to run both Lightning 
+Network Protocol (LNP) and Generalized Lightning Channels (GLC).
+
+One may ask: another LN node? Why we need it? And what is "Lightning network
+protocol" and "Generalized Lightning Channels"?
+
+**Lightning Network Protocol** is normal Lightning messaging protocol extracted
+from BOLTs and extended to support arbitrary messaging, RPC, P2P and publish/
+subscribe APIs over TCP/IP, TCP/Tor, UDP, ZeroMQ and high latency communication
+channels (mesh/satellite networks) with end-to-end encryption. It is 100% LN
+compatible (and in these terms Lightning Network runs on this protocol de-facto)
+but allows much more than current LN uses. The protocol is defined as a set of
+LNPBP15-19 standards, which are strict extensions of BOLTs. In other words, with 
+this protocol you can do an arbitrary messaging (and build complex distrubuted 
+systems without central authorities like DNS, SSL, IP addresses), so at LNP/BP
+we use it everywhere, even for internal communications between microservices.
+
+**Generalized Lightning Channels** is a way of defining payment channels in a
+modular and extensible way such that you can easily add new transaction outputs 
+to the commitment transaction, switch from HTLCs to PTLCs payments, use eltoo &
+do a lot of experimenttion without inventing new messages and standards each 
+time: peers are using LNP to negotiate channel and transaction structure with
+partially-signed transactions.
+
+Idea for both protocols came from Dr Maxim Orlovsky, Dr Christian Decker and
+Giacomo Zucco discussions in 2019-2020 and implemented by Maxim Orlovsky as a 
+part of [LNP/BP Core Library](https://github.com/LNP-BP/rust-lnpbp).
+We recommend to watch to [Potzblitz about LNP Node](https://www.youtube.com/watch?v=YmmNsWS5wiM&t=5s&ab_channel=Fulmo%E2%9A%A1)
+and [LNP/BP networking presentation](https://www.youtube.com/watch?v=kTwZKsbIPbc&t=2123s&ab_channel=LNPBPStandardsAssociation)
+to get a deeper insight into these topics. Presentations slides are also 
+avaliable:
+* [LNP/BP Decentralization Solutions]()
+* [Future of the Lightning Network]() (slides from the Postblitz talk)
+
+## Rationale & features
+
+The problem with the existing Lightning node implementations is their very 
+limited extensibility for such things as:
+
+* future LN upgrades ([channel factories](https://tik-old.ee.ethz.ch/file//a20a865ce40d40c8f942cf206a7cba96/Scalable_Funding_Of_Blockchain_Micropayment_Networks%20(1).pdf), 
+  [payment points](https://suredbits.com/payment-points-part-1/), Taproot),
+  since they do not separate network communication, channel operation and 
+  channel parameters from each other in a well manner, such that it will be 
+  possible, for instance, to replace HTLCs with payment points using some 
+  extension/module.
+* protocols on top of LN (layer 3), like DLCs or proposed 
+  [Lightspeed protocol](https://github.com/LNP-BP/lnpbps/issues/24), which 
+  require modification on the structure of the commitment transaction.
+
+We name the extensions to Lightning network required to build this rich 
+functionality a "Generalized Lightning Network". With this project 
+[LNP/BP Standards Association](https://github.com/LNP-BP) is trying to build an 
+LN node with extensible and highly-modular architecture, utilizing state of the 
+art Rust approaches like:
+* Microservice architecture
+* Dockerization for scalability at the level of separate processes (per-channel 
+  scalability etc)
+* Tokio-based async/non-blocking IO and rumtime
+* Fast and performant ZeroMQ for APIs and IPCs
+* Avoiding non-efficient Bitcoin blockchain parsing and instead relying on new 
+  [scalable blockchain indexing service](https://github.com/LNP-BP/txserv) and 
+  new format of [universal bitcoin identifiers](https://github.com/LNP-BP/lnpbps/blob/master/lnpbp-0005.md)
+* Mobile- & web-ready via C- and WASM-bindings & build targets for the core 
+  components
+
+This new node will be used to implement:
+
+* Bidirectional channels
+* [Channel factories/multipeer channels](https://tik-old.ee.ethz.ch/file//a20a865ce40d40c8f942cf206a7cba96/Scalable_Funding_Of_Blockchain_Micropayment_Networks%20(1).pdf)
+* [Payment points](https://suredbits.com/payment-points-part-1/)
+* [DLCs on LN](https://hackmd.io/@lpQxZaCeTG6OJZI3awxQPQ/LN-DLC)
+* [RGB & Spectrum](https://github.com/rgb-org/spec)
+* Future [Storm](https://github.com/storm-org/storm-spec) (storage & messaging) 
+  edition for LN
+* Future [Prometheus](https://github.com/pandoracore/prometheus-spec/blob/master/prometheus.pdf) 
+  (high-load computing) edition for LN
+* [Lightspeed payment protocol](https://github.com/LNP-BP/lnpbps/issues/24)
+
+The node must maintain simple/modular upgradability for:
+
+* RGB smart contracts (client-validated smart contract system)
+* Discrete log contracts (DLCs)
+* Schnorr's/Taproot
+* Pay-to-elliptic curve point lock contracts (PTLC) - replacement for HTLCs
+* eltoo
+
+See [here](/doc/demo-alpha.4) for a demo of the node capabilities as for version `v0.1.0-alpha.4`.
+
+## Design
+
+### Approach
+
+The node (as other nodes maitained by LNP/BP Standards Association and Pandora
+Core company subsidiaries) consists of multiple microservices, communicating
+with each other via LNP ZMQ RPC interface.
+
+![Node architacture](doc/node_arch.jpeg)
+
+The set of microservices representing node can run as either:
+1) single daemon process on desktop or a server;
+2) cloud of docker-based daemons, one per microservice, with instance 
+   scalability and in geo-distributed environment;
+3) inside a single mobile app as threads;
+4) and even different nodes can be combined in their services between themselves
+   into a single executables/mobile apps;
+5) all P2P communications are end-to-end encrypted and work over Tor.
+
+Other nodes, designed an maintained by LNP/BP Standards Association with the 
+same architecture include:
+* [RGB Node](https://github.com/LNP-BP/rgb-node) for running RGB smart contracts
+  over bitcoin and lightning network
+* [BP Node](https://github.com/LNP-BP/bp-node) for indexing bitcoin blockchain
+  (you may think of it as a more efficient Electrum server alternative)
+* Bifrost – node for storing/passing client-side-validated data with watchtower 
+  functionality and used for Storm/RGB/DEX infrastructure
+
+Other third parties provide their own nodes:
+* [Keyring](https://github.com/pandoracore/keyring) for managing private key
+  accounts, storage and signatures with support for miniscript and PSBTs
+* [MyCitadel](https://github.com/mycitadel/mycitadel-node) Bitcoin, LN & RGB
+  enabled wallet service with support for other LNP/BP protocols
+
+### LNP Node Architecture Specifics
+
+The overall architecture of LNP Node is the following:
+
+![Node architacture](doc/lnp_node_arch.jpeg)
+
+More information on the service buses used in the node:
+
+![Node architacture](doc/node_esb.jpeg)
+
+
+## Project organization & architecture
+
+* [`src/bin/`](src/bin/) – binaries for daemons & CLI launching main process
+* [`src/cli/`](src/cli/) – CLAP-based command line API talking to message bus
+* [`src/rpc/`](src/rpc/) – RPC commands for all daemons used for message bus
+* [`src/i8n/`](src/i8n/) – functions exposed to FFI talking to message bus
+* `src/<name>/` – service/daemon-specific code:
+  - [`src/peerd/`](src/peerd) – daemon managing peer connections 
+    within Lightning peer network using LNP (Lightning network protocol). 
+    Specific supported message types are defined as a part of 
+    [LNP/BP Core Library](https://github.com/LNP-BP/rust-lnpbp)
+  - [`src/channeld`](src/channeld) – daemon managing generalized Lightning
+    channels with their extensions
+  - [`src/lnpd`](src/lnpd) – daemon initializing creation of new channels and
+    connections
+  - [`src/routed`](src/routed) – daemon managing routing information
+  - [`src/gossip`](src/gossip) – daemon managing gossip data
+  - [`src/keyd`](src/keyd) - key managing daemon
+
+Each daemon (more correctly "microservice", as it can run as a thread, not 
+necessary a process) or other binary (like CLI tool) follows the same
+organization concept for module/file names:
+* `error.rs` – daemon-specific error types;
+* `opts.rs` – CLAP arguments & daemon configuration data;
+* `runtime.rs` – singleton managing main daemon thread and keeping all ZMQ/P2P 
+  connections and sockets; receiving and processing messages through them;
+* `index/`, `storage/`, `cache/` – storage interfaces and engines;
+* `db/` – SQL-specific schema and code, if needed.
+
+## Build and usage
 
-See [CHANGELOG.md](CHANGELOG.md) and [RELEASING.md](RELEASING.md).
+### Local
 
-## About
+To compile the node, please install [cargo](https://doc.rust-lang.org/cargo/),
+then run the following commands:
 
-This work is part of the Farcaster cross-chain atomic swap project, see [Farcaster Project](https://github.com/farcaster-project).
+```bash
+sudo apt install -y libsqlite3-dev libssl-dev libzmq3-dev pkg-config
+cargo install --path . --bins --all-features
+cargo run --color=always --bin peerd --features=server -- --listen -vvvv
+```
 
-## Licensing
+### In docker
 
-The code in this project is licensed under the [MIT License](LICENSE).
+```bash
+docker build -t lnp-node .
+docker run --rm --name lnp_node lnp-node
+```
 
 ## Ways of communication
 
-IRC channels on Libera.chat \#monero-swap, Bitcoin-Monero cross-chain atomic swaps research and development.
+* IRC channels on Freenode
+    * \#lnp-bp: discussions on this and other LNP/BP projects
+    * \#rust-bitcoin: here you can get general support on rust-lightning
+    * \#lightning-dev: here better to ask generic lightning-network questions
+    * dr_orlovsky: my account on IRC
+* Lightning Hackdays Mattermost channel:
+  <https://mm.fulmo.org/fulmo/channels/x-rust-ln-node>
diff --git a/RELEASING.md b/RELEASING.md
deleted file mode 100644
index b80975aa..00000000
--- a/RELEASING.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# Releasing
-
-Releases of this crate are fairly automated.
-
-To create a new release:
-
-1. Make sure `CHANGELOG.md` is up-to-date and contain all modifications under the `[Unreleased]` section.
-2. Trigger [this](https://github.com/farcaster-project/farcaster-node/actions/workflows/draft-new-release.yml) workflow with the desired version number.
-3. Merge the resulting PR (watch your notifications).
-
-The `[Unreleased]` section of `CHANGELOG.md` must contain all the changes that will be documented for the release.
diff --git a/codecov.yml b/codecov.yml
index c3aa43c2..ff8e118e 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -14,4 +14,4 @@ coverage:
     patch:
       default:
         target: 0%
-        threshold: 1%
+        threshold: 1%
\ No newline at end of file
diff --git a/doc/lnp/contrib/demo.sh b/contrib/demo.sh
similarity index 100%
rename from doc/lnp/contrib/demo.sh
rename to contrib/demo.sh
diff --git a/doc/README.md b/doc/README.md
deleted file mode 100644
index 2cd78c9c..00000000
--- a/doc/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Farcaster Node Documentation
-
-- [`test local swap`](./local-swap.md) describe how to run a swap with both participant locally
-- [`docker stack`](./docker-stack.md) documents how to use the node with docker and docker compose
-- [`sequence diagram`](./sequencediagram.txt) of message flow between services
diff --git a/doc/lnp/demo-alpha.4/Dockerfile b/doc/demo-alpha.4/Dockerfile
similarity index 100%
rename from doc/lnp/demo-alpha.4/Dockerfile
rename to doc/demo-alpha.4/Dockerfile
diff --git a/doc/lnp/demo-alpha.4/README.md b/doc/demo-alpha.4/README.md
similarity index 100%
rename from doc/lnp/demo-alpha.4/README.md
rename to doc/demo-alpha.4/README.md
diff --git a/doc/lnp/demo-alpha.4/docker-compose.yml b/doc/demo-alpha.4/docker-compose.yml
similarity index 100%
rename from doc/lnp/demo-alpha.4/docker-compose.yml
rename to doc/demo-alpha.4/docker-compose.yml
diff --git a/doc/docker-stack.md b/doc/docker-stack.md
deleted file mode 100644
index 60b5751e..00000000
--- a/doc/docker-stack.md
+++ /dev/null
@@ -1,79 +0,0 @@
-# How to use `farcasterd` with Docker
-
-This project provide some docker images and a docker compose setup to help running the node locally with less efforts. Before reading further make sure docker and docker compose are installed on your machine.
-
-## Docker compose
-
-To launch `farcasterd` on your machine clone this project and run:
-
-```
-docker-compose up -d
-docker-compose logs farcasterd -f --no-log-prefix
-```
-
-These commands launch a local Monero Wallet RPC and the Farcaster node, then follow the logs produced by `farcasterd`. Daemons used by the services are public testnet servers.
-
-Install `swap-cli` on your machine and run:
-
-```
-swap-cli -x "lnpz://127.0.0.1:9981/?api=esb" info
-```
-
-You should get a returned value from the node. You can use this stack to take a public offer or to make an offer. In the case you want to make an offer make sure the public address and the port will be reachable from external networks.
-
-:mega: The only port forwarded to the host is `9735`, make sure to use this one when making offers.
-
-To stop and remove the containers simply run in the project folder:
-
-```
-docker-compose down
-```
-
-### Images used
-
-Images used in the docker compose are produced by the `farcaster-project` directly.
-
-| Service      | Image                                                             |
-| ------------ | ----------------------------------------------------------------- |
-| `farcasterd` | `ghcr.io/farcaster-project/farcaster-node/farcasterd:latest`      |
-| `walletrpc`  | `ghcr.io/farcaster-project/containers/monero-wallet-rpc:0.17.2.3` |
-
-## Docker image
-
-You can use the docker image produced directly by the GitHub CI with:
-
-```
-docker run --rm -t -p 9735:9735 -p 9981:9981\
-    --name farcaster_node\
-    ghcr.io/farcaster-project/farcaster-node/farcasterd:latest\
-    -vv\
-    --electrum-server {ip:port}\
-    --monero-daemon http://{ip:port}\
-    --monero-rpc-wallet http://{ip:port}
-```
-
-or build the node image and start a container by running inside the project folder:
-
-```
-docker build -t farcasterd:latest .
-docker run --rm -t -p 9735:9735 -p 9981:9981\
-    --name farcaster_node\
-    farcasterd:latest\
-    ...
-```
-
-The container will be removed after execution (`--rm`), allocate a pseudo-TTY (`-t`), and publish exposed ports `9735` and `9981` on the host.
-
-Stop the container with `docker stop farcaster_node` (ctrl+c does not work yet).
-
-:warning: this exposes the control bus on the host, only intended for debugging or on a trusted network.
-
-### Connect a client
-
-To connect a client and command `farcasterd` running inside a Docker container simply run:
-
-```
-swap-cli -x "lnpz://127.0.0.1:9981/?api=esb" info
-```
-
-This configure the cli to connects to the exposed port `9981` of `farcasterd`.
diff --git a/doc/lnp/CHANGELOG.md b/doc/lnp/CHANGELOG.md
deleted file mode 100644
index 9bdb6255..00000000
--- a/doc/lnp/CHANGELOG.md
+++ /dev/null
@@ -1,56 +0,0 @@
-Change Log
-==========
-
-The lnp-node pre-fork CHANGELOG:
-
-v0.1.0-beta.3
--------------
-- Support for lightning peer message encoding (before used custom strict-encding)
-- Better representation of log messages
-- Migration on v0.3 of LNP/BP Core libraries after their complete refactoring
-
-v0.1.0-beta.2
--------------
-- Fix balances consistency in channeld
-- README & build instructions improvements
-- Modernizing for LNP/BP Core v0.2 release & RGB Node v0.2 RC
-
-v0.1.0-beta.1
---------------
-- Channel funding
-- Channel operations, HTLCs
-- RGB node integration
-- Adding assets to the channel (funding with assets)
-- Transfers of RGB assets
-
-v0.1.0-alpha.4
---------------
-- Daemon management with LNPd
-- Connection initialisation cycle
-- Much better reporting in console tool
-- Completed set of command-line management & information commands
-- Improved channel creation lifecycle
-- Multiple improvements to debugging information representation
-
-v0.1.0-alpha.3
---------------
-- Channel negotiation between nodes
-- Reworked service buses; added inter-daemon routing
-- Separated general service runtime functionality
-
-v0.1.0-alpha.2
---------------
-- Skeleton for lnpd, channeld, gossipd and routed services/daemons
-- Ping/pong interplay betweeen nodes
-- Completed implementatino of enterprise service buses (CTL, MSG, BRIDGE)
-- Basic channel creating workflow
-
-v0.1.0-alpha.1
---------------
-Initial pre-release:
-- Multithreaded concurrent architecture
-- Connection daemon
-- Command-line tool
-- LN peer connectivity
-- ZMQ RPC command buses: for LN messages and control
-
diff --git a/doc/lnp/README.md b/doc/lnp/README.md
deleted file mode 100644
index 62d3ce37..00000000
--- a/doc/lnp/README.md
+++ /dev/null
@@ -1,198 +0,0 @@
-This node was forked from `lnp-node` on 2021-03-12.
-
-Below the original for `lnp-node` README.
-
-# LNP-node original readme
-
-`lnp-node` is a new Lightning Network node written from scratch in Rust. 
-Actually, it's a suite of daemons/microservices able to run both Lightning 
-Network Protocol (LNP) and Generalized Lightning Channels (GLC).
-
-One may ask: another LN node? Why we need it? And what is "Lightning network
-protocol" and "Generalized Lightning Channels"?
-
-**Lightning Network Protocol** is normal Lightning messaging protocol extracted
-from BOLTs and extended to support arbitrary messaging, RPC, P2P and publish/
-subscribe APIs over TCP/IP, TCP/Tor, UDP, ZeroMQ and high latency communication
-channels (mesh/satellite networks) with end-to-end encryption. It is 100% LN
-compatible (and in these terms Lightning Network runs on this protocol de-facto)
-but allows much more than current LN uses. The protocol is defined as a set of
-LNPBP15-19 standards, which are strict extensions of BOLTs. In other words, with 
-this protocol you can do an arbitrary messaging (and build complex distrubuted 
-systems without central authorities like DNS, SSL, IP addresses), so at LNP/BP
-we use it everywhere, even for internal communications between microservices.
-
-**Generalized Lightning Channels** is a way of defining payment channels in a
-modular and extensible way such that you can easily add new transaction outputs 
-to the commitment transaction, switch from HTLCs to PTLCs payments, use eltoo &
-do a lot of experimenttion without inventing new messages and standards each 
-time: peers are using LNP to negotiate channel and transaction structure with
-partially-signed transactions.
-
-Idea for both protocols came from Dr Maxim Orlovsky, Dr Christian Decker and
-Giacomo Zucco discussions in 2019-2020 and implemented by Maxim Orlovsky as a 
-part of [LNP/BP Core Library](https://github.com/LNP-BP/rust-lnpbp).
-We recommend to watch to [Potzblitz about LNP Node](https://www.youtube.com/watch?v=YmmNsWS5wiM&t=5s&ab_channel=Fulmo%E2%9A%A1)
-and [LNP/BP networking presentation](https://www.youtube.com/watch?v=kTwZKsbIPbc&t=2123s&ab_channel=LNPBPStandardsAssociation)
-to get a deeper insight into these topics. Presentations slides are also 
-avaliable:
-* [LNP/BP Decentralization Solutions]()
-* [Future of the Lightning Network]() (slides from the Postblitz talk)
-
-## Rationale & features
-
-The problem with the existing Lightning node implementations is their very 
-limited extensibility for such things as:
-
-* future LN upgrades ([channel factories](https://tik-old.ee.ethz.ch/file//a20a865ce40d40c8f942cf206a7cba96/Scalable_Funding_Of_Blockchain_Micropayment_Networks%20(1).pdf), 
-  [payment points](https://suredbits.com/payment-points-part-1/), Taproot),
-  since they do not separate network communication, channel operation and 
-  channel parameters from each other in a well manner, such that it will be 
-  possible, for instance, to replace HTLCs with payment points using some 
-  extension/module.
-* protocols on top of LN (layer 3), like DLCs or proposed 
-  [Lightspeed protocol](https://github.com/LNP-BP/lnpbps/issues/24), which 
-  require modification on the structure of the commitment transaction.
-
-We name the extensions to Lightning network required to build this rich 
-functionality a "Generalized Lightning Network". With this project 
-[LNP/BP Standards Association](https://github.com/LNP-BP) is trying to build an 
-LN node with extensible and highly-modular architecture, utilizing state of the 
-art Rust approaches like:
-* Microservice architecture
-* Dockerization for scalability at the level of separate processes (per-channel 
-  scalability etc)
-* Tokio-based async/non-blocking IO and rumtime
-* Fast and performant ZeroMQ for APIs and IPCs
-* Avoiding non-efficient Bitcoin blockchain parsing and instead relying on new 
-  [scalable blockchain indexing service](https://github.com/LNP-BP/txserv) and 
-  new format of [universal bitcoin identifiers](https://github.com/LNP-BP/lnpbps/blob/master/lnpbp-0005.md)
-* Mobile- & web-ready via C- and WASM-bindings & build targets for the core 
-  components
-
-This new node will be used to implement:
-
-* Bidirectional channels
-* [Channel factories/multipeer channels](https://tik-old.ee.ethz.ch/file//a20a865ce40d40c8f942cf206a7cba96/Scalable_Funding_Of_Blockchain_Micropayment_Networks%20(1).pdf)
-* [Payment points](https://suredbits.com/payment-points-part-1/)
-* [DLCs on LN](https://hackmd.io/@lpQxZaCeTG6OJZI3awxQPQ/LN-DLC)
-* [RGB & Spectrum](https://github.com/rgb-org/spec)
-* Future [Storm](https://github.com/storm-org/storm-spec) (storage & messaging) 
-  edition for LN
-* Future [Prometheus](https://github.com/pandoracore/prometheus-spec/blob/master/prometheus.pdf) 
-  (high-load computing) edition for LN
-* [Lightspeed payment protocol](https://github.com/LNP-BP/lnpbps/issues/24)
-
-The node must maintain simple/modular upgradability for:
-
-* RGB smart contracts (client-validated smart contract system)
-* Discrete log contracts (DLCs)
-* Schnorr's/Taproot
-* Pay-to-elliptic curve point lock contracts (PTLC) - replacement for HTLCs
-* eltoo
-
-See [here](./demo-alpha.4) for a demo of the node capabilities as for version `v0.1.0-alpha.4`.
-
-## Design
-
-### Approach
-
-The node (as other nodes maitained by LNP/BP Standards Association and Pandora
-Core company subsidiaries) consists of multiple microservices, communicating
-with each other via LNP ZMQ RPC interface.
-
-![Node architacture](./node_arch.jpeg)
-
-The set of microservices representing node can run as either:
-1) single daemon process on desktop or a server;
-2) cloud of docker-based daemons, one per microservice, with instance 
-   scalability and in geo-distributed environment;
-3) inside a single mobile app as threads;
-4) and even different nodes can be combined in their services between themselves
-   into a single executables/mobile apps;
-5) all P2P communications are end-to-end encrypted and work over Tor.
-
-Other nodes, designed an maintained by LNP/BP Standards Association with the 
-same architecture include:
-* [RGB Node](https://github.com/LNP-BP/rgb-node) for running RGB smart contracts
-  over bitcoin and lightning network
-* [BP Node](https://github.com/LNP-BP/bp-node) for indexing bitcoin blockchain
-  (you may think of it as a more efficient Electrum server alternative)
-* Bifrost – node for storing/passing client-side-validated data with watchtower 
-  functionality and used for Storm/RGB/DEX infrastructure
-
-Other third parties provide their own nodes:
-* [Keyring](https://github.com/pandoracore/keyring) for managing private key
-  accounts, storage and signatures with support for miniscript and PSBTs
-* [MyCitadel](https://github.com/mycitadel/mycitadel-node) Bitcoin, LN & RGB
-  enabled wallet service with support for other LNP/BP protocols
-
-### LNP Node Architecture Specifics
-
-The overall architecture of LNP Node is the following:
-
-![Node architacture](./lnp_node_arch.jpeg)
-
-More information on the service buses used in the node:
-
-![Node architacture](./node_esb.jpeg)
-
-
-## Project organization & architecture
-
-* [`src/bin/`](src/bin/) – binaries for daemons & CLI launching main process
-* [`src/cli/`](src/cli/) – CLAP-based command line API talking to message bus
-* [`src/rpc/`](src/rpc/) – RPC commands for all daemons used for message bus
-* [`src/i8n/`](src/i8n/) – functions exposed to FFI talking to message bus
-* `src/<name>/` – service/daemon-specific code:
-  - [`src/peerd/`](src/peerd) – daemon managing peer connections 
-    within Lightning peer network using LNP (Lightning network protocol). 
-    Specific supported message types are defined as a part of 
-    [LNP/BP Core Library](https://github.com/LNP-BP/rust-lnpbp)
-  - [`src/channeld`](src/channeld) – daemon managing generalized Lightning
-    channels with their extensions
-  - [`src/lnpd`](src/lnpd) – daemon initializing creation of new channels and
-    connections
-  - [`src/routed`](src/routed) – daemon managing routing information
-  - [`src/gossip`](src/gossip) – daemon managing gossip data
-  - [`src/keyd`](src/keyd) - key managing daemon
-
-Each daemon (more correctly "microservice", as it can run as a thread, not 
-necessary a process) or other binary (like CLI tool) follows the same
-organization concept for module/file names:
-* `error.rs` – daemon-specific error types;
-* `opts.rs` – CLAP arguments & daemon configuration data;
-* `runtime.rs` – singleton managing main daemon thread and keeping all ZMQ/P2P 
-  connections and sockets; receiving and processing messages through them;
-* `index/`, `storage/`, `cache/` – storage interfaces and engines;
-* `db/` – SQL-specific schema and code, if needed.
-
-## Build and usage
-
-### Local
-
-To compile the node, please install [cargo](https://doc.rust-lang.org/cargo/),
-then run the following commands:
-
-```bash
-sudo apt install -y libsqlite3-dev libssl-dev libzmq3-dev pkg-config
-cargo install --path . --bins --all-features
-cargo run --color=always --bin peerd --features=server -- --listen -vvvv
-```
-
-### In docker
-
-```bash
-docker build -t lnp-node .
-docker run --rm --name lnp_node lnp-node
-```
-
-## Ways of communication
-
-* IRC channels on Freenode
-    * \#lnp-bp: discussions on this and other LNP/BP projects
-    * \#rust-bitcoin: here you can get general support on rust-lightning
-    * \#lightning-dev: here better to ask generic lightning-network questions
-    * dr_orlovsky: my account on IRC
-* Lightning Hackdays Mattermost channel:
-  <https://mm.fulmo.org/fulmo/channels/x-rust-ln-node>
diff --git a/doc/lnp/lnp_node_arch.jpeg b/doc/lnp_node_arch.jpeg
similarity index 100%
rename from doc/lnp/lnp_node_arch.jpeg
rename to doc/lnp_node_arch.jpeg
diff --git a/doc/local-swap.md b/doc/local-swap.md
deleted file mode 100644
index 8ff3130e..00000000
--- a/doc/local-swap.md
+++ /dev/null
@@ -1,75 +0,0 @@
-# Run a swap locally
-
-This document describe how to test a swap running both participant locally.
-
-You'll need to build and install the node locally as described [here](../README.md#local).
-
-We'll create two node instances and will make them running a swap, for that make sure to have access to the three services: an `electrum server`, a `monero daemon`, and a `monero rpc wallet`.
-
-## Launch `farcasterd` services
-
-Launch two `farcasterd` services with different data directories (`.node01` and `.node02`):
-
-```
-farcasterd -vv\
-    --data-dir {.node01|.node02}\
-    --electrum-server {ip:port}\
-    --monero-daemon http://{ip:port}\
-    --monero-rpc-wallet http://{ip:port}
-```
-
-:bulb: You can use a public daemons with
-
-| daemon            | value                              |
-| ----------------- | ---------------------------------- |
-| `electrum-server` | `blockstream.info:143`             |
-| `monero-daemon`   | `http://stagenet.melo.tools:38081` |
-
-## Configure `swap-cli` clients
-
-For simplicity you can create two aliases for the `swap-cli`, you will use them to make and take offers:
-
-```
-alias swap1-cli="swap-cli -d .node01"
-alias swap2-cli="swap-cli -d .node02"
-```
-
-## Access to assets
-
-You need to have access to Bitcoin (testnet) and Monero (stagenet) test coins, use your wallet of choice. You just need a way to fund the swap in bitcoins and moneros.
-
-## Make the offer
-
-Maker creates offer and start listening. Command used to print a hex representation of the offer that shall be shared with taker. Additionally it spins up the listener awaiting for connection related to this offer (binded on `0.0.0.0:9735` with an offer public address of `127.0.0.1:9735`).
-
-```
-swap1-cli make tb1q4gj53tuew3e6u4a32kdtle2q72su8te39dpceq\
-    Testnet ECDSA Monero\
-    "0.00001350 BTC" "0.00000001 XMR"\
-    Alice 4 5 "1 satoshi/vByte"\
-    "127.0.0.1" "0.0.0.0" 9735
-```
-
-:mag_right: The `ECDSA` above is a temporary hack, but it represents `Bitcoin<ECDSA>`, as Bitcoin can take many forms.
-
-This will produce the following hex encoded offer:
-
-`464353574150010002000000808000008008004605000000000000080010270000000000000400040000000400050000000108000100000000000000012100027c083752e022b460a24b436f05998723188791e5367d1bd81ff3de96b697eaa40000000000000000000000000000000000000000000000000000000000007f000001260700`
-
-This public offer should be shared by maker with taker. It also contains information on how to connect to maker. Additionally, it adds the public offers to the set of public offers in `farcasterd` that will be later used to initiate the swap upon takers message.
-
-To get a detailed explaination of make parameters run `swap1-cli make --help`.
-
-## Take an offer
-
-Taker accepts offer and connects to maker's daemon node with the following command (run `swap2-cli take --help` for more details):
-
-Example of taking the offer above produced by maker:
-
-```
-swap2-cli take tb1qt3r3t6yultt8ne88ffgvgyym0sstj4apwsz05j 464353574150010002000000808000008008004605000000000000080010270000000000000400040000000400050000000108000100000000000000012100027c083752e022b460a24b436f05998723188791e5367d1bd81ff3de96b697eaa40000000000000000000000000000000000000000000000000000000000007f000001260700
-```
-
-The first argument is the Bitcoin destination address in case the bitcoins needs to be refunded or if the swap completes (depending on the swap role).
-
-:mag_right: Flag of interest: `--without-validation` or `-w`, for externally validated automated setups (skip validation in cli).
diff --git a/doc/lnp/node_arch.jpeg b/doc/node_arch.jpeg
similarity index 100%
rename from doc/lnp/node_arch.jpeg
rename to doc/node_arch.jpeg
diff --git a/doc/lnp/node_esb.jpeg b/doc/node_esb.jpeg
similarity index 100%
rename from doc/lnp/node_esb.jpeg
rename to doc/node_esb.jpeg
diff --git a/doc/lnp/wallet.md b/doc/wallet.md
similarity index 100%
rename from doc/lnp/wallet.md
rename to doc/wallet.md
diff --git a/doc/lnp/workflows.md b/doc/workflows.md
similarity index 100%
rename from doc/lnp/workflows.md
rename to doc/workflows.md
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index e9b78b22..00000000
--- a/docker-compose.yml
+++ /dev/null
@@ -1,13 +0,0 @@
-version: "3.9"
-services:
-  farcasterd:
-    image: "ghcr.io/farcaster-project/farcaster-node/farcasterd:latest"
-    ports:
-      - "9735:9735"
-      - "9981:9981"
-    command: "-vv --electrum-server blockstream.info:143 --monero-daemon http://stagenet.melo.tools:38081 --monero-rpc-wallet http://walletrpc:18083"
-    depends_on:
-      - "walletrpc"
-  walletrpc:
-    image: "ghcr.io/farcaster-project/containers/monero-wallet-rpc:0.17.2.3"
-    command: "/usr/bin/monero-wallet-rpc --stagenet --disable-rpc-login --wallet-dir wallets --daemon-host stagenet.melo.tools:38081 --rpc-bind-ip 0.0.0.0 --rpc-bind-port 18083 --confirm-external-bind"
diff --git a/dprint.json b/dprint.json
deleted file mode 100644
index 9b5d083f..00000000
--- a/dprint.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "incremental": true,
-  "markdown": {
-  },
-  "dockerfile": {
-  },
-  "includes": ["**/*.{md,dockerfile,toml}"],
-  "excludes": ["doc/lnp/**/*", ".github", "target"],
-  "plugins": [
-    "https://plugins.dprint.dev/markdown-0.11.2.wasm",
-    "https://plugins.dprint.dev/dockerfile-0.1.1.wasm",
-    "https://plugins.dprint.dev/toml-0.5.2.wasm"
-  ]
-}
diff --git a/doc/sequencediagram.txt b/sequencediagram.txt
similarity index 100%
rename from doc/sequencediagram.txt
rename to sequencediagram.txt
diff --git a/src/bin/syncerd.rs b/src/bin/syncerd.rs
index 326fac9e..2875039c 100644
--- a/src/bin/syncerd.rs
+++ b/src/bin/syncerd.rs
@@ -53,7 +53,8 @@ fn main() {
         monero_daemon: opts.shared.monero_daemon,
         monero_rpc_wallet: opts.shared.monero_rpc_wallet,
     };
-    syncerd::run(config, opts.coin, opts.network, syncer_servers).expect("Error running syncerd runtime");
+    syncerd::run(config, opts.coin, opts.network, syncer_servers)
+        .expect("Error running syncerd runtime");
 
     unreachable!()
 }
diff --git a/src/bin/walletd.rs b/src/bin/walletd.rs
index 39473a2f..0a7ca8f2 100644
--- a/src/bin/walletd.rs
+++ b/src/bin/walletd.rs
@@ -31,11 +31,11 @@ extern crate log;
 
 use clap::Clap;
 
+use farcaster_node::Config;
 use farcaster_node::{
     rpc::request::Token,
     walletd::{self, Opts},
 };
-use farcaster_node::Config;
 
 fn main() {
     let mut opts = Opts::parse();
diff --git a/src/cli/command.rs b/src/cli/command.rs
index 4f0557af..34370551 100644
--- a/src/cli/command.rs
+++ b/src/cli/command.rs
@@ -15,6 +15,7 @@
 use std::{
     convert::TryFrom,
     io::{self, Read, Write},
+    sync::mpsc,
     time::Duration,
 };
 use std::{str::FromStr, thread::sleep};
@@ -204,7 +205,7 @@ impl Exec for Command {
             }
             Command::Progress { swapid } => {
                 runtime.request(ServiceId::Farcasterd, Request::ReadProgress(swapid))?;
-                while let Ok(n) = runtime.report_progress() {}
+                runtime.report_progress()?;
             }
             _ => unimplemented!(),
         }
@@ -225,4 +226,3 @@ fn take_offer() -> bool {
         _ => take_offer(),
     }
 }
-
diff --git a/src/cli/opts.rs b/src/cli/opts.rs
index f32f5737..92d40913 100644
--- a/src/cli/opts.rs
+++ b/src/cli/opts.rs
@@ -174,6 +174,7 @@ pub enum Command {
         #[clap(short, long)]
         without_validation: bool,
     },
+
     /// Request Swap progress report
     Progress {
         /// SwapId
diff --git a/src/farcasterd/runtime.rs b/src/farcasterd/runtime.rs
index 585fe798..ed364892 100644
--- a/src/farcasterd/runtime.rs
+++ b/src/farcasterd/runtime.rs
@@ -213,7 +213,8 @@ impl Runtime {
                         &public_offer
                     );
                     if let Some(arb_addr) = self.arb_addrs.remove(&public_offer.id()) {
-                        let btc_addr_req = Request::BitcoinAddress(BitcoinAddress(*swap_id, arb_addr));
+                        let btc_addr_req =
+                            Request::BitcoinAddress(BitcoinAddress(*swap_id, arb_addr));
                         senders.send_to(
                             ServiceBus::Msg,
                             self.identity(),
@@ -680,12 +681,12 @@ impl Runtime {
                     let PublicOffer {
                         version,
                         offer,
-                        node_id, // bitcoin::Pubkey
-                        peer_address,   // InetSocketAddr
+                        node_id,      // bitcoin::Pubkey
+                        peer_address, // InetSocketAddr
                     } = public_offer.clone();
 
                     let daemon_service = internet2::RemoteNodeAddr {
-                        node_id,                           // checked above
+                        node_id,                                           // checked above
                         remote_addr: RemoteSocketAddr::Ftcp(peer_address), /* expected RemoteSocketAddr */
                     };
                     let peer = daemon_service
@@ -759,14 +760,31 @@ impl Runtime {
                 if !self.progress.contains_key(&source) {
                     self.progress.insert(source.clone(), none!());
                 };
+                let identity = self.identity();
                 let queue = self.progress.get_mut(&source).expect("checked/added above");
                 queue.push_back(request);
             }
             Request::ReadProgress(swapid) => {
                 let id = &ServiceId::Swap(swapid);
+                let identify = self.identity();
                 if let Some(queue) = self.progress.get_mut(id) {
-                    for req in queue.iter() {
-                        report_to.push((Some(source.clone()), req.clone()))
+                    let n = queue.len();
+
+                    for (i, req) in queue.iter().enumerate() {
+                        let x = match req {
+                            Request::Progress(x)
+                            | Request::Success(OptionDetails(Some(x)))
+                            | Request::Failure(Failure { code: _, info: x }) => x,
+                            _ => unreachable!("not handled here"),
+                        };
+                        let req = if i < n - 1 {
+                            Request::Progress(x.clone())
+                        } else {
+                            Request::Success(OptionDetails(Some(x.clone())))
+                        };
+                        report_to.push((Some(source.clone()), req));
+                        // senders.send_to(ServiceBus::Ctl, identify.clone(),
+                        // source.clone(), req)?;
                     }
                 }
             }
diff --git a/src/rpc/request.rs b/src/rpc/request.rs
index 0d590239..e9772a6e 100644
--- a/src/rpc/request.rs
+++ b/src/rpc/request.rs
@@ -12,7 +12,10 @@
 // along with this software.
 // If not, see <https://opensource.org/licenses/MIT>.
 
-use crate::syncerd::types::{Event, Task};
+use crate::syncerd::{
+    types::{Event, Task},
+    SweepXmrAddress,
+};
 use crate::walletd::NodeSecrets;
 use amplify::{Holder, ToYamlString, Wrapper};
 use farcaster_core::syncer::BroadcastTransaction;
@@ -488,19 +491,24 @@ pub enum Request {
     // #[from]
     // CreateTask(u64), // FIXME
     #[api(type = 1300)]
-    #[display("task({0})", alt = "{0:#}")]
+    #[display("syncer_task({0})", alt = "{0:#}")]
     #[from]
     SyncerTask(Task),
 
     #[api(type = 1301)]
-    #[display("task({0})", alt = "{0:#}")]
+    #[display("syncer_event({0})", alt = "{0:#}")]
     #[from]
     SyncerEvent(Event),
 
     #[api(type = 1302)]
-    #[display("task({0})", alt = "{0:#}")]
+    #[display("syncer_bridge_ev({0})", alt = "{0:#}")]
     #[from]
     SyncerdBridgeEvent(SyncerdBridgeEvent),
+
+    #[api(type = 1303)]
+    #[display("task({0})", alt = "{0:#}")]
+    #[from]
+    SweepXmrAddress(SweepXmrAddress),
 }
 
 impl rpc_connection::Request for Request {}
diff --git a/src/swapd/runtime.rs b/src/swapd/runtime.rs
index d1b1f4e2..5bb015b6 100644
--- a/src/swapd/runtime.rs
+++ b/src/swapd/runtime.rs
@@ -13,7 +13,10 @@
 // along with this software.
 // If not, see <https://opensource.org/licenses/MIT>.
 
-use crate::syncerd::{opts::Coin, Abort, HeightChanged, WatchHeight, XmrAddressAddendum};
+use crate::syncerd::{
+    opts::Coin, Abort, HeightChanged, SweepAddress, SweepAddressAddendum, SweepXmrAddress,
+    WatchHeight, XmrAddressAddendum,
+};
 use std::{
     any::Any,
     collections::{BTreeMap, HashMap, HashSet},
@@ -75,7 +78,7 @@ use internet2::{
 };
 use lnpbp::chain::Chain;
 use microservices::esb::{self, Handler};
-use monero::cryptonote::hash::keccak_256;
+use monero::{cryptonote::hash::keccak_256, PrivateKey, ViewPair};
 use request::{Commit, InitSwap, Params, Reveal, TakeCommit, Tx};
 
 pub fn run(
@@ -120,12 +123,6 @@ pub fn run(
         watched_addrs: none!(),
         watched_txs: none!(),
     };
-
-    let net = match network {
-        blockchain::Network::Mainnet => monero::Network::Mainnet,
-        blockchain::Network::Testnet => monero::Network::Stagenet,
-        blockchain::Network::Local => monero::Network::Stagenet,
-    };
     let syncer_state = SyncerState {
         tasks,
         monero_height: 0,
@@ -133,7 +130,7 @@ pub fn run(
         confirmation_bound: 50000,
         lock_tx_confs: None,
         cancel_tx_confs: None,
-        monero_address: Box::new(move |view_pair| monero::Address::from_viewpair(net, view_pair)),
+        network,
         bitcoin_syncer: ServiceId::Syncer(Coin::Bitcoin, network),
         monero_syncer: ServiceId::Syncer(Coin::Monero, network),
         monero_amount,
@@ -156,6 +153,7 @@ pub fn run(
         )?),
         pending_requests: none!(),
         txs: none!(),
+        local_params: None,
         remote_params: None,
     };
     let broker = false;
@@ -177,6 +175,7 @@ pub struct Runtime {
     txs: HashMap<TxLabel, bitcoin::Transaction>,
     #[allow(dead_code)]
     storage: Box<dyn storage::Driver>,
+    local_params: Option<Params>,
     remote_params: Option<Params>,
 }
 
@@ -260,7 +259,7 @@ struct SyncerState {
     confirmation_bound: u32,
     lock_tx_confs: Option<Request>,
     cancel_tx_confs: Option<Request>,
-    monero_address: Box<dyn Fn(&monero::ViewPair) -> monero::Address>,
+    network: farcaster_core::blockchain::Network,
     bitcoin_syncer: ServiceId,
     monero_syncer: ServiceId,
     monero_amount: monero::Amount,
@@ -301,6 +300,8 @@ pub struct RefundSigA {
     xmr_locked: bool,
     #[display("buy_published({0})")]
     buy_published: bool,
+    /* #[display("local_view_share({0})")]
+     * local_params: Params */
 }
 
 #[derive(Display, Clone)]
@@ -397,6 +398,7 @@ impl SyncerState {
             u64::MAX
         }
     }
+
     fn from_height(&self, coin: Coin) -> u64 {
         let lookback = match coin {
             Coin::Monero => 300,
@@ -448,13 +450,15 @@ impl SyncerState {
             confirmation_bound: self.confirmation_bound,
         })
     }
+
     fn watch_tx_xmr(&mut self, hash: Vec<u8>, tx_label: TxLabel) -> Task {
         let id = self.tasks.new_taskid();
         self.tasks.watched_txs.insert(id, tx_label);
         info!(
-            "Watching tx {} {}",
+            "Watching tx {} {} with id {}",
             tx_label.bright_green_bold(),
-            hex::encode(&hash).addr()
+            hex::encode(&hash).addr(),
+            id
         );
         Task::WatchTransaction(WatchTransaction {
             id,
@@ -488,25 +492,14 @@ impl SyncerState {
     fn watch_addr_xmr(
         &mut self,
         spend: monero::PublicKey,
-        accordant_shared_keys: Vec<TaggedElement<SharedKeyId, monero::PrivateKey>>,
+        view: monero::PrivateKey,
         swap_role: SwapRole,
         tx_label: TxLabel,
     ) -> Task {
-        let view = accordant_shared_keys
-            .into_iter()
-            .find(|vk| vk.tag() == &SharedKeyId::new(SHARED_VIEW_KEY_ID))
-            .unwrap()
-            .elem()
-            .clone();
-        if swap_role == SwapRole::Alice {
-            let viewpair = monero::ViewPair { spend, view };
-            let address = (self.monero_address)(&viewpair);
-            info!(
-                "Alice, please send {} to {}",
-                self.monero_amount.to_string().bright_green_bold(),
-                address.addr(),
-            );
-        }
+        info!("XMR view key: {}", view);
+        info!("XMR spend key: {}", spend);
+        let viewpair = monero::ViewPair { spend, view };
+        let address = monero::Address::from_viewpair(self.network.into(), &viewpair);
 
         let from_height = self.from_height(Coin::Monero);
 
@@ -519,7 +512,12 @@ impl SyncerState {
         let id = self.tasks.new_taskid();
         self.tasks.watched_addrs.insert(id, tx_label);
 
-        info!("Watching address {}", tx_label.bright_green_bold());
+        info!(
+            "Watching address {} {}",
+            tx_label.bright_green_bold(),
+            address.addr()
+        );
+
         let watch_addr = WatchAddress {
             id,
             lifetime: self.task_lifetime(Coin::Monero),
@@ -528,9 +526,30 @@ impl SyncerState {
         };
         Task::WatchAddress(watch_addr)
     }
+    fn sweep_xmr(
+        &mut self,
+        view_key: monero::PrivateKey,
+        spend_key: monero::PrivateKey,
+        address: String,
+    ) -> Task {
+        let id = self.tasks.new_taskid();
+        let lifetime = self.task_lifetime(Coin::Monero);
+        let addendum = SweepAddressAddendum::Monero(SweepXmrAddress {
+            view_key,
+            spend_key,
+            address,
+        });
+        let sweep_task = SweepAddress {
+            id,
+            lifetime,
+            addendum,
+        };
+        Task::SweepAddress(sweep_task)
+    }
+
     fn acc_lock_watched(&self) -> bool {
         self.tasks
-            .watched_txs
+            .watched_addrs
             .values()
             .find(|&&x| x == TxLabel::AccLock)
             .is_some()
@@ -591,7 +610,8 @@ impl Runtime {
             next_state.bright_white_bold()
         );
         self.state = next_state;
-        self.report_success_to(senders, self.enquirer.clone(), Some(msg))
+        self.report_success_to(senders, self.enquirer.clone(), Some(msg))?;
+        Ok(())
     }
 
     fn broadcast(
@@ -1053,6 +1073,20 @@ impl Runtime {
 
         let ctl_bus = ServiceBus::Ctl;
         match request {
+            Request::SweepXmrAddress(SweepXmrAddress {
+                view_key,
+                spend_key,
+                address,
+            }) if source == ServiceId::Wallet => {
+                let task = self.syncer_state.sweep_xmr(view_key, spend_key, address);
+
+                senders.send_to(
+                    ServiceBus::Ctl,
+                    self.identity(),
+                    self.syncer_state.monero_syncer(),
+                    Request::SyncerTask(task),
+                )?
+            }
             Request::TakeSwap(InitSwap {
                 peerd,
                 report_to,
@@ -1070,6 +1104,7 @@ impl Runtime {
                 };
                 self.peer_service = peerd.clone();
                 self.enquirer = report_to.clone();
+                self.local_params = Some(local_params.clone());
 
                 if let ServiceId::Peer(ref addr) = peerd {
                     self.maker_peer = Some(addr.clone());
@@ -1092,7 +1127,7 @@ impl Runtime {
                             (State::Bob(BobState::CommitB(
                                 CommitC {
                                     trade_role: local_trade_role,
-                                    local_params: local_params.clone(),
+                                    local_params: local_params,
                                     local_commit: local_commit.clone(),
                                     remote_commit: None,
                                 },
@@ -1105,7 +1140,7 @@ impl Runtime {
                         Ok((
                             (State::Alice(AliceState::CommitA(CommitC {
                                 trade_role: local_trade_role,
-                                local_params: local_params.clone(),
+                                local_params: local_params,
                                 local_commit: local_commit.clone(),
                                 remote_commit: None,
                             }))),
@@ -1132,7 +1167,7 @@ impl Runtime {
                     let swap_id = reveal_proof.swap_id();
                     self.send_peer(senders, reveal_proof)?;
                     trace!("forwarded reveal_proof");
-                    let reveal_params: Reveal = (swap_id, local_params.clone()).into();
+                    let reveal_params: Reveal = (swap_id, local_params).into();
                     self.send_peer(senders, Msg::Reveal(reveal_params))?;
                     trace!("sent reveal_proof to peerd");
                     let next_state = match self.state {
@@ -1159,6 +1194,7 @@ impl Runtime {
                     self.maker_peer = Some(addr.clone());
                 }
                 self.enquirer = report_to.clone();
+                self.local_params = Some(local_params.clone());
                 let local_commit = self
                     .maker_commit(senders, &peerd, swap_id, &local_params)
                     .map_err(|err| {
@@ -1289,11 +1325,16 @@ impl Runtime {
                         block,
                         tx,
                     }) if self.state.swap_role() == SwapRole::Alice => {
+                        info!(
+                            "Event details: {} {:?} {} {:?} {:?}",
+                            id, hash, amount, block, tx
+                        );
                         if let State::Alice(AliceState::RefundSigA(RefundSigA {
                             xmr_locked, ..
                         })) = &mut self.state
                         {
                             if !*xmr_locked {
+                                warn!("setting xmr_locked");
                                 *xmr_locked = true;
                             } else {
                                 warn!("xmr_locked was already set to true")
@@ -1487,15 +1528,41 @@ impl Runtime {
                                         xmr_locked: false,
                                     })) = self.state
                                     {
-                                        if let Some(Params::Bob(BobParameters {
-                                            spend,
-                                            accordant_shared_keys,
-                                            ..
-                                        })) = self.remote_params.clone()
+                                        if let (
+                                            Some(Params::Alice(alice_params)),
+                                            Some(Params::Bob(bob_params)),
+                                        ) = (&self.local_params, &self.remote_params)
                                         {
+                                            let (spend, view) = aggregate_xmr_spend_view(
+                                                &alice_params,
+                                                &bob_params,
+                                            );
+                                            let viewpair = monero::ViewPair { spend, view };
+                                            let address = monero::Address::from_viewpair(
+                                                self.syncer_state.network.into(),
+                                                &viewpair,
+                                            );
+                                            info!(
+                                                "Alice, please send {} to {}",
+                                                self.syncer_state
+                                                    .monero_amount
+                                                    .to_string()
+                                                    .bright_green_bold(),
+                                                address.addr(),
+                                            );
+                                            info!(
+                                                "reporting success of {} to {}",
+                                                address.to_string(),
+                                                self.enquirer.clone().unwrap()
+                                            );
+                                            self.report_success_to(
+                                                senders,
+                                                self.enquirer.clone(),
+                                                Some(address.to_string()),
+                                            )?;
                                             let watch_addr_task = self.syncer_state.watch_addr_xmr(
                                                 spend,
-                                                accordant_shared_keys,
+                                                view,
                                                 self.state.swap_role(),
                                                 TxLabel::AccLock,
                                             );
@@ -1530,7 +1597,8 @@ impl Runtime {
                                     if let State::Alice(AliceState::RefundSigA(RefundSigA {
                                         buy_published: false,
                                         xmr_locked,
-                                    })) = self.state
+                                        // local_params,
+                                    })) = self.state.clone()
                                     {
                                         if let Some(buy_tx) = self.txs.remove(&TxLabel::Buy) {
                                             self.broadcast(buy_tx, TxLabel::Buy, senders)?;
@@ -1538,6 +1606,7 @@ impl Runtime {
                                                 State::Alice(AliceState::RefundSigA(RefundSigA {
                                                     buy_published: true,
                                                     xmr_locked,
+                                                    // local_params,
                                                 }));
                                         } else {
                                             warn!(
@@ -1608,6 +1677,9 @@ impl Runtime {
                     Event::TaskAborted(event) => {
                         info!("{}", event)
                     }
+                    Event::SweepSuccess(event) => {
+                        debug!("{}", event)
+                    }
                 }
             }
             Request::Protocol(Msg::CoreArbitratingSetup(core_arb_setup)) => {
@@ -1648,15 +1720,13 @@ impl Runtime {
                 if let State::Bob(BobState::CorearbB(..)) = self.state {
                     info!("received {}", TxLabel::Lock);
                     self.broadcast(btc_lock, TxLabel::Lock, senders)?;
-                    if let Some(Params::Alice(AliceParameters {
-                        spend,
-                        accordant_shared_keys,
-                        ..
-                    })) = self.remote_params.clone()
+                    if let (Some(Params::Bob(bob_params)), Some(Params::Alice(alice_params))) =
+                        (&self.local_params, &self.remote_params)
                     {
+                        let (spend, view) = aggregate_xmr_spend_view(&alice_params, &bob_params);
                         let task = self.syncer_state.watch_addr_xmr(
                             spend,
-                            accordant_shared_keys,
+                            view,
                             self.state.swap_role(),
                             TxLabel::AccLock,
                         );
@@ -1714,7 +1784,7 @@ impl Runtime {
             }
 
             Request::Protocol(Msg::RefundProcedureSignatures(refund_proc_sigs)) => {
-                let next_state = match self.state {
+                let next_state = match self.state.clone() {
                     State::Alice(AliceState::RevealA(_, _)) => {
                         Ok(State::Alice(AliceState::RefundSigA(RefundSigA {
                             xmr_locked: false,
@@ -1919,6 +1989,29 @@ fn log_tx_received(txlabel: TxLabel) {
     info!("received tx {} from {}", txlabel, ServiceId::Wallet);
 }
 
+fn aggregate_xmr_spend_view(
+    alice_params: &AliceParameters<BtcXmr>,
+    bob_params: &BobParameters<BtcXmr>,
+) -> (monero::PublicKey, monero::PrivateKey) {
+    let alice_view = alice_params
+        .accordant_shared_keys
+        .clone()
+        .into_iter()
+        .find(|vk| vk.tag() == &SharedKeyId::new(SHARED_VIEW_KEY_ID))
+        .unwrap()
+        .elem()
+        .clone();
+    let bob_view = bob_params
+        .accordant_shared_keys
+        .clone()
+        .into_iter()
+        .find(|vk| vk.tag() == &SharedKeyId::new(SHARED_VIEW_KEY_ID))
+        .unwrap()
+        .elem()
+        .clone();
+    (alice_params.spend + bob_params.spend, alice_view + bob_view)
+}
+
 #[derive(Debug)]
 struct PendingRequest {
     request: Request,
diff --git a/src/syncerd/bitcoin_syncer.rs b/src/syncerd/bitcoin_syncer.rs
index fb2b77ea..9c33b483 100644
--- a/src/syncerd/bitcoin_syncer.rs
+++ b/src/syncerd/bitcoin_syncer.rs
@@ -338,6 +338,9 @@ async fn run_syncerd_task_receiver(
             match syncerd_task {
                 Ok(syncerd_task) => {
                     match syncerd_task.task {
+                        Task::SweepAddress(_) => {
+                            error!("sweep address not implemented for bitcoin syncer");
+                        }
                         Task::Abort(task) => {
                             let mut state_guard = state.lock().await;
                             state_guard.abort(task.id, syncerd_task.source).await;
diff --git a/src/syncerd/monero_syncer.rs b/src/syncerd/monero_syncer.rs
index a198cd04..d36a2ede 100644
--- a/src/syncerd/monero_syncer.rs
+++ b/src/syncerd/monero_syncer.rs
@@ -9,7 +9,7 @@ use crate::syncerd::runtime::SyncerdTask;
 use crate::syncerd::syncer_state::create_set;
 use crate::syncerd::syncer_state::AddressTx;
 use crate::syncerd::syncer_state::SyncerState;
-use crate::syncerd::types::{AddressAddendum, Task};
+use crate::syncerd::types::{AddressAddendum, SweepAddressAddendum, Task};
 use crate::syncerd::Event;
 use crate::syncerd::SyncerServers;
 use crate::syncerd::TransactionBroadcasted;
@@ -18,15 +18,14 @@ use internet2::zmqsocket::{Connection, ZmqType};
 use internet2::PlainTranscoder;
 use lnpbp::chain::Chain;
 use monero::Hash;
-use monero_rpc::GenerateFromKeysArgs;
-use monero_rpc::GetBlockHeaderSelector;
-use monero_rpc::GetTransfersCategory;
-use monero_rpc::GetTransfersSelector;
-use monero_rpc::TransferType;
+use monero_rpc::{
+    GenerateFromKeysArgs, GetBlockHeaderSelector, GetTransfersCategory, GetTransfersSelector,
+    TransferType,
+};
 use std::collections::HashMap;
 use std::ops::Range;
-use std::sync::mpsc::Receiver;
-use std::sync::mpsc::TryRecvError;
+use std::str::FromStr;
+use std::sync::mpsc::{Receiver, TryRecvError};
 use std::sync::Arc;
 use tokio::runtime::Runtime;
 use tokio::sync::mpsc::Receiver as TokioReceiver;
@@ -43,6 +42,7 @@ pub struct MoneroRpc {
     block_hash: Vec<u8>,
 }
 
+#[derive(Debug)]
 pub struct Block {
     height: u64,
     block_hash: Vec<u8>,
@@ -152,15 +152,17 @@ impl MoneroRpc {
         &mut self,
         address_addendum: XmrAddressAddendum,
         network: monero::Network,
+        wallet_mutex: Arc<Mutex<monero_rpc::WalletClient>>,
     ) -> Result<AddressNotif, Error> {
         let keypair = monero::ViewPair {
             spend: address_addendum.spend_key,
             view: address_addendum.view_key,
         };
         let address = monero::Address::from_viewpair(network, &keypair);
-        let wallet = monero_rpc::RpcClient::new(self.wallet_rpc_url.clone()).wallet();
         let password = s!(" ");
 
+        let wallet = wallet_mutex.lock().await;
+
         match wallet
             .open_wallet(address.to_string(), Some(password.clone()))
             .await
@@ -203,6 +205,7 @@ impl MoneroRpc {
         };
 
         let mut transfers = wallet.get_transfers(selector).await?;
+        drop(wallet);
 
         let mut address_txs: Vec<AddressTx> = vec![];
         for (_category, mut txs) in transfers.drain() {
@@ -220,6 +223,77 @@ impl MoneroRpc {
     }
 }
 
+async fn sweep_address(
+    dest_address: monero::Address,
+    view: monero::PrivateKey,
+    spend: monero::PrivateKey,
+    network: &monero::Network,
+    wallet_mutex: Arc<Mutex<monero_rpc::WalletClient>>,
+) -> Result<Option<Vec<Vec<u8>>>, Error> {
+    let keypair = monero::KeyPair { view, spend };
+    let password = s!(" ");
+    let address = monero::Address::from_keypair(*network, &keypair);
+
+    let wallet = wallet_mutex.lock().await;
+
+    match wallet
+        .open_wallet(address.to_string(), Some(password.clone()))
+        .await
+    {
+        Ok(_) => {}
+        Err(err) => {
+            debug!(
+                "error opening to be sweeped wallet: {:?}, falling back to generating a new wallet",
+                err,
+            );
+            wallet
+                .generate_from_keys(GenerateFromKeysArgs {
+                    restore_height: Some(1),
+                    filename: address.to_string(),
+                    address,
+                    spendkey: Some(keypair.spend),
+                    viewkey: keypair.view,
+                    password: password.clone(),
+                    autosave_current: Some(true),
+                })
+                .await?;
+            wallet
+                .open_wallet(address.to_string(), Some(password))
+                .await?;
+        }
+    }
+
+    wallet.refresh(Some(1)).await?;
+
+    let balance = wallet.get_balance(0, None).await?;
+    // only sweep once all the balance is unlocked
+    if balance.balance >= balance.unlocked_balance {
+        let sweep_args = monero_rpc::SweepAllArgs {
+            address: dest_address,
+            account_index: 0,
+            subaddr_indices: None,
+            priority: monero_rpc::TransferPriority::Default,
+            mixin: 10,
+            ring_size: 11,
+            unlock_time: 0,
+            get_tx_keys: None,
+            below_amount: None,
+            do_not_relay: None,
+            get_tx_hex: None,
+            get_tx_metadata: None,
+        };
+        let res = wallet.sweep_all(sweep_args).await?;
+        let tx_ids: Vec<Vec<u8>> = res
+            .tx_hash_list
+            .iter()
+            .map(|hash| hex::decode(hash.to_string()).unwrap())
+            .collect();
+
+        return Ok(Some(tx_ids));
+    }
+    Ok(None)
+}
+
 pub struct MoneroSyncer {}
 
 impl MoneroSyncer {
@@ -243,6 +317,18 @@ async fn run_syncerd_task_receiver(
             match syncerd_task {
                 Ok(syncerd_task) => {
                     match syncerd_task.task {
+                        Task::SweepAddress(task) => match task.addendum.clone() {
+                            SweepAddressAddendum::Monero(_) => {
+                                info!("sweeping to address: {}", task);
+                                let mut state_guard = state.lock().await;
+                                state_guard.sweep_address(task, syncerd_task.source);
+                            }
+                            _ => {
+                                error!("Aborting sweep address task - unable to decode sweep address addendum");
+                                let mut state_guard = state.lock().await;
+                                state_guard.abort(task.id, syncerd_task.source).await;
+                            }
+                        },
                         Task::Abort(task) => {
                             let mut state_guard = state.lock().await;
                             state_guard.abort(task.id, syncerd_task.source).await;
@@ -295,6 +381,7 @@ fn address_polling(
     state: Arc<Mutex<SyncerState>>,
     syncer_servers: SyncerServers,
     network: monero::Network,
+    wallet_mutex: Arc<Mutex<monero_rpc::WalletClient>>,
 ) -> tokio::task::JoinHandle<()> {
     tokio::task::spawn(async move {
         let mut rpc = MoneroRpc::new(
@@ -314,7 +401,11 @@ fn address_polling(
                 // wallet
                 let mut address_transactions = None;
                 match rpc
-                    .check_address(address_addendum.clone(), network.clone())
+                    .check_address(
+                        address_addendum.clone(),
+                        network.clone(),
+                        Arc::clone(&wallet_mutex),
+                    )
                     .await
                 {
                     Ok(addr_txs) => {
@@ -391,6 +482,48 @@ fn height_polling(
     })
 }
 
+fn sweep_polling(
+    state: Arc<Mutex<SyncerState>>,
+    wallet: Arc<Mutex<monero_rpc::WalletClient>>,
+    network: monero::Network,
+) -> tokio::task::JoinHandle<()> {
+    tokio::task::spawn(async move {
+        loop {
+            let state_guard = state.lock().await;
+            let sweep_addresses = state_guard.sweep_addresses.clone();
+            drop(state_guard);
+            for (id, sweep_address_task) in sweep_addresses.iter() {
+                if let SweepAddressAddendum::Monero(addendum) = sweep_address_task.addendum.clone()
+                {
+                    let mut sweep_address_txs = None;
+                    match sweep_address(
+                        monero::Address::from_str(&addendum.address).unwrap(),
+                        addendum.view_key,
+                        addendum.spend_key,
+                        &network,
+                        Arc::clone(&wallet),
+                    )
+                    .await
+                    {
+                        Ok(val) => {
+                            sweep_address_txs = val;
+                        }
+                        Err(err) => {
+                            trace!("error polling sweep address {:?}, retrying", err);
+                        }
+                    }
+                    if let Some(txids) = sweep_address_txs {
+                        let mut state_guard = state.lock().await;
+                        state_guard.success_sweep(id, txids).await;
+                        drop(state_guard);
+                    }
+                }
+            }
+            tokio::time::sleep(std::time::Duration::from_secs(5)).await;
+        }
+    })
+}
+
 fn unseen_transaction_polling(
     state: Arc<Mutex<SyncerState>>,
     syncer_servers: SyncerServers,
@@ -473,6 +606,18 @@ impl Synclet for MoneroSyncer {
         if !polling {
             error!("monero syncer only supports polling for now - switching to polling=true");
         }
+        let network = match chain.clone() {
+            Chain::Mainnet | Chain::Regtest(_) => monero::Network::Mainnet,
+            Chain::Testnet3 => monero::Network::Stagenet,
+            Chain::Signet => monero::Network::Testnet,
+            _ => {
+                error!(
+                    "invalid chain type for monero: {}- switching to mainnet",
+                    chain
+                );
+                monero::Network::Mainnet
+            }
+        };
         let _handle = std::thread::spawn(move || {
             use tokio::runtime::Builder;
             let rt = Builder::new_multi_thread()
@@ -481,6 +626,9 @@ impl Synclet for MoneroSyncer {
                 .build()
                 .unwrap();
             rt.block_on(async {
+                let wallet_mutex = Arc::new(Mutex::new(
+                    monero_rpc::RpcClient::new(syncer_servers.monero_rpc_wallet.clone()).wallet(),
+                ));
                 let (event_tx, event_rx): (
                     TokioSender<SyncerdBridgeEvent>,
                     TokioReceiver<SyncerdBridgeEvent>,
@@ -495,21 +643,12 @@ impl Synclet for MoneroSyncer {
                 .await;
                 run_syncerd_bridge_event_sender(tx, event_rx, syncer_address).await;
 
-                let network = match chain.clone() {
-                    Chain::Mainnet | Chain::Regtest(_) => monero::Network::Mainnet,
-                    Chain::Testnet3 => monero::Network::Stagenet,
-                    Chain::Signet => monero::Network::Testnet,
-                    _ => {
-                        error!(
-                            "invalid chain type for monero: {}- switching to mainnet",
-                            chain
-                        );
-                        monero::Network::Mainnet
-                    }
-                };
-
-                let address_handle =
-                    address_polling(Arc::clone(&state), syncer_servers.clone(), network);
+                let address_handle = address_polling(
+                    Arc::clone(&state),
+                    syncer_servers.clone(),
+                    network.clone(),
+                    Arc::clone(&wallet_mutex),
+                );
 
                 // transaction polling is done in the same loop
                 let height_handle = height_polling(Arc::clone(&state), syncer_servers.clone());
@@ -517,8 +656,15 @@ impl Synclet for MoneroSyncer {
                 let unseen_transaction_handle =
                     unseen_transaction_polling(Arc::clone(&state), syncer_servers.clone());
 
-                let res =
-                    tokio::try_join!(address_handle, height_handle, unseen_transaction_handle);
+                let sweep_handle =
+                    sweep_polling(Arc::clone(&state), Arc::clone(&wallet_mutex), network);
+
+                let res = tokio::try_join!(
+                    address_handle,
+                    height_handle,
+                    unseen_transaction_handle,
+                    sweep_handle
+                );
                 debug!("exiting monero synclet run routine with: {:?}", res);
             });
         });
diff --git a/src/syncerd/opts.rs b/src/syncerd/opts.rs
index 6cf57413..3b8162b8 100644
--- a/src/syncerd/opts.rs
+++ b/src/syncerd/opts.rs
@@ -13,6 +13,7 @@
 // If not, see <https://opensource.org/licenses/MIT>.
 
 use clap::{AppSettings, Clap};
+use farcaster_core::blockchain;
 use std::str::FromStr;
 use strict_encoding::{StrictDecode, StrictEncode};
 
@@ -82,3 +83,11 @@ impl Opts {
         self.shared.process();
     }
 }
+
+// fn into(network: &farcaster_core::blockchain::Network) -> monero::Network {
+//     match network {
+//         blockchain::Network::Mainnet => monero::Network::Mainnet,
+//         blockchain::Network::Testnet => monero::Network::Stagenet,
+//         blockchain::Network::Local => monero::Network::Mainnet,
+//     }
+// }
diff --git a/src/syncerd/syncer_state.rs b/src/syncerd/syncer_state.rs
index 887494c7..79a82ca9 100644
--- a/src/syncerd/syncer_state.rs
+++ b/src/syncerd/syncer_state.rs
@@ -1,4 +1,4 @@
-use crate::farcaster_core::consensus::Decodable;
+use crate::farcaster_core::{blockchain::Network, consensus::Decodable};
 use crate::rpc::request::SyncerdBridgeEvent;
 use crate::syncerd::opts::Coin;
 use crate::syncerd::runtime::SyncerdTask;
@@ -25,6 +25,7 @@ pub struct SyncerState {
     pub addresses: HashMap<u32, AddressTransactions>,
     pub transactions: HashMap<u32, WatchedTransaction>,
     pub unseen_transactions: HashSet<u32>,
+    pub sweep_addresses: HashMap<u32, SweepAddress>,
     tx_event: TokioSender<SyncerdBridgeEvent>,
     task_count: u32,
 }
@@ -63,6 +64,7 @@ impl SyncerState {
             addresses: HashMap::new(),
             transactions: HashMap::new(),
             unseen_transactions: HashSet::new(),
+            sweep_addresses: HashMap::new(),
             tx_event,
             task_count: 0,
         }
@@ -75,9 +77,9 @@ impl SyncerState {
             .iter()
             .filter_map(|(id, address_transaction)| {
                 if address_transaction.task.id == task_id {
-                    return Some(id.clone());
+                    Some(id.clone())
                 } else {
-                    return None;
+                    None
                 }
             })
             .collect();
@@ -107,9 +109,9 @@ impl SyncerState {
             .iter()
             .filter_map(|(id, watched_transaction)| {
                 if watched_transaction.task.id == task_id {
-                    return Some(id.clone());
+                    Some(id.clone())
                 } else {
-                    return None;
+                    None
                 }
             })
             .collect();
@@ -139,9 +141,9 @@ impl SyncerState {
             .iter()
             .filter_map(|(id, watch_height)| {
                 if watch_height.id == task_id {
-                    return Some(id.clone());
+                    Some(id.clone())
                 } else {
-                    return None;
+                    None
                 }
             })
             .collect();
@@ -165,6 +167,38 @@ impl SyncerState {
             }
         }
 
+        // check sweep address tasks
+        let ids: Vec<u32> = self
+            .sweep_addresses
+            .iter()
+            .filter_map(|(id, sweep_address)| {
+                if sweep_address.id == task_id {
+                    Some(id.clone())
+                } else {
+                    None
+                }
+            })
+            .collect();
+        for id in ids.iter() {
+            if let Some(source_id) = self.tasks_sources.get(&id) {
+                if *source_id == source {
+                    self.remove_sweep_address(id);
+                    send_event(
+                        &self.tx_event,
+                        &mut vec![(
+                            Event::TaskAborted(TaskAborted {
+                                id: task_id.clone(),
+                                error: None,
+                            }),
+                            source.clone(),
+                        )],
+                    )
+                    .await;
+                    return;
+                }
+            }
+        }
+
         send_event(
             &self.tx_event,
             &mut vec![(
@@ -245,6 +279,18 @@ impl SyncerState {
         self.unseen_transactions.insert(self.task_count);
     }
 
+    pub fn sweep_address(&mut self, task: SweepAddress, source: ServiceId) {
+        self.task_count += 1;
+        // This is technically valid behavior; immediately prune the task for being past
+        // its lifetime by never inserting it
+        if let Err(e) = self.add_lifetime(task.lifetime, self.task_count) {
+            error!("{}", e);
+            return;
+        }
+        self.sweep_addresses.insert(self.task_count, task.clone());
+        self.tasks_sources.insert(self.task_count, source.clone());
+    }
+
     pub async fn change_height(&mut self, height: u64, block: Vec<u8>) {
         if self.block_height != height || self.block_hash != block {
             self.block_height = height;
@@ -394,9 +440,7 @@ impl SyncerState {
                     if confirmations.is_some() {
                         unseen_transactions.remove(&id);
                     } else {
-                        if !unseen_transactions.contains(&id) {
-                            unseen_transactions.insert(id.clone());
-                        }
+                        unseen_transactions.insert(id.clone());
                     }
                     let transaction_confirmations = if confirmations
                         != watched_tx.transaction_confirmations.confirmations
@@ -434,6 +478,33 @@ impl SyncerState {
         send_event(&self.tx_event, &mut events).await;
     }
 
+    pub async fn success_sweep(&mut self, id: &u32, txids: Vec<Vec<u8>>) {
+        if let Some(sweep_address) = self.sweep_addresses.get(id) {
+            send_event(
+                &self.tx_event,
+                &mut vec![(
+                    Event::SweepSuccess(SweepSuccess {
+                        id: sweep_address.id,
+                        txids,
+                    }),
+                    self.tasks_sources
+                        .get(&id)
+                        .cloned()
+                        .expect("task source missing"),
+                )],
+            )
+            .await;
+            if let Some(ids) = self.lifetimes.get_mut(&sweep_address.lifetime) {
+                ids.remove(id);
+                if ids.is_empty() {
+                    self.lifetimes.remove(&sweep_address.lifetime);
+                }
+            }
+            self.sweep_addresses.remove(id);
+            self.tasks_sources.remove(id);
+        }
+    }
+
     fn drop_lifetimes(&mut self) {
         let lifetimes: Vec<u64> = Iterator::collect(self.lifetimes.keys().map(|&x| x.to_owned()));
         for lifetime in lifetimes {
@@ -466,6 +537,7 @@ impl SyncerState {
                 self.transactions.remove(task);
                 self.unseen_transactions.remove(task);
                 self.watch_height.remove(task);
+                self.sweep_addresses.remove(task);
                 self.tasks_sources.remove(task);
             }
         } else {
@@ -512,6 +584,19 @@ impl SyncerState {
         self.watch_height.remove(id);
         self.tasks_sources.remove(id);
     }
+
+    fn remove_sweep_address(&mut self, id: &u32) {
+        if let Some(sweep_address) = self.sweep_addresses.get(id) {
+            if let Some(ids) = self.lifetimes.get_mut(&sweep_address.lifetime) {
+                ids.remove(id);
+                if ids.is_empty() {
+                    self.lifetimes.remove(&sweep_address.lifetime);
+                }
+            }
+        }
+        self.sweep_addresses.remove(id);
+        self.tasks_sources.remove(id);
+    }
 }
 
 async fn send_event(
@@ -548,25 +633,15 @@ async fn syncer_state_transaction() {
         confirmation_bound: 4,
     };
     let height_task = WatchHeight { id: 0, lifetime: 4 };
+    let source1 = ServiceId::Syncer(Coin::Bitcoin, Network::Mainnet);
 
-    state.watch_transaction(
-        transaction_task_one.clone(),
-        ServiceId::Syncer(Coin::Bitcoin),
-    );
-    state.abort(0, ServiceId::Syncer(Coin::Bitcoin)).await;
+    state.watch_transaction(transaction_task_one.clone(), source1.clone());
+    state.abort(0, source1.clone()).await;
     assert!(event_rx.try_recv().is_ok());
 
-    state.watch_transaction(
-        transaction_task_one.clone(),
-        ServiceId::Syncer(Coin::Bitcoin),
-    );
-    state.watch_transaction(
-        transaction_task_two.clone(),
-        ServiceId::Syncer(Coin::Bitcoin),
-    );
-    state
-        .watch_height(height_task, ServiceId::Syncer(Coin::Bitcoin))
-        .await;
+    state.watch_transaction(transaction_task_one.clone(), source1.clone());
+    state.watch_transaction(transaction_task_two.clone(), source1.clone());
+    state.watch_height(height_task, source1.clone()).await;
     assert_eq!(state.lifetimes.len(), 3);
     assert_eq!(state.transactions.len(), 2);
     assert_eq!(state.tasks_sources.len(), 3);
@@ -619,11 +694,9 @@ async fn syncer_state_transaction() {
     assert_eq!(state.unseen_transactions.len(), 2);
     assert!(event_rx.try_recv().is_ok());
 
-    state.watch_transaction(
-        transaction_task_two.clone(),
-        ServiceId::Syncer(Coin::Monero),
-    );
-    state.abort(0, ServiceId::Syncer(Coin::Monero)).await;
+    let source2 = ServiceId::Syncer(Coin::Monero, Network::Mainnet);
+    state.watch_transaction(transaction_task_two.clone(), source2.clone());
+    state.abort(0, source2.clone()).await;
     assert_eq!(state.lifetimes.len(), 3);
     assert_eq!(state.transactions.len(), 2);
     assert_eq!(state.tasks_sources.len(), 3);
@@ -665,19 +738,18 @@ async fn syncer_state_addresses() {
         addendum: addendum.clone(),
         include_tx: Boolean::False,
     };
+    let source1 = ServiceId::Syncer(Coin::Bitcoin, Network::Mainnet);
 
     state
-        .watch_address(address_task_two.clone(), ServiceId::Syncer(Coin::Bitcoin))
+        .watch_address(address_task_two.clone(), source1.clone())
         .unwrap();
-    state.abort(0, ServiceId::Syncer(Coin::Bitcoin)).await;
+    state.abort(0, source1.clone()).await;
     assert_eq!(state.lifetimes.len(), 0);
     assert_eq!(state.tasks_sources.len(), 0);
     assert_eq!(state.addresses.len(), 0);
     assert!(event_rx.try_recv().is_ok());
 
-    state
-        .watch_address(address_task, ServiceId::Syncer(Coin::Bitcoin))
-        .unwrap();
+    state.watch_address(address_task, source1.clone()).unwrap();
     assert_eq!(state.lifetimes.len(), 1);
     assert_eq!(state.tasks_sources.len(), 1);
     assert_eq!(state.addresses.len(), 1);
@@ -763,10 +835,11 @@ async fn syncer_state_addresses() {
     assert!(event_rx.try_recv().is_ok());
     assert!(event_rx.try_recv().is_ok());
 
+    let source2 = ServiceId::Syncer(Coin::Monero, Network::Testnet);
     state
-        .watch_address(address_task_two.clone(), ServiceId::Syncer(Coin::Monero))
+        .watch_address(address_task_two.clone(), source2.clone())
         .unwrap();
-    state.abort(0, ServiceId::Syncer(Coin::Monero)).await;
+    state.abort(0, source2.clone()).await;
     assert_eq!(state.lifetimes.len(), 1);
     assert_eq!(state.tasks_sources.len(), 1);
     assert_eq!(state.addresses.len(), 1);
@@ -774,9 +847,7 @@ async fn syncer_state_addresses() {
 
     state.change_height(1, vec![0]).await;
     let height_task = WatchHeight { id: 0, lifetime: 3 };
-    state
-        .watch_height(height_task, ServiceId::Syncer(Coin::Bitcoin))
-        .await;
+    state.watch_height(height_task, source1.clone()).await;
     assert_eq!(state.lifetimes.len(), 2);
     assert_eq!(state.tasks_sources.len(), 2);
     assert!(event_rx.try_recv().is_ok());
@@ -794,6 +865,53 @@ async fn syncer_state_addresses() {
     assert!(event_rx.try_recv().is_ok());
 }
 
+#[tokio::test]
+async fn syncer_state_sweep_addresses() {
+    use std::str::FromStr;
+    use tokio::sync::mpsc::Receiver as TokioReceiver;
+    let (event_tx, mut event_rx): (
+        TokioSender<SyncerdBridgeEvent>,
+        TokioReceiver<SyncerdBridgeEvent>,
+    ) = tokio::sync::mpsc::channel(120);
+    let mut state = SyncerState::new(event_tx.clone());
+    let sweep_task = SweepAddress {
+        id: 0,
+        lifetime: 11,
+        addendum: SweepAddressAddendum::Monero(SweepXmrAddress {
+            view_key: monero::PrivateKey::from_str(
+                "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404",
+            )
+            .unwrap(),
+            spend_key: monero::PrivateKey::from_str(
+                "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404",
+            )
+            .unwrap(),
+            address: "address".to_string(),
+        }),
+    };
+    let source1 = ServiceId::Syncer(Coin::Monero, Network::Mainnet);
+
+    state.sweep_address(sweep_task.clone(), source1.clone());
+    assert_eq!(state.lifetimes.len(), 1);
+    assert_eq!(state.tasks_sources.len(), 1);
+    assert_eq!(state.sweep_addresses.len(), 1);
+    state.abort(0, source1.clone()).await;
+    assert_eq!(state.lifetimes.len(), 0);
+    assert_eq!(state.tasks_sources.len(), 0);
+    assert_eq!(state.sweep_addresses.len(), 0);
+    assert!(event_rx.try_recv().is_ok());
+
+    state.sweep_address(sweep_task, source1.clone());
+    assert_eq!(state.lifetimes.len(), 1);
+    assert_eq!(state.tasks_sources.len(), 1);
+    assert_eq!(state.sweep_addresses.len(), 1);
+    state.success_sweep(&2, vec![vec![0]]).await;
+    assert_eq!(state.lifetimes.len(), 0);
+    assert_eq!(state.tasks_sources.len(), 0);
+    assert_eq!(state.sweep_addresses.len(), 0);
+    assert!(event_rx.try_recv().is_ok());
+}
+
 #[tokio::test]
 async fn syncer_state_height() {
     use tokio::sync::mpsc::Receiver as TokioReceiver;
@@ -804,24 +922,22 @@ async fn syncer_state_height() {
     let mut state = SyncerState::new(event_tx.clone());
     let height_task = WatchHeight { id: 0, lifetime: 0 };
     let another_height_task = WatchHeight { id: 0, lifetime: 3 };
+    let source1 = ServiceId::Syncer(Coin::Bitcoin, Network::Mainnet);
 
     state
-        .watch_height(height_task.clone(), ServiceId::Syncer(Coin::Bitcoin))
+        .watch_height(height_task.clone(), source1.clone())
         .await;
-    state.abort(0, ServiceId::Syncer(Coin::Bitcoin)).await;
+    state.abort(0, source1.clone()).await;
     assert_eq!(state.lifetimes.len(), 0);
     assert_eq!(state.tasks_sources.len(), 0);
     assert_eq!(state.watch_height.len(), 0);
     assert!(event_rx.try_recv().is_ok());
 
     state
-        .watch_height(height_task.clone(), ServiceId::Syncer(Coin::Bitcoin))
+        .watch_height(height_task.clone(), source1.clone())
         .await;
     state
-        .watch_height(
-            another_height_task.clone(),
-            ServiceId::Syncer(Coin::Bitcoin),
-        )
+        .watch_height(another_height_task.clone(), source1.clone())
         .await;
     assert_eq!(state.lifetimes.len(), 2);
     assert_eq!(state.tasks_sources.len(), 2);
@@ -846,10 +962,11 @@ async fn syncer_state_height() {
     assert_eq!(state.watch_height.len(), 1);
     assert!(event_rx.try_recv().is_err());
 
+    let source2 = ServiceId::Syncer(Coin::Monero, Network::Mainnet);
     state
-        .watch_height(another_height_task.clone(), ServiceId::Syncer(Coin::Monero))
+        .watch_height(another_height_task.clone(), source2.clone())
         .await;
-    state.abort(0, ServiceId::Syncer(Coin::Monero)).await;
+    state.abort(0, source2.clone()).await;
     assert_eq!(state.lifetimes.len(), 1);
     assert_eq!(state.tasks_sources.len(), 1);
     assert_eq!(state.watch_height.len(), 1);
diff --git a/src/syncerd/types.rs b/src/syncerd/types.rs
index 78c12460..20c24076 100644
--- a/src/syncerd/types.rs
+++ b/src/syncerd/types.rs
@@ -26,6 +26,36 @@ pub struct XmrAddressAddendum {
     pub from_height: u64,
 }
 
+#[derive(Clone, Debug, Display, StrictEncode, StrictDecode, Eq, PartialEq, Hash)]
+#[display(Debug)]
+pub struct SweepAddress {
+    pub id: u32,
+    pub lifetime: u64,
+    pub addendum: SweepAddressAddendum,
+}
+
+#[derive(Clone, Debug, Display, StrictEncode, StrictDecode, Eq, PartialEq, Hash)]
+#[display(Debug)]
+pub enum SweepAddressAddendum {
+    Monero(SweepXmrAddress),
+    Bitcoin(SweepBitcoinAddress),
+}
+
+#[derive(Clone, Debug, Display, StrictEncode, StrictDecode, Eq, PartialEq, Hash)]
+#[display(Debug)]
+pub struct SweepXmrAddress {
+    pub spend_key: monero::PrivateKey,
+    pub view_key: monero::PrivateKey,
+    pub address: String,
+}
+
+#[derive(Clone, Debug, Display, StrictEncode, StrictDecode, Eq, PartialEq, Hash)]
+#[display(Debug)]
+pub struct SweepBitcoinAddress {
+    pub private_key: [u8; 32],
+    pub address: bitcoin::Address,
+}
+
 #[derive(Clone, Debug, Display, StrictEncode, StrictDecode, Eq, PartialEq, Hash)]
 #[display(Debug)]
 pub struct Abort {
@@ -90,6 +120,7 @@ pub enum Task {
     WatchAddress(WatchAddress),
     WatchTransaction(WatchTransaction),
     BroadcastTransaction(BroadcastTransaction),
+    SweepAddress(SweepAddress),
 }
 
 #[derive(Clone, Debug, Display, StrictEncode, StrictDecode, Eq, PartialEq, Hash)]
@@ -133,6 +164,13 @@ pub struct TransactionBroadcasted {
     pub error: Option<String>,
 }
 
+#[derive(Clone, Debug, Display, StrictEncode, StrictDecode, Eq, PartialEq, Hash)]
+#[display(Debug)]
+pub struct SweepSuccess {
+    pub id: u32,
+    pub txids: Vec<Vec<u8>>,
+}
+
 /// Events returned by syncers to the daemon to update the blockchain states.
 /// Events are identified with a unique 32-bits integer that match the [`Task`]
 /// id.
@@ -144,6 +182,7 @@ pub enum Event {
     AddressTransaction(AddressTransaction),
     TransactionConfirmations(TransactionConfirmations),
     TransactionBroadcasted(TransactionBroadcasted),
+    SweepSuccess(SweepSuccess),
     /// Notify the daemon the task has been aborted with success or failure.
     /// Carries the status for the task abortion.
     TaskAborted(TaskAborted),
diff --git a/src/walletd/runtime.rs b/src/walletd/runtime.rs
index 0feae3d9..cbacd66e 100644
--- a/src/walletd/runtime.rs
+++ b/src/walletd/runtime.rs
@@ -7,14 +7,17 @@ use std::{
     str::FromStr,
 };
 
-use crate::rpc::{
-    request::{self, BitcoinAddress, Commit, Keys, Msg, Params, Reveal, Token, Tx},
-    Request, ServiceBus,
-};
 use crate::swapd::get_swap_id;
 use crate::walletd::NodeSecrets;
 use crate::LogStyle;
 use crate::Senders;
+use crate::{
+    rpc::{
+        request::{self, BitcoinAddress, Commit, Keys, Msg, Params, Reveal, Token, Tx},
+        Request, ServiceBus,
+    },
+    syncerd::SweepXmrAddress,
+};
 use crate::{Config, CtlServer, Error, Service, ServiceId};
 use bitcoin::{
     hashes::hex::FromHex,
@@ -1094,10 +1097,12 @@ impl Runtime {
             Request::Tx(Tx::Buy(buy_tx)) => {
                 if let Some(Wallet::Bob(BobState {
                     bob,
+                    local_params,
                     key_manager,
                     remote_params: Some(alice_params),
                     core_arb_setup: Some(_),
                     adaptor_buy: Some(adaptor_buy),
+                    pub_offer,
                     ..
                 })) = self.wallets.get_mut(&get_swap_id(&source)?)
                 {
@@ -1112,18 +1117,49 @@ impl Runtime {
                     let sk_a = monero::PrivateKey::from_slice(sk_a_btc_buf.as_ref())
                         .expect("Valid Monero Private Key");
                     info!("Extracted alice's monero key from Buy tx: {}", sk_a);
-
                     let sk_b = key_manager.get_or_derive_monero_spend_key()?;
-                    info!("Full secret monero spending key: {}", sk_a + sk_b);
+                    let spend_private = sk_a + sk_b;
+                    let spend = monero::PublicKey::from_private_key(&spend_private);
+                    info!("Full secret monero spending key: {}", spend_private);
 
-                    let view_key = *alice_params
+                    let view_key_alice = *alice_params
                         .accordant_shared_keys
                         .clone()
                         .into_iter()
                         .find(|vk| vk.tag() == &SharedKeyId::new(SHARED_VIEW_KEY_ID))
                         .unwrap()
                         .elem();
-                    info!("Full secret monero view key: {}", view_key);
+
+                    let view_key_bob = *local_params
+                        .accordant_shared_keys
+                        .clone()
+                        .into_iter()
+                        .find(|vk| vk.tag() == &SharedKeyId::new(SHARED_VIEW_KEY_ID))
+                        .unwrap()
+                        .elem();
+                    let view = view_key_alice + view_key_bob;
+                    // let view_key_bob = params.
+                    info!("Full aggregated secret monero view key: {}", view);
+
+                    let viewpair = monero::ViewPair { spend, view };
+                    // let address = (swapd.monero_address)(&viewpair);
+                    let address =
+                        monero::Address::from_viewpair(monero::Network::Stagenet, &viewpair);
+
+                    info!("Corresponding address: {}", address.addr());
+
+                    let address = s!("76KwxdgtWyJQK5a27PpqT973R6s7bZ4cKWTtUG6JEyrbLQy5F4ZwdUqgKpssGQzRxnd99LKWqFjYo92b8ngS7GqD1VZBEEL");
+                    let sweep_keys = SweepXmrAddress {
+                        view_key: view,
+                        spend_key: spend_private,
+                        address,
+                    };
+                    senders.send_to(
+                        ServiceBus::Ctl,
+                        self.identity(),
+                        source,
+                        Request::SweepXmrAddress(sweep_keys),
+                    )?;
                 }
             }
             Request::Tx(Tx::Refund(refund_tx)) => {
@@ -1149,7 +1185,8 @@ impl Runtime {
                     info!("Extracted alice's monero key from Buy tx: {}", sk_b);
 
                     let sk_c = key_manager.get_or_derive_monero_spend_key()?;
-                    info!("Full secret monero spending key: {}", sk_b + sk_c);
+                    let spend_key = sk_b + sk_c;
+                    info!("Full secret monero spending key: {}", spend_key);
 
                     let view_key = *bob_params
                         .accordant_shared_keys
@@ -1159,6 +1196,18 @@ impl Runtime {
                         .unwrap()
                         .elem();
                     info!("Full secret monero view key: {}", view_key);
+                    let address = s!("73oh6eWaf9MStZuxXqeE4nVx9jP5Y8XBZeLQ9AbAqFMWfGLSxjPdkbigWsb2PYvzyTWN3p2wa1jJn28V3vb4zqA4UoCXJp9");
+                    let sweep_keys = SweepXmrAddress {
+                        view_key,
+                        spend_key,
+                        address,
+                    };
+                    senders.send_to(
+                        ServiceBus::Ctl,
+                        self.identity(),
+                        source,
+                        Request::SweepXmrAddress(sweep_keys),
+                    )?;
                 }
             }
             Request::GetKeys(request::GetKeys(wallet_token, request_id)) => {


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 4271e007..f4cc0027 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -2,41 +2,26 @@ name: Tests
 
 on:
   push:
-    branches: [ main ]
+    branches:
+      -main
   pull_request:
-    branches: [ main ]
 
 env:
   CARGO_TERM_COLOR: always
 
 jobs:
-  static_analysis:
-    name: Static Analysis
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-
-      - name: Run Rustfmt
-        uses: actions-rs/cargo@v1
-        with:
-          command: fmt
-          args: --all -- --check
-
   test:
-    name: Unit tests
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
       - name: Install dependencies
         run: sudo apt-get install -y cmake libzmq3-dev
-
       - name: Install latest nightly
         uses: actions-rs/toolchain@v1
         with:
           toolchain: nightly
           override: true
           components: rustfmt
-
       - name: Build & test
         uses: actions-rs/cargo@v1
         with:
@@ -46,38 +31,35 @@ jobs:
           CARGO_INCREMENTAL: '0'
           RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off'
           RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off'
-
       - id: coverage
         name: Generate coverage
-        uses: actions-rs/grcov@v0.1.6
-
+        uses: actions-rs/grcov@v0.1.5
       - name: Upload coverage to Codecov
-        uses: codecov/codecov-action@v2
+        uses: codecov/codecov-action@v1
         with:
           file: ${{ steps.coverage.outputs.report }}
           directory: ./coverage/reports/
-
+      - uses: actions-rs/cargo@v1
+        name: Lints
+        with:
+          command: fmt
+          args: --all -- --check
   functional-test:
-    name: Functional tests
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@v2
       - name: Install dependencies
         run: sudo apt-get install -y cmake libzmq3-dev docker-compose
-
       - name: Compose docker services
         run: cd tests && docker-compose up -d
-
       - name: Install latest nightly
         uses: actions-rs/toolchain@v1
         with:
           toolchain: nightly
           override: true
           components: rustfmt
-
       - name: Correct permissions
         run: sudo chown -R $USER tests
-
       - name: Build & test
         uses: actions-rs/cargo@v1
         with:
diff --git a/tests/README.md b/tests/README.md
index 9672cae4..e28555a3 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -4,7 +4,7 @@ By default the functional tests are not run when executing `cargo test`. To run
 them, run `cargo test -- --ignored`.
 
 Before running the functional tests, start the docker containers first with
-`docker-compose up`.
+`docker-compose up`. 
 
 Docker creates and mounts a volume in the `data_dir` directory. The owner of
 this directory is the root user, so ownership has to be changed before running
@@ -18,3 +18,4 @@ expecting the following endpoints:
 - Electrs at `tcp://localhost:50001`
 - Monerod at `http://localhost:18081` run with arguments `--regtest --offline --fixed-difficulty 1`
 - Two instances of Monero-wallet-rpc at `http://localhost:18083` and `htp://localhost:18084` run with arguments `--disable-rpc-login --wallet-dir wallets`
+
diff --git a/tests/functional.rs b/tests/functional.rs
index 66418515..bd4dcc64 100644
--- a/tests/functional.rs
+++ b/tests/functional.rs
@@ -6,6 +6,9 @@ use farcaster_node::syncerd::bitcoin_syncer::Synclet;
 use farcaster_node::syncerd::monero_syncer::MoneroSyncer;
 use farcaster_node::syncerd::opts::Coin;
 use farcaster_node::syncerd::runtime::SyncerdTask;
+use farcaster_node::syncerd::SweepAddress;
+use farcaster_node::syncerd::SweepAddressAddendum;
+use farcaster_node::syncerd::SweepXmrAddress;
 use farcaster_node::syncerd::SyncerServers;
 use farcaster_node::syncerd::XmrAddressAddendum;
 use farcaster_node::ServiceId;
@@ -25,11 +28,15 @@ use bitcoin::hashes::Hash;
 use internet2::{CreateUnmarshaller, Unmarshall};
 use std::str::FromStr;
 
+use farcaster_core::blockchain::Network;
 use farcaster_node::syncerd::types::{
     Abort, AddressAddendum, Boolean, BroadcastTransaction, BtcAddressAddendum, Event, Task,
     WatchAddress, WatchHeight, WatchTransaction,
 };
 
+const SOURCE1: ServiceId = ServiceId::Syncer(Coin::Bitcoin, Network::Local);
+const SOURCE2: ServiceId = ServiceId::Syncer(Coin::Monero, Network::Local);
+
 /*
 These tests need to run serialy, otherwise we cannot verify events based on the
 state of electrum and bitcoin
@@ -52,6 +59,7 @@ fn bitcoin_syncer_test() {
 #[tokio::test]
 #[ignore] // it's too expensive
 async fn monero_syncer_test() {
+    monero_syncer_sweep_test().await;
     monero_syncer_block_height_test().await;
     monero_syncer_address_test().await;
     monero_syncer_transaction_test().await;
@@ -85,7 +93,7 @@ fn bitcoin_syncer_block_height_test(polling: bool) {
             id: 0,
             lifetime: blocks + 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
 
@@ -111,7 +119,7 @@ fn bitcoin_syncer_block_height_test(polling: bool) {
             id: 1,
             lifetime: blocks + 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
     let message = rx_event.recv_multipart(0).unwrap();
@@ -192,7 +200,7 @@ fn bitcoin_syncer_address_test(polling: bool) {
             addendum: addendum_1,
             include_tx: Boolean::True,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(watch_address_task_1).unwrap();
     let watch_address_task_2 = SyncerdTask {
@@ -202,7 +210,7 @@ fn bitcoin_syncer_address_test(polling: bool) {
             addendum: addendum_2.clone(),
             include_tx: Boolean::True,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(watch_address_task_2).unwrap();
 
@@ -266,7 +274,7 @@ fn bitcoin_syncer_address_test(polling: bool) {
             addendum: addendum_2,
             include_tx: Boolean::True,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(watch_address_task_3).unwrap();
     println!("waiting for watch transaction message");
@@ -302,7 +310,7 @@ fn bitcoin_syncer_address_test(polling: bool) {
                 addendum: addendum_4.clone(),
                 include_tx: Boolean::True,
             }),
-            source: ServiceId::Syncer(Coin::Bitcoin),
+            source: SOURCE1.clone(),
         })
         .unwrap();
     }
@@ -359,7 +367,7 @@ fn bitcoin_syncer_transaction_test(polling: bool) {
             hash: txid_1.to_vec(),
             confirmation_bound: 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     })
     .unwrap();
 
@@ -367,21 +375,21 @@ fn bitcoin_syncer_transaction_test(polling: bool) {
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 0, vec![0]);
+    assert_transaction_confirmations(request, Some(0), vec![0]);
 
     let block_hash = bitcoin_rpc.generate_to_address(1, &address).unwrap();
     println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 1, block_hash[0].to_vec());
+    assert_transaction_confirmations(request, Some(1), block_hash[0].to_vec());
 
     bitcoin_rpc.generate_to_address(1, &address).unwrap();
     println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 2, block_hash[0].to_vec());
+    assert_transaction_confirmations(request, Some(2), block_hash[0].to_vec());
 
     let block_hash = bitcoin_rpc.generate_to_address(1, &address).unwrap();
     let block = bitcoin_rpc.get_block(&block_hash[0]).unwrap();
@@ -393,21 +401,21 @@ fn bitcoin_syncer_transaction_test(polling: bool) {
             hash: address_txid.to_vec(),
             confirmation_bound: 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     })
     .unwrap();
     println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 1, block_hash[0].to_vec());
+    assert_transaction_confirmations(request, Some(1), block_hash[0].to_vec());
 
     bitcoin_rpc.generate_to_address(1, &address).unwrap();
     println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 2, block_hash[0].to_vec());
+    assert_transaction_confirmations(request, Some(2), block_hash[0].to_vec());
 
     let txid_2 = bitcoin_rpc
         .send_to_address(&address, amount, None, None, None, None, None, None)
@@ -423,7 +431,7 @@ fn bitcoin_syncer_transaction_test(polling: bool) {
             hash: txid_2.to_vec(),
             confirmation_bound: 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     })
     .unwrap();
     tx.send(SyncerdTask {
@@ -433,7 +441,7 @@ fn bitcoin_syncer_transaction_test(polling: bool) {
             hash: txid_3.to_vec(),
             confirmation_bound: 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     })
     .unwrap();
 
@@ -441,12 +449,12 @@ fn bitcoin_syncer_transaction_test(polling: bool) {
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 0, vec![0]);
+    assert_transaction_confirmations(request, Some(0), vec![0]);
     println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 0, vec![0]);
+    assert_transaction_confirmations(request, Some(0), vec![0]);
 }
 
 fn bitcoin_syncer_abort_test() {
@@ -461,13 +469,13 @@ fn bitcoin_syncer_abort_test() {
             hash: vec![0; 32],
             confirmation_bound: 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
 
     let task = SyncerdTask {
         task: Task::Abort(Abort { id: 0 }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
     println!("waiting for task aborted message");
@@ -478,7 +486,7 @@ fn bitcoin_syncer_abort_test() {
 
     let task = SyncerdTask {
         task: Task::Abort(Abort { id: 0 }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
     println!("waiting for task aborted message");
@@ -526,7 +534,7 @@ fn bitcoin_syncer_broadcast_tx_test(polling: bool) {
             id: 0,
             tx: transaction.hex,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
 
@@ -571,7 +579,7 @@ fn bitcoin_syncer_broadcast_tx_test(polling: bool) {
             id: 0,
             tx: signed_tx.hex,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
 
@@ -598,7 +606,7 @@ fn create_bitcoin_syncer(polling: bool) -> (std::sync::mpsc::Sender<SyncerdTask>
     syncer.run(
         rx,
         tx_event,
-        ServiceId::Syncer(Coin::Bitcoin).into(),
+        SOURCE1.clone().into(),
         syncer_servers,
         Chain::Regtest(dummy),
         polling,
@@ -671,7 +679,7 @@ async fn monero_syncer_block_height_test() {
             id: 0,
             lifetime: blocks + 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
 
@@ -696,7 +704,7 @@ async fn monero_syncer_block_height_test() {
             id: 1,
             lifetime: u64::from(blocks) + 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(task).unwrap();
     println!("waiting for height changed");
@@ -719,6 +727,55 @@ async fn monero_syncer_block_height_test() {
     assert_received_height_changed(request, blocks);
 }
 
+async fn monero_syncer_sweep_test() {
+    let (regtest, wallet) = setup_monero().await;
+    let address = wallet.get_address(0, None).await.unwrap();
+    let blocks = regtest.generate_blocks(200, address.address).await.unwrap();
+
+    let duration = std::time::Duration::from_secs(20);
+    std::thread::sleep(duration);
+
+    let (tx, rx_event) = create_monero_syncer();
+
+    let spend_key = monero::PrivateKey::from_str(
+        "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404",
+    )
+    .unwrap();
+    let view_key = monero::PrivateKey::from_str(
+        "8163466f1883598e6dd14027b8da727057165da91485834314f5500a65846f09",
+    )
+    .unwrap();
+    let keypair = monero::KeyPair {
+        view: view_key,
+        spend: spend_key,
+    };
+    let to_be_sweeped_address = monero::Address::from_keypair(monero::Network::Mainnet, &keypair);
+    let dest_address = "43qHP7gSJJf8HZw1G3ZmpWVyYnbxkKdfta34Qj2nuRENjAsXBtj9JcMWcYMeT3n4NyTZqxhUkKgsTS6P2TNgM6ksM32czSp";
+    send_monero(&wallet, to_be_sweeped_address, 1000000000000).await;
+
+    let task = SyncerdTask {
+        task: Task::SweepAddress(SweepAddress {
+            id: 0,
+            lifetime: blocks + 40,
+            addendum: SweepAddressAddendum::Monero(SweepXmrAddress {
+                spend_key,
+                view_key,
+                address: dest_address.to_string(),
+            }),
+        }),
+        source: SOURCE2.clone(),
+    };
+    tx.send(task).unwrap();
+
+    regtest.generate_blocks(20, address.address).await.unwrap();
+
+    println!("waiting for sweep address message");
+    let message = rx_event.recv_multipart(0).unwrap();
+    println!("received sweep success message");
+    let request = get_request_from_message(message);
+    println!("request: {:?}", request);
+}
+
 /*
 We test for the following scenarios in the address transaction tests:
 
@@ -763,7 +820,7 @@ async fn monero_syncer_address_test() {
             addendum: addendum_1,
             include_tx: Boolean::True,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(watch_address_task_1).unwrap();
 
@@ -790,7 +847,7 @@ async fn monero_syncer_address_test() {
             addendum: addendum_2,
             include_tx: Boolean::True,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(watch_address_task_2).unwrap();
 
@@ -818,7 +875,7 @@ async fn monero_syncer_address_test() {
             addendum: addendum_3,
             include_tx: Boolean::True,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     };
     tx.send(watch_address_task_3).unwrap();
     println!("waiting for address transaction message");
@@ -849,7 +906,7 @@ async fn monero_syncer_address_test() {
                 addendum: addendum_4.clone(),
                 include_tx: Boolean::True,
             }),
-            source: ServiceId::Syncer(Coin::Monero),
+            source: SOURCE2.clone(),
         })
         .unwrap();
     }
@@ -894,7 +951,7 @@ async fn monero_syncer_transaction_test() {
             hash: txid_1.to_vec(),
             confirmation_bound: 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     })
     .unwrap();
 
@@ -902,7 +959,7 @@ async fn monero_syncer_transaction_test() {
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 0, vec![0]);
+    assert_transaction_confirmations(request, Some(0), vec![0]);
 
     let block_height = regtest.generate_blocks(1, address).await.unwrap();
     let block_hash = get_block_hash_from_height(&regtest, block_height).await;
@@ -910,14 +967,14 @@ async fn monero_syncer_transaction_test() {
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 1, block_hash.clone());
+    assert_transaction_confirmations(request, Some(1), block_hash.clone());
 
     regtest.generate_blocks(1, address).await.unwrap();
     println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 2, block_hash);
+    assert_transaction_confirmations(request, Some(2), block_hash);
 
     let tx_id2 = send_monero(&wallet, address, 1).await;
     let block_height = regtest.generate_blocks(1, address).await.unwrap();
@@ -929,21 +986,21 @@ async fn monero_syncer_transaction_test() {
             hash: tx_id2,
             confirmation_bound: 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     })
     .unwrap();
     println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 1, block_hash.clone());
+    assert_transaction_confirmations(request, Some(1), block_hash.clone());
 
     regtest.generate_blocks(1, address).await.unwrap();
     println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 2, block_hash);
+    assert_transaction_confirmations(request, Some(2), block_hash);
 
     let txid_2 = send_monero(&wallet, address, 1).await;
     let txid_3 = send_monero(&wallet, address, 1).await;
@@ -955,7 +1012,7 @@ async fn monero_syncer_transaction_test() {
             hash: txid_2.to_vec(),
             confirmation_bound: 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     })
     .unwrap();
     tx.send(SyncerdTask {
@@ -965,7 +1022,7 @@ async fn monero_syncer_transaction_test() {
             hash: txid_3.to_vec(),
             confirmation_bound: 2,
         }),
-        source: ServiceId::Syncer(Coin::Bitcoin),
+        source: SOURCE1.clone(),
     })
     .unwrap();
 
@@ -973,12 +1030,12 @@ async fn monero_syncer_transaction_test() {
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 0, vec![0]);
+    assert_transaction_confirmations(request, Some(0), vec![0]);
     println!("awaiting confirmations");
     let message = rx_event.recv_multipart(0).unwrap();
     println!("received confirmation");
     let request = get_request_from_message(message);
-    assert_transaction_confirmations(request, 0, vec![0]);
+    assert_transaction_confirmations(request, Some(0), vec![0]);
 }
 
 async fn monero_syncer_abort_test() {
@@ -994,24 +1051,28 @@ async fn monero_syncer_abort_test() {
             hash: vec![0; 32],
             confirmation_bound: 2,
         }),
-        source: ServiceId::Syncer(Coin::Monero),
+        source: SOURCE2.clone(),
     };
     tx.send(task).unwrap();
+    let message = rx_event.recv_multipart(0).unwrap();
+    let request = get_request_from_message(message);
+    assert_transaction_confirmations(request, None, vec![0]);
 
     let task = SyncerdTask {
         task: Task::Abort(Abort { id: 0 }),
-        source: ServiceId::Syncer(Coin::Monero),
+        source: SOURCE2.clone(),
     };
     tx.send(task).unwrap();
     println!("waiting for task aborted message");
     let message = rx_event.recv_multipart(0).unwrap();
     println!("task aborted");
     let request = get_request_from_message(message);
+    println!("message: {:?}", request);
     assert_task_aborted(request, None, 0);
 
     let task = SyncerdTask {
         task: Task::Abort(Abort { id: 0 }),
-        source: ServiceId::Syncer(Coin::Monero),
+        source: SOURCE2.clone(),
     };
     tx.send(task).unwrap();
     println!("waiting for task aborted message");
@@ -1037,7 +1098,7 @@ async fn monero_syncer_broadcast_tx_test() {
 
     let task = SyncerdTask {
         task: Task::BroadcastTransaction(BroadcastTransaction { id: 0, tx: vec![0] }),
-        source: ServiceId::Syncer(Coin::Monero),
+        source: SOURCE2.clone(),
     };
     tx.send(task).unwrap();
 
@@ -1085,7 +1146,7 @@ fn create_monero_syncer() -> (std::sync::mpsc::Sender<SyncerdTask>, zmq::Socket)
     syncer.run(
         rx,
         tx_event,
-        ServiceId::Syncer(Coin::Monero).into(),
+        SOURCE2.clone().into(),
         syncer_servers,
         Chain::Regtest(dummy),
         true,
@@ -1195,7 +1256,7 @@ fn assert_received_height_changed(request: Request, expected_height: u64) {
 
 fn assert_transaction_confirmations(
     request: Request,
-    expected_confirmations: u32,
+    expected_confirmations: Option<u32>,
     expected_block_hash: Vec<u8>,
 ) {
     match request {
@@ -1203,7 +1264,7 @@ fn assert_transaction_confirmations(
             Event::TransactionConfirmations(transaction_confirmations) => {
                 assert_eq!(
                     transaction_confirmations.confirmations,
-                    Some(expected_confirmations)
+                    expected_confirmations
                 );
                 assert_eq!(transaction_confirmations.block, expected_block_hash);
             }