mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2024-10-01 01:45:40 -04:00
Merge #606
606: Script to list sellers and auto trigger swap + docs r=da-kami a=da-kami Uses best price to determine the seller to swap with. TODO: - [x] Codebase to be tested on test-/stagenet. - [x] Update readme - [x] Update docs This includes a couple of changes that I found while toying with the output and writing docs. Can drop the individual commits if necessary (but would need to adapt the docs then). Co-authored-by: Daniel Karzel <daniel@comit.network> Co-authored-by: Thomas Eizinger <thomas@eizinger.io>
This commit is contained in:
commit
93a69563a9
@ -34,6 +34,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- The commandline interface of the CLI to combine `--seller-addr` and `--seller-peer-id`.
|
||||
These two parameters have been merged into a parameter `--seller` that accepts a single [multiaddress](https://docs.libp2p.io/concepts/addressing/).
|
||||
The multiaddress must end with a `/p2p` protocol defining the seller's peer ID.
|
||||
- The `--data-dir` option to `--data-base-dir`.
|
||||
Previously, this option determined the final data directory, regardless of the `--testnet` flag.
|
||||
With `--data-base-dir`, a subdirectory (either `testnet` or `mainnet`) will be created under the given path.
|
||||
This allows using the same command with or without `--testnet`.
|
||||
|
||||
### Removed
|
||||
|
||||
|
53
README.md
53
README.md
@ -5,50 +5,33 @@ It implements the protocol described in section 3 of [this](https://arxiv.org/ab
|
||||
|
||||
More information about the protocol in this [presentation](https://youtu.be/Jj8rd4WOEy0) and this [blog post](https://comit.network/blog/2020/10/06/monero-bitcoin).
|
||||
|
||||
## Quick start - CLI
|
||||
Currently, swaps are only offered in one direction with the `swap` CLI on the buying side (send BTC, receive XMR).
|
||||
We are working on implementing a protocol where XMR moves first, but are currently blocked by advances on Monero itself.
|
||||
You can read [this blogpost](https://comit.network/blog/2021/07/02/transaction-presigning) for more information.
|
||||
|
||||
From version `0.6.0` onwards the software default to running on `mainnet`.
|
||||
It is recommended to try the software on testnet first, which can be achieved by providing the `--testnet` flag.
|
||||
This quickstart guide assumes that you are running the software on testnet (i.e. Bitcoin testnet3 and Monero stagenet):
|
||||
## Quick Start
|
||||
|
||||
1. Download the [latest `swap` binary release](https://github.com/comit-network/xmr-btc-swap/releases/latest) for your operating system
|
||||
2. Run the binary specifying the monero address where you wish to receive monero and the connection details of the seller:
|
||||
`./swap --testnet buy-xmr --receive-address <YOUR MONERO ADDRESS> --seller-peer-id <SELLERS PEER ID> --seller-addr <SELLERS MULTIADDRESS>`
|
||||
You can generate a receive address using your monero wallet.
|
||||
The seller will provide you their peer id and multiaddress.
|
||||
We are running an `asb` instance on testnet.
|
||||
You can swap with to get familiar with the `swap` CLI.
|
||||
Our peer id is `12D3KooWCdMKjesXMJz1SiZ7HgotrxuqhQJbP5sgBm2BwP1cqThi` and our multiaddress is `/dnsaddr/xmr-btc-asb.coblox.tech`
|
||||
3. Follow the instructions printed to the terminal
|
||||
1. Download the [latest `swap` binary release](https://github.com/comit-network/xmr-btc-swap/releases/latest) for your operating system.
|
||||
2. Find a seller to swap with:
|
||||
|
||||
For running the software on mainnet you just omit the `--testnet` flag.
|
||||
Running on mainnet will automatically apply sane defaults.
|
||||
Be aware that this software is still early-stage.
|
||||
Make sure to check `--help` and understand how the `cancel` and `refund` commands work before running on mainnet.
|
||||
You are running this software at your own risk.
|
||||
As always we recommend: Verify, don't trust.
|
||||
All code is available in this repository.
|
||||
```shell
|
||||
./swap --testnet --list-sellers
|
||||
```
|
||||
|
||||
## How it works
|
||||
3. Swap with a seller:
|
||||
|
||||
This repository primarily hosts two components:
|
||||
```shell
|
||||
./swap --testnet buy-xmr --receive-address <YOUR MONERO ADDRESS> --change-address <YOUR BITCOIN CHANGE ADDRESS> --seller <SELLER MULTIADDRESS>
|
||||
```
|
||||
|
||||
- the `swap` CLI
|
||||
- the [`asb` service](/docs/asb/README.md)
|
||||
For more detailed documentation on the CLI, see [this README](./docs/cli/README.md).
|
||||
|
||||
### swap CLI
|
||||
## Becoming a Market Maker
|
||||
|
||||
The `swap` CLI acts in the role of Bob and swaps BTC for XMR.
|
||||
See `./swap --help` for a description of all commands.
|
||||
The main command is `buy-xmr` which automatically connects to an instance of `asb`.
|
||||
Swapping of course needs two parties - and the CLI is only one of them: The taker that occasionally starts a swap with a market maker.
|
||||
|
||||
### asb service
|
||||
|
||||
`asb` is short for **a**utomated **s**wap **b**ackend (we are open to suggestions for better names!).
|
||||
The service acts as the counter-party for the `swap` CLI in the role of Alice.
|
||||
It provides the CLI with a quote and the liquidity necessary for swapping BTC into XMR.
|
||||
|
||||
For details on how to run the ASB please refer to the [ASB docs](/docs/asb/README.md).
|
||||
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).
|
||||
|
||||
## Contact
|
||||
|
||||
|
6
docs/README.md
Normal file
6
docs/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# Documentation
|
||||
|
||||
This directory hosts various pieces of documentation.
|
||||
|
||||
- [`swap` CLI](./cli/README.md)
|
||||
- [`asb` service](./asb/README.md)
|
@ -1,6 +1,6 @@
|
||||
# XMR to BTC Atomic Swap - Automated Swap Backend (ASB)
|
||||
# Automated Swap Backend (ASB)
|
||||
|
||||
## Quick Start ASB
|
||||
## Quick Start
|
||||
|
||||
From version `0.6.0` onwards the software default to running on `mainnet`.
|
||||
It is recommended to try the software on testnet first, which can be achieved by providing the `--testnet` flag.
|
||||
@ -33,7 +33,7 @@ Public Electrum mainnet nodes can be found [here](https://1209k.com/bitcoin-eye/
|
||||
The ASB is a long running daemon that acts as the trading partner to the swap CLI.
|
||||
The CLI user is buying XMR (i.e. receives XMR, sends BTC), the ASB service provider is selling XMR (i.e. sends XMR, receives BTC).
|
||||
The ASB can handle multiple swaps with different peers concurrently.
|
||||
The ASB communicates with the CLI on various [libp2p](https://libp2p.io/)-based network protocols.
|
||||
The ASB communicates with the CLI on various [libp2p-based](https://libp2p.io/) network protocols.
|
||||
|
||||
Both the ASB and the CLI can be run by anybody.
|
||||
The CLI is designed to run one specific swap against an ASB.
|
||||
@ -42,23 +42,22 @@ Since the ASB is a long running task we specify the person running an ASB as ser
|
||||
|
||||
### ASB discovery
|
||||
|
||||
Currently, there is no automated discovery for service providers running an ASB.
|
||||
A service provider has to manually provide the connection details to users that will run the CLI.
|
||||
The ASB daemon supports the libp2p [rendezvous-protocol](https://github.com/libp2p/specs/tree/master/rendezvous).
|
||||
Usage of the rendezvous functionality is entirely optional.
|
||||
|
||||
[Libp2p addressing](https://docs.libp2p.io/concepts/addressing/) is used to identify a service provider by multi-address and peer-id.
|
||||
The Peer-ID is printed upon startup of the ASB.
|
||||
The multi-address typically consists of IP-address or URL (if DNS entry configured) of the service provider.
|
||||
You can configure a rendezvous point in the `[network]` section of your config file.
|
||||
For the registration to be successful, you also need to configure the externally reachable addresses within the `[network]` section.
|
||||
For example:
|
||||
|
||||
When configuring a domain name for the ASB through a DNS entry, a service provider can configure it by using the [`dnsaddr` format](https://github.com/multiformats/multiaddr/blob/master/protocols/DNSADDR.md) for the TXT entry.
|
||||
This will simplify the connection detail `--seller-addr` for CLI users connecting to the ASB and provides more flexibility with e.g. ports (i.e. `/dnsaddr/your.domain.tld` instead of `/dns4/your.domain.tld/tcp/port`).
|
||||
```toml
|
||||
[network]
|
||||
rendezvous_point = "/dnsaddr/rendezvous.coblox.tech/p2p/12D3KooWQUt9DkNZxEn2R5ymJzWj15MpG6mTW84kyd8vDaRZi46o"
|
||||
external_addresses = ["/dns4/example.com/tcp/9939"]
|
||||
```
|
||||
|
||||
Each service provider running an ASB can decide how/where to share these connection details.
|
||||
|
||||
![Service Provider scenarios](http://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/comit-network/xmr-btc-swap/d2cf45d8b9f0c2e180cd85aa034f370965adc11c/docs/asb/diagrams/cli-asb-overview.puml)
|
||||
|
||||
Eventually, more elaborate discovery mechanisms can be added.
|
||||
|
||||
The **CLI** user can specify a service providers's multiaddress and peer-id with `--seller-addr` and `--seller-peer-id`, see `./swap --help` for details.
|
||||
For more information on the concept of multiaddresses, check out the libp2p documentation [here](https://docs.libp2p.io/concepts/addressing/).
|
||||
In particular, you may be interested in setting up your ASB to be reachable via a [`/dnsaddr`](https://github.com/multiformats/multiaddr/blob/master/protocols/DNSADDR.md) multiaddress.
|
||||
`/dnsaddr` addresses provide you with flexibility over the port and also allow you to register two addresses with transports (with and without websockets for example) under the same name.
|
||||
|
||||
### Setup Details
|
||||
|
||||
@ -96,28 +95,37 @@ The Bitcoin wallet is created upon initial startup and stored in the data folder
|
||||
|
||||
#### Market Making
|
||||
|
||||
For market making the ASB offers the following parameters in the config:
|
||||
|
||||
```toml
|
||||
[maker]
|
||||
min_buy_btc = 0.0001
|
||||
max_buy_btc = 0.0001
|
||||
ask_spread = 0.02
|
||||
price_ticker_ws_url = "wss://ws.kraken.com"
|
||||
```
|
||||
|
||||
The minimum and maximum amount as well as a spread, that is added on top of the price fetched from a central exchange, can be configured.
|
||||
|
||||
In order to be able to trade, the ASB must define a price to be able to agree on the amounts to be swapped with a CLI.
|
||||
Currently we use a spot-price mode, i.e. the ASB dictates the price to the CLI.
|
||||
|
||||
A CLI can connect to the ASB at any time and request a quote for buying XMR.
|
||||
The ASB then returns the current price and the maximum amount tradeable.
|
||||
|
||||
The maximum amount tradeable can be configured with the `--max-buy-btc` parameter.
|
||||
|
||||
The `XMR<>BTC` price is currently determined by the price from the central exchange Kraken.
|
||||
Upon startup the ASB connects to the Kraken price websocket and listens on the stream for price updates.
|
||||
You can plug in a different price ticker websocket using the the `price_ticker_ws_url` configuration option.
|
||||
You will have to make sure that the format returned is the same as the format used by Kraken.
|
||||
|
||||
Currently the spot price is equal to the market price on Kraken.
|
||||
Currently, we use a spot-price model, i.e. the ASB dictates the price to the CLI.
|
||||
A CLI can connect to the ASB at any time and request a quote for buying XMR.
|
||||
The ASB then returns the current price and the minimum and maximum amount tradeable.
|
||||
|
||||
#### Swap Execution
|
||||
|
||||
Swap execution within the ASB is automated.
|
||||
Incoming swaps request will be automatically processed and the swap will execute automatically.
|
||||
Incoming swaps request will be automatically processed, and the swap will execute automatically.
|
||||
Swaps where Bob does not act, so Alice cannot redeem, will be automatically refunded or punished.
|
||||
When the ASB is restarted unfinished swaps will be resumed automatically.
|
||||
If the ASB is restarted unfinished swaps will be resumed automatically.
|
||||
|
||||
The refund scenario is a scenario where the CLI refunds the Bitcoin.
|
||||
The ASB can then refund the Monero which will be automatically transferred to the `asb-wallet`.
|
||||
The ASB can then refund the Monero which will be automatically transferred back to the `asb-wallet`.
|
||||
|
||||
The punish scenario is a scenario where the CLI does not refund and hence the ASB cannot refund the Monero.
|
||||
After a second timelock expires the ASB will automatically punish the CLI user by taking the Bitcoin.
|
||||
@ -128,7 +136,6 @@ All claimed Bitcoin ends up in the internal Bitcoin wallet of the ASB.
|
||||
The ASB offers a commands to withdraw Bitcoin and check the balance, run `./asb --help` for details.
|
||||
|
||||
If the ASB has insufficient Monero funds to accept a swap the swap setup is rejected.
|
||||
Note that currently there is no specific error sent back to the CLI for such kind of cases, so a user might not know why the swap execution was rejected.
|
||||
Note that there is currently no notification service implemented for low funds.
|
||||
The ASB provider has to monitor Monero funds to make sure the ASB still has liquidity.
|
||||
|
||||
|
141
docs/cli/README.md
Normal file
141
docs/cli/README.md
Normal file
@ -0,0 +1,141 @@
|
||||
# Swap CLI
|
||||
|
||||
The CLI defaults to **mainnet** (from version 0.6.0 onwards).
|
||||
For testing and to familiarise yourself with the tool, we recommend you to try it on testnet first.
|
||||
To do that, pass the `--testnet` flag with the actual command:
|
||||
|
||||
```shell
|
||||
swap --testnet <SUBCOMMAND>
|
||||
```
|
||||
|
||||
The two main commands of the CLI are:
|
||||
|
||||
- `buy-xmr`: for swapping BTC to XMR with a particular seller
|
||||
- `list-sellers`: for discovering available sellers through a rendezvous point
|
||||
|
||||
Running `swap --help` gives us roughly the following output:
|
||||
|
||||
```
|
||||
swap 0.8.0
|
||||
The COMIT guys <hello@comit.network>
|
||||
CLI for swapping BTC for XMR
|
||||
|
||||
USAGE:
|
||||
swap [FLAGS] [OPTIONS] <SUBCOMMAND>
|
||||
|
||||
FLAGS:
|
||||
--debug Activate debug logging
|
||||
-h, --help Prints help information
|
||||
-j, --json Outputs all logs in JSON format instead of plain text
|
||||
--testnet Swap on testnet and assume testnet defaults for data-dir and the blockchain related parameters
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
--data-base-dir <data> The base data directory to be used for mainnet / testnet specific data like database, wallets etc
|
||||
|
||||
SUBCOMMANDS:
|
||||
buy-xmr Start a BTC for XMR swap
|
||||
list-sellers Discover and list sellers (i.e. ASB providers)
|
||||
|
||||
cancel Try to cancel an ongoing swap (expert users only)
|
||||
help Prints this message or the help of the given subcommand(s)
|
||||
history Show a list of past, ongoing and completed swaps
|
||||
refund Try to cancel a swap and refund the BTC (expert users only)
|
||||
resume Resume a swap
|
||||
```
|
||||
|
||||
## Swapping BTC for XMR
|
||||
|
||||
Running `swap buy-xmr --help` gives us roughly the following output:
|
||||
|
||||
```
|
||||
swap-buy-xmr 0.8.0
|
||||
Start a BTC for XMR swap
|
||||
|
||||
USAGE:
|
||||
swap buy-xmr [FLAGS] [OPTIONS] --change-address <bitcoin-change-address> --receive-address <monero-receive-address> --seller <seller>
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
--testnet Swap on testnet and assume testnet defaults for data-dir and the blockchain related parameters
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
--change-address <bitcoin-change-address> The bitcoin address where any form of change or excess funds should be sent to
|
||||
--receive-address <monero-receive-address> The monero address where you would like to receive monero
|
||||
--seller <seller> The seller's address. Must include a peer ID part, i.e. `/p2p/`
|
||||
|
||||
--electrum-rpc <bitcoin-electrum-rpc-url> Provide the Bitcoin Electrum RPC URL
|
||||
--bitcoin-target-block <bitcoin-target-block> Estimate Bitcoin fees such that transactions are confirmed within the specified number of blocks
|
||||
--monero-daemon-address <monero-daemon-address> Specify to connect to a monero daemon of your choice: <host>:<port>
|
||||
--tor-socks5-port <tor-socks5-port> Your local Tor socks5 proxy port [default: 9050]
|
||||
```
|
||||
|
||||
This command has three core options:
|
||||
|
||||
- `--change-address`: A Bitcoin address you control. Will be used for refunds of any kind.
|
||||
- `--receive-address`: A Monero address you control. This is where you will receive the Monero after the swap.
|
||||
- `--seller`: The multiaddress of the seller you want to swap with.
|
||||
|
||||
## Discovering sellers
|
||||
|
||||
Running `swap list-sellers --help` gives us roughly the following output:
|
||||
|
||||
```
|
||||
swap-list-sellers 0.8.0
|
||||
Discover and list sellers (i.e. ASB providers)
|
||||
|
||||
USAGE:
|
||||
swap list-sellers [FLAGS] [OPTIONS]
|
||||
|
||||
FLAGS:
|
||||
-h, --help Prints help information
|
||||
--testnet Swap on testnet and assume testnet defaults for data-dir and the blockchain related parameters
|
||||
-V, --version Prints version information
|
||||
|
||||
OPTIONS:
|
||||
--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]
|
||||
```
|
||||
|
||||
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 ...
|
||||
Discovered peer 12D3KooWPZ69DRp4wbGB3wJsxxsg1XW1EVZ2evtVwcARCF3a1nrx at /dns4/ac4hgzmsmekwekjbdl77brufqqbylddugzze4tel6qsnlympgmr46iid.onion/tcp/8765
|
||||
+----------------+----------------+----------------+--------+----------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| PRICE | MIN_QUANTITY | MAX_QUANTITY | STATUS | ADDRESS |
|
||||
+====================================================================================================================================================================================================+
|
||||
| 0.00665754 BTC | 0.00010000 BTC | 0.00100000 BTC | Online | /dns4/ac4hgzmsmekwekjbdl77brufqqbylddugzze4tel6qsnlympgmr46iid.onion/tcp/8765/p2p/12D3KooWPZ69DRp4wbGB3wJsxxsg1XW1EVZ2evtVwcARCF3a1nrx |
|
||||
+----------------+----------------+----------------+--------+----------------------------------------------------------------------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
or this if a node is not reachable:
|
||||
|
||||
```
|
||||
Connected to rendezvous point, discovering nodes in 'xmr-btc-swap-testnet' namespace ...
|
||||
Discovered peer 12D3KooWPZ69DRp4wbGB3wJsxxsg1XW1EVZ2evtVwcARCF3a1nrx at /dns4/ac4hgzmsmekwekjbdl77brufqqbylddugzze4tel6qsnlympgmr46iid.onion/tcp/8765
|
||||
+-------+--------------+--------------+-------------+----------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| PRICE | MIN_QUANTITY | MAX_QUANTITY | STATUS | ADDRESS |
|
||||
+============================================================================================================================================================================================+
|
||||
| ??? | ??? | ??? | Unreachable | /dns4/ac4hgzmsmekwekjbdl77brufqqbylddugzze4tel6qsnlympgmr46iid.onion/tcp/8765/p2p/12D3KooWPZ69DRp4wbGB3wJsxxsg1XW1EVZ2evtVwcARCF3a1nrx |
|
||||
+-------+--------------+--------------+-------------+----------------------------------------------------------------------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
## Automating discover and swapping
|
||||
|
||||
The `buy-xmr` and `list-sellers` command have been designed to be composed.
|
||||
[This script](./discover_and_take.sh) is example of what can be done.
|
||||
Deciding on the seller to use is non-trivial to automate which is why it is not implemented as part of the tool.
|
||||
|
||||
## Tor
|
||||
|
||||
By default, the CLI will look for Tor at the default socks port `9050` and automatically route all traffic with a seller through Tor.
|
||||
This allows swapping with sellers that are only reachable with an onion address.
|
||||
|
||||
Disclaimer:
|
||||
Communication with public blockchain explorers (Electrum, public XMR nodes) currently goes through clearnet.
|
||||
For complete anonymity it is recommended to run your own blockchain nodes.
|
||||
Use `swap buy-xmr --help` to see configuration options.
|
28
docs/cli/discover_and_take.sh
Executable file
28
docs/cli/discover_and_take.sh
Executable file
@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
|
||||
CLI_PATH=$1
|
||||
YOUR_MONERO_ADDR=$2
|
||||
YOUR_BITCOIN_ADDR=$3
|
||||
|
||||
CLI_LIST_SELLERS="$CLI_PATH --testnet --json --debug list-sellers"
|
||||
echo "Requesting sellers with command: $CLI_LIST_SELLERS"
|
||||
echo
|
||||
|
||||
BEST_SELLER=$($CLI_LIST_SELLERS | jq -s -c 'min_by(.status .Online .price)' | jq -r '.multiaddr, (.status .Online .price), (.status .Online .min_quantity), (.status .Online .max_quantity)')
|
||||
read ADDR PRICE MIN MAX < <(echo $BEST_SELLER)
|
||||
|
||||
echo
|
||||
|
||||
echo "Seller with best price:"
|
||||
echo " multiaddr : $ADDR"
|
||||
echo " price : $PRICE sat"
|
||||
echo " min_quantity: $MIN sat"
|
||||
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"
|
||||
|
||||
echo "Starting swap with best seller using command $CLI_SWAP"
|
||||
echo
|
||||
$CLI_SWAP
|
@ -24,7 +24,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use swap::bitcoin::TxLock;
|
||||
use swap::cli::command::{parse_args_and_apply_defaults, Arguments, Command, ParseResult};
|
||||
use swap::cli::{list_sellers, EventLoop};
|
||||
use swap::cli::{list_sellers, EventLoop, SellerStatus};
|
||||
use swap::database::Database;
|
||||
use swap::env::Config;
|
||||
use swap::libp2p_ext::MultiAddrExt;
|
||||
@ -272,11 +272,11 @@ async fn main() -> Result<()> {
|
||||
cli::refund(swap_id, Arc::new(bitcoin_wallet), db, force).await??;
|
||||
}
|
||||
Command::ListSellers {
|
||||
rendezvous_node_addr,
|
||||
rendezvous_point,
|
||||
namespace,
|
||||
tor_socks5_port,
|
||||
} => {
|
||||
let rendezvous_node_peer_id = rendezvous_node_addr
|
||||
let rendezvous_node_peer_id = rendezvous_point
|
||||
.extract_peer_id()
|
||||
.context("Rendezvous node address must contain peer ID")?;
|
||||
|
||||
@ -285,14 +285,15 @@ 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_node_addr,
|
||||
rendezvous_point,
|
||||
namespace,
|
||||
tor_socks5_port,
|
||||
identity,
|
||||
)
|
||||
.await?;
|
||||
sellers.sort();
|
||||
|
||||
if json {
|
||||
for seller in sellers {
|
||||
@ -301,15 +302,37 @@ async fn main() -> Result<()> {
|
||||
} else {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec!["PRICE", "MIN_QUANTITY", "MAX_QUANTITY", "ADDRESS"]);
|
||||
table.set_header(vec![
|
||||
"PRICE",
|
||||
"MIN_QUANTITY",
|
||||
"MAX_QUANTITY",
|
||||
"STATUS",
|
||||
"ADDRESS",
|
||||
]);
|
||||
|
||||
for seller in sellers {
|
||||
table.add_row(vec![
|
||||
seller.quote.price.to_string(),
|
||||
seller.quote.min_quantity.to_string(),
|
||||
seller.quote.max_quantity.to_string(),
|
||||
seller.multiaddr.to_string(),
|
||||
]);
|
||||
let row = match seller.status {
|
||||
SellerStatus::Online(quote) => {
|
||||
vec![
|
||||
quote.price.to_string(),
|
||||
quote.min_quantity.to_string(),
|
||||
quote.max_quantity.to_string(),
|
||||
"Online".to_owned(),
|
||||
seller.multiaddr.to_string(),
|
||||
]
|
||||
}
|
||||
SellerStatus::Unreachable => {
|
||||
vec![
|
||||
"???".to_owned(),
|
||||
"???".to_owned(),
|
||||
"???".to_owned(),
|
||||
"Unreachable".to_owned(),
|
||||
seller.multiaddr.to_string(),
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
table.add_row(row);
|
||||
}
|
||||
|
||||
println!("{}", table);
|
||||
|
@ -10,14 +10,14 @@ pub mod transport;
|
||||
pub use behaviour::{Behaviour, OutEvent};
|
||||
pub use cancel::cancel;
|
||||
pub use event_loop::{EventLoop, EventLoopHandle};
|
||||
pub use list_sellers::list_sellers;
|
||||
pub use list_sellers::{list_sellers, Seller, Status as SellerStatus};
|
||||
pub use refund::refund;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::asb;
|
||||
use crate::cli::list_sellers::Seller;
|
||||
use crate::cli::list_sellers::{Seller, Status};
|
||||
use crate::network::quote;
|
||||
use crate::network::quote::BidQuote;
|
||||
use crate::network::rendezvous::XmrBtcNamespace;
|
||||
@ -122,7 +122,7 @@ mod tests {
|
||||
|
||||
Seller {
|
||||
multiaddr: asb_address.with(Protocol::P2p(asb_peer_id.into())),
|
||||
quote: static_quote,
|
||||
status: Status::Online(static_quote),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,7 +188,7 @@ where
|
||||
},
|
||||
},
|
||||
RawCommand::ListSellers {
|
||||
rendezvous_node_addr,
|
||||
rendezvous_point,
|
||||
tor: Tor { tor_socks5_port },
|
||||
} => Arguments {
|
||||
env_config: env_config_from(is_testnet),
|
||||
@ -196,7 +196,7 @@ where
|
||||
json,
|
||||
data_dir: data::data_dir_from(data, is_testnet)?,
|
||||
cmd: Command::ListSellers {
|
||||
rendezvous_node_addr,
|
||||
rendezvous_point,
|
||||
namespace: rendezvous_namespace_from(is_testnet),
|
||||
tor_socks5_port,
|
||||
},
|
||||
@ -238,7 +238,7 @@ pub enum Command {
|
||||
bitcoin_target_block: usize,
|
||||
},
|
||||
ListSellers {
|
||||
rendezvous_node_addr: Multiaddr,
|
||||
rendezvous_point: Multiaddr,
|
||||
namespace: XmrBtcNamespace,
|
||||
tor_socks5_port: u16,
|
||||
},
|
||||
@ -246,38 +246,38 @@ pub enum Command {
|
||||
|
||||
#[derive(structopt::StructOpt, Debug)]
|
||||
#[structopt(name = "swap", about = "CLI for swapping BTC for XMR", author)]
|
||||
pub struct RawArguments {
|
||||
struct RawArguments {
|
||||
// global is necessary to ensure that clap can match against testnet in subcommands
|
||||
#[structopt(
|
||||
long,
|
||||
help = "Swap on testnet and assume testnet defaults for data-dir and the blockchain related parameters",
|
||||
global = true
|
||||
)]
|
||||
pub testnet: bool,
|
||||
testnet: bool,
|
||||
|
||||
#[structopt(
|
||||
long = "--data-dir",
|
||||
help = "Provide the data directory path to be used to store application data using testnet and mainnet as subfolder"
|
||||
long = "--data-base-dir",
|
||||
help = "The base data directory to be used for mainnet / testnet specific data like database, wallets etc"
|
||||
)]
|
||||
pub data: Option<PathBuf>,
|
||||
data: Option<PathBuf>,
|
||||
|
||||
#[structopt(long, help = "Activate debug logging.")]
|
||||
pub debug: bool,
|
||||
#[structopt(long, help = "Activate debug logging")]
|
||||
debug: bool,
|
||||
|
||||
#[structopt(
|
||||
short,
|
||||
long = "json",
|
||||
help = "Changes the log messages to json vs plain-text. This can be helpful to simplify automated log analyses."
|
||||
help = "Outputs all logs in JSON format instead of plain text"
|
||||
)]
|
||||
pub json: bool,
|
||||
json: bool,
|
||||
|
||||
#[structopt(subcommand)]
|
||||
pub cmd: RawCommand,
|
||||
cmd: RawCommand,
|
||||
}
|
||||
|
||||
#[derive(structopt::StructOpt, Debug)]
|
||||
pub enum RawCommand {
|
||||
/// Start a XMR for BTC swap
|
||||
enum RawCommand {
|
||||
/// Start a BTC for XMR swap
|
||||
BuyXmr {
|
||||
#[structopt(flatten)]
|
||||
seller: Seller,
|
||||
@ -303,7 +303,7 @@ pub enum RawCommand {
|
||||
#[structopt(flatten)]
|
||||
tor: Tor,
|
||||
},
|
||||
/// Show a list of past ongoing and completed swaps
|
||||
/// Show a list of past, ongoing and completed swaps
|
||||
History,
|
||||
/// Resume a swap
|
||||
Resume {
|
||||
@ -330,7 +330,7 @@ pub enum RawCommand {
|
||||
#[structopt(flatten)]
|
||||
bitcoin: Bitcoin,
|
||||
},
|
||||
/// Try to cancel a swap and refund my BTC (expert users only)
|
||||
/// Try to cancel a swap and refund the BTC (expert users only)
|
||||
Refund {
|
||||
#[structopt(flatten)]
|
||||
swap_id: SwapId,
|
||||
@ -341,13 +341,14 @@ pub enum RawCommand {
|
||||
#[structopt(flatten)]
|
||||
bitcoin: Bitcoin,
|
||||
},
|
||||
/// Discover and list sellers (i.e. ASB providers)
|
||||
ListSellers {
|
||||
#[structopt(
|
||||
long,
|
||||
help = "The multiaddr (including peer-id) of a rendezvous node that sellers register with",
|
||||
help = "Address of the rendezvous point you want to use to discover ASBs",
|
||||
default_value = DEFAULT_RENDEZVOUS_ADDRESS
|
||||
)]
|
||||
rendezvous_node_addr: Multiaddr,
|
||||
rendezvous_point: Multiaddr,
|
||||
|
||||
#[structopt(flatten)]
|
||||
tor: Tor,
|
||||
@ -355,75 +356,66 @@ pub enum RawCommand {
|
||||
}
|
||||
|
||||
#[derive(structopt::StructOpt, Debug)]
|
||||
pub struct Monero {
|
||||
struct Monero {
|
||||
#[structopt(
|
||||
long = "monero-daemon-address",
|
||||
help = "Specify to connect to a monero daemon of your choice: <host>:<port>"
|
||||
)]
|
||||
pub monero_daemon_address: Option<String>,
|
||||
monero_daemon_address: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(structopt::StructOpt, Debug)]
|
||||
pub struct Bitcoin {
|
||||
struct Bitcoin {
|
||||
#[structopt(long = "electrum-rpc", help = "Provide the Bitcoin Electrum RPC URL")]
|
||||
pub bitcoin_electrum_rpc_url: Option<Url>,
|
||||
bitcoin_electrum_rpc_url: Option<Url>,
|
||||
|
||||
#[structopt(
|
||||
long = "bitcoin-target-block",
|
||||
help = "Use for fee estimation, decides within how many blocks the Bitcoin transactions should be confirmed."
|
||||
help = "Estimate Bitcoin fees such that transactions are confirmed within the specified number of blocks"
|
||||
)]
|
||||
pub bitcoin_target_block: Option<usize>,
|
||||
bitcoin_target_block: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(structopt::StructOpt, Debug)]
|
||||
pub struct Tor {
|
||||
struct Tor {
|
||||
#[structopt(
|
||||
long = "tor-socks5-port",
|
||||
help = "Your local Tor socks5 proxy port",
|
||||
default_value = DEFAULT_TOR_SOCKS5_PORT
|
||||
)]
|
||||
pub tor_socks5_port: u16,
|
||||
tor_socks5_port: u16,
|
||||
}
|
||||
|
||||
#[derive(structopt::StructOpt, Debug)]
|
||||
pub struct SwapId {
|
||||
struct SwapId {
|
||||
#[structopt(
|
||||
long = "swap-id",
|
||||
help = "The swap id can be retrieved using the history subcommand"
|
||||
)]
|
||||
pub swap_id: Uuid,
|
||||
swap_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(structopt::StructOpt, Debug)]
|
||||
pub struct Seller {
|
||||
struct Seller {
|
||||
#[structopt(
|
||||
long,
|
||||
help = "The seller's address. Must include a peer ID part, i.e. `/p2p/`"
|
||||
)]
|
||||
pub seller: Multiaddr,
|
||||
seller: Multiaddr,
|
||||
}
|
||||
|
||||
mod data {
|
||||
use super::*;
|
||||
|
||||
pub fn data_dir_from(arg_dir: Option<PathBuf>, testnet: bool) -> Result<PathBuf> {
|
||||
let dir = if let Some(dir) = arg_dir {
|
||||
dir
|
||||
} else if testnet {
|
||||
testnet_default()?
|
||||
} else {
|
||||
mainnet_default()?
|
||||
let base_dir = match arg_dir {
|
||||
Some(custom_base_dir) => custom_base_dir,
|
||||
None => os_default()?,
|
||||
};
|
||||
|
||||
Ok(dir)
|
||||
}
|
||||
let sub_directory = if testnet { "testnet" } else { "mainnet" };
|
||||
|
||||
fn testnet_default() -> Result<PathBuf> {
|
||||
Ok(os_default()?.join("testnet"))
|
||||
}
|
||||
|
||||
fn mainnet_default() -> Result<PathBuf> {
|
||||
Ok(os_default()?.join("mainnet"))
|
||||
Ok(base_dir.join(sub_directory))
|
||||
}
|
||||
|
||||
fn os_default() -> Result<PathBuf> {
|
||||
@ -699,7 +691,7 @@ mod tests {
|
||||
|
||||
let raw_ars = vec![
|
||||
BINARY_NAME,
|
||||
"--data-dir",
|
||||
"--data-base-dir",
|
||||
data_dir,
|
||||
"buy-xmr",
|
||||
"--change-address",
|
||||
@ -716,14 +708,14 @@ mod tests {
|
||||
args,
|
||||
ParseResult::Arguments(
|
||||
Arguments::buy_xmr_mainnet_defaults()
|
||||
.with_data_dir(PathBuf::from_str(data_dir).unwrap())
|
||||
.with_data_dir(PathBuf::from_str(data_dir).unwrap().join("mainnet"))
|
||||
)
|
||||
);
|
||||
|
||||
let raw_ars = vec![
|
||||
BINARY_NAME,
|
||||
"--testnet",
|
||||
"--data-dir",
|
||||
"--data-base-dir",
|
||||
data_dir,
|
||||
"buy-xmr",
|
||||
"--change-address",
|
||||
@ -740,13 +732,13 @@ mod tests {
|
||||
args,
|
||||
ParseResult::Arguments(
|
||||
Arguments::buy_xmr_testnet_defaults()
|
||||
.with_data_dir(PathBuf::from_str(data_dir).unwrap())
|
||||
.with_data_dir(PathBuf::from_str(data_dir).unwrap().join("testnet"))
|
||||
)
|
||||
);
|
||||
|
||||
let raw_ars = vec![
|
||||
BINARY_NAME,
|
||||
"--data-dir",
|
||||
"--data-base-dir",
|
||||
data_dir,
|
||||
"resume",
|
||||
"--swap-id",
|
||||
@ -759,14 +751,14 @@ mod tests {
|
||||
args,
|
||||
ParseResult::Arguments(
|
||||
Arguments::resume_mainnet_defaults()
|
||||
.with_data_dir(PathBuf::from_str(data_dir).unwrap())
|
||||
.with_data_dir(PathBuf::from_str(data_dir).unwrap().join("mainnet"))
|
||||
)
|
||||
);
|
||||
|
||||
let raw_ars = vec![
|
||||
BINARY_NAME,
|
||||
"--testnet",
|
||||
"--data-dir",
|
||||
"--data-base-dir",
|
||||
data_dir,
|
||||
"resume",
|
||||
"--swap-id",
|
||||
@ -779,7 +771,7 @@ mod tests {
|
||||
args,
|
||||
ParseResult::Arguments(
|
||||
Arguments::resume_testnet_defaults()
|
||||
.with_data_dir(PathBuf::from_str(data_dir).unwrap())
|
||||
.with_data_dir(PathBuf::from_str(data_dir).unwrap().join("testnet"))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ use libp2p::swarm::SwarmEvent;
|
||||
use libp2p::{identity, rendezvous, Multiaddr, PeerId, Swarm};
|
||||
use serde::Serialize;
|
||||
use serde_with::{serde_as, DisplayFromStr};
|
||||
use std::collections::hash_map::Entry;
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
@ -52,11 +53,17 @@ pub async fn list_sellers(
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Serialize, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Serialize, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
pub struct Seller {
|
||||
pub status: Status,
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
pub multiaddr: Multiaddr,
|
||||
pub quote: BidQuote,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Eq, Hash, Copy, Clone, Ord, PartialOrd)]
|
||||
pub enum Status {
|
||||
Online(BidQuote),
|
||||
Unreachable,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -90,7 +97,7 @@ struct Behaviour {
|
||||
#[derive(Debug)]
|
||||
enum QuoteStatus {
|
||||
Pending,
|
||||
Received(BidQuote),
|
||||
Received(Status),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -104,7 +111,8 @@ struct EventLoop {
|
||||
rendezvous_peer_id: PeerId,
|
||||
rendezvous_addr: Multiaddr,
|
||||
namespace: XmrBtcNamespace,
|
||||
asb_address: HashMap<PeerId, Multiaddr>,
|
||||
reachable_asb_address: HashMap<PeerId, Multiaddr>,
|
||||
unreachable_asb_address: HashMap<PeerId, Multiaddr>,
|
||||
asb_quote_status: HashMap<PeerId, QuoteStatus>,
|
||||
state: State,
|
||||
}
|
||||
@ -121,7 +129,8 @@ impl EventLoop {
|
||||
rendezvous_peer_id,
|
||||
rendezvous_addr,
|
||||
namespace,
|
||||
asb_address: Default::default(),
|
||||
reachable_asb_address: Default::default(),
|
||||
unreachable_asb_address: Default::default(),
|
||||
asb_quote_status: Default::default(),
|
||||
state: State::WaitForDiscovery,
|
||||
}
|
||||
@ -147,7 +156,7 @@ impl EventLoop {
|
||||
);
|
||||
} else {
|
||||
let address = endpoint.get_remote_address();
|
||||
self.asb_address.insert(peer_id, address.clone());
|
||||
self.reachable_asb_address.insert(peer_id, address.clone());
|
||||
}
|
||||
}
|
||||
SwarmEvent::UnreachableAddr { peer_id, error, address, .. } => {
|
||||
@ -166,9 +175,16 @@ impl EventLoop {
|
||||
address,
|
||||
error
|
||||
);
|
||||
self.unreachable_asb_address.insert(peer_id, address.clone());
|
||||
|
||||
// if a different peer than the rendezvous node is unreachable (i.e. a seller) we remove that seller from the quote status state
|
||||
self.asb_quote_status.remove(&peer_id);
|
||||
match self.asb_quote_status.entry(peer_id) {
|
||||
Entry::Occupied(mut entry) => {
|
||||
entry.insert(QuoteStatus::Received(Status::Unreachable));
|
||||
},
|
||||
_ => {
|
||||
tracing::debug!(%peer_id, %error, "Connection error with unexpected peer")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SwarmEvent::Behaviour(OutEvent::Rendezvous(
|
||||
@ -205,7 +221,7 @@ impl EventLoop {
|
||||
RequestResponseEvent::Message { peer, message } => {
|
||||
match message {
|
||||
RequestResponseMessage::Response { response, .. } => {
|
||||
if self.asb_quote_status.insert(peer, QuoteStatus::Received(response)).is_none() {
|
||||
if self.asb_quote_status.insert(peer, QuoteStatus::Received(Status::Online(response))).is_none() {
|
||||
tracing::error!(%peer, "Received bid quote from unexpected peer, this record will be removed!");
|
||||
self.asb_quote_status.remove(&peer);
|
||||
}
|
||||
@ -247,15 +263,26 @@ impl EventLoop {
|
||||
.iter()
|
||||
.map(|(peer_id, quote_status)| match quote_status {
|
||||
QuoteStatus::Pending => Err(StillPending {}),
|
||||
QuoteStatus::Received(quote) => {
|
||||
QuoteStatus::Received(Status::Online(quote)) => {
|
||||
let address = self
|
||||
.asb_address
|
||||
.reachable_asb_address
|
||||
.get(&peer_id)
|
||||
.expect("if we got a quote we must have stored an address");
|
||||
|
||||
Ok(Seller {
|
||||
multiaddr: address.clone(),
|
||||
quote: *quote,
|
||||
status: Status::Online(*quote),
|
||||
})
|
||||
}
|
||||
QuoteStatus::Received(Status::Unreachable) => {
|
||||
let address = self
|
||||
.unreachable_asb_address
|
||||
.get(&peer_id)
|
||||
.expect("if we got a quote we must have stored an address");
|
||||
|
||||
Ok(Seller {
|
||||
multiaddr: address.clone(),
|
||||
status: Status::Unreachable,
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -279,3 +306,51 @@ impl From<PingEvent> for OutEvent {
|
||||
OutEvent::Ping(event)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn sellers_sort_with_unreachable_coming_last() {
|
||||
let mut list = vec![
|
||||
Seller {
|
||||
multiaddr: "/ip4/127.0.0.1/tcp/1234".parse().unwrap(),
|
||||
status: Status::Unreachable,
|
||||
},
|
||||
Seller {
|
||||
multiaddr: Multiaddr::empty(),
|
||||
status: Status::Unreachable,
|
||||
},
|
||||
Seller {
|
||||
multiaddr: "/ip4/127.0.0.1/tcp/5678".parse().unwrap(),
|
||||
status: Status::Online(BidQuote {
|
||||
price: Default::default(),
|
||||
min_quantity: Default::default(),
|
||||
max_quantity: Default::default(),
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
list.sort();
|
||||
|
||||
assert_eq!(list, vec![
|
||||
Seller {
|
||||
multiaddr: "/ip4/127.0.0.1/tcp/5678".parse().unwrap(),
|
||||
status: Status::Online(BidQuote {
|
||||
price: Default::default(),
|
||||
min_quantity: Default::default(),
|
||||
max_quantity: Default::default(),
|
||||
})
|
||||
},
|
||||
Seller {
|
||||
multiaddr: Multiaddr::empty(),
|
||||
status: Status::Unreachable
|
||||
},
|
||||
Seller {
|
||||
multiaddr: "/ip4/127.0.0.1/tcp/1234".parse().unwrap(),
|
||||
status: Status::Unreachable
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,8 @@ pub fn init(debug: bool, json: bool, dir: impl AsRef<Path>, swap_id: Option<Uuid
|
||||
.with_env_filter(format!("swap={}", level))
|
||||
.with_writer(std::io::stderr)
|
||||
.with_ansi(is_terminal)
|
||||
.with_timer(ChronoLocal::with_format("%F %T".to_owned()))
|
||||
.with_level(false)
|
||||
.without_time()
|
||||
.with_target(false)
|
||||
.init();
|
||||
|
||||
|
@ -24,7 +24,7 @@ impl ProtocolName for BidQuoteProtocol {
|
||||
}
|
||||
|
||||
/// Represents a quote for buying XMR.
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
pub struct BidQuote {
|
||||
/// The price at which the maker is willing to buy at.
|
||||
#[serde(with = "::bitcoin::util::amount::serde::as_sat")]
|
||||
|
Loading…
Reference in New Issue
Block a user