xmr-btc-swap/src-gui/src/store/features/makersSlice.ts
Mohan 210cc04ced
feat(gui, cli): Request quotes concurrently at all sellers (#429)
* feat(gui): Implement base structure for new swap ux

- refactored file structure to match common projecte structure
- implement step get bitcoin

* feat(gui): Implement basic multi step modal

* feat(gui): Add outline of add choose maker and offer step

* feat(gui): Add receive address selector

* refactor(gui): format code

* feat(gui): Make Swap Overveiw interactive

* feat(gui): Add action to swap amount selector to quickly go to deposit bitcoin step

* progress

* feat(gui, cli): Request quotes concurrently at all sellers

* refresh offers occasionally, display progress

* progress

* feat(gui, cli): Request quotes concurrently at all sellers

* refresh offers occasionally, display progress

* progress, works again

* allow closing dialog without warning if no funds have been locked

* progress

* feat(gui): Rewrite Swap Components to have flow directly on swap page

* feat: log monero_rpc_pool only at >= INFO level

* remove full_url, add migration to change scheme of node.monerodevs.org to http

* feat: send known_quotes with WaitingForBitcoinDeposit Tauri progress event (even if our balance is too low)

* lock swap lock later

* refactor(monero-rpc-pool): Pass around tuple of (scheme, host, port) as nodes

* refactor(gui): Remove modal for swap and adjust few pages for swap process

- Moved files from swap modal to page directory
- Use new layouts for init page
- Use new layout for depositBTC Step
- Use new layout for Offer Page

* allow cancel before lock

* remove unused code

* dynamic layout, chips for amounts

* feat(gui): Add breakpoints

* remove continue button, add select button on each maker box

* add GetCurrentSwapArgs tauri command to only suspend swap if one is actually running

* feat(gui): Show all known quotes and disable the ones that aren't available

* fix get_current_swap, kill tasks when buy_xmr is cancelled

* cleanup: remove CleanupGuard

* feat(gui): Add cancel button on every page

* refactor(gui): Fix merge issues

* refactor(gui): Unify Cancel Button insertion by using a swap base page

* refactor(gui): Unify Cancel Button insertion by using a swap base page

* refactor(gui): Remove deeply nested relative paths

* refactor(gui): Made BaseSwapPage obsolete by moving Cancel Button to SwapStatePage

* refactor(gui): Adjust condition for showing SwapSuspendAlert

* fix(gui): Fetch previous monero redeem addresses repeatedly

* refactor(gui): Remove QR Code from deposit and choose maker page

* refactor(gui): Don't display dialog on History page

* fix(gui): If no swap was running "suspend_current_swap" will still return success now, less logic in the CancelButton

* get offer select working

* refactor: dont display cancel button on set redeem address page

* feat: add pagination to offers

* refactor

* emit partial events for list_sellers

* refactor: remove torSlice

* refactor: use sync (non tokio) mutex for approvals

* throttle getSwapInfo calls

* feat: add debug page back, add info in suspend dialog about what will happen

* refactor: format files

* refactor(gui): Remove sortMakers method and replace with method that sorts approvals

* refactor(gui): Refactor swap page structure

* fix(gui): Add breakpoints to swapSetupInflightPage

* feat(gui): Add flag for outdated makers

* refactor(gui): Reduce fetch rate for maker quotes

* fix(gui): Debug Window size

* no unwrap

---------

Co-authored-by: b-enedict <benedict.seuss@gmail.com>
2025-07-02 16:21:36 +02:00

122 lines
3.7 KiB
TypeScript

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { ExtendedMakerStatus, MakerStatus } from "models/apiModel";
import { SellerStatus } from "models/tauriModel";
import { getStubTestnetMaker } from "store/config";
import { rendezvousSellerToMakerStatus } from "utils/conversionUtils";
import { isMakerOutdated } from "utils/multiAddrUtils";
const stubTestnetMaker = getStubTestnetMaker();
export interface MakersSlice {
rendezvous: {
makers: (ExtendedMakerStatus | MakerStatus)[];
};
registry: {
makers: ExtendedMakerStatus[] | null;
// This counts how many failed connections attempts we have counted since the last successful connection
connectionFailsCount: number;
};
selectedMaker: ExtendedMakerStatus | null;
}
const initialState: MakersSlice = {
rendezvous: {
makers: [],
},
registry: {
makers: stubTestnetMaker ? [stubTestnetMaker] : null,
connectionFailsCount: 0,
},
selectedMaker: null,
};
function selectNewSelectedMaker(
slice: MakersSlice,
peerId?: string,
): MakerStatus {
const selectedPeerId = peerId || slice.selectedMaker?.peerId;
// Check if we still have a record of the currently selected provider
const currentMaker =
slice.registry.makers?.find((prov) => prov.peerId === selectedPeerId) ||
slice.rendezvous.makers.find((prov) => prov.peerId === selectedPeerId);
// If the currently selected provider is not outdated, keep it
if (currentMaker != null && !isMakerOutdated(currentMaker)) {
return currentMaker;
}
// Otherwise we'd prefer to switch to a provider that has the newest version
const providers = [
...(slice.registry.makers ?? []),
...(slice.rendezvous.makers ?? []),
];
return providers.at(0) || null;
}
export const makersSlice = createSlice({
name: "providers",
initialState,
reducers: {
discoveredMakersByRendezvous(slice, action: PayloadAction<SellerStatus[]>) {
action.payload.forEach((discoveredSeller) => {
const discoveredMakerStatus =
rendezvousSellerToMakerStatus(discoveredSeller);
// If the seller has a status of "Unreachable" the provider is not added to the list
if (discoveredMakerStatus === null) {
return;
}
// If the provider was already discovered via the public registry, don't add it again
const indexOfExistingMaker = slice.rendezvous.makers.findIndex(
(prov) =>
prov.peerId === discoveredMakerStatus.peerId &&
prov.multiAddr === discoveredMakerStatus.multiAddr,
);
// Avoid duplicate entries, replace them instead
if (indexOfExistingMaker !== -1) {
slice.rendezvous.makers[indexOfExistingMaker] = discoveredMakerStatus;
} else {
slice.rendezvous.makers.push(discoveredMakerStatus);
}
});
// Sort the provider list and select a new provider if needed
slice.selectedMaker = selectNewSelectedMaker(slice);
},
setRegistryMakers(slice, action: PayloadAction<ExtendedMakerStatus[]>) {
if (stubTestnetMaker) {
action.payload.push(stubTestnetMaker);
}
// Sort the provider list and select a new provider if needed
slice.selectedMaker = selectNewSelectedMaker(slice);
},
registryConnectionFailed(slice) {
slice.registry.connectionFailsCount += 1;
},
setSelectedMaker(
slice,
action: PayloadAction<{
peerId: string;
}>,
) {
slice.selectedMaker = selectNewSelectedMaker(
slice,
action.payload.peerId,
);
},
},
});
export const {
discoveredMakersByRendezvous,
setRegistryMakers,
registryConnectionFailed,
setSelectedMaker,
} = makersSlice.actions;
export default makersSlice.reducer;