diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f131453..00d9f36f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- GUI: Fix an issue where an error in the UI runtime would cause a white screen to be displayed and nothing would be rendered. + ## [3.2.8] - 2025-11-02 - ASB + CONTROLLER: Add a `registration-status` command to the controller shell. You can use it to get the registration status of the ASB at the configured rendezvous points. diff --git a/src-gui/package.json b/src-gui/package.json index 7b437da6..73a19c6e 100644 --- a/src-gui/package.json +++ b/src-gui/package.json @@ -20,7 +20,6 @@ "@emotion/styled": "^11.14.0", "@fontsource/roboto": "^5.1.0", "@mui/icons-material": "^7.1.1", - "@mui/lab": "^7.0.0-beta.13", "@mui/material": "^7.1.1", "@mui/x-date-pickers": "^8.8.0", "@reduxjs/toolkit": "^2.3.0", diff --git a/src-gui/src/models/tauriModelExt.ts b/src-gui/src/models/tauriModelExt.ts index 7773f672..5ed587b6 100644 --- a/src-gui/src/models/tauriModelExt.ts +++ b/src-gui/src/models/tauriModelExt.ts @@ -8,6 +8,8 @@ import { TauriSwapProgressEvent, SendMoneroDetails, ContextStatus, + QuoteWithAddress, + ExportBitcoinWalletResponse, } from "./tauriModel"; import { ContextStatusType, @@ -17,6 +19,16 @@ import { export type TauriSwapProgressEventType = TauriSwapProgressEvent["type"]; +// Wrapper for QuoteWithAddress with an optional approval request +// Approving that request will result in a swap being initiated with that maker +export type SortableQuoteWithAddress = { + quote_with_address: QuoteWithAddress; + approval: { + request_id: string; + expiration_ts: number; + } | null; +}; + export type TauriSwapProgressEventContent< T extends TauriSwapProgressEventType, > = Extract["content"]; @@ -354,9 +366,9 @@ export function isPendingPasswordApprovalEvent( * @returns True if funds have been locked, false otherwise */ export function haveFundsBeenLocked( - event: TauriSwapProgressEvent | null, + event: TauriSwapProgressEvent | null | undefined, ): boolean { - if (event === null) { + if (event === null || event === undefined) { return false; } @@ -372,7 +384,7 @@ export function haveFundsBeenLocked( } export function isContextFullyInitialized( - status: ResultContextStatus, + status: ResultContextStatus | null, ): boolean { if (status == null || status.type === ContextStatusType.Error) { return false; @@ -396,3 +408,20 @@ export function isContextWithMoneroWallet( ): boolean { return status?.monero_wallet_available ?? false; } + +export type ExportBitcoinWalletResponseExt = ExportBitcoinWalletResponse & { + wallet_descriptor: { + descriptor: string; + }; +}; + +export function hasDescriptorProperty( + response: ExportBitcoinWalletResponse, +): response is ExportBitcoinWalletResponseExt { + return ( + typeof response.wallet_descriptor === "object" && + response.wallet_descriptor !== null && + "descriptor" in response.wallet_descriptor && + typeof (response.wallet_descriptor as { descriptor?: unknown }).descriptor === "string" + ); +} diff --git a/src-gui/src/renderer/components/App.tsx b/src-gui/src/renderer/components/App.tsx index 9f928344..8ea05a46 100644 --- a/src-gui/src/renderer/components/App.tsx +++ b/src-gui/src/renderer/components/App.tsx @@ -26,6 +26,7 @@ import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import PasswordEntryDialog from "./modal/password-entry/PasswordEntryDialog"; import ContextErrorDialog from "./modal/context-error/ContextErrorDialog"; +import ErrorBoundary from "./other/ErrorBoundary"; declare module "@mui/material/styles" { interface Theme { @@ -47,24 +48,26 @@ export default function App() { console.log("Current theme:", { theme, currentTheme }); return ( - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + ); } @@ -79,13 +82,13 @@ function InnerContent() { }} > - } /> - } /> - } /> - } /> - } /> - } /> - } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> ); diff --git a/src-gui/src/renderer/components/PromiseInvokeButton.tsx b/src-gui/src/renderer/components/PromiseInvokeButton.tsx index 90d573f9..4987b700 100644 --- a/src-gui/src/renderer/components/PromiseInvokeButton.tsx +++ b/src-gui/src/renderer/components/PromiseInvokeButton.tsx @@ -34,7 +34,7 @@ interface PromiseInvokeButtonProps { export default function PromiseInvokeButton({ disabled = false, - onSuccess = null, + onSuccess, onInvoke, children, startIcon, @@ -44,7 +44,7 @@ export default function PromiseInvokeButton({ isIconButton = false, isChipButton = false, displayErrorSnackbar = false, - onPendingChange = null, + onPendingChange, contextRequirement = true, tooltipTitle = null, ...rest diff --git a/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx b/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx index 9b780fad..25d4734e 100644 --- a/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx +++ b/src-gui/src/renderer/components/alert/SwapStatusAlert/SwapStatusAlert.tsx @@ -69,11 +69,7 @@ function BitcoinRedeemedStateAlert({ swap }: { swap: GetSwapInfoResponseExt }) { "If this step fails, you can manually redeem your funds", ]} /> - + ); } @@ -208,10 +204,7 @@ export function StateAlert({ ); case "Cancel": return ( - + ); case "Punish": return ; @@ -235,6 +228,7 @@ export function StateAlert({ // 72 is the default cancel timelock in blocks // 4 blocks are around 40 minutes // If the swap has taken longer than 40 minutes, we consider it unusual +// See: swap-env/src/env.rs const UNUSUAL_AMOUNT_OF_TIME_HAS_PASSED_THRESHOLD = 72 - 4; /** @@ -246,10 +240,16 @@ export default function SwapStatusAlert({ swap, onlyShowIfUnusualAmountOfTimeHasPassed, }: { - swap: GetSwapInfoResponseExt; + swap: GetSwapInfoResponseExt | null; onlyShowIfUnusualAmountOfTimeHasPassed?: boolean; }) { - const timelock = useAppSelector(selectSwapTimelock(swap.swap_id)); + const swapId = swap?.swap_id ?? null; + const timelock = useAppSelector(selectSwapTimelock(swapId)); + const isRunning = useIsSpecificSwapRunning(swapId); + + if (swap == null) { + return null; + } if (!isGetSwapInfoResponseRunningSwap(swap)) { return null; @@ -263,12 +263,10 @@ export default function SwapStatusAlert({ timelock.type === "None" && timelock.content.blocks_left > UNUSUAL_AMOUNT_OF_TIME_HAS_PASSED_THRESHOLD; - if (onlyShowIfUnusualAmountOfTimeHasPassed && hasUnusualAmountOfTimePassed) { + if (onlyShowIfUnusualAmountOfTimeHasPassed && !hasUnusualAmountOfTimePassed) { return null; } - const isRunning = useIsSpecificSwapRunning(swap.swap_id); - return ( - Swap {swap.swap_id} is - not running + Swap {swap.swap_id} is not running )} @@ -303,7 +300,7 @@ export default function SwapStatusAlert({ }} > - + {timelock && } ); diff --git a/src-gui/src/renderer/components/alert/SwapStatusAlert/TimelockTimeline.tsx b/src-gui/src/renderer/components/alert/SwapStatusAlert/TimelockTimeline.tsx index 0a3647bd..247b5fc6 100644 --- a/src-gui/src/renderer/components/alert/SwapStatusAlert/TimelockTimeline.tsx +++ b/src-gui/src/renderer/components/alert/SwapStatusAlert/TimelockTimeline.tsx @@ -50,7 +50,7 @@ function TimelineSegment({ opacity: isActive ? 1 : 0.3, }} > - {isActive && ( + {isActive && durationOfSegment && ( s.rates?.xmrBtcRate); - if (marketExchangeRate == null) return null; - - const makerExchangeRate = satsToBtc(maker.price); - /** The markup of the exchange rate compared to the market rate in percent */ - const markup = getMarkup(makerExchangeRate, marketExchangeRate); - - return ( - - - - ); -} - -export default function MakerInfo({ maker }: { maker: ExtendedMakerStatus }) { - const isOutdated = isMakerOutdated(maker); - - return ( - - - - - - - - - - - {maker.peerId} - - - - {maker.multiAddr} - - - - - - Exchange rate:{" "} - - - - Minimum amount: - - - Maximum amount: - - - - {maker.testnet && } - {maker.uptime && ( - - - - )} - {maker.age && ( - - )} - {maker.recommended === true && ( - - } color="primary" /> - - )} - {isOutdated && ( - - } color="primary" /> - - )} - {maker.version && ( - - - - )} - - - - ); -} diff --git a/src-gui/src/renderer/components/modal/provider/MakerListDialog.tsx b/src-gui/src/renderer/components/modal/provider/MakerListDialog.tsx deleted file mode 100644 index 61c5112d..00000000 --- a/src-gui/src/renderer/components/modal/provider/MakerListDialog.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { - Avatar, - Button, - Dialog, - DialogActions, - DialogContent, - DialogTitle, - List, - ListItemAvatar, - ListItemText, -} from "@mui/material"; -import AddIcon from "@mui/icons-material/Add"; -import SearchIcon from "@mui/icons-material/Search"; -import { ExtendedMakerStatus } from "models/apiModel"; -import { useState } from "react"; -import { setSelectedMaker } from "store/features/makersSlice"; -import { useAllMakers, useAppDispatch } from "store/hooks"; -import MakerInfo from "./MakerInfo"; -import MakerSubmitDialog from "./MakerSubmitDialog"; - -import ListItemButton from "@mui/material/ListItemButton"; - -type MakerSelectDialogProps = { - open: boolean; - onClose: () => void; -}; - -export function MakerSubmitDialogOpenButton() { - const [open, setOpen] = useState(false); - - return ( - { - // Prevents background from being clicked and reopening dialog - if (!open) { - setOpen(true); - } - }} - > - setOpen(false)} /> - - - - - - - - ); -} - -export default function MakerListDialog({ - open, - onClose, -}: MakerSelectDialogProps) { - const makers = useAllMakers(); - const dispatch = useAppDispatch(); - - function handleMakerChange(maker: ExtendedMakerStatus) { - dispatch(setSelectedMaker(maker)); - onClose(); - } - - return ( - - Select a maker - - - {makers.map((maker) => ( - handleMakerChange(maker)} - key={maker.peerId} - > - - - ))} - - - - - - - - ); -} diff --git a/src-gui/src/renderer/components/modal/provider/MakerSelect.tsx b/src-gui/src/renderer/components/modal/provider/MakerSelect.tsx deleted file mode 100644 index 016dd8aa..00000000 --- a/src-gui/src/renderer/components/modal/provider/MakerSelect.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Paper, Card, CardContent, IconButton } from "@mui/material"; -import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; -import { useState } from "react"; -import { useAppSelector } from "store/hooks"; -import MakerInfo from "./MakerInfo"; -import MakerListDialog from "./MakerListDialog"; - -export default function MakerSelect() { - const [selectDialogOpen, setSelectDialogOpen] = useState(false); - const selectedMaker = useAppSelector((state) => state.makers.selectedMaker); - - if (!selectedMaker) return <>No maker selected; - - function handleSelectDialogClose() { - setSelectDialogOpen(false); - } - - function handleSelectDialogOpen() { - setSelectDialogOpen(true); - } - - return ( - - - - - - - - - - - - ); -} diff --git a/src-gui/src/renderer/components/modal/provider/MakerSubmitDialog.tsx b/src-gui/src/renderer/components/modal/provider/MakerSubmitDialog.tsx deleted file mode 100644 index f0eb7e3b..00000000 --- a/src-gui/src/renderer/components/modal/provider/MakerSubmitDialog.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { - Button, - Dialog, - DialogActions, - DialogContent, - DialogContentText, - DialogTitle, - TextField, -} from "@mui/material"; -import { Multiaddr } from "multiaddr"; -import { ChangeEvent, useState } from "react"; - -type MakerSubmitDialogProps = { - open: boolean; - onClose: () => void; -}; - -export default function MakerSubmitDialog({ - open, - onClose, -}: MakerSubmitDialogProps) { - const [multiAddr, setMultiAddr] = useState(""); - const [peerId, setPeerId] = useState(""); - - async function handleMakerSubmit() { - if (multiAddr && peerId) { - await fetch("https://api.unstoppableswap.net/api/submit-provider", { - method: "post", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - multiAddr, - peerId, - }), - }); - setMultiAddr(""); - setPeerId(""); - onClose(); - } - } - - function handleMultiAddrChange(event: ChangeEvent) { - setMultiAddr(event.target.value); - } - - function handlePeerIdChange(event: ChangeEvent) { - setPeerId(event.target.value); - } - - function getMultiAddressError(): string | null { - try { - const multiAddress = new Multiaddr(multiAddr); - if (multiAddress.protoNames().includes("p2p")) { - return "The multi address should not contain the peer id (/p2p/)"; - } - if (multiAddress.protoNames().find((name) => name.includes("onion"))) { - return "It is currently not possible to add a maker that is only reachable via Tor"; - } - return null; - } catch (e) { - return "Not a valid multi address"; - } - } - - return ( - - Submit a maker to the public registry - - - If the maker is valid and reachable, it will be displayed to all other - users to trade with. - - - - - - - - - - ); -} diff --git a/src-gui/src/renderer/components/modal/updater/UpdaterDialog.tsx b/src-gui/src/renderer/components/modal/updater/UpdaterDialog.tsx index 259fc859..e91f78fd 100644 --- a/src-gui/src/renderer/components/modal/updater/UpdaterDialog.tsx +++ b/src-gui/src/renderer/components/modal/updater/UpdaterDialog.tsx @@ -49,7 +49,7 @@ function LinearProgressWithLabel( }} > - {props.label || `${Math.round(props.value)}%`} + {props.label || `${Math.round(props.value ?? 0)}%`} @@ -84,6 +84,8 @@ export default function UpdaterDialog() { } async function handleInstall() { + if (!availableUpdate) return; + try { await availableUpdate.downloadAndInstall((event: DownloadEvent) => { if (event.event === "Started") { @@ -92,10 +94,13 @@ export default function UpdaterDialog() { downloadedBytes: 0, }); } else if (event.event === "Progress") { - setDownloadProgress((prev) => ({ - ...prev, - downloadedBytes: prev.downloadedBytes + event.data.chunkLength, - })); + setDownloadProgress((prev) => { + if (!prev) return null; + return { + contentLength: prev.contentLength, + downloadedBytes: prev.downloadedBytes + event.data.chunkLength, + }; + }); } }); @@ -110,12 +115,13 @@ export default function UpdaterDialog() { const isDownloading = downloadProgress !== null; - const progress = isDownloading - ? Math.round( - (downloadProgress.downloadedBytes / downloadProgress.contentLength) * - 100, - ) - : 0; + const progress = + isDownloading && downloadProgress.contentLength + ? Math.round( + (downloadProgress.downloadedBytes / downloadProgress.contentLength) * + 100, + ) + : 0; return ( withdrawBtc(withdrawAddress)} + onInvoke={() => sweepBtc(withdrawAddress)} onPendingChange={setPending} onSuccess={setWithdrawTxId} contextRequirement={isContextWithBitcoinWallet} diff --git a/src-gui/src/renderer/components/other/ActionableMonospaceTextBox.tsx b/src-gui/src/renderer/components/other/ActionableMonospaceTextBox.tsx index 0fa14df7..8211ac89 100644 --- a/src-gui/src/renderer/components/other/ActionableMonospaceTextBox.tsx +++ b/src-gui/src/renderer/components/other/ActionableMonospaceTextBox.tsx @@ -13,7 +13,7 @@ type ModalProps = { }; type Props = { - content: string; + content: string | null; displayCopyIcon?: boolean; enableQrCode?: boolean; light?: boolean; @@ -72,6 +72,7 @@ export default function ActionableMonospaceTextBox({ const [isRevealed, setIsRevealed] = useState(!spoilerText); const handleCopy = async () => { + if (!content) return; await writeText(content); setCopied(true); setTimeout(() => setCopied(false), 2000); @@ -160,7 +161,7 @@ export default function ActionableMonospaceTextBox({ )} - {enableQrCode && ( + {enableQrCode && content && ( setQrCodeOpen(false)} diff --git a/src-gui/src/renderer/components/other/ErrorBoundary.tsx b/src-gui/src/renderer/components/other/ErrorBoundary.tsx new file mode 100644 index 00000000..ecf04fd5 --- /dev/null +++ b/src-gui/src/renderer/components/other/ErrorBoundary.tsx @@ -0,0 +1,52 @@ +// Vendored from https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/error_boundaries/ +import React, { Component, ErrorInfo, ReactNode } from "react"; + +interface Props { + children?: ReactNode; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +class ErrorBoundary extends Component { + public state: State = { + hasError: false, + error: null + }; + + public static getDerivedStateFromError(error: Error): State { + // Update state so the next render will show the fallback UI. + return { hasError: true, error }; + } + + public componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error("Uncaught error:", error, errorInfo); + } + + public render() { + if (this.state.hasError) { + return ( +
+

Sorry.. there was an error

+
+            {this.state.error?.message}
+          
+ {this.state.error?.stack && ( +
+ Stack trace +
+                {this.state.error.stack}
+              
+
+ )} +
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/src-gui/src/renderer/components/other/JSONViewTree.tsx b/src-gui/src/renderer/components/other/JSONViewTree.tsx deleted file mode 100644 index fefd7b22..00000000 --- a/src-gui/src/renderer/components/other/JSONViewTree.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import ChevronRightIcon from "@mui/icons-material/ChevronRight"; -import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import TreeItem from "@mui/lab/TreeItem"; -import TreeView from "@mui/lab/TreeView"; -import ScrollablePaperTextBox from "./ScrollablePaperTextBox"; - -interface JsonTreeViewProps { - data: unknown; - label: string; -} - -export default function JsonTreeView({ data, label }: JsonTreeViewProps) { - const renderTree = (nodes: unknown, parentId: string) => { - return Object.keys(nodes).map((key, _) => { - const nodeId = `${parentId}.${key}`; - if (typeof nodes[key] === "object" && nodes[key] !== null) { - return ( - - {renderTree(nodes[key], nodeId)} - - ); - } - return ( - - ); - }); - }; - - return ( - } - defaultExpandIcon={} - defaultExpanded={["root"]} - > - - {renderTree(data ?? {}, "root")} - - , - ]} - /> - ); -} diff --git a/src-gui/src/renderer/components/other/ScrollablePaperTextBox.tsx b/src-gui/src/renderer/components/other/ScrollablePaperTextBox.tsx index 57d975fb..548673f4 100644 --- a/src-gui/src/renderer/components/other/ScrollablePaperTextBox.tsx +++ b/src-gui/src/renderer/components/other/ScrollablePaperTextBox.tsx @@ -108,7 +108,7 @@ export default function ScrollablePaperTextBox({ - {searchQuery !== undefined && setSearchQuery !== undefined && ( + {searchQuery !== null && setSearchQuery !== null && ( )} diff --git a/src-gui/src/renderer/components/other/TruncatedText.tsx b/src-gui/src/renderer/components/other/TruncatedText.tsx index b3c4c0eb..e0bc7f09 100644 --- a/src-gui/src/renderer/components/other/TruncatedText.tsx +++ b/src-gui/src/renderer/components/other/TruncatedText.tsx @@ -9,7 +9,7 @@ export default function TruncatedText({ ellipsis?: string; truncateMiddle?: boolean; }) { - let finalChildren = children ?? ""; + const finalChildren = children ?? ""; const truncatedText = finalChildren.length > limit diff --git a/src-gui/src/renderer/components/other/Units.tsx b/src-gui/src/renderer/components/other/Units.tsx index 78ab3df7..cc485ace 100644 --- a/src-gui/src/renderer/components/other/Units.tsx +++ b/src-gui/src/renderer/components/other/Units.tsx @@ -9,7 +9,7 @@ export function AmountWithUnit({ unit, fixedPrecision, exchangeRate, - parenthesisText = null, + parenthesisText, labelStyles, amountStyles, disableTooltip = false, @@ -18,7 +18,7 @@ export function AmountWithUnit({ unit: string; fixedPrecision: number; exchangeRate?: Amount; - parenthesisText?: string; + parenthesisText?: string | null; labelStyles?: SxProps; amountStyles?: SxProps; disableTooltip?: boolean; @@ -142,7 +142,7 @@ export function MoneroBitcoinExchangeRate({ }) { const marketRate = useAppSelector((state) => state.rates?.xmrBtcRate); const markup = - displayMarkup && marketRate != null + displayMarkup && marketRate != null && rate != null ? `${getMarkup(rate, marketRate).toFixed(2)}% markup` : null; @@ -179,7 +179,7 @@ export function MoneroSatsExchangeRate({ rate: Amount; displayMarkup?: boolean; }) { - const btc = satsToBtc(rate); + const btc = rate == null ? null : satsToBtc(rate); return ; } diff --git a/src-gui/src/renderer/components/pages/help/ConversationsBox.tsx b/src-gui/src/renderer/components/pages/help/ConversationsBox.tsx index eb8d02ad..edc56b6f 100644 --- a/src-gui/src/renderer/components/pages/help/ConversationsBox.tsx +++ b/src-gui/src/renderer/components/pages/help/ConversationsBox.tsx @@ -378,10 +378,6 @@ function MessageBubble({ message }: { message: Message }) { ({ padding: 1.5, - borderRadius: - typeof theme.shape.borderRadius === "number" - ? theme.shape.borderRadius * 2 - : 8, maxWidth: "75%", wordBreak: "break-word", boxShadow: theme.shadows[1], diff --git a/src-gui/src/renderer/components/pages/help/SettingsBox.tsx b/src-gui/src/renderer/components/pages/help/SettingsBox.tsx index ba71bc33..62765c88 100644 --- a/src-gui/src/renderer/components/pages/help/SettingsBox.tsx +++ b/src-gui/src/renderer/components/pages/help/SettingsBox.tsx @@ -432,7 +432,7 @@ function MoneroNodeUrlSetting() { value && handleNodeUrlChange(value)} placeholder={PLACEHOLDER_MONERO_NODE_URL} disabled={useMoneroRpcPool} fullWidth @@ -675,7 +675,7 @@ function NodeTable({ setNewNode(value ?? "")} placeholder={placeholder} fullWidth isValid={isValid} @@ -843,7 +843,9 @@ function RendezvousPointsSetting() { + setNewPoint(value ?? "") + } placeholder="/dns4/rendezvous.observer/tcp/8888/p2p/12D3KooWMjceGXrYuGuDMGrfmJxALnSDbK4km6s1i1sJEgDTgGQa" fullWidth isValid={isValidMultiAddressWithPeerId} diff --git a/src-gui/src/renderer/components/pages/history/table/SwapMoneroRecoveryButton.tsx b/src-gui/src/renderer/components/pages/history/table/SwapMoneroRecoveryButton.tsx index 4d304bac..5e8e2a48 100644 --- a/src-gui/src/renderer/components/pages/history/table/SwapMoneroRecoveryButton.tsx +++ b/src-gui/src/renderer/components/pages/history/table/SwapMoneroRecoveryButton.tsx @@ -119,9 +119,9 @@ export function SwapMoneroRecoveryButton({ onInvoke={(): Promise => getMoneroRecoveryKeys(swap.swap_id) } - onSuccess={(keys: MoneroRecoveryResponse) => - store.dispatch(rpcSetMoneroRecoveryKeys([swap.swap_id, keys])) - } + onSuccess={(keys: MoneroRecoveryResponse) => { + store.dispatch(rpcSetMoneroRecoveryKeys([swap.swap_id, keys])); + }} {...props} > Display Monero Recovery Keys diff --git a/src-gui/src/renderer/components/pages/monero/components/SendAmountInput.tsx b/src-gui/src/renderer/components/pages/monero/components/SendAmountInput.tsx index 5cb44bb7..ca23c8cd 100644 --- a/src-gui/src/renderer/components/pages/monero/components/SendAmountInput.tsx +++ b/src-gui/src/renderer/components/pages/monero/components/SendAmountInput.tsx @@ -16,7 +16,7 @@ interface SendAmountInputProps { currency: string; onCurrencyChange: (currency: string) => void; fiatCurrency: string; - xmrPrice: number; + xmrPrice: number | null; showFiatRate: boolean; disabled?: boolean; } @@ -48,6 +48,10 @@ export default function SendAmountInput({ return "0.00"; } + if (xmrPrice === null) { + return "?"; + } + const primaryValue = parseFloat(amount); if (currency === "XMR") { // Primary is XMR, secondary is USD @@ -76,7 +80,7 @@ export default function SendAmountInput({ if (currency === "XMR") { onAmountChange(Math.max(0, maxAmountXmr).toString()); - } else { + } else if (xmrPrice !== null) { // Convert to USD for display const maxAmountUsd = maxAmountXmr * xmrPrice; onAmountChange(Math.max(0, maxAmountUsd).toString()); @@ -103,8 +107,9 @@ export default function SendAmountInput({ (currency === "XMR" ? parseFloat(amount) > piconerosToXmr(parseFloat(balance.unlocked_balance)) - : parseFloat(amount) / xmrPrice > - piconerosToXmr(parseFloat(balance.unlocked_balance))); + : xmrPrice !== null && + parseFloat(amount) / xmrPrice > + piconerosToXmr(parseFloat(balance.unlocked_balance))); return ( Available - - - - - XMR - + + + diff --git a/src-gui/src/renderer/components/pages/monero/components/SendTransactionContent.tsx b/src-gui/src/renderer/components/pages/monero/components/SendTransactionContent.tsx index e69827cb..99f2cd8e 100644 --- a/src-gui/src/renderer/components/pages/monero/components/SendTransactionContent.tsx +++ b/src-gui/src/renderer/components/pages/monero/components/SendTransactionContent.tsx @@ -90,7 +90,9 @@ export default function SendTransactionContent({ const moneroAmount = currency === "XMR" ? parseFloat(sendAmount) - : parseFloat(sendAmount) / xmrPrice; + : xmrPrice !== null + ? parseFloat(sendAmount) / xmrPrice + : null; const handleSend = async () => { if (!sendAddress) { @@ -103,7 +105,7 @@ export default function SendTransactionContent({ amount: { type: "Sweep" }, }); } else { - if (!sendAmount || sendAmount === "") { + if (!sendAmount || sendAmount === "" || moneroAmount === null) { throw new Error("Amount is required"); } diff --git a/src-gui/src/renderer/components/pages/monero/components/StateIndicator.tsx b/src-gui/src/renderer/components/pages/monero/components/StateIndicator.tsx index 96e5e466..4f6297b8 100644 --- a/src-gui/src/renderer/components/pages/monero/components/StateIndicator.tsx +++ b/src-gui/src/renderer/components/pages/monero/components/StateIndicator.tsx @@ -1,6 +1,6 @@ import { Box, darken, lighten, useTheme } from "@mui/material"; -function getColor(colorName: string) { +function getColor(colorName: string): string { const theme = useTheme(); switch (colorName) { case "primary": @@ -11,6 +11,8 @@ function getColor(colorName: string) { return theme.palette.success.main; case "warning": return theme.palette.warning.main; + default: + return theme.palette.primary.main; } } diff --git a/src-gui/src/renderer/components/pages/monero/components/TransactionHistory.tsx b/src-gui/src/renderer/components/pages/monero/components/TransactionHistory.tsx index 1075b693..10adfdbf 100644 --- a/src-gui/src/renderer/components/pages/monero/components/TransactionHistory.tsx +++ b/src-gui/src/renderer/components/pages/monero/components/TransactionHistory.tsx @@ -5,9 +5,9 @@ import dayjs from "dayjs"; import TransactionItem from "./TransactionItem"; interface TransactionHistoryProps { - history?: { + history: { transactions: TransactionInfo[]; - }; + } | null; } interface TransactionGroup { diff --git a/src-gui/src/renderer/components/pages/monero/components/WalletOverview.tsx b/src-gui/src/renderer/components/pages/monero/components/WalletOverview.tsx index c69a98ab..182bc21e 100644 --- a/src-gui/src/renderer/components/pages/monero/components/WalletOverview.tsx +++ b/src-gui/src/renderer/components/pages/monero/components/WalletOverview.tsx @@ -4,7 +4,10 @@ import { PiconeroAmount } from "../../../other/Units"; import { FiatPiconeroAmount } from "../../../other/Units"; import StateIndicator from "./StateIndicator"; import humanizeDuration from "humanize-duration"; -import { GetMoneroSyncProgressResponse } from "models/tauriModel"; +import { + GetMoneroBalanceResponse, + GetMoneroSyncProgressResponse, +} from "models/tauriModel"; interface TimeEstimationResult { blocksLeft: number; @@ -16,7 +19,7 @@ interface TimeEstimationResult { const AVG_MONERO_BLOCK_SIZE_KB = 130; function useSyncTimeEstimation( - syncProgress: GetMoneroSyncProgressResponse | undefined, + syncProgress: GetMoneroSyncProgressResponse | null, ): TimeEstimationResult | null { const poolStatus = useAppSelector((state) => state.pool.status); const restoreHeight = useAppSelector( @@ -80,11 +83,8 @@ function useSyncTimeEstimation( } interface WalletOverviewProps { - balance?: { - unlocked_balance: string; - total_balance: string; - }; - syncProgress?: GetMoneroSyncProgressResponse; + balance: GetMoneroBalanceResponse | null; + syncProgress: GetMoneroSyncProgressResponse | null; } // Component for displaying wallet address and balance @@ -99,10 +99,11 @@ export default function WalletOverview({ const poolStatus = useAppSelector((state) => state.pool.status); const timeEstimation = useSyncTimeEstimation(syncProgress); - const pendingBalance = - parseFloat(balance.total_balance) - parseFloat(balance.unlocked_balance); + const pendingBalance = balance + ? parseFloat(balance.total_balance) - parseFloat(balance.unlocked_balance) + : null; - const isSyncing = syncProgress && syncProgress.progress_percentage < 100; + const isSyncing = !!(syncProgress && syncProgress.progress_percentage < 100); // syncProgress.progress_percentage is not good to display // assuming we have an old wallet, eventually we will always only use the last few cm of the progress bar @@ -184,18 +185,18 @@ export default function WalletOverview({ - {pendingBalance > 0 && ( + {pendingBalance !== null && pendingBalance > 0 && ( - {Array.from({ length: 2 }).map((_) => ( - + {Array.from({ length: 2 }).map((_, i) => ( + ))} diff --git a/src-gui/src/renderer/components/pages/swap/swap/components/InfoBox.tsx b/src-gui/src/renderer/components/pages/swap/swap/components/InfoBox.tsx index 4f6e485b..b7321c26 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/components/InfoBox.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/components/InfoBox.tsx @@ -2,7 +2,7 @@ import { Box, LinearProgress, Paper, Typography } from "@mui/material"; import { ReactNode } from "react"; type Props = { - id?: string; + id?: string | null; title: ReactNode | null; mainContent: ReactNode; additionalContent: ReactNode; @@ -11,7 +11,7 @@ type Props = { }; export default function InfoBox({ - id = null, + id, title, mainContent, additionalContent, @@ -21,7 +21,7 @@ export default function InfoBox({ return ( ); })} diff --git a/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx b/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx index 035c1c85..e651826a 100644 --- a/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx +++ b/src-gui/src/renderer/components/pages/swap/swap/init/deposit_and_choose_offer/MakerOfferItem.tsx @@ -111,7 +111,12 @@ export default function MakerOfferItem({ resolveApproval(requestId, true as unknown as object)} + onInvoke={() => { + if (!requestId) { + throw new Error("Request ID is required"); + } + return resolveApproval(requestId, true as unknown as object); + }} displayErrorSnackbar disabled={!requestId} tooltipTitle={ diff --git a/src-gui/src/renderer/components/pages/wallet/components/WalletDescriptorButton.tsx b/src-gui/src/renderer/components/pages/wallet/components/WalletDescriptorButton.tsx index acd24743..ab617c1f 100644 --- a/src-gui/src/renderer/components/pages/wallet/components/WalletDescriptorButton.tsx +++ b/src-gui/src/renderer/components/pages/wallet/components/WalletDescriptorButton.tsx @@ -13,7 +13,10 @@ import ActionableMonospaceTextBox from "renderer/components/other/ActionableMono import { getWalletDescriptor } from "renderer/rpc"; import { ExportBitcoinWalletResponse } from "models/tauriModel"; import PromiseInvokeButton from "renderer/components/PromiseInvokeButton"; -import { isContextWithBitcoinWallet } from "models/tauriModelExt"; +import { + isContextWithBitcoinWallet, + hasDescriptorProperty, +} 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"; @@ -58,8 +61,12 @@ function WalletDescriptorModal({ onClose: () => void; walletDescriptor: ExportBitcoinWalletResponse; }) { + if (!hasDescriptorProperty(walletDescriptor)) { + throw new Error("Wallet descriptor does not have descriptor property"); + } + const parsedDescriptor = JSON.parse( - walletDescriptor.wallet_descriptor["descriptor"], + walletDescriptor.wallet_descriptor.descriptor, ); const stringifiedDescriptor = JSON.stringify(parsedDescriptor, null, 4); diff --git a/src-gui/src/renderer/components/theme.tsx b/src-gui/src/renderer/components/theme.tsx index b7f905d0..1f711946 100644 --- a/src-gui/src/renderer/components/theme.tsx +++ b/src-gui/src/renderer/components/theme.tsx @@ -24,7 +24,7 @@ declare module "@mui/material/styles" { tint?: string; } - interface PaletteColorOptions { + interface SimplePaletteColorOptions { tint?: string; } } @@ -61,16 +61,6 @@ const baseTheme: ThemeOptions = { backgroundColor: "color-mix(in srgb, #bdbdbd 10%, transparent)", }, }, - sizeTiny: { - fontSize: "0.75rem", - fontWeight: 500, - padding: "4px 8px", - minHeight: "24px", - minWidth: "auto", - lineHeight: 1.2, - textTransform: "none", - borderRadius: "4px", - }, }, variants: [ { diff --git a/src-gui/src/renderer/rpc.ts b/src-gui/src/renderer/rpc.ts index 4cfd41e6..8317fe6a 100644 --- a/src-gui/src/renderer/rpc.ts +++ b/src-gui/src/renderer/rpc.ts @@ -210,7 +210,13 @@ export async function buyXmr() { address_pool.push( { - address: moneroReceiveAddress, + // We need to assert this as being not null even though it can be null + // + // This is correct because a LabeledMoneroAddress can actually have a null address but + // typeshare cannot express that yet (easily) + // + // TODO: Let typescript do its job here and not assert it + address: moneroReceiveAddress!, percentage: 1 - donationPercentage, label: "Your wallet", }, @@ -222,7 +228,13 @@ export async function buyXmr() { ); } else { address_pool.push({ - address: moneroReceiveAddress, + // We need to assert this as being not null even though it can be null + // + // This is correct because a LabeledMoneroAddress can actually have a null address but + // typeshare cannot express that yet (easily) + // + // TODO: Let typescript do its job here and not assert it + address: moneroReceiveAddress!, percentage: 1, label: "Your wallet", }); @@ -232,7 +244,9 @@ export async function buyXmr() { rendezvous_points: PRESET_RENDEZVOUS_POINTS, sellers, monero_receive_pool: address_pool, - bitcoin_change_address: bitcoinChangeAddress, + // We convert null to undefined because typescript + // expects undefined if the field is optional and does not accept null here + bitcoin_change_address: bitcoinChangeAddress ?? undefined, }); } @@ -284,7 +298,7 @@ export async function initializeContext() { }); logger.info("Initialized context"); } catch (error) { - throw new Error(error); + throw new Error(String(error)); } } @@ -340,12 +354,12 @@ export async function getSwapInfo(swapId: string) { } export async function getSwapTimelock(swapId: string) { - const response = await invoke< - GetSwapTimelockArgs, - GetSwapTimelockResponse - >("get_swap_timelock", { - swap_id: swapId, - }); + const response = await invoke( + "get_swap_timelock", + { + swap_id: swapId, + }, + ); store.dispatch( timelockChangeEventReceived({ @@ -369,12 +383,12 @@ export async function getAllSwapTimelocks() { ); } -export async function withdrawBtc(address: string): Promise { +export async function sweepBtc(address: string): Promise { const response = await invoke( "withdraw_btc", { address, - amount: null, + amount: undefined, }, ); diff --git a/src-gui/src/store/features/makersSlice.ts b/src-gui/src/store/features/makersSlice.ts index 16221b4d..73b6ed19 100644 --- a/src-gui/src/store/features/makersSlice.ts +++ b/src-gui/src/store/features/makersSlice.ts @@ -30,31 +30,6 @@ const initialState: MakersSlice = { selectedMaker: null, }; -function selectNewSelectedMaker( - slice: MakersSlice, - peerId?: string, -): MakerStatus { - const selectedPeerId = peerId || slice.selectedMaker?.peerId; - - // Check if we still have a record of the currently selected provider - const currentMaker = - slice.registry.makers?.find((prov) => prov.peerId === selectedPeerId) || - slice.rendezvous.makers.find((prov) => prov.peerId === selectedPeerId); - - // If the currently selected provider is not outdated, keep it - if (currentMaker != null && !isMakerOutdated(currentMaker)) { - return currentMaker; - } - - // Otherwise we'd prefer to switch to a provider that has the newest version - const providers = [ - ...(slice.registry.makers ?? []), - ...(slice.rendezvous.makers ?? []), - ]; - - return providers.at(0) || null; -} - export const makersSlice = createSlice({ name: "providers", initialState, @@ -83,32 +58,15 @@ export const makersSlice = createSlice({ slice.rendezvous.makers.push(discoveredMakerStatus); } }); - - // Sort the provider list and select a new provider if needed - slice.selectedMaker = selectNewSelectedMaker(slice); }, setRegistryMakers(slice, action: PayloadAction) { if (stubTestnetMaker) { action.payload.push(stubTestnetMaker); } - - // Sort the provider list and select a new provider if needed - slice.selectedMaker = selectNewSelectedMaker(slice); }, registryConnectionFailed(slice) { slice.registry.connectionFailsCount += 1; }, - setSelectedMaker( - slice, - action: PayloadAction<{ - peerId: string; - }>, - ) { - slice.selectedMaker = selectNewSelectedMaker( - slice, - action.payload.peerId, - ); - }, }, }); @@ -116,7 +74,6 @@ export const { discoveredMakersByRendezvous, setRegistryMakers, registryConnectionFailed, - setSelectedMaker, } = makersSlice.actions; export default makersSlice.reducer; diff --git a/src-gui/src/store/features/rpcSlice.ts b/src-gui/src/store/features/rpcSlice.ts index 15858632..3c4c3b65 100644 --- a/src-gui/src/store/features/rpcSlice.ts +++ b/src-gui/src/store/features/rpcSlice.ts @@ -89,8 +89,10 @@ export const rpcSlice = createSlice({ slice: RPCSlice, action: PayloadAction, ) { - slice.state.swapTimelocks[action.payload.swap_id] = - action.payload.timelock; + if (action.payload.timelock) { + slice.state.swapTimelocks[action.payload.swap_id] = + action.payload.timelock; + } }, rpcSetWithdrawTxId(slice, action: PayloadAction) { slice.state.withdrawTxId = action.payload; diff --git a/src-gui/src/store/features/walletSlice.ts b/src-gui/src/store/features/walletSlice.ts index 73be6319..788abd7b 100644 --- a/src-gui/src/store/features/walletSlice.ts +++ b/src-gui/src/store/features/walletSlice.ts @@ -48,8 +48,8 @@ export const walletSlice = createSlice({ slice.state.lowestCurrentBlock = Math.min( // We ignore anything below 10 blocks as this may be something like wallet2 // sending a wrong value when it hasn't initialized yet - slice.state.lowestCurrentBlock < 10 || - slice.state.lowestCurrentBlock === null + slice.state.lowestCurrentBlock === null || + slice.state.lowestCurrentBlock < 10 ? Infinity : slice.state.lowestCurrentBlock, action.payload.current_block, diff --git a/src-gui/src/store/hooks.ts b/src-gui/src/store/hooks.ts index 15aeda51..77a6b46c 100644 --- a/src-gui/src/store/hooks.ts +++ b/src-gui/src/store/hooks.ts @@ -253,7 +253,11 @@ export function useBitcoinSyncProgress(): TauriBitcoinSyncProgress[] { const syncingProcesses = pendingProcesses .map(([_, c]) => c) .filter(isBitcoinSyncProgress); - return syncingProcesses.map((c) => c.progress.content); + return syncingProcesses + .map((c) => c.progress.content) + .filter( + (content): content is TauriBitcoinSyncProgress => content !== undefined, + ); } export function isSyncingBitcoin(): boolean { diff --git a/src-gui/src/store/selectors.ts b/src-gui/src/store/selectors.ts index 9cf67d12..d1b1e156 100644 --- a/src-gui/src/store/selectors.ts +++ b/src-gui/src/store/selectors.ts @@ -18,10 +18,9 @@ export const selectSwapTimelocks = createSelector( (rpcState) => rpcState.swapTimelocks, ); -export const selectSwapTimelock = (swapId: string) => - createSelector( - [selectSwapTimelocks], - (timelocks) => timelocks[swapId] ?? null, +export const selectSwapTimelock = (swapId: string | null) => + createSelector([selectSwapTimelocks], (timelocks) => + swapId ? (timelocks[swapId] ?? null) : null, ); export const selectSwapInfoWithTimelock = (swapId: string) => diff --git a/src-gui/src/utils/sortUtils.ts b/src-gui/src/utils/sortUtils.ts index 9ab03d74..7df03b5e 100644 --- a/src-gui/src/utils/sortUtils.ts +++ b/src-gui/src/utils/sortUtils.ts @@ -10,48 +10,47 @@ export function sortApprovalsAndKnownQuotes( pendingSelectMakerApprovals: PendingSelectMakerApprovalRequest[], known_quotes: QuoteWithAddress[], ) { - const sortableQuotes = pendingSelectMakerApprovals.map((approval) => { - return { - ...approval.request.content.maker, - expiration_ts: - approval.request_status.state === "Pending" - ? approval.request_status.content.expiration_ts - : undefined, - request_id: approval.request_id, - } as SortableQuoteWithAddress; - }); + const sortableQuotes: SortableQuoteWithAddress[] = + pendingSelectMakerApprovals.map((approval) => { + return { + quote_with_address: approval.request.content.maker, + approval: + approval.request_status.state === "Pending" + ? { + request_id: approval.request_id, + expiration_ts: approval.request_status.content.expiration_ts, + } + : null, + }; + }); sortableQuotes.push( ...known_quotes.map((quote) => ({ - ...quote, - request_id: null, + quote_with_address: quote, + approval: null, })), ); - return sortMakerApprovals(sortableQuotes); -} - -export function sortMakerApprovals(list: SortableQuoteWithAddress[]) { return ( - _(list) + _(sortableQuotes) .orderBy( [ // Prefer makers that have a 'version' attribute // If we don't have a version, we cannot clarify if it's outdated or not - (m) => (m.version ? 0 : 1), + (m) => (m.quote_with_address.version ? 0 : 1), // Prefer makers with a minimum quantity > 0 - (m) => ((m.quote.min_quantity ?? 0) > 0 ? 0 : 1), + (m) => ((m.quote_with_address.quote.min_quantity ?? 0) > 0 ? 0 : 1), // Prefer makers that are not outdated - (m) => (isMakerVersionOutdated(m.version) ? 1 : 0), + (m) => (isMakerVersionOutdated(m.quote_with_address.version) ? 1 : 0), // Prefer approvals over actual quotes - (m) => (m.request_id ? 0 : 1), + (m) => (m.approval ? 0 : 1), // Prefer makers with a lower price - (m) => m.quote.price, + (m) => m.quote_with_address.quote.price, ], ["asc", "asc", "asc", "asc", "asc"], ) // Remove duplicate makers - .uniqBy((m) => m.peer_id) + .uniqBy((m) => m.quote_with_address.peer_id) .value() ); } diff --git a/src-gui/tsconfig.json b/src-gui/tsconfig.json index ec7dd968..3ddd12a0 100644 --- a/src-gui/tsconfig.json +++ b/src-gui/tsconfig.json @@ -15,7 +15,7 @@ "jsx": "react-jsx", /* Linting */ - "strict": false, + "strict": true, "noUnusedLocals": false, "noUnusedParameters": false, "noFallthroughCasesInSwitch": false, diff --git a/src-gui/yarn.lock b/src-gui/yarn.lock index b76576b7..d916b5bd 100644 --- a/src-gui/yarn.lock +++ b/src-gui/yarn.lock @@ -531,19 +531,7 @@ integrity sha512-upzCtG6awpL6noEZlJ5Z01khZ9VnLNLaj7tb6iPbN6G97eYfUTs8e9OyPKy3rEms3VQWmVBfri7jzeaRxdFIzA== dependencies: "@babel/runtime" "^7.28.2" - -"@mui/lab@^7.0.0-beta.13": - version "7.0.0-beta.16" - resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-7.0.0-beta.16.tgz#99045e2840c3f4db0383cdcc477af8c7b60c83a2" - integrity sha512-YiyDU84F6ujjaa5xuItuXa40KN1aPC+8PBkP2OAOJGO2MMvdEicuvkEfVSnikH6uLHtKOwGzOeqEqrfaYxcOxw== - dependencies: - "@babel/runtime" "^7.28.2" - "@mui/system" "^7.3.1" - "@mui/types" "^7.4.5" - "@mui/utils" "^7.3.1" - clsx "^2.1.1" - prop-types "^15.8.1" - + "@mui/material@^7.1.1": version "7.3.1" resolved "https://registry.yarnpkg.com/@mui/material/-/material-7.3.1.tgz#bd1bf1344cc7a69b6e459248b544f0ae97945b1d" diff --git a/swap-core/src/monero/primitives.rs b/swap-core/src/monero/primitives.rs index 5c1e4176..128a91e2 100644 --- a/swap-core/src/monero/primitives.rs +++ b/swap-core/src/monero/primitives.rs @@ -216,6 +216,7 @@ impl Amount { #[typeshare] pub struct LabeledMoneroAddress { // If this is None, we will use an address of the internal Monero wallet + // TODO: This should be string | null but typeshare cannot do that yet #[typeshare(serialized_as = "string")] address: Option, #[typeshare(serialized_as = "number")] diff --git a/swap/src/common/tracing_util.rs b/swap/src/common/tracing_util.rs index 2eb0fd9a..3b127a27 100644 --- a/swap/src/common/tracing_util.rs +++ b/swap/src/common/tracing_util.rs @@ -78,9 +78,7 @@ pub fn init( let tracing_file_layer = json_rolling_layer!( &dir, "tracing", - env_filter_with_all_crates(vec![ - (crates::OUR_CRATES.to_vec(), LevelFilter::TRACE) - ]), + env_filter_with_all_crates(vec![(crates::OUR_CRATES.to_vec(), LevelFilter::TRACE)]), 24 ); @@ -104,7 +102,10 @@ pub fn init( let monero_wallet_file_layer = json_rolling_layer!( &dir, "tracing-monero-wallet", - env_filter_with_all_crates(vec![(crates::MONERO_WALLET_CRATES.to_vec(), LevelFilter::TRACE)]), + env_filter_with_all_crates(vec![( + crates::MONERO_WALLET_CRATES.to_vec(), + LevelFilter::TRACE + )]), 24 ); @@ -145,7 +146,9 @@ pub fn init( (crates::LIBP2P_CRATES.to_vec(), LevelFilter::INFO), (crates::TOR_CRATES.to_vec(), LevelFilter::INFO), ])?, - false => env_filter_with_all_crates(vec![(crates::OUR_CRATES.to_vec(), LevelFilter::INFO)])?, + false => { + env_filter_with_all_crates(vec![(crates::OUR_CRATES.to_vec(), LevelFilter::INFO)])? + } }; let final_terminal_layer = match format { @@ -227,10 +230,7 @@ mod crates { "unstoppableswap_gui_rs", ]; - pub const MONERO_WALLET_CRATES: &[&str] = &[ - "monero_cpp", - "monero_rpc_pool", - ]; + pub const MONERO_WALLET_CRATES: &[&str] = &["monero_cpp", "monero_rpc_pool"]; } /// A writer that forwards tracing log messages to the tauri guest. @@ -275,4 +275,4 @@ impl std::io::Write for TauriWriter { // No-op, we don't need to flush anything Ok(()) } -} \ No newline at end of file +}