feat: swap history tauri connector

This commit is contained in:
binarybaron 2024-08-08 12:02:59 +02:00
parent cdd6635c8f
commit 2e1b6f6b43
No known key found for this signature in database
GPG Key ID: 99B75D3E1476A26E
22 changed files with 1315 additions and 1297 deletions

103
Cargo.lock generated
View File

@ -1601,15 +1601,6 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "encoding_rs"
version = "0.8.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "enum-as-inner" name = "enum-as-inner"
version = "0.3.4" version = "0.3.4"
@ -2763,25 +2754,6 @@ dependencies = [
"nom", "nom",
] ]
[[package]]
name = "is-docker"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
dependencies = [
"once_cell",
]
[[package]]
name = "is-wsl"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
dependencies = [
"is-docker",
"once_cell",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.5" version = "0.10.5"
@ -4089,17 +4061,6 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "open"
version = "5.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d2c909a3fce3bd80efef4cd1c6c056bd9376a8fe06fcfdbebaf32cb485a7e37"
dependencies = [
"is-wsl",
"libc",
"pathdiff",
]
[[package]] [[package]]
name = "open-metrics-client" name = "open-metrics-client"
version = "0.14.0" version = "0.14.0"
@ -4135,16 +4096,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "os_pipe"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29d73ba8daf8fac13b0501d1abeddcfe21ba7401ada61a819144b6c2a4f32209"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"
@ -5932,16 +5883,6 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "shared_child"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0d94659ad3c2137fef23ae75b03d5241d633f8acded53d672decfa0e6e0caef"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "shell-words" name = "shell-words"
version = "1.1.0" version = "1.1.0"
@ -6729,44 +6670,6 @@ dependencies = [
"tauri-utils", "tauri-utils",
] ]
[[package]]
name = "tauri-plugin"
version = "2.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51a5c65ab8536a7e27b70ecbb0713ab42e8508acd9af1bc4a0817ccf7caf3165"
dependencies = [
"anyhow",
"glob",
"plist",
"schemars",
"serde",
"serde_json",
"tauri-utils",
"toml 0.8.2",
"walkdir",
]
[[package]]
name = "tauri-plugin-shell"
version = "2.0.0-rc.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9209f6c32caec61e156a5616f7d80ba7683ca4a0a5641cbe5d3086ab371aaab2"
dependencies = [
"encoding_rs",
"log",
"open",
"os_pipe",
"regex",
"schemars",
"serde",
"serde_json",
"shared_child",
"tauri",
"tauri-plugin",
"thiserror",
"tokio",
]
[[package]] [[package]]
name = "tauri-runtime" name = "tauri-runtime"
version = "2.0.0-rc.1" version = "2.0.0-rc.1"
@ -7683,7 +7586,7 @@ dependencies = [
"swap", "swap",
"tauri", "tauri",
"tauri-build", "tauri-build",
"tauri-plugin-shell", "uuid",
] ]
[[package]] [[package]]
@ -7731,9 +7634,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.9.1" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314"
dependencies = [ dependencies = [
"getrandom 0.2.15", "getrandom 0.2.15",
"serde", "serde",

View File

@ -1,24 +1,24 @@
import { piconerosToXmr, satsToBtc } from 'utils/conversionUtils'; import { piconerosToXmr, satsToBtc } from "utils/conversionUtils";
import { exhaustiveGuard } from 'utils/typescriptUtils'; import { exhaustiveGuard } from "utils/typescriptUtils";
export enum RpcMethod { export enum RpcMethod {
GET_BTC_BALANCE = 'get_bitcoin_balance', GET_BTC_BALANCE = "get_bitcoin_balance",
WITHDRAW_BTC = 'withdraw_btc', WITHDRAW_BTC = "withdraw_btc",
BUY_XMR = 'buy_xmr', BUY_XMR = "buy_xmr",
RESUME_SWAP = 'resume_swap', RESUME_SWAP = "resume_swap",
LIST_SELLERS = 'list_sellers', LIST_SELLERS = "list_sellers",
CANCEL_REFUND_SWAP = 'cancel_refund_swap', CANCEL_REFUND_SWAP = "cancel_refund_swap",
GET_SWAP_INFO = 'get_swap_info', GET_SWAP_INFO = "get_swap_info",
SUSPEND_CURRENT_SWAP = 'suspend_current_swap', SUSPEND_CURRENT_SWAP = "suspend_current_swap",
GET_HISTORY = 'get_history', GET_HISTORY = "get_history",
GET_MONERO_RECOVERY_KEYS = 'get_monero_recovery_info', GET_MONERO_RECOVERY_KEYS = "get_monero_recovery_info",
} }
export enum RpcProcessStateType { export enum RpcProcessStateType {
STARTED = 'starting...', STARTED = "starting...",
LISTENING_FOR_CONNECTIONS = 'running', LISTENING_FOR_CONNECTIONS = "running",
EXITED = 'exited', EXITED = "exited",
NOT_STARTED = 'not started', NOT_STARTED = "not started",
} }
export type RawRpcResponseSuccess<T> = { export type RawRpcResponseSuccess<T> = {
@ -38,13 +38,13 @@ export type RawRpcResponse<T> = RawRpcResponseSuccess<T> | RawRpcResponseError;
export function isSuccessResponse<T>( export function isSuccessResponse<T>(
response: RawRpcResponse<T>, response: RawRpcResponse<T>,
): response is RawRpcResponseSuccess<T> { ): response is RawRpcResponseSuccess<T> {
return 'result' in response; return "result" in response;
} }
export function isErrorResponse<T>( export function isErrorResponse<T>(
response: RawRpcResponse<T>, response: RawRpcResponse<T>,
): response is RawRpcResponseError { ): response is RawRpcResponseError {
return 'error' in response; return "error" in response;
} }
export interface RpcSellerStatus { export interface RpcSellerStatus {
@ -56,7 +56,7 @@ export interface RpcSellerStatus {
max_quantity: number; max_quantity: number;
}; };
} }
| 'Unreachable'; | "Unreachable";
multiaddr: string; multiaddr: string;
} }
@ -80,7 +80,7 @@ export type SwapTimelockInfoCancelled = {
}; };
}; };
export type SwapTimelockInfoPunished = 'Punish'; export type SwapTimelockInfoPunished = "Punish";
export type SwapTimelockInfo = export type SwapTimelockInfo =
| SwapTimelockInfoNone | SwapTimelockInfoNone
@ -90,19 +90,19 @@ export type SwapTimelockInfo =
export function isSwapTimelockInfoNone( export function isSwapTimelockInfoNone(
info: SwapTimelockInfo, info: SwapTimelockInfo,
): info is SwapTimelockInfoNone { ): info is SwapTimelockInfoNone {
return typeof info === 'object' && 'None' in info; return typeof info === "object" && "None" in info;
} }
export function isSwapTimelockInfoCancelled( export function isSwapTimelockInfoCancelled(
info: SwapTimelockInfo, info: SwapTimelockInfo,
): info is SwapTimelockInfoCancelled { ): info is SwapTimelockInfoCancelled {
return typeof info === 'object' && 'Cancel' in info; return typeof info === "object" && "Cancel" in info;
} }
export function isSwapTimelockInfoPunished( export function isSwapTimelockInfoPunished(
info: SwapTimelockInfo, info: SwapTimelockInfo,
): info is SwapTimelockInfoPunished { ): info is SwapTimelockInfoPunished {
return info === 'Punish'; return info === "Punish";
} }
export type SwapSellerInfo = { export type SwapSellerInfo = {
@ -111,21 +111,21 @@ export type SwapSellerInfo = {
}; };
export interface GetSwapInfoResponse { export interface GetSwapInfoResponse {
swapId: string; swap_id: string;
completed: boolean; completed: boolean;
seller: SwapSellerInfo; seller: SwapSellerInfo;
startDate: string; start_date: string;
stateName: SwapStateName; state_name: SwapStateName;
timelock: null | SwapTimelockInfo; timelock: null | SwapTimelockInfo;
txLockId: string; tx_lock_id: string;
txCancelFee: number; tx_cancel_fee: number;
txRefundFee: number; tx_refund_fee: number;
txLockFee: number; tx_lock_fee: number;
btcAmount: number; btc_amount: number;
xmrAmount: number; xmr_amount: number;
btcRefundAddress: string; btc_refund_address: string;
cancelTimelock: number; cancel_timelock: number;
punishTimelock: number; punish_timelock: number;
} }
export type MoneroRecoveryResponse = { export type MoneroRecoveryResponse = {
@ -144,19 +144,19 @@ export interface GetHistoryResponse {
} }
export enum SwapStateName { export enum SwapStateName {
Started = 'quote has been requested', Started = "quote has been requested",
SwapSetupCompleted = 'execution setup done', SwapSetupCompleted = "execution setup done",
BtcLocked = 'btc is locked', BtcLocked = "btc is locked",
XmrLockProofReceived = 'XMR lock transaction transfer proof received', XmrLockProofReceived = "XMR lock transaction transfer proof received",
XmrLocked = 'xmr is locked', XmrLocked = "xmr is locked",
EncSigSent = 'encrypted signature is sent', EncSigSent = "encrypted signature is sent",
BtcRedeemed = 'btc is redeemed', BtcRedeemed = "btc is redeemed",
CancelTimelockExpired = 'cancel timelock is expired', CancelTimelockExpired = "cancel timelock is expired",
BtcCancelled = 'btc is cancelled', BtcCancelled = "btc is cancelled",
BtcRefunded = 'btc is refunded', BtcRefunded = "btc is refunded",
XmrRedeemed = 'xmr is redeemed', XmrRedeemed = "xmr is redeemed",
BtcPunished = 'btc is punished', BtcPunished = "btc is punished",
SafelyAborted = 'safely aborted', SafelyAborted = "safely aborted",
} }
export type SwapStateNameRunningSwap = Exclude< export type SwapStateNameRunningSwap = Exclude<
@ -275,7 +275,7 @@ export function isSwapStateNamePossiblyRefundableSwap(
export function isGetSwapInfoResponseRunningSwap( export function isGetSwapInfoResponseRunningSwap(
response: GetSwapInfoResponse, response: GetSwapInfoResponse,
): response is GetSwapInfoResponseRunningSwap { ): response is GetSwapInfoResponseRunningSwap {
return isSwapStateNameRunningSwap(response.stateName); return isSwapStateNameRunningSwap(response.state_name);
} }
export function isSwapMoneroRecoverable(swapStateName: SwapStateName): boolean { export function isSwapMoneroRecoverable(swapStateName: SwapStateName): boolean {
@ -286,46 +286,46 @@ export function isSwapMoneroRecoverable(swapStateName: SwapStateName): boolean {
export function getHumanReadableDbStateType(type: SwapStateName): string { export function getHumanReadableDbStateType(type: SwapStateName): string {
switch (type) { switch (type) {
case SwapStateName.Started: case SwapStateName.Started:
return 'Quote has been requested'; return "Quote has been requested";
case SwapStateName.SwapSetupCompleted: case SwapStateName.SwapSetupCompleted:
return 'Swap has been initiated'; return "Swap has been initiated";
case SwapStateName.BtcLocked: case SwapStateName.BtcLocked:
return 'Bitcoin has been locked'; return "Bitcoin has been locked";
case SwapStateName.XmrLockProofReceived: case SwapStateName.XmrLockProofReceived:
return 'Monero lock transaction transfer proof has been received'; return "Monero lock transaction transfer proof has been received";
case SwapStateName.XmrLocked: case SwapStateName.XmrLocked:
return 'Monero has been locked'; return "Monero has been locked";
case SwapStateName.EncSigSent: case SwapStateName.EncSigSent:
return 'Encrypted signature has been sent'; return "Encrypted signature has been sent";
case SwapStateName.BtcRedeemed: case SwapStateName.BtcRedeemed:
return 'Bitcoin has been redeemed'; return "Bitcoin has been redeemed";
case SwapStateName.CancelTimelockExpired: case SwapStateName.CancelTimelockExpired:
return 'Cancel timelock has expired'; return "Cancel timelock has expired";
case SwapStateName.BtcCancelled: case SwapStateName.BtcCancelled:
return 'Swap has been cancelled'; return "Swap has been cancelled";
case SwapStateName.BtcRefunded: case SwapStateName.BtcRefunded:
return 'Bitcoin has been refunded'; return "Bitcoin has been refunded";
case SwapStateName.XmrRedeemed: case SwapStateName.XmrRedeemed:
return 'Monero has been redeemed'; return "Monero has been redeemed";
case SwapStateName.BtcPunished: case SwapStateName.BtcPunished:
return 'Bitcoin has been punished'; return "Bitcoin has been punished";
case SwapStateName.SafelyAborted: case SwapStateName.SafelyAborted:
return 'Swap has been safely aborted'; return "Swap has been safely aborted";
default: default:
return exhaustiveGuard(type); return exhaustiveGuard(type);
} }
} }
export function getSwapTxFees(swap: GetSwapInfoResponse): number { export function getSwapTxFees(swap: GetSwapInfoResponse): number {
return satsToBtc(swap.txLockFee); return satsToBtc(swap.tx_lock_fee);
} }
export function getSwapBtcAmount(swap: GetSwapInfoResponse): number { export function getSwapBtcAmount(swap: GetSwapInfoResponse): number {
return satsToBtc(swap.btcAmount); return satsToBtc(swap.btc_amount);
} }
export function getSwapXmrAmount(swap: GetSwapInfoResponse): number { export function getSwapXmrAmount(swap: GetSwapInfoResponse): number {
return piconerosToXmr(swap.xmrAmount); return piconerosToXmr(swap.xmr_amount);
} }
export function getSwapExchangeRate(swap: GetSwapInfoResponse): number { export function getSwapExchangeRate(swap: GetSwapInfoResponse): number {

View File

@ -1,11 +1,11 @@
import { makeStyles } from '@material-ui/core'; import { makeStyles } from "@material-ui/core";
import { Alert, AlertTitle } from '@material-ui/lab'; import { Alert, AlertTitle } from "@material-ui/lab";
import { useActiveSwapInfo } from 'store/hooks'; import { useActiveSwapInfo } from "store/hooks";
import { import {
isSwapTimelockInfoCancelled, isSwapTimelockInfoCancelled,
isSwapTimelockInfoNone, isSwapTimelockInfoNone,
} from 'models/rpcModel'; } from "models/rpcModel";
import HumanizedBitcoinBlockDuration from '../other/HumanizedBitcoinBlockDuration'; import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
outer: { outer: {
@ -33,31 +33,32 @@ export default function SwapMightBeCancelledAlert({
} }
const { timelock } = swap; const { timelock } = swap;
const punishTimelockOffset = swap.punishTimelock; const punishTimelockOffset = swap.punish_timelock;
return ( return (
<Alert severity="warning" className={classes.outer} variant="filled"> <Alert severity="warning" className={classes.outer} variant="filled">
<AlertTitle>Be careful!</AlertTitle> <AlertTitle>Be careful!</AlertTitle>
The swap provider has taken a long time to lock their Monero. This might The swap provider has taken a long time to lock their Monero. This
mean that: might mean that:
<ul className={classes.list}> <ul className={classes.list}>
<li> <li>
There is a technical issue that prevents them from locking their funds There is a technical issue that prevents them from locking
their funds
</li> </li>
<li>They are a malicious actor (unlikely)</li> <li>They are a malicious actor (unlikely)</li>
</ul> </ul>
<br /> <br />
There is still hope for the swap to be successful but you have to be extra There is still hope for the swap to be successful but you have to be
careful. Regardless of why it has taken them so long, it is important that extra careful. Regardless of why it has taken them so long, it is
you refund the swap within the required time period if the swap is not important that you refund the swap within the required time period
completed. If you fail to to do so, you will be punished and lose your if the swap is not completed. If you fail to to do so, you will be
money. punished and lose your money.
<ul className={classes.list}> <ul className={classes.list}>
{isSwapTimelockInfoNone(timelock) && ( {isSwapTimelockInfoNone(timelock) && (
<> <>
<li> <li>
<strong> <strong>
You will be able to refund in about{' '} You will be able to refund in about{" "}
<HumanizedBitcoinBlockDuration <HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left} blocks={timelock.None.blocks_left}
/> />
@ -66,9 +67,13 @@ export default function SwapMightBeCancelledAlert({
<li> <li>
<strong> <strong>
If you have not refunded or completed the swap in about{' '} If you have not refunded or completed the swap
in about{" "}
<HumanizedBitcoinBlockDuration <HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left + punishTimelockOffset} blocks={
timelock.None.blocks_left +
punishTimelockOffset
}
/> />
, you will lose your funds. , you will lose your funds.
</strong> </strong>
@ -78,7 +83,8 @@ export default function SwapMightBeCancelledAlert({
{isSwapTimelockInfoCancelled(timelock) && ( {isSwapTimelockInfoCancelled(timelock) && (
<li> <li>
<strong> <strong>
If you have not refunded or completed the swap in about{' '} If you have not refunded or completed the swap in
about{" "}
<HumanizedBitcoinBlockDuration <HumanizedBitcoinBlockDuration
blocks={timelock.Cancel.blocks_left} blocks={timelock.Cancel.blocks_left}
/> />
@ -88,8 +94,8 @@ export default function SwapMightBeCancelledAlert({
)} )}
<li> <li>
As long as you see this screen, the swap will be refunded As long as you see this screen, the swap will be refunded
automatically when the time comes. If this fails, you have to manually automatically when the time comes. If this fails, you have
refund by navigating to the History page. to manually refund by navigating to the History page.
</li> </li>
</ul> </ul>
</Alert> </Alert>

View File

@ -1,12 +1,12 @@
import { Alert, AlertTitle } from '@material-ui/lab/'; import { Alert, AlertTitle } from "@material-ui/lab/";
import { Box, makeStyles } from '@material-ui/core'; import { Box, makeStyles } from "@material-ui/core";
import { ReactNode } from 'react'; import { ReactNode } from "react";
import { exhaustiveGuard } from 'utils/typescriptUtils'; import { exhaustiveGuard } from "utils/typescriptUtils";
import { import {
SwapCancelRefundButton, SwapCancelRefundButton,
SwapResumeButton, SwapResumeButton,
} from '../pages/history/table/HistoryRowActions'; } from "../pages/history/table/HistoryRowActions";
import HumanizedBitcoinBlockDuration from '../other/HumanizedBitcoinBlockDuration'; import HumanizedBitcoinBlockDuration from "../other/HumanizedBitcoinBlockDuration";
import { import {
GetSwapInfoResponse, GetSwapInfoResponse,
GetSwapInfoResponseRunningSwap, GetSwapInfoResponseRunningSwap,
@ -17,18 +17,18 @@ import {
SwapStateName, SwapStateName,
SwapTimelockInfoCancelled, SwapTimelockInfoCancelled,
SwapTimelockInfoNone, SwapTimelockInfoNone,
} from '../../../models/rpcModel'; } from "../../../models/rpcModel";
import { SwapMoneroRecoveryButton } from '../pages/history/table/SwapMoneroRecoveryButton'; import { SwapMoneroRecoveryButton } from "../pages/history/table/SwapMoneroRecoveryButton";
const useStyles = makeStyles({ const useStyles = makeStyles({
box: { box: {
display: 'flex', display: "flex",
flexDirection: 'column', flexDirection: "column",
gap: '0.5rem', gap: "0.5rem",
}, },
list: { list: {
padding: '0px', padding: "0px",
margin: '0px', margin: "0px",
}, },
}); });
@ -60,13 +60,17 @@ const BitcoinRedeemedStateAlert = ({ swap }: { swap: GetSwapInfoResponse }) => {
<Box className={classes.box}> <Box className={classes.box}>
<MessageList <MessageList
messages={[ messages={[
'The Bitcoin has been redeemed by the other party', "The Bitcoin has been redeemed by the other party",
'There is no risk of losing funds. You can take your time', "There is no risk of losing funds. You can take your time",
'The Monero will be automatically redeemed to the address you provided as soon as you resume the swap', "The Monero will be automatically redeemed to the address you provided as soon as you resume the swap",
'If this step fails, you can manually redeem the funds', "If this step fails, you can manually redeem the funds",
]} ]}
/> />
<SwapMoneroRecoveryButton swap={swap} size="small" variant="contained" /> <SwapMoneroRecoveryButton
swap={swap}
size="small"
variant="contained"
/>
</Box> </Box>
); );
}; };
@ -87,13 +91,16 @@ const BitcoinLockedNoTimelockExpiredStateAlert = ({
<MessageList <MessageList
messages={[ messages={[
<> <>
Your Bitcoin is locked. If the swap is not completed in approximately{' '} Your Bitcoin is locked. If the swap is not completed in
<HumanizedBitcoinBlockDuration blocks={timelock.None.blocks_left} />, approximately{" "}
you need to refund <HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left}
/>
, you need to refund
</>, </>,
<> <>
You will lose your funds if you do not refund or complete the swap You will lose your funds if you do not refund or complete the
within{' '} swap within{" "}
<HumanizedBitcoinBlockDuration <HumanizedBitcoinBlockDuration
blocks={timelock.None.blocks_left + punishTimelockOffset} blocks={timelock.None.blocks_left + punishTimelockOffset}
/> />
@ -121,17 +128,21 @@ const BitcoinPossiblyCancelledAlert = ({
<Box className={classes.box}> <Box className={classes.box}>
<MessageList <MessageList
messages={[ messages={[
'The swap was cancelled because it did not complete in time', "The swap was cancelled because it did not complete in time",
'You must resume the swap immediately to refund your Bitcoin. If that fails, you can manually refund it', "You must resume the swap immediately to refund your Bitcoin. If that fails, you can manually refund it",
<> <>
You will lose your funds if you do not refund within{' '} You will lose your funds if you do not refund within{" "}
<HumanizedBitcoinBlockDuration <HumanizedBitcoinBlockDuration
blocks={timelock.Cancel.blocks_left} blocks={timelock.Cancel.blocks_left}
/> />
</>, </>,
]} ]}
/> />
<SwapCancelRefundButton swap={swap} size="small" variant="contained" /> <SwapCancelRefundButton
swap={swap}
size="small"
variant="contained"
/>
</Box> </Box>
); );
}; };
@ -154,7 +165,7 @@ function SwapAlertStatusText({
}: { }: {
swap: GetSwapInfoResponseRunningSwap; swap: GetSwapInfoResponseRunningSwap;
}) { }) {
switch (swap.stateName) { switch (swap.state_name) {
// This is the state where the swap is safe because the other party has redeemed the Bitcoin // This is the state where the swap is safe because the other party has redeemed the Bitcoin
// It cannot be punished anymore // It cannot be punished anymore
case SwapStateName.BtcRedeemed: case SwapStateName.BtcRedeemed:
@ -172,7 +183,7 @@ function SwapAlertStatusText({
if (isSwapTimelockInfoNone(swap.timelock)) { if (isSwapTimelockInfoNone(swap.timelock)) {
return ( return (
<BitcoinLockedNoTimelockExpiredStateAlert <BitcoinLockedNoTimelockExpiredStateAlert
punishTimelockOffset={swap.punishTimelock} punishTimelockOffset={swap.punish_timelock}
timelock={swap.timelock} timelock={swap.timelock}
/> />
); );
@ -197,7 +208,7 @@ function SwapAlertStatusText({
} }
return <ImmediateActionAlert />; return <ImmediateActionAlert />;
default: default:
return exhaustiveGuard(swap.stateName); return exhaustiveGuard(swap.state_name);
} }
} }
@ -219,13 +230,13 @@ export default function SwapStatusAlert({
return ( return (
<Alert <Alert
key={swap.swapId} key={swap.swap_id}
severity="warning" severity="warning"
action={<SwapResumeButton swap={swap} />} action={<SwapResumeButton swap={swap} />}
variant="filled" variant="filled"
> >
<AlertTitle> <AlertTitle>
Swap {swap.swapId.substring(0, 5)}... is unfinished Swap {swap.swap_id.substring(0, 5)}... is unfinished
</AlertTitle> </AlertTitle>
<SwapAlertStatusText swap={swap} /> <SwapAlertStatusText swap={swap} />
</Alert> </Alert>

View File

@ -1,11 +1,11 @@
import { Box, makeStyles } from '@material-ui/core'; import { Box, makeStyles } from "@material-ui/core";
import { useSwapInfosSortedByDate } from 'store/hooks'; import { useSwapInfosSortedByDate } from "store/hooks";
import SwapStatusAlert from './SwapStatusAlert'; import SwapStatusAlert from "./SwapStatusAlert";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
outer: { outer: {
display: 'flex', display: "flex",
flexDirection: 'column', flexDirection: "column",
gap: theme.spacing(1), gap: theme.spacing(1),
}, },
})); }));
@ -21,7 +21,7 @@ export default function SwapTxLockAlertsBox() {
return ( return (
<Box className={classes.outer}> <Box className={classes.outer}>
{swaps.map((swap) => ( {swaps.map((swap) => (
<SwapStatusAlert key={swap.swapId} swap={swap} /> <SwapStatusAlert key={swap.swap_id} swap={swap} />
))} ))}
</Box> </Box>
); );

View File

@ -9,28 +9,25 @@ import {
MenuItem, MenuItem,
Select, Select,
TextField, TextField,
} from '@material-ui/core'; } from "@material-ui/core";
import { useState } from 'react'; import { useState } from "react";
import { useSnackbar } from 'notistack'; import { useSnackbar } from "notistack";
import { import { useActiveSwapInfo, useAppSelector } from "store/hooks";
useActiveSwapInfo, import { parseDateString } from "utils/parseUtils";
useAppSelector, import { store } from "renderer/store/storeRenderer";
} from 'store/hooks'; import { CliLog } from "models/cliModel";
import { parseDateString } from 'utils/parseUtils'; import { submitFeedbackViaHttp } from "../../../api";
import { store } from 'renderer/store/storeRenderer'; import { PiconeroAmount } from "../../other/Units";
import { CliLog } from 'models/cliModel'; import LoadingButton from "../../other/LoadingButton";
import { submitFeedbackViaHttp } from '../../../api';
import { PiconeroAmount } from '../../other/Units';
import LoadingButton from '../../other/LoadingButton';
async function submitFeedback(body: string, swapId: string | number) { async function submitFeedback(body: string, swapId: string | number) {
let attachedBody = ''; let attachedBody = "";
if (swapId !== 0 && typeof swapId === 'string') { if (swapId !== 0 && typeof swapId === "string") {
const swapInfo = store.getState().rpc.state.swapInfos[swapId]; const swapInfo = store.getState().rpc.state.swapInfos[swapId];
const logs = [] as CliLog[]; const logs = [] as CliLog[];
throw new Error('Not implemented'); throw new Error("Not implemented");
if (swapInfo === undefined) { if (swapInfo === undefined) {
throw new Error(`Swap with id ${swapId} not found`); throw new Error(`Swap with id ${swapId} not found`);
@ -38,7 +35,7 @@ async function submitFeedback(body: string, swapId: string | number) {
attachedBody = `${JSON.stringify(swapInfo, null, 4)} \n\nLogs: ${logs attachedBody = `${JSON.stringify(swapInfo, null, 4)} \n\nLogs: ${logs
.map((l) => JSON.stringify(l)) .map((l) => JSON.stringify(l))
.join('\n====\n')}`; .join("\n====\n")}`;
} }
await submitFeedbackViaHttp(body, attachedBody); await submitFeedbackViaHttp(body, attachedBody);
@ -70,10 +67,11 @@ function SwapSelectDropDown({
> >
<MenuItem value={0}>Do not attach logs</MenuItem> <MenuItem value={0}>Do not attach logs</MenuItem>
{swaps.map((swap) => ( {swaps.map((swap) => (
<MenuItem value={swap.swapId}> <MenuItem value={swap.swap_id}>
Swap {swap.swapId.substring(0, 5)}... from{' '} Swap {swap.swap_id.substring(0, 5)}... from{" "}
{new Date(parseDateString(swap.startDate)).toDateString()} ( {new Date(parseDateString(swap.start_date)).toDateString()}{" "}
<PiconeroAmount amount={swap.xmrAmount} />) (
<PiconeroAmount amount={swap.xmr_amount} />)
</MenuItem> </MenuItem>
))} ))}
</Select> </Select>
@ -90,14 +88,14 @@ export default function FeedbackDialog({
onClose: () => void; onClose: () => void;
}) { }) {
const [pending, setPending] = useState(false); const [pending, setPending] = useState(false);
const [bodyText, setBodyText] = useState(''); const [bodyText, setBodyText] = useState("");
const currentSwapId = useActiveSwapInfo(); const currentSwapId = useActiveSwapInfo();
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
const [selectedAttachedSwap, setSelectedAttachedSwap] = useState< const [selectedAttachedSwap, setSelectedAttachedSwap] = useState<
string | number string | number
>(currentSwapId?.swapId || 0); >(currentSwapId?.swap_id || 0);
const bodyTooLong = bodyText.length > MAX_FEEDBACK_LENGTH; const bodyTooLong = bodyText.length > MAX_FEEDBACK_LENGTH;
@ -106,13 +104,19 @@ export default function FeedbackDialog({
<DialogTitle>Submit Feedback</DialogTitle> <DialogTitle>Submit Feedback</DialogTitle>
<DialogContent> <DialogContent>
<DialogContentText> <DialogContentText>
Got something to say? Drop us a message below. If you had an issue Got something to say? Drop us a message below. If you had an
with a specific swap, select it from the dropdown to attach the logs. issue with a specific swap, select it from the dropdown to
It will help us figure out what went wrong. Hit that submit button attach the logs. It will help us figure out what went wrong.
when you are ready. We appreciate you taking the time to share your Hit that submit button when you are ready. We appreciate you
thoughts! taking the time to share your thoughts!
</DialogContentText> </DialogContentText>
<Box style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}> <Box
style={{
display: "flex",
flexDirection: "column",
gap: "1rem",
}}
>
<TextField <TextField
variant="outlined" variant="outlined"
value={bodyText} value={bodyText}
@ -120,7 +124,7 @@ export default function FeedbackDialog({
label={ label={
bodyTooLong bodyTooLong
? `Text is too long (${bodyText.length}/${MAX_FEEDBACK_LENGTH})` ? `Text is too long (${bodyText.length}/${MAX_FEEDBACK_LENGTH})`
: 'Feedback' : "Feedback"
} }
multiline multiline
minRows={4} minRows={4}
@ -146,15 +150,24 @@ export default function FeedbackDialog({
try { try {
setPending(true); setPending(true);
await submitFeedback(bodyText, selectedAttachedSwap); await submitFeedback(
enqueueSnackbar('Feedback submitted successfully!', { bodyText,
variant: 'success', selectedAttachedSwap,
}); );
enqueueSnackbar(
"Feedback submitted successfully!",
{
variant: "success",
},
);
} catch (e) { } catch (e) {
console.error(`Failed to submit feedback: ${e}`); console.error(`Failed to submit feedback: ${e}`);
enqueueSnackbar(`Failed to submit feedback (${e})`, { enqueueSnackbar(
variant: 'error', `Failed to submit feedback (${e})`,
}); {
variant: "error",
},
);
} finally { } finally {
setPending(false); setPending(false);
} }

View File

@ -1,12 +1,12 @@
import { Step, StepLabel, Stepper, Typography } from '@material-ui/core'; import { Step, StepLabel, Stepper, Typography } from "@material-ui/core";
import { SwapSpawnType } from 'models/cliModel'; import { SwapSpawnType } from "models/cliModel";
import { SwapStateName } from 'models/rpcModel'; import { SwapStateName } from "models/rpcModel";
import { useActiveSwapInfo, useAppSelector } from 'store/hooks'; import { useActiveSwapInfo, useAppSelector } from "store/hooks";
import { exhaustiveGuard } from 'utils/typescriptUtils'; import { exhaustiveGuard } from "utils/typescriptUtils";
export enum PathType { export enum PathType {
HAPPY_PATH = 'happy path', HAPPY_PATH = "happy path",
UNHAPPY_PATH = 'unhappy path', UNHAPPY_PATH = "unhappy path",
} }
function getActiveStep( function getActiveStep(
@ -150,9 +150,12 @@ function UnhappyPathStepper({
export default function SwapStateStepper() { export default function SwapStateStepper() {
const currentSwapSpawnType = useAppSelector((s) => s.swap.spawnType); const currentSwapSpawnType = useAppSelector((s) => s.swap.spawnType);
const stateName = useActiveSwapInfo()?.stateName ?? null; const stateName = useActiveSwapInfo()?.state_name ?? null;
const processExited = useAppSelector((s) => !s.swap.processRunning); const processExited = useAppSelector((s) => !s.swap.processRunning);
const [pathType, activeStep, error] = getActiveStep(stateName, processExited); const [pathType, activeStep, error] = getActiveStep(
stateName,
processExited,
);
// If the current swap is being manually cancelled and refund, we want to show the unhappy path even though the current state is not a "unhappy" state // If the current swap is being manually cancelled and refund, we want to show the unhappy path even though the current state is not a "unhappy" state
if (currentSwapSpawnType === SwapSpawnType.CANCEL_REFUND) { if (currentSwapSpawnType === SwapSpawnType.CANCEL_REFUND) {

View File

@ -1,8 +1,8 @@
import { Box, DialogContentText } from '@material-ui/core'; import { Box, DialogContentText } from "@material-ui/core";
import { SwapStateBtcRefunded } from 'models/storeModel'; import { SwapStateBtcRefunded } from "models/storeModel";
import { useActiveSwapInfo } from 'store/hooks'; import { useActiveSwapInfo } from "store/hooks";
import BitcoinTransactionInfoBox from '../../BitcoinTransactionInfoBox'; import BitcoinTransactionInfoBox from "../../BitcoinTransactionInfoBox";
import FeedbackInfoBox from '../../../../pages/help/FeedbackInfoBox'; import FeedbackInfoBox from "../../../../pages/help/FeedbackInfoBox";
export default function BitcoinRefundedPage({ export default function BitcoinRefundedPage({
state, state,
@ -11,21 +11,22 @@ export default function BitcoinRefundedPage({
}) { }) {
const swap = useActiveSwapInfo(); const swap = useActiveSwapInfo();
const additionalContent = swap const additionalContent = swap
? `Refund address: ${swap.btcRefundAddress}` ? `Refund address: ${swap.btc_refund_address}`
: null; : null;
return ( return (
<Box> <Box>
<DialogContentText> <DialogContentText>
Unfortunately, the swap was not successful. However, rest assured that Unfortunately, the swap was not successful. However, rest
all your Bitcoin has been refunded to the specified address. The swap assured that all your Bitcoin has been refunded to the specified
process is now complete, and you are free to exit the application. address. The swap process is now complete, and you are free to
exit the application.
</DialogContentText> </DialogContentText>
<Box <Box
style={{ style={{
display: 'flex', display: "flex",
flexDirection: 'column', flexDirection: "column",
gap: '0.5rem', gap: "0.5rem",
}} }}
> >
{state && ( {state && (

View File

@ -1,8 +1,8 @@
import { Box, DialogContentText } from '@material-ui/core'; import { Box, DialogContentText } from "@material-ui/core";
import { useActiveSwapInfo, useAppSelector } from 'store/hooks'; import { useActiveSwapInfo, useAppSelector } from "store/hooks";
import { SwapStateProcessExited } from 'models/storeModel'; import { SwapStateProcessExited } from "models/storeModel";
import CliLogsBox from '../../../../other/RenderedCliLog'; import CliLogsBox from "../../../../other/RenderedCliLog";
import { SwapSpawnType } from 'models/cliModel'; import { SwapSpawnType } from "models/cliModel";
export default function ProcessExitedAndNotDonePage({ export default function ProcessExitedAndNotDonePage({
state, state,
@ -22,30 +22,30 @@ export default function ProcessExitedAndNotDonePage({
messages.push( messages.push(
isCancelRefund isCancelRefund
? 'The manual cancel and refund was unsuccessful.' ? "The manual cancel and refund was unsuccessful."
: 'The swap exited unexpectedly without completing.', : "The swap exited unexpectedly without completing.",
); );
if (!hasSwap && !isCancelRefund) { if (!hasSwap && !isCancelRefund) {
messages.push('No funds were locked.'); messages.push("No funds were locked.");
} }
messages.push( messages.push(
hasRpcError hasRpcError
? 'Check the error and the logs below for more information.' ? "Check the error and the logs below for more information."
: 'Check the logs below for more information.', : "Check the logs below for more information.",
); );
if (hasSwap) { if (hasSwap) {
messages.push(`The swap is in the "${swap.stateName}" state.`); messages.push(`The swap is in the "${swap.state_name}" state.`);
if (!isCancelRefund) { if (!isCancelRefund) {
messages.push( messages.push(
'Try resuming the swap or attempt to initiate a manual cancel and refund.', "Try resuming the swap or attempt to initiate a manual cancel and refund.",
); );
} }
} }
return messages.join(' '); return messages.join(" ");
} }
return ( return (
@ -53,9 +53,9 @@ export default function ProcessExitedAndNotDonePage({
<DialogContentText>{getText()}</DialogContentText> <DialogContentText>{getText()}</DialogContentText>
<Box <Box
style={{ style={{
display: 'flex', display: "flex",
flexDirection: 'column', flexDirection: "column",
gap: '0.5rem', gap: "0.5rem",
}} }}
> >
{state.rpcError && ( {state.rpcError && (

View File

@ -1,17 +1,17 @@
import { useActiveSwapInfo } from 'store/hooks'; import { useActiveSwapInfo } from "store/hooks";
import { SwapStateName } from 'models/rpcModel'; import { SwapStateName } from "models/rpcModel";
import { import {
isSwapStateBtcPunished, isSwapStateBtcPunished,
isSwapStateBtcRefunded, isSwapStateBtcRefunded,
isSwapStateXmrRedeemInMempool, isSwapStateXmrRedeemInMempool,
SwapStateProcessExited, SwapStateProcessExited,
} from '../../../../../../models/storeModel'; } from "../../../../../../models/storeModel";
import XmrRedeemInMempoolPage from '../done/XmrRedeemInMempoolPage'; import XmrRedeemInMempoolPage from "../done/XmrRedeemInMempoolPage";
import BitcoinPunishedPage from '../done/BitcoinPunishedPage'; import BitcoinPunishedPage from "../done/BitcoinPunishedPage";
// eslint-disable-next-line import/no-cycle // eslint-disable-next-line import/no-cycle
import SwapStatePage from '../SwapStatePage'; import SwapStatePage from "../SwapStatePage";
import BitcoinRefundedPage from '../done/BitcoinRefundedPage'; import BitcoinRefundedPage from "../done/BitcoinRefundedPage";
import ProcessExitedAndNotDonePage from './ProcessExitedAndNotDonePage'; import ProcessExitedAndNotDonePage from "./ProcessExitedAndNotDonePage";
type ProcessExitedPageProps = { type ProcessExitedPageProps = {
state: SwapStateProcessExited; state: SwapStateProcessExited;
@ -31,13 +31,13 @@ export default function ProcessExitedPage({ state }: ProcessExitedPageProps) {
// If we don't have a swap state for a "done" state, we should fall back to using the database to display as much information as we can // If we don't have a swap state for a "done" state, we should fall back to using the database to display as much information as we can
if (swap) { if (swap) {
if (swap.stateName === SwapStateName.XmrRedeemed) { if (swap.state_name === SwapStateName.XmrRedeemed) {
return <XmrRedeemInMempoolPage state={null} />; return <XmrRedeemInMempoolPage state={null} />;
} }
if (swap.stateName === SwapStateName.BtcRefunded) { if (swap.state_name === SwapStateName.BtcRefunded) {
return <BitcoinRefundedPage state={null} />; return <BitcoinRefundedPage state={null} />;
} }
if (swap.stateName === SwapStateName.BtcPunished) { if (swap.state_name === SwapStateName.BtcPunished) {
return <BitcoinPunishedPage />; return <BitcoinPunishedPage />;
} }
} }

View File

@ -5,20 +5,20 @@ import {
makeStyles, makeStyles,
TableCell, TableCell,
TableRow, TableRow,
} from '@material-ui/core'; } from "@material-ui/core";
import { useState } from 'react'; import { useState } from "react";
import ArrowForwardIcon from '@material-ui/icons/ArrowForward'; import ArrowForwardIcon from "@material-ui/icons/ArrowForward";
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; import KeyboardArrowDownIcon from "@material-ui/icons/KeyboardArrowDown";
import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; import KeyboardArrowUpIcon from "@material-ui/icons/KeyboardArrowUp";
import { import {
getHumanReadableDbStateType, getHumanReadableDbStateType,
getSwapBtcAmount, getSwapBtcAmount,
getSwapXmrAmount, getSwapXmrAmount,
GetSwapInfoResponse, GetSwapInfoResponse,
} from '../../../../../models/rpcModel'; } from "../../../../../models/rpcModel";
import HistoryRowActions from './HistoryRowActions'; import HistoryRowActions from "./HistoryRowActions";
import HistoryRowExpanded from './HistoryRowExpanded'; import HistoryRowExpanded from "./HistoryRowExpanded";
import { BitcoinAmount, MoneroAmount } from '../../../other/Units'; import { BitcoinAmount, MoneroAmount } from "../../../other/Units";
type HistoryRowProps = { type HistoryRowProps = {
swap: GetSwapInfoResponse; swap: GetSwapInfoResponse;
@ -26,8 +26,8 @@ type HistoryRowProps = {
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
amountTransferContainer: { amountTransferContainer: {
display: 'flex', display: "flex",
alignItems: 'center', alignItems: "center",
gap: theme.spacing(1), gap: theme.spacing(1),
}, },
})); }));
@ -60,15 +60,27 @@ export default function HistoryRow({ swap }: HistoryRowProps) {
<> <>
<TableRow> <TableRow>
<TableCell> <TableCell>
<IconButton size="small" onClick={() => setExpanded(!expanded)}> <IconButton
{expanded ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} size="small"
onClick={() => setExpanded(!expanded)}
>
{expanded ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton> </IconButton>
</TableCell> </TableCell>
<TableCell>{swap.swapId.substring(0, 5)}...</TableCell> <TableCell>{swap.swap_id.substring(0, 5)}...</TableCell>
<TableCell> <TableCell>
<AmountTransfer xmrAmount={xmrAmount} btcAmount={btcAmount} /> <AmountTransfer
xmrAmount={xmrAmount}
btcAmount={btcAmount}
/>
</TableCell>
<TableCell>
{getHumanReadableDbStateType(swap.state_name)}
</TableCell> </TableCell>
<TableCell>{getHumanReadableDbStateType(swap.stateName)}</TableCell>
<TableCell> <TableCell>
<HistoryRowActions swap={swap} /> <HistoryRowActions swap={swap} />
</TableCell> </TableCell>

View File

@ -1,16 +1,16 @@
import { Tooltip } from '@material-ui/core'; import { Tooltip } from "@material-ui/core";
import Button, { ButtonProps } from '@material-ui/core/Button/Button'; import Button, { ButtonProps } from "@material-ui/core/Button/Button";
import DoneIcon from '@material-ui/icons/Done'; import DoneIcon from "@material-ui/icons/Done";
import ErrorIcon from '@material-ui/icons/Error'; import ErrorIcon from "@material-ui/icons/Error";
import { green, red } from '@material-ui/core/colors'; import { green, red } from "@material-ui/core/colors";
import PlayArrowIcon from '@material-ui/icons/PlayArrow'; import PlayArrowIcon from "@material-ui/icons/PlayArrow";
import IpcInvokeButton from '../../../IpcInvokeButton'; import IpcInvokeButton from "../../../IpcInvokeButton";
import { import {
GetSwapInfoResponse, GetSwapInfoResponse,
SwapStateName, SwapStateName,
isSwapStateNamePossiblyCancellableSwap, isSwapStateNamePossiblyCancellableSwap,
isSwapStateNamePossiblyRefundableSwap, isSwapStateNamePossiblyRefundableSwap,
} from '../../../../../models/rpcModel'; } from "../../../../../models/rpcModel";
export function SwapResumeButton({ export function SwapResumeButton({
swap, swap,
@ -22,7 +22,7 @@ export function SwapResumeButton({
color="primary" color="primary"
disabled={swap.completed} disabled={swap.completed}
ipcChannel="spawn-resume-swap" ipcChannel="spawn-resume-swap"
ipcArgs={[swap.swapId]} ipcArgs={[swap.swap_id]}
endIcon={<PlayArrowIcon />} endIcon={<PlayArrowIcon />}
requiresRpc requiresRpc
{...props} {...props}
@ -37,8 +37,8 @@ export function SwapCancelRefundButton({
...props ...props
}: { swap: GetSwapInfoResponse } & ButtonProps) { }: { swap: GetSwapInfoResponse } & ButtonProps) {
const cancelOrRefundable = const cancelOrRefundable =
isSwapStateNamePossiblyCancellableSwap(swap.stateName) || isSwapStateNamePossiblyCancellableSwap(swap.state_name) ||
isSwapStateNamePossiblyRefundableSwap(swap.stateName); isSwapStateNamePossiblyRefundableSwap(swap.state_name);
if (!cancelOrRefundable) { if (!cancelOrRefundable) {
return <></>; return <></>;
@ -47,7 +47,7 @@ export function SwapCancelRefundButton({
return ( return (
<IpcInvokeButton <IpcInvokeButton
ipcChannel="spawn-cancel-refund" ipcChannel="spawn-cancel-refund"
ipcArgs={[swap.swapId]} ipcArgs={[swap.swap_id]}
requiresRpc requiresRpc
displayErrorSnackbar={false} displayErrorSnackbar={false}
{...props} {...props}
@ -62,7 +62,7 @@ export default function HistoryRowActions({
}: { }: {
swap: GetSwapInfoResponse; swap: GetSwapInfoResponse;
}) { }) {
if (swap.stateName === SwapStateName.XmrRedeemed) { if (swap.state_name === SwapStateName.XmrRedeemed) {
return ( return (
<Tooltip title="The swap is completed because you have redeemed the XMR"> <Tooltip title="The swap is completed because you have redeemed the XMR">
<DoneIcon style={{ color: green[500] }} /> <DoneIcon style={{ color: green[500] }} />
@ -70,7 +70,7 @@ export default function HistoryRowActions({
); );
} }
if (swap.stateName === SwapStateName.BtcRefunded) { if (swap.state_name === SwapStateName.BtcRefunded) {
return ( return (
<Tooltip title="The swap is completed because your BTC have been refunded"> <Tooltip title="The swap is completed because your BTC have been refunded">
<DoneIcon style={{ color: green[500] }} /> <DoneIcon style={{ color: green[500] }} />
@ -78,7 +78,7 @@ export default function HistoryRowActions({
); );
} }
if (swap.stateName === SwapStateName.BtcPunished) { if (swap.state_name === SwapStateName.BtcPunished) {
return ( return (
<Tooltip title="The swap is completed because you have been punished"> <Tooltip title="The swap is completed because you have been punished">
<ErrorIcon style={{ color: red[500] }} /> <ErrorIcon style={{ color: red[500] }} />

View File

@ -7,9 +7,9 @@ import {
TableCell, TableCell,
TableContainer, TableContainer,
TableRow, TableRow,
} from '@material-ui/core'; } from "@material-ui/core";
import { getBitcoinTxExplorerUrl } from 'utils/conversionUtils'; import { getBitcoinTxExplorerUrl } from "utils/conversionUtils";
import { isTestnet } from 'store/config'; import { isTestnet } from "store/config";
import { import {
getHumanReadableDbStateType, getHumanReadableDbStateType,
getSwapBtcAmount, getSwapBtcAmount,
@ -17,25 +17,25 @@ import {
getSwapTxFees, getSwapTxFees,
getSwapXmrAmount, getSwapXmrAmount,
GetSwapInfoResponse, GetSwapInfoResponse,
} from '../../../../../models/rpcModel'; } from "../../../../../models/rpcModel";
import SwapLogFileOpenButton from './SwapLogFileOpenButton'; import SwapLogFileOpenButton from "./SwapLogFileOpenButton";
import { SwapCancelRefundButton } from './HistoryRowActions'; import { SwapCancelRefundButton } from "./HistoryRowActions";
import { SwapMoneroRecoveryButton } from './SwapMoneroRecoveryButton'; import { SwapMoneroRecoveryButton } from "./SwapMoneroRecoveryButton";
import { import {
BitcoinAmount, BitcoinAmount,
MoneroAmount, MoneroAmount,
MoneroBitcoinExchangeRate, MoneroBitcoinExchangeRate,
} from 'renderer/components/other/Units'; } from "renderer/components/other/Units";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
outer: { outer: {
display: 'grid', display: "grid",
padding: theme.spacing(1), padding: theme.spacing(1),
gap: theme.spacing(1), gap: theme.spacing(1),
}, },
actionsOuter: { actionsOuter: {
display: 'flex', display: "flex",
flexDirection: 'row', flexDirection: "row",
gap: theme.spacing(1), gap: theme.spacing(1),
}, },
})); }));
@ -47,7 +47,7 @@ export default function HistoryRowExpanded({
}) { }) {
const classes = useStyles(); const classes = useStyles();
const { seller, startDate } = swap; const { seller, start_date: startDate } = swap;
const btcAmount = getSwapBtcAmount(swap); const btcAmount = getSwapBtcAmount(swap);
const xmrAmount = getSwapXmrAmount(swap); const xmrAmount = getSwapXmrAmount(swap);
const txFees = getSwapTxFees(swap); const txFees = getSwapTxFees(swap);
@ -64,12 +64,12 @@ export default function HistoryRowExpanded({
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell>Swap ID</TableCell> <TableCell>Swap ID</TableCell>
<TableCell>{swap.swapId}</TableCell> <TableCell>{swap.swap_id}</TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell>State Name</TableCell> <TableCell>State Name</TableCell>
<TableCell> <TableCell>
{getHumanReadableDbStateType(swap.stateName)} {getHumanReadableDbStateType(swap.state_name)}
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
@ -87,7 +87,9 @@ export default function HistoryRowExpanded({
<TableRow> <TableRow>
<TableCell>Exchange Rate</TableCell> <TableCell>Exchange Rate</TableCell>
<TableCell> <TableCell>
<MoneroBitcoinExchangeRate rate={exchangeRate} /> <MoneroBitcoinExchangeRate
rate={exchangeRate}
/>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
@ -99,17 +101,20 @@ export default function HistoryRowExpanded({
<TableRow> <TableRow>
<TableCell>Provider Address</TableCell> <TableCell>Provider Address</TableCell>
<TableCell> <TableCell>
<Box>{seller.addresses.join(', ')}</Box> <Box>{seller.addresses.join(", ")}</Box>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell>Bitcoin lock transaction</TableCell> <TableCell>Bitcoin lock transaction</TableCell>
<TableCell> <TableCell>
<Link <Link
href={getBitcoinTxExplorerUrl(swap.txLockId, isTestnet())} href={getBitcoinTxExplorerUrl(
swap.tx_lock_id,
isTestnet(),
)}
target="_blank" target="_blank"
> >
{swap.txLockId} {swap.tx_lock_id}
</Link> </Link>
</TableCell> </TableCell>
</TableRow> </TableRow>
@ -118,11 +123,15 @@ export default function HistoryRowExpanded({
</TableContainer> </TableContainer>
<Box className={classes.actionsOuter}> <Box className={classes.actionsOuter}>
<SwapLogFileOpenButton <SwapLogFileOpenButton
swapId={swap.swapId} swapId={swap.swap_id}
variant="outlined" variant="outlined"
size="small" size="small"
/> />
<SwapCancelRefundButton swap={swap} variant="contained" size="small" /> <SwapCancelRefundButton
swap={swap}
variant="contained"
size="small"
/>
<SwapMoneroRecoveryButton <SwapMoneroRecoveryButton
swap={swap} swap={swap}
variant="contained" variant="contained"

View File

@ -8,14 +8,14 @@ import {
TableContainer, TableContainer,
TableHead, TableHead,
TableRow, TableRow,
} from '@material-ui/core'; } from "@material-ui/core";
import { sortBy } from 'lodash'; import { sortBy } from "lodash";
import { parseDateString } from 'utils/parseUtils'; import { parseDateString } from "utils/parseUtils";
import { import {
useAppSelector, useAppSelector,
useSwapInfosSortedByDate, useSwapInfosSortedByDate,
} from '../../../../../store/hooks'; } from "../../../../../store/hooks";
import HistoryRow from './HistoryRow'; import HistoryRow from "./HistoryRow";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
outer: { outer: {
@ -43,7 +43,7 @@ export default function HistoryTable() {
</TableHead> </TableHead>
<TableBody> <TableBody>
{swapSortedByDate.map((swap) => ( {swapSortedByDate.map((swap) => (
<HistoryRow swap={swap} key={swap.swapId} /> <HistoryRow swap={swap} key={swap.swap_id} />
))} ))}
</TableBody> </TableBody>
</Table> </Table>

View File

@ -1,4 +1,4 @@
import { ButtonProps } from '@material-ui/core/Button/Button'; import { ButtonProps } from "@material-ui/core/Button/Button";
import { import {
Box, Box,
Button, Button,
@ -7,16 +7,16 @@ import {
DialogContent, DialogContent,
DialogContentText, DialogContentText,
Link, Link,
} from '@material-ui/core'; } from "@material-ui/core";
import { useAppDispatch, useAppSelector } from 'store/hooks'; import { useAppDispatch, useAppSelector } from "store/hooks";
import { rpcResetMoneroRecoveryKeys } from 'store/features/rpcSlice'; import { rpcResetMoneroRecoveryKeys } from "store/features/rpcSlice";
import { import {
GetSwapInfoResponse, GetSwapInfoResponse,
isSwapMoneroRecoverable, isSwapMoneroRecoverable,
} from '../../../../../models/rpcModel'; } from "../../../../../models/rpcModel";
import IpcInvokeButton from '../../../IpcInvokeButton'; import IpcInvokeButton from "../../../IpcInvokeButton";
import DialogHeader from '../../../modal/DialogHeader'; import DialogHeader from "../../../modal/DialogHeader";
import ScrollablePaperTextBox from '../../../other/ScrollablePaperTextBox'; import ScrollablePaperTextBox from "../../../other/ScrollablePaperTextBox";
function MoneroRecoveryKeysDialog({ swap }: { swap: GetSwapInfoResponse }) { function MoneroRecoveryKeysDialog({ swap }: { swap: GetSwapInfoResponse }) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -26,54 +26,55 @@ function MoneroRecoveryKeysDialog({ swap }: { swap: GetSwapInfoResponse }) {
dispatch(rpcResetMoneroRecoveryKeys()); dispatch(rpcResetMoneroRecoveryKeys());
} }
if (keys === null || keys.swapId !== swap.swapId) { if (keys === null || keys.swapId !== swap.swap_id) {
return <></>; return <></>;
} }
return ( return (
<Dialog open onClose={onClose} maxWidth="sm" fullWidth> <Dialog open onClose={onClose} maxWidth="sm" fullWidth>
<DialogHeader <DialogHeader
title={`Recovery Keys for swap ${swap.swapId.substring(0, 5)}...`} title={`Recovery Keys for swap ${swap.swap_id.substring(0, 5)}...`}
/> />
<DialogContent> <DialogContent>
<DialogContentText> <DialogContentText>
You can use the keys below to manually redeem the Monero funds from You can use the keys below to manually redeem the Monero
the multi-signature wallet. funds from the multi-signature wallet.
<ul> <ul>
<li> <li>
This is useful if the swap daemon fails to redeem the funds itself This is useful if the swap daemon fails to redeem
the funds itself
</li> </li>
<li> <li>
If you have come this far, there is no risk of losing funds. You If you have come this far, there is no risk of
are the only one with access to these keys and can use them to losing funds. You are the only one with access to
access your funds these keys and can use them to access your funds
</li> </li>
<li> <li>
View{' '} View{" "}
<Link <Link
href="https://www.getmonero.org/resources/user-guides/restore_from_keys.html" href="https://www.getmonero.org/resources/user-guides/restore_from_keys.html"
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
> >
this guide this guide
</Link>{' '} </Link>{" "}
for a detailed description on how to import the keys and spend the for a detailed description on how to import the keys
funds. and spend the funds.
</li> </li>
</ul> </ul>
</DialogContentText> </DialogContentText>
<Box <Box
style={{ style={{
display: 'flex', display: "flex",
gap: '0.5rem', gap: "0.5rem",
flexDirection: 'column', flexDirection: "column",
}} }}
> >
{[ {[
['Primary Address', keys.keys.address], ["Primary Address", keys.keys.address],
['View Key', keys.keys.view_key], ["View Key", keys.keys.view_key],
['Spend Key', keys.keys.spend_key], ["Spend Key", keys.keys.spend_key],
['Restore Height', keys.keys.restore_height.toString()], ["Restore Height", keys.keys.restore_height.toString()],
].map(([title, value]) => ( ].map(([title, value]) => (
<ScrollablePaperTextBox <ScrollablePaperTextBox
minHeight="2rem" minHeight="2rem"
@ -97,7 +98,7 @@ export function SwapMoneroRecoveryButton({
swap, swap,
...props ...props
}: { swap: GetSwapInfoResponse } & ButtonProps) { }: { swap: GetSwapInfoResponse } & ButtonProps) {
const isRecoverable = isSwapMoneroRecoverable(swap.stateName); const isRecoverable = isSwapMoneroRecoverable(swap.state_name);
if (!isRecoverable) { if (!isRecoverable) {
return <></>; return <></>;
@ -107,7 +108,7 @@ export function SwapMoneroRecoveryButton({
<> <>
<IpcInvokeButton <IpcInvokeButton
ipcChannel="spawn-monero-recovery" ipcChannel="spawn-monero-recovery"
ipcArgs={[swap.swapId]} ipcArgs={[swap.swap_id]}
requiresRpc requiresRpc
{...props} {...props}
> >

View File

@ -1,7 +1,7 @@
import { invoke } from "@tauri-apps/api/core"; import { invoke } from "@tauri-apps/api/core";
import { BalanceBitcoinResponse } from "models/rpcModel"; import { BalanceBitcoinResponse } from "models/rpcModel";
import { store } from "./store/storeRenderer"; import { store } from "./store/storeRenderer";
import { rpcSetBalance } from "store/features/rpcSlice"; import { rpcSetBalance, rpcSetSwapInfo } from "store/features/rpcSlice";
export async function checkBitcoinBalance() { export async function checkBitcoinBalance() {
// TODO: use tauri-bindgen here // TODO: use tauri-bindgen here
@ -13,6 +13,7 @@ export async function checkBitcoinBalance() {
} }
export async function getRawSwapInfos() { export async function getRawSwapInfos() {
const response = await invoke("swap_infos"); const response = await invoke("swap_infos_all");
console.log(response); console.log(response);
(response as any[]).forEach((info) => store.dispatch(rpcSetSwapInfo(info)));
} }

View File

@ -1,11 +1,11 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit'; import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { ExtendedProviderStatus, ProviderStatus } from 'models/apiModel'; import { ExtendedProviderStatus, ProviderStatus } from "models/apiModel";
import { MoneroWalletRpcUpdateState } from 'models/storeModel'; import { MoneroWalletRpcUpdateState } from "models/storeModel";
import { import {
GetSwapInfoResponse, GetSwapInfoResponse,
MoneroRecoveryResponse, MoneroRecoveryResponse,
RpcProcessStateType, RpcProcessStateType,
} from '../../models/rpcModel'; } from "../../models/rpcModel";
import { import {
CliLog, CliLog,
isCliLog, isCliLog,
@ -14,8 +14,8 @@ import {
isCliLogFinishedSyncingMoneroWallet, isCliLogFinishedSyncingMoneroWallet,
isCliLogStartedRpcServer, isCliLogStartedRpcServer,
isCliLogStartedSyncingMoneroWallet, isCliLogStartedSyncingMoneroWallet,
} from '../../models/cliModel'; } from "../../models/cliModel";
import { getLogsAndStringsFromRawFileString } from 'utils/parseUtils'; import { getLogsAndStringsFromRawFileString } from "utils/parseUtils";
type Process = type Process =
| { | {
@ -82,7 +82,7 @@ const initialState: RPCSlice = {
}; };
export const rpcSlice = createSlice({ export const rpcSlice = createSlice({
name: 'rpc', name: "rpc",
initialState, initialState,
reducers: { reducers: {
rpcAddLogs(slice, action: PayloadAction<(CliLog | string)[]>) { rpcAddLogs(slice, action: PayloadAction<(CliLog | string)[]>) {
@ -110,7 +110,7 @@ export const rpcSlice = createSlice({
downloadUrl: log.fields.download_url, downloadUrl: log.fields.download_url,
}; };
if (log.fields.progress === '100%') { if (log.fields.progress === "100%") {
slice.state.moneroWalletRpc.updateState = false; slice.state.moneroWalletRpc.updateState = false;
} }
} else if (isCliLogStartedSyncingMoneroWallet(log)) { } else if (isCliLogStartedSyncingMoneroWallet(log)) {
@ -169,7 +169,7 @@ export const rpcSlice = createSlice({
slice.state.withdrawTxId = null; slice.state.withdrawTxId = null;
}, },
rpcSetSwapInfo(slice, action: PayloadAction<GetSwapInfoResponse>) { rpcSetSwapInfo(slice, action: PayloadAction<GetSwapInfoResponse>) {
slice.state.swapInfos[action.payload.swapId] = action.payload; slice.state.swapInfos[action.payload.swap_id] = action.payload;
}, },
rpcSetEndpointBusy(slice, action: PayloadAction<string>) { rpcSetEndpointBusy(slice, action: PayloadAction<string>) {
if (!slice.busyEndpoints.includes(action.payload)) { if (!slice.busyEndpoints.includes(action.payload)) {

View File

@ -1,7 +1,7 @@
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { AppDispatch, RootState } from 'renderer/store/storeRenderer'; import type { AppDispatch, RootState } from "renderer/store/storeRenderer";
import { sortBy } from 'lodash'; import { sortBy } from "lodash";
import { parseDateString } from 'utils/parseUtils'; import { parseDateString } from "utils/parseUtils";
// Use throughout your app instead of plain `useDispatch` and `useSelector` // Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>(); export const useAppDispatch = () => useDispatch<AppDispatch>();
@ -22,7 +22,7 @@ export function useIsSwapRunning() {
export function useSwapInfo(swapId: string | null) { export function useSwapInfo(swapId: string | null) {
return useAppSelector((state) => return useAppSelector((state) =>
swapId ? state.rpc.state.swapInfos[swapId] ?? null : null, swapId ? (state.rpc.state.swapInfos[swapId] ?? null) : null,
); );
} }
@ -51,6 +51,6 @@ export function useSwapInfosSortedByDate() {
const swapInfos = useAppSelector((state) => state.rpc.state.swapInfos); const swapInfos = useAppSelector((state) => state.rpc.state.swapInfos);
return sortBy( return sortBy(
Object.values(swapInfos), Object.values(swapInfos),
(swap) => -parseDateString(swap.startDate), (swap) => -parseDateString(swap.start_date),
); );
} }

View File

@ -21,4 +21,4 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
swap = { path = "../swap" } swap = { path = "../swap" }
tauri = { version = "2.0.0-rc.1", features = ["config-json5"] } tauri = { version = "2.0.0-rc.1", features = ["config-json5"] }
tauri-plugin-shell = "2.0.0-rc.0" uuid = "1.10.0"

View File

@ -3,5 +3,5 @@
"identifier": "default", "identifier": "default",
"description": "Capability for the main window", "description": "Capability for the main window",
"windows": ["main"], "windows": ["main"],
"permissions": ["shell:allow-open"] "permissions": []
} }

View File

@ -3,11 +3,15 @@ use std::sync::Arc;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use swap::{ use swap::{
api::{ api::{
request::{get_balance, BalanceArgs, BalanceResponse}, request::{
get_balance, get_swap_info, get_swap_infos_all, BalanceArgs, BalanceResponse,
GetSwapInfoResponse,
},
Context, Context,
}, },
cli::command::{Bitcoin, Monero}, cli::command::{Bitcoin, Monero},
}; };
use uuid::Uuid;
// Lazy load the Context // Lazy load the Context
static CONTEXT: OnceCell<Arc<Context>> = OnceCell::new(); static CONTEXT: OnceCell<Arc<Context>> = OnceCell::new();
@ -26,6 +30,15 @@ async fn balance() -> Result<BalanceResponse, String> {
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
} }
#[tauri::command]
async fn swap_infos_all() -> Result<Vec<GetSwapInfoResponse>, String> {
let context = CONTEXT.get().unwrap();
get_swap_infos_all(context.clone())
.await
.map_err(|e| e.to_string())
}
fn setup<'a>(app: &'a mut tauri::App) -> Result<(), Box<dyn std::error::Error>> { fn setup<'a>(app: &'a mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
tauri::async_runtime::block_on(async { tauri::async_runtime::block_on(async {
let context = Context::build( let context = Context::build(
@ -56,8 +69,7 @@ fn setup<'a>(app: &'a mut tauri::App) -> Result<(), Box<dyn std::error::Error>>
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_shell::init()) .invoke_handler(tauri::generate_handler![balance, swap_infos_all])
.invoke_handler(tauri::generate_handler![balance])
.setup(setup) .setup(setup)
.run(tauri::generate_context!()) .run(tauri::generate_context!())
.expect("error while running tauri application"); .expect("error while running tauri application");

View File

@ -7,6 +7,7 @@ use crate::network::swarm;
use crate::protocol::bob::{BobState, Swap}; use crate::protocol::bob::{BobState, Swap};
use crate::protocol::{bob, State}; use crate::protocol::{bob, State};
use crate::{bitcoin, cli, monero, rpc}; use crate::{bitcoin, cli, monero, rpc};
use ::bitcoin::Txid;
use anyhow::{bail, Context as AnyContext, Result}; use anyhow::{bail, Context as AnyContext, Result};
use libp2p::core::Multiaddr; use libp2p::core::Multiaddr;
use qrcode::render::unicode; use qrcode::render::unicode;
@ -52,22 +53,6 @@ pub struct MoneroRecoveryArgs {
pub swap_id: Uuid, pub swap_id: Uuid,
} }
#[derive(Serialize, Deserialize, Debug)]
pub struct ResumeSwapResponse {
pub result: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct BalanceResponse {
pub balance: u64, // in satoshis
}
#[derive(Serialize, Deserialize, Debug)]
pub struct BuyXmrResponse {
pub swap_id: String,
pub quote: BidQuote, // You'll need to import or define BidQuote
}
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub struct WithdrawBtcArgs { pub struct WithdrawBtcArgs {
pub amount: Option<Amount>, pub amount: Option<Amount>,
@ -94,6 +79,52 @@ pub struct GetSwapInfoArgs {
pub swap_id: Uuid, pub swap_id: Uuid,
} }
#[derive(Serialize, Deserialize, Debug)]
pub struct ResumeSwapResponse {
pub result: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct BalanceResponse {
pub balance: u64, // in satoshis
}
#[derive(Serialize, Deserialize, Debug)]
pub struct BuyXmrResponse {
pub swap_id: String,
pub quote: BidQuote, // You'll need to import or define BidQuote
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GetHistoryResponse {
swaps: Vec<(Uuid, String)>,
}
#[derive(Serialize)]
pub struct GetSwapInfoResponse {
pub swap_id: Uuid,
pub seller: Seller,
pub completed: bool,
pub start_date: String,
pub state_name: String,
pub xmr_amount: u64,
pub btc_amount: u64,
pub tx_lock_id: Txid,
pub tx_cancel_fee: u64,
pub tx_refund_fee: u64,
pub tx_lock_fee: u64,
pub btc_refund_address: String,
pub cancel_timelock: u32,
pub punish_timelock: u32,
pub timelock: Option<ExpiredTimelocks>,
}
#[derive(Serialize, Deserialize)]
pub struct Seller {
pub peer_id: String,
pub addresses: Vec<Multiaddr>,
}
// TODO: We probably dont even need this. // TODO: We probably dont even need this.
// We can just call the method directly from the RPC server, the CLI and the Tauri connector // We can just call the method directly from the RPC server, the CLI and the Tauri connector
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@ -231,8 +262,23 @@ async fn suspend_current_swap(context: Arc<Context>) -> Result<serde_json::Value
} }
} }
pub async fn get_swap_infos_all(context: Arc<Context>) -> Result<Vec<GetSwapInfoResponse>> {
let swap_ids = context.db.all().await?;
let mut swap_infos = Vec::new();
for (swap_id, _) in swap_ids {
let swap_info = get_swap_info(GetSwapInfoArgs { swap_id }, context.clone()).await?;
swap_infos.push(swap_info);
}
Ok(swap_infos)
}
// #[tracing::instrument(fields(method="get_swap_info", swap_id = args.swap_id), skip(context))] // #[tracing::instrument(fields(method="get_swap_info", swap_id = args.swap_id), skip(context))]
async fn get_swap_info(args: GetSwapInfoArgs, context: Arc<Context>) -> Result<serde_json::Value> { pub async fn get_swap_info(
args: GetSwapInfoArgs,
context: Arc<Context>,
) -> Result<GetSwapInfoResponse> {
let bitcoin_wallet = context let bitcoin_wallet = context
.bitcoin_wallet .bitcoin_wallet
.as_ref() .as_ref()
@ -311,38 +357,38 @@ async fn get_swap_info(args: GetSwapInfoArgs, context: Arc<Context>) -> Result<s
} }
BobState::BtcLocked { state3: state, .. } BobState::BtcLocked { state3: state, .. }
| BobState::XmrLockProofReceived { state, .. } => { | BobState::XmrLockProofReceived { state, .. } => {
Some(state.expired_timelock(bitcoin_wallet).await) Some(state.expired_timelock(bitcoin_wallet).await?)
} }
BobState::XmrLocked(state) | BobState::EncSigSent(state) => { BobState::XmrLocked(state) | BobState::EncSigSent(state) => {
Some(state.expired_timelock(bitcoin_wallet).await) Some(state.expired_timelock(bitcoin_wallet).await?)
} }
BobState::CancelTimelockExpired(state) | BobState::BtcCancelled(state) => { BobState::CancelTimelockExpired(state) | BobState::BtcCancelled(state) => {
Some(state.expired_timelock(bitcoin_wallet).await) Some(state.expired_timelock(bitcoin_wallet).await?)
} }
BobState::BtcPunished { .. } => Some(Ok(ExpiredTimelocks::Punish)), BobState::BtcPunished { .. } => Some(ExpiredTimelocks::Punish),
BobState::BtcRefunded(_) | BobState::BtcRedeemed(_) | BobState::XmrRedeemed { .. } => None, BobState::BtcRefunded(_) | BobState::BtcRedeemed(_) | BobState::XmrRedeemed { .. } => None,
}; };
Ok(json!({ Ok(GetSwapInfoResponse {
"swapId": args.swap_id, swap_id: args.swap_id,
"seller": { seller: Seller {
"peerId": peerId.to_string(), peer_id: peerId.to_string(),
"addresses": addresses addresses,
}, },
"completed": is_completed, completed: is_completed,
"startDate": start_date, start_date,
"stateName": state_name, state_name,
"xmrAmount": xmr_amount, xmr_amount: xmr_amount.as_piconero(),
"btcAmount": btc_amount, btc_amount,
"txLockId": tx_lock_id, tx_lock_id,
"txCancelFee": tx_cancel_fee, tx_cancel_fee,
"txRefundFee": tx_refund_fee, tx_refund_fee,
"txLockFee": tx_lock_fee, tx_lock_fee,
"btcRefundAddress": btc_refund_address.to_string(), btc_refund_address: btc_refund_address.to_string(),
"cancelTimelock": cancel_timelock, cancel_timelock: cancel_timelock.into(),
"punishTimelock": punish_timelock, punish_timelock: punish_timelock.into(),
"timelock": timelock.map(|tl| tl.map(|tl| json!(tl)).unwrap_or(json!(null))).unwrap_or(json!(null)), timelock,
})) })
} }
async fn buy_xmr(buy_xmr: BuyXmrArgs, context: Arc<Context>) -> Result<serde_json::Value> { async fn buy_xmr(buy_xmr: BuyXmrArgs, context: Arc<Context>) -> Result<serde_json::Value> {
@ -653,7 +699,7 @@ async fn cancel_and_refund(
}) })
} }
async fn get_history(context: Arc<Context>) -> Result<serde_json::Value> { async fn get_history(context: Arc<Context>) -> Result<GetHistoryResponse> {
let swaps = context.db.all().await?; let swaps = context.db.all().await?;
let mut vec: Vec<(Uuid, String)> = Vec::new(); let mut vec: Vec<(Uuid, String)> = Vec::new();
for (swap_id, state) in swaps { for (swap_id, state) in swaps {
@ -661,7 +707,7 @@ async fn get_history(context: Arc<Context>) -> Result<serde_json::Value> {
vec.push((swap_id, state.to_string())); vec.push((swap_id, state.to_string()));
} }
Ok(json!({ "swaps": vec })) Ok(GetHistoryResponse { swaps: vec })
} }
async fn get_raw_states(context: Arc<Context>) -> Result<serde_json::Value> { async fn get_raw_states(context: Arc<Context>) -> Result<serde_json::Value> {