Compare commits

..

3 Commits

Author SHA1 Message Date
Daniel Karzel 23c2e6d5ab
Remove bitcoin change address from discover and take script
3 years ago
Daniel Karzel ec019550dd
Revert "Add a mandatory `--change-address` parameter to `buy-xmr`"
3 years ago
Daniel Karzel 1821a4b623
Update default Monero node URLs and info
3 years ago

@ -54,7 +54,7 @@ jobs:
run: git push origin release/${{ github.event.inputs.version }} --force
- name: Create pull request
uses: thomaseizinger/create-pull-request@1.2.1
uses: thomaseizinger/create-pull-request@1.1.0
with:
GITHUB_TOKEN: ${{ secrets.BOTTY_GITHUB_TOKEN }}
head: release/${{ github.event.inputs.version }}

@ -9,18 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- An issue where the connection between ASB and CLI would get closed prematurely.
The CLI expects to be connected to the ASB throughout the entire swap and hence reconnects as soon as the connection is closed.
This resulted in a loop of connections being established but instantly closed again because the ASB deemed the connection to not be necessary.
See issue https://github.com/comit-network/xmr-btc-swap/issues/648.
## [0.8.1] - 2021-08-16
### Fixed
- An occasional error where users couldn't start a swap because of `InsufficientFunds` that were off by exactly 1 satoshi.
- An issue where the default Monero node connection string would not work, because the public nodes were moved to a different domain.
The default monerod nodes were updated to use the [melo tool nodes](https://melo.tools/nodes.html).
## [0.8.0] - 2021-07-09
## [0.8.0] - 2021-07-07
### Added
@ -32,19 +24,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Registration and discovery of ASBs using the [libp2p rendezvous protocol](https://github.com/libp2p/specs/blob/master/rendezvous/README.md).
ASBs can register with a rendezvous node upon startup and, once registered, can be automatically discovered by the CLI using the `list-sellers` command.
The rendezvous node address (`rendezvous_point`), as well as the ASB's external addresses (`external_addresses`) to be registered, is configured in the `network` section of the ASB config file.
A rendezvous node is provided at `/dnsaddr/rendezvous.coblox.tech/p2p/12D3KooWQUt9DkNZxEn2R5ymJzWj15MpG6mTW84kyd8vDaRZi46o` for testing purposes.
A rendezvous node is provided at `/dnsaddr/rendezvous.coblox.tech/p2p/12D3KooWQUt9DkNZxEn2R5ymJzWj15MpG6mTW84kyd8vDaRZi46o` which is used as default for discovery in the CLI.
Upon discovery using `list-sellers` CLI users are provided with quote and connection information for each ASB discovered through the rendezvous node.
- A mandatory `--change-address` parameter to the CLI's `buy-xmr` command.
The provided address is used to transfer Bitcoin in case of a refund and in case the user transfers more than the specified amount into the swap.
For more information see [#513](https://github.com/comit-network/xmr-btc-swap/issues/513).
### Fixed
- An issue where the ASB gives long price guarantees when setting up a swap.
Now, after sending a spot price the ASB will wait for one minute for the CLI's to trigger the execution setup, and three minutes to see the BTC lock transaction of the CLI in mempool after the swap started.
If the first timeout is triggered the execution setup will be aborted, if the second timeout is triggered the swap will be safely aborted.
- An issue where the default Monero node connection string would not work, because the public nodes were moved to a different domain.
The default monerod nodes were updated to use the [melo tool nodes](https://melo.tools/nodes.html).
### Changed
@ -188,8 +175,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed an issue where Alice would not verify if Bob's Bitcoin lock transaction is semantically correct, i.e. pays the agreed upon amount to an output owned by both of them.
Fixing this required a **breaking change** on the network layer and hence old versions are not compatible with this version.
[Unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.8.1...HEAD
[0.8.1]: https://github.com/comit-network/xmr-btc-swap/compare/0.8.0...0.8.1
[Unreleased]: https://github.com/comit-network/xmr-btc-swap/compare/0.8.0...HEAD
[0.8.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.7.0...0.8.0
[0.7.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.6.0...0.7.0
[0.6.0]: https://github.com/comit-network/xmr-btc-swap/compare/0.5.0...0.6.0

193
Cargo.lock generated

@ -78,9 +78,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.43"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28ae2b3dec75a406790005a200b1bd89785afc02517a00ca99ecfe093ee9e6cf"
checksum = "15af2628f6890fe2609a3b91bef4c83450512802e59489f9c1cb1fa5df064a61"
[[package]]
name = "arrayref"
@ -115,9 +115,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.51"
version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e"
checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722"
dependencies = [
"proc-macro2 1.0.27",
"quote 1.0.9",
@ -235,20 +235,11 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "base64-compat"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7"
dependencies = [
"byteorder",
]
[[package]]
name = "bdk"
version = "0.10.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f4da304c23a06c21807598a7fe3223566e84c76c6bba2cab2504370dd6f4938"
checksum = "05d7fee1aedf8935ba1e2c9aeee640d1b9754da1b64f30ad47e8b8e2b7904ec0"
dependencies = [
"async-trait",
"bdk-macros",
@ -261,13 +252,14 @@ dependencies = [
"serde",
"serde_json",
"sled",
"tokio",
]
[[package]]
name = "bdk-macros"
version = "0.5.0"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3f510015e946c5995cc169f7ed4c92ba032bbce795c0956ee0d98d82f7aff78"
checksum = "b45570b78250774145859a8f85bfdb6e310663fc82640d7e159a44b1386074a2"
dependencies = [
"proc-macro2 1.0.27",
"quote 1.0.9",
@ -339,7 +331,6 @@ version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6742ec672d3f12506f4ac5c0d853926ff1f94e675f60ffd3224039972bf663f1"
dependencies = [
"base64-compat",
"bech32",
"bitcoin_hashes",
"secp256k1",
@ -629,14 +620,13 @@ checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "comfy-table"
version = "4.1.1"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11e95a3e867422fd8d04049041f5671f94d53c32a9dcd82e2be268714942f3f3"
checksum = "3ac47a9f5b89127ada154ad504ab5f8eea37e3679ab63d52a694617d0f9e2018"
dependencies = [
"crossterm",
"strum",
"strum_macros",
"unicode-width",
]
[[package]]
@ -687,22 +677,6 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
[[package]]
name = "core-foundation"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "cpufeatures"
version = "0.1.4"
@ -1469,9 +1443,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hex-literal"
version = "0.3.3"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e4590e13640f19f249fe3e4eca5113bc4289f2497710378190e7f4bd96f45b"
checksum = "76505e26b6ca3bbdbbb360b68472abbb80998c5fa5dc43672eca34f28258e138"
[[package]]
name = "hmac"
@ -1579,9 +1553,9 @@ dependencies = [
[[package]]
name = "hyper"
version = "0.14.11"
version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b61cf2d1aebcf6e6352c97b81dc2244ca29194be1b276f5d8ad5c6330fffb11"
checksum = "07d6baa1b441335f3ce5098ac421fb6547c46dda735ca1bc6d0153c838f9dd83"
dependencies = [
"bytes 1.0.1",
"futures-channel",
@ -1608,7 +1582,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64"
dependencies = [
"futures-util",
"hyper 0.14.11",
"hyper 0.14.9",
"log 0.4.14",
"rustls 0.19.0",
"tokio",
@ -1689,6 +1663,15 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "input_buffer"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413"
dependencies = [
"bytes 1.0.1",
]
[[package]]
name = "instant"
version = "0.1.9"
@ -1786,9 +1769,9 @@ dependencies = [
[[package]]
name = "jsonrpc_client"
version = "0.7.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f2e6b724d546ba449f65c42d02f97d437742962fe4a5735733a5132b3453208"
checksum = "a85cf2c5ce158eabf30b2ac4f535463d7b09ce7905502e11238b7d6048ef7d02"
dependencies = [
"async-trait",
"jsonrpc_client_macro 0.3.0",
@ -1861,9 +1844,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.98"
version = "0.2.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
checksum = "ba4aede83fc3617411dc6993bc8c70919750c1c257c6ca6a502aed6e0e2394ae"
[[package]]
name = "libgit2-sys"
@ -1880,7 +1863,7 @@ dependencies = [
[[package]]
name = "libp2p"
version = "0.39.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"atomic",
"bytes 1.0.1",
@ -1908,7 +1891,7 @@ dependencies = [
[[package]]
name = "libp2p-core"
version = "0.29.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"asn1_der",
"bs58",
@ -1941,7 +1924,7 @@ dependencies = [
[[package]]
name = "libp2p-dns"
version = "0.29.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"futures",
"libp2p-core",
@ -1953,7 +1936,7 @@ dependencies = [
[[package]]
name = "libp2p-mplex"
version = "0.29.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"asynchronous-codec",
"bytes 1.0.1",
@ -1970,7 +1953,7 @@ dependencies = [
[[package]]
name = "libp2p-noise"
version = "0.32.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"bytes 1.0.1",
"curve25519-dalek",
@ -1991,7 +1974,7 @@ dependencies = [
[[package]]
name = "libp2p-ping"
version = "0.30.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"futures",
"libp2p-core",
@ -2005,7 +1988,7 @@ dependencies = [
[[package]]
name = "libp2p-rendezvous"
version = "0.1.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"asynchronous-codec",
"bimap",
@ -2027,7 +2010,7 @@ dependencies = [
[[package]]
name = "libp2p-request-response"
version = "0.12.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"async-trait",
"bytes 1.0.1",
@ -2046,7 +2029,7 @@ dependencies = [
[[package]]
name = "libp2p-swarm"
version = "0.30.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"either",
"futures",
@ -2061,7 +2044,7 @@ dependencies = [
[[package]]
name = "libp2p-swarm-derive"
version = "0.23.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"quote 1.0.9",
"syn 1.0.73",
@ -2070,7 +2053,7 @@ dependencies = [
[[package]]
name = "libp2p-tcp"
version = "0.29.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"futures",
"futures-timer",
@ -2086,7 +2069,7 @@ dependencies = [
[[package]]
name = "libp2p-websocket"
version = "0.30.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"either",
"futures",
@ -2103,7 +2086,7 @@ dependencies = [
[[package]]
name = "libp2p-yamux"
version = "0.33.0"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"futures",
"libp2p-core",
@ -2377,9 +2360,9 @@ dependencies = [
[[package]]
name = "monero-epee-bin-serde"
version = "1.0.1"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f8a3f7f7ef5bb1fd6c953be9187e48df8cc1a0ffc7d94f9fbabd4a23e37321e"
checksum = "13be5b525af150f294b98d4291b0ec01e5bc157db740de2822827c17561d3960"
dependencies = [
"byteorder",
"serde",
@ -2408,7 +2391,7 @@ dependencies = [
"curve25519-dalek",
"hex 0.4.3",
"hex-literal",
"jsonrpc_client 0.7.0",
"jsonrpc_client 0.6.0",
"monero",
"monero-epee-bin-serde",
"rand 0.7.3",
@ -2488,7 +2471,7 @@ checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
[[package]]
name = "multistream-select"
version = "0.10.3"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#4af8af780ba3b1c8344b3d19a146d9628ad5bdb2"
source = "git+https://github.com/comit-network/rust-libp2p?branch=rendezvous#74a39f45df6f2ea5e3e08a01fe765bd9b7128758"
dependencies = [
"bytes 1.0.1",
"futures",
@ -2641,12 +2624,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl-probe"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
[[package]]
name = "parking_lot"
version = "0.10.2"
@ -3323,7 +3300,7 @@ dependencies = [
"futures-util",
"http",
"http-body",
"hyper 0.14.11",
"hyper 0.14.9",
"hyper-rustls",
"ipnet",
"js-sys",
@ -3374,9 +3351,9 @@ dependencies = [
[[package]]
name = "rust_decimal"
version = "1.15.0"
version = "1.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5446d1cf2dfe2d6367c8b27f2082bdf011e60e76fa1fcd140047f535156d6e7"
checksum = "01127cb8617e5e21bcf2e19b5eb48317735ca677f1d0a94833c21c331c446582"
dependencies = [
"arrayvec",
"num-traits",
@ -3449,18 +3426,6 @@ dependencies = [
"webpki",
]
[[package]]
name = "rustls-native-certs"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092"
dependencies = [
"openssl-probe",
"rustls 0.19.0",
"schannel",
"security-framework",
]
[[package]]
name = "rustversion"
version = "1.0.4"
@ -3502,16 +3467,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
[[package]]
name = "schannel"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
"lazy_static",
"winapi 0.3.9",
]
[[package]]
name = "scoped-tls"
version = "1.0.0"
@ -3576,29 +3531,6 @@ dependencies = [
"subtle-ng",
]
[[package]]
name = "security-framework"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "0.9.0"
@ -3674,9 +3606,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.66"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "336b10da19a12ad094b59d870ebde26a45402e5b470add4b5fd03c5048a32127"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
@ -4106,7 +4038,7 @@ checksum = "8049cf85f0e715d6af38dde439cb0ccb91f67fb9f5f63c80f8b43e48356e1a3f"
[[package]]
name = "swap"
version = "0.8.1"
version = "0.8.0"
dependencies = [
"anyhow",
"async-compression",
@ -4131,7 +4063,7 @@ dependencies = [
"ed25519-dalek",
"futures",
"get-port",
"hyper 0.14.11",
"hyper 0.14.9",
"itertools 0.10.1",
"libp2p",
"miniscript",
@ -4460,9 +4392,9 @@ dependencies = [
[[package]]
name = "tokio-tungstenite"
version = "0.15.0"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "511de3f85caf1c98983545490c3d09685fa8eb634e57eec22bb4db271f46cbd8"
checksum = "1e96bb520beab540ab664bd5a9cfeaa1fcd846fa68c830b42e2c8963071251d2"
dependencies = [
"futures-util",
"log 0.4.14",
@ -4600,9 +4532,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.2.20"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9cbe87a2fa7e35900ce5de20220a582a9483a7063811defce79d7cbd59d4cfe"
checksum = "ab69019741fca4d98be3c62d2b75254528b5432233fd8a4d2739fec20278de48"
dependencies = [
"ansi_term 0.12.1",
"chrono",
@ -4678,24 +4610,25 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "tungstenite"
version = "0.14.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0b2d8558abd2e276b0a8df5c05a2ec762609344191e5fd23e292c910e9165b5"
checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093"
dependencies = [
"base64 0.13.0",
"byteorder",
"bytes 1.0.1",
"http",
"httparse",
"input_buffer",
"log 0.4.14",
"rand 0.8.3",
"rustls 0.19.0",
"rustls-native-certs",
"sha-1",
"thiserror",
"url 2.2.2",
"utf-8",
"webpki",
"webpki-roots 0.21.0",
]
[[package]]
@ -4867,9 +4800,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "vergen"
version = "5.1.13"
version = "5.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "542f37b4798c879409865dde7908e746d836f77839c3a6bea5c8b4e4dcf6620b"
checksum = "5f710158a7c9449f400cf734e97cb417a09a7b5f0cf54b133718b6b2951c9f79"
dependencies = [
"anyhow",
"cfg-if 1.0.0",

@ -15,7 +15,7 @@ You can read [this blogpost](https://comit.network/blog/2021/07/02/transaction-p
2. Find a seller to swap with:
```shell
./swap --testnet list-sellers
./swap --testnet --list-sellers
```
3. Swap with a seller:
@ -33,12 +33,6 @@ Swapping of course needs two parties - and the CLI is only one of them: The take
If you are interested in becoming a market maker you will want to run the second binary provided in this repository: `asb` - the Automated Swap Backend.
Detailed documentation for the `asb` can be found [in this README](./docs/asb/README.md).
## Safety
This software is using cryptography that has not been formally audited.
While we do our best to make it safe, it is up to the user to evaluate whether or not it is safe to use for their purposes.
Please also see section 15 and 16 of the [license](./LICENSE).
## Contact
Feel free to reach out to us in the [COMIT-Monero Matrix channel](https://matrix.to/#/#comit-monero:matrix.org).

@ -94,11 +94,13 @@ FLAGS:
-V, --version Prints version information
OPTIONS:
--rendezvous-point <rendezvous-point> Address of the rendezvous point you want to use to discover ASBs
--rendezvous-point <rendezvous-point> Address of the rendezvous point you want to use to discover ASBs [default: /dnsaddr/rendezvous.coblox.tech/p2p/12D3KooWQUt9DkNZxEn2R5ymJzWj15MpG6mTW84kyd8vDaRZi46o]
--tor-socks5-port <tor-socks5-port> Your local Tor socks5 proxy port [default: 9050]
```
Running `swap --testnet list-sellers --rendezvous-point /dnsaddr/rendezvous.coblox.tech/p2p/12D3KooWQUt9DkNZxEn2R5ymJzWj15MpG6mTW84kyd8vDaRZi46o` will give you something like:
This command only takes optional parameters and can be run as-is:
Running `swap --testnet list-sellers` will give you something like:
```
Connected to rendezvous point, discovering nodes in 'xmr-btc-swap-testnet' namespace ...

@ -1,21 +1,9 @@
#!/bin/bash
# This is a utility script to showcase how the swap CLI can discover sellers and then trigger a swap using the discovered sellers
#
# 1st param: Path to the "swap" binary (aka the swap CLI)
# 2nd param: Multiaddress of the rendezvous node to be used for discovery
# 3rd param: Your Monero stagenet address where the XMR will be received
# 4th param: Your bech32 Bitcoin testnet address that will be used for any change output (e.g. refund scenario or when swapping an amount smaller than the transferred BTC)
#
# Example usage:
# discover_and_take.sh "PATH/TO/swap" "/dnsaddr/rendezvous.coblox.tech/p2p/12D3KooWQUt9DkNZxEn2R5ymJzWj15MpG6mTW84kyd8vDaRZi46o" "YOUR_XMR_STAGENET_ADDRESS" "YOUR_BECH32_BITCOIN_TESTNET_ADDRESS"
CLI_PATH=$1
RENDEZVOUS_POINT=$2
YOUR_MONERO_ADDR=$3
YOUR_BITCOIN_ADDR=$4
YOUR_MONERO_ADDR=$2
CLI_LIST_SELLERS="$CLI_PATH --testnet --json --debug list-sellers --rendezvous-point $RENDEZVOUS_POINT"
CLI_LIST_SELLERS="$CLI_PATH --testnet --json --debug list-sellers"
echo "Requesting sellers with command: $CLI_LIST_SELLERS"
echo
@ -32,7 +20,7 @@ echo " max_quantity: $MAX sat"
echo
CLI_SWAP="$CLI_PATH --testnet --debug buy-xmr --receive-address $YOUR_MONERO_ADDR --change-address $YOUR_BITCOIN_ADDR --seller $ADDR"
CLI_SWAP="$CLI_PATH --testnet --debug buy-xmr --receive-address $YOUR_MONERO_ADDR --seller $ADDR"
echo "Starting swap with best seller using command $CLI_SWAP"
echo

@ -8,7 +8,7 @@ edition = "2018"
anyhow = "1"
curve25519-dalek = "3.1"
hex = "0.4"
jsonrpc_client = { version = "0.7", features = [ "reqwest" ] }
jsonrpc_client = { version = "0.6", features = [ "reqwest" ] }
monero = "0.12"
monero-epee-bin-serde = "1"
rand = "0.7"

@ -1,6 +1,6 @@
[package]
name = "swap"
version = "0.8.1"
version = "0.8.0"
authors = [ "The COMIT guys <hello@comit.network>" ]
edition = "2018"
description = "XMR/BTC trustless atomic swaps."
@ -15,11 +15,11 @@ async-trait = "0.1"
atty = "0.2"
backoff = { version = "0.3", features = [ "tokio" ] }
base64 = "0.13"
bdk = "0.10"
bdk = "0.8"
big-bytes = "1"
bitcoin = { version = "0.26", features = [ "rand", "use-serde" ] }
bmrng = "0.5"
comfy-table = "4.1.1"
comfy-table = "4.0.0"
config = { version = "0.11", default-features = false, features = [ "toml" ] }
conquer-once = "0.3"
curve25519-dalek = { package = "curve25519-dalek-ng", version = "4" }
@ -45,7 +45,7 @@ rust_decimal_macros = "1"
serde = { version = "1", features = [ "derive" ] }
serde_cbor = "0.11"
serde_json = "1"
serde_with = { version = "1", features = [ "macros" ] }
serde_with = { version = "1.9.4", features = [ "macros" ] }
sha2 = "0.9"
sigma_fun = { git = "https://github.com/LLFourn/secp256kfun", default-features = false, features = [ "ed25519", "serde" ] }
sled = "0.34"
@ -55,7 +55,7 @@ thiserror = "1"
time = "0.2"
tokio = { version = "1", features = [ "rt-multi-thread", "time", "macros", "sync", "process", "fs", "net" ] }
tokio-socks = "0.5"
tokio-tungstenite = { version = "0.15", features = [ "rustls-tls" ] }
tokio-tungstenite = { version = "0.14", features = [ "rustls-tls" ] }
tokio-util = { version = "0.6", features = [ "io" ] }
toml = "0.5"
torut = { version = "0.1", default-features = false, features = [ "v3", "control" ] }
@ -80,7 +80,6 @@ get-port = "3"
hyper = "0.14"
monero-harness = { path = "../monero-harness" }
port_check = "0.1"
proptest = "1"
serde_cbor = "0.11"
spectral = "0.6"
tempfile = "3"

@ -1,7 +0,0 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 849f8b01f49fc9a913100203698a9151d8de8a37564e1d3b1e3b4169e192f58a # shrinks to funding_amount = 290250686, num_utxos = 3, sats_per_vb = 75.35638, key = ExtendedPrivKey { network: Regtest, depth: 0, parent_fingerprint: 00000000, child_number: Normal { index: 0 }, private_key: [private key data], chain_code: 0b7a29ca6990bbc9b9187c1d1a07e2cf68e32f5ce55d2df01edf8a4ac2ee2a4b }, alice = Point<Normal,Public,NonZero>(0299a8c6a662e2e9e8ee7c6889b75a51c432812b4bf70c1d76eace63abc1bdfb1b), bob = Point<Normal,Public,NonZero>(027165b1f9924030c90d38c511da0f4397766078687997ed34d6ef2743d2a7bbed)

@ -1,5 +1,6 @@
use crate::env::{Mainnet, Testnet};
use crate::fs::{ensure_directory_exists, system_config_dir, system_data_dir};
use crate::network::rendezvous::DEFAULT_RENDEZVOUS_ADDRESS;
use crate::tor::{DEFAULT_CONTROL_PORT, DEFAULT_SOCKS5_PORT};
use anyhow::{bail, Context, Result};
use config::ConfigError;
@ -248,7 +249,7 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
.map(|str| str.parse())
.collect::<Result<Vec<Multiaddr>, _>>()?;
let electrum_rpc_url = Input::with_theme(&ColorfulTheme::default())
let electrum_rpc_url: Url = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Enter Electrum RPC URL or hit return to use default")
.default(defaults.electrum_rpc_url)
.interact_text()?;
@ -289,22 +290,24 @@ pub fn query_user_for_initial_config(testnet: bool) -> Result<Config> {
}
let ask_spread = Decimal::from_f64(ask_spread).context("Unable to parse spread")?;
let rendezvous_point = Input::<Multiaddr>::with_theme(&ColorfulTheme::default())
let rendezvous_address = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Do you want to advertise your ASB instance with a rendezvous node? Enter an empty string if not.")
.allow_empty(true)
.default(DEFAULT_RENDEZVOUS_ADDRESS.to_string())
.interact_text()?;
let rendezvous_point = if rendezvous_address.is_empty() {
None
} else {
Some(Multiaddr::from_str(&rendezvous_address)?)
};
println!();
Ok(Config {
data: Data { dir: data_dir },
network: Network {
listen: listen_addresses,
rendezvous_point: if rendezvous_point.is_empty() {
None
} else {
Some(rendezvous_point)
},
rendezvous_point,
external_addresses: vec![],
},
bitcoin: Bitcoin {
@ -355,7 +358,7 @@ mod tests {
},
network: Network {
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
rendezvous_point: None,
rendezvous_point: Some(DEFAULT_RENDEZVOUS_ADDRESS.parse().unwrap()),
external_addresses: vec![],
},
@ -398,7 +401,7 @@ mod tests {
},
network: Network {
listen: vec![defaults.listen_address_tcp, defaults.listen_address_ws],
rendezvous_point: None,
rendezvous_point: Some(DEFAULT_RENDEZVOUS_ADDRESS.parse().unwrap()),
external_addresses: vec![],
},

@ -13,7 +13,7 @@ use libp2p::core::connection::ConnectionId;
use libp2p::core::muxing::StreamMuxerBox;
use libp2p::core::transport::Boxed;
use libp2p::dns::TokioDnsConfig;
use libp2p::ping::{Ping, PingConfig, PingEvent};
use libp2p::ping::{Ping, PingEvent};
use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::swarm::{
DialPeerCondition, IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction,
@ -152,7 +152,7 @@ pub mod behaviour {
),
transfer_proof: transfer_proof::alice(),
encrypted_signature: encrypted_signature::alice(),
ping: Ping::new(PingConfig::new().with_keep_alive(true)),
ping: Ping::default(),
}
}
}

@ -225,9 +225,7 @@ async fn main() -> Result<()> {
}
};
let psbt = bitcoin_wallet
.send_to_address(address, amount, None)
.await?;
let psbt = bitcoin_wallet.send_to_address(address, amount).await?;
let signed_tx = bitcoin_wallet.sign_and_finalize(psbt).await?;
bitcoin_wallet.broadcast(signed_tx, "withdraw").await?;

@ -58,7 +58,6 @@ async fn main() -> Result<()> {
seller,
bitcoin_electrum_rpc_url,
bitcoin_target_block,
bitcoin_change_address,
monero_receive_address,
monero_daemon_address,
tor_socks5_port,
@ -124,7 +123,6 @@ async fn main() -> Result<()> {
env_config,
event_loop_handle,
monero_receive_address,
bitcoin_change_address,
amount,
);
@ -285,7 +283,7 @@ async fn main() -> Result<()> {
.context("Failed to read in seed file")?;
let identity = seed.derive_libp2p_identity();
let sellers = list_sellers(
let mut sellers = list_sellers(
rendezvous_node_peer_id,
rendezvous_point,
namespace,
@ -293,6 +291,7 @@ async fn main() -> Result<()> {
identity,
)
.await?;
sellers.sort();
if json {
for seller in sellers {
@ -422,7 +421,7 @@ where
price = %bid_quote.price,
minimum_amount = %bid_quote.min_quantity,
maximum_amount = %bid_quote.max_quantity,
"Received quote",
"Received quote: 1 XMR ~ ",
);
let mut max_giveable = max_giveable_fn().await?;
@ -445,26 +444,28 @@ where
"Waiting for Bitcoin deposit",
);
max_giveable = loop {
sync().await?;
let new_max_givable = max_giveable_fn().await?;
sync().await?;
if new_max_givable > max_giveable {
break new_max_givable;
}
let new_max_givable = max_giveable_fn().await?;
tokio::time::sleep(Duration::from_secs(1)).await;
};
if new_max_givable != max_giveable {
max_giveable = new_max_givable;
let new_balance = balance().await?;
tracing::info!(%new_balance, %max_giveable, "Received Bitcoin");
let new_balance = balance().await?;
tracing::info!(
%new_balance,
%max_giveable,
"Received Bitcoin",
);
if max_giveable < bid_quote.min_quantity {
tracing::info!("Deposited amount is less than `min_quantity`");
continue;
if max_giveable >= bid_quote.min_quantity {
break;
} else {
tracing::info!("Deposited amount is less than `min_quantity`",);
}
}
break;
tokio::time::sleep(Duration::from_secs(1)).await;
}
};
@ -480,16 +481,40 @@ where
#[cfg(test)]
mod tests {
use super::*;
use crate::determine_btc_to_swap;
use ::bitcoin::Amount;
use std::sync::Mutex;
use swap::tracing_ext::capture_logs;
use tracing::level_filters::LevelFilter;
use ::bitcoin::Amount;
use tracing::subscriber;
use crate::determine_btc_to_swap;
use super::*;
struct MaxGiveable {
amounts: Vec<Amount>,
call_counter: usize,
}
impl MaxGiveable {
fn new(amounts: Vec<Amount>) -> Self {
Self {
amounts,
call_counter: 0,
}
}
fn give(&mut self) -> Result<Amount> {
let amount = self
.amounts
.get(self.call_counter)
.ok_or_else(|| anyhow::anyhow!("No more balances available"))?;
self.call_counter += 1;
Ok(*amount)
}
}
#[tokio::test]
async fn given_no_balance_and_transfers_less_than_max_swaps_max_giveable() {
let writer = capture_logs(LevelFilter::INFO);
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
Amount::ZERO,
Amount::from_btc(0.0009).unwrap(),
@ -512,19 +537,11 @@ mod tests {
let expected_amount = Amount::from_btc(0.0009).unwrap();
let expected_fees = Amount::from_btc(0.0001).unwrap();
assert_eq!((amount, fees), (expected_amount, expected_fees));
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
INFO swap: Received Bitcoin new_balance=0.00100000 BTC max_giveable=0.00090000 BTC
"
);
assert_eq!((amount, fees), (expected_amount, expected_fees))
}
#[tokio::test]
async fn given_no_balance_and_transfers_more_then_swaps_max_quantity_from_quote() {
let writer = capture_logs(LevelFilter::INFO);
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
Amount::ZERO,
Amount::from_btc(0.1).unwrap(),
@ -547,19 +564,12 @@ mod tests {
let expected_amount = Amount::from_btc(0.01).unwrap();
let expected_fees = Amount::from_btc(0.0001).unwrap();
assert_eq!((amount, fees), (expected_amount, expected_fees));
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
INFO swap: Received Bitcoin new_balance=0.10010000 BTC max_giveable=0.10000000 BTC
"
);
assert_eq!((amount, fees), (expected_amount, expected_fees))
}
#[tokio::test]
async fn given_initial_balance_below_max_quantity_swaps_max_givable() {
let writer = capture_logs(LevelFilter::INFO);
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
Amount::from_btc(0.0049).unwrap(),
Amount::from_btc(99.9).unwrap(),
@ -582,17 +592,12 @@ mod tests {
let expected_amount = Amount::from_btc(0.0049).unwrap();
let expected_fees = Amount::from_btc(0.0001).unwrap();
assert_eq!((amount, fees), (expected_amount, expected_fees));
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
"
);
assert_eq!((amount, fees), (expected_amount, expected_fees))
}
#[tokio::test]
async fn given_initial_balance_above_max_quantity_swaps_max_quantity() {
let writer = capture_logs(LevelFilter::INFO);
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
Amount::from_btc(0.1).unwrap(),
Amount::from_btc(99.9).unwrap(),
@ -615,17 +620,12 @@ mod tests {
let expected_amount = Amount::from_btc(0.01).unwrap();
let expected_fees = Amount::from_btc(0.0001).unwrap();
assert_eq!((amount, fees), (expected_amount, expected_fees));
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.00000000 BTC maximum_amount=0.01000000 BTC
"
);
assert_eq!((amount, fees), (expected_amount, expected_fees))
}
#[tokio::test]
async fn given_no_initial_balance_then_min_wait_for_sufficient_deposit() {
let writer = capture_logs(LevelFilter::INFO);
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
Amount::ZERO,
Amount::from_btc(0.01).unwrap(),
@ -648,19 +648,12 @@ mod tests {
let expected_amount = Amount::from_btc(0.01).unwrap();
let expected_fees = Amount::from_btc(0.0001).unwrap();
assert_eq!((amount, fees), (expected_amount, expected_fees));
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC
"
);
assert_eq!((amount, fees), (expected_amount, expected_fees))
}
#[tokio::test]
async fn given_balance_less_then_min_wait_for_sufficient_deposit() {
let writer = capture_logs(LevelFilter::INFO);
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
Amount::from_btc(0.0001).unwrap(),
Amount::from_btc(0.01).unwrap(),
@ -683,19 +676,12 @@ mod tests {
let expected_amount = Amount::from_btc(0.01).unwrap();
let expected_fees = Amount::from_btc(0.0001).unwrap();
assert_eq!((amount, fees), (expected_amount, expected_fees));
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00010000 BTC minimum_amount=0.01000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC
"
);
assert_eq!((amount, fees), (expected_amount, expected_fees))
}
#[tokio::test]
async fn given_no_initial_balance_and_transfers_less_than_min_keep_waiting() {
let writer = capture_logs(LevelFilter::INFO);
let _guard = subscriber::set_default(tracing_subscriber::fmt().with_test_writer().finish());
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
Amount::ZERO,
Amount::from_btc(0.01).unwrap(),
@ -721,82 +707,7 @@ mod tests {
.await
.unwrap_err();
assert!(matches!(error, tokio::time::error::Elapsed { .. }));
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Received Bitcoin new_balance=0.01010000 BTC max_giveable=0.01000000 BTC
INFO swap: Deposited amount is less than `min_quantity`
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.01000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
"
);
}
#[tokio::test]
async fn given_longer_delay_until_deposit_should_not_spam_user() {
let writer = capture_logs(LevelFilter::INFO);
let givable = Arc::new(Mutex::new(MaxGiveable::new(vec![
Amount::ZERO,
Amount::ZERO,
Amount::ZERO,
Amount::ZERO,
Amount::ZERO,
Amount::ZERO,
Amount::ZERO,
Amount::ZERO,
Amount::ZERO,
Amount::from_btc(0.2).unwrap(),
])));
tokio::time::timeout(
Duration::from_secs(10),
determine_btc_to_swap(
true,
async { Ok(quote_with_min(0.1)) },
get_dummy_address(),
|| async { Ok(Amount::from_btc(0.21)?) },
|| async {
let mut result = givable.lock().unwrap();
result.give()
},
|| async { Ok(()) },
),
)
.await
.unwrap()
.unwrap();
assert_eq!(
writer.captured(),
r" INFO swap: Received quote price=0.00100000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Waiting for Bitcoin deposit deposit_address=1PdfytjS7C8wwd9Lq5o4x9aXA2YRqaCpH6 max_giveable=0.00000000 BTC minimum_amount=0.10000000 BTC maximum_amount=184467440737.09551615 BTC
INFO swap: Received Bitcoin new_balance=0.21000000 BTC max_giveable=0.20000000 BTC
"
);
}
struct MaxGiveable {
amounts: Vec<Amount>,
call_counter: usize,
}
impl MaxGiveable {
fn new(amounts: Vec<Amount>) -> Self {
Self {
amounts,
call_counter: 0,
}
}
fn give(&mut self) -> Result<Amount> {
let amount = self
.amounts
.get(self.call_counter)
.ok_or_else(|| anyhow::anyhow!("No more balances available"))?;
self.call_counter += 1;
Ok(*amount)
}
assert!(matches!(error, tokio::time::error::Elapsed { .. }))
}
fn quote_with_max(btc: f64) -> BidQuote {

@ -21,9 +21,6 @@ pub use ecdsa_fun::fun::Scalar;
pub use ecdsa_fun::Signature;
pub use wallet::Wallet;
#[cfg(test)]
pub use wallet::WalletBuilder;
use crate::bitcoin::wallet::ScriptStatus;
use ::bitcoin::hashes::hex::ToHex;
use ::bitcoin::hashes::Hash;
@ -320,8 +317,8 @@ mod tests {
#[tokio::test]
async fn calculate_transaction_weights() {
let alice_wallet = WalletBuilder::new(Amount::ONE_BTC.as_sat()).build();
let bob_wallet = WalletBuilder::new(Amount::ONE_BTC.as_sat()).build();
let alice_wallet = Wallet::new_funded_default_fees(Amount::ONE_BTC.as_sat());
let bob_wallet = Wallet::new_funded_default_fees(Amount::ONE_BTC.as_sat());
let spending_fee = Amount::from_sat(1_000);
let btc_amount = Amount::from_sat(500_000);
let xmr_amount = crate::monero::Amount::from_piconero(10000);

@ -7,11 +7,11 @@ use ::bitcoin::{OutPoint, TxIn, TxOut, Txid};
use anyhow::{bail, Result};
use bdk::database::BatchDatabase;
use bitcoin::Script;
use ecdsa_fun::fun::Point;
use miniscript::{Descriptor, DescriptorTrait};
use rand::thread_rng;
use serde::{Deserialize, Serialize};
const SCRIPT_SIZE: usize = 34;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct TxLock {
inner: PartiallySignedTransaction,
@ -24,7 +24,6 @@ impl TxLock {
amount: Amount,
A: PublicKey,
B: PublicKey,
change: bitcoin::Address,
) -> Result<Self>
where
C: EstimateFeeRate,
@ -35,9 +34,7 @@ impl TxLock {
.address(wallet.get_network())
.expect("can derive address from descriptor");
let psbt = wallet
.send_to_address(address, amount, Some(change))
.await?;
let psbt = wallet.send_to_address(address, amount).await?;
Ok(Self {
inner: psbt,
@ -112,7 +109,12 @@ impl TxLock {
/// Calculate the size of the script used by this transaction.
pub fn script_size() -> usize {
SCRIPT_SIZE
build_shared_output_descriptor(
Point::random(&mut thread_rng()),
Point::random(&mut thread_rng()),
)
.script_pubkey()
.len()
}
pub fn script_pubkey(&self) -> Script {
@ -183,12 +185,11 @@ impl Watchable for TxLock {
mod tests {
use super::*;
use crate::bitcoin::wallet::StaticFeeRate;
use crate::bitcoin::WalletBuilder;
#[tokio::test]
async fn given_bob_sends_good_psbt_when_reconstructing_then_succeeeds() {
let (A, B) = alice_and_bob();
let wallet = WalletBuilder::new(50_000).build();
let wallet = Wallet::new_funded_default_fees(50000);
let agreed_amount = Amount::from_sat(10000);
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await;
@ -202,7 +203,7 @@ mod tests {
let (A, B) = alice_and_bob();
let fees = 610;
let agreed_amount = Amount::from_sat(10000);
let wallet = WalletBuilder::new(agreed_amount.as_sat() + fees).build();
let wallet = Wallet::new_funded_default_fees(agreed_amount.as_sat() + fees);
let psbt = bob_make_psbt(A, B, &wallet, agreed_amount).await;
assert_eq!(
@ -218,7 +219,7 @@ mod tests {
#[tokio::test]
async fn given_bob_is_sending_less_than_agreed_when_reconstructing_txlock_then_fails() {
let (A, B) = alice_and_bob();
let wallet = WalletBuilder::new(50_000).build();
let wallet = Wallet::new_funded_default_fees(50000);
let agreed_amount = Amount::from_sat(10000);
let bad_amount = Amount::from_sat(5000);
@ -231,7 +232,7 @@ mod tests {
#[tokio::test]
async fn given_bob_is_sending_to_a_bad_output_reconstructing_txlock_then_fails() {
let (A, B) = alice_and_bob();
let wallet = WalletBuilder::new(50_000).build();
let wallet = Wallet::new_funded_default_fees(50000);
let agreed_amount = Amount::from_sat(10000);
let E = eve();
@ -241,17 +242,6 @@ mod tests {
result.expect_err("PSBT to be invalid");
}
proptest::proptest! {
#[test]
fn estimated_tx_lock_script_size_never_changes(a in crate::proptest::ecdsa_fun::point(), b in crate::proptest::ecdsa_fun::point()) {
proptest::prop_assume!(a != b);
let computed_size = build_shared_output_descriptor(a, b).script_pubkey().len();
assert_eq!(computed_size, SCRIPT_SIZE);
}
}
/// Helper function that represents Bob's action of constructing the PSBT.
///
/// Extracting this allows us to keep the tests concise.
@ -261,11 +251,7 @@ mod tests {
wallet: &Wallet<(), bdk::database::MemoryDatabase, StaticFeeRate>,
amount: Amount,
) -> PartiallySignedTransaction {
let change = wallet.new_address().await.unwrap();
TxLock::new(&wallet, amount, A, B, change)
.await
.unwrap()
.into()
TxLock::new(&wallet, amount, A, B).await.unwrap().into()
}
fn alice_and_bob() -> (PublicKey, PublicKey) {

@ -149,7 +149,16 @@ impl Wallet {
}
};
last_status = Some(print_status_change(txid, last_status, new_status));
match (last_status, new_status) {
(None, new_status) => {
tracing::debug!(%txid, status = %new_status, "Found relevant Bitcoin transaction");
},
(Some(old_status), new_status) => {
tracing::debug!(%txid, %new_status, %old_status, "Bitcoin transaction status changed");
}
}
last_status = Some(new_status);
let all_receivers_gone = sender.send(new_status).is_err();
@ -173,20 +182,6 @@ impl Wallet {
}
}
fn print_status_change(txid: Txid, old: Option<ScriptStatus>, new: ScriptStatus) -> ScriptStatus {
match (old, new) {
(None, new_status) => {
tracing::debug!(%txid, status = %new_status, "Found relevant Bitcoin transaction");
}
(Some(old_status), new_status) if old_status != new_status => {
tracing::debug!(%txid, %new_status, %old_status, "Bitcoin transaction status changed");
}
_ => {}
}
new
}
/// Represents a subscription to the status of a given transaction.
#[derive(Debug, Clone)]
pub struct Subscription {
@ -306,8 +301,7 @@ where
.iter()
.find(|tx| tx.txid == txid)
.context("Could not find tx in bdk wallet when trying to determine fees")?
.fee
.expect("fees are always present with Electrum backend");
.fees;
Ok(Amount::from_sat(fees))
}
@ -320,18 +314,11 @@ where
&self,
address: Address,
amount: Amount,
change_override: Option<Address>,
) -> Result<PartiallySignedTransaction> {
if self.network != address.network {
bail!("Cannot build PSBT because network of given address is {} but wallet is on network {}", address.network, self.network);
}
if let Some(change) = change_override.as_ref() {
if self.network != change.network {
bail!("Cannot build PSBT because network of given address is {} but wallet is on network {}", change.network, self.network);
}
}
let wallet = self.wallet.lock().await;
let client = self.client.lock().await;
let fee_rate = client.estimate_feerate(self.target_block)?;
@ -358,17 +345,6 @@ where
_ => bail!("Unexpected transaction layout"),
}
if let ([_, change], [_, psbt_output], Some(change_override)) = (
&mut psbt.global.unsigned_tx.output.as_mut_slice(),
&mut psbt.outputs.as_mut_slice(),
change_override,
) {
change.script_pubkey = change_override.script_pubkey();
// Might be populated based on the previously set change address, but for the
// overwrite we don't know unless we ask the user for more information.
psbt_output.bip32_derivation.clear();
}
Ok(psbt)
}
@ -395,16 +371,14 @@ where
let mut tx_builder = wallet.build_tx();
let dummy_script = Script::from(vec![0u8; locking_script_size]);
tx_builder.drain_to(dummy_script);
tx_builder.set_single_recipient(dummy_script);
tx_builder.drain_wallet();
tx_builder.fee_rate(fee_rate);
let response = tx_builder.finish();
match response {
Ok((_, details)) => {
let max_giveable = details.sent
- details
.fee
.expect("fees are always present with Electrum backend");
let max_giveable = details.sent - details.fees;
Ok(Amount::from_sat(max_giveable))
}
Err(bdk::Error::InsufficientFunds { .. }) => Ok(Amount::ZERO),
@ -550,84 +524,48 @@ impl EstimateFeeRate for StaticFeeRate {
}
#[cfg(test)]
pub struct WalletBuilder {
utxo_amount: u64,
sats_per_vb: f32,
min_relay_fee_sats: u64,
key: bitcoin::util::bip32::ExtendedPrivKey,
num_utxos: u8,
}
#[cfg(test)]
impl WalletBuilder {
impl Wallet<(), bdk::database::MemoryDatabase, StaticFeeRate> {
/// Creates a new, funded wallet with sane default fees.
///
/// Unless you are testing things related to fees, this is likely what you
/// want.
pub fn new(amount: u64) -> Self {
WalletBuilder {
utxo_amount: amount,
sats_per_vb: 1.0,
min_relay_fee_sats: 1000,
key: "tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m".parse().unwrap(),
num_utxos: 1,
}
}
pub fn with_zero_fees(self) -> Self {
Self {
sats_per_vb: 0.0,
min_relay_fee_sats: 0,
..self
}
}
pub fn with_fees(self, sats_per_vb: f32, min_relay_fee_sats: u64) -> Self {
Self {
sats_per_vb,
min_relay_fee_sats,
..self
}
}
pub fn with_key(self, key: bitcoin::util::bip32::ExtendedPrivKey) -> Self {
Self { key, ..self }
pub fn new_funded_default_fees(amount: u64) -> Self {
Self::new_funded(amount, 1.0, 1000)
}
pub fn with_num_utxos(self, number: u8) -> Self {
Self {
num_utxos: number,
..self
}
/// Creates a new, funded wallet that doesn't pay any fees.
///
/// This will create invalid transactions but can be useful if you want full
/// control over the output amounts.
pub fn new_funded_zero_fees(amount: u64) -> Self {
Self::new_funded(amount, 0.0, 0)
}
pub fn build(self) -> Wallet<(), bdk::database::MemoryDatabase, StaticFeeRate> {
/// Creates a new, funded wallet to be used within tests.
pub fn new_funded(amount: u64, sats_per_vb: f32, min_relay_fee_sats: u64) -> Self {
use bdk::database::MemoryDatabase;
use bdk::{ConfirmationTime, LocalUtxo, TransactionDetails};
use bdk::{LocalUtxo, TransactionDetails};
use bitcoin::OutPoint;
use testutils::testutils;
let descriptors = testutils!(@descriptors (&format!("wpkh({}/*)", self.key)));
let descriptors = testutils!(@descriptors ("wpkh(tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m/*)"));
let mut database = MemoryDatabase::new();
for index in 0..self.num_utxos {
bdk::populate_test_db!(
&mut database,
testutils! {
@tx ( (@external descriptors, index as u32) => self.utxo_amount ) (@confirmations 1)
},
Some(100)
);
}
bdk::populate_test_db!(
&mut database,
testutils! {
@tx ( (@external descriptors, 0) => amount ) (@confirmations 1)
},
Some(100)
);
let wallet =
bdk::Wallet::new_offline(&descriptors.0, None, Network::Regtest, database).unwrap();
Wallet {
Self {
client: Arc::new(Mutex::new(StaticFeeRate {
fee_rate: FeeRate::from_sat_per_vb(self.sats_per_vb),
min_relay_fee: bitcoin::Amount::from_sat(self.min_relay_fee_sats),
fee_rate: FeeRate::from_sat_per_vb(sats_per_vb),
min_relay_fee: bitcoin::Amount::from_sat(min_relay_fee_sats),
})),
wallet: Arc::new(Mutex::new(wallet)),
finality_confirmations: 1,
@ -894,9 +832,7 @@ impl fmt::Display for ScriptStatus {
mod tests {
use super::*;
use crate::bitcoin::{PublicKey, TxLock};
use crate::tracing_ext::capture_logs;
use proptest::prelude::*;
use tracing::level_filters::LevelFilter;
#[test]
fn given_depth_0_should_meet_confirmation_target_one() {
@ -1086,7 +1022,7 @@ mod tests {
#[tokio::test]
async fn given_no_balance_returns_amount_0() {
let wallet = WalletBuilder::new(0).with_fees(1.0, 1).build();
let wallet = Wallet::new_funded(0, 1.0, 1);
let amount = wallet.max_giveable(TxLock::script_size()).await.unwrap();
assert_eq!(amount, Amount::ZERO);
@ -1094,7 +1030,7 @@ mod tests {
#[tokio::test]
async fn given_balance_below_min_relay_fee_returns_amount_0() {
let wallet = WalletBuilder::new(1000).with_fees(1.0, 1001).build();
let wallet = Wallet::new_funded(1000, 1.0, 1001);
let amount = wallet.max_giveable(TxLock::script_size()).await.unwrap();
assert_eq!(amount, Amount::ZERO);
@ -1102,7 +1038,7 @@ mod tests {
#[tokio::test]
async fn given_balance_above_relay_fee_returns_amount_greater_0() {
let wallet = WalletBuilder::new(10_000).build();
let wallet = Wallet::new_funded_default_fees(10_000);
let amount = wallet.max_giveable(TxLock::script_size()).await.unwrap();
assert!(amount.as_sat() > 0);
@ -1122,14 +1058,13 @@ mod tests {
let balance = 2000;
// We don't care about fees in this test, thus use a zero fee rate
let wallet = WalletBuilder::new(balance).with_zero_fees().build();
let wallet = Wallet::new_funded_zero_fees(balance);
// sorting is only relevant for amounts that have a change output
// if the change output is below dust it will be dropped by the BDK
for amount in above_dust..(balance - (above_dust - 1)) {
let (A, B) = (PublicKey::random(), PublicKey::random());
let change = wallet.new_address().await.unwrap();
let txlock = TxLock::new(&wallet, bitcoin::Amount::from_sat(amount), A, B, change)
let txlock = TxLock::new(&wallet, bitcoin::Amount::from_sat(amount), A, B)
.await
.unwrap();
let txlock_output = txlock.script_pubkey();
@ -1144,81 +1079,4 @@ mod tests {
);
}
}
#[tokio::test]
async fn can_override_change_address() {
let wallet = WalletBuilder::new(50_000).build();
let custom_change = "bcrt1q08pfqpsyrt7acllzyjm8q5qsz5capvyahm49rw"
.parse::<Address>()
.unwrap();
let psbt = wallet
.send_to_address(
wallet.new_address().await.unwrap(),
Amount::from_sat(10_000),
Some(custom_change.clone()),
)
.await
.unwrap();
let transaction = wallet.sign_and_finalize(psbt).await.unwrap();
match transaction.output.as_slice() {
[first, change] => {
assert_eq!(first.value, 10_000);
assert_eq!(change.script_pubkey, custom_change.script_pubkey());
}
_ => panic!("expected exactly two outputs"),
}
}
#[test]
fn printing_status_change_doesnt_spam_on_same_status() {
let writer = capture_logs(LevelFilter::DEBUG);
let tx = Txid::default();
let mut old = None;
old = Some(print_status_change(tx, old, ScriptStatus::Unseen));
old = Some(print_status_change(tx, old, ScriptStatus::InMempool));
old = Some(print_status_change(tx, old, ScriptStatus::InMempool));
old = Some(print_status_change(tx, old, ScriptStatus::InMempool));
old = Some(print_status_change(tx, old, ScriptStatus::InMempool));
old = Some(print_status_change(tx, old, ScriptStatus::InMempool));
old = Some(print_status_change(tx, old, ScriptStatus::InMempool));
old = Some(print_status_change(tx, old, confs(1)));
old = Some(print_status_change(tx, old, confs(2)));
old = Some(print_status_change(tx, old, confs(3)));
old = Some(print_status_change(tx, old, confs(3)));
print_status_change(tx, old, confs(3));
assert_eq!(
writer.captured(),
r"DEBUG swap::bitcoin::wallet: Found relevant Bitcoin transaction txid=0000000000000000000000000000000000000000000000000000000000000000 status=unseen
DEBUG swap::bitcoin::wallet: Bitcoin transaction status changed txid=0000000000000000000000000000000000000000000000000000000000000000 new_status=in mempool old_status=unseen
DEBUG swap::bitcoin::wallet: Bitcoin transaction status changed txid=0000000000000000000000000000000000000000000000000000000000000000 new_status=confirmed with 1 blocks old_status=in mempool
DEBUG swap::bitcoin::wallet: Bitcoin transaction status changed txid=0000000000000000000000000000000000000000000000000000000000000000 new_status=confirmed with 2 blocks old_status=confirmed with 1 blocks
DEBUG swap::bitcoin::wallet: Bitcoin transaction status changed txid=0000000000000000000000000000000000000000000000000000000000000000 new_status=confirmed with 3 blocks old_status=confirmed with 2 blocks
"
)
}
fn confs(confirmations: u32) -> ScriptStatus {
ScriptStatus::from_confirmations(confirmations)
}
proptest::proptest! {
#[test]
fn funding_never_fails_with_insufficient_funds(funding_amount in 3000u32.., num_utxos in 1..5u8, sats_per_vb in 1.0..500.0f32, key in crate::proptest::bitcoin::extended_priv_key(), alice in crate::proptest::ecdsa_fun::point(), bob in crate::proptest::ecdsa_fun::point()) {
proptest::prop_assume!(alice != bob);
tokio::runtime::Runtime::new().unwrap().block_on(async move {
let wallet = WalletBuilder::new(funding_amount as u64).with_key(key).with_num_utxos(num_utxos).with_fees(sats_per_vb, 1000).build();
let amount = wallet.max_giveable(TxLock::script_size()).await.unwrap();
let psbt: PartiallySignedTransaction = TxLock::new(&wallet, amount, PublicKey::from(alice), PublicKey::from(bob), wallet.new_address().await.unwrap()).await.unwrap().into();
let result = wallet.sign_and_finalize(psbt).await;
result.expect("transaction to be signed");
});
}
}
}

@ -5,7 +5,7 @@ use crate::protocol::bob::State2;
use crate::{bitcoin, env};
use anyhow::{anyhow, Error, Result};
use libp2p::core::Multiaddr;
use libp2p::ping::{Ping, PingConfig, PingEvent};
use libp2p::ping::{Ping, PingEvent};
use libp2p::request_response::{RequestId, ResponseChannel};
use libp2p::{NetworkBehaviour, PeerId};
use std::sync::Arc;
@ -83,7 +83,7 @@ impl Behaviour {
transfer_proof: transfer_proof::bob(),
encrypted_signature: encrypted_signature::bob(),
redial: redial::Behaviour::new(alice, Duration::from_secs(2)),
ping: Ping::new(PingConfig::new().with_keep_alive(true)),
ping: Ping::default(),
}
}

@ -1,9 +1,8 @@
use crate::env::GetConfig;
use crate::fs::system_data_dir;
use crate::network::rendezvous::XmrBtcNamespace;
use crate::network::rendezvous::{XmrBtcNamespace, DEFAULT_RENDEZVOUS_ADDRESS};
use crate::{env, monero};
use anyhow::{Context, Result};
use bitcoin::AddressType;
use libp2p::core::Multiaddr;
use std::ffi::OsString;
use std::path::PathBuf;
@ -70,36 +69,39 @@ where
let arguments = match args.cmd {
RawCommand::BuyXmr {
seller: Seller { seller },
bitcoin,
bitcoin_change_address,
monero,
bitcoin:
Bitcoin {
bitcoin_electrum_rpc_url,
bitcoin_target_block,
},
monero: Monero {
monero_daemon_address,
},
monero_receive_address,
tor: Tor { tor_socks5_port },
} => {
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
bitcoin.apply_defaults(is_testnet)?;
let monero_daemon_address = monero.apply_defaults(is_testnet);
let monero_receive_address =
validate_monero_address(monero_receive_address, is_testnet)?;
let bitcoin_change_address =
validate_bitcoin_address(bitcoin_change_address, is_testnet)?;
Arguments {
env_config: env_config_from(is_testnet),
debug,
json,
data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::BuyXmr {
seller,
} => Arguments {
env_config: env_config_from(is_testnet),
debug,
json,
data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::BuyXmr {
seller,
bitcoin_electrum_rpc_url: bitcoin_electrum_rpc_url_from(
bitcoin_electrum_rpc_url,
bitcoin_target_block,
bitcoin_change_address,
is_testnet,
)?,
bitcoin_target_block: bitcoin_target_block_from(bitcoin_target_block, is_testnet),
monero_receive_address: validate_monero_address(
monero_receive_address,
is_testnet,
)?,
monero_daemon_address: monero_daemon_address_from(
monero_daemon_address,
tor_socks5_port,
},
}
}
is_testnet,
),
tor_socks5_port,
},
},
RawCommand::History => Arguments {
env_config: env_config_from(is_testnet),
debug,
@ -109,70 +111,80 @@ where
},
RawCommand::Resume {
swap_id: SwapId { swap_id },
bitcoin,
monero,
tor: Tor { tor_socks5_port },
} => {
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
bitcoin.apply_defaults(is_testnet)?;
let monero_daemon_address = monero.apply_defaults(is_testnet);
Arguments {
env_config: env_config_from(is_testnet),
debug,
json,
data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::Resume {
swap_id,
bitcoin:
Bitcoin {
bitcoin_electrum_rpc_url,
bitcoin_target_block,
monero_daemon_address,
tor_socks5_port,
},
}
}
monero: Monero {
monero_daemon_address,
},
tor: Tor { tor_socks5_port },
} => Arguments {
env_config: env_config_from(is_testnet),
debug,
json,
data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::Resume {
swap_id,
bitcoin_electrum_rpc_url: bitcoin_electrum_rpc_url_from(
bitcoin_electrum_rpc_url,
is_testnet,
)?,
bitcoin_target_block: bitcoin_target_block_from(bitcoin_target_block, is_testnet),
monero_daemon_address: monero_daemon_address_from(
monero_daemon_address,
is_testnet,
),
tor_socks5_port,
},
},
RawCommand::Cancel {
swap_id: SwapId { swap_id },
force,
bitcoin,
} => {
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
bitcoin.apply_defaults(is_testnet)?;
Arguments {
env_config: env_config_from(is_testnet),
debug,
json,
data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::Cancel {
swap_id,
force,
bitcoin:
Bitcoin {
bitcoin_electrum_rpc_url,
bitcoin_target_block,
},
}
}
} => Arguments {
env_config: env_config_from(is_testnet),
debug,
json,
data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::Cancel {
swap_id,
force,
bitcoin_electrum_rpc_url: bitcoin_electrum_rpc_url_from(
bitcoin_electrum_rpc_url,
is_testnet,
)?,
bitcoin_target_block: bitcoin_target_block_from(bitcoin_target_block, is_testnet),
},
},
RawCommand::Refund {
swap_id: SwapId { swap_id },
force,
bitcoin,
} => {
let (bitcoin_electrum_rpc_url, bitcoin_target_block) =
bitcoin.apply_defaults(is_testnet)?;
Arguments {
env_config: env_config_from(is_testnet),
debug,
json,
data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::Refund {
swap_id,
force,
bitcoin:
Bitcoin {
bitcoin_electrum_rpc_url,
bitcoin_target_block,
},
}
}
} => Arguments {
env_config: env_config_from(is_testnet),
debug,
json,
data_dir: data::data_dir_from(data, is_testnet)?,
cmd: Command::Refund {
swap_id,
force,
bitcoin_electrum_rpc_url: bitcoin_electrum_rpc_url_from(
bitcoin_electrum_rpc_url,
is_testnet,
)?,
bitcoin_target_block: bitcoin_target_block_from(bitcoin_target_block, is_testnet),
},
},
RawCommand::ListSellers {
rendezvous_point,
tor: Tor { tor_socks5_port },
@ -198,7 +210,6 @@ pub enum Command {
seller: Multiaddr,
bitcoin_electrum_rpc_url: Url,
bitcoin_target_block: usize,
bitcoin_change_address: bitcoin::Address,
monero_receive_address: monero::Address,
monero_daemon_address: String,
tor_socks5_port: u16,
@ -276,12 +287,6 @@ enum RawCommand {
#[structopt(flatten)]
bitcoin: Bitcoin,
#[structopt(
long = "change-address",
help = "The bitcoin address where any form of change or excess funds should be sent to"
)]
bitcoin_change_address: bitcoin::Address,
#[structopt(flatten)]
monero: Monero,
@ -336,7 +341,8 @@ enum RawCommand {
ListSellers {
#[structopt(
long,
help = "Address of the rendezvous point you want to use to discover ASBs"
help = "Address of the rendezvous point you want to use to discover ASBs",
default_value = DEFAULT_RENDEZVOUS_ADDRESS
)]
rendezvous_point: Multiaddr,
@ -346,7 +352,7 @@ enum RawCommand {
}
#[derive(structopt::StructOpt, Debug)]
struct Monero {
pub struct Monero {
#[structopt(
long = "monero-daemon-address",
help = "Specify to connect to a monero daemon of your choice: <host>:<port>"
@ -354,18 +360,6 @@ struct Monero {
monero_daemon_address: Option<String>,
}
impl Monero {
fn apply_defaults(self, testnet: bool) -> String {
if let Some(address) = self.monero_daemon_address {
address
} else if testnet {
DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string()
} else {
DEFAULT_MONERO_DAEMON_ADDRESS.to_string()
}
}
}
#[derive(structopt::StructOpt, Debug)]
struct Bitcoin {
#[structopt(long = "electrum-rpc", help = "Provide the Bitcoin Electrum RPC URL")]
@ -378,28 +372,6 @@ struct Bitcoin {
bitcoin_target_block: Option<usize>,
}
impl Bitcoin {
fn apply_defaults(self, testnet: bool) -> Result<(Url, usize)> {
let bitcoin_electrum_rpc_url = if let Some(url) = self.bitcoin_electrum_rpc_url {
url
} else if testnet {
Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET)?
} else {
Url::from_str(DEFAULT_ELECTRUM_RPC_URL)?
};
let bitcoin_target_block = if let Some(target_block) = self.bitcoin_target_block {
target_block
} else if testnet {
DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET
} else {
DEFAULT_BITCOIN_CONFIRMATION_TARGET
};
Ok((bitcoin_electrum_rpc_url, bitcoin_target_block))
}
}
#[derive(structopt::StructOpt, Debug)]
struct Tor {
#[structopt(
@ -447,6 +419,16 @@ mod data {
}
}
fn bitcoin_electrum_rpc_url_from(url: Option<Url>, testnet: bool) -> Result<Url> {
if let Some(url) = url {
Ok(url)
} else if testnet {
Ok(Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET)?)
} else {
Ok(Url::from_str(DEFAULT_ELECTRUM_RPC_URL)?)
}
}
fn rendezvous_namespace_from(is_testnet: bool) -> XmrBtcNamespace {
if is_testnet {
XmrBtcNamespace::Testnet
@ -455,6 +437,26 @@ fn rendezvous_namespace_from(is_testnet: bool) -> XmrBtcNamespace {
}
}
fn bitcoin_target_block_from(target_block: Option<usize>, testnet: bool) -> usize {
if let Some(target_block) = target_block {
target_block
} else if testnet {
DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET
} else {
DEFAULT_BITCOIN_CONFIRMATION_TARGET
}
}
fn monero_daemon_address_from(address: Option<String>, testnet: bool) -> String {
if let Some(address) = address {
address
} else if testnet {
DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string()
} else {
DEFAULT_MONERO_DAEMON_ADDRESS.to_string()
}
}
fn env_config_from(testnet: bool) -> env::Config {
if testnet {
env::Testnet::get_config()
@ -483,28 +485,6 @@ fn validate_monero_address(
Ok(address)
}
fn validate_bitcoin_address(address: bitcoin::Address, testnet: bool) -> Result<bitcoin::Address> {
let expected_network = if testnet {
bitcoin::Network::Testnet
} else {
bitcoin::Network::Bitcoin
};
if address.network != expected_network {
anyhow::bail!(
"Invalid Bitcoin address provided; expected network {} but provided address is for {}",
expected_network,
address.network
);
}
if address.address_type() != Some(AddressType::P2wpkh) {
anyhow::bail!("Invalid Bitcoin address provided, only bech32 format is supported!")
}
Ok(address)
}
fn parse_monero_address(s: &str) -> Result<monero::Address> {
monero::Address::from_str(s).with_context(|| {
format!(
@ -515,7 +495,7 @@ fn parse_monero_address(s: &str) -> Result<monero::Address> {
}
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq)]
#[error("Invalid monero address provided, expected address on network {expected:?} but address provided is on {actual:?}")]
#[error("Invalid monero address provided, expected address on network {expected:?} but address provided is on {actual:?}")]
pub struct MoneroAddressNetworkMismatch {
expected: monero::Network,
actual: monero::Network,
@ -532,9 +512,7 @@ mod tests {
const MAINNET: &str = "mainnet";
const MONERO_STAGENET_ADDRESS: &str = "53gEuGZUhP9JMEBZoGaFNzhwEgiG7hwQdMCqFxiyiTeFPmkbt1mAoNybEUvYBKHcnrSgxnVWgZsTvRBaHBNXPa8tHiCU51a";
const BITCOIN_TESTNET_ADDRESS: &str = "tb1qr3em6k3gfnyl8r7q0v7t4tlnyxzgxma3lressv";
const MONERO_MAINNET_ADDRESS: &str = "44Ato7HveWidJYUAVw5QffEcEtSH1DwzSP3FPPkHxNAS4LX9CqgucphTisH978FLHE34YNEx7FcbBfQLQUU8m3NUC4VqsRa";
const BITCOIN_MAINNET_ADDRESS: &str = "bc1qe4epnfklcaa0mun26yz5g8k24em5u9f92hy325";
const MULTI_ADDRESS: &str =
"/ip4/127.0.0.1/tcp/9939/p2p/12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi";
const SWAP_ID: &str = "ea030832-3be9-454f-bb98-5ea9a788406b";
@ -546,8 +524,6 @@ mod tests {
"buy-xmr",
"--receive-address",
MONERO_MAINNET_ADDRESS,
"--change-address",
BITCOIN_MAINNET_ADDRESS,
"--seller",
MULTI_ADDRESS,
];
@ -566,8 +542,6 @@ mod tests {
"buy-xmr",
"--receive-address",
MONERO_STAGENET_ADDRESS,
"--change-address",
BITCOIN_TESTNET_ADDRESS,
"--seller",
MULTI_ADDRESS,
];
@ -587,8 +561,6 @@ mod tests {
"buy-xmr",
"--receive-address",
MONERO_STAGENET_ADDRESS,
"--change-address",
BITCOIN_TESTNET_ADDRESS,
"--seller",
MULTI_ADDRESS,
];
@ -612,8 +584,6 @@ mod tests {
"buy-xmr",
"--receive-address",
MONERO_MAINNET_ADDRESS,
"--change-address",
BITCOIN_MAINNET_ADDRESS,
"--seller",
MULTI_ADDRESS,
];
@ -710,8 +680,6 @@ mod tests {
"--data-base-dir",
data_dir,
"buy-xmr",
"--change-address",
BITCOIN_MAINNET_ADDRESS,
"--receive-address",
MONERO_MAINNET_ADDRESS,
"--seller",
@ -734,8 +702,6 @@ mod tests {
"--data-base-dir",
data_dir,
"buy-xmr",
"--change-address",
BITCOIN_TESTNET_ADDRESS,
"--receive-address",
MONERO_STAGENET_ADDRESS,
"--seller",
@ -798,8 +764,6 @@ mod tests {
BINARY_NAME,
"--debug",
"buy-xmr",
"--change-address",
BITCOIN_MAINNET_ADDRESS,
"--receive-address",
MONERO_MAINNET_ADDRESS,
"--seller",
@ -817,8 +781,6 @@ mod tests {
"--testnet",
"--debug",
"buy-xmr",
"--change-address",
BITCOIN_TESTNET_ADDRESS,
"--receive-address",
MONERO_STAGENET_ADDRESS,
"--seller",
@ -861,8 +823,6 @@ mod tests {
BINARY_NAME,
"--json",
"buy-xmr",
"--change-address",
BITCOIN_MAINNET_ADDRESS,
"--receive-address",
MONERO_MAINNET_ADDRESS,
"--seller",
@ -880,8 +840,6 @@ mod tests {
"--testnet",
"--json",
"buy-xmr",
"--change-address",
BITCOIN_TESTNET_ADDRESS,
"--receive-address",
MONERO_STAGENET_ADDRESS,
"--seller",
@ -918,105 +876,6 @@ mod tests {
);
}
#[test]
fn only_bech32_addresses_mainnet_are_allowed() {
let raw_ars = vec![
BINARY_NAME,
"buy-xmr",
"--change-address",
"1A5btpLKZjgYm8R22rJAhdbTFVXgSRA2Mp",
"--receive-address",
MONERO_MAINNET_ADDRESS,
"--seller",
MULTI_ADDRESS,
];
let result = parse_args_and_apply_defaults(raw_ars);
assert_eq!(
result.unwrap_err().to_string(),
"Invalid Bitcoin address provided, only bech32 format is supported!"
);
let raw_ars = vec![
BINARY_NAME,
"buy-xmr",
"--change-address",
"36vn4mFhmTXn7YcNwELFPxTXhjorw2ppu2",
"--receive-address",
MONERO_MAINNET_ADDRESS,
"--seller",
MULTI_ADDRESS,
];
let result = parse_args_and_apply_defaults(raw_ars);
assert_eq!(
result.unwrap_err().to_string(),
"Invalid Bitcoin address provided, only bech32 format is supported!"
);
let raw_ars = vec![
BINARY_NAME,
"buy-xmr",
"--change-address",
"bc1qh4zjxrqe3trzg7s6m7y67q2jzrw3ru5mx3z7j3",
"--receive-address",
MONERO_MAINNET_ADDRESS,
"--seller",
MULTI_ADDRESS,
];
let result = parse_args_and_apply_defaults(raw_ars).unwrap();
assert!(matches!(result, ParseResult::Arguments(_)));
}
#[test]
fn only_bech32_addresses_testnet_are_allowed() {
let raw_ars = vec![
BINARY_NAME,
"--testnet",
"buy-xmr",
"--change-address",
"n2czxyeFCQp9e8WRyGpy4oL4YfQAeKkkUH",
"--receive-address",
MONERO_STAGENET_ADDRESS,
"--seller",
MULTI_ADDRESS,
];
let result = parse_args_and_apply_defaults(raw_ars);
assert_eq!(
result.unwrap_err().to_string(),
"Invalid Bitcoin address provided, only bech32 format is supported!"
);
let raw_ars = vec![
BINARY_NAME,
"--testnet",
"buy-xmr",
"--change-address",
"2ND9a4xmQG89qEWG3ETRuytjKpLmGrW7Jvf",
"--receive-address",
MONERO_STAGENET_ADDRESS,
"--seller",
MULTI_ADDRESS,
];
let result = parse_args_and_apply_defaults(raw_ars);
assert_eq!(
result.unwrap_err().to_string(),
"Invalid Bitcoin address provided, only bech32 format is supported!"
);
let raw_ars = vec![
BINARY_NAME,
"--testnet",
"buy-xmr",
"--change-address",
"tb1q958vfh3wkdp232pktq8zzvmttyxeqnj80zkz3v",
"--receive-address",
MONERO_STAGENET_ADDRESS,
"--seller",
MULTI_ADDRESS,
];
let result = parse_args_and_apply_defaults(raw_ars).unwrap();
assert!(matches!(result, ParseResult::Arguments(_)));
}
impl Arguments {
pub fn buy_xmr_testnet_defaults() -> Self {
Self {
@ -1029,7 +888,6 @@ mod tests {
bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL_TESTNET)
.unwrap(),
bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET_TESTNET,
bitcoin_change_address: BITCOIN_TESTNET_ADDRESS.parse().unwrap(),
monero_receive_address: monero::Address::from_str(MONERO_STAGENET_ADDRESS)
.unwrap(),
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS_STAGENET.to_string(),
@ -1048,7 +906,6 @@ mod tests {
seller: Multiaddr::from_str(MULTI_ADDRESS).unwrap(),
bitcoin_electrum_rpc_url: Url::from_str(DEFAULT_ELECTRUM_RPC_URL).unwrap(),
bitcoin_target_block: DEFAULT_BITCOIN_CONFIRMATION_TARGET,
bitcoin_change_address: BITCOIN_MAINNET_ADDRESS.parse().unwrap(),
monero_receive_address: monero::Address::from_str(MONERO_MAINNET_ADDRESS)
.unwrap(),
monero_daemon_address: DEFAULT_MONERO_DAEMON_ADDRESS.to_string(),

@ -15,12 +15,6 @@ use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::time::Duration;
/// Returns sorted list of sellers, with [Online](Status::Online) listed first.
///
/// First uses the rendezvous node to discover peers in the given namespace,
/// then fetches a quote from each peer that was discovered. If fetching a quote
/// from a discovered peer fails the seller's status will be
/// [Unreachable](Status::Unreachable).
pub async fn list_sellers(
rendezvous_node_peer_id: PeerId,
rendezvous_node_addr: Multiaddr,
@ -295,10 +289,7 @@ impl EventLoop {
.collect::<Result<Vec<_>, _>>();
match all_quotes_fetched {
Ok(mut sellers) => {
sellers.sort();
break sellers;
}
Ok(sellers) => break sellers,
Err(StillPending {}) => continue,
}
}

@ -3,17 +3,13 @@ use crate::protocol::bob;
use crate::protocol::bob::BobState;
use monero_rpc::wallet::BlockHeight;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use std::fmt;
#[serde_as]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum Bob {
Started {
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
btc_amount: bitcoin::Amount,
#[serde_as(as = "DisplayFromStr")]
change_address: bitcoin::Address,
},
ExecutionSetupDone {
state2: bob::State2,
@ -49,13 +45,7 @@ pub enum BobEndState {
impl From<BobState> for Bob {
fn from(bob_state: BobState) -> Self {
match bob_state {
BobState::Started {
btc_amount,
change_address,
} => Bob::Started {
btc_amount,
change_address,
},
BobState::Started { btc_amount } => Bob::Started { btc_amount },
BobState::SwapSetupCompleted(state2) => Bob::ExecutionSetupDone { state2 },
BobState::BtcLocked(state3) => Bob::BtcLocked { state3 },
BobState::XmrLockProofReceived {
@ -87,13 +77,7 @@ impl From<BobState> for Bob {
impl From<Bob> for BobState {
fn from(db_state: Bob) -> Self {
match db_state {
Bob::Started {
btc_amount,
change_address,
} => BobState::Started {
btc_amount,
change_address,
},
Bob::Started { btc_amount } => BobState::Started { btc_amount },
Bob::ExecutionSetupDone { state2 } => BobState::SwapSetupCompleted(state2),
Bob::BtcLocked { state3 } => BobState::BtcLocked(state3),
Bob::XmrLockProofReceived {

@ -29,9 +29,5 @@ pub mod network;
pub mod protocol;
pub mod seed;
pub mod tor;
pub mod tracing_ext;
mod monero_ext;
#[cfg(test)]
mod proptest;

@ -1,5 +1,7 @@
mod impl_from_rr_event;
pub mod alice;
pub mod bob;
pub mod cbor_request_response;
pub mod encrypted_signature;
pub mod json_pull_codec;

@ -1,6 +1,9 @@
use libp2p::rendezvous::Namespace;
use std::fmt;
pub const DEFAULT_RENDEZVOUS_ADDRESS: &str =
"/dnsaddr/rendezvous.coblox.tech/p2p/12D3KooWQUt9DkNZxEn2R5ymJzWj15MpG6mTW84kyd8vDaRZi46o";
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum XmrBtcNamespace {
Mainnet,

@ -1,29 +0,0 @@
use proptest::prelude::*;
pub mod ecdsa_fun {
use super::*;
use ::ecdsa_fun::fun::marker::{Mark, NonZero, Normal};
use ::ecdsa_fun::fun::{Point, Scalar, G};
pub fn point() -> impl Strategy<Value = Point> {
scalar().prop_map(|mut scalar| Point::from_scalar_mul(&G, &mut scalar).mark::<Normal>())
}
pub fn scalar() -> impl Strategy<Value = Scalar> {
prop::array::uniform32(0..255u8).prop_filter_map("generated the 0 element", |bytes| {
Scalar::from_bytes_mod_order(bytes).mark::<NonZero>()
})
}
}
pub mod bitcoin {
use super::*;
use ::bitcoin::util::bip32::ExtendedPrivKey;
use ::bitcoin::Network;
pub fn extended_priv_key() -> impl Strategy<Value = ExtendedPrivKey> {
prop::array::uniform8(0..255u8).prop_filter_map("invalid secret key generated", |bytes| {
ExtendedPrivKey::new_master(Network::Regtest, &bytes).ok()
})
}
}

@ -33,14 +33,10 @@ impl Swap {
env_config: env::Config,
event_loop_handle: cli::EventLoopHandle,
monero_receive_address: monero::Address,
bitcoin_change_address: bitcoin::Address,
btc_amount: bitcoin::Amount,
) -> Self {
Self {
state: BobState::Started {
btc_amount,
change_address: bitcoin_change_address,
},
state: BobState::Started { btc_amount },
event_loop_handle,
db,
bitcoin_wallet,
@ -51,7 +47,6 @@ impl Swap {
}
}
#[allow(clippy::too_many_arguments)]
pub fn from_db(
db: Database,
id: Uuid,

@ -25,7 +25,6 @@ use uuid::Uuid;
pub enum BobState {
Started {
btc_amount: bitcoin::Amount,
change_address: bitcoin::Address,
},
SwapSetupCompleted(State2),
BtcLocked(State3),
@ -170,14 +169,7 @@ impl State0 {
bail!("Alice's dleq proof doesn't verify")
}
let tx_lock = bitcoin::TxLock::new(
wallet,
self.btc,
msg.A,
self.b.public(),
self.refund_address.clone(),
)
.await?;
let tx_lock = bitcoin::TxLock::new(wallet, self.btc, msg.A, self.b.public()).await?;
let v = msg.v_a + self.v_b;
Ok(State1 {

@ -61,10 +61,8 @@ async fn next_state(
tracing::trace!(%state, "Advancing state");
Ok(match state {
BobState::Started {
btc_amount,
change_address,
} => {
BobState::Started { btc_amount } => {
let bitcoin_refund_address = bitcoin_wallet.new_address().await?;
let tx_refund_fee = bitcoin_wallet
.estimate_fee(TxRefund::weight(), btc_amount)
.await?;
@ -78,7 +76,7 @@ async fn next_state(
btc: btc_amount,
tx_refund_fee,
tx_cancel_fee,
bitcoin_refund_address: change_address,
bitcoin_refund_address,
})
.await?;

@ -1,65 +0,0 @@
#![allow(clippy::unwrap_used)] // This is only meant to be used in tests.
use std::io;
use std::sync::{Arc, Mutex};
use tracing::subscriber;
use tracing_subscriber::filter::LevelFilter;
use tracing_subscriber::fmt::MakeWriter;
/// Setup tracing with a capturing writer, allowing assertions on the log
/// messages.
///
/// Time and ANSI are disabled to make the output more predictable and
/// readable.
pub fn capture_logs(min_level: LevelFilter) -> MakeCapturingWriter {
let make_writer = MakeCapturingWriter::default();
let guard = subscriber::set_default(
tracing_subscriber::fmt()
.with_ansi(false)
.without_time()
.with_writer(make_writer.clone())
.with_env_filter(format!("{}", min_level))
.finish(),
);
// don't clean up guard we stay initialized
std::mem::forget(guard);
make_writer
}
#[derive(Default, Clone)]
pub struct MakeCapturingWriter {
writer: CapturingWriter,
}
impl MakeCapturingWriter {
pub fn captured(&self) -> String {
let captured = &self.writer.captured;
let cursor = captured.lock().unwrap();
String::from_utf8(cursor.clone().into_inner()).unwrap()
}
}
impl MakeWriter for MakeCapturingWriter {
type Writer = CapturingWriter;
fn make_writer(&self) -> Self::Writer {
self.writer.clone()
}
}
#[derive(Default, Clone)]
pub struct CapturingWriter {
captured: Arc<Mutex<io::Cursor<Vec<u8>>>>,
}
impl io::Write for CapturingWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.captured.lock().unwrap().write(buf)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}

@ -434,7 +434,6 @@ impl BobParams {
self.env_config,
handle,
self.monero_wallet.get_main_address(),
self.bitcoin_wallet.new_address().await?,
btc_amount,
);

Loading…
Cancel
Save