diff --git a/CHANGELOG.md b/CHANGELOG.md
index e110b501..bd4cec22 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- ASB + CLI: You can now use the `logs` command to retrieve logs stored in the past, redacting addresses and id's using `logs --redact`.
- ASB: The `--disable-timestamp` flag has been removed
- Introduced a cooperative Monero redeem feature for Bob to request from Alice if Bob is punished for not refunding in time. Alice can choose to cooperate but is not obligated to do so. This change is backwards compatible. To attempt recovery, resume a swap in the "Bitcoin punished" state. Success depends on Alice being active and still having a record of the swap. Note that Alice's cooperation is voluntary and recovery is not guaranteed
+- CLI: `--change-address` can now be omitted. In that case, any change is refunded to the internal bitcoin wallet.
## [0.13.2] - 2024-07-02
diff --git a/docs/cli/README.md b/docs/cli/README.md
index d95d90ef..eff9d072 100644
--- a/docs/cli/README.md
+++ b/docs/cli/README.md
@@ -75,7 +75,7 @@ OPTIONS:
This command has three core options:
-- `--change-address`: A Bitcoin address you control. Will be used for refunds of any kind.
+- `--change-address`: A Bitcoin address you control. Will be used for refunds of any kind. You can also omit this flag which will refund any change to the internal wallet.
- `--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.
diff --git a/src-gui/src/renderer/components/modal/swap/pages/init/InitPage.tsx b/src-gui/src/renderer/components/modal/swap/pages/init/InitPage.tsx
index fbd80111..0836ea73 100644
--- a/src-gui/src/renderer/components/modal/swap/pages/init/InitPage.tsx
+++ b/src-gui/src/renderer/components/modal/swap/pages/init/InitPage.tsx
@@ -1,4 +1,11 @@
-import { Box, DialogContentText, makeStyles } from "@material-ui/core";
+import {
+ Box,
+ makeStyles,
+ Paper,
+ Tab,
+ Tabs,
+ Typography,
+} from "@material-ui/core";
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
import { useState } from "react";
import BitcoinAddressTextField from "renderer/components/inputs/BitcoinAddressTextField";
@@ -21,61 +28,90 @@ const useStyles = makeStyles((theme) => ({
export default function InitPage() {
const classes = useStyles();
+
const [redeemAddress, setRedeemAddress] = useState("");
const [refundAddress, setRefundAddress] = useState("");
+ const [useExternalRefundAddress, setUseExternalRefundAddress] =
+ useState(false);
+
const [redeemAddressValid, setRedeemAddressValid] = useState(false);
const [refundAddressValid, setRefundAddressValid] = useState(false);
+
const selectedProvider = useAppSelector(
(state) => state.providers.selectedProvider,
);
async function init() {
- await buyXmr(selectedProvider, refundAddress, redeemAddress);
+ await buyXmr(
+ selectedProvider,
+ useExternalRefundAddress ? refundAddress : null,
+ redeemAddress,
+ );
}
return (
-
- Please specify the address to which the Monero should be sent upon
- completion of the swap and the address for receiving a Bitcoin refund if
- the swap fails.
-
-
-
+
+
+ setUseExternalRefundAddress(newValue === 1)
+ }
+ >
+
+
+
+
+ {useExternalRefundAddress ? (
+
+ ) : (
+
+ In case something goes wrong, the Bitcoin will be refunded to
+ the internal Bitcoin wallet of the GUI. You can then withdraw
+ them from there or use them for another swap directly.
+
+ )}
+
+
+
+
+ }
+ onInvoke={init}
+ displayErrorSnackbar
+ >
+ Request quote and start swap
+
-
- }
- onInvoke={init}
- displayErrorSnackbar
- >
- Start swap
-
);
}
diff --git a/src-gui/src/renderer/rpc.ts b/src-gui/src/renderer/rpc.ts
index 241d8774..ee878521 100644
--- a/src-gui/src/renderer/rpc.ts
+++ b/src-gui/src/renderer/rpc.ts
@@ -89,14 +89,22 @@ export async function withdrawBtc(address: string): Promise {
export async function buyXmr(
seller: Provider,
- bitcoin_change_address: string,
+ bitcoin_change_address: string | null,
monero_receive_address: string,
) {
- await invoke("buy_xmr", {
- seller: providerToConcatenatedMultiAddr(seller),
- bitcoin_change_address,
- monero_receive_address,
- });
+ await invoke(
+ "buy_xmr",
+ bitcoin_change_address == null
+ ? {
+ seller: providerToConcatenatedMultiAddr(seller),
+ monero_receive_address,
+ }
+ : {
+ seller: providerToConcatenatedMultiAddr(seller),
+ monero_receive_address,
+ bitcoin_change_address,
+ },
+ );
}
export async function resumeSwap(swapId: string) {
diff --git a/swap/src/cli/api.rs b/swap/src/cli/api.rs
index df1be01a..3a2da19b 100644
--- a/swap/src/cli/api.rs
+++ b/swap/src/cli/api.rs
@@ -415,6 +415,10 @@ impl Context {
Ok(())
}
+
+ pub fn bitcoin_wallet(&self) -> Option> {
+ self.bitcoin_wallet.clone()
+ }
}
impl fmt::Debug for Context {
diff --git a/swap/src/cli/api/request.rs b/swap/src/cli/api/request.rs
index 34c6f5ae..85b8cf5c 100644
--- a/swap/src/cli/api/request.rs
+++ b/swap/src/cli/api/request.rs
@@ -44,8 +44,8 @@ pub trait Request {
pub struct BuyXmrArgs {
#[typeshare(serialized_as = "string")]
pub seller: Multiaddr,
- #[typeshare(serialized_as = "string")]
- pub bitcoin_change_address: bitcoin::Address,
+ #[typeshare(serialized_as = "Option")]
+ pub bitcoin_change_address: Option,
#[typeshare(serialized_as = "string")]
pub monero_receive_address: monero::Address,
}
@@ -545,6 +545,21 @@ pub async fn buy_xmr(
.as_ref()
.expect("Could not find Bitcoin wallet"),
);
+
+ let bitcoin_change_address = match bitcoin_change_address {
+ Some(addr) => addr,
+ None => {
+ let internal_wallet_address = bitcoin_wallet.new_address().await?;
+
+ tracing::info!(
+ internal_wallet_address=%internal_wallet_address,
+ "No --change-address supplied. Any change will be received to the internal wallet."
+ );
+
+ internal_wallet_address
+ }
+ };
+
let monero_wallet = Arc::clone(
context
.monero_wallet
diff --git a/swap/src/cli/command.rs b/swap/src/cli/command.rs
index 9bec6db3..190ec5ba 100644
--- a/swap/src/cli/command.rs
+++ b/swap/src/cli/command.rs
@@ -78,8 +78,10 @@ where
} => {
let monero_receive_address =
monero_address::validate_is_testnet(monero_receive_address, is_testnet)?;
- let bitcoin_change_address =
- bitcoin_address::validate_is_testnet(bitcoin_change_address, is_testnet)?;
+
+ let bitcoin_change_address = bitcoin_change_address
+ .map(|address| bitcoin_address::validate_is_testnet(address, is_testnet))
+ .transpose()?;
let context = Arc::new(
ContextBuilder::new(is_testnet)
@@ -372,10 +374,10 @@ enum CliCommand {
#[structopt(
long = "change-address",
- help = "The bitcoin address where any form of change or excess funds should be sent to",
+ help = "The bitcoin address where any form of change or excess funds should be sent to. If omitted they will be sent to the internal wallet.",
parse(try_from_str = bitcoin_address::parse)
)]
- bitcoin_change_address: bitcoin::Address,
+ bitcoin_change_address: Option,
#[structopt(flatten)]
monero: Monero,
diff --git a/swap/src/rpc/methods.rs b/swap/src/rpc/methods.rs
index f871d0d6..d31b35f7 100644
--- a/swap/src/rpc/methods.rs
+++ b/swap/src/rpc/methods.rs
@@ -90,11 +90,13 @@ pub fn register_modules(outer_context: Context) -> Result> {
module.register_async_method("buy_xmr", |params_raw, context| async move {
let mut params: BuyXmrArgs = params_raw.parse()?;
- params.bitcoin_change_address = bitcoin_address::validate(
- params.bitcoin_change_address,
- context.config.env_config.bitcoin_network,
- )
- .to_jsonrpsee_result()?;
+ params.bitcoin_change_address = params
+ .bitcoin_change_address
+ .map(|address| {
+ bitcoin_address::validate(address, context.config.env_config.bitcoin_network)
+ })
+ .transpose()
+ .to_jsonrpsee_result()?;
params.monero_receive_address = monero_address::validate(
params.monero_receive_address,