This commit is contained in:
Mohan 2025-10-28 00:33:47 +01:00 committed by GitHub
parent 6db365bf23
commit c3f3623b6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 375 additions and 221 deletions

View file

@ -3,11 +3,11 @@ import { TauriEvent } from "models/tauriModel";
import {
contextStatusEventReceived,
contextInitializationFailed,
rpcSetBalance,
timelockChangeEventReceived,
approvalEventReceived,
backgroundProgressEventReceived,
} from "store/features/rpcSlice";
import { setBitcoinBalance } from "store/features/bitcoinWalletSlice";
import { receivedCliLog } from "store/features/logsSlice";
import { poolStatusReceived } from "store/features/poolSlice";
import { swapProgressEventReceived } from "store/features/swapSlice";
@ -118,7 +118,7 @@ listen<TauriEvent>(TAURI_UNIFIED_EVENT_CHANNEL_NAME, (event) => {
break;
case "BalanceChange":
store.dispatch(rpcSetBalance(eventData.balance));
store.dispatch(setBitcoinBalance(eventData.balance));
break;
case "SwapDatabaseStateUpdate":

View file

@ -1,30 +0,0 @@
import { Button } from "@mui/material";
import Alert from "@mui/material/Alert";
import { useNavigate } from "react-router-dom";
import { useAppSelector } from "store/hooks";
export default function FundsLeftInWalletAlert() {
const fundsLeft = useAppSelector((state) => state.rpc.state.balance);
const navigate = useNavigate();
if (fundsLeft != null && fundsLeft > 0) {
return (
<Alert
variant="filled"
severity="info"
action={
<Button
variant="outlined"
size="small"
onClick={() => navigate("/bitcoin-wallet")}
>
View
</Button>
}
>
There are some Bitcoin left in your wallet
</Alert>
);
}
return null;
}

View file

@ -14,7 +14,7 @@ export default function AddressInputPage({
<>
<DialogContentText>
To withdraw the Bitcoin inside the internal wallet, please enter an
address. All funds will be sent to that address.
address. All funds (the entire balance) will be sent to that address.
</DialogContentText>
<BitcoinAddressTextField
@ -22,6 +22,7 @@ export default function AddressInputPage({
onAddressChange={setWithdrawAddress}
onAddressValidityChange={setWithdrawAddressValid}
helperText="All Bitcoin of the internal wallet will be transferred to this address"
allowEmpty={false}
fullWidth
/>
</>

View file

@ -1,6 +1,5 @@
import { Box, Tooltip } from "@mui/material";
import { BackgroundProgressAlerts } from "../alert/DaemonStatusAlert";
import FundsLeftInWalletAlert from "../alert/FundsLeftInWalletAlert";
import UnfinishedSwapsAlert from "../alert/UnfinishedSwapsAlert";
import BackgroundRefundAlert from "../alert/BackgroundRefundAlert";
import ContactInfoBox from "../other/ContactInfoBox";
@ -15,7 +14,6 @@ export default function NavigationFooter() {
gap: 1,
}}
>
<FundsLeftInWalletAlert />
<UnfinishedSwapsAlert />
<BackgroundRefundAlert />
<BackgroundProgressAlerts />

View file

@ -92,41 +92,44 @@ export default function ActionableMonospaceTextBox({
>
<Box
sx={{
display: "flex",
alignItems: "center",
cursor: "pointer",
filter: spoilerText && !isRevealed ? "blur(8px)" : "none",
transition: "filter 0.3s ease",
}}
>
<Box sx={{ flexGrow: 1 }} onClick={handleCopy}>
<MonospaceTextBox light={light}>
{content}
{displayCopyIcon && (
<IconButton
onClick={handleCopy}
size="small"
sx={{ marginLeft: 1 }}
>
<MonospaceTextBox
light={light}
actions={
<>
{displayCopyIcon && (
<Tooltip title="Copy to clipboard" arrow>
<IconButton onClick={handleCopy} size="small">
<FileCopyOutlined />
</IconButton>
</Tooltip>
)}
{enableQrCode && (
<Tooltip title="Show QR Code" arrow>
<IconButton
onClick={() => setQrCodeOpen(true)}
onClick={(e) => {
e.stopPropagation();
setQrCodeOpen(true);
}}
onMouseEnter={() => setIsQrCodeButtonHovered(true)}
onMouseLeave={() => setIsQrCodeButtonHovered(false)}
size="small"
sx={{ marginLeft: 1 }}
>
<QrCodeIcon />
</IconButton>
</Tooltip>
)}
</>
}
>
{content}
</MonospaceTextBox>
</Box>
</Box>
</Tooltip>
{spoilerText && !isRevealed && (

View file

@ -3,18 +3,25 @@ import { Box, Typography } from "@mui/material";
type Props = {
children: React.ReactNode;
light?: boolean;
actions?: React.ReactNode;
};
export default function MonospaceTextBox({ children, light = false }: Props) {
export default function MonospaceTextBox({
children,
light = false,
actions,
}: Props) {
return (
<Box
sx={(theme) => ({
display: "flex",
alignItems: "center",
justifyContent: "space-between",
backgroundColor: light ? "transparent" : theme.palette.grey[900],
borderRadius: 2,
border: light ? `1px solid ${theme.palette.grey[800]}` : "none",
padding: theme.spacing(1),
gap: 1,
})}
>
<Typography
@ -25,12 +32,14 @@ export default function MonospaceTextBox({ children, light = false }: Props) {
whiteSpace: "pre-wrap",
fontFamily: "monospace",
lineHeight: 1.5,
display: "flex",
alignItems: "center",
flex: 1,
}}
>
{children}
</Typography>
{actions && (
<Box sx={{ display: "flex", gap: 0.5, flexShrink: 0 }}>{actions}</Box>
)}
</Box>
);
}

View file

@ -1,126 +0,0 @@
import {
Box,
Typography,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Link,
DialogContentText,
} from "@mui/material";
import InfoBox from "renderer/components/pages/swap/swap/components/InfoBox";
import { useState } from "react";
import { getWalletDescriptor } from "renderer/rpc";
import { ExportBitcoinWalletResponse } from "models/tauriModel";
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
import ActionableMonospaceTextBox from "renderer/components/other/ActionableMonospaceTextBox";
import { isContextWithBitcoinWallet } from "models/tauriModelExt";
export default function ExportDataBox() {
const [walletDescriptor, setWalletDescriptor] =
useState<ExportBitcoinWalletResponse | null>(null);
const handleCloseDialog = () => {
setWalletDescriptor(null);
};
return (
<InfoBox
title="Export Bitcoin Wallet"
icon={null}
loading={false}
mainContent={
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
gap: 2,
}}
>
<Typography variant="subtitle2">
You can export the wallet descriptor of the interal Bitcoin wallet
for backup or recovery purposes. Please make sure to store it
securely.
</Typography>
</Box>
}
additionalContent={
<>
<PromiseInvokeButton
variant="outlined"
onInvoke={getWalletDescriptor}
onSuccess={setWalletDescriptor}
displayErrorSnackbar={true}
contextRequirement={isContextWithBitcoinWallet}
>
Reveal Bitcoin Wallet Private Key
</PromiseInvokeButton>
{walletDescriptor !== null && (
<WalletDescriptorModal
open={walletDescriptor !== null}
onClose={handleCloseDialog}
walletDescriptor={walletDescriptor}
/>
)}
</>
}
/>
);
}
function WalletDescriptorModal({
open,
onClose,
walletDescriptor,
}: {
open: boolean;
onClose: () => void;
walletDescriptor: ExportBitcoinWalletResponse;
}) {
const parsedDescriptor = JSON.parse(
walletDescriptor.wallet_descriptor["descriptor"],
);
const stringifiedDescriptor = JSON.stringify(parsedDescriptor, null, 4);
return (
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
<DialogTitle>Bitcoin Wallet Descriptor</DialogTitle>
<DialogContent>
<DialogContentText>
<ul style={{ marginTop: 0 }}>
<li>
The text below contains the wallet descriptor of the internal
Bitcoin wallet. It contains your private key and can be used to
derive your wallet. It should thus be stored securely.
</li>
<li>
It can be imported into other Bitcoin wallets or services that
support the descriptor format.
</li>
<li>
For more information on what to do with the descriptor, see our{" "}
<Link
href="https://github.com/eigenwallet/core/blob/master/dev-docs/asb/README.md#exporting-the-bitcoin-wallet-descriptor"
target="_blank"
>
documentation
</Link>
</li>
</ul>
</DialogContentText>
<ActionableMonospaceTextBox
content={stringifiedDescriptor}
displayCopyIcon={true}
enableQrCode={false}
/>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary" variant="contained">
Done
</Button>
</DialogActions>
</Dialog>
);
}

View file

@ -32,7 +32,6 @@ export default function SettingsPage() {
<SettingsBox />
<DaemonControlBox />
<MoneroPoolHealthBox />
<ExportDataBox />
<DiscoveryBox />
</Box>
);

View file

@ -13,7 +13,7 @@ export default function ConfirmationsBadge({
return (
<Chip
icon={<AutoAwesomeIcon />}
label="Published"
label="Unconfirmed"
color="secondary"
size="small"
/>

View file

@ -1,21 +1,32 @@
import { Box, Typography } from "@mui/material";
import { Alert } from "@mui/material";
import WithdrawWidget from "./WithdrawWidget";
import { Box } from "@mui/material";
import { useAppSelector } from "store/hooks";
import WalletOverview from "./components/WalletOverview";
import WalletActionButtons from "./components/WalletActionButtons";
import ActionableMonospaceTextBox from "renderer/components/other/ActionableMonospaceTextBox";
export default function WalletPage() {
const walletBalance = useAppSelector((state) => state.bitcoinWallet.balance);
const bitcoinAddress = useAppSelector((state) => state.bitcoinWallet.address);
return (
<Box
sx={{
maxWidth: 800,
mx: "auto",
display: "flex",
flexDirection: "column",
gap: "1rem",
gap: 2,
pb: 2,
}}
>
<Alert severity="info">
You do not have to deposit money before starting a swap. Instead, you
will be greeted with a deposit address after you initiate one.
</Alert>
<WithdrawWidget />
<WalletOverview balance={walletBalance} />
{bitcoinAddress && (
<ActionableMonospaceTextBox
content={bitcoinAddress}
displayCopyIcon={true}
/>
)}
<WalletActionButtons />
</Box>
);
}

View file

@ -9,7 +9,7 @@ import WithdrawDialog from "../../modal/wallet/WithdrawDialog";
import WalletRefreshButton from "./WalletRefreshButton";
export default function WithdrawWidget() {
const walletBalance = useAppSelector((state) => state.rpc.state.balance);
const walletBalance = useAppSelector((state) => state.bitcoinWallet.balance);
const [showDialog, setShowDialog] = useState(false);
function onShowDialog() {

View file

@ -0,0 +1,37 @@
import { Box, Chip } from "@mui/material";
import { Send as SendIcon } from "@mui/icons-material";
import { useState } from "react";
import { useAppSelector } from "store/hooks";
import WithdrawDialog from "../../../modal/wallet/WithdrawDialog";
import WalletDescriptorButton from "./WalletDescriptorButton";
export default function WalletActionButtons() {
const [showDialog, setShowDialog] = useState(false);
const balance = useAppSelector((state) => state.bitcoinWallet.balance);
return (
<>
<WithdrawDialog open={showDialog} onClose={() => setShowDialog(false)} />
<Box sx={{ display: "flex", justifyContent: "space-between" }}>
<Box
sx={{
display: "flex",
flexWrap: "wrap",
gap: 1,
alignItems: "center",
}}
>
<Chip
icon={<SendIcon />}
label="Sweep"
variant="button"
clickable
onClick={() => setShowDialog(true)}
disabled={balance === null || balance <= 0}
/>
<WalletDescriptorButton />
</Box>
</Box>
</>
);
}

View file

@ -0,0 +1,101 @@
import {
Button,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
DialogContentText,
Link,
} from "@mui/material";
import { Key as KeyIcon } from "@mui/icons-material";
import { useState } from "react";
import ActionableMonospaceTextBox from "renderer/components/other/ActionableMonospaceTextBox";
import { getWalletDescriptor } from "renderer/rpc";
import { ExportBitcoinWalletResponse } from "models/tauriModel";
import PromiseInvokeButton from "renderer/components/PromiseInvokeButton";
import { isContextWithBitcoinWallet } from "models/tauriModelExt";
const WALLET_DESCRIPTOR_DOCS_URL =
"https://github.com/eigenwallet/core/blob/master/dev-docs/asb/README.md#exporting-the-bitcoin-wallet-descriptor";
export default function WalletDescriptorButton() {
const [walletDescriptor, setWalletDescriptor] =
useState<ExportBitcoinWalletResponse | null>(null);
const handleCloseDialog = () => {
setWalletDescriptor(null);
};
return (
<>
<PromiseInvokeButton
isChipButton={true}
startIcon={<KeyIcon />}
onInvoke={getWalletDescriptor}
onSuccess={setWalletDescriptor}
displayErrorSnackbar={true}
contextRequirement={isContextWithBitcoinWallet}
>
Reveal Private Key
</PromiseInvokeButton>
{walletDescriptor !== null && (
<WalletDescriptorModal
open={walletDescriptor !== null}
onClose={handleCloseDialog}
walletDescriptor={walletDescriptor}
/>
)}
</>
);
}
function WalletDescriptorModal({
open,
onClose,
walletDescriptor,
}: {
open: boolean;
onClose: () => void;
walletDescriptor: ExportBitcoinWalletResponse;
}) {
const parsedDescriptor = JSON.parse(
walletDescriptor.wallet_descriptor["descriptor"],
);
const stringifiedDescriptor = JSON.stringify(parsedDescriptor, null, 4);
return (
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
<DialogTitle>Bitcoin Wallet Descriptor</DialogTitle>
<DialogContent>
<DialogContentText>
The Bitcoin wallet is derived from your Monero wallet. Opening the
same Monero wallet in another Eigenwallet will yield the same Bitcoin
wallet.
<br />
<br />
It contains your private key. Anyone who has it can spend your funds.
It should thus be stored securely.
<br />
<br />
It can be imported into other Bitcoin wallets or services that support
the descriptor format. For more information on what to do with the
descriptor, see our{" "}
<Link href={WALLET_DESCRIPTOR_DOCS_URL} target="_blank">
documentation
</Link>
</DialogContentText>
<ActionableMonospaceTextBox
content={stringifiedDescriptor}
displayCopyIcon={true}
enableQrCode={false}
spoilerText="Press to Reveal Private Key"
/>
</DialogContent>
<DialogActions>
<Button onClick={onClose} color="primary" variant="contained">
Done
</Button>
</DialogActions>
</Dialog>
);
}

View file

@ -0,0 +1,80 @@
import { Box, Typography, Card } from "@mui/material";
import { BitcoinAmount } from "renderer/components/other/Units";
import { useAppSelector, useSettings } from "store/hooks";
import { satsToBtc } from "utils/conversionUtils";
import WalletRefreshButton from "../WalletRefreshButton";
interface WalletOverviewProps {
balance: number | null;
}
function FiatBitcoinAmount({ amount }: { amount: number | null }) {
const btcPrice = useAppSelector((state) => state.rates.btcPrice);
const [fetchFiatPrices, fiatCurrency] = useSettings((settings) => [
settings.fetchFiatPrices,
settings.fiatCurrency,
]);
if (
!fetchFiatPrices ||
fiatCurrency == null ||
amount == null ||
btcPrice == null
) {
return <span />;
}
return (
<span>
{(amount * btcPrice).toFixed(2)} {fiatCurrency}
</span>
);
}
export default function WalletOverview({ balance }: WalletOverviewProps) {
const btcBalance = balance == null ? null : satsToBtc(balance);
return (
<Card sx={{ p: 2, position: "relative", borderRadius: 2 }} elevation={4}>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "stretch",
mb: 1,
}}
>
{/* Left side content */}
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 0.5,
}}
>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
Available Funds
</Typography>
<Typography variant="h4">
<BitcoinAmount amount={btcBalance} />
</Typography>
<Typography variant="body2" color="text.secondary">
<FiatBitcoinAmount amount={btcBalance} />
</Typography>
</Box>
{/* Right side - Refresh button */}
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "flex-end",
justifyContent: "flex-start",
}}
>
<WalletRefreshButton />
</Box>
</Box>
</Card>
);
}

View file

@ -16,6 +16,7 @@ import {
WithdrawBtcResponse,
GetSwapInfoArgs,
ExportBitcoinWalletResponse,
GetBitcoinAddressResponse,
CheckMoneroNodeArgs,
CheckSeedArgs,
CheckSeedResponse,
@ -49,11 +50,11 @@ import {
ContextStatus,
} from "models/tauriModel";
import {
rpcSetBalance,
rpcSetSwapInfo,
approvalRequestsReplaced,
contextInitializationFailed,
} from "store/features/rpcSlice";
import { setBitcoinBalance } from "store/features/bitcoinWalletSlice";
import {
setMainAddress,
setBalance,
@ -166,7 +167,7 @@ export async function checkBitcoinBalance() {
force_refresh: true,
});
store.dispatch(rpcSetBalance(response.balance));
store.dispatch(setBitcoinBalance(response.balance));
}
export async function cheapCheckBitcoinBalance() {
@ -174,7 +175,15 @@ export async function cheapCheckBitcoinBalance() {
force_refresh: false,
});
store.dispatch(rpcSetBalance(response.balance));
store.dispatch(setBitcoinBalance(response.balance));
}
export async function getBitcoinAddress() {
const response = await invokeNoArgs<GetBitcoinAddressResponse>(
"get_bitcoin_address",
);
return response.address;
}
export async function getAllSwapInfos() {

View file

@ -8,6 +8,7 @@ import nodesSlice from "./features/nodesSlice";
import conversationsSlice from "./features/conversationsSlice";
import poolSlice from "./features/poolSlice";
import walletSlice from "./features/walletSlice";
import bitcoinWalletSlice from "./features/bitcoinWalletSlice";
import logsSlice from "./features/logsSlice";
export const reducers = {
@ -21,5 +22,6 @@ export const reducers = {
conversations: conversationsSlice,
pool: poolSlice,
wallet: walletSlice,
bitcoinWallet: bitcoinWalletSlice,
logs: logsSlice,
};

View file

@ -0,0 +1,32 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface BitcoinWalletState {
address: string | null;
balance: number | null;
}
const initialState: BitcoinWalletState = {
address: null,
balance: null,
};
export const bitcoinWalletSlice = createSlice({
name: "bitcoinWallet",
initialState,
reducers: {
setBitcoinAddress(state, action: PayloadAction<string>) {
state.address = action.payload;
},
setBitcoinBalance(state, action: PayloadAction<number>) {
state.balance = action.payload;
},
resetBitcoinWalletState(state) {
return initialState;
},
},
});
export const { setBitcoinAddress, setBitcoinBalance, resetBitcoinWalletState } =
bitcoinWalletSlice.actions;
export default bitcoinWalletSlice.reducer;

View file

@ -14,7 +14,6 @@ import { GetSwapInfoResponseExt } from "models/tauriModelExt";
import logger from "utils/logger";
interface State {
balance: number | null;
withdrawTxId: string | null;
rendezvousDiscoveredSellers: (ExtendedMakerStatus | MakerStatus)[];
swapInfos: {
@ -54,7 +53,6 @@ export interface RPCSlice {
const initialState: RPCSlice = {
status: null,
state: {
balance: null,
withdrawTxId: null,
rendezvousDiscoveredSellers: [],
swapInfos: {},
@ -95,9 +93,6 @@ export const rpcSlice = createSlice({
);
}
},
rpcSetBalance(slice, action: PayloadAction<number>) {
slice.state.balance = action.payload;
},
rpcSetWithdrawTxId(slice, action: PayloadAction<string>) {
slice.state.withdrawTxId = action.payload;
},
@ -177,7 +172,6 @@ export const rpcSlice = createSlice({
export const {
contextStatusEventReceived,
contextInitializationFailed,
rpcSetBalance,
rpcSetWithdrawTxId,
rpcResetWithdrawTxId,
rpcSetRendezvousDiscoveredMakers,

View file

@ -3,6 +3,7 @@ import { throttle, debounce } from "lodash";
import {
getAllSwapInfos,
checkBitcoinBalance,
getBitcoinAddress,
updateAllNodeStatuses,
fetchSellersAtPresetRendezvousPoints,
getSwapInfo,
@ -30,6 +31,7 @@ import {
addFeedbackId,
setConversation,
} from "store/features/conversationsSlice";
import { setBitcoinAddress } from "store/features/bitcoinWalletSlice";
// Create a Map to store throttled functions per swap_id
const throttledGetSwapInfoFunctions = new Map<
@ -86,15 +88,21 @@ export function createMainListeners() {
if (!status) return;
// If the Bitcoin wallet just came available, check the Bitcoin balance
// If the Bitcoin wallet just came available, check the Bitcoin balance and get address
if (
status.bitcoin_wallet_available &&
!previousContextStatus?.bitcoin_wallet_available
) {
logger.info(
"Bitcoin wallet just became available, checking balance...",
"Bitcoin wallet just became available, checking balance and getting address...",
);
await checkBitcoinBalance();
try {
const address = await getBitcoinAddress();
store.dispatch(setBitcoinAddress(address));
} catch (error) {
logger.error("Failed to fetch Bitcoin address", error);
}
}
// If the Monero wallet just came available, initialize the Monero wallet

View file

@ -8,11 +8,11 @@ use swap::cli::{
BalanceArgs, BuyXmrArgs, CancelAndRefundArgs, ChangeMoneroNodeArgs,
CheckElectrumNodeArgs, CheckElectrumNodeResponse, CheckMoneroNodeArgs,
CheckMoneroNodeResponse, CheckSeedArgs, CheckSeedResponse, DfxAuthenticateResponse,
ExportBitcoinWalletArgs, GetCurrentSwapArgs, GetDataDirArgs, GetHistoryArgs,
GetLogsArgs, GetMoneroAddressesArgs, GetMoneroBalanceArgs, GetMoneroHistoryArgs,
GetMoneroMainAddressArgs, GetMoneroSeedArgs, GetMoneroSyncProgressArgs,
GetPendingApprovalsResponse, GetRestoreHeightArgs, GetSwapInfoArgs,
GetSwapInfosAllArgs, ListSellersArgs, MoneroRecoveryArgs, RedactArgs,
ExportBitcoinWalletArgs, GetBitcoinAddressArgs, GetCurrentSwapArgs, GetDataDirArgs,
GetHistoryArgs, GetLogsArgs, GetMoneroAddressesArgs, GetMoneroBalanceArgs,
GetMoneroHistoryArgs, GetMoneroMainAddressArgs, GetMoneroSeedArgs,
GetMoneroSyncProgressArgs, GetPendingApprovalsResponse, GetRestoreHeightArgs,
GetSwapInfoArgs, GetSwapInfosAllArgs, ListSellersArgs, MoneroRecoveryArgs, RedactArgs,
RejectApprovalArgs, RejectApprovalResponse, ResolveApprovalArgs, ResumeSwapArgs,
SendMoneroArgs, SetRestoreHeightArgs, SuspendCurrentSwapArgs, WithdrawBtcArgs,
},
@ -35,6 +35,7 @@ macro_rules! generate_command_handlers {
() => {
tauri::generate_handler![
get_balance,
get_bitcoin_address,
get_monero_addresses,
get_swap_info,
get_swap_infos_all,
@ -435,6 +436,7 @@ tauri_command!(send_monero, SendMoneroArgs);
tauri_command!(change_monero_node, ChangeMoneroNodeArgs);
// These commands require no arguments
tauri_command!(get_bitcoin_address, GetBitcoinAddressArgs, no_args);
tauri_command!(get_wallet_descriptor, ExportBitcoinWalletArgs, no_args);
tauri_command!(suspend_current_swap, SuspendCurrentSwapArgs, no_args);
tauri_command!(get_swap_info, GetSwapInfoArgs);

View file

@ -279,6 +279,30 @@ impl Request for BalanceArgs {
}
}
// GetBitcoinAddress
#[typeshare]
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct GetBitcoinAddressArgs;
#[typeshare]
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GetBitcoinAddressResponse {
#[typeshare(serialized_as = "string")]
#[serde(with = "swap_serde::bitcoin::address_serde")]
pub address: bitcoin::Address,
}
impl Request for GetBitcoinAddressArgs {
type Response = GetBitcoinAddressResponse;
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
let bitcoin_wallet = ctx.try_get_bitcoin_wallet().await?;
let address = bitcoin_wallet.new_address().await?;
Ok(GetBitcoinAddressResponse { address })
}
}
// GetHistory
#[typeshare]
#[derive(Serialize, Deserialize, Debug)]