mirror of
https://github.com/comit-network/xmr-btc-swap.git
synced 2025-12-18 10:02:45 -05:00
refactor(gui): seperate get info and get timelock to speed up display of swaps (#661)
* refactor(gui): seperate get info and get timelock to speed up display of swaps * progress * progress * remove unused function useSwapInfoWithTimelock * use GetSwapTimelockArgs and GetSwapTimelockResponse types
This commit is contained in:
parent
0fec5d556d
commit
33662b0a06
11 changed files with 333 additions and 203 deletions
|
|
@ -131,10 +131,6 @@ export type GetSwapInfoResponseExtRunningSwap = GetSwapInfoResponseExt & {
|
||||||
state_name: BobStateNameRunningSwap;
|
state_name: BobStateNameRunningSwap;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GetSwapInfoResponseExtWithTimelock = GetSwapInfoResponseExt & {
|
|
||||||
timelock: ExpiredTimelocks;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function isBobStateNameRunningSwap(
|
export function isBobStateNameRunningSwap(
|
||||||
state: BobStateName,
|
state: BobStateName,
|
||||||
): state is BobStateNameRunningSwap {
|
): state is BobStateNameRunningSwap {
|
||||||
|
|
@ -252,17 +248,6 @@ export function isGetSwapInfoResponseRunningSwap(
|
||||||
return isBobStateNameRunningSwap(response.state_name);
|
return isBobStateNameRunningSwap(response.state_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Type guard for GetSwapInfoResponseExt to ensure timelock is not null
|
|
||||||
* @param response The swap info response to check
|
|
||||||
* @returns True if the timelock exists, false otherwise
|
|
||||||
*/
|
|
||||||
export function isGetSwapInfoResponseWithTimelock(
|
|
||||||
response: GetSwapInfoResponseExt,
|
|
||||||
): response is GetSwapInfoResponseExtWithTimelock {
|
|
||||||
return response.timelock !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PendingApprovalRequest = ApprovalRequest & {
|
export type PendingApprovalRequest = ApprovalRequest & {
|
||||||
content: Extract<ApprovalRequest["request_status"], { state: "Pending" }>;
|
content: Extract<ApprovalRequest["request_status"], { state: "Pending" }>;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import {
|
||||||
import {
|
import {
|
||||||
checkContextStatus,
|
checkContextStatus,
|
||||||
getSwapInfo,
|
getSwapInfo,
|
||||||
|
getSwapTimelock,
|
||||||
initializeContext,
|
initializeContext,
|
||||||
listSellersAtRendezvousPoint,
|
listSellersAtRendezvousPoint,
|
||||||
refreshApprovals,
|
refreshApprovals,
|
||||||
|
|
@ -122,12 +123,32 @@ listen<TauriEvent>(TAURI_UNIFIED_EVENT_CHANNEL_NAME, (event) => {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "SwapDatabaseStateUpdate":
|
case "SwapDatabaseStateUpdate":
|
||||||
getSwapInfo(eventData.swap_id);
|
getSwapInfo(eventData.swap_id).catch((error) => {
|
||||||
|
logger.debug(
|
||||||
|
`Failed to fetch swap info for swap ${eventData.swap_id}: ${error}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
getSwapTimelock(eventData.swap_id).catch((error) => {
|
||||||
|
logger.debug(
|
||||||
|
`Failed to fetch timelock for swap ${eventData.swap_id}: ${error}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// This is ugly but it's the best we can do for now
|
// This is ugly but it's the best we can do for now
|
||||||
// Sometimes we are too quick to fetch the swap info and the new state is not yet reflected
|
// Sometimes we are too quick to fetch the swap info and the new state is not yet reflected
|
||||||
// in the database. So we wait a bit before fetching the new state
|
// in the database. So we wait a bit before fetching the new state
|
||||||
setTimeout(() => getSwapInfo(eventData.swap_id), 3000);
|
setTimeout(() => {
|
||||||
|
getSwapInfo(eventData.swap_id).catch((error) => {
|
||||||
|
logger.debug(
|
||||||
|
`Failed to fetch swap info for swap ${eventData.swap_id}: ${error}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
getSwapTimelock(eventData.swap_id).catch((error) => {
|
||||||
|
logger.debug(
|
||||||
|
`Failed to fetch timelock for swap ${eventData.swap_id}: ${error}`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, 3000);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "TimelockChange":
|
case "TimelockChange":
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import {
|
||||||
GetSwapInfoResponseExt,
|
GetSwapInfoResponseExt,
|
||||||
GetSwapInfoResponseExtRunningSwap,
|
GetSwapInfoResponseExtRunningSwap,
|
||||||
isGetSwapInfoResponseRunningSwap,
|
isGetSwapInfoResponseRunningSwap,
|
||||||
isGetSwapInfoResponseWithTimelock,
|
|
||||||
TimelockCancel,
|
TimelockCancel,
|
||||||
TimelockNone,
|
TimelockNone,
|
||||||
} from "models/tauriModelExt";
|
} from "models/tauriModelExt";
|
||||||
|
|
@ -15,7 +14,9 @@ import HumanizedBitcoinBlockDuration from "../../other/HumanizedBitcoinBlockDura
|
||||||
import TruncatedText from "../../other/TruncatedText";
|
import TruncatedText from "../../other/TruncatedText";
|
||||||
import { SwapMoneroRecoveryButton } from "../../pages/history/table/SwapMoneroRecoveryButton";
|
import { SwapMoneroRecoveryButton } from "../../pages/history/table/SwapMoneroRecoveryButton";
|
||||||
import { TimelockTimeline } from "./TimelockTimeline";
|
import { TimelockTimeline } from "./TimelockTimeline";
|
||||||
import { useIsSpecificSwapRunning } from "store/hooks";
|
import { useIsSpecificSwapRunning, useAppSelector } from "store/hooks";
|
||||||
|
import { selectSwapTimelock } from "store/selectors";
|
||||||
|
import { ExpiredTimelocks } from "models/tauriModel";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for displaying a list of messages.
|
* Component for displaying a list of messages.
|
||||||
|
|
@ -68,7 +69,11 @@ function BitcoinRedeemedStateAlert({ swap }: { swap: GetSwapInfoResponseExt }) {
|
||||||
"If this step fails, you can manually redeem your funds",
|
"If this step fails, you can manually redeem your funds",
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<SwapMoneroRecoveryButton swap={swap} size="small" variant="contained" />
|
<SwapMoneroRecoveryButton
|
||||||
|
swap={swap}
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -167,9 +172,11 @@ function PunishTimelockExpiredAlert() {
|
||||||
*/
|
*/
|
||||||
export function StateAlert({
|
export function StateAlert({
|
||||||
swap,
|
swap,
|
||||||
|
timelock,
|
||||||
isRunning,
|
isRunning,
|
||||||
}: {
|
}: {
|
||||||
swap: GetSwapInfoResponseExtRunningSwap;
|
swap: GetSwapInfoResponseExtRunningSwap;
|
||||||
|
timelock: ExpiredTimelocks | null;
|
||||||
isRunning: boolean;
|
isRunning: boolean;
|
||||||
}) {
|
}) {
|
||||||
switch (swap.state_name) {
|
switch (swap.state_name) {
|
||||||
|
|
@ -188,12 +195,12 @@ export function StateAlert({
|
||||||
case BobStateName.BtcCancelled:
|
case BobStateName.BtcCancelled:
|
||||||
case BobStateName.BtcRefundPublished: // Even if the transactions have been published, it cannot be
|
case BobStateName.BtcRefundPublished: // Even if the transactions have been published, it cannot be
|
||||||
case BobStateName.BtcEarlyRefundPublished: // guaranteed that they will be confirmed in time
|
case BobStateName.BtcEarlyRefundPublished: // guaranteed that they will be confirmed in time
|
||||||
if (swap.timelock != null) {
|
if (timelock != null) {
|
||||||
switch (swap.timelock.type) {
|
switch (timelock.type) {
|
||||||
case "None":
|
case "None":
|
||||||
return (
|
return (
|
||||||
<BitcoinLockedNoTimelockExpiredStateAlert
|
<BitcoinLockedNoTimelockExpiredStateAlert
|
||||||
timelock={swap.timelock}
|
timelock={timelock}
|
||||||
cancelTimelockOffset={swap.cancel_timelock}
|
cancelTimelockOffset={swap.cancel_timelock}
|
||||||
punishTimelockOffset={swap.punish_timelock}
|
punishTimelockOffset={swap.punish_timelock}
|
||||||
isRunning={isRunning}
|
isRunning={isRunning}
|
||||||
|
|
@ -202,16 +209,14 @@ export function StateAlert({
|
||||||
case "Cancel":
|
case "Cancel":
|
||||||
return (
|
return (
|
||||||
<BitcoinPossiblyCancelledAlert
|
<BitcoinPossiblyCancelledAlert
|
||||||
timelock={swap.timelock}
|
timelock={timelock}
|
||||||
swap={swap}
|
swap={swap}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case "Punish":
|
case "Punish":
|
||||||
return <PunishTimelockExpiredAlert />;
|
return <PunishTimelockExpiredAlert />;
|
||||||
default:
|
default:
|
||||||
// We have covered all possible timelock states above
|
exhaustiveGuard(timelock);
|
||||||
// If we reach this point, it means we have missed a case
|
|
||||||
exhaustiveGuard(swap.timelock);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return <PunishTimelockExpiredAlert />;
|
return <PunishTimelockExpiredAlert />;
|
||||||
|
|
@ -244,26 +249,20 @@ export default function SwapStatusAlert({
|
||||||
swap: GetSwapInfoResponseExt;
|
swap: GetSwapInfoResponseExt;
|
||||||
onlyShowIfUnusualAmountOfTimeHasPassed?: boolean;
|
onlyShowIfUnusualAmountOfTimeHasPassed?: boolean;
|
||||||
}) {
|
}) {
|
||||||
if (swap == null) {
|
const timelock = useAppSelector(selectSwapTimelock(swap.swap_id));
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the swap is completed, we do not need to display anything
|
|
||||||
if (!isGetSwapInfoResponseRunningSwap(swap)) {
|
if (!isGetSwapInfoResponseRunningSwap(swap)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't have a timelock for the swap, we cannot display the alert
|
if (timelock == null) {
|
||||||
if (!isGetSwapInfoResponseWithTimelock(swap)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasUnusualAmountOfTimePassed =
|
const hasUnusualAmountOfTimePassed =
|
||||||
swap.timelock.type === "None" &&
|
timelock.type === "None" &&
|
||||||
swap.timelock.content.blocks_left >
|
timelock.content.blocks_left > UNUSUAL_AMOUNT_OF_TIME_HAS_PASSED_THRESHOLD;
|
||||||
UNUSUAL_AMOUNT_OF_TIME_HAS_PASSED_THRESHOLD;
|
|
||||||
|
|
||||||
// If we are only showing if an unusual amount of time has passed, we need to check if the swap has been running for a while
|
|
||||||
if (onlyShowIfUnusualAmountOfTimeHasPassed && hasUnusualAmountOfTimePassed) {
|
if (onlyShowIfUnusualAmountOfTimeHasPassed && hasUnusualAmountOfTimePassed) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -291,7 +290,8 @@ export default function SwapStatusAlert({
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
Swap <TruncatedText>{swap.swap_id}</TruncatedText> is not running
|
Swap <TruncatedText>{swap.swap_id}</TruncatedText> is
|
||||||
|
not running
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
|
|
@ -302,8 +302,8 @@ export default function SwapStatusAlert({
|
||||||
gap: 1,
|
gap: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<StateAlert swap={swap} isRunning={isRunning} />
|
<StateAlert swap={swap} timelock={timelock} isRunning={isRunning} />
|
||||||
<TimelockTimeline swap={swap} />
|
<TimelockTimeline swap={swap} timelock={timelock} />
|
||||||
</Box>
|
</Box>
|
||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -112,9 +112,10 @@ function TimelineSegment({
|
||||||
|
|
||||||
export function TimelockTimeline({
|
export function TimelockTimeline({
|
||||||
swap,
|
swap,
|
||||||
|
timelock,
|
||||||
}: {
|
}: {
|
||||||
// This forces the timelock to not be null
|
swap: GetSwapInfoResponseExt;
|
||||||
swap: GetSwapInfoResponseExt & { timelock: ExpiredTimelocks };
|
timelock: ExpiredTimelocks;
|
||||||
}) {
|
}) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
|
|
@ -143,7 +144,7 @@ export function TimelockTimeline({
|
||||||
|
|
||||||
const totalBlocks = swap.cancel_timelock + swap.punish_timelock;
|
const totalBlocks = swap.cancel_timelock + swap.punish_timelock;
|
||||||
const absoluteBlock = getAbsoluteBlock(
|
const absoluteBlock = getAbsoluteBlock(
|
||||||
swap.timelock,
|
timelock,
|
||||||
swap.cancel_timelock,
|
swap.cancel_timelock,
|
||||||
swap.punish_timelock,
|
swap.punish_timelock,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
import ContactInfoBox from "./ContactInfoBox";
|
|
||||||
import DonateInfoBox from "./DonateInfoBox";
|
import DonateInfoBox from "./DonateInfoBox";
|
||||||
import DaemonControlBox from "./DaemonControlBox";
|
import DaemonControlBox from "./DaemonControlBox";
|
||||||
import SettingsBox from "./SettingsBox";
|
import SettingsBox from "./SettingsBox";
|
||||||
import ExportDataBox from "./ExportDataBox";
|
|
||||||
import DiscoveryBox from "./DiscoveryBox";
|
import DiscoveryBox from "./DiscoveryBox";
|
||||||
import MoneroPoolHealthBox from "./MoneroPoolHealthBox";
|
import MoneroPoolHealthBox from "./MoneroPoolHealthBox";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
|
|
|
||||||
|
|
@ -48,12 +48,16 @@ import {
|
||||||
MoneroNodeConfig,
|
MoneroNodeConfig,
|
||||||
GetMoneroSeedResponse,
|
GetMoneroSeedResponse,
|
||||||
ContextStatus,
|
ContextStatus,
|
||||||
|
GetSwapTimelockArgs,
|
||||||
|
GetSwapTimelockResponse,
|
||||||
} from "models/tauriModel";
|
} from "models/tauriModel";
|
||||||
import {
|
import {
|
||||||
rpcSetSwapInfo,
|
rpcSetSwapInfo,
|
||||||
approvalRequestsReplaced,
|
approvalRequestsReplaced,
|
||||||
contextInitializationFailed,
|
contextInitializationFailed,
|
||||||
|
timelockChangeEventReceived,
|
||||||
} from "store/features/rpcSlice";
|
} from "store/features/rpcSlice";
|
||||||
|
import { selectAllSwapIds } from "store/selectors";
|
||||||
import { setBitcoinBalance } from "store/features/bitcoinWalletSlice";
|
import { setBitcoinBalance } from "store/features/bitcoinWalletSlice";
|
||||||
import {
|
import {
|
||||||
setMainAddress,
|
setMainAddress,
|
||||||
|
|
@ -122,19 +126,6 @@ export const PRESET_RENDEZVOUS_POINTS = [
|
||||||
"/dns4/getxmr.st/tcp/8888/p2p/12D3KooWHHwiz6WDThPT8cEurstomg3kDSxzL2L8pwxfyX2fpxVk",
|
"/dns4/getxmr.st/tcp/8888/p2p/12D3KooWHHwiz6WDThPT8cEurstomg3kDSxzL2L8pwxfyX2fpxVk",
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function fetchSellersAtPresetRendezvousPoints() {
|
|
||||||
await Promise.all(
|
|
||||||
PRESET_RENDEZVOUS_POINTS.map(async (rendezvousPoint) => {
|
|
||||||
const response = await listSellersAtRendezvousPoint([rendezvousPoint]);
|
|
||||||
store.dispatch(discoveredMakersByRendezvous(response.sellers));
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
`Discovered ${response.sellers.length} sellers at rendezvous point ${rendezvousPoint} during startup fetch`,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function invoke<ARGS, RESPONSE>(
|
async function invoke<ARGS, RESPONSE>(
|
||||||
command: string,
|
command: string,
|
||||||
args: ARGS,
|
args: ARGS,
|
||||||
|
|
@ -148,6 +139,19 @@ async function invokeNoArgs<RESPONSE>(command: string): Promise<RESPONSE> {
|
||||||
return invokeUnsafe(command) as Promise<RESPONSE>;
|
return invokeUnsafe(command) as Promise<RESPONSE>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function fetchSellersAtPresetRendezvousPoints() {
|
||||||
|
await Promise.all(
|
||||||
|
PRESET_RENDEZVOUS_POINTS.map(async (rendezvousPoint) => {
|
||||||
|
const response = await listSellersAtRendezvousPoint([rendezvousPoint]);
|
||||||
|
store.dispatch(discoveredMakersByRendezvous(response.sellers));
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`Discovered ${response.sellers.length} sellers at rendezvous point ${rendezvousPoint} during startup fetch`,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function checkBitcoinBalance() {
|
export async function checkBitcoinBalance() {
|
||||||
// If we are already syncing, don't start a new sync
|
// If we are already syncing, don't start a new sync
|
||||||
if (
|
if (
|
||||||
|
|
@ -170,58 +174,6 @@ export async function checkBitcoinBalance() {
|
||||||
store.dispatch(setBitcoinBalance(response.balance));
|
store.dispatch(setBitcoinBalance(response.balance));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cheapCheckBitcoinBalance() {
|
|
||||||
const response = await invoke<BalanceArgs, BalanceResponse>("get_balance", {
|
|
||||||
force_refresh: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
store.dispatch(setBitcoinBalance(response.balance));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getBitcoinAddress() {
|
|
||||||
const response = await invokeNoArgs<GetBitcoinAddressResponse>(
|
|
||||||
"get_bitcoin_address",
|
|
||||||
);
|
|
||||||
|
|
||||||
return response.address;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getAllSwapInfos() {
|
|
||||||
const response =
|
|
||||||
await invokeNoArgs<GetSwapInfoResponse[]>("get_swap_infos_all");
|
|
||||||
|
|
||||||
response.forEach((swapInfo) => {
|
|
||||||
store.dispatch(rpcSetSwapInfo(swapInfo));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getSwapInfo(swapId: string) {
|
|
||||||
const response = await invoke<GetSwapInfoArgs, GetSwapInfoResponse>(
|
|
||||||
"get_swap_info",
|
|
||||||
{
|
|
||||||
swap_id: swapId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
store.dispatch(rpcSetSwapInfo(response));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function withdrawBtc(address: string): Promise<string> {
|
|
||||||
const response = await invoke<WithdrawBtcArgs, WithdrawBtcResponse>(
|
|
||||||
"withdraw_btc",
|
|
||||||
{
|
|
||||||
address,
|
|
||||||
amount: null,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// We check the balance, this is cheap and does not sync the wallet
|
|
||||||
// but instead uses our local cached balance
|
|
||||||
await cheapCheckBitcoinBalance();
|
|
||||||
|
|
||||||
return response.txid;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function buyXmr() {
|
export async function buyXmr() {
|
||||||
const state = store.getState();
|
const state = store.getState();
|
||||||
|
|
||||||
|
|
@ -284,6 +236,155 @@ export async function buyXmr() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function initializeContext() {
|
||||||
|
const network = getNetwork();
|
||||||
|
const testnet = isTestnet();
|
||||||
|
const useTor = store.getState().settings.enableTor;
|
||||||
|
|
||||||
|
// Get all Bitcoin nodes without checking availability
|
||||||
|
// The backend ElectrumBalancer will handle load balancing and failover
|
||||||
|
const bitcoinNodes =
|
||||||
|
store.getState().settings.nodes[network][Blockchain.Bitcoin];
|
||||||
|
|
||||||
|
// For Monero nodes, determine whether to use pool or custom node
|
||||||
|
const useMoneroRpcPool = store.getState().settings.useMoneroRpcPool;
|
||||||
|
|
||||||
|
const useMoneroTor = store.getState().settings.enableMoneroTor;
|
||||||
|
|
||||||
|
const moneroNodeUrl =
|
||||||
|
store.getState().settings.nodes[network][Blockchain.Monero][0] ?? null;
|
||||||
|
|
||||||
|
// Check the state of the Monero node
|
||||||
|
const moneroNodeConfig =
|
||||||
|
useMoneroRpcPool ||
|
||||||
|
moneroNodeUrl == null ||
|
||||||
|
!(await getMoneroNodeStatus(moneroNodeUrl, network))
|
||||||
|
? { type: "Pool" as const }
|
||||||
|
: {
|
||||||
|
type: "SingleNode" as const,
|
||||||
|
content: {
|
||||||
|
url: moneroNodeUrl,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initialize Tauri settings
|
||||||
|
const tauriSettings: TauriSettings = {
|
||||||
|
electrum_rpc_urls: bitcoinNodes,
|
||||||
|
monero_node_config: moneroNodeConfig,
|
||||||
|
use_tor: useTor,
|
||||||
|
enable_monero_tor: useMoneroTor,
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info({ tauriSettings }, "Initializing context with settings");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await invokeUnsafe<void>("initialize_context", {
|
||||||
|
settings: tauriSettings,
|
||||||
|
testnet,
|
||||||
|
});
|
||||||
|
logger.info("Initialized context");
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateAllNodeStatuses() {
|
||||||
|
const network = getNetwork();
|
||||||
|
const settings = store.getState().settings;
|
||||||
|
|
||||||
|
// Only check Monero nodes if we're using custom nodes (not RPC pool)
|
||||||
|
// Skip Bitcoin nodes since we pass all electrum servers to the backend without checking them (ElectrumBalancer handles failover)
|
||||||
|
if (!settings.useMoneroRpcPool) {
|
||||||
|
await Promise.all(
|
||||||
|
settings.nodes[network][Blockchain.Monero].map((node) =>
|
||||||
|
updateNodeStatus(node, Blockchain.Monero, network),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function cheapCheckBitcoinBalance() {
|
||||||
|
const response = await invoke<BalanceArgs, BalanceResponse>("get_balance", {
|
||||||
|
force_refresh: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
store.dispatch(setBitcoinBalance(response.balance));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getBitcoinAddress() {
|
||||||
|
const response = await invokeNoArgs<GetBitcoinAddressResponse>(
|
||||||
|
"get_bitcoin_address",
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllSwapInfos() {
|
||||||
|
const response =
|
||||||
|
await invokeNoArgs<GetSwapInfoResponse[]>("get_swap_infos_all");
|
||||||
|
|
||||||
|
response.forEach((swapInfo) => {
|
||||||
|
store.dispatch(rpcSetSwapInfo(swapInfo));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSwapInfo(swapId: string) {
|
||||||
|
const response = await invoke<GetSwapInfoArgs, GetSwapInfoResponse>(
|
||||||
|
"get_swap_info",
|
||||||
|
{
|
||||||
|
swap_id: swapId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
store.dispatch(rpcSetSwapInfo(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSwapTimelock(swapId: string) {
|
||||||
|
const response = await invoke<
|
||||||
|
GetSwapTimelockArgs,
|
||||||
|
GetSwapTimelockResponse
|
||||||
|
>("get_swap_timelock", {
|
||||||
|
swap_id: swapId,
|
||||||
|
});
|
||||||
|
|
||||||
|
store.dispatch(
|
||||||
|
timelockChangeEventReceived({
|
||||||
|
swap_id: response.swap_id,
|
||||||
|
timelock: response.timelock,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAllSwapTimelocks() {
|
||||||
|
const swapIds = selectAllSwapIds(store.getState());
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
swapIds.map(async (swapId) => {
|
||||||
|
try {
|
||||||
|
await getSwapTimelock(swapId);
|
||||||
|
} catch (error) {
|
||||||
|
logger.debug(`Failed to fetch timelock for swap ${swapId}: ${error}`);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function withdrawBtc(address: string): Promise<string> {
|
||||||
|
const response = await invoke<WithdrawBtcArgs, WithdrawBtcResponse>(
|
||||||
|
"withdraw_btc",
|
||||||
|
{
|
||||||
|
address,
|
||||||
|
amount: null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// We check the balance, this is cheap and does not sync the wallet
|
||||||
|
// but instead uses our local cached balance
|
||||||
|
await cheapCheckBitcoinBalance();
|
||||||
|
|
||||||
|
return response.txid;
|
||||||
|
}
|
||||||
|
|
||||||
export async function resumeSwap(swapId: string) {
|
export async function resumeSwap(swapId: string) {
|
||||||
await invoke<ResumeSwapArgs, ResumeSwapResponse>("resume_swap", {
|
await invoke<ResumeSwapArgs, ResumeSwapResponse>("resume_swap", {
|
||||||
swap_id: swapId,
|
swap_id: swapId,
|
||||||
|
|
@ -342,58 +443,6 @@ export async function listSellersAtRendezvousPoint(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initializeContext() {
|
|
||||||
const network = getNetwork();
|
|
||||||
const testnet = isTestnet();
|
|
||||||
const useTor = store.getState().settings.enableTor;
|
|
||||||
|
|
||||||
// Get all Bitcoin nodes without checking availability
|
|
||||||
// The backend ElectrumBalancer will handle load balancing and failover
|
|
||||||
const bitcoinNodes =
|
|
||||||
store.getState().settings.nodes[network][Blockchain.Bitcoin];
|
|
||||||
|
|
||||||
// For Monero nodes, determine whether to use pool or custom node
|
|
||||||
const useMoneroRpcPool = store.getState().settings.useMoneroRpcPool;
|
|
||||||
|
|
||||||
const useMoneroTor = store.getState().settings.enableMoneroTor;
|
|
||||||
|
|
||||||
const moneroNodeUrl =
|
|
||||||
store.getState().settings.nodes[network][Blockchain.Monero][0] ?? null;
|
|
||||||
|
|
||||||
// Check the state of the Monero node
|
|
||||||
const moneroNodeConfig =
|
|
||||||
useMoneroRpcPool ||
|
|
||||||
moneroNodeUrl == null ||
|
|
||||||
!(await getMoneroNodeStatus(moneroNodeUrl, network))
|
|
||||||
? { type: "Pool" as const }
|
|
||||||
: {
|
|
||||||
type: "SingleNode" as const,
|
|
||||||
content: {
|
|
||||||
url: moneroNodeUrl,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Initialize Tauri settings
|
|
||||||
const tauriSettings: TauriSettings = {
|
|
||||||
electrum_rpc_urls: bitcoinNodes,
|
|
||||||
monero_node_config: moneroNodeConfig,
|
|
||||||
use_tor: useTor,
|
|
||||||
enable_monero_tor: useMoneroTor,
|
|
||||||
};
|
|
||||||
|
|
||||||
logger.info({ tauriSettings }, "Initializing context with settings");
|
|
||||||
|
|
||||||
try {
|
|
||||||
await invokeUnsafe<void>("initialize_context", {
|
|
||||||
settings: tauriSettings,
|
|
||||||
testnet,
|
|
||||||
});
|
|
||||||
logger.info("Initialized context");
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getWalletDescriptor() {
|
export async function getWalletDescriptor() {
|
||||||
return await invokeNoArgs<ExportBitcoinWalletResponse>(
|
return await invokeNoArgs<ExportBitcoinWalletResponse>(
|
||||||
"get_wallet_descriptor",
|
"get_wallet_descriptor",
|
||||||
|
|
@ -451,21 +500,6 @@ async function updateNodeStatus(
|
||||||
store.dispatch(setStatus({ node, status, blockchain }));
|
store.dispatch(setStatus({ node, status, blockchain }));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateAllNodeStatuses() {
|
|
||||||
const network = getNetwork();
|
|
||||||
const settings = store.getState().settings;
|
|
||||||
|
|
||||||
// Only check Monero nodes if we're using custom nodes (not RPC pool)
|
|
||||||
// Skip Bitcoin nodes since we pass all electrum servers to the backend without checking them (ElectrumBalancer handles failover)
|
|
||||||
if (!settings.useMoneroRpcPool) {
|
|
||||||
await Promise.all(
|
|
||||||
settings.nodes[network][Blockchain.Monero].map((node) =>
|
|
||||||
updateNodeStatus(node, Blockchain.Monero, network),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getMoneroAddresses(): Promise<GetMoneroAddressesResponse> {
|
export async function getMoneroAddresses(): Promise<GetMoneroAddressesResponse> {
|
||||||
return await invokeNoArgs<GetMoneroAddressesResponse>("get_monero_addresses");
|
return await invokeNoArgs<GetMoneroAddressesResponse>("get_monero_addresses");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import {
|
||||||
ApprovalRequest,
|
ApprovalRequest,
|
||||||
TauriBackgroundProgressWrapper,
|
TauriBackgroundProgressWrapper,
|
||||||
TauriBackgroundProgress,
|
TauriBackgroundProgress,
|
||||||
|
ExpiredTimelocks,
|
||||||
} from "models/tauriModel";
|
} from "models/tauriModel";
|
||||||
import { MoneroRecoveryResponse } from "../../models/rpcModel";
|
import { MoneroRecoveryResponse } from "../../models/rpcModel";
|
||||||
import { GetSwapInfoResponseExt } from "models/tauriModelExt";
|
import { GetSwapInfoResponseExt } from "models/tauriModelExt";
|
||||||
|
|
@ -19,6 +20,9 @@ interface State {
|
||||||
swapInfos: {
|
swapInfos: {
|
||||||
[swapId: string]: GetSwapInfoResponseExt;
|
[swapId: string]: GetSwapInfoResponseExt;
|
||||||
};
|
};
|
||||||
|
swapTimelocks: {
|
||||||
|
[swapId: string]: ExpiredTimelocks;
|
||||||
|
};
|
||||||
moneroRecovery: {
|
moneroRecovery: {
|
||||||
swapId: string;
|
swapId: string;
|
||||||
keys: MoneroRecoveryResponse;
|
keys: MoneroRecoveryResponse;
|
||||||
|
|
@ -56,6 +60,7 @@ const initialState: RPCSlice = {
|
||||||
withdrawTxId: null,
|
withdrawTxId: null,
|
||||||
rendezvousDiscoveredSellers: [],
|
rendezvousDiscoveredSellers: [],
|
||||||
swapInfos: {},
|
swapInfos: {},
|
||||||
|
swapTimelocks: {},
|
||||||
moneroRecovery: null,
|
moneroRecovery: null,
|
||||||
background: {},
|
background: {},
|
||||||
backgroundRefund: null,
|
backgroundRefund: null,
|
||||||
|
|
@ -84,14 +89,8 @@ export const rpcSlice = createSlice({
|
||||||
slice: RPCSlice,
|
slice: RPCSlice,
|
||||||
action: PayloadAction<TauriTimelockChangeEvent>,
|
action: PayloadAction<TauriTimelockChangeEvent>,
|
||||||
) {
|
) {
|
||||||
if (slice.state.swapInfos[action.payload.swap_id]) {
|
slice.state.swapTimelocks[action.payload.swap_id] =
|
||||||
slice.state.swapInfos[action.payload.swap_id].timelock =
|
|
||||||
action.payload.timelock;
|
action.payload.timelock;
|
||||||
} else {
|
|
||||||
logger.warn(
|
|
||||||
`Received timelock change event for unknown swap ${action.payload.swap_id}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
rpcSetWithdrawTxId(slice, action: PayloadAction<string>) {
|
rpcSetWithdrawTxId(slice, action: PayloadAction<string>) {
|
||||||
slice.state.withdrawTxId = action.payload;
|
slice.state.withdrawTxId = action.payload;
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,11 @@ import {
|
||||||
} from "models/tauriModel";
|
} from "models/tauriModel";
|
||||||
import { Alert } from "models/apiModel";
|
import { Alert } from "models/apiModel";
|
||||||
import { fnv1a } from "utils/hash";
|
import { fnv1a } from "utils/hash";
|
||||||
|
import {
|
||||||
|
selectAllSwapInfos,
|
||||||
|
selectPendingApprovals,
|
||||||
|
selectSwapInfoWithTimelock,
|
||||||
|
} from "./selectors";
|
||||||
|
|
||||||
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
export const useAppDispatch = () => useDispatch<AppDispatch>();
|
||||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||||
|
|
@ -159,8 +164,8 @@ export function useAllMakers() {
|
||||||
/// This hook returns the all swap infos, as an array
|
/// This hook returns the all swap infos, as an array
|
||||||
/// Excluding those who are in a state where it's better to hide them from the user
|
/// Excluding those who are in a state where it's better to hide them from the user
|
||||||
export function useSaneSwapInfos() {
|
export function useSaneSwapInfos() {
|
||||||
const swapInfos = useAppSelector((state) => state.rpc.state.swapInfos);
|
const swapInfos = useAppSelector(selectAllSwapInfos);
|
||||||
return Object.values(swapInfos).filter((swap) => {
|
return swapInfos.filter((swap) => {
|
||||||
// We hide swaps that are in the SwapSetupCompleted state
|
// We hide swaps that are in the SwapSetupCompleted state
|
||||||
// This is because they are probably ones where:
|
// This is because they are probably ones where:
|
||||||
// 1. The user force stopped the swap while we were waiting for their confirmation of the offer
|
// 1. The user force stopped the swap while we were waiting for their confirmation of the offer
|
||||||
|
|
@ -203,10 +208,7 @@ export function useNodes<T>(selector: (nodes: NodesSlice) => T): T {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePendingApprovals(): PendingApprovalRequest[] {
|
export function usePendingApprovals(): PendingApprovalRequest[] {
|
||||||
const approvals = useAppSelector((state) => state.rpc.state.approvalRequests);
|
return useAppSelector(selectPendingApprovals) as PendingApprovalRequest[];
|
||||||
return Object.values(approvals).filter(
|
|
||||||
(c) => c.request_status.state === "Pending",
|
|
||||||
) as PendingApprovalRequest[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePendingLockBitcoinApproval(): PendingLockBitcoinApprovalRequest[] {
|
export function usePendingLockBitcoinApproval(): PendingLockBitcoinApprovalRequest[] {
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,13 @@ import { createListenerMiddleware } from "@reduxjs/toolkit";
|
||||||
import { throttle, debounce } from "lodash";
|
import { throttle, debounce } from "lodash";
|
||||||
import {
|
import {
|
||||||
getAllSwapInfos,
|
getAllSwapInfos,
|
||||||
|
getAllSwapTimelocks,
|
||||||
checkBitcoinBalance,
|
checkBitcoinBalance,
|
||||||
getBitcoinAddress,
|
getBitcoinAddress,
|
||||||
updateAllNodeStatuses,
|
updateAllNodeStatuses,
|
||||||
fetchSellersAtPresetRendezvousPoints,
|
fetchSellersAtPresetRendezvousPoints,
|
||||||
getSwapInfo,
|
getSwapInfo,
|
||||||
|
getSwapTimelock,
|
||||||
initializeMoneroWallet,
|
initializeMoneroWallet,
|
||||||
changeMoneroNode,
|
changeMoneroNode,
|
||||||
getCurrentMoneroNodeConfig,
|
getCurrentMoneroNodeConfig,
|
||||||
|
|
@ -46,7 +48,12 @@ const getThrottledSwapInfoUpdater = (swapId: string) => {
|
||||||
// but will wait for 3 seconds of quiet during rapid calls (using debounce)
|
// but will wait for 3 seconds of quiet during rapid calls (using debounce)
|
||||||
const debouncedGetSwapInfo = debounce(() => {
|
const debouncedGetSwapInfo = debounce(() => {
|
||||||
logger.debug(`Executing getSwapInfo for swap ${swapId}`);
|
logger.debug(`Executing getSwapInfo for swap ${swapId}`);
|
||||||
getSwapInfo(swapId);
|
getSwapInfo(swapId).catch((error) => {
|
||||||
|
logger.debug(`Failed to fetch swap info for swap ${swapId}: ${error}`);
|
||||||
|
});
|
||||||
|
getSwapTimelock(swapId).catch((error) => {
|
||||||
|
logger.debug(`Failed to fetch timelock for swap ${swapId}: ${error}`);
|
||||||
|
});
|
||||||
}, 3000); // 3 seconds debounce for rapid calls
|
}, 3000); // 3 seconds debounce for rapid calls
|
||||||
|
|
||||||
const throttledFunction = throttle(debouncedGetSwapInfo, 2000, {
|
const throttledFunction = throttle(debouncedGetSwapInfo, 2000, {
|
||||||
|
|
@ -131,6 +138,7 @@ export function createMainListeners() {
|
||||||
"Database & Bitcoin wallet just became available, fetching swap infos...",
|
"Database & Bitcoin wallet just became available, fetching swap infos...",
|
||||||
);
|
);
|
||||||
await getAllSwapInfos();
|
await getAllSwapInfos();
|
||||||
|
await getAllSwapTimelocks();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the database just became availiable, fetch sellers at preset rendezvous points
|
// If the database just became availiable, fetch sellers at preset rendezvous points
|
||||||
|
|
|
||||||
50
src-gui/src/store/selectors.ts
Normal file
50
src-gui/src/store/selectors.ts
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { createSelector } from "@reduxjs/toolkit";
|
||||||
|
import { RootState } from "renderer/store/storeRenderer";
|
||||||
|
import { GetSwapInfoResponseExt } from "models/tauriModelExt";
|
||||||
|
import { ExpiredTimelocks } from "models/tauriModel";
|
||||||
|
|
||||||
|
const selectRpcState = (state: RootState) => state.rpc.state;
|
||||||
|
|
||||||
|
export const selectAllSwapIds = createSelector([selectRpcState], (rpcState) =>
|
||||||
|
Object.keys(rpcState.swapInfos),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectAllSwapInfos = createSelector([selectRpcState], (rpcState) =>
|
||||||
|
Object.values(rpcState.swapInfos),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectSwapTimelocks = createSelector(
|
||||||
|
[selectRpcState],
|
||||||
|
(rpcState) => rpcState.swapTimelocks,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectSwapTimelock = (swapId: string) =>
|
||||||
|
createSelector(
|
||||||
|
[selectSwapTimelocks],
|
||||||
|
(timelocks) => timelocks[swapId] ?? null,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectSwapInfoWithTimelock = (swapId: string) =>
|
||||||
|
createSelector(
|
||||||
|
[selectRpcState],
|
||||||
|
(
|
||||||
|
rpcState,
|
||||||
|
):
|
||||||
|
| (GetSwapInfoResponseExt & { timelock: ExpiredTimelocks | null })
|
||||||
|
| null => {
|
||||||
|
const swapInfo = rpcState.swapInfos[swapId];
|
||||||
|
if (!swapInfo) return null;
|
||||||
|
return {
|
||||||
|
...swapInfo,
|
||||||
|
timelock: rpcState.swapTimelocks[swapId] ?? null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const selectPendingApprovals = createSelector(
|
||||||
|
[selectRpcState],
|
||||||
|
(rpcState) =>
|
||||||
|
Object.values(rpcState.approvalRequests).filter(
|
||||||
|
(c) => c.request_status.state === "Pending",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
@ -244,7 +244,6 @@ pub struct GetSwapInfoResponse {
|
||||||
pub btc_refund_address: String,
|
pub btc_refund_address: String,
|
||||||
pub cancel_timelock: CancelTimelock,
|
pub cancel_timelock: CancelTimelock,
|
||||||
pub punish_timelock: PunishTimelock,
|
pub punish_timelock: PunishTimelock,
|
||||||
pub timelock: Option<ExpiredTimelocks>,
|
|
||||||
pub monero_receive_pool: MoneroAddressPool,
|
pub monero_receive_pool: MoneroAddressPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,6 +255,30 @@ impl Request for GetSwapInfoArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSwapTimelock
|
||||||
|
#[typeshare]
|
||||||
|
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub struct GetSwapTimelockArgs {
|
||||||
|
#[typeshare(serialized_as = "string")]
|
||||||
|
pub swap_id: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[typeshare]
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct GetSwapTimelockResponse {
|
||||||
|
#[typeshare(serialized_as = "string")]
|
||||||
|
pub swap_id: Uuid,
|
||||||
|
pub timelock: Option<ExpiredTimelocks>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request for GetSwapTimelockArgs {
|
||||||
|
type Response = GetSwapTimelockResponse;
|
||||||
|
|
||||||
|
async fn request(self, ctx: Arc<Context>) -> Result<Self::Response> {
|
||||||
|
get_swap_timelock(self, ctx).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Balance
|
// Balance
|
||||||
#[typeshare]
|
#[typeshare]
|
||||||
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
|
|
@ -826,7 +849,6 @@ pub async fn get_swap_info(
|
||||||
args: GetSwapInfoArgs,
|
args: GetSwapInfoArgs,
|
||||||
context: Arc<Context>,
|
context: Arc<Context>,
|
||||||
) -> Result<GetSwapInfoResponse> {
|
) -> Result<GetSwapInfoResponse> {
|
||||||
let bitcoin_wallet = context.try_get_bitcoin_wallet().await?;
|
|
||||||
let db = context.try_get_db().await?;
|
let db = context.try_get_db().await?;
|
||||||
|
|
||||||
let state = db.get_state(args.swap_id).await?;
|
let state = db.get_state(args.swap_id).await?;
|
||||||
|
|
@ -890,14 +912,6 @@ pub async fn get_swap_info(
|
||||||
})
|
})
|
||||||
.with_context(|| "Did not find SwapSetupCompleted state for swap")?;
|
.with_context(|| "Did not find SwapSetupCompleted state for swap")?;
|
||||||
|
|
||||||
let timelock = match swap_state.expired_timelocks(bitcoin_wallet.clone()).await {
|
|
||||||
Ok(timelock) => timelock,
|
|
||||||
Err(err) => {
|
|
||||||
error!(swap_id = %args.swap_id, error = ?err, "Failed to fetch expired timelock status");
|
|
||||||
None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let monero_receive_pool = db.get_monero_address_pool(args.swap_id).await?;
|
let monero_receive_pool = db.get_monero_address_pool(args.swap_id).await?;
|
||||||
|
|
||||||
Ok(GetSwapInfoResponse {
|
Ok(GetSwapInfoResponse {
|
||||||
|
|
@ -918,11 +932,29 @@ pub async fn get_swap_info(
|
||||||
btc_refund_address: btc_refund_address.to_string(),
|
btc_refund_address: btc_refund_address.to_string(),
|
||||||
cancel_timelock,
|
cancel_timelock,
|
||||||
punish_timelock,
|
punish_timelock,
|
||||||
timelock,
|
|
||||||
monero_receive_pool,
|
monero_receive_pool,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(fields(method = "get_swap_timelock"), skip(context))]
|
||||||
|
pub async fn get_swap_timelock(
|
||||||
|
args: GetSwapTimelockArgs,
|
||||||
|
context: Arc<Context>,
|
||||||
|
) -> Result<GetSwapTimelockResponse> {
|
||||||
|
let bitcoin_wallet = context.try_get_bitcoin_wallet().await?;
|
||||||
|
let db = context.try_get_db().await?;
|
||||||
|
|
||||||
|
let state = db.get_state(args.swap_id).await?;
|
||||||
|
let swap_state: BobState = state.try_into()?;
|
||||||
|
|
||||||
|
let timelock = swap_state.expired_timelocks(bitcoin_wallet.clone()).await?;
|
||||||
|
|
||||||
|
Ok(GetSwapTimelockResponse {
|
||||||
|
swap_id: args.swap_id,
|
||||||
|
timelock,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[tracing::instrument(fields(method = "buy_xmr"), skip(context))]
|
#[tracing::instrument(fields(method = "buy_xmr"), skip(context))]
|
||||||
pub async fn buy_xmr(
|
pub async fn buy_xmr(
|
||||||
buy_xmr: BuyXmrArgs,
|
buy_xmr: BuyXmrArgs,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue