mirror of
https://github.com/RoboSats/robosats.git
synced 2025-01-19 04:31:35 +00:00
Merge pull request #1407 from RoboSats/refactor-order-model
Refactor Order/Slot models
This commit is contained in:
commit
a390254e2d
@ -21,8 +21,8 @@ const App = (): JSX.Element => {
|
|||||||
<Suspense fallback='loading'>
|
<Suspense fallback='loading'>
|
||||||
<I18nextProvider i18n={i18n}>
|
<I18nextProvider i18n={i18n}>
|
||||||
<AppContextProvider>
|
<AppContextProvider>
|
||||||
<GarageContextProvider>
|
|
||||||
<FederationContextProvider>
|
<FederationContextProvider>
|
||||||
|
<GarageContextProvider>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
{window.NativeRobosats === undefined && window.RobosatsClient === undefined ? (
|
{window.NativeRobosats === undefined && window.RobosatsClient === undefined ? (
|
||||||
<HostAlert />
|
<HostAlert />
|
||||||
@ -30,8 +30,8 @@ const App = (): JSX.Element => {
|
|||||||
<TorConnectionBadge />
|
<TorConnectionBadge />
|
||||||
)}
|
)}
|
||||||
<Main />
|
<Main />
|
||||||
</FederationContextProvider>
|
|
||||||
</GarageContextProvider>
|
</GarageContextProvider>
|
||||||
|
</FederationContextProvider>
|
||||||
</AppContextProvider>
|
</AppContextProvider>
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
@ -12,12 +12,10 @@ import BookTable from '../../components/BookTable';
|
|||||||
import { BarChart, FormatListBulleted, Map } from '@mui/icons-material';
|
import { BarChart, FormatListBulleted, Map } from '@mui/icons-material';
|
||||||
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||||
import MapChart from '../../components/Charts/MapChart';
|
import MapChart from '../../components/Charts/MapChart';
|
||||||
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
|
||||||
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
const BookPage = (): JSX.Element => {
|
const BookPage = (): JSX.Element => {
|
||||||
const { windowSize } = useContext<UseAppStoreType>(AppContext);
|
const { windowSize } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { setDelay, setCurrentOrderId } = useContext<UseFederationStoreType>(FederationContext);
|
|
||||||
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -32,8 +30,6 @@ const BookPage = (): JSX.Element => {
|
|||||||
|
|
||||||
const onOrderClicked = function (id: number, shortAlias: string): void {
|
const onOrderClicked = function (id: number, shortAlias: string): void {
|
||||||
if (garage.getSlot()?.hashId) {
|
if (garage.getSlot()?.hashId) {
|
||||||
setDelay(10000);
|
|
||||||
setCurrentOrderId({ id, shortAlias });
|
|
||||||
navigate(`/order/${shortAlias}/${id}`);
|
navigate(`/order/${shortAlias}/${id}`);
|
||||||
} else {
|
} else {
|
||||||
setOpenNoRobot(true);
|
setOpenNoRobot(true);
|
||||||
@ -102,9 +98,6 @@ const BookPage = (): JSX.Element => {
|
|||||||
>
|
>
|
||||||
<Box sx={{ maxWidth: '18em', padding: '0.5em' }}>
|
<Box sx={{ maxWidth: '18em', padding: '0.5em' }}>
|
||||||
<MakerForm
|
<MakerForm
|
||||||
onOrderCreated={(id) => {
|
|
||||||
navigate(`/order/${id}`);
|
|
||||||
}}
|
|
||||||
onClickGenerateRobot={() => {
|
onClickGenerateRobot={() => {
|
||||||
navigate('/robot');
|
navigate('/robot');
|
||||||
}}
|
}}
|
||||||
|
@ -14,8 +14,7 @@ import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageCon
|
|||||||
|
|
||||||
const MakerPage = (): JSX.Element => {
|
const MakerPage = (): JSX.Element => {
|
||||||
const { fav, windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext);
|
const { fav, windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { federation, setDelay, setCurrentOrderId } =
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
useContext<UseFederationStoreType>(FederationContext);
|
|
||||||
const { garage, maker } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage, maker } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -54,8 +53,6 @@ const MakerPage = (): JSX.Element => {
|
|||||||
|
|
||||||
const onOrderClicked = function (id: number, shortAlias: string): void {
|
const onOrderClicked = function (id: number, shortAlias: string): void {
|
||||||
if (garage.getSlot()?.hashId) {
|
if (garage.getSlot()?.hashId) {
|
||||||
setDelay(10000);
|
|
||||||
setCurrentOrderId({ id, shortAlias });
|
|
||||||
navigate(`/order/${shortAlias}/${id}`);
|
navigate(`/order/${shortAlias}/${id}`);
|
||||||
} else {
|
} else {
|
||||||
setOpenNoRobot(true);
|
setOpenNoRobot(true);
|
||||||
@ -105,10 +102,6 @@ const MakerPage = (): JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MakerForm
|
<MakerForm
|
||||||
onOrderCreated={(shortAlias, id) => {
|
|
||||||
setCurrentOrderId({ id, shortAlias });
|
|
||||||
navigate(`/order/${shortAlias}/${id}`);
|
|
||||||
}}
|
|
||||||
disableRequest={matches.length > 0 && !showMatches}
|
disableRequest={matches.length > 0 && !showMatches}
|
||||||
collapseAll={showMatches}
|
collapseAll={showMatches}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
|
@ -17,15 +17,13 @@ import {
|
|||||||
import RobotAvatar from '../../components/RobotAvatar';
|
import RobotAvatar from '../../components/RobotAvatar';
|
||||||
import { AppContext, type UseAppStoreType, closeAll } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType, closeAll } from '../../contexts/AppContext';
|
||||||
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
|
||||||
|
|
||||||
const NavBar = (): JSX.Element => {
|
const NavBar = (): JSX.Element => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { page, setPage, settings, setSlideDirection, open, setOpen, windowSize, navbarHeight } =
|
const { page, setPage, settings, setSlideDirection, open, setOpen, windowSize, navbarHeight } =
|
||||||
useContext<UseAppStoreType>(AppContext);
|
useContext<UseAppStoreType>(AppContext);
|
||||||
const { garage, robotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage, slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { setCurrentOrderId } = useContext<UseFederationStoreType>(FederationContext);
|
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -50,7 +48,7 @@ const NavBar = (): JSX.Element => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// re-render on orde rand robot updated at for latest orderId in tab
|
// re-render on orde rand robot updated at for latest orderId in tab
|
||||||
}, [robotUpdatedAt]);
|
}, [slotUpdatedAt]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// change tab (page) into the current route
|
// change tab (page) into the current route
|
||||||
@ -77,14 +75,10 @@ const NavBar = (): JSX.Element => {
|
|||||||
const slot = garage.getSlot();
|
const slot = garage.getSlot();
|
||||||
handleSlideDirection(page, newPage);
|
handleSlideDirection(page, newPage);
|
||||||
setPage(newPage);
|
setPage(newPage);
|
||||||
const shortAlias = String(slot?.activeShortAlias);
|
|
||||||
const activeOrderId = slot?.getRobot(slot?.activeShortAlias ?? '')?.activeOrderId;
|
const shortAlias = slot?.activeOrder?.shortAlias;
|
||||||
const lastOrderId = slot?.getRobot(slot?.lastShortAlias ?? '')?.lastOrderId;
|
const orderId = slot?.activeOrder?.id;
|
||||||
const param =
|
const param = newPage === 'order' ? `${String(shortAlias)}/${String(orderId)}` : '';
|
||||||
newPage === 'order' ? `${shortAlias}/${String(activeOrderId ?? lastOrderId)}` : '';
|
|
||||||
if (newPage === 'order') {
|
|
||||||
setCurrentOrderId({ id: activeOrderId ?? lastOrderId, shortAlias });
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate(`/${newPage}/${param}`);
|
navigate(`/${newPage}/${param}`);
|
||||||
}, theme.transitions.duration.leavingScreen * 3);
|
}, theme.transitions.duration.leavingScreen * 3);
|
||||||
@ -162,7 +156,7 @@ const NavBar = (): JSX.Element => {
|
|||||||
sx={tabSx}
|
sx={tabSx}
|
||||||
label={smallBar ? undefined : t('Order')}
|
label={smallBar ? undefined : t('Order')}
|
||||||
value='order'
|
value='order'
|
||||||
disabled={!slot?.getRobot()?.activeOrderId}
|
disabled={!slot?.activeOrder}
|
||||||
icon={<Assignment />}
|
icon={<Assignment />}
|
||||||
iconPosition='start'
|
iconPosition='start'
|
||||||
/>
|
/>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Tab, Tabs, Paper, CircularProgress, Grid, Typography, Box } from '@mui/material';
|
import { Tab, Tabs, Paper, CircularProgress, Grid, Typography, Box } from '@mui/material';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
@ -6,60 +6,59 @@ import { useNavigate, useParams } from 'react-router-dom';
|
|||||||
import TradeBox from '../../components/TradeBox';
|
import TradeBox from '../../components/TradeBox';
|
||||||
import OrderDetails from '../../components/OrderDetails';
|
import OrderDetails from '../../components/OrderDetails';
|
||||||
|
|
||||||
import { AppContext, closeAll, type UseAppStoreType } from '../../contexts/AppContext';
|
import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
||||||
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
|
||||||
import { WarningDialog } from '../../components/Dialogs';
|
import { WarningDialog } from '../../components/Dialogs';
|
||||||
|
import { Order, type Slot } from '../../models';
|
||||||
|
import { type UseGarageStoreType, GarageContext } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
const OrderPage = (): JSX.Element => {
|
const OrderPage = (): JSX.Element => {
|
||||||
const {
|
const { windowSize, setOpen, acknowledgedWarning, setAcknowledgedWarning, navbarHeight } =
|
||||||
windowSize,
|
useContext<UseAppStoreType>(AppContext);
|
||||||
open,
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
setOpen,
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
acknowledgedWarning,
|
|
||||||
setAcknowledgedWarning,
|
|
||||||
settings,
|
|
||||||
navbarHeight,
|
|
||||||
hostUrl,
|
|
||||||
origin,
|
|
||||||
} = useContext<UseAppStoreType>(AppContext);
|
|
||||||
const { federation, currentOrder, currentOrderId, setCurrentOrderId } =
|
|
||||||
useContext<UseFederationStoreType>(FederationContext);
|
|
||||||
const { badOrder } = useContext<UseGarageStoreType>(GarageContext);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
const paramsRef = useRef(params);
|
||||||
|
|
||||||
const doublePageWidth: number = 50;
|
const doublePageWidth: number = 50;
|
||||||
const maxHeight: number = (windowSize?.height - navbarHeight) * 0.85 - 3;
|
const maxHeight: number = (windowSize?.height - navbarHeight) * 0.85 - 3;
|
||||||
|
|
||||||
const [tab, setTab] = useState<'order' | 'contract'>('contract');
|
const [tab, setTab] = useState<'order' | 'contract'>('contract');
|
||||||
const [baseUrl, setBaseUrl] = useState<string>(hostUrl);
|
const [currentOrder, setCurrentOrder] = useState<Order | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
paramsRef.current = params;
|
||||||
const shortAlias = params.shortAlias;
|
const shortAlias = params.shortAlias;
|
||||||
const coordinator = federation.getCoordinator(shortAlias ?? '');
|
|
||||||
if (coordinator) {
|
|
||||||
const endpoint = coordinator?.getEndpoint(
|
|
||||||
settings.network,
|
|
||||||
origin,
|
|
||||||
settings.selfhostedClient,
|
|
||||||
hostUrl,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (endpoint) setBaseUrl(`${endpoint?.url}${endpoint?.basePath}`);
|
|
||||||
|
|
||||||
const orderId = Number(params.orderId);
|
const orderId = Number(params.orderId);
|
||||||
if (
|
const slot = garage.getSlot();
|
||||||
orderId &&
|
if (slot?.token) {
|
||||||
currentOrderId.id !== orderId &&
|
let order = new Order({ id: orderId, shortAlias });
|
||||||
currentOrderId.shortAlias !== shortAlias &&
|
if (slot.activeOrder?.id === orderId && slot.activeOrder?.shortAlias === shortAlias) {
|
||||||
shortAlias
|
order = slot.activeOrder;
|
||||||
)
|
} else if (slot.lastOrder?.id === orderId && slot.lastOrder?.shortAlias === shortAlias) {
|
||||||
setCurrentOrderId({ id: orderId, shortAlias });
|
order = slot.lastOrder;
|
||||||
if (!acknowledgedWarning) setOpen({ ...closeAll, warning: true });
|
|
||||||
}
|
}
|
||||||
}, [params, currentOrderId]);
|
void order.fecth(federation, slot).then((updatedOrder) => {
|
||||||
|
updateSlotFromOrder(updatedOrder, slot);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
setCurrentOrder(null);
|
||||||
|
};
|
||||||
|
}, [params.orderId]);
|
||||||
|
|
||||||
|
const updateSlotFromOrder = (updatedOrder: Order, slot: Slot): void => {
|
||||||
|
if (
|
||||||
|
Number(paramsRef.current.orderId) === updatedOrder.id &&
|
||||||
|
paramsRef.current.shortAlias === updatedOrder.shortAlias
|
||||||
|
) {
|
||||||
|
setCurrentOrder(updatedOrder);
|
||||||
|
slot.updateSlotFromOrder(updatedOrder);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onClickCoordinator = function (): void {
|
const onClickCoordinator = function (): void {
|
||||||
if (currentOrder?.shortAlias != null) {
|
if (currentOrder?.shortAlias != null) {
|
||||||
@ -87,7 +86,7 @@ const OrderPage = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const tradeBoxSpace = currentOrder ? (
|
const tradeBoxSpace = currentOrder ? (
|
||||||
<TradeBox baseUrl={baseUrl} onStartAgain={startAgain} />
|
<TradeBox onStartAgain={startAgain} currentOrder={currentOrder} />
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
);
|
);
|
||||||
@ -95,20 +94,19 @@ const OrderPage = (): JSX.Element => {
|
|||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<WarningDialog
|
<WarningDialog
|
||||||
open={open.warning}
|
open={!acknowledgedWarning && currentOrder?.status === 0}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen(closeAll);
|
|
||||||
setAcknowledgedWarning(true);
|
setAcknowledgedWarning(true);
|
||||||
}}
|
}}
|
||||||
longAlias={federation.getCoordinator(params.shortAlias ?? '')?.longAlias}
|
longAlias={federation.getCoordinator(params.shortAlias ?? '')?.longAlias}
|
||||||
/>
|
/>
|
||||||
{currentOrder === null && badOrder === undefined && <CircularProgress />}
|
{!currentOrder?.maker_hash_id && <CircularProgress />}
|
||||||
{badOrder !== undefined ? (
|
{currentOrder?.bad_request && currentOrder.status !== 5 ? (
|
||||||
<Typography align='center' variant='subtitle2' color='secondary'>
|
<Typography align='center' variant='subtitle2' color='secondary'>
|
||||||
{t(badOrder)}
|
{t(currentOrder.bad_request)}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
{currentOrder !== null && badOrder === undefined ? (
|
{currentOrder?.maker_hash_id && (!currentOrder.bad_request || currentOrder.status === 5) ? (
|
||||||
currentOrder.is_participant ? (
|
currentOrder.is_participant ? (
|
||||||
windowSize.width > doublePageWidth ? (
|
windowSize.width > doublePageWidth ? (
|
||||||
// DOUBLE PAPER VIEW
|
// DOUBLE PAPER VIEW
|
||||||
|
@ -22,7 +22,6 @@ import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
|||||||
import { genBase62Token } from '../../utils';
|
import { genBase62Token } from '../../utils';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
|
||||||
|
|
||||||
interface RobotProfileProps {
|
interface RobotProfileProps {
|
||||||
robot: Robot;
|
robot: Robot;
|
||||||
@ -45,8 +44,7 @@ const RobotProfile = ({
|
|||||||
width,
|
width,
|
||||||
}: RobotProfileProps): JSX.Element => {
|
}: RobotProfileProps): JSX.Element => {
|
||||||
const { windowSize } = useContext<UseAppStoreType>(AppContext);
|
const { windowSize } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { garage, robotUpdatedAt, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage, slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { setCurrentOrderId } = useContext<UseFederationStoreType>(FederationContext);
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -59,7 +57,7 @@ const RobotProfile = ({
|
|||||||
if (slot?.hashId) {
|
if (slot?.hashId) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [orderUpdatedAt, robotUpdatedAt, loading]);
|
}, [slotUpdatedAt, loading]);
|
||||||
|
|
||||||
const handleAddRobot = (): void => {
|
const handleAddRobot = (): void => {
|
||||||
getGenerateRobot(genBase62Token(36));
|
getGenerateRobot(genBase62Token(36));
|
||||||
@ -147,7 +145,7 @@ const RobotProfile = ({
|
|||||||
tooltip={t('This is your trading avatar')}
|
tooltip={t('This is your trading avatar')}
|
||||||
tooltipPosition='top'
|
tooltipPosition='top'
|
||||||
/>
|
/>
|
||||||
{robot?.found && Boolean(slot?.lastShortAlias) ? (
|
{robot?.found && Boolean(slot?.lastOrder?.id) ? (
|
||||||
<Typography align='center' variant='h6'>
|
<Typography align='center' variant='h6'>
|
||||||
{t('Welcome back!')}
|
{t('Welcome back!')}
|
||||||
</Typography>
|
</Typography>
|
||||||
@ -156,38 +154,38 @@ const RobotProfile = ({
|
|||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{loadingCoordinators > 0 && !robot?.activeOrderId ? (
|
{loadingCoordinators > 0 && !slot?.activeOrder?.id ? (
|
||||||
<Grid>
|
<Grid>
|
||||||
<b>{t('Looking for orders!')}</b>
|
<b>{t('Looking for orders!')}</b>
|
||||||
<LinearProgress />
|
<LinearProgress />
|
||||||
</Grid>
|
</Grid>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{Boolean(robot?.activeOrderId) && Boolean(slot?.hashId) ? (
|
{slot?.activeOrder ? (
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentOrderId({ id: robot?.activeOrderId, shortAlias: slot?.activeShortAlias });
|
|
||||||
navigate(
|
navigate(
|
||||||
`/order/${String(slot?.activeShortAlias)}/${String(robot?.activeOrderId)}`,
|
`/order/${String(slot?.activeOrder?.shortAlias)}/${String(slot?.activeOrder?.id)}`,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('Active order #{{orderID}}', { orderID: robot?.activeOrderId })}
|
{t('Active order #{{orderID}}', { orderID: slot?.activeOrder?.id })}
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{Boolean(robot?.lastOrderId) && Boolean(slot?.hashId) ? (
|
{!slot?.activeOrder?.id && Boolean(slot?.lastOrder?.id) ? (
|
||||||
<Grid item container direction='column' alignItems='center'>
|
<Grid item container direction='column' alignItems='center'>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentOrderId({ id: robot?.lastOrderId, shortAlias: slot?.activeShortAlias });
|
navigate(
|
||||||
navigate(`/order/${String(slot?.lastShortAlias)}/${String(robot?.lastOrderId)}`);
|
`/order/${String(slot?.lastOrder?.shortAlias)}/${String(slot?.lastOrder?.id)}`,
|
||||||
|
);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('Last order #{{orderID}}', { orderID: robot?.lastOrderId })}
|
{t('Last order #{{orderID}}', { orderID: slot?.lastOrder?.id })}
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
@ -210,10 +208,7 @@ const RobotProfile = ({
|
|||||||
</Grid>
|
</Grid>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{!robot?.activeOrderId &&
|
{!slot?.activeOrder && !slot?.lastOrder && loadingCoordinators === 0 ? (
|
||||||
slot?.hashId &&
|
|
||||||
!robot?.lastOrderId &&
|
|
||||||
loadingCoordinators === 0 ? (
|
|
||||||
<Grid item>{t('No existing orders found')}</Grid>
|
<Grid item>{t('No existing orders found')}</Grid>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
@ -45,7 +45,6 @@ const RobotPage = (): JSX.Element => {
|
|||||||
if (token !== undefined && token !== null && page === 'robot') {
|
if (token !== undefined && token !== null && page === 'robot') {
|
||||||
setInputToken(token);
|
setInputToken(token);
|
||||||
if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) {
|
if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) {
|
||||||
getGenerateRobot(token);
|
|
||||||
setView('profile');
|
setView('profile');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,7 +69,7 @@ const RobotPage = (): JSX.Element => {
|
|||||||
pubKey: key.publicKeyArmored,
|
pubKey: key.publicKeyArmored,
|
||||||
encPrivKey: key.encryptedPrivateKeyArmored,
|
encPrivKey: key.encryptedPrivateKeyArmored,
|
||||||
});
|
});
|
||||||
void federation.fetchRobot(garage, token);
|
void garage.fetchRobot(federation, token);
|
||||||
garage.setCurrentSlot(token);
|
garage.setCurrentSlot(token);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -13,11 +13,10 @@ const Routes: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener('navigateToPage', (event) => {
|
window.addEventListener('navigateToPage', (event) => {
|
||||||
console.log('navigateToPage', JSON.stringify(event));
|
|
||||||
const orderId: string = event?.detail?.order_id;
|
const orderId: string = event?.detail?.order_id;
|
||||||
const coordinator: string = event?.detail?.coordinator;
|
const coordinator: string = event?.detail?.coordinator;
|
||||||
if (orderId && coordinator) {
|
if (orderId && coordinator) {
|
||||||
const slot = garage.getSlotByOrder(coordinator, orderId);
|
const slot = garage.getSlotByOrder(coordinator, parseInt(orderId, 10));
|
||||||
if (slot?.token) {
|
if (slot?.token) {
|
||||||
garage.setCurrentSlot(slot?.token);
|
garage.setCurrentSlot(slot?.token);
|
||||||
navigate(`/order/${coordinator}/${orderId}`);
|
navigate(`/order/${coordinator}/${orderId}`);
|
||||||
|
@ -5,10 +5,12 @@ import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
|||||||
import FederationTable from '../../components/FederationTable';
|
import FederationTable from '../../components/FederationTable';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
|
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
|
||||||
const SettingsPage = (): JSX.Element => {
|
const SettingsPage = (): JSX.Element => {
|
||||||
const { windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext);
|
const { windowSize, navbarHeight } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { federation, addNewCoordinator } = useContext<UseFederationStoreType>(FederationContext);
|
const { federation, addNewCoordinator } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const maxHeight = (windowSize.height - navbarHeight) * 0.85 - 3;
|
const maxHeight = (windowSize.height - navbarHeight) * 0.85 - 3;
|
||||||
const [newAlias, setNewAlias] = useState<string>('');
|
const [newAlias, setNewAlias] = useState<string>('');
|
||||||
const [newUrl, setNewUrl] = useState<string>('');
|
const [newUrl, setNewUrl] = useState<string>('');
|
||||||
@ -26,6 +28,7 @@ const SettingsPage = (): JSX.Element => {
|
|||||||
fullNewUrl = `http://${newUrl}`;
|
fullNewUrl = `http://${newUrl}`;
|
||||||
}
|
}
|
||||||
addNewCoordinator(newAlias, fullNewUrl);
|
addNewCoordinator(newAlias, fullNewUrl);
|
||||||
|
garage.syncCoordinator(federation, newAlias);
|
||||||
setNewAlias('');
|
setNewAlias('');
|
||||||
setNewUrl('');
|
setNewUrl('');
|
||||||
} else {
|
} else {
|
||||||
|
@ -88,8 +88,7 @@ const BookTable = ({
|
|||||||
onOrderClicked = () => null,
|
onOrderClicked = () => null,
|
||||||
}: BookTableProps): JSX.Element => {
|
}: BookTableProps): JSX.Element => {
|
||||||
const { fav, setOpen } = useContext<UseAppStoreType>(AppContext);
|
const { fav, setOpen } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { federation, coordinatorUpdatedAt } =
|
const { federation, federationUpdatedAt } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
useContext<UseFederationStoreType>(FederationContext);
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -123,7 +122,7 @@ const BookTable = ({
|
|||||||
pageSize: federation.loading && orders.length === 0 ? 0 : defaultPageSize,
|
pageSize: federation.loading && orders.length === 0 ? 0 : defaultPageSize,
|
||||||
page: paginationModel.page,
|
page: paginationModel.page,
|
||||||
});
|
});
|
||||||
}, [coordinatorUpdatedAt, orders, defaultPageSize]);
|
}, [federationUpdatedAt, orders, defaultPageSize]);
|
||||||
|
|
||||||
const localeText = useMemo(() => {
|
const localeText = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
|
@ -47,8 +47,7 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
onOrderClicked = () => null,
|
onOrderClicked = () => null,
|
||||||
}) => {
|
}) => {
|
||||||
const { fav } = useContext<UseAppStoreType>(AppContext);
|
const { fav } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { federation, coordinatorUpdatedAt, federationUpdatedAt } =
|
const { federation, federationUpdatedAt } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
useContext<UseFederationStoreType>(FederationContext);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [enrichedOrders, setEnrichedOrders] = useState<PublicOrder[]>([]);
|
const [enrichedOrders, setEnrichedOrders] = useState<PublicOrder[]>([]);
|
||||||
@ -81,7 +80,7 @@ const DepthChart: React.FC<DepthChartProps> = ({
|
|||||||
});
|
});
|
||||||
setEnrichedOrders(enriched);
|
setEnrichedOrders(enriched);
|
||||||
}
|
}
|
||||||
}, [coordinatorUpdatedAt, currencyCode]);
|
}, [federationUpdatedAt, currencyCode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (enrichedOrders.length > 0) {
|
if (enrichedOrders.length > 0) {
|
||||||
|
@ -34,14 +34,14 @@ interface Props {
|
|||||||
|
|
||||||
const ExchangeDialog = ({ open = false, onClose }: Props): JSX.Element => {
|
const ExchangeDialog = ({ open = false, onClose }: Props): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { federation, coordinatorUpdatedAt, federationUpdatedAt } = useContext(FederationContext);
|
const { federation, federationUpdatedAt } = useContext(FederationContext);
|
||||||
const [loadingProgress, setLoadingProgress] = useState<number>(0);
|
const [loadingProgress, setLoadingProgress] = useState<number>(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadedCoordinators =
|
const loadedCoordinators =
|
||||||
federation.exchange.enabledCoordinators - federation.exchange.loadingCoordinators;
|
federation.exchange.enabledCoordinators - federation.exchange.loadingCoordinators;
|
||||||
setLoadingProgress((loadedCoordinators / federation.exchange.enabledCoordinators) * 100);
|
setLoadingProgress((loadedCoordinators / federation.exchange.enabledCoordinators) * 100);
|
||||||
}, [open, coordinatorUpdatedAt, federationUpdatedAt]);
|
}, [open, federationUpdatedAt]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose}>
|
<Dialog open={open} onClose={onClose}>
|
||||||
|
@ -27,7 +27,7 @@ interface Props {
|
|||||||
|
|
||||||
const ProfileDialog = ({ open = false, onClose }: Props): JSX.Element => {
|
const ProfileDialog = ({ open = false, onClose }: Props): JSX.Element => {
|
||||||
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
const { garage, robotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage, slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const slot = garage.getSlot();
|
const slot = garage.getSlot();
|
||||||
@ -42,7 +42,7 @@ const ProfileDialog = ({ open = false, onClose }: Props): JSX.Element => {
|
|||||||
setLoadingCoordinators(
|
setLoadingCoordinators(
|
||||||
Object.values(slot?.robots ?? {}).filter((robot) => robot.loading).length,
|
Object.values(slot?.robots ?? {}).filter((robot) => robot.loading).length,
|
||||||
);
|
);
|
||||||
}, [robotUpdatedAt]);
|
}, [slotUpdatedAt]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
|
@ -21,7 +21,7 @@ const FederationTable = ({
|
|||||||
fillContainer = false,
|
fillContainer = false,
|
||||||
}: FederationTableProps): JSX.Element => {
|
}: FederationTableProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { federation, sortedCoordinators, coordinatorUpdatedAt, federationUpdatedAt } =
|
const { federation, sortedCoordinators, federationUpdatedAt } =
|
||||||
useContext<UseFederationStoreType>(FederationContext);
|
useContext<UseFederationStoreType>(FederationContext);
|
||||||
const { setOpen, settings } = useContext<UseAppStoreType>(AppContext);
|
const { setOpen, settings } = useContext<UseAppStoreType>(AppContext);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -43,7 +43,7 @@ const FederationTable = ({
|
|||||||
if (useDefaultPageSize) {
|
if (useDefaultPageSize) {
|
||||||
setPageSize(defaultPageSize);
|
setPageSize(defaultPageSize);
|
||||||
}
|
}
|
||||||
}, [coordinatorUpdatedAt, federationUpdatedAt]);
|
}, [federationUpdatedAt]);
|
||||||
|
|
||||||
const localeText = {
|
const localeText = {
|
||||||
MuiTablePagination: { labelRowsPerPage: t('Coordinators per page:') },
|
MuiTablePagination: { labelRowsPerPage: t('Coordinators per page:') },
|
||||||
@ -111,7 +111,7 @@ const FederationTable = ({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[coordinatorUpdatedAt],
|
[federationUpdatedAt],
|
||||||
);
|
);
|
||||||
|
|
||||||
const upObj = useCallback(
|
const upObj = useCallback(
|
||||||
@ -140,7 +140,7 @@ const FederationTable = ({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[coordinatorUpdatedAt],
|
[federationUpdatedAt],
|
||||||
);
|
);
|
||||||
|
|
||||||
const columnSpecs = {
|
const columnSpecs = {
|
||||||
|
@ -24,12 +24,11 @@ import {
|
|||||||
IconButton,
|
IconButton,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
|
|
||||||
import { type LimitList, defaultMaker } from '../../models';
|
import { type LimitList, defaultMaker, type Order } from '../../models';
|
||||||
|
|
||||||
import { LocalizationProvider, MobileTimePicker } from '@mui/x-date-pickers';
|
import { LocalizationProvider, MobileTimePicker } from '@mui/x-date-pickers';
|
||||||
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
|
||||||
import { ConfirmationDialog, F2fMapDialog } from '../Dialogs';
|
import { ConfirmationDialog, F2fMapDialog } from '../Dialogs';
|
||||||
import { apiClient } from '../../services/api';
|
|
||||||
|
|
||||||
import { FlagWithProps } from '../Icons';
|
import { FlagWithProps } from '../Icons';
|
||||||
import AutocompletePayments from './AutocompletePayments';
|
import AutocompletePayments from './AutocompletePayments';
|
||||||
@ -44,6 +43,7 @@ import { AppContext, type UseAppStoreType } from '../../contexts/AppContext';
|
|||||||
import SelectCoordinator from './SelectCoordinator';
|
import SelectCoordinator from './SelectCoordinator';
|
||||||
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
import { FederationContext, type UseFederationStoreType } from '../../contexts/FederationContext';
|
||||||
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
interface MakerFormProps {
|
interface MakerFormProps {
|
||||||
disableRequest?: boolean;
|
disableRequest?: boolean;
|
||||||
@ -52,7 +52,6 @@ interface MakerFormProps {
|
|||||||
onSubmit?: () => void;
|
onSubmit?: () => void;
|
||||||
onReset?: () => void;
|
onReset?: () => void;
|
||||||
submitButtonLabel?: string;
|
submitButtonLabel?: string;
|
||||||
onOrderCreated?: (shortAlias: string, id: number) => void;
|
|
||||||
onClickGenerateRobot?: () => void;
|
onClickGenerateRobot?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,16 +62,15 @@ const MakerForm = ({
|
|||||||
onSubmit = () => {},
|
onSubmit = () => {},
|
||||||
onReset = () => {},
|
onReset = () => {},
|
||||||
submitButtonLabel = 'Create Order',
|
submitButtonLabel = 'Create Order',
|
||||||
onOrderCreated = () => null,
|
|
||||||
onClickGenerateRobot = () => null,
|
onClickGenerateRobot = () => null,
|
||||||
}: MakerFormProps): JSX.Element => {
|
}: MakerFormProps): JSX.Element => {
|
||||||
const { fav, setFav, settings, hostUrl, origin } = useContext<UseAppStoreType>(AppContext);
|
const { fav, setFav } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { federation, coordinatorUpdatedAt, federationUpdatedAt } =
|
const { federation, federationUpdatedAt } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
useContext<UseFederationStoreType>(FederationContext);
|
|
||||||
const { maker, setMaker, garage } = useContext<UseGarageStoreType>(GarageContext);
|
const { maker, setMaker, garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [badRequest, setBadRequest] = useState<string | null>(null);
|
const [badRequest, setBadRequest] = useState<string | null>(null);
|
||||||
const [amountLimits, setAmountLimits] = useState<number[]>([1, 1000]);
|
const [amountLimits, setAmountLimits] = useState<number[]>([1, 1000]);
|
||||||
@ -92,11 +90,11 @@ const MakerForm = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrencyCode(currencyDict[fav.currency === 0 ? 1 : fav.currency]);
|
setCurrencyCode(currencyDict[fav.currency === 0 ? 1 : fav.currency]);
|
||||||
}, [coordinatorUpdatedAt]);
|
}, [federationUpdatedAt]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateCoordinatorInfo();
|
updateCoordinatorInfo();
|
||||||
}, [maker.coordinator, coordinatorUpdatedAt]);
|
}, [maker.coordinator, federationUpdatedAt]);
|
||||||
|
|
||||||
const updateCoordinatorInfo = (): void => {
|
const updateCoordinatorInfo = (): void => {
|
||||||
if (maker.coordinator != null) {
|
if (maker.coordinator != null) {
|
||||||
@ -297,21 +295,14 @@ const MakerForm = ({
|
|||||||
const handleCreateOrder = function (): void {
|
const handleCreateOrder = function (): void {
|
||||||
const slot = garage.getSlot();
|
const slot = garage.getSlot();
|
||||||
|
|
||||||
if (slot?.activeShortAlias) {
|
if (slot?.activeOrder?.id) {
|
||||||
setBadRequest(t('You are already maker of an active order'));
|
setBadRequest(t('You are already maker of an active order'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { url, basePath } =
|
if (!disableRequest && maker.coordinator && slot) {
|
||||||
federation
|
|
||||||
.getCoordinator(maker.coordinator)
|
|
||||||
?.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl) ?? {};
|
|
||||||
|
|
||||||
const auth = slot?.getRobot()?.getAuthHeaders();
|
|
||||||
|
|
||||||
if (!disableRequest && maker.coordinator != null && auth !== null) {
|
|
||||||
setSubmittingRequest(true);
|
setSubmittingRequest(true);
|
||||||
const body = {
|
const orderAttributes = {
|
||||||
type: fav.type === 0 ? 1 : 0,
|
type: fav.type === 0 ? 1 : 0,
|
||||||
currency: fav.currency === 0 ? 1 : fav.currency,
|
currency: fav.currency === 0 ? 1 : fav.currency,
|
||||||
amount: makerHasAmountRange ? null : maker.amount,
|
amount: makerHasAmountRange ? null : maker.amount,
|
||||||
@ -328,15 +319,16 @@ const MakerForm = ({
|
|||||||
bond_size: maker.bondSize,
|
bond_size: maker.bondSize,
|
||||||
latitude: maker.latitude,
|
latitude: maker.latitude,
|
||||||
longitude: maker.longitude,
|
longitude: maker.longitude,
|
||||||
|
shortAlias: maker.coordinator,
|
||||||
};
|
};
|
||||||
|
|
||||||
apiClient
|
void slot
|
||||||
.post(url, `${basePath}/api/make/`, body, auth)
|
.makeOrder(federation, orderAttributes)
|
||||||
.then((data: any) => {
|
.then((order: Order) => {
|
||||||
setBadRequest(data.bad_request);
|
if (order.id) {
|
||||||
if (data.id !== undefined) {
|
navigate(`/order/${order.shortAlias}/${order.id}`);
|
||||||
onOrderCreated(maker.coordinator, data.id);
|
} else if (order?.bad_request) {
|
||||||
garage.updateOrder(data);
|
setBadRequest(order?.bad_request);
|
||||||
}
|
}
|
||||||
setSubmittingRequest(false);
|
setSubmittingRequest(false);
|
||||||
})
|
})
|
||||||
@ -513,7 +505,7 @@ const MakerForm = ({
|
|||||||
federation.getCoordinator(maker.coordinator)?.info === undefined ||
|
federation.getCoordinator(maker.coordinator)?.info === undefined ||
|
||||||
federation.getCoordinator(maker.coordinator)?.limits === undefined
|
federation.getCoordinator(maker.coordinator)?.limits === undefined
|
||||||
);
|
);
|
||||||
}, [maker, amountLimits, coordinatorUpdatedAt, fav.type, makerHasAmountRange]);
|
}, [maker, amountLimits, federationUpdatedAt, fav.type, makerHasAmountRange]);
|
||||||
|
|
||||||
const clearMaker = function (): void {
|
const clearMaker = function (): void {
|
||||||
setFav({ ...fav, type: null });
|
setFav({ ...fav, type: null });
|
||||||
|
@ -69,7 +69,7 @@ const Notifications = ({
|
|||||||
}: NotificationsProps): JSX.Element => {
|
}: NotificationsProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { garage, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage, slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
|
||||||
const [message, setMessage] = useState<NotificationMessage>(emptyNotificationMessage);
|
const [message, setMessage] = useState<NotificationMessage>(emptyNotificationMessage);
|
||||||
const [inFocus, setInFocus] = useState<boolean>(true);
|
const [inFocus, setInFocus] = useState<boolean>(true);
|
||||||
@ -85,7 +85,7 @@ const Notifications = ({
|
|||||||
const basePageTitle = t('RoboSats - Simple and Private Bitcoin Exchange');
|
const basePageTitle = t('RoboSats - Simple and Private Bitcoin Exchange');
|
||||||
|
|
||||||
const moveToOrderPage = function (): void {
|
const moveToOrderPage = function (): void {
|
||||||
navigate(`/order/${String(garage.getSlot()?.order?.id)}`);
|
navigate(`/order/${String(garage.getSlot()?.activeOrder?.id)}`);
|
||||||
setShow(false);
|
setShow(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -106,7 +106,9 @@ const Notifications = ({
|
|||||||
|
|
||||||
const Messages: MessagesProps = {
|
const Messages: MessagesProps = {
|
||||||
bondLocked: {
|
bondLocked: {
|
||||||
title: t(`${garage.getSlot()?.order?.is_maker === true ? 'Maker' : 'Taker'} bond locked`),
|
title: t(
|
||||||
|
`${garage.getSlot()?.activeOrder?.is_maker === true ? 'Maker' : 'Taker'} bond locked`,
|
||||||
|
),
|
||||||
severity: 'info',
|
severity: 'info',
|
||||||
onClick: moveToOrderPage,
|
onClick: moveToOrderPage,
|
||||||
sound: audio.ding,
|
sound: audio.ding,
|
||||||
@ -228,7 +230,7 @@ const Notifications = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleStatusChange = function (oldStatus: number | undefined, status: number): void {
|
const handleStatusChange = function (oldStatus: number | undefined, status: number): void {
|
||||||
const order = garage.getSlot()?.order;
|
const order = garage.getSlot()?.activeOrder;
|
||||||
|
|
||||||
if (order === undefined || order === null) return;
|
if (order === undefined || order === null) return;
|
||||||
|
|
||||||
@ -293,7 +295,7 @@ const Notifications = ({
|
|||||||
|
|
||||||
// Notify on order status change
|
// Notify on order status change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const order = garage.getSlot()?.order;
|
const order = garage.getSlot()?.activeOrder;
|
||||||
if (order !== undefined && order !== null) {
|
if (order !== undefined && order !== null) {
|
||||||
if (order.status !== oldOrderStatus) {
|
if (order.status !== oldOrderStatus) {
|
||||||
handleStatusChange(oldOrderStatus, order.status);
|
handleStatusChange(oldOrderStatus, order.status);
|
||||||
@ -305,7 +307,7 @@ const Notifications = ({
|
|||||||
setOldChatIndex(order.chat_last_index);
|
setOldChatIndex(order.chat_last_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [orderUpdatedAt]);
|
}, [slotUpdatedAt]);
|
||||||
|
|
||||||
// Notify on rewards change
|
// Notify on rewards change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -19,15 +19,14 @@ import {
|
|||||||
import Countdown from 'react-countdown';
|
import Countdown from 'react-countdown';
|
||||||
|
|
||||||
import currencies from '../../../static/assets/currencies.json';
|
import currencies from '../../../static/assets/currencies.json';
|
||||||
import { apiClient } from '../../services/api';
|
|
||||||
|
|
||||||
import { type Order, type Info } from '../../models';
|
import { type Order, type Info } from '../../models';
|
||||||
import { ConfirmationDialog } from '../Dialogs';
|
import { ConfirmationDialog } from '../Dialogs';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
import { computeSats } from '../../utils';
|
import { computeSats } from '../../utils';
|
||||||
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
import { GarageContext, type UseGarageStoreType } from '../../contexts/GarageContext';
|
||||||
import { type UseAppStoreType, AppContext } from '../../contexts/AppContext';
|
|
||||||
import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
|
import { type UseFederationStoreType, FederationContext } from '../../contexts/FederationContext';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
interface TakeButtonProps {
|
interface TakeButtonProps {
|
||||||
currentOrder: Order;
|
currentOrder: Order;
|
||||||
@ -48,9 +47,9 @@ const TakeButton = ({
|
|||||||
}: TakeButtonProps): JSX.Element => {
|
}: TakeButtonProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { settings, origin, hostUrl } = useContext<UseAppStoreType>(AppContext);
|
const navigate = useNavigate();
|
||||||
const { garage, orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage, slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { federation, setCurrentOrderId } = useContext<UseFederationStoreType>(FederationContext);
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
|
||||||
const [takeAmount, setTakeAmount] = useState<string>('');
|
const [takeAmount, setTakeAmount] = useState<string>('');
|
||||||
const [badRequest, setBadRequest] = useState<string>('');
|
const [badRequest, setBadRequest] = useState<string>('');
|
||||||
@ -79,7 +78,7 @@ const TakeButton = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSatoshis(satoshisNow() ?? '');
|
setSatoshis(satoshisNow() ?? '');
|
||||||
}, [orderUpdatedAt, takeAmount, info]);
|
}, [slotUpdatedAt, takeAmount, info]);
|
||||||
|
|
||||||
const currencyCode: string =
|
const currencyCode: string =
|
||||||
currentOrder?.currency === 1000 ? 'Sats' : currencies[`${Number(currentOrder?.currency)}`];
|
currentOrder?.currency === 1000 ? 'Sats' : currencies[`${Number(currentOrder?.currency)}`];
|
||||||
@ -165,7 +164,7 @@ const TakeButton = ({
|
|||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}, [orderUpdatedAt, takeAmount]);
|
}, [slotUpdatedAt, takeAmount]);
|
||||||
|
|
||||||
const onTakeOrderClicked = function (): void {
|
const onTakeOrderClicked = function (): void {
|
||||||
if (currentOrder?.maker_status === 'Inactive') {
|
if (currentOrder?.maker_status === 'Inactive') {
|
||||||
@ -184,7 +183,7 @@ const TakeButton = ({
|
|||||||
takeAmount === '' ||
|
takeAmount === '' ||
|
||||||
takeAmount == null
|
takeAmount == null
|
||||||
);
|
);
|
||||||
}, [takeAmount, orderUpdatedAt]);
|
}, [takeAmount, slotUpdatedAt]);
|
||||||
|
|
||||||
const takeOrderButton = function (): JSX.Element {
|
const takeOrderButton = function (): JSX.Element {
|
||||||
if (currentOrder?.has_range) {
|
if (currentOrder?.has_range) {
|
||||||
@ -314,31 +313,20 @@ const TakeButton = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const takeOrder = function (): void {
|
const takeOrder = function (): void {
|
||||||
const robot = garage.getSlot()?.getRobot() ?? null;
|
const slot = garage.getSlot();
|
||||||
|
|
||||||
if (currentOrder === null || robot === null) return;
|
if (currentOrder === null || slot === null) return;
|
||||||
|
|
||||||
setLoadingTake(true);
|
setLoadingTake(true);
|
||||||
const { url, basePath } = federation
|
|
||||||
.getCoordinator(currentOrder.shortAlias)
|
slot
|
||||||
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
.takeOrder(federation, currentOrder, takeAmount)
|
||||||
setCurrentOrderId({ id: null, shortAlias: null });
|
.then((order) => {
|
||||||
apiClient
|
if (order?.bad_request !== undefined) {
|
||||||
.post(
|
setBadRequest(order.bad_request);
|
||||||
url + basePath,
|
|
||||||
`/api/order/?order_id=${String(currentOrder?.id)}`,
|
|
||||||
{
|
|
||||||
action: 'take',
|
|
||||||
amount: currentOrder?.currency === 1000 ? takeAmount / 100000000 : takeAmount,
|
|
||||||
},
|
|
||||||
{ tokenSHA256: robot?.tokenSHA256 },
|
|
||||||
)
|
|
||||||
.then((data) => {
|
|
||||||
if (data?.bad_request !== undefined) {
|
|
||||||
setBadRequest(data.bad_request);
|
|
||||||
} else {
|
} else {
|
||||||
setCurrentOrderId({ id: currentOrder?.id, shortAlias: currentOrder?.shortAlias });
|
|
||||||
setBadRequest('');
|
setBadRequest('');
|
||||||
|
navigate(`/order/${order.shortAlias}/${order.id}`);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
|
@ -174,8 +174,8 @@ const OrderDetails = ({
|
|||||||
|
|
||||||
const isBuyer = (order.type === 0 && order.is_maker) || (order.type === 1 && !order.is_maker);
|
const isBuyer = (order.type === 0 && order.is_maker) || (order.type === 1 && !order.is_maker);
|
||||||
const tradeFee = order.is_maker
|
const tradeFee = order.is_maker
|
||||||
? coordinator.info?.maker_fee ?? 0
|
? (coordinator.info?.maker_fee ?? 0)
|
||||||
: coordinator.info?.taker_fee ?? 0;
|
: (coordinator.info?.taker_fee ?? 0);
|
||||||
const defaultRoutingBudget = 0.001;
|
const defaultRoutingBudget = 0.001;
|
||||||
const btc_now = order.satoshis_now / 100000000;
|
const btc_now = order.satoshis_now / 100000000;
|
||||||
const rate = Number(order.max_amount ?? order.amount) / btc_now;
|
const rate = Number(order.max_amount ?? order.amount) / btc_now;
|
||||||
|
@ -41,7 +41,7 @@ interface Props {
|
|||||||
|
|
||||||
const RobotInfo: React.FC<Props> = ({ coordinator, onClose, disabled }: Props) => {
|
const RobotInfo: React.FC<Props> = ({ coordinator, onClose, disabled }: Props) => {
|
||||||
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { setCurrentOrderId } = useContext<UseFederationStoreType>(FederationContext);
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -55,6 +55,8 @@ const RobotInfo: React.FC<Props> = ({ coordinator, onClose, disabled }: Props) =
|
|||||||
const [weblnEnabled, setWeblnEnabled] = useState<boolean>(false);
|
const [weblnEnabled, setWeblnEnabled] = useState<boolean>(false);
|
||||||
const [openEnableTelegram, setOpenEnableTelegram] = useState<boolean>(false);
|
const [openEnableTelegram, setOpenEnableTelegram] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const robot = garage.getSlot()?.getRobot(coordinator.shortAlias);
|
||||||
|
|
||||||
const handleWebln = async (): Promise<void> => {
|
const handleWebln = async (): Promise<void> => {
|
||||||
void getWebln()
|
void getWebln()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -72,7 +74,6 @@ const RobotInfo: React.FC<Props> = ({ coordinator, onClose, disabled }: Props) =
|
|||||||
|
|
||||||
const handleWeblnInvoiceClicked = async (e: MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
const handleWeblnInvoiceClicked = async (e: MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const robot = garage.getSlot()?.getRobot(coordinator.shortAlias);
|
|
||||||
if (robot != null && robot.earnedRewards > 0) {
|
if (robot != null && robot.earnedRewards > 0) {
|
||||||
const webln = await getWebln();
|
const webln = await getWebln();
|
||||||
const invoice = webln.makeInvoice(robot.earnedRewards).then(() => {
|
const invoice = webln.makeInvoice(robot.earnedRewards).then(() => {
|
||||||
@ -87,14 +88,11 @@ const RobotInfo: React.FC<Props> = ({ coordinator, onClose, disabled }: Props) =
|
|||||||
setBadInvoice('');
|
setBadInvoice('');
|
||||||
setShowRewardsSpinner(true);
|
setShowRewardsSpinner(true);
|
||||||
|
|
||||||
const slot = garage.getSlot();
|
if (robot?.token && robot.encPrivKey != null) {
|
||||||
const robot = slot?.getRobot(coordinator.shortAlias);
|
void signCleartextMessage(rewardInvoice, robot.encPrivKey, robot?.token).then(
|
||||||
|
|
||||||
if (robot != null && slot?.token != null && robot.encPrivKey != null) {
|
|
||||||
void signCleartextMessage(rewardInvoice, robot.encPrivKey, slot?.token).then(
|
|
||||||
(signedInvoice) => {
|
(signedInvoice) => {
|
||||||
console.log('Signed message:', signedInvoice);
|
console.log('Signed message:', signedInvoice);
|
||||||
void coordinator.fetchReward(signedInvoice, garage, slot?.token).then((data) => {
|
void robot.fetchReward(federation, signedInvoice).then((data) => {
|
||||||
setBadInvoice(data.bad_invoice ?? '');
|
setBadInvoice(data.bad_invoice ?? '');
|
||||||
setShowRewardsSpinner(false);
|
setShowRewardsSpinner(false);
|
||||||
setWithdrawn(data.successful_withdrawal);
|
setWithdrawn(data.successful_withdrawal);
|
||||||
@ -106,16 +104,10 @@ const RobotInfo: React.FC<Props> = ({ coordinator, onClose, disabled }: Props) =
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
const setStealthInvoice = (wantsStealth: boolean): void => {
|
const setStealthInvoice = (): void => {
|
||||||
const slot = garage.getSlot();
|
if (robot) void robot.fetchStealth(federation, !robot?.stealthInvoices);
|
||||||
if (slot?.token != null) {
|
|
||||||
void coordinator.fetchStealth(wantsStealth, garage, slot?.token);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const slot = garage.getSlot();
|
|
||||||
const robot = slot?.getRobot(coordinator.shortAlias);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion disabled={disabled}>
|
<Accordion disabled={disabled}>
|
||||||
<AccordionSummary expandIcon={<ExpandMore />}>
|
<AccordionSummary expandIcon={<ExpandMore />}>
|
||||||
@ -123,28 +115,21 @@ const RobotInfo: React.FC<Props> = ({ coordinator, onClose, disabled }: Props) =
|
|||||||
{(robot?.earnedRewards ?? 0) > 0 && (
|
{(robot?.earnedRewards ?? 0) > 0 && (
|
||||||
<Typography color='success'> {t('Claim Sats!')} </Typography>
|
<Typography color='success'> {t('Claim Sats!')} </Typography>
|
||||||
)}
|
)}
|
||||||
{slot?.activeShortAlias === coordinator.shortAlias && (
|
{robot?.activeOrderId ? (
|
||||||
<Typography color='success'>
|
<Typography color='success'>
|
||||||
<b>{t('Active order!')}</b>
|
<b>{t('Active order!')}</b>
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
) : (
|
||||||
{slot?.lastShortAlias === coordinator.shortAlias && (
|
robot?.lastOrderId && <Typography color='warning'> {t('finished order')}</Typography>
|
||||||
<Typography color='warning'> {t('finished order')}</Typography>
|
|
||||||
)}
|
)}
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<List dense disablePadding={true}>
|
<List dense disablePadding={true}>
|
||||||
{slot?.activeShortAlias === coordinator.shortAlias ? (
|
{robot?.activeOrderId ? (
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentOrderId({
|
|
||||||
id: slot?.activeShortAlias,
|
|
||||||
shortAlias: slot?.getRobot(slot?.activeShortAlias ?? '')?.activeOrderId,
|
|
||||||
});
|
|
||||||
navigate(
|
navigate(
|
||||||
`/order/${String(slot?.activeShortAlias)}/${String(
|
`/order/${String(coordinator.shortAlias)}/${String(robot?.activeOrderId)}`,
|
||||||
slot?.getRobot(slot?.activeShortAlias ?? '')?.activeOrderId,
|
|
||||||
)}`,
|
|
||||||
);
|
);
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
@ -156,23 +141,15 @@ const RobotInfo: React.FC<Props> = ({ coordinator, onClose, disabled }: Props) =
|
|||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('One active order #{{orderID}}', {
|
primary={t('One active order #{{orderID}}', {
|
||||||
orderID: slot?.getRobot(slot?.activeShortAlias ?? '')?.activeOrderId,
|
orderID: String(robot?.activeOrderId),
|
||||||
})}
|
})}
|
||||||
secondary={t('Your current order')}
|
secondary={t('Your current order')}
|
||||||
/>
|
/>
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
) : (robot?.lastOrderId ?? 0) > 0 && slot?.lastShortAlias === coordinator.shortAlias ? (
|
) : robot?.lastOrderId ? (
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setCurrentOrderId({
|
navigate(`/order/${String(coordinator.shortAlias)}/${String(robot?.lastOrderId)}`);
|
||||||
id: slot?.activeShortAlias,
|
|
||||||
shortAlias: slot?.getRobot(slot?.activeShortAlias ?? '')?.lastOrderId,
|
|
||||||
});
|
|
||||||
navigate(
|
|
||||||
`/order/${String(slot?.lastShortAlias)}/${String(
|
|
||||||
slot?.getRobot(slot?.lastShortAlias ?? '')?.lastOrderId,
|
|
||||||
)}`,
|
|
||||||
);
|
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -181,7 +158,7 @@ const RobotInfo: React.FC<Props> = ({ coordinator, onClose, disabled }: Props) =
|
|||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={t('Your last order #{{orderID}}', {
|
primary={t('Your last order #{{orderID}}', {
|
||||||
orderID: slot?.getRobot(slot?.lastShortAlias ?? '')?.lastOrderId,
|
orderID: robot?.lastOrderId,
|
||||||
})}
|
})}
|
||||||
secondary={t('Inactive order')}
|
secondary={t('Inactive order')}
|
||||||
/>
|
/>
|
||||||
@ -253,7 +230,7 @@ const RobotInfo: React.FC<Props> = ({ coordinator, onClose, disabled }: Props) =
|
|||||||
<Switch
|
<Switch
|
||||||
checked={robot?.stealthInvoices}
|
checked={robot?.stealthInvoices}
|
||||||
onChange={() => {
|
onChange={() => {
|
||||||
setStealthInvoice(!robot?.stealthInvoices);
|
setStealthInvoice();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,6 @@ interface Props {
|
|||||||
makerHashId: string;
|
makerHashId: string;
|
||||||
messages: EncryptedChatMessage[];
|
messages: EncryptedChatMessage[];
|
||||||
setMessages: (messages: EncryptedChatMessage[]) => void;
|
setMessages: (messages: EncryptedChatMessage[]) => void;
|
||||||
baseUrl: string;
|
|
||||||
turtleMode: boolean;
|
turtleMode: boolean;
|
||||||
setTurtleMode: (state: boolean) => void;
|
setTurtleMode: (state: boolean) => void;
|
||||||
}
|
}
|
||||||
@ -50,14 +49,13 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
takerHashId,
|
takerHashId,
|
||||||
messages,
|
messages,
|
||||||
setMessages,
|
setMessages,
|
||||||
baseUrl,
|
|
||||||
turtleMode,
|
turtleMode,
|
||||||
setTurtleMode,
|
setTurtleMode,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { origin, hostUrl, settings } = useContext<UseAppStoreType>(AppContext);
|
const { origin, hostUrl, settings } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { garage, robotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage, slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
|
|
||||||
const [audio] = useState(() => new Audio(`${audioPath}/chat-open.mp3`));
|
const [audio] = useState(() => new Audio(`${audioPath}/chat-open.mp3`));
|
||||||
@ -78,7 +76,7 @@ const EncryptedSocketChat: React.FC<Props> = ({
|
|||||||
if (!connected && Boolean(garage.getSlot()?.hashId)) {
|
if (!connected && Boolean(garage.getSlot()?.hashId)) {
|
||||||
connectWebsocket();
|
connectWebsocket();
|
||||||
}
|
}
|
||||||
}, [connected, robotUpdatedAt]);
|
}, [connected, slotUpdatedAt]);
|
||||||
|
|
||||||
// Make sure to not keep reconnecting once status is not Chat
|
// Make sure to not keep reconnecting once status is not Chat
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -30,7 +30,6 @@ interface Props {
|
|||||||
chatOffset: number;
|
chatOffset: number;
|
||||||
messages: EncryptedChatMessage[];
|
messages: EncryptedChatMessage[];
|
||||||
setMessages: (messages: EncryptedChatMessage[]) => void;
|
setMessages: (messages: EncryptedChatMessage[]) => void;
|
||||||
baseUrl: string;
|
|
||||||
turtleMode: boolean;
|
turtleMode: boolean;
|
||||||
setTurtleMode: (state: boolean) => void;
|
setTurtleMode: (state: boolean) => void;
|
||||||
}
|
}
|
||||||
@ -49,7 +48,6 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
chatOffset,
|
chatOffset,
|
||||||
messages,
|
messages,
|
||||||
setMessages,
|
setMessages,
|
||||||
baseUrl,
|
|
||||||
setTurtleMode,
|
setTurtleMode,
|
||||||
turtleMode,
|
turtleMode,
|
||||||
}: Props): JSX.Element => {
|
}: Props): JSX.Element => {
|
||||||
@ -91,7 +89,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
}, [chatOffset]);
|
}, [chatOffset]);
|
||||||
|
|
||||||
const loadMessages: () => void = () => {
|
const loadMessages: () => void = () => {
|
||||||
const shortAlias = garage.getSlot()?.activeShortAlias;
|
const shortAlias = garage.getSlot()?.activeOrder?.shortAlias;
|
||||||
|
|
||||||
if (!shortAlias) return;
|
if (!shortAlias) return;
|
||||||
|
|
||||||
@ -100,7 +98,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
apiClient
|
apiClient
|
||||||
.get(url + basePath, `/api/chat/?order_id=${order.id}&offset=${lastIndex}`, {
|
.get(url + basePath, `/api/chat/?order_id=${order.id}&offset=${lastIndex}`, {
|
||||||
tokenSHA256: garage.getSlot()?.getRobot()?.tokenSHA256 ?? '',
|
tokenSHA256: garage.getSlot()?.tokenSHA256 ?? '',
|
||||||
})
|
})
|
||||||
.then((results: any) => {
|
.then((results: any) => {
|
||||||
if (results != null) {
|
if (results != null) {
|
||||||
@ -198,7 +196,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
// If input string contains '#' send unencrypted and unlogged message
|
// If input string contains '#' send unencrypted and unlogged message
|
||||||
else if (value.substring(0, 1) === '#') {
|
else if (value.substring(0, 1) === '#') {
|
||||||
const { url, basePath } = federation
|
const { url, basePath } = federation
|
||||||
.getCoordinator(garage.getSlot()?.activeShortAlias ?? '')
|
.getCoordinator(garage.getSlot()?.activeOrder?.shortAlias)
|
||||||
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
apiClient
|
apiClient
|
||||||
.post(
|
.post(
|
||||||
@ -209,7 +207,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
order_id: order.id,
|
order_id: order.id,
|
||||||
offset: lastIndex,
|
offset: lastIndex,
|
||||||
},
|
},
|
||||||
{ tokenSHA256: robot?.tokenSHA256 ?? '' },
|
{ tokenSHA256: slot?.getRobot()?.tokenSHA256 ?? '' },
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
@ -231,7 +229,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
encryptMessage(value, robot?.pubKey, peerPubKey ?? '', robot?.encPrivKey, slot?.token)
|
encryptMessage(value, robot?.pubKey, peerPubKey ?? '', robot?.encPrivKey, slot?.token)
|
||||||
.then((encryptedMessage) => {
|
.then((encryptedMessage) => {
|
||||||
const { url, basePath } = federation
|
const { url, basePath } = federation
|
||||||
.getCoordinator(garage.getSlot()?.activeShortAlias ?? '')
|
.getCoordinator(garage.getSlot()?.activeOrder?.shortAlias ?? '')
|
||||||
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
||||||
apiClient
|
apiClient
|
||||||
.post(
|
.post(
|
||||||
@ -242,7 +240,7 @@ const EncryptedTurtleChat: React.FC<Props> = ({
|
|||||||
order_id: order.id,
|
order_id: order.id,
|
||||||
offset: lastIndex,
|
offset: lastIndex,
|
||||||
},
|
},
|
||||||
{ tokenSHA256: robot?.tokenSHA256 },
|
{ tokenSHA256: slot?.getRobot()?.tokenSHA256 },
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
|
@ -7,7 +7,6 @@ interface Props {
|
|||||||
order: Order;
|
order: Order;
|
||||||
status: number;
|
status: number;
|
||||||
chatOffset: number;
|
chatOffset: number;
|
||||||
baseUrl: string;
|
|
||||||
messages: EncryptedChatMessage[];
|
messages: EncryptedChatMessage[];
|
||||||
setMessages: (state: EncryptedChatMessage[]) => void;
|
setMessages: (state: EncryptedChatMessage[]) => void;
|
||||||
}
|
}
|
||||||
@ -32,7 +31,6 @@ export interface ServerMessage {
|
|||||||
const EncryptedChat: React.FC<Props> = ({
|
const EncryptedChat: React.FC<Props> = ({
|
||||||
order,
|
order,
|
||||||
chatOffset,
|
chatOffset,
|
||||||
baseUrl,
|
|
||||||
setMessages,
|
setMessages,
|
||||||
messages,
|
messages,
|
||||||
status,
|
status,
|
||||||
@ -49,7 +47,6 @@ const EncryptedChat: React.FC<Props> = ({
|
|||||||
makerHashId={order.maker_hash_id}
|
makerHashId={order.maker_hash_id}
|
||||||
userNick={order.ur_nick}
|
userNick={order.ur_nick}
|
||||||
chatOffset={chatOffset}
|
chatOffset={chatOffset}
|
||||||
baseUrl={baseUrl}
|
|
||||||
turtleMode={turtleMode}
|
turtleMode={turtleMode}
|
||||||
setTurtleMode={setTurtleMode}
|
setTurtleMode={setTurtleMode}
|
||||||
/>
|
/>
|
||||||
@ -63,7 +60,6 @@ const EncryptedChat: React.FC<Props> = ({
|
|||||||
takerHashId={order.taker_hash_id}
|
takerHashId={order.taker_hash_id}
|
||||||
makerHashId={order.maker_hash_id}
|
makerHashId={order.maker_hash_id}
|
||||||
userNick={order.ur_nick}
|
userNick={order.ur_nick}
|
||||||
baseUrl={baseUrl}
|
|
||||||
turtleMode={turtleMode}
|
turtleMode={turtleMode}
|
||||||
setTurtleMode={setTurtleMode}
|
setTurtleMode={setTurtleMode}
|
||||||
/>
|
/>
|
||||||
|
@ -20,7 +20,6 @@ interface ChatPromptProps {
|
|||||||
loadingReceived: boolean;
|
loadingReceived: boolean;
|
||||||
onClickDispute: () => void;
|
onClickDispute: () => void;
|
||||||
loadingDispute: boolean;
|
loadingDispute: boolean;
|
||||||
baseUrl: string;
|
|
||||||
messages: EncryptedChatMessage[];
|
messages: EncryptedChatMessage[];
|
||||||
setMessages: (state: EncryptedChatMessage[]) => void;
|
setMessages: (state: EncryptedChatMessage[]) => void;
|
||||||
}
|
}
|
||||||
@ -35,12 +34,11 @@ export const ChatPrompt = ({
|
|||||||
loadingReceived,
|
loadingReceived,
|
||||||
onClickDispute,
|
onClickDispute,
|
||||||
loadingDispute,
|
loadingDispute,
|
||||||
baseUrl,
|
|
||||||
messages,
|
messages,
|
||||||
setMessages,
|
setMessages,
|
||||||
}: ChatPromptProps): JSX.Element => {
|
}: ChatPromptProps): JSX.Element => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { orderUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
const { slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
|
|
||||||
const [sentButton, setSentButton] = useState<boolean>(false);
|
const [sentButton, setSentButton] = useState<boolean>(false);
|
||||||
const [receivedButton, setReceivedButton] = useState<boolean>(false);
|
const [receivedButton, setReceivedButton] = useState<boolean>(false);
|
||||||
@ -113,7 +111,7 @@ export const ChatPrompt = ({
|
|||||||
setText(t("The buyer has sent the fiat. Click 'Confirm Received' once you receive it."));
|
setText(t("The buyer has sent the fiat. Click 'Confirm Received' once you receive it."));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [orderUpdatedAt]);
|
}, [slotUpdatedAt]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
@ -135,7 +133,6 @@ export const ChatPrompt = ({
|
|||||||
status={order.status}
|
status={order.status}
|
||||||
chatOffset={order.chat_last_index}
|
chatOffset={order.chat_last_index}
|
||||||
order={order}
|
order={order}
|
||||||
baseUrl={baseUrl}
|
|
||||||
messages={messages}
|
messages={messages}
|
||||||
setMessages={setMessages}
|
setMessages={setMessages}
|
||||||
/>
|
/>
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
import React, { useState, useEffect, useContext } from 'react';
|
import React, { useState, useEffect, useContext } from 'react';
|
||||||
import { Box, Divider, Grid } from '@mui/material';
|
import { Box, Divider, Grid } from '@mui/material';
|
||||||
|
|
||||||
import { apiClient } from '../../services/api';
|
|
||||||
import { getWebln, pn } from '../../utils';
|
import { getWebln, pn } from '../../utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -102,7 +100,7 @@ const closeAll: OpenDialogProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface TradeBoxProps {
|
interface TradeBoxProps {
|
||||||
baseUrl: string;
|
currentOrder: Order;
|
||||||
onStartAgain: () => void;
|
onStartAgain: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,10 +113,10 @@ interface Contract {
|
|||||||
titleIcon: () => JSX.Element;
|
titleIcon: () => JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
const TradeBox = ({ currentOrder, onStartAgain }: TradeBoxProps): JSX.Element => {
|
||||||
const { garage, orderUpdatedAt, setBadOrder } = useContext<UseGarageStoreType>(GarageContext);
|
const { garage, slotUpdatedAt } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const { settings, hostUrl, origin } = useContext<UseAppStoreType>(AppContext);
|
const { settings } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { federation, setCurrentOrderId } = useContext<UseFederationStoreType>(FederationContext);
|
const { federation } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
// Buttons and Dialogs
|
// Buttons and Dialogs
|
||||||
@ -155,42 +153,29 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renewOrder = function (): void {
|
const renewOrder = function (): void {
|
||||||
const currentOrder = garage.getSlot()?.order;
|
const slot = garage.getSlot();
|
||||||
if (currentOrder) {
|
const newOrder = currentOrder;
|
||||||
const body = {
|
if (newOrder && slot) {
|
||||||
type: currentOrder.type,
|
const orderAttributes = {
|
||||||
currency: currentOrder.currency,
|
type: newOrder.type,
|
||||||
amount: currentOrder.has_range ? null : currentOrder.amount,
|
currency: newOrder.currency,
|
||||||
has_range: currentOrder.has_range,
|
amount: newOrder.has_range ? null : newOrder.amount,
|
||||||
min_amount: currentOrder.min_amount,
|
has_range: newOrder.has_range,
|
||||||
max_amount: currentOrder.max_amount,
|
min_amount: newOrder.min_amount,
|
||||||
payment_method: currentOrder.payment_method,
|
max_amount: newOrder.max_amount,
|
||||||
is_explicit: currentOrder.is_explicit,
|
payment_method: newOrder.payment_method,
|
||||||
premium: currentOrder.is_explicit ? null : currentOrder.premium,
|
is_explicit: newOrder.is_explicit,
|
||||||
satoshis: currentOrder.is_explicit ? currentOrder.satoshis : null,
|
premium: newOrder.is_explicit ? null : newOrder.premium,
|
||||||
public_duration: currentOrder.public_duration,
|
satoshis: newOrder.is_explicit ? newOrder.satoshis : null,
|
||||||
escrow_duration: currentOrder.escrow_duration,
|
public_duration: newOrder.public_duration,
|
||||||
bond_size: currentOrder.bond_size,
|
escrow_duration: newOrder.escrow_duration,
|
||||||
latitude: currentOrder.latitude,
|
bond_size: newOrder.bond_size,
|
||||||
longitude: currentOrder.longitude,
|
latitude: newOrder.latitude,
|
||||||
|
longitude: newOrder.longitude,
|
||||||
|
shortAlias: newOrder.shortAlias,
|
||||||
};
|
};
|
||||||
const { url, basePath } = federation
|
void slot.makeOrder(federation, orderAttributes).then((order: Order) => {
|
||||||
.getCoordinator(currentOrder.shortAlias)
|
if (order?.id) navigate(`/order/${String(order?.shortAlias)}/${String(order.id)}`);
|
||||||
.getEndpoint(settings.network, origin, settings.selfhostedClient, hostUrl);
|
|
||||||
apiClient
|
|
||||||
.post(url + basePath, '/api/make/', body, {
|
|
||||||
tokenSHA256: garage.getSlot()?.getRobot()?.tokenSHA256,
|
|
||||||
})
|
|
||||||
.then((data: any) => {
|
|
||||||
if (data.bad_request !== undefined) {
|
|
||||||
setBadOrder(data.bad_request);
|
|
||||||
} else if (data.id !== undefined) {
|
|
||||||
navigate(`/order/${String(currentOrder?.shortAlias)}/${String(data.id)}`);
|
|
||||||
setCurrentOrderId({ id: data.id, shortAlias: currentOrder?.shortAlias });
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setBadOrder('Request error');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -204,14 +189,11 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
|||||||
statement,
|
statement,
|
||||||
rating,
|
rating,
|
||||||
}: SubmitActionProps): void {
|
}: SubmitActionProps): void {
|
||||||
const robot = garage.getSlot()?.getRobot();
|
const slot = garage.getSlot();
|
||||||
const currentOrder = garage.getSlot()?.order;
|
|
||||||
|
|
||||||
void apiClient
|
if (slot && currentOrder) {
|
||||||
.post(
|
currentOrder
|
||||||
baseUrl,
|
.submitAction(federation, slot, {
|
||||||
`/api/order/?order_id=${Number(currentOrder?.id)}`,
|
|
||||||
{
|
|
||||||
action,
|
action,
|
||||||
invoice,
|
invoice,
|
||||||
routing_budget_ppm,
|
routing_budget_ppm,
|
||||||
@ -219,29 +201,24 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
|||||||
mining_fee_rate,
|
mining_fee_rate,
|
||||||
statement,
|
statement,
|
||||||
rating,
|
rating,
|
||||||
},
|
})
|
||||||
{ tokenSHA256: robot?.tokenSHA256 },
|
|
||||||
)
|
|
||||||
.then((data: Order) => {
|
.then((data: Order) => {
|
||||||
setOpen(closeAll);
|
setOpen(closeAll);
|
||||||
setLoadingButtons({ ...noLoadingButtons });
|
setLoadingButtons({ ...noLoadingButtons });
|
||||||
if (data.bad_request !== undefined) {
|
if (data.bad_address !== undefined) {
|
||||||
setBadOrder(data.bad_request);
|
|
||||||
} else if (data.bad_address !== undefined) {
|
|
||||||
setOnchain({ ...onchain, badAddress: data.bad_address });
|
setOnchain({ ...onchain, badAddress: data.bad_address });
|
||||||
} else if (data.bad_invoice !== undefined) {
|
} else if (data.bad_invoice !== undefined) {
|
||||||
setLightning({ ...lightning, badInvoice: data.bad_invoice });
|
setLightning({ ...lightning, badInvoice: data.bad_invoice });
|
||||||
} else if (data.bad_statement !== undefined) {
|
} else if (data.bad_statement !== undefined) {
|
||||||
setDispute({ ...dispute, badStatement: data.bad_statement });
|
setDispute({ ...dispute, badStatement: data.bad_statement });
|
||||||
} else {
|
|
||||||
garage.updateOrder(data);
|
|
||||||
setBadOrder(undefined);
|
|
||||||
}
|
}
|
||||||
|
slot.updateSlotFromOrder(data);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setOpen(closeAll);
|
setOpen(closeAll);
|
||||||
setLoadingButtons({ ...noLoadingButtons });
|
setLoadingButtons({ ...noLoadingButtons });
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const cancel = function (): void {
|
const cancel = function (): void {
|
||||||
@ -363,15 +340,14 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
|||||||
|
|
||||||
// Effect on Order Status change (used for WebLN)
|
// Effect on Order Status change (used for WebLN)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentOrder = garage.getSlot()?.order;
|
|
||||||
if (currentOrder && currentOrder?.status !== lastOrderStatus) {
|
if (currentOrder && currentOrder?.status !== lastOrderStatus) {
|
||||||
setLastOrderStatus(currentOrder.status);
|
setLastOrderStatus(currentOrder.status);
|
||||||
void handleWebln(currentOrder);
|
void handleWebln(currentOrder);
|
||||||
}
|
}
|
||||||
}, [orderUpdatedAt]);
|
}, [slotUpdatedAt]);
|
||||||
|
|
||||||
const statusToContract = function (): Contract {
|
const statusToContract = function (): Contract {
|
||||||
const order = garage.getSlot()?.order;
|
const order = currentOrder;
|
||||||
|
|
||||||
const baseContract: Contract = {
|
const baseContract: Contract = {
|
||||||
title: 'Unknown Order Status',
|
title: 'Unknown Order Status',
|
||||||
@ -382,7 +358,7 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
|||||||
titleIcon: () => <></>,
|
titleIcon: () => <></>,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!order) return baseContract;
|
if (!currentOrder) return baseContract;
|
||||||
|
|
||||||
const status = order.status;
|
const status = order.status;
|
||||||
const isBuyer = order.is_buyer;
|
const isBuyer = order.is_buyer;
|
||||||
@ -573,7 +549,6 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
|||||||
setOpen({ ...open, confirmDispute: true });
|
setOpen({ ...open, confirmDispute: true });
|
||||||
}}
|
}}
|
||||||
loadingDispute={loadingButtons.openDispute}
|
loadingDispute={loadingButtons.openDispute}
|
||||||
baseUrl={baseUrl}
|
|
||||||
messages={messages}
|
messages={messages}
|
||||||
setMessages={setMessages}
|
setMessages={setMessages}
|
||||||
/>
|
/>
|
||||||
@ -746,7 +721,7 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
|||||||
setOpen(closeAll);
|
setOpen(closeAll);
|
||||||
}}
|
}}
|
||||||
waitingWebln={waitingWebln}
|
waitingWebln={waitingWebln}
|
||||||
isBuyer={garage.getSlot()?.order?.is_buyer ?? false}
|
isBuyer={currentOrder.is_buyer ?? false}
|
||||||
/>
|
/>
|
||||||
<ConfirmDisputeDialog
|
<ConfirmDisputeDialog
|
||||||
open={open.confirmDispute}
|
open={open.confirmDispute}
|
||||||
@ -769,11 +744,11 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
|||||||
}}
|
}}
|
||||||
onCollabCancelClick={cancel}
|
onCollabCancelClick={cancel}
|
||||||
loading={loadingButtons.cancel}
|
loading={loadingButtons.cancel}
|
||||||
peerAskedCancel={garage.getSlot()?.order?.pending_cancel ?? false}
|
peerAskedCancel={currentOrder?.pending_cancel ?? false}
|
||||||
/>
|
/>
|
||||||
<ConfirmFiatSentDialog
|
<ConfirmFiatSentDialog
|
||||||
open={open.confirmFiatSent}
|
open={open.confirmFiatSent}
|
||||||
order={garage.getSlot()?.order ?? null}
|
order={currentOrder ?? null}
|
||||||
loadingButton={loadingButtons.fiatSent}
|
loadingButton={loadingButtons.fiatSent}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen(closeAll);
|
setOpen(closeAll);
|
||||||
@ -790,14 +765,14 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
|||||||
/>
|
/>
|
||||||
<ConfirmFiatReceivedDialog
|
<ConfirmFiatReceivedDialog
|
||||||
open={open.confirmFiatReceived}
|
open={open.confirmFiatReceived}
|
||||||
order={garage.getSlot()?.order ?? null}
|
order={currentOrder ?? null}
|
||||||
loadingButton={loadingButtons.fiatReceived}
|
loadingButton={loadingButtons.fiatReceived}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setOpen(closeAll);
|
setOpen(closeAll);
|
||||||
}}
|
}}
|
||||||
onConfirmClick={confirmFiatReceived}
|
onConfirmClick={confirmFiatReceived}
|
||||||
/>
|
/>
|
||||||
<CollabCancelAlert order={garage.getSlot()?.order ?? null} />
|
<CollabCancelAlert order={currentOrder ?? null} />
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
padding={1}
|
padding={1}
|
||||||
@ -808,7 +783,7 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
|||||||
>
|
>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Title
|
<Title
|
||||||
order={garage.getSlot()?.order ?? null}
|
order={currentOrder ?? null}
|
||||||
text={contract?.title}
|
text={contract?.title}
|
||||||
color={contract?.titleColor}
|
color={contract?.titleColor}
|
||||||
icon={contract?.titleIcon}
|
icon={contract?.titleIcon}
|
||||||
@ -822,10 +797,7 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
|||||||
{contract?.bondStatus !== 'hide' ? (
|
{contract?.bondStatus !== 'hide' ? (
|
||||||
<Grid item sx={{ width: '100%' }}>
|
<Grid item sx={{ width: '100%' }}>
|
||||||
<Divider />
|
<Divider />
|
||||||
<BondStatus
|
<BondStatus status={contract?.bondStatus} isMaker={currentOrder?.is_maker ?? false} />
|
||||||
status={contract?.bondStatus}
|
|
||||||
isMaker={garage.getSlot()?.order?.is_maker ?? false}
|
|
||||||
/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
@ -833,7 +805,7 @@ const TradeBox = ({ baseUrl, onStartAgain }: TradeBoxProps): JSX.Element => {
|
|||||||
|
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<CancelButton
|
<CancelButton
|
||||||
order={garage.getSlot()?.order ?? null}
|
order={currentOrder ?? null}
|
||||||
onClickCancel={cancel}
|
onClickCancel={cancel}
|
||||||
openCancelDialog={() => {
|
openCancelDialog={() => {
|
||||||
setOpen({ ...closeAll, confirmCancel: true });
|
setOpen({ ...closeAll, confirmCancel: true });
|
||||||
|
@ -76,7 +76,11 @@ const makeTheme = function (settings: Settings): Theme {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getHostUrl = (network = 'mainnet'): string => {
|
const getHostUrl = (network = 'mainnet'): string => {
|
||||||
let host = defaultFederation.exp[network].onion;
|
const randomAlias =
|
||||||
|
Object.keys(defaultFederation)[
|
||||||
|
Math.floor(Math.random() * Object.keys(defaultFederation).length)
|
||||||
|
];
|
||||||
|
let host = defaultFederation[randomAlias][network].onion;
|
||||||
let protocol = 'http:';
|
let protocol = 'http:';
|
||||||
if (window.NativeRobosats === undefined) {
|
if (window.NativeRobosats === undefined) {
|
||||||
host = getHost();
|
host = getHost();
|
||||||
|
@ -8,7 +8,7 @@ import React, {
|
|||||||
type ReactNode,
|
type ReactNode,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { type Order, Federation, Settings } from '../models';
|
import { Federation, Settings } from '../models';
|
||||||
|
|
||||||
import { federationLottery } from '../utils';
|
import { federationLottery } from '../utils';
|
||||||
|
|
||||||
@ -16,30 +16,6 @@ import { AppContext, type UseAppStoreType } from './AppContext';
|
|||||||
import { GarageContext, type UseGarageStoreType } from './GarageContext';
|
import { GarageContext, type UseGarageStoreType } from './GarageContext';
|
||||||
import { type Origin, type Origins } from '../models/Coordinator.model';
|
import { type Origin, type Origins } from '../models/Coordinator.model';
|
||||||
|
|
||||||
// Refresh delays (ms) according to Order status
|
|
||||||
const defaultDelay = 5000;
|
|
||||||
const statusToDelay = [
|
|
||||||
3000, // 'Waiting for maker bond'
|
|
||||||
35000, // 'Public'
|
|
||||||
180000, // 'Paused'
|
|
||||||
3000, // 'Waiting for taker bond'
|
|
||||||
999999, // 'Cancelled'
|
|
||||||
999999, // 'Expired'
|
|
||||||
8000, // 'Waiting for trade collateral and buyer invoice'
|
|
||||||
8000, // 'Waiting only for seller trade collateral'
|
|
||||||
8000, // 'Waiting only for buyer invoice'
|
|
||||||
10000, // 'Sending fiat - In chatroom'
|
|
||||||
10000, // 'Fiat sent - In chatroom'
|
|
||||||
100000, // 'In dispute'
|
|
||||||
999999, // 'Collaboratively cancelled'
|
|
||||||
10000, // 'Sending satoshis to buyer'
|
|
||||||
60000, // 'Successful trade'
|
|
||||||
30000, // 'Failed lightning network routing'
|
|
||||||
300000, // 'Wait for dispute resolution'
|
|
||||||
300000, // 'Maker lost dispute'
|
|
||||||
300000, // 'Taker lost dispute'
|
|
||||||
];
|
|
||||||
|
|
||||||
export interface CurrentOrderIdProps {
|
export interface CurrentOrderIdProps {
|
||||||
id: number | null;
|
id: number | null;
|
||||||
shortAlias: string | null;
|
shortAlias: string | null;
|
||||||
@ -52,10 +28,6 @@ export interface FederationContextProviderProps {
|
|||||||
export interface UseFederationStoreType {
|
export interface UseFederationStoreType {
|
||||||
federation: Federation;
|
federation: Federation;
|
||||||
sortedCoordinators: string[];
|
sortedCoordinators: string[];
|
||||||
setDelay: Dispatch<SetStateAction<number>>;
|
|
||||||
currentOrderId: CurrentOrderIdProps;
|
|
||||||
setCurrentOrderId: Dispatch<SetStateAction<CurrentOrderIdProps>>;
|
|
||||||
currentOrder: Order | null;
|
|
||||||
coordinatorUpdatedAt: string;
|
coordinatorUpdatedAt: string;
|
||||||
federationUpdatedAt: string;
|
federationUpdatedAt: string;
|
||||||
addNewCoordinator: (alias: string, url: string) => void;
|
addNewCoordinator: (alias: string, url: string) => void;
|
||||||
@ -64,10 +36,6 @@ export interface UseFederationStoreType {
|
|||||||
export const initialFederationContext: UseFederationStoreType = {
|
export const initialFederationContext: UseFederationStoreType = {
|
||||||
federation: new Federation('onion', new Settings(), ''),
|
federation: new Federation('onion', new Settings(), ''),
|
||||||
sortedCoordinators: [],
|
sortedCoordinators: [],
|
||||||
setDelay: () => {},
|
|
||||||
currentOrderId: { id: null, shortAlias: null },
|
|
||||||
setCurrentOrderId: () => {},
|
|
||||||
currentOrder: null,
|
|
||||||
coordinatorUpdatedAt: '',
|
coordinatorUpdatedAt: '',
|
||||||
federationUpdatedAt: '',
|
federationUpdatedAt: '',
|
||||||
addNewCoordinator: () => {},
|
addNewCoordinator: () => {},
|
||||||
@ -80,24 +48,13 @@ export const FederationContextProvider = ({
|
|||||||
}: FederationContextProviderProps): JSX.Element => {
|
}: FederationContextProviderProps): JSX.Element => {
|
||||||
const { settings, page, origin, hostUrl, open, torStatus } =
|
const { settings, page, origin, hostUrl, open, torStatus } =
|
||||||
useContext<UseAppStoreType>(AppContext);
|
useContext<UseAppStoreType>(AppContext);
|
||||||
const { setMaker, garage, setBadOrder } = useContext<UseGarageStoreType>(GarageContext);
|
const { setMaker, garage } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
const [federation] = useState(new Federation(origin, settings, hostUrl));
|
const [federation] = useState(new Federation(origin, settings, hostUrl));
|
||||||
const [sortedCoordinators, setSortedCoordinators] = useState(federationLottery(federation));
|
const [sortedCoordinators, setSortedCoordinators] = useState(federationLottery(federation));
|
||||||
const [coordinatorUpdatedAt, setCoordinatorUpdatedAt] = useState<string>(
|
const [coordinatorUpdatedAt, setCoordinatorUpdatedAt] = useState<string>(
|
||||||
new Date().toISOString(),
|
new Date().toISOString(),
|
||||||
);
|
);
|
||||||
const [federationUpdatedAt, setFederationUpdatedAt] = useState<string>(new Date().toISOString());
|
const [federationUpdatedAt, setFederationUpdatedAt] = useState<string>(new Date().toISOString());
|
||||||
const [currentOrderId, setCurrentOrderId] = useState<CurrentOrderIdProps>(
|
|
||||||
initialFederationContext.currentOrderId,
|
|
||||||
);
|
|
||||||
const [currentOrder, setCurrentOrder] = useState<Order | null>(
|
|
||||||
initialFederationContext.currentOrder,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [delay, setDelay] = useState<number>(defaultDelay);
|
|
||||||
const [timer, setTimer] = useState<NodeJS.Timer | undefined>(() =>
|
|
||||||
setInterval(() => null, delay),
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMaker((maker) => {
|
setMaker((maker) => {
|
||||||
@ -106,65 +63,15 @@ export const FederationContextProvider = ({
|
|||||||
federation.registerHook('onFederationUpdate', () => {
|
federation.registerHook('onFederationUpdate', () => {
|
||||||
setFederationUpdatedAt(new Date().toISOString());
|
setFederationUpdatedAt(new Date().toISOString());
|
||||||
});
|
});
|
||||||
federation.registerHook('onCoordinatorUpdate', () => {
|
|
||||||
setCoordinatorUpdatedAt(new Date().toISOString());
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) {
|
if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) {
|
||||||
void federation.updateUrl(origin, settings, hostUrl);
|
void federation.updateUrl(origin, settings, hostUrl);
|
||||||
void federation.update();
|
void federation.update();
|
||||||
|
|
||||||
const token = garage.getSlot()?.getRobot()?.token;
|
|
||||||
if (token) void federation.fetchRobot(garage, token);
|
|
||||||
}
|
}
|
||||||
}, [settings.network, settings.useProxy, torStatus]);
|
}, [settings.network, settings.useProxy, torStatus]);
|
||||||
|
|
||||||
const onOrderReceived = (order: Order): void => {
|
|
||||||
let newDelay = defaultDelay;
|
|
||||||
if (order?.bad_request) {
|
|
||||||
newDelay = 99999999;
|
|
||||||
setBadOrder(order.bad_request);
|
|
||||||
garage.updateOrder(null);
|
|
||||||
setCurrentOrder(null);
|
|
||||||
}
|
|
||||||
if (order?.id) {
|
|
||||||
newDelay =
|
|
||||||
order.status >= 0 && order.status <= 18
|
|
||||||
? page === 'order'
|
|
||||||
? statusToDelay[order.status]
|
|
||||||
: statusToDelay[order.status] * 5 // If user is not looking at "order" tab, refresh less often.
|
|
||||||
: 99999999;
|
|
||||||
garage.updateOrder(order);
|
|
||||||
setCurrentOrder(order);
|
|
||||||
setBadOrder(undefined);
|
|
||||||
}
|
|
||||||
clearInterval(timer);
|
|
||||||
console.log('New Delay:', newDelay);
|
|
||||||
setDelay(newDelay);
|
|
||||||
setTimer(setTimeout(fetchCurrentOrder, newDelay));
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchCurrentOrder: () => void = () => {
|
|
||||||
const slot = garage?.getSlot();
|
|
||||||
const robot = slot?.getRobot();
|
|
||||||
if (robot && slot?.token && currentOrderId.id && currentOrderId.shortAlias) {
|
|
||||||
const coordinator = federation.getCoordinator(currentOrderId.shortAlias);
|
|
||||||
void coordinator?.fetchOrder(currentOrderId.id, robot, slot?.token).then((order) => {
|
|
||||||
onOrderReceived(order as Order);
|
|
||||||
});
|
|
||||||
} else if (slot?.token && slot?.activeShortAlias && robot?.activeOrderId) {
|
|
||||||
const coordinator = federation.getCoordinator(slot.activeShortAlias);
|
|
||||||
void coordinator?.fetchOrder(robot.activeOrderId, robot, slot.token).then((order) => {
|
|
||||||
onOrderReceived(order as Order);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
clearInterval(timer);
|
|
||||||
setTimer(setTimeout(fetchCurrentOrder, defaultDelay));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const addNewCoordinator: (alias: string, url: string) => void = (alias, url) => {
|
const addNewCoordinator: (alias: string, url: string) => void = (alias, url) => {
|
||||||
if (!federation.coordinators[alias]) {
|
if (!federation.coordinators[alias]) {
|
||||||
const attributes: Record<any, any> = {
|
const attributes: Record<any, any> = {
|
||||||
@ -188,24 +95,12 @@ export const FederationContextProvider = ({
|
|||||||
newCoordinator.update(() => {
|
newCoordinator.update(() => {
|
||||||
setCoordinatorUpdatedAt(new Date().toISOString());
|
setCoordinatorUpdatedAt(new Date().toISOString());
|
||||||
});
|
});
|
||||||
garage.syncCoordinator(newCoordinator);
|
garage.syncCoordinator(federation, alias);
|
||||||
setSortedCoordinators(federationLottery(federation));
|
setSortedCoordinators(federationLottery(federation));
|
||||||
setFederationUpdatedAt(new Date().toISOString());
|
setFederationUpdatedAt(new Date().toISOString());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentOrderId.id && currentOrderId.shortAlias) {
|
|
||||||
setCurrentOrder(null);
|
|
||||||
setBadOrder(undefined);
|
|
||||||
clearInterval(timer);
|
|
||||||
fetchCurrentOrder();
|
|
||||||
}
|
|
||||||
return () => {
|
|
||||||
clearInterval(timer);
|
|
||||||
};
|
|
||||||
}, [currentOrderId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (page === 'offers') void federation.updateBook();
|
if (page === 'offers') void federation.updateBook();
|
||||||
}, [page]);
|
}, [page]);
|
||||||
@ -215,7 +110,7 @@ export const FederationContextProvider = ({
|
|||||||
const slot = garage.getSlot();
|
const slot = garage.getSlot();
|
||||||
|
|
||||||
if (open.profile && slot?.hashId && slot?.token) {
|
if (open.profile && slot?.hashId && slot?.token) {
|
||||||
void federation.fetchRobot(garage, slot?.token); // refresh/update existing robot
|
void garage.fetchRobot(federation, slot?.token); // refresh/update existing robot
|
||||||
}
|
}
|
||||||
}, [open.profile]);
|
}, [open.profile]);
|
||||||
|
|
||||||
@ -224,10 +119,6 @@ export const FederationContextProvider = ({
|
|||||||
value={{
|
value={{
|
||||||
federation,
|
federation,
|
||||||
sortedCoordinators,
|
sortedCoordinators,
|
||||||
currentOrderId,
|
|
||||||
setCurrentOrderId,
|
|
||||||
currentOrder,
|
|
||||||
setDelay,
|
|
||||||
coordinatorUpdatedAt,
|
coordinatorUpdatedAt,
|
||||||
federationUpdatedAt,
|
federationUpdatedAt,
|
||||||
addNewCoordinator,
|
addNewCoordinator,
|
||||||
|
@ -5,10 +5,14 @@ import React, {
|
|||||||
type SetStateAction,
|
type SetStateAction,
|
||||||
useEffect,
|
useEffect,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
|
useContext,
|
||||||
|
useRef,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
import { defaultMaker, type Maker, Garage } from '../models';
|
import { defaultMaker, type Maker, Garage } from '../models';
|
||||||
import { systemClient } from '../services/System';
|
import { systemClient } from '../services/System';
|
||||||
|
import { type UseAppStoreType, AppContext } from './AppContext';
|
||||||
|
import { type UseFederationStoreType, FederationContext } from './FederationContext';
|
||||||
|
|
||||||
export interface GarageContextProviderProps {
|
export interface GarageContextProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@ -18,61 +22,138 @@ export interface UseGarageStoreType {
|
|||||||
garage: Garage;
|
garage: Garage;
|
||||||
maker: Maker;
|
maker: Maker;
|
||||||
setMaker: Dispatch<SetStateAction<Maker>>;
|
setMaker: Dispatch<SetStateAction<Maker>>;
|
||||||
badOrder?: string;
|
setDelay: Dispatch<SetStateAction<number>>;
|
||||||
setBadOrder: Dispatch<SetStateAction<string | undefined>>;
|
fetchSlotActiveOrder: () => void;
|
||||||
robotUpdatedAt: string;
|
slotUpdatedAt: string;
|
||||||
orderUpdatedAt: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialGarageContext: UseGarageStoreType = {
|
export const initialGarageContext: UseGarageStoreType = {
|
||||||
garage: new Garage(),
|
garage: new Garage(),
|
||||||
maker: defaultMaker,
|
maker: defaultMaker,
|
||||||
setMaker: () => {},
|
setMaker: () => {},
|
||||||
badOrder: undefined,
|
setDelay: () => {},
|
||||||
setBadOrder: () => {},
|
fetchSlotActiveOrder: () => {},
|
||||||
robotUpdatedAt: '',
|
slotUpdatedAt: '',
|
||||||
orderUpdatedAt: '',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const defaultDelay = 5000;
|
||||||
|
// Refresh delays (ms) according to Order status
|
||||||
|
const statusToDelay = [
|
||||||
|
3000, // 'Waiting for maker bond'
|
||||||
|
35000, // 'Public'
|
||||||
|
180000, // 'Paused'
|
||||||
|
3000, // 'Waiting for taker bond'
|
||||||
|
999999, // 'Cancelled'
|
||||||
|
999999, // 'Expired'
|
||||||
|
8000, // 'Waiting for trade collateral and buyer invoice'
|
||||||
|
8000, // 'Waiting only for seller trade collateral'
|
||||||
|
8000, // 'Waiting only for buyer invoice'
|
||||||
|
10000, // 'Sending fiat - In chatroom'
|
||||||
|
10000, // 'Fiat sent - In chatroom'
|
||||||
|
100000, // 'In dispute'
|
||||||
|
999999, // 'Collaboratively cancelled'
|
||||||
|
10000, // 'Sending satoshis to buyer'
|
||||||
|
60000, // 'Sucessful trade'
|
||||||
|
30000, // 'Failed lightning network routing'
|
||||||
|
300000, // 'Wait for dispute resolution'
|
||||||
|
300000, // 'Maker lost dispute'
|
||||||
|
300000, // 'Taker lost dispute'
|
||||||
|
];
|
||||||
|
|
||||||
export const GarageContext = createContext<UseGarageStoreType>(initialGarageContext);
|
export const GarageContext = createContext<UseGarageStoreType>(initialGarageContext);
|
||||||
|
|
||||||
export const GarageContextProvider = ({ children }: GarageContextProviderProps): JSX.Element => {
|
export const GarageContextProvider = ({ children }: GarageContextProviderProps): JSX.Element => {
|
||||||
// All garage data structured
|
// All garage data structured
|
||||||
|
const { settings, torStatus, open, page } = useContext<UseAppStoreType>(AppContext);
|
||||||
|
const pageRef = useRef(page);
|
||||||
|
const { federation, sortedCoordinators } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
const [garage] = useState<Garage>(initialGarageContext.garage);
|
const [garage] = useState<Garage>(initialGarageContext.garage);
|
||||||
const [maker, setMaker] = useState<Maker>(initialGarageContext.maker);
|
const [maker, setMaker] = useState<Maker>(initialGarageContext.maker);
|
||||||
const [badOrder, setBadOrder] = useState<string>();
|
const [slotUpdatedAt, setSlotUpdatedAt] = useState<string>(new Date().toISOString());
|
||||||
const [robotUpdatedAt, setRobotUpdatedAt] = useState<string>(new Date().toISOString());
|
const [lastOrderCheckAt] = useState<number>(+new Date());
|
||||||
const [orderUpdatedAt, setOrderUpdatedAt] = useState<string>(new Date().toISOString());
|
const lastOrderCheckAtRef = useRef(lastOrderCheckAt);
|
||||||
|
const [delay, setDelay] = useState<number>(defaultDelay);
|
||||||
|
const [timer, setTimer] = useState<NodeJS.Timer | undefined>(() =>
|
||||||
|
setInterval(() => null, delay),
|
||||||
|
);
|
||||||
|
|
||||||
const onRobotUpdated = (): void => {
|
const onSlotUpdated = (): void => {
|
||||||
setRobotUpdatedAt(new Date().toISOString());
|
setSlotUpdatedAt(new Date().toISOString());
|
||||||
};
|
|
||||||
|
|
||||||
const onOrderUpdate = (): void => {
|
|
||||||
setOrderUpdatedAt(new Date().toISOString());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
garage.registerHook('onRobotUpdate', onRobotUpdated);
|
setMaker((maker) => {
|
||||||
garage.registerHook('onOrderUpdate', onOrderUpdate);
|
return { ...maker, coordinator: sortedCoordinators[0] };
|
||||||
|
}); // default MakerForm coordinator is decided via sorted lottery
|
||||||
|
garage.registerHook('onSlotUpdate', onSlotUpdated);
|
||||||
|
clearInterval(timer);
|
||||||
|
fetchSlotActiveOrder();
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (window.NativeRobosats === undefined || torStatus === 'ON' || !settings.useProxy) {
|
||||||
|
const token = garage.getSlot()?.token;
|
||||||
|
if (token) void garage.fetchRobot(federation, token);
|
||||||
|
}
|
||||||
|
}, [settings.network, settings.useProxy, torStatus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.NativeRobosats !== undefined && !systemClient.loading) {
|
if (window.NativeRobosats !== undefined && !systemClient.loading) {
|
||||||
garage.loadSlots();
|
garage.loadSlots();
|
||||||
}
|
}
|
||||||
}, [systemClient.loading]);
|
}, [systemClient.loading]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
pageRef.current = page;
|
||||||
|
}, [page]);
|
||||||
|
|
||||||
|
// use effects to fetchRobots on Profile open
|
||||||
|
useEffect(() => {
|
||||||
|
const slot = garage.getSlot();
|
||||||
|
|
||||||
|
if (open.profile && slot?.hashId && slot?.token) {
|
||||||
|
void garage.fetchRobot(federation, slot?.token); // refresh/update existing robot
|
||||||
|
}
|
||||||
|
}, [open.profile]);
|
||||||
|
|
||||||
|
const fetchSlotActiveOrder: () => void = () => {
|
||||||
|
const slot = garage?.getSlot();
|
||||||
|
if (slot?.activeOrder?.id) {
|
||||||
|
let delay =
|
||||||
|
slot.activeOrder.status >= 0 && slot.activeOrder.status <= 18
|
||||||
|
? statusToDelay[slot.activeOrder.status]
|
||||||
|
: defaultDelay;
|
||||||
|
if (pageRef.current !== 'order') delay = delay * 5;
|
||||||
|
if (+new Date() - lastOrderCheckAtRef.current >= delay) {
|
||||||
|
void slot.fetchActiveOrder(federation).finally(() => {
|
||||||
|
lastOrderCheckAtRef.current = +new Date();
|
||||||
|
resetInterval();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resetInterval();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resetInterval();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetInterval = (): void => {
|
||||||
|
clearInterval(timer);
|
||||||
|
setDelay(defaultDelay);
|
||||||
|
setTimer(setTimeout(() => fetchSlotActiveOrder(), defaultDelay));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GarageContext.Provider
|
<GarageContext.Provider
|
||||||
value={{
|
value={{
|
||||||
garage,
|
garage,
|
||||||
maker,
|
maker,
|
||||||
setMaker,
|
setMaker,
|
||||||
badOrder,
|
setDelay,
|
||||||
setBadOrder,
|
fetchSlotActiveOrder,
|
||||||
robotUpdatedAt,
|
slotUpdatedAt,
|
||||||
orderUpdatedAt,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -1,16 +1,7 @@
|
|||||||
import {
|
import { type LimitList, type PublicOrder, type Settings } from '.';
|
||||||
type Robot,
|
|
||||||
type LimitList,
|
|
||||||
type PublicOrder,
|
|
||||||
type Settings,
|
|
||||||
type Order,
|
|
||||||
type Garage,
|
|
||||||
} from '.';
|
|
||||||
import { roboidentitiesClient } from '../services/Roboidentities/Web';
|
import { roboidentitiesClient } from '../services/Roboidentities/Web';
|
||||||
import { apiClient } from '../services/api';
|
import { apiClient } from '../services/api';
|
||||||
import { validateTokenEntropy } from '../utils';
|
|
||||||
import { compareUpdateLimit } from './Limit.model';
|
import { compareUpdateLimit } from './Limit.model';
|
||||||
import { defaultOrder } from './Order.model';
|
|
||||||
|
|
||||||
export interface Contact {
|
export interface Contact {
|
||||||
nostr?: string | undefined;
|
nostr?: string | undefined;
|
||||||
@ -180,7 +171,6 @@ export class Coordinator {
|
|||||||
public loadingInfo: boolean = false;
|
public loadingInfo: boolean = false;
|
||||||
public limits: LimitList = {};
|
public limits: LimitList = {};
|
||||||
public loadingLimits: boolean = false;
|
public loadingLimits: boolean = false;
|
||||||
public loadingRobot: string | null;
|
|
||||||
|
|
||||||
updateUrl = (origin: Origin, settings: Settings, hostUrl: string): void => {
|
updateUrl = (origin: Origin, settings: Settings, hostUrl: string): void => {
|
||||||
if (settings.selfhostedClient && this.shortAlias !== 'local') {
|
if (settings.selfhostedClient && this.shortAlias !== 'local') {
|
||||||
@ -331,132 +321,6 @@ export class Coordinator {
|
|||||||
return { url: String(this[network][origin]), basePath: '' };
|
return { url: String(this[network][origin]), basePath: '' };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchRobot = async (garage: Garage, token: string): Promise<Robot | null> => {
|
|
||||||
if (!this.enabled || !token || this.loadingRobot === token) return null;
|
|
||||||
|
|
||||||
const robot = garage?.getSlot(token)?.getRobot() ?? null;
|
|
||||||
const authHeaders = robot?.getAuthHeaders();
|
|
||||||
|
|
||||||
if (!authHeaders) return null;
|
|
||||||
|
|
||||||
const { hasEnoughEntropy, bitsEntropy, shannonEntropy } = validateTokenEntropy(token);
|
|
||||||
|
|
||||||
if (!hasEnoughEntropy) return null;
|
|
||||||
|
|
||||||
this.loadingRobot = token;
|
|
||||||
|
|
||||||
garage.updateRobot(token, this.shortAlias, { loading: true });
|
|
||||||
|
|
||||||
const newAttributes = await apiClient
|
|
||||||
.get(this.url, `${this.basePath}/api/robot/`, authHeaders)
|
|
||||||
.then((data: any) => {
|
|
||||||
return {
|
|
||||||
nickname: data.nickname,
|
|
||||||
activeOrderId: data.active_order_id ?? null,
|
|
||||||
lastOrderId: data.last_order_id ?? null,
|
|
||||||
earnedRewards: data.earned_rewards ?? 0,
|
|
||||||
stealthInvoices: data.wants_stealth,
|
|
||||||
tgEnabled: data.tg_enabled,
|
|
||||||
tgBotName: data.tg_bot_name,
|
|
||||||
tgToken: data.tg_token,
|
|
||||||
found: data?.found,
|
|
||||||
last_login: data.last_login,
|
|
||||||
pubKey: data.public_key,
|
|
||||||
encPrivKey: data.encrypted_private_key,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e);
|
|
||||||
})
|
|
||||||
.finally(() => (this.loadingRobot = null));
|
|
||||||
|
|
||||||
garage.updateRobot(token, this.shortAlias, {
|
|
||||||
...newAttributes,
|
|
||||||
tokenSHA256: authHeaders.tokenSHA256,
|
|
||||||
loading: false,
|
|
||||||
bitsEntropy,
|
|
||||||
shannonEntropy,
|
|
||||||
shortAlias: this.shortAlias,
|
|
||||||
});
|
|
||||||
|
|
||||||
return garage.getSlot(this.shortAlias)?.getRobot() ?? null;
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchOrder = async (orderId: number, robot: Robot, token: string): Promise<Order | null> => {
|
|
||||||
if (!this.enabled) return null;
|
|
||||||
if (!token) return null;
|
|
||||||
|
|
||||||
const authHeaders = robot.getAuthHeaders();
|
|
||||||
if (!authHeaders) return null;
|
|
||||||
|
|
||||||
return await apiClient
|
|
||||||
.get(this.url, `${this.basePath}/api/order/?order_id=${orderId}`, authHeaders)
|
|
||||||
.then((data) => {
|
|
||||||
const order: Order = {
|
|
||||||
...defaultOrder,
|
|
||||||
...data,
|
|
||||||
shortAlias: this.shortAlias,
|
|
||||||
};
|
|
||||||
return order;
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
console.log(e);
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchReward = async (
|
|
||||||
signedInvoice: string,
|
|
||||||
garage: Garage,
|
|
||||||
index: string,
|
|
||||||
): Promise<null | {
|
|
||||||
bad_invoice?: string;
|
|
||||||
successful_withdrawal?: boolean;
|
|
||||||
}> => {
|
|
||||||
if (!this.enabled) return null;
|
|
||||||
|
|
||||||
const slot = garage.getSlot(index);
|
|
||||||
const robot = slot?.getRobot();
|
|
||||||
|
|
||||||
if (!slot?.token || !robot?.encPrivKey) return null;
|
|
||||||
|
|
||||||
const data = await apiClient.post(
|
|
||||||
this.url,
|
|
||||||
`${this.basePath}/api/reward/`,
|
|
||||||
{
|
|
||||||
invoice: signedInvoice,
|
|
||||||
},
|
|
||||||
{ tokenSHA256: robot.tokenSHA256 },
|
|
||||||
);
|
|
||||||
garage.updateRobot(slot?.token, this.shortAlias, {
|
|
||||||
earnedRewards: data?.successful_withdrawal === true ? 0 : robot.earnedRewards,
|
|
||||||
});
|
|
||||||
|
|
||||||
return data ?? {};
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchStealth = async (wantsStealth: boolean, garage: Garage, index: string): Promise<null> => {
|
|
||||||
if (!this.enabled) return null;
|
|
||||||
|
|
||||||
const slot = garage.getSlot(index);
|
|
||||||
const robot = slot?.getRobot();
|
|
||||||
|
|
||||||
if (!(slot?.token != null) || !(robot?.encPrivKey != null)) return null;
|
|
||||||
|
|
||||||
await apiClient.post(
|
|
||||||
this.url,
|
|
||||||
`${this.basePath}/api/stealth/`,
|
|
||||||
{ wantsStealth },
|
|
||||||
{ tokenSHA256: robot.tokenSHA256 },
|
|
||||||
);
|
|
||||||
|
|
||||||
garage.updateRobot(slot?.token, this.shortAlias, {
|
|
||||||
stealthInvoices: wantsStealth,
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Coordinator;
|
export default Coordinator;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Coordinator,
|
Coordinator,
|
||||||
type Exchange,
|
type Exchange,
|
||||||
type Garage,
|
|
||||||
type Origin,
|
type Origin,
|
||||||
type PublicOrder,
|
type PublicOrder,
|
||||||
type Settings,
|
type Settings,
|
||||||
@ -13,7 +12,7 @@ import { getHost } from '../utils';
|
|||||||
import { coordinatorDefaultValues } from './Coordinator.model';
|
import { coordinatorDefaultValues } from './Coordinator.model';
|
||||||
import { updateExchangeInfo } from './Exchange.model';
|
import { updateExchangeInfo } from './Exchange.model';
|
||||||
|
|
||||||
type FederationHooks = 'onCoordinatorUpdate' | 'onFederationUpdate';
|
type FederationHooks = 'onFederationUpdate';
|
||||||
|
|
||||||
export class Federation {
|
export class Federation {
|
||||||
constructor(origin: Origin, settings: Settings, hostUrl: string) {
|
constructor(origin: Origin, settings: Settings, hostUrl: string) {
|
||||||
@ -36,7 +35,6 @@ export class Federation {
|
|||||||
};
|
};
|
||||||
this.book = [];
|
this.book = [];
|
||||||
this.hooks = {
|
this.hooks = {
|
||||||
onCoordinatorUpdate: [],
|
|
||||||
onFederationUpdate: [],
|
onFederationUpdate: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -97,7 +95,6 @@ export class Federation {
|
|||||||
this.book = Object.values(this.coordinators).reduce<PublicOrder[]>((array, coordinator) => {
|
this.book = Object.values(this.coordinators).reduce<PublicOrder[]>((array, coordinator) => {
|
||||||
return [...array, ...coordinator.book];
|
return [...array, ...coordinator.book];
|
||||||
}, []);
|
}, []);
|
||||||
this.triggerHook('onCoordinatorUpdate');
|
|
||||||
this.exchange.loadingCoordinators =
|
this.exchange.loadingCoordinators =
|
||||||
this.exchange.loadingCoordinators < 1 ? 0 : this.exchange.loadingCoordinators - 1;
|
this.exchange.loadingCoordinators < 1 ? 0 : this.exchange.loadingCoordinators - 1;
|
||||||
this.loading = this.exchange.loadingCoordinators > 0;
|
this.loading = this.exchange.loadingCoordinators > 0;
|
||||||
@ -140,11 +137,12 @@ export class Federation {
|
|||||||
updateBook = async (): Promise<void> => {
|
updateBook = async (): Promise<void> => {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.book = [];
|
this.book = [];
|
||||||
this.triggerHook('onCoordinatorUpdate');
|
this.triggerHook('onFederationUpdate');
|
||||||
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
|
this.exchange.loadingCoordinators = Object.keys(this.coordinators).length;
|
||||||
for (const coor of Object.values(this.coordinators)) {
|
for (const coor of Object.values(this.coordinators)) {
|
||||||
void coor.updateBook(() => {
|
void coor.updateBook(() => {
|
||||||
this.onCoordinatorSaved();
|
this.onCoordinatorSaved();
|
||||||
|
this.triggerHook('onFederationUpdate');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -154,13 +152,6 @@ export class Federation {
|
|||||||
this.triggerHook('onFederationUpdate');
|
this.triggerHook('onFederationUpdate');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetchs
|
|
||||||
fetchRobot = async (garage: Garage, token: string): Promise<void> => {
|
|
||||||
Object.values(this.coordinators).forEach((coor) => {
|
|
||||||
void coor.fetchRobot(garage, token);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Coordinators
|
// Coordinators
|
||||||
getCoordinator = (shortAlias: string): Coordinator => {
|
getCoordinator = (shortAlias: string): Coordinator => {
|
||||||
return this.coordinators[shortAlias];
|
return this.coordinators[shortAlias];
|
||||||
@ -169,13 +160,13 @@ export class Federation {
|
|||||||
disableCoordinator = (shortAlias: string): void => {
|
disableCoordinator = (shortAlias: string): void => {
|
||||||
this.coordinators[shortAlias].disable();
|
this.coordinators[shortAlias].disable();
|
||||||
this.updateEnabledCoordinators();
|
this.updateEnabledCoordinators();
|
||||||
this.triggerHook('onCoordinatorUpdate');
|
this.triggerHook('onFederationUpdate');
|
||||||
};
|
};
|
||||||
|
|
||||||
enableCoordinator = (shortAlias: string): void => {
|
enableCoordinator = (shortAlias: string): void => {
|
||||||
this.coordinators[shortAlias].enable(() => {
|
this.coordinators[shortAlias].enable(() => {
|
||||||
this.updateEnabledCoordinators();
|
this.updateEnabledCoordinators();
|
||||||
this.triggerHook('onCoordinatorUpdate');
|
this.triggerHook('onFederationUpdate');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { type Coordinator, type Order } from '.';
|
import { type Federation, Order } from '.';
|
||||||
import { systemClient } from '../services/System';
|
import { systemClient } from '../services/System';
|
||||||
import { saveAsJson } from '../utils';
|
import { saveAsJson } from '../utils';
|
||||||
import Slot from './Slot.model';
|
import Slot from './Slot.model';
|
||||||
|
|
||||||
type GarageHooks = 'onRobotUpdate' | 'onOrderUpdate';
|
type GarageHooks = 'onSlotUpdate';
|
||||||
|
|
||||||
class Garage {
|
class Garage {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -11,8 +11,7 @@ class Garage {
|
|||||||
this.currentSlot = null;
|
this.currentSlot = null;
|
||||||
|
|
||||||
this.hooks = {
|
this.hooks = {
|
||||||
onRobotUpdate: [],
|
onSlotUpdate: [],
|
||||||
onOrderUpdate: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.loadSlots();
|
this.loadSlots();
|
||||||
@ -29,6 +28,7 @@ class Garage {
|
|||||||
};
|
};
|
||||||
|
|
||||||
triggerHook = (hookName: GarageHooks): void => {
|
triggerHook = (hookName: GarageHooks): void => {
|
||||||
|
this.save();
|
||||||
this.hooks[hookName]?.forEach((fn) => {
|
this.hooks[hookName]?.forEach((fn) => {
|
||||||
fn();
|
fn();
|
||||||
});
|
});
|
||||||
@ -47,8 +47,7 @@ class Garage {
|
|||||||
this.slots = {};
|
this.slots = {};
|
||||||
this.currentSlot = null;
|
this.currentSlot = null;
|
||||||
systemClient.deleteItem('garage_slots');
|
systemClient.deleteItem('garage_slots');
|
||||||
this.triggerHook('onRobotUpdate');
|
this.triggerHook('onSlotUpdate');
|
||||||
this.triggerHook('onOrderUpdate');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
loadSlots = (): void => {
|
loadSlots = (): void => {
|
||||||
@ -64,26 +63,23 @@ class Garage {
|
|||||||
Object.keys(rawSlot.robots),
|
Object.keys(rawSlot.robots),
|
||||||
{},
|
{},
|
||||||
() => {
|
() => {
|
||||||
this.triggerHook('onRobotUpdate');
|
this.triggerHook('onSlotUpdate');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Object.keys(rawSlot.robots).forEach((shortAlias) => {
|
this.slots[rawSlot.token].updateSlotFromOrder(new Order(rawSlot.lastOrder));
|
||||||
const rawRobot = rawSlot.robots[shortAlias];
|
this.slots[rawSlot.token].updateSlotFromOrder(new Order(rawSlot.activeOrder));
|
||||||
this.updateRobot(rawSlot.token, shortAlias, rawRobot);
|
|
||||||
});
|
|
||||||
this.currentSlot = rawSlot?.token;
|
this.currentSlot = rawSlot?.token;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
console.log('Robot Garage was loaded from local storage');
|
console.log('Robot Garage was loaded from local storage');
|
||||||
this.triggerHook('onRobotUpdate');
|
this.triggerHook('onSlotUpdate');
|
||||||
this.triggerHook('onOrderUpdate');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Slots
|
// Slots
|
||||||
getSlot: (token?: string) => Slot | null = (token) => {
|
getSlot: (token?: string) => Slot | null = (token) => {
|
||||||
const currentToken = token ?? this.currentSlot;
|
const currentToken = token ?? this.currentSlot;
|
||||||
return currentToken ? this.slots[currentToken] ?? null : null;
|
return currentToken ? (this.slots[currentToken] ?? null) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteSlot: (token?: string) => void = (token) => {
|
deleteSlot: (token?: string) => void = (token) => {
|
||||||
@ -92,8 +88,7 @@ class Garage {
|
|||||||
Reflect.deleteProperty(this.slots, targetIndex);
|
Reflect.deleteProperty(this.slots, targetIndex);
|
||||||
this.currentSlot = null;
|
this.currentSlot = null;
|
||||||
this.save();
|
this.save();
|
||||||
this.triggerHook('onRobotUpdate');
|
this.triggerHook('onSlotUpdate');
|
||||||
this.triggerHook('onOrderUpdate');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -105,7 +100,7 @@ class Garage {
|
|||||||
if (attributes) {
|
if (attributes) {
|
||||||
if (attributes.copiedToken !== undefined) slot?.setCopiedToken(attributes.copiedToken);
|
if (attributes.copiedToken !== undefined) slot?.setCopiedToken(attributes.copiedToken);
|
||||||
this.save();
|
this.save();
|
||||||
this.triggerHook('onRobotUpdate');
|
this.triggerHook('onSlotUpdate');
|
||||||
}
|
}
|
||||||
return slot;
|
return slot;
|
||||||
};
|
};
|
||||||
@ -113,7 +108,7 @@ class Garage {
|
|||||||
setCurrentSlot: (currentSlot: string) => void = (currentSlot) => {
|
setCurrentSlot: (currentSlot: string) => void = (currentSlot) => {
|
||||||
this.currentSlot = currentSlot;
|
this.currentSlot = currentSlot;
|
||||||
this.save();
|
this.save();
|
||||||
this.triggerHook('onRobotUpdate');
|
this.triggerHook('onSlotUpdate');
|
||||||
};
|
};
|
||||||
|
|
||||||
getSlotByOrder: (coordinator: string, orderID: number) => Slot | null = (
|
getSlotByOrder: (coordinator: string, orderID: number) => Slot | null = (
|
||||||
@ -123,7 +118,7 @@ class Garage {
|
|||||||
return (
|
return (
|
||||||
Object.values(this.slots).find((slot) => {
|
Object.values(this.slots).find((slot) => {
|
||||||
const robot = slot.getRobot(coordinator);
|
const robot = slot.getRobot(coordinator);
|
||||||
return slot.activeShortAlias === coordinator && robot?.activeOrderId === orderID;
|
return slot.activeOrder?.shortAlias === coordinator && robot?.activeOrderId === orderID;
|
||||||
}) ?? null
|
}) ?? null
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -138,55 +133,29 @@ class Garage {
|
|||||||
|
|
||||||
if (this.getSlot(token) === null) {
|
if (this.getSlot(token) === null) {
|
||||||
this.slots[token] = new Slot(token, shortAliases, attributes, () => {
|
this.slots[token] = new Slot(token, shortAliases, attributes, () => {
|
||||||
this.triggerHook('onRobotUpdate');
|
this.triggerHook('onSlotUpdate');
|
||||||
});
|
});
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateRobot: (token: string, shortAlias: string, attributes: Record<any, any>) => void = (
|
fetchRobot = async (federation: Federation, token: string): Promise<void> => {
|
||||||
token,
|
|
||||||
shortAlias,
|
|
||||||
attributes,
|
|
||||||
) => {
|
|
||||||
if (!token || !shortAlias) return;
|
|
||||||
|
|
||||||
const slot = this.getSlot(token);
|
const slot = this.getSlot(token);
|
||||||
|
|
||||||
if (slot != null) {
|
if (slot != null) {
|
||||||
slot.updateRobot(shortAlias, { token, ...attributes });
|
await slot.fetchRobot(federation);
|
||||||
this.save();
|
this.save();
|
||||||
this.triggerHook('onRobotUpdate');
|
this.triggerHook('onSlotUpdate');
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Orders
|
|
||||||
updateOrder: (order: Order | null) => void = (order) => {
|
|
||||||
const slot = this.getSlot();
|
|
||||||
if (slot != null) {
|
|
||||||
if (order !== null) {
|
|
||||||
const updatedOrder = slot.order ?? null;
|
|
||||||
if (updatedOrder !== null && updatedOrder.id === order.id) {
|
|
||||||
Object.assign(updatedOrder, order);
|
|
||||||
slot.order = updatedOrder;
|
|
||||||
} else {
|
|
||||||
slot.order = order;
|
|
||||||
}
|
|
||||||
if (slot.order?.is_participant) {
|
|
||||||
slot.activeShortAlias = order.shortAlias;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
slot.order = null;
|
|
||||||
}
|
|
||||||
this.save();
|
|
||||||
this.triggerHook('onOrderUpdate');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Coordinators
|
// Coordinators
|
||||||
syncCoordinator: (coordinator: Coordinator) => void = (coordinator) => {
|
syncCoordinator: (federation: Federation, shortAlias: string) => void = (
|
||||||
|
federation,
|
||||||
|
shortAlias,
|
||||||
|
) => {
|
||||||
Object.values(this.slots).forEach((slot) => {
|
Object.values(this.slots).forEach((slot) => {
|
||||||
slot.syncCoordinator(coordinator, this);
|
slot.syncCoordinator(federation, shortAlias);
|
||||||
});
|
});
|
||||||
this.save();
|
this.save();
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import defaultFederation from '../../static/federation.json';
|
||||||
|
|
||||||
export interface Maker {
|
export interface Maker {
|
||||||
advancedOptions: boolean;
|
advancedOptions: boolean;
|
||||||
coordinator: string;
|
coordinator: string;
|
||||||
@ -23,7 +25,10 @@ export interface Maker {
|
|||||||
|
|
||||||
export const defaultMaker: Maker = {
|
export const defaultMaker: Maker = {
|
||||||
advancedOptions: false,
|
advancedOptions: false,
|
||||||
coordinator: 'exp',
|
coordinator:
|
||||||
|
Object.keys(defaultFederation)[
|
||||||
|
Math.floor(Math.random() * Object.keys(defaultFederation).length)
|
||||||
|
] ?? '',
|
||||||
isExplicit: false,
|
isExplicit: false,
|
||||||
amount: '',
|
amount: '',
|
||||||
paymentMethods: [],
|
paymentMethods: [],
|
||||||
@ -40,6 +45,8 @@ export const defaultMaker: Maker = {
|
|||||||
maxAmount: '',
|
maxAmount: '',
|
||||||
badPremiumText: '',
|
badPremiumText: '',
|
||||||
badSatoshisText: '',
|
badSatoshisText: '',
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Maker;
|
export default Maker;
|
||||||
|
@ -1,3 +1,28 @@
|
|||||||
|
import { apiClient } from '../services/api';
|
||||||
|
import type Federation from './Federation.model';
|
||||||
|
import type Slot from './Slot.model';
|
||||||
|
|
||||||
|
export interface SubmitActionProps {
|
||||||
|
action:
|
||||||
|
| 'cancel'
|
||||||
|
| 'dispute'
|
||||||
|
| 'pause'
|
||||||
|
| 'confirm'
|
||||||
|
| 'undo_confirm'
|
||||||
|
| 'update_invoice'
|
||||||
|
| 'update_address'
|
||||||
|
| 'submit_statement'
|
||||||
|
| 'rate_platform'
|
||||||
|
| 'take';
|
||||||
|
invoice?: string;
|
||||||
|
routing_budget_ppm?: number;
|
||||||
|
address?: string;
|
||||||
|
mining_fee_rate?: number;
|
||||||
|
statement?: string;
|
||||||
|
rating?: number;
|
||||||
|
amount?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TradeRobotSummary {
|
export interface TradeRobotSummary {
|
||||||
is_buyer: boolean;
|
is_buyer: boolean;
|
||||||
sent_fiat: number;
|
sent_fiat: number;
|
||||||
@ -24,168 +49,82 @@ export interface TradeCoordinatorSummary {
|
|||||||
trade_revenue_sats: number;
|
trade_revenue_sats: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Order {
|
class Order {
|
||||||
id: number;
|
constructor(attributes: object) {
|
||||||
status: number;
|
Object.assign(this, attributes);
|
||||||
created_at: Date;
|
}
|
||||||
expires_at: Date;
|
|
||||||
type: number;
|
|
||||||
currency: number;
|
|
||||||
amount: number;
|
|
||||||
has_range: boolean;
|
|
||||||
min_amount: number;
|
|
||||||
max_amount: number;
|
|
||||||
payment_method: string;
|
|
||||||
is_explicit: boolean;
|
|
||||||
premium: number;
|
|
||||||
satoshis: number;
|
|
||||||
maker: number;
|
|
||||||
taker: number;
|
|
||||||
escrow_duration: number;
|
|
||||||
total_secs_exp: number;
|
|
||||||
penalty: Date | undefined;
|
|
||||||
is_maker: boolean;
|
|
||||||
is_taker: boolean;
|
|
||||||
is_participant: boolean;
|
|
||||||
maker_status: 'Active' | 'Seen recently' | 'Inactive';
|
|
||||||
taker_status: 'Active' | 'Seen recently' | 'Inactive';
|
|
||||||
price_now: number | undefined;
|
|
||||||
satoshis_now: number;
|
|
||||||
latitude: number;
|
|
||||||
longitude: number;
|
|
||||||
premium_now: number | undefined;
|
|
||||||
premium_percentile: number;
|
|
||||||
num_similar_orders: number;
|
|
||||||
tg_enabled: boolean; // deprecated
|
|
||||||
tg_token: string;
|
|
||||||
tg_bot_name: string;
|
|
||||||
is_buyer: boolean;
|
|
||||||
is_seller: boolean;
|
|
||||||
maker_nick: string;
|
|
||||||
maker_hash_id: string;
|
|
||||||
taker_nick: string;
|
|
||||||
taker_hash_id: string;
|
|
||||||
status_message: string;
|
|
||||||
is_fiat_sent: boolean;
|
|
||||||
is_disputed: boolean;
|
|
||||||
ur_nick: string;
|
|
||||||
maker_locked: boolean;
|
|
||||||
taker_locked: boolean;
|
|
||||||
escrow_locked: boolean;
|
|
||||||
trade_satoshis: number;
|
|
||||||
bond_invoice: string;
|
|
||||||
bond_satoshis: number;
|
|
||||||
escrow_invoice: string;
|
|
||||||
escrow_satoshis: number;
|
|
||||||
invoice_amount: number;
|
|
||||||
swap_allowed: boolean;
|
|
||||||
swap_failure_reason: string;
|
|
||||||
suggested_mining_fee_rate: number;
|
|
||||||
swap_fee_rate: number;
|
|
||||||
pending_cancel: boolean;
|
|
||||||
asked_for_cancel: boolean;
|
|
||||||
statement_submitted: boolean;
|
|
||||||
retries: number;
|
|
||||||
next_retry_time: Date;
|
|
||||||
failure_reason: string;
|
|
||||||
invoice_expired: boolean;
|
|
||||||
public_duration: number;
|
|
||||||
bond_size: string;
|
|
||||||
trade_fee_percent: number;
|
|
||||||
bond_size_sats: number;
|
|
||||||
bond_size_percent: number;
|
|
||||||
chat_last_index: number;
|
|
||||||
maker_summary: TradeRobotSummary;
|
|
||||||
taker_summary: TradeRobotSummary;
|
|
||||||
platform_summary: TradeCoordinatorSummary;
|
|
||||||
expiry_reason: number;
|
|
||||||
expiry_message: string;
|
|
||||||
num_satoshis: number;
|
|
||||||
sent_satoshis: number;
|
|
||||||
txid: string;
|
|
||||||
tx_queued: boolean;
|
|
||||||
address: string;
|
|
||||||
network: 'mainnet' | 'testnet';
|
|
||||||
shortAlias: string;
|
|
||||||
bad_request?: string;
|
|
||||||
bad_address?: string;
|
|
||||||
bad_invoice?: string;
|
|
||||||
bad_statement?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const defaultOrder: Order = {
|
id: number = 0;
|
||||||
shortAlias: '',
|
status: number = 0;
|
||||||
id: 0,
|
created_at: Date = new Date();
|
||||||
status: 0,
|
expires_at: Date = new Date();
|
||||||
created_at: new Date(),
|
type: number = 0;
|
||||||
expires_at: new Date(),
|
currency: number = 0;
|
||||||
type: 0,
|
amount: number = 0;
|
||||||
currency: 0,
|
has_range: boolean = false;
|
||||||
amount: 0,
|
min_amount: number = 0;
|
||||||
has_range: false,
|
max_amount: number = 0;
|
||||||
min_amount: 0,
|
payment_method: string = '';
|
||||||
max_amount: 0,
|
is_explicit: boolean = false;
|
||||||
payment_method: '',
|
premium: number = 0;
|
||||||
is_explicit: false,
|
satoshis: number = 0;
|
||||||
premium: 0,
|
maker: number = 0;
|
||||||
satoshis: 0,
|
taker: number = 0;
|
||||||
maker: 0,
|
escrow_duration: number = 0;
|
||||||
taker: 0,
|
total_secs_exp: number = 0;
|
||||||
escrow_duration: 0,
|
penalty: Date | undefined = undefined;
|
||||||
total_secs_exp: 0,
|
is_maker: boolean = false;
|
||||||
penalty: undefined,
|
is_taker: boolean = false;
|
||||||
is_maker: false,
|
is_participant: boolean = false;
|
||||||
is_taker: false,
|
maker_status: 'Active' | 'Seen recently' | 'Inactive' = 'Active';
|
||||||
is_participant: false,
|
taker_status: 'Active' | 'Seen recently' | 'Inactive' = 'Active';
|
||||||
maker_status: 'Active',
|
price_now: number | undefined = undefined;
|
||||||
taker_status: 'Active',
|
satoshis_now: number = 0;
|
||||||
price_now: undefined,
|
latitude: number = 0;
|
||||||
satoshis_now: 0,
|
longitude: number = 0;
|
||||||
latitude: 0,
|
premium_now: number | undefined = undefined;
|
||||||
longitude: 0,
|
premium_percentile: number = 0;
|
||||||
premium_now: undefined,
|
num_similar_orders: number = 0;
|
||||||
premium_percentile: 0,
|
tg_enabled: boolean = false; // deprecated
|
||||||
num_similar_orders: 0,
|
tg_token: string = '';
|
||||||
tg_enabled: false,
|
tg_bot_name: string = '';
|
||||||
tg_token: '',
|
is_buyer: boolean = false;
|
||||||
tg_bot_name: '',
|
is_seller: boolean = false;
|
||||||
is_buyer: false,
|
maker_nick: string = '';
|
||||||
is_seller: false,
|
maker_hash_id: string = '';
|
||||||
maker_nick: '',
|
taker_nick: string = '';
|
||||||
maker_hash_id: '',
|
taker_hash_id: string = '';
|
||||||
taker_nick: '',
|
status_message: string = '';
|
||||||
taker_hash_id: '',
|
is_fiat_sent: boolean = false;
|
||||||
status_message: '',
|
is_disputed: boolean = false;
|
||||||
is_fiat_sent: false,
|
ur_nick: string = '';
|
||||||
is_disputed: false,
|
maker_locked: boolean = false;
|
||||||
ur_nick: '',
|
taker_locked: boolean = false;
|
||||||
maker_locked: false,
|
escrow_locked: boolean = false;
|
||||||
taker_locked: false,
|
trade_satoshis: number = 0;
|
||||||
escrow_locked: false,
|
bond_invoice: string = '';
|
||||||
trade_satoshis: 0,
|
bond_satoshis: number = 0;
|
||||||
bond_invoice: '',
|
escrow_invoice: string = '';
|
||||||
bond_satoshis: 0,
|
escrow_satoshis: number = 0;
|
||||||
escrow_invoice: '',
|
invoice_amount: number = 0;
|
||||||
escrow_satoshis: 0,
|
swap_allowed: boolean = false;
|
||||||
invoice_amount: 0,
|
swap_failure_reason: string = '';
|
||||||
swap_allowed: false,
|
suggested_mining_fee_rate: number = 0;
|
||||||
swap_failure_reason: '',
|
swap_fee_rate: number = 0;
|
||||||
suggested_mining_fee_rate: 0,
|
pending_cancel: boolean = false;
|
||||||
swap_fee_rate: 0,
|
asked_for_cancel: boolean = false;
|
||||||
pending_cancel: false,
|
statement_submitted: boolean = false;
|
||||||
asked_for_cancel: false,
|
retries: number = 0;
|
||||||
statement_submitted: false,
|
next_retry_time: Date = new Date();
|
||||||
retries: 0,
|
failure_reason: string = '';
|
||||||
next_retry_time: new Date(),
|
invoice_expired: boolean = false;
|
||||||
failure_reason: '',
|
public_duration: number = 0;
|
||||||
invoice_expired: false,
|
bond_size: string = '';
|
||||||
public_duration: 0,
|
trade_fee_percent: number = 0;
|
||||||
bond_size: '',
|
bond_size_sats: number = 0;
|
||||||
trade_fee_percent: 0,
|
bond_size_percent: number = 0;
|
||||||
bond_size_sats: 0,
|
chat_last_index: number = 0;
|
||||||
bond_size_percent: 0,
|
maker_summary: TradeRobotSummary = {
|
||||||
chat_last_index: 0,
|
|
||||||
maker_summary: {
|
|
||||||
is_buyer: false,
|
is_buyer: false,
|
||||||
sent_fiat: 0,
|
sent_fiat: 0,
|
||||||
received_sats: 0,
|
received_sats: 0,
|
||||||
@ -197,8 +136,9 @@ export const defaultOrder: Order = {
|
|||||||
sent_sats: 0,
|
sent_sats: 0,
|
||||||
received_fiat: 0,
|
received_fiat: 0,
|
||||||
trade_fee_sats: 0,
|
trade_fee_sats: 0,
|
||||||
},
|
};
|
||||||
taker_summary: {
|
|
||||||
|
taker_summary: TradeRobotSummary = {
|
||||||
is_buyer: false,
|
is_buyer: false,
|
||||||
sent_fiat: 0,
|
sent_fiat: 0,
|
||||||
received_sats: 0,
|
received_sats: 0,
|
||||||
@ -210,22 +150,118 @@ export const defaultOrder: Order = {
|
|||||||
sent_sats: 0,
|
sent_sats: 0,
|
||||||
received_fiat: 0,
|
received_fiat: 0,
|
||||||
trade_fee_sats: 0,
|
trade_fee_sats: 0,
|
||||||
},
|
};
|
||||||
platform_summary: {
|
|
||||||
|
platform_summary: TradeCoordinatorSummary = {
|
||||||
contract_timestamp: new Date(),
|
contract_timestamp: new Date(),
|
||||||
contract_total_time: 0,
|
contract_total_time: 0,
|
||||||
contract_exchange_rate: 0,
|
contract_exchange_rate: 0,
|
||||||
routing_budget_sats: 0,
|
routing_budget_sats: 0,
|
||||||
trade_revenue_sats: 0,
|
trade_revenue_sats: 0,
|
||||||
},
|
};
|
||||||
expiry_reason: 0,
|
|
||||||
expiry_message: '',
|
expiry_reason: number = 0;
|
||||||
num_satoshis: 0,
|
expiry_message: string = '';
|
||||||
sent_satoshis: 0,
|
num_satoshis: number = 0;
|
||||||
txid: '',
|
sent_satoshis: number = 0;
|
||||||
tx_queued: false,
|
txid: string = '';
|
||||||
address: '',
|
tx_queued: boolean = false;
|
||||||
network: 'mainnet',
|
address: string = '';
|
||||||
};
|
network: 'mainnet' | 'testnet' = 'mainnet';
|
||||||
|
shortAlias: string = '';
|
||||||
|
bad_request?: string = '';
|
||||||
|
bad_address?: string = '';
|
||||||
|
bad_invoice?: string = '';
|
||||||
|
bad_statement?: string = '';
|
||||||
|
|
||||||
|
update = (attributes: Record<string, any>): Order => {
|
||||||
|
Object.assign(this, attributes);
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
make: (federation: Federation, slot: Slot) => Promise<this> = async (federation, slot) => {
|
||||||
|
const body = {
|
||||||
|
type: this.type,
|
||||||
|
currency: this.currency,
|
||||||
|
amount: this.has_range ? null : this.amount,
|
||||||
|
has_range: this.has_range,
|
||||||
|
min_amount: this.min_amount,
|
||||||
|
max_amount: this.max_amount,
|
||||||
|
payment_method: this.payment_method,
|
||||||
|
is_explicit: this.is_explicit,
|
||||||
|
premium: this.is_explicit ? null : this.premium,
|
||||||
|
satoshis: this.is_explicit ? this.satoshis : null,
|
||||||
|
public_duration: this.public_duration,
|
||||||
|
escrow_duration: this.escrow_duration,
|
||||||
|
bond_size: this.bond_size,
|
||||||
|
latitude: this.latitude,
|
||||||
|
longitude: this.longitude,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (slot) {
|
||||||
|
const coordinator = federation.getCoordinator(this.shortAlias);
|
||||||
|
const { basePath, url } = coordinator;
|
||||||
|
const data = await apiClient
|
||||||
|
.post(url + basePath, '/api/make/', body, {
|
||||||
|
tokenSHA256: slot?.getRobot()?.tokenSHA256 ?? '',
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
if (data) this.update(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
take: (federation: Federation, slot: Slot, takeAmount: string) => Promise<this> = async (
|
||||||
|
federation,
|
||||||
|
slot,
|
||||||
|
takeAmount,
|
||||||
|
) => {
|
||||||
|
return this.submitAction(federation, slot, {
|
||||||
|
action: 'take',
|
||||||
|
amount: this?.currency === 1000 ? Number(takeAmount) / 100000000 : Number(takeAmount),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
submitAction: (federation: Federation, slot: Slot, action: SubmitActionProps) => Promise<this> =
|
||||||
|
async (federation, slot, action) => {
|
||||||
|
if (this.id < 1) return this;
|
||||||
|
|
||||||
|
if (slot) {
|
||||||
|
const coordinator = federation.getCoordinator(this.shortAlias);
|
||||||
|
const { basePath, url } = coordinator;
|
||||||
|
const data = await apiClient
|
||||||
|
.post(url + basePath, `/api/order/?order_id=${Number(this.id)}`, action, {
|
||||||
|
tokenSHA256: slot?.getRobot()?.tokenSHA256 ?? '',
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
if (data) this.update(data);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
fecth: (federation: Federation, slot: Slot) => Promise<this> = async (federation, slot) => {
|
||||||
|
if (this.id < 1) return this;
|
||||||
|
if (!slot) return this;
|
||||||
|
|
||||||
|
const coordinator = federation.getCoordinator(this.shortAlias);
|
||||||
|
const authHeaders = slot.getRobot()?.getAuthHeaders();
|
||||||
|
if (!authHeaders) return this;
|
||||||
|
const { basePath, url } = coordinator;
|
||||||
|
const data = await apiClient
|
||||||
|
.get(url + basePath, `/api/order/?order_id=${this.id}`, authHeaders)
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (data) this.update(data);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default Order;
|
export default Order;
|
||||||
|
@ -1,29 +1,13 @@
|
|||||||
import { sha256 } from 'js-sha256';
|
import { apiClient } from '../services/api';
|
||||||
import { hexToBase91 } from '../utils';
|
import type Federation from './Federation.model';
|
||||||
|
import { type AuthHeaders } from './Slot.model';
|
||||||
interface AuthHeaders {
|
|
||||||
tokenSHA256: string;
|
|
||||||
keys: {
|
|
||||||
pubKey: string;
|
|
||||||
encPrivKey: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
class Robot {
|
class Robot {
|
||||||
constructor(attributes?: Record<any, any>) {
|
constructor(attributes?: Record<any, any>) {
|
||||||
if (attributes != null) {
|
Object.assign(this, attributes);
|
||||||
this.token = attributes?.token ?? undefined;
|
|
||||||
this.tokenSHA256 =
|
|
||||||
attributes?.tokenSHA256 ?? (this.token != null ? hexToBase91(sha256(this.token)) : '');
|
|
||||||
this.pubKey = attributes?.pubKey ?? undefined;
|
|
||||||
this.encPrivKey = attributes?.encPrivKey ?? undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public token?: string;
|
public token?: string;
|
||||||
public bitsEntropy?: number;
|
|
||||||
public shannonEntropy?: number;
|
|
||||||
public tokenSHA256: string = '';
|
|
||||||
public pubKey?: string;
|
public pubKey?: string;
|
||||||
public encPrivKey?: string;
|
public encPrivKey?: string;
|
||||||
public stealthInvoices: boolean = true;
|
public stealthInvoices: boolean = true;
|
||||||
@ -37,6 +21,10 @@ class Robot {
|
|||||||
public found: boolean = false;
|
public found: boolean = false;
|
||||||
public last_login: string = '';
|
public last_login: string = '';
|
||||||
public shortAlias: string = '';
|
public shortAlias: string = '';
|
||||||
|
public bitsEntropy?: number;
|
||||||
|
public shannonEntropy?: number;
|
||||||
|
public tokenSHA256: string = '';
|
||||||
|
public hasEnoughEntropy: boolean = false;
|
||||||
|
|
||||||
update = (attributes: Record<string, any>): void => {
|
update = (attributes: Record<string, any>): void => {
|
||||||
Object.assign(this, attributes);
|
Object.assign(this, attributes);
|
||||||
@ -55,6 +43,85 @@ class Robot {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fetch = async (federation: Federation): Promise<Robot | null> => {
|
||||||
|
const authHeaders = this.getAuthHeaders();
|
||||||
|
const coordinator = federation.getCoordinator(this.shortAlias);
|
||||||
|
|
||||||
|
if (!authHeaders || !coordinator || !this.hasEnoughEntropy) return null;
|
||||||
|
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
await apiClient
|
||||||
|
.get(coordinator.url, `${coordinator.basePath}/api/robot/`, authHeaders)
|
||||||
|
.then((data: any) => {
|
||||||
|
this.update({
|
||||||
|
nickname: data.nickname,
|
||||||
|
activeOrderId: data.active_order_id ?? null,
|
||||||
|
lastOrderId: data.last_order_id ?? null,
|
||||||
|
earnedRewards: data.earned_rewards ?? 0,
|
||||||
|
stealthInvoices: data.wants_stealth,
|
||||||
|
tgEnabled: data.tg_enabled,
|
||||||
|
tgBotName: data.tg_bot_name,
|
||||||
|
tgToken: data.tg_token,
|
||||||
|
found: data?.found,
|
||||||
|
last_login: data.last_login,
|
||||||
|
pubKey: data.public_key,
|
||||||
|
encPrivKey: data.encrypted_private_key,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
})
|
||||||
|
.finally(() => (this.loading = false));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchReward = async (
|
||||||
|
federation: Federation,
|
||||||
|
signedInvoice: string,
|
||||||
|
): Promise<null | {
|
||||||
|
bad_invoice?: string;
|
||||||
|
successful_withdrawal?: boolean;
|
||||||
|
}> => {
|
||||||
|
if (!federation) return null;
|
||||||
|
|
||||||
|
const coordinator = federation.getCoordinator(this.shortAlias);
|
||||||
|
const data = await apiClient
|
||||||
|
.post(
|
||||||
|
coordinator.url,
|
||||||
|
`${coordinator.basePath}/api/reward/`,
|
||||||
|
{
|
||||||
|
invoice: signedInvoice,
|
||||||
|
},
|
||||||
|
{ tokenSHA256: this.tokenSHA256 },
|
||||||
|
)
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
this.earnedRewards = data?.successful_withdrawal === true ? 0 : this.earnedRewards;
|
||||||
|
|
||||||
|
return data ?? {};
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchStealth = async (federation: Federation, wantsStealth: boolean): Promise<void> => {
|
||||||
|
if (!federation) return;
|
||||||
|
|
||||||
|
const coordinator = federation.getCoordinator(this.shortAlias);
|
||||||
|
await apiClient
|
||||||
|
.post(
|
||||||
|
coordinator.url,
|
||||||
|
`${coordinator.basePath}/api/stealth/`,
|
||||||
|
{ wantsStealth },
|
||||||
|
{ tokenSHA256: this.tokenSHA256 },
|
||||||
|
)
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.stealthInvoices = wantsStealth;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Robot;
|
export default Robot;
|
||||||
|
@ -1,88 +1,170 @@
|
|||||||
import { sha256 } from 'js-sha256';
|
import { sha256 } from 'js-sha256';
|
||||||
import { type Coordinator, type Garage, Robot, type Order } from '.';
|
import { Robot, Order, type Federation } from '.';
|
||||||
import { roboidentitiesClient } from '../services/Roboidentities/Web';
|
import { roboidentitiesClient } from '../services/Roboidentities/Web';
|
||||||
|
import { hexToBase91, validateTokenEntropy } from '../utils';
|
||||||
|
|
||||||
|
export interface AuthHeaders {
|
||||||
|
tokenSHA256: string;
|
||||||
|
keys: {
|
||||||
|
pubKey: string;
|
||||||
|
encPrivKey: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
class Slot {
|
class Slot {
|
||||||
constructor(
|
constructor(
|
||||||
token: string,
|
token: string,
|
||||||
shortAliases: string[],
|
shortAliases: string[],
|
||||||
robotAttributes: Record<any, any>,
|
robotAttributes: Record<any, any>,
|
||||||
onRobotUpdate: () => void,
|
onSlotUpdate: () => void,
|
||||||
) {
|
) {
|
||||||
|
this.onSlotUpdate = onSlotUpdate;
|
||||||
this.token = token;
|
this.token = token;
|
||||||
|
|
||||||
this.hashId = sha256(sha256(this.token));
|
this.hashId = sha256(sha256(this.token));
|
||||||
this.nickname = null;
|
this.nickname = null;
|
||||||
void roboidentitiesClient.generateRoboname(this.hashId).then((nickname) => {
|
void roboidentitiesClient.generateRoboname(this.hashId).then((nickname) => {
|
||||||
this.nickname = nickname;
|
this.nickname = nickname;
|
||||||
onRobotUpdate();
|
onSlotUpdate();
|
||||||
});
|
});
|
||||||
void roboidentitiesClient.generateRobohash(this.hashId, 'small');
|
void roboidentitiesClient.generateRobohash(this.hashId, 'small');
|
||||||
void roboidentitiesClient.generateRobohash(this.hashId, 'large');
|
void roboidentitiesClient.generateRobohash(this.hashId, 'large');
|
||||||
|
|
||||||
|
const { hasEnoughEntropy, bitsEntropy, shannonEntropy } = validateTokenEntropy(token);
|
||||||
|
const tokenSHA256 = hexToBase91(sha256(token));
|
||||||
|
|
||||||
this.robots = shortAliases.reduce((acc: Record<string, Robot>, shortAlias: string) => {
|
this.robots = shortAliases.reduce((acc: Record<string, Robot>, shortAlias: string) => {
|
||||||
acc[shortAlias] = new Robot(robotAttributes);
|
acc[shortAlias] = new Robot({
|
||||||
|
...robotAttributes,
|
||||||
|
shortAlias,
|
||||||
|
hasEnoughEntropy,
|
||||||
|
bitsEntropy,
|
||||||
|
shannonEntropy,
|
||||||
|
tokenSHA256,
|
||||||
|
pubKey: robotAttributes.pubKey,
|
||||||
|
encPrivKey: robotAttributes.encPrivKey,
|
||||||
|
});
|
||||||
|
this.updateSlotFromRobot(acc[shortAlias]);
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
this.order = null;
|
|
||||||
|
|
||||||
this.activeShortAlias = null;
|
|
||||||
this.lastShortAlias = null;
|
|
||||||
this.copiedToken = false;
|
this.copiedToken = false;
|
||||||
onRobotUpdate();
|
this.onSlotUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
token: string | null;
|
token: string | null;
|
||||||
hashId: string | null;
|
hashId: string | null;
|
||||||
nickname: string | null;
|
nickname: string | null;
|
||||||
robots: Record<string, Robot>;
|
robots: Record<string, Robot>;
|
||||||
order: Order | null;
|
activeOrder: Order | null = null;
|
||||||
activeShortAlias: string | null;
|
lastOrder: Order | null = null;
|
||||||
lastShortAlias: string | null;
|
|
||||||
copiedToken: boolean;
|
copiedToken: boolean;
|
||||||
|
|
||||||
|
onSlotUpdate: () => void;
|
||||||
|
|
||||||
setCopiedToken = (copied: boolean): void => {
|
setCopiedToken = (copied: boolean): void => {
|
||||||
this.copiedToken = copied;
|
this.copiedToken = copied;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Robots
|
||||||
getRobot = (shortAlias?: string): Robot | null => {
|
getRobot = (shortAlias?: string): Robot | null => {
|
||||||
if (shortAlias) {
|
if (shortAlias) {
|
||||||
return this.robots[shortAlias];
|
return this.robots[shortAlias];
|
||||||
} else if (this.activeShortAlias !== null && this.robots[this.activeShortAlias]) {
|
} else if (this.activeOrder?.id) {
|
||||||
return this.robots[this.activeShortAlias];
|
return this.robots[this.activeOrder.shortAlias];
|
||||||
} else if (this.lastShortAlias !== null && this.robots[this.lastShortAlias]) {
|
} else if (this.lastOrder?.id && this.robots[this.lastOrder.shortAlias]) {
|
||||||
return this.robots[this.lastShortAlias];
|
return this.robots[this.lastOrder.shortAlias];
|
||||||
} else if (Object.values(this.robots).length > 0) {
|
} else if (Object.values(this.robots).length > 0) {
|
||||||
return Object.values(this.robots)[0];
|
return Object.values(this.robots)[0];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
updateRobot = (shortAlias: string, attributes: Record<any, any>): Robot | null => {
|
fetchRobot = async (federation: Federation): Promise<void> => {
|
||||||
this.robots[shortAlias].update(attributes);
|
Object.values(this.robots).forEach((robot) => {
|
||||||
|
void robot.fetch(federation).then((robot) => {
|
||||||
if (attributes.lastOrderId) {
|
this.updateSlotFromRobot(robot);
|
||||||
this.lastShortAlias = shortAlias;
|
});
|
||||||
if (this.activeShortAlias === shortAlias) {
|
});
|
||||||
this.activeShortAlias = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (attributes.activeOrderId) {
|
|
||||||
this.activeShortAlias = attributes.shortAlias;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.robots[shortAlias];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
syncCoordinator: (coordinator: Coordinator, garage: Garage) => void = (coordinator, garage) => {
|
updateSlotFromRobot = (robot: Robot | null): void => {
|
||||||
|
if (robot?.lastOrderId && this.lastOrder?.id !== robot?.lastOrderId) {
|
||||||
|
this.lastOrder = new Order({ id: robot.lastOrderId, shortAlias: robot.shortAlias });
|
||||||
|
if (this.activeOrder?.id === robot.lastOrderId) {
|
||||||
|
this.lastOrder = this.activeOrder;
|
||||||
|
this.activeOrder = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (robot?.activeOrderId && this.activeOrder?.id !== robot.activeOrderId) {
|
||||||
|
this.activeOrder = new Order({
|
||||||
|
id: robot.activeOrderId,
|
||||||
|
shortAlias: robot.shortAlias,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.onSlotUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Orders
|
||||||
|
fetchActiveOrder = async (federation: Federation): Promise<void> => {
|
||||||
|
void this.activeOrder?.fecth(federation, this);
|
||||||
|
this.updateSlotFromOrder(this.activeOrder);
|
||||||
|
};
|
||||||
|
|
||||||
|
takeOrder = async (federation: Federation, order: Order, takeAmount: string): Promise<Order> => {
|
||||||
|
await order.take(federation, this, takeAmount);
|
||||||
|
this.updateSlotFromOrder(order);
|
||||||
|
return order;
|
||||||
|
};
|
||||||
|
|
||||||
|
makeOrder = async (federation: Federation, attributes: object): Promise<Order> => {
|
||||||
|
const order = new Order(attributes);
|
||||||
|
await order.make(federation, this);
|
||||||
|
this.lastOrder = this.activeOrder;
|
||||||
|
this.activeOrder = order;
|
||||||
|
this.onSlotUpdate();
|
||||||
|
return this.activeOrder;
|
||||||
|
};
|
||||||
|
|
||||||
|
updateSlotFromOrder: (newOrder: Order | null) => void = (newOrder) => {
|
||||||
|
if (newOrder) {
|
||||||
|
// FIXME: API responses with bad_request should include also order's status
|
||||||
|
if (newOrder?.bad_request?.includes('expired')) newOrder.status = 5;
|
||||||
|
if (newOrder?.bad_request?.includes('collaborativelly')) newOrder.status = 12;
|
||||||
|
if (
|
||||||
|
newOrder.id === this.activeOrder?.id &&
|
||||||
|
newOrder.shortAlias === this.activeOrder?.shortAlias
|
||||||
|
) {
|
||||||
|
this.activeOrder?.update(newOrder);
|
||||||
|
if (this.activeOrder?.bad_request) {
|
||||||
|
this.lastOrder = this.activeOrder;
|
||||||
|
this.activeOrder = null;
|
||||||
|
}
|
||||||
|
this.onSlotUpdate();
|
||||||
|
} else if (newOrder?.is_participant && this.lastOrder?.id !== newOrder.id) {
|
||||||
|
this.activeOrder = newOrder;
|
||||||
|
this.onSlotUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
syncCoordinator: (federation: Federation, shortAlias: string) => void = (
|
||||||
|
federation,
|
||||||
|
shortAlias,
|
||||||
|
) => {
|
||||||
const defaultRobot = this.getRobot();
|
const defaultRobot = this.getRobot();
|
||||||
if (defaultRobot?.token) {
|
if (defaultRobot?.token) {
|
||||||
this.robots[coordinator.shortAlias] = new Robot({
|
this.robots[shortAlias] = new Robot({
|
||||||
|
shortAlias,
|
||||||
|
hasEnoughEntropy: defaultRobot.hasEnoughEntropy,
|
||||||
|
bitsEntropy: defaultRobot.bitsEntropy,
|
||||||
|
shannonEntropy: defaultRobot.shannonEntropy,
|
||||||
token: defaultRobot.token,
|
token: defaultRobot.token,
|
||||||
pubKey: defaultRobot.pubKey,
|
pubKey: defaultRobot.pubKey,
|
||||||
encPrivKey: defaultRobot.encPrivKey,
|
encPrivKey: defaultRobot.encPrivKey,
|
||||||
});
|
});
|
||||||
void coordinator.fetchRobot(garage, defaultRobot.token);
|
void this.robots[shortAlias].fetch(federation);
|
||||||
|
this.updateSlotFromRobot(this.robots[shortAlias]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,14 +3,14 @@ import Garage from './Garage.model';
|
|||||||
import Settings from './Settings.default.basic';
|
import Settings from './Settings.default.basic';
|
||||||
import Coordinator from './Coordinator.model';
|
import Coordinator from './Coordinator.model';
|
||||||
import Federation from './Federation.model';
|
import Federation from './Federation.model';
|
||||||
export { Robot, Garage, Settings, Coordinator, Federation };
|
import Order from './Order.model';
|
||||||
|
import Slot from './Slot.model';
|
||||||
|
export { Robot, Garage, Settings, Coordinator, Federation, Order, Slot };
|
||||||
|
|
||||||
export type { LimitList, Limit, Limits } from './Limit.model';
|
export type { LimitList, Limit } from './Limit.model';
|
||||||
export type { Exchange } from './Exchange.model';
|
export type { Exchange } from './Exchange.model';
|
||||||
export type { Maker } from './Maker.model';
|
export type { Maker } from './Maker.model';
|
||||||
export type { Order } from './Order.model';
|
|
||||||
export type { Book, PublicOrder } from './Book.model';
|
export type { Book, PublicOrder } from './Book.model';
|
||||||
export type { Slot } from './Garage.model';
|
|
||||||
export type { Language } from './Settings.model';
|
export type { Language } from './Settings.model';
|
||||||
export type { Favorites } from './Favorites.model';
|
export type { Favorites } from './Favorites.model';
|
||||||
export type { Contact, Info, Version, Origin } from './Coordinator.model';
|
export type { Contact, Info, Version, Origin } from './Coordinator.model';
|
||||||
|
@ -14,20 +14,11 @@ interface FederationWidgetProps {
|
|||||||
onTouchEnd?: () => void;
|
onTouchEnd?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FederationWidget = React.forwardRef(function Component(
|
const FederationWidget = React.forwardRef(function Component({
|
||||||
{
|
|
||||||
layout,
|
layout,
|
||||||
gridCellSize,
|
gridCellSize,
|
||||||
style,
|
}: FederationWidgetProps) {
|
||||||
className,
|
const { federation, federationUpdatedAt } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
onMouseDown,
|
|
||||||
onMouseUp,
|
|
||||||
onTouchEnd,
|
|
||||||
}: FederationWidgetProps,
|
|
||||||
ref,
|
|
||||||
) {
|
|
||||||
const { federation, coordinatorUpdatedAt } =
|
|
||||||
useContext<UseFederationStoreType>(FederationContext);
|
|
||||||
|
|
||||||
return React.useMemo(() => {
|
return React.useMemo(() => {
|
||||||
return (
|
return (
|
||||||
@ -38,7 +29,7 @@ const FederationWidget = React.forwardRef(function Component(
|
|||||||
/>
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
}, [federation, coordinatorUpdatedAt]);
|
}, [federation, federationUpdatedAt]);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default FederationWidget;
|
export default FederationWidget;
|
||||||
|
@ -19,7 +19,7 @@ const MakerWidget = React.forwardRef(function Component(
|
|||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const { fav } = useContext<UseAppStoreType>(AppContext);
|
const { fav } = useContext<UseAppStoreType>(AppContext);
|
||||||
const { coordinatorUpdatedAt } = useContext<UseFederationStoreType>(FederationContext);
|
const { federationUpdatedAt } = useContext<UseFederationStoreType>(FederationContext);
|
||||||
const { maker } = useContext<UseGarageStoreType>(GarageContext);
|
const { maker } = useContext<UseGarageStoreType>(GarageContext);
|
||||||
return React.useMemo(() => {
|
return React.useMemo(() => {
|
||||||
return (
|
return (
|
||||||
@ -27,7 +27,7 @@ const MakerWidget = React.forwardRef(function Component(
|
|||||||
<MakerForm />
|
<MakerForm />
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
}, [maker, fav, coordinatorUpdatedAt]);
|
}, [maker, fav, federationUpdatedAt]);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default MakerWidget;
|
export default MakerWidget;
|
||||||
|
@ -1,47 +1,4 @@
|
|||||||
{
|
{
|
||||||
"exp": {
|
|
||||||
"longAlias": "Experimental",
|
|
||||||
"shortAlias": "exp",
|
|
||||||
"description": "RoboSats node for development and experimentation. This is the original RoboSats coordinator operated by the RoboSats devs since 2022.",
|
|
||||||
"motto": "Original Robohost. P2P FTW!",
|
|
||||||
"color": "#1976d2",
|
|
||||||
"established": "2022-03-01",
|
|
||||||
"contact": {
|
|
||||||
"email": "robosats@protonmail.com",
|
|
||||||
"telegram": "robosats",
|
|
||||||
"twitter": "robosats",
|
|
||||||
"reddit": "r/robosats",
|
|
||||||
"matrix": "#robosats:matrix.org",
|
|
||||||
"website": "https://learn.robosats.com",
|
|
||||||
"nostr": "npub1p2psats79rypr8lpnl9t5qdekfp700x660qsgw284xvq4s09lqrqqk3m82",
|
|
||||||
"pgp": "/static/federation/pgp/B4AB5F19113D4125DDF217739C4585B561315571.asc",
|
|
||||||
"fingerprint": "B4AB5F19113D4125DDF217739C4585B561315571"
|
|
||||||
},
|
|
||||||
"badges": {
|
|
||||||
"isFounder": true,
|
|
||||||
"donatesToDevFund": 20,
|
|
||||||
"hasGoodOpSec": true,
|
|
||||||
"robotsLove": true,
|
|
||||||
"hasLargeLimits": true
|
|
||||||
},
|
|
||||||
"policies": {
|
|
||||||
"Experimental": "Experimental coordinator used for development. Use at your own risk.",
|
|
||||||
"Dispute Policy": "Evidence in Disputes: In the event of a dispute, users will be asked to provide transaction-related evidence. This could include transaction IDs, screenshots of payment confirmations, or other pertinent transaction records. Personal information or unrelated transaction details should be redacted to maintain privacy.",
|
|
||||||
"Non eligible countries": "USA citizens and residents are not allowed to use the Experimental coordinator. F2F transactions are explicitly blocked at creation time for US locations. If a US citizen or resident violates this rule and is found out to be using the Experimental coordinator during a dispute process, they will be denied further service and the dispute mediation will be terminated."
|
|
||||||
},
|
|
||||||
"mainnet": {
|
|
||||||
"onion": "http://robosats6tkf3eva7x2voqso3a5wcorsnw34jveyxfqi2fu7oyheasid.onion",
|
|
||||||
"clearnet": "https://unsafe.robosats.com",
|
|
||||||
"i2p": "http://r7r4sckft6ptmk4r2jajiuqbowqyxiwsle4iyg4fijtoordc6z7a.b32.i2p"
|
|
||||||
},
|
|
||||||
"testnet": {
|
|
||||||
"onion": "http://robotestagw3dcxmd66r4rgksb4nmmr43fh77bzn2ia2eucduyeafnyd.onion",
|
|
||||||
"clearnet": "https://unsafe.testnet.robosats.com",
|
|
||||||
"i2p": ""
|
|
||||||
},
|
|
||||||
"mainnetNodesPubkeys": ["0282eb467bc073833a039940392592bf10cf338a830ba4e392c1667d7697654c7e"],
|
|
||||||
"testnetNodesPubkeys": ["03ecb271b3e2e36f2b91c92c65bab665e5165f8cdfdada1b5f46cfdd3248c87fd6"]
|
|
||||||
},
|
|
||||||
"temple": {
|
"temple": {
|
||||||
"longAlias": "Temple of Sats",
|
"longAlias": "Temple of Sats",
|
||||||
"shortAlias": "temple",
|
"shortAlias": "temple",
|
||||||
|
Loading…
Reference in New Issue
Block a user