move out transfer status hook

This commit is contained in:
Bill He
2023-08-29 10:11:49 -07:00
parent 765f39ec5e
commit cb0bd0f39a
6 changed files with 203 additions and 171 deletions
+87
View File
@@ -0,0 +1,87 @@
import { useCallback, useEffect, useMemo } from 'react';
import { useQuery } from 'react-query';
import { LocalStorageKey } from '@/constants/localStorage';
import {
type TransferNotifcation,
} from '@/constants/notifications';
import { useAccounts } from '@/hooks/useAccounts';
import { useSquid } from '@/hooks/useSquid';
import { useLocalStorage } from './useLocalStorage';
import { StatusResponse } from '@0xsquid/sdk';
export const useLocalNotifications = () => {
// transfer notifications
const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{[key: `dydx${string}`]: TransferNotifcation[]}>({
key: LocalStorageKey.TransferNotifications,
defaultValue: {},
});
const { dydxAddress } = useAccounts();
const transferNotifications = useMemo(() => {
if (!dydxAddress) return [];
console.log({allTransferNotifications, transferNotifications: allTransferNotifications[dydxAddress]})
return allTransferNotifications[dydxAddress] || [];
}, [allTransferNotifications, dydxAddress]);
const setTransferNotifications = useCallback(
(notifications: TransferNotifcation[]) => {
if (!dydxAddress) return;
const updatedNotifications = { ...allTransferNotifications };
updatedNotifications[dydxAddress] = notifications;
setAllTransferNotifications(updatedNotifications);
},
[setAllTransferNotifications, dydxAddress]
);
const addTransferNotification = useCallback(
(notification: TransferNotifcation) =>
setTransferNotifications([...transferNotifications, notification]),
[transferNotifications]
);
const squid = useSquid();
const { data: transferStatuses } = useQuery({
queryKey: ['getTransactionStatus', transferNotifications],
queryFn: async () => {
const statuses: { [key: string]: StatusResponse } = {};
for (const {
txHash,
toChainId,
fromChainId,
status: currentStatus,
} of transferNotifications) {
if (currentStatus && currentStatus?.squidTransactionStatus !== 'ongoing') continue;
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId });
if (status) statuses[txHash] = status;
}
return statuses;
},
refetchInterval: 10_000,
});
useEffect(() => {
if (!transferStatuses) return;
const newTransferNotifications = transferNotifications.map((notification) => {
const status = transferStatuses[notification.txHash];
if (!status) return notification;
return {
...notification,
status,
};
});
setTransferNotifications(newTransferNotifications);
}, [transferStatuses]);
return {
transferNotifications,
addTransferNotification,
};
};
+109 -106
View File
@@ -12,6 +12,8 @@ import {
} from '@/constants/notifications';
import { ORDER_SIDE_STRINGS, TRADE_TYPE_STRINGS, TradeTypes } from '@/constants/trade';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { Icon, IconName } from '@/components/Icon';
import { TransferStatusToast } from '@/views/TransferStatus';
@@ -28,123 +30,124 @@ import { useStringGetter } from './useStringGetter';
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client';
export const notificationTypes = (transferNotifications: TransferNotifcation[]) =>
[
{
type: NotificationType.OrderStatusChanged,
export const notificationTypes = [
{
type: NotificationType.OrderStatusChanged,
useTrigger: ({ trigger, lastUpdated }) => {
const stringGetter = useStringGetter();
useTrigger: ({ trigger, lastUpdated }) => {
const stringGetter = useStringGetter();
const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
const ordersByOrderId = Object.fromEntries(orders.map((order) => [order.id, order]));
const orders = useSelector(getSubaccountOrders, shallowEqual) || [];
const ordersByOrderId = Object.fromEntries(orders.map((order) => [order.id, order]));
const fills = useSelector(getSubaccountFills, shallowEqual) || [];
const fillsByOrderId = groupBy(fills, (fill) => fill.orderId);
const fills = useSelector(getSubaccountFills, shallowEqual) || [];
const fillsByOrderId = groupBy(fills, (fill) => fill.orderId);
const orderIds = useMemo(
() => [...Object.keys(ordersByOrderId), ...Object.keys(fillsByOrderId)],
[orders, fills]
);
const orderIds = useMemo(
() => [...Object.keys(ordersByOrderId), ...Object.keys(fillsByOrderId)],
[orders, fills]
);
useEffect(() => {
for (const orderId of orderIds) {
const fills = fillsByOrderId[orderId];
useEffect(() => {
for (const orderId of orderIds) {
const fills = fillsByOrderId[orderId];
const order =
ordersByOrderId[orderId] ??
(fills?.length
? {
...fills[fills.length - 1],
id: orderId,
createdAtMilliseconds: Math.max(
...fills.map((fill) => fill.createdAtMilliseconds)
),
status: AbacusOrderStatus.filled,
}
: undefined);
if (order)
trigger(
order.id,
{
icon: (
<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} />
const order =
ordersByOrderId[orderId] ??
(fills?.length
? {
...fills[fills.length - 1],
id: orderId,
createdAtMilliseconds: Math.max(
...fills.map((fill) => fill.createdAtMilliseconds)
),
title: `${stringGetter({
key: TRADE_TYPE_STRINGS[order.type.rawValue as TradeTypes].tradeTypeKey,
})} ${
order.status === AbacusOrderStatus.open && order.totalFilled > 0
? stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED })
: stringGetter({ key: ORDER_STATUS_STRINGS[order.status.name] })
}`,
description: `${stringGetter({
key: ORDER_SIDE_STRINGS[ORDER_SIDES[order.side.name]],
})} ${order.size} ${order.marketId} @ $${order.price}`,
actionDescription: 'View Order',
actionAltText: 'View this order in the Orders tab or the Notifications menu.',
toastSensitivity:
order.status === AbacusOrderStatus.pending ? 'foreground' : 'background',
toastDuration: 5000,
},
[order.status.name, order.size],
!order.createdAtMilliseconds || order.createdAtMilliseconds > lastUpdated
);
}
}, [orderIds]);
},
useNotificationAction: () => {
const dispatch = useDispatch();
return (orderId) => {
dispatch(
openDialog({
type: DialogTypes.OrderDetails,
dialogProps: { orderId },
})
);
};
},
} as NotificationTypeConfig<string, [string, number]>,
{
type: NotificationType.SquidTransfer,
useTrigger: ({ trigger, lastUpdated }) => {
const stringGetter = useStringGetter();
const getTitleStringKey = useCallback((type: 'deposit' | 'withdraw', finished: boolean) => {
if (type === 'deposit' && !finished) return STRING_KEYS.DEPOSIT_IN_PROGRESS;
if (type === 'deposit' && finished) return STRING_KEYS.DEPOSIT;
if (type === 'withdraw' && !finished) return STRING_KEYS.WITHDRAW_IN_PROGRESS;
return STRING_KEYS.WITHDRAW;
}, []);
useEffect(() => {
for (const transfer of transferNotifications) {
const { toChainId, status, txHash, toAmount } = transfer;
const finished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
const type = toChainId === TESTNET_CHAIN_ID ? 'deposit' : 'withdraw';
status: AbacusOrderStatus.filled,
}
: undefined);
if (order)
trigger(
txHash,
order.id,
{
icon: <Icon iconName={finished ? IconName.Transfer : IconName.Clock} />,
title: stringGetter({ key: getTitleStringKey(type, finished) }),
description: type === 'deposit' ? `Deposit of $${toAmount}` : `Withdraw of ${toAmount}`,
customContent: (
<TransferStatusToast
toAmount={transfer.toAmount}
triggeredAt={transfer.triggeredAt}
status={transfer.status}
/>
icon: (
<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} />
),
customMenuContent: !finished && <TransferStatusSteps status={transfer.status} />,
toastSensitivity: 'foreground',
title: `${stringGetter({
key: TRADE_TYPE_STRINGS[order.type.rawValue as TradeTypes].tradeTypeKey,
})} ${
order.status === AbacusOrderStatus.open && order.totalFilled > 0
? stringGetter({ key: STRING_KEYS.PARTIALLY_FILLED })
: stringGetter({ key: ORDER_STATUS_STRINGS[order.status.name] })
}`,
description: `${stringGetter({
key: ORDER_SIDE_STRINGS[ORDER_SIDES[order.side.name]],
})} ${order.size} ${order.marketId} @ $${order.price}`,
actionDescription: 'View Order',
actionAltText: 'View this order in the Orders tab or the Notifications menu.',
toastSensitivity:
order.status === AbacusOrderStatus.pending ? 'foreground' : 'background',
toastDuration: 5000,
},
[]
[order.status.name, order.size],
!order.createdAtMilliseconds || order.createdAtMilliseconds > lastUpdated
);
}
}, [transferNotifications]);
},
}
}, [orderIds]);
},
] satisfies NotificationTypeConfig[];
useNotificationAction: () => {
const dispatch = useDispatch();
return (orderId) => {
dispatch(
openDialog({
type: DialogTypes.OrderDetails,
dialogProps: { orderId },
})
);
};
},
} as NotificationTypeConfig<string, [string, number]>,
{
type: NotificationType.SquidTransfer,
useTrigger: ({ trigger, lastUpdated }) => {
const stringGetter = useStringGetter();
const { transferNotifications } = useLocalNotifications();
const getTitleStringKey = useCallback((type: 'deposit' | 'withdraw', finished: boolean) => {
if (type === 'deposit' && !finished) return STRING_KEYS.DEPOSIT_IN_PROGRESS;
if (type === 'deposit' && finished) return STRING_KEYS.DEPOSIT;
if (type === 'withdraw' && !finished) return STRING_KEYS.WITHDRAW_IN_PROGRESS;
return STRING_KEYS.WITHDRAW;
}, []);
useEffect(() => {
for (const transfer of transferNotifications) {
const { toChainId, status, txHash, toAmount } = transfer;
const finished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
const type = toChainId === TESTNET_CHAIN_ID ? 'deposit' : 'withdraw';
trigger(
txHash,
{
icon: <Icon iconName={finished ? IconName.Transfer : IconName.Clock} />,
title: stringGetter({ key: getTitleStringKey(type, finished) }),
description:
type === 'deposit' ? `Deposit of $${toAmount}` : `Withdraw of ${toAmount}`,
customContent: (
<TransferStatusToast
toAmount={transfer.toAmount}
triggeredAt={transfer.triggeredAt}
status={transfer.status}
/>
),
customMenuContent: !finished && <TransferStatusSteps status={transfer.status} />,
toastSensitivity: 'foreground',
},
[]
);
}
}, [transferNotifications]);
},
},
] satisfies NotificationTypeConfig[];
+1 -56
View File
@@ -12,7 +12,7 @@ import {
import { useSquid } from '@/hooks/useSquid';
import { useLocalStorage } from './useLocalStorage';
import { notificationTypes as notificationTypesFactory } from './useNotificationTypes';
import { notificationTypes } from './useNotificationTypes';
import { renderSvgToDataUrl } from '../lib/renderSvgToDataUrl';
import { StatusResponse } from '@0xsquid/sdk';
@@ -41,58 +41,6 @@ const useNotificationsContext = () => {
defaultValue: Date.now(),
});
// transfer notifications
const [transferNotifications, setTransferNotifications] = useLocalStorage<TransferNotifcation[]>({
key: LocalStorageKey.TransferNotifications,
defaultValue: [],
});
const addTransferNotification = useCallback(
(notification: TransferNotifcation) =>
setTransferNotifications([...transferNotifications, notification]),
[transferNotifications]
);
const notificationTypes = useMemo(
() => notificationTypesFactory(transferNotifications),
[transferNotifications]
);
const squid = useSquid();
const { data: transferStatuses } = useQuery({
queryKey: ['getTransactionStatus', transferNotifications],
queryFn: async () => {
const statuses: { [key: string]: StatusResponse } = {};
for (const {
txHash,
toChainId,
fromChainId,
status: currentStatus,
} of transferNotifications) {
if (currentStatus && currentStatus?.squidTransactionStatus !== 'ongoing') continue;
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId });
if (status) statuses[txHash] = status;
}
return statuses;
},
refetchInterval: 10_000,
});
useEffect(() => {
if (!transferStatuses) return;
const newTransferNotifications = transferNotifications.map((notification) => {
const status = transferStatuses[notification.txHash];
if (!status) return notification;
return {
...notification,
status,
};
});
setTransferNotifications(newTransferNotifications);
}, [transferStatuses]);
useEffect(() => {
setNotificationsLastUpdated(Date.now());
}, [notifications]);
@@ -295,8 +243,5 @@ const useNotificationsContext = () => {
// Menu state
isMenuOpen,
setIsMenuOpen,
addTransferNotification,
};
};
// }
+2 -2
View File
@@ -922,7 +922,7 @@
"CLOSE_MARKET_POSITIONS_BODY": "{MARKET} is set to close only mode and the close price of {MARKET} is now fixed. Please close your open {MARKET} position as soon as you are able. We're here to help via the help chat if you run into issues.",
"COMPLIANCE_ALERT": "Compliance alert",
"DEPOSIT_IN_PROGRESS_DESCRIPTION": "Your deposit of {AMOUNT_ELEMENT} will be available after 14 confirmations.",
"DEPOSIT_IN_PROGRESS": "Deposit in progress",
"DEPOSIT_IN_PROGRESS": "Deposit in progress...",
"DEPOSIT_SUCCESS_DESCRIPTION": "Your deposit of {AMOUNT_ELEMENT} has been confirmed and is now available for trading.",
"DEPOSIT_SUCCESS": "Deposit success!",
"DEPOSIT_TO_CHAIN": "Deposit to {CHAIN}",
@@ -952,7 +952,7 @@
"SLOW_WITHDRAW_PENDING": "Slow withdraw(s) pending",
"SUSPICIOUS_TRADE": "Suspicious activity",
"SUSPICIOUS_TRADE_BODY": "We have noticed suspicious trading activity related to your account on dYdX, including potential wash trading activity. We will be reviewing your activity on an ongoing basis. If we notice similar activity in the future, then you will be permanently blocked from performing transfers within the protocol and from placing orders other than market orders that reduce your positions.",
"WITHDRAW_IN_PROGRESS": "Withdrawal(s) in progress",
"WITHDRAW_IN_PROGRESS": "Withdrawal(s) in progress...",
"WITHDRAW_TO_CHAIN": "Withdraw to {CHAIN}"
},
"EMAIL_NOTIFICATIONS": {
@@ -13,7 +13,7 @@ import { NumberSign } from '@/constants/numbers';
import { useAccounts, useDebounce, useStringGetter } from '@/hooks';
import { useAccountBalance } from '@/hooks/useAccountBalance';
import { useNotifications } from '@/hooks/useNotifications';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { layoutMixins } from '@/styles/layoutMixins';
import { formMixins } from '@/styles/formMixins';
@@ -52,7 +52,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
const { signerWagmi } = useAccounts();
const { addTransferNotification } = useNotifications();
const { addTransferNotification } = useLocalNotifications();
const {
requestPayload,
@@ -74,8 +74,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
const debouncedAmount = useDebounce<string>(fromAmount, 500);
// Async Data
const [transactionHash, setTransactionHash] = useState<string>();
const { balance, queryStatus, isQueryFetching } = useAccountBalance({
addressOrDenom: sourceToken?.address || undefined,
assetSymbol: sourceToken?.symbol || undefined,
@@ -194,7 +192,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
onDeposit?.();
if (txHash) {
setTransactionHash(txHash);
addTransferNotification({
txHash,
toChainId: TESTNET_CHAIN_ID,
@@ -16,7 +16,7 @@ import { STRING_KEYS } from '@/constants/localization';
import { NumberSign, QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { useDebounce, useStringGetter, useSubaccount } from '@/hooks';
import { useNotifications } from '@/hooks/useNotifications';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -67,7 +67,7 @@ export const WithdrawForm = () => {
[token, resources]
);
const { addTransferNotification } = useNotifications();
const { addTransferNotification } = useLocalNotifications();
// Async Data
const [transactionHash, setTransactionHash] = useState<string>();