Skip to content

Commit

Permalink
poll ucm for changes on updates / finish order window updates
Browse files Browse the repository at this point in the history
  • Loading branch information
NickJ202 committed Jan 7, 2025
1 parent 59c7168 commit bb7dd04
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 116 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"@othent/kms": "^1.0.7",
"@permaweb/aoconnect": "^0.0.45",
"@permaweb/stampjs": "^1.0.3",
"@permaweb/ucm": "0.0.2",
"@permaweb/ucm": "0.0.3",
"async-lock": "^1.4.1",
"jwt-decode": "^4.0.0",
"nprogress": "^0.2.0",
Expand Down
95 changes: 24 additions & 71 deletions src/components/organisms/OrderCancel/OrderCancel.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';

import { messageResults, readHandler } from 'api';
import { cancelOrder } from '@permaweb/ucm';

import { Button } from 'components/atoms/Button';
import { Notification } from 'components/atoms/Notification';
import { Modal } from 'components/molecules/Modal';
import { AO } from 'helpers/config';
import { NotificationType } from 'helpers/types';
import * as windowUtils from 'helpers/window';
import { useAppProvider } from 'providers/AppProvider';
import { useArweaveProvider } from 'providers/ArweaveProvider';
import { useLanguageProvider } from 'providers/LanguageProvider';
import { RootState } from 'store';
import * as ucmActions from 'store/ucm/actions';

import * as S from './styles';
import { IProps } from './types';

export default function OrderCancel(props: IProps) {
const dispatch = useDispatch();

const appProvider = useAppProvider();
const arProvider = useArweaveProvider();

const languageProvider = useLanguageProvider();
const language = languageProvider.object[languageProvider.current];

const ucmReducer = useSelector((state: RootState) => state.ucmReducer);

const [loading, setLoading] = React.useState<boolean>(false);
const [showConfirmation, setShowConfirmation] = React.useState<boolean>(false);
const [cancelProcessed, setCancelProcessed] = React.useState<boolean>(false);
Expand All @@ -36,75 +31,33 @@ export default function OrderCancel(props: IProps) {
if (arProvider.wallet && arProvider.profile && arProvider.profile.id) {
setLoading(true);
try {
const response = await messageResults({
processId: arProvider.profile.id,
action: 'Run-Action',
wallet: arProvider.wallet,
tags: null,
data: {
Target: AO.ucm,
Action: 'Cancel-Order',
Input: JSON.stringify({
Pair: [props.listing.token, props.listing.currency],
OrderTxId: props.listing.id,
}),
const cancelOrderId = await cancelOrder(
{
orderbookId: AO.ucm,
orderId: props.listing.id,
profileId: arProvider.profile.id,
dominantToken: props.listing.token,
swapToken: props.listing.currency,
},
handler: 'Cancel-Order',
});

if (response) {
if (response['Action-Response']) {
setResponse({
message: response['Action-Response'].message,
status: response['Action-Response'].status === 'Success' ? 'success' : 'warning',
});
} else {
setResponse({
message: 'Order cancelled',
status: 'success',
});
arProvider.wallet,
(args: { processing: boolean; success: boolean; message: string }) => {
console.log(args.message);
}
);

const existingUCM = { ...ucmReducer };
const maxTries = 10;
let tries = 0;
let changeDetected = false;

const fetchUntilChange = async () => {
while (!changeDetected && tries < maxTries) {
const ucmState = await readHandler({
processId: AO.ucm,
action: 'Info',
});

dispatch(ucmActions.setUCM(ucmState));

if (JSON.stringify(existingUCM) !== JSON.stringify(ucmState)) {
changeDetected = true;
} else {
await new Promise((resolve) => setTimeout(resolve, 1000));
tries++;
}
}

if (!changeDetected) {
console.warn(`No changes detected after ${maxTries} attempts`);
}
};

await fetchUntilChange();

setCancelProcessed(true);
console.log(`Order Cancellation ID: ${cancelOrderId}`);

arProvider.setToggleTokenBalanceUpdate(!arProvider.toggleTokenBalanceUpdate);
props.toggleUpdate();
setResponse({ status: 'success', message: 'Order cancelled' });

setShowConfirmation(false);
setCancelProcessed(false);
windowUtils.scrollTo(0, 0, 'smooth');
}
setCancelProcessed(true);
appProvider.refreshUcm();
arProvider.setToggleTokenBalanceUpdate(!arProvider.toggleTokenBalanceUpdate);
props.toggleUpdate();
setShowConfirmation(false);
setCancelProcessed(false);
windowUtils.scrollTo(0, 0, 'smooth');
} catch (e: any) {
console.error(e);
setResponse({ status: 'success', message: e.message ?? 'Error cancelling order' });
}
setLoading(false);
}
Expand Down
2 changes: 2 additions & 0 deletions src/helpers/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const language = {
fetching: `Fetching`,
fetchingAsset: `Fetching asset`,
fetchingProfile: `Fetching profile`,
fetchingTokenbalances: `Fetching token balances`,
floorPrice: `Floor price`,
getWrappedAr: `Get Wrapped AR`,
gettingStamps: `Getting your stamps`,
Expand Down Expand Up @@ -114,6 +115,7 @@ export const language = {
pixlBalanceInfo: `Your PIXL balance will show here`,
pixlHoldings: `PIXL Holdings`,
price: `Price`,
priceTooLow: `Price is too low`,
profileCreated: `Profile created`,
profileUpdated: `Profile updated`,
profileCreatingInfo: `Creating profile`,
Expand Down
96 changes: 92 additions & 4 deletions src/providers/AppProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { isEqual } from 'lodash';

import { readHandler, stamps } from 'api';

Expand Down Expand Up @@ -49,7 +50,7 @@ export function AppProvider(props: AppProviderProps) {
lastUpdate: ucmReducer?.lastUpdate,
});

const [ucmRefreshTrigger, setUcmRefreshTrigger] = React.useState<boolean>(false);
const [ucmRefreshTrigger, setUcmRefreshTrigger] = React.useState<boolean | null>(null);

const [streaksState, setStreaksState] = React.useState<AppContextState['streaks']>({
updating: false,
Expand All @@ -67,7 +68,6 @@ export function AppProvider(props: AppProviderProps) {
if (stampsReducer) setStampsState((prevState) => ({ ...prevState, completed: true }));
}, [stampsReducer]);

// TODO: Fetch until change
React.useEffect(() => {
(async function () {
setUCMState((prevState) => ({ ...prevState, updating: true }));
Expand Down Expand Up @@ -95,7 +95,95 @@ export function AppProvider(props: AppProviderProps) {
setUCMState((prevState) => ({ ...prevState, updating: false }));
}
})();
}, [ucmRefreshTrigger]);
}, []);

React.useEffect(() => {
const fetchAndCompareUCM = async () => {
setUCMState((prevState) => ({ ...prevState, updating: true }));

try {
const newUCMState = await readHandler({
processId: AO.ucm,
action: 'Info',
});

const normalizedOrders = (ucm) =>
ucm?.Orderbook?.map((entry) => ({
Pair: entry.Pair?.sort() || [],
PriceData: entry.PriceData
? {
...entry.PriceData,
MatchLogs: entry.PriceData.MatchLogs
? entry.PriceData.MatchLogs.map((log) => ({
...log,
})).sort((a, b) => a.Id.localeCompare(b.Id))
: [],
}
: null,
Orders: entry.Orders
? entry.Orders.map((order) => ({
...order,
})).sort((a, b) => a.Id.localeCompare(b.Id))
: [],
})).sort((a, b) => JSON.stringify(a.Pair).localeCompare(JSON.stringify(b.Pair))) || [];

const currentOrders = normalizedOrders(ucmReducer);
const newOrders = normalizedOrders(newUCMState);

const hasDifferences = !isEqual(currentOrders, newOrders);

if (hasDifferences) {
dispatch(
ucmActions.setUCM({
...newUCMState,
lastUpdate: Date.now(),
})
);

setUCMState({
updating: false,
completed: true,
lastUpdate: Date.now(),
});

setUcmRefreshTrigger(null);

return true;
} else {
setUCMState((prevState) => ({
...prevState,
updating: false,
}));
return false;
}
} catch (e) {
console.error(e);
setUCMState((prevState) => ({ ...prevState, updating: false }));
return true;
}
};

let isPolling = false;

const pollUntilDifference = async () => {
if (isPolling) return;
isPolling = true;

let differencesDetected = false;
do {
differencesDetected = await fetchAndCompareUCM();
if (!differencesDetected) {
await new Promise((resolve) => setTimeout(resolve, 2000));
}
} while (!differencesDetected);

isPolling = false;
};

if (ucmRefreshTrigger !== null) {
pollUntilDifference();
}
}, [ucmRefreshTrigger, ucmReducer]);

React.useEffect(() => {
(async function () {
Expand Down Expand Up @@ -222,7 +310,7 @@ export function AppProvider(props: AppProviderProps) {
streaks: streaksState,
stamps: stampsState,
refreshUcm: () => {
setUcmRefreshTrigger((prev) => !prev);
setUcmRefreshTrigger((prev) => (prev === null ? true : !prev));
},
}}
>
Expand Down
36 changes: 14 additions & 22 deletions src/providers/ArweaveProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -193,56 +193,49 @@ export function ArweaveProvider(props: ArweaveProviderProps) {
}, [toggleProfileUpdate]);

React.useEffect(() => {
const fetchWalletBalances = async () => {
if (!walletAddress) return;
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

const fetchBalances = async () => {
if (!walletAddress || !profile?.id) return;

try {
const defaultTokenWalletBalance = await readHandler({
processId: AO.defaultToken,
action: 'Balance',
tags: [{ name: 'Recipient', value: walletAddress }],
});
await sleep(500);

const pixlTokenWalletBalance = await readHandler({
processId: AO.pixl,
action: 'Balance',
tags: [{ name: 'Recipient', value: walletAddress }],
});
setTokenBalances((prevBalances) => ({
...prevBalances,
[AO.defaultToken]: {
...prevBalances[AO.defaultToken],
walletBalance: defaultTokenWalletBalance ?? null,
},
[AO.pixl]: {
...prevBalances[AO.pixl],
walletBalance: pixlTokenWalletBalance ?? null,
},
}));
} catch (e) {
console.error(e);
}
};
await sleep(500);

const fetchProfileBalances = async () => {
if (!profile || !profile.id) return;
try {
const defaultTokenProfileBalance = await readHandler({
processId: AO.defaultToken,
action: 'Balance',
tags: [{ name: 'Recipient', value: profile.id }],
});
await sleep(500);

const pixlTokenProfileBalance = await readHandler({
processId: AO.pixl,
action: 'Balance',
tags: [{ name: 'Recipient', value: profile.id }],
});

setTokenBalances((prevBalances) => ({
...prevBalances,
[AO.defaultToken]: {
...prevBalances[AO.defaultToken],
walletBalance: defaultTokenWalletBalance ?? null,
profileBalance: defaultTokenProfileBalance ?? null,
},
[AO.pixl]: {
...prevBalances[AO.pixl],
walletBalance: pixlTokenWalletBalance ?? null,
profileBalance: pixlTokenProfileBalance ?? null,
},
}));
Expand All @@ -251,8 +244,7 @@ export function ArweaveProvider(props: ArweaveProviderProps) {
}
};

fetchWalletBalances();
fetchProfileBalances();
fetchBalances();
}, [walletAddress, profile, toggleTokenBalanceUpdate]);

React.useEffect(() => {
Expand Down
6 changes: 4 additions & 2 deletions src/views/Asset/AssetAction/AssetAction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,11 @@ export default function AssetAction(props: IProps) {
)}
</S.OwnerLinesWrapper>
)}
{appProvider.ucm.updating && (
{(appProvider.ucm.updating || props.updating) && (
<S.MessageWrapper className={'update-wrapper'}>
<span>{`${language.ordersUpdating}...`}</span>
<span>
{appProvider.ucm.updating ? `${language.ordersUpdating}...` : `${language.updatingAsset}...`}
</span>
</S.MessageWrapper>
)}
</S.OrdersWrapper>
Expand Down
Loading

0 comments on commit bb7dd04

Please sign in to comment.