diff --git a/api/api.go b/api/api.go
index 47ca130bb..38ec705bc 100644
--- a/api/api.go
+++ b/api/api.go
@@ -810,10 +810,18 @@ func (api *api) RefundSwap(refundSwapRequest *RefundSwapRequest) error {
}
func (api *api) GetAutoSwapConfig() (*GetAutoSwapConfigResponse, error) {
+ if api.svc.GetSwapsService() == nil {
+ return nil, errors.New("SwapsService not started")
+ }
+
swapOutBalanceThresholdStr, _ := api.cfg.Get(config.AutoSwapBalanceThresholdKey, "")
swapOutAmountStr, _ := api.cfg.Get(config.AutoSwapAmountKey, "")
swapOutDestination, _ := api.cfg.Get(config.AutoSwapDestinationKey, "")
+ if xpub := api.svc.GetSwapsService().GetDecryptedAutoSwapXpub(); xpub != "" {
+ swapOutDestination = xpub
+ }
+
swapOutEnabled := swapOutBalanceThresholdStr != "" && swapOutAmountStr != ""
var swapOutBalanceThreshold, swapOutAmount uint64
if swapOutEnabled {
@@ -984,6 +992,30 @@ func (api *api) InitiateSwapIn(ctx context.Context, initiateSwapInRequest *Initi
}
func (api *api) EnableAutoSwapOut(ctx context.Context, enableAutoSwapsRequest *EnableAutoSwapRequest) error {
+ if api.svc.GetSwapsService() == nil {
+ return errors.New("SwapsService not started")
+ }
+
+ encryptionKey := ""
+ if enableAutoSwapsRequest.Destination != "" {
+ switch enableAutoSwapsRequest.DestinationType {
+ case "address":
+ if err := api.svc.GetSwapsService().ValidateAddress(enableAutoSwapsRequest.Destination); err != nil {
+ return err
+ }
+ case "xpub":
+ if !api.cfg.CheckUnlockPassword(enableAutoSwapsRequest.UnlockPassword) {
+ return errors.New("invalid unlock password")
+ }
+ if err := api.svc.GetSwapsService().ValidateXpub(enableAutoSwapsRequest.Destination); err != nil {
+ return err
+ }
+ encryptionKey = enableAutoSwapsRequest.UnlockPassword
+ default:
+ return errors.New("destination type must be address or xpub")
+ }
+ }
+
err := api.cfg.SetUpdate(config.AutoSwapBalanceThresholdKey, strconv.FormatUint(enableAutoSwapsRequest.BalanceThreshold, 10), "")
if err != nil {
logger.Logger.WithError(err).Error("Failed to save autoswap balance threshold to config")
@@ -996,16 +1028,13 @@ func (api *api) EnableAutoSwapOut(ctx context.Context, enableAutoSwapsRequest *E
return err
}
- err = api.cfg.SetUpdate(config.AutoSwapDestinationKey, enableAutoSwapsRequest.Destination, "")
+ err = api.cfg.SetUpdate(config.AutoSwapDestinationKey, enableAutoSwapsRequest.Destination, encryptionKey)
if err != nil {
logger.Logger.WithError(err).Error("Failed to save autoswap destination to config")
return err
}
- if api.svc.GetSwapsService() == nil {
- return errors.New("SwapsService not started")
- }
- return api.svc.GetSwapsService().EnableAutoSwapOut()
+ return api.svc.GetSwapsService().EnableAutoSwapOut(enableAutoSwapsRequest.UnlockPassword)
}
func (api *api) DisableAutoSwap() error {
diff --git a/api/models.go b/api/models.go
index b4c77accf..4d2151253 100644
--- a/api/models.go
+++ b/api/models.go
@@ -168,6 +168,8 @@ type EnableAutoSwapRequest struct {
BalanceThreshold uint64 `json:"balanceThreshold"`
SwapAmount uint64 `json:"swapAmount"`
Destination string `json:"destination"`
+ DestinationType string `json:"destinationType"`
+ UnlockPassword string `json:"unlockPassword"`
}
type GetAutoSwapConfigResponse struct {
diff --git a/frontend/src/screens/wallet/swap/AutoSwap.tsx b/frontend/src/screens/wallet/swap/AutoSwap.tsx
index 19803fe52..8b56c5c63 100644
--- a/frontend/src/screens/wallet/swap/AutoSwap.tsx
+++ b/frontend/src/screens/wallet/swap/AutoSwap.tsx
@@ -2,6 +2,7 @@ import {
ArrowDownUpIcon,
ClipboardPasteIcon,
ClockIcon,
+ CopyIcon,
MoveRightIcon,
XCircleIcon,
} from "lucide-react";
@@ -11,14 +12,24 @@ import AppHeader from "src/components/AppHeader";
import ExternalLink from "src/components/ExternalLink";
import { FormattedBitcoinAmount } from "src/components/FormattedBitcoinAmount";
import Loading from "src/components/Loading";
+import PasswordInput from "src/components/password/PasswordInput";
import ResponsiveLinkButton from "src/components/ResponsiveLinkButton";
+import {
+ AlertDialog,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from "src/components/ui/alert-dialog";
import { Button } from "src/components/ui/button";
import { LoadingButton } from "src/components/ui/custom/loading-button";
import { Input } from "src/components/ui/input";
import { Label } from "src/components/ui/label";
import { RadioGroup, RadioGroupItem } from "src/components/ui/radio-group";
-import { useBalances } from "src/hooks/useBalances";
import { useAutoSwapsConfig, useSwapInfo } from "src/hooks/useSwaps";
+import { copyToClipboard } from "src/lib/clipboard";
import { AutoSwapConfig } from "src/types";
import { request } from "src/utils/request";
@@ -54,7 +65,6 @@ export default function AutoSwap() {
}
function AutoSwapOutForm() {
- const { data: balances } = useBalances();
const { mutate } = useAutoSwapsConfig();
const { data: swapInfo } = useSwapInfo("out");
@@ -65,18 +75,35 @@ function AutoSwapOutForm() {
const [externalType, setExternalType] = useState<"address" | "xpub">(
"address"
);
+ const [unlockPassword, setUnlockPassword] = useState("");
+ const [showUnlockPasswordDialog, setShowUnlockPasswordDialog] =
+ useState(false);
const [loading, setLoading] = useState(false);
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault();
- if (swapAmount > balanceThreshold) {
+ if (Number(swapAmount) > Number(balanceThreshold)) {
toast.info(
"Balance threshold must be greater than or equal to swap amount"
);
return;
}
+ if (externalType === "xpub" && !isInternalSwap) {
+ setShowUnlockPasswordDialog(true);
+ return;
+ }
+
+ await submitAutoSwap();
+ };
+
+ const onConfirmUnlockPassword = async (e: React.FormEvent) => {
+ e.preventDefault();
+ await submitAutoSwap(unlockPassword);
+ };
+
+ const submitAutoSwap = async (password?: string) => {
try {
setLoading(true);
await request("/api/autoswap", {
@@ -88,8 +115,12 @@ function AutoSwapOutForm() {
swapAmount: parseInt(swapAmount),
balanceThreshold: parseInt(balanceThreshold),
destination,
+ destinationType: !isInternalSwap ? externalType : undefined,
+ unlockPassword: password,
}),
});
+ setUnlockPassword("");
+ setShowUnlockPasswordDialog(false);
toast("Auto swap enabled successfully");
await mutate();
} catch (error) {
@@ -106,198 +137,238 @@ function AutoSwapOutForm() {
setDestination(text.trim());
};
- if (!balances || !swapInfo) {
+ if (!swapInfo) {
return