temp
This commit is contained in:
+1
-1
@@ -29,7 +29,7 @@
|
||||
},
|
||||
"packageManager": "pnpm@8.6.6",
|
||||
"dependencies": {
|
||||
"@0xsquid/sdk": "^1.7.2",
|
||||
"@0xsquid/sdk": "^1.10.0",
|
||||
"@cosmjs/amino": "^0.31.0",
|
||||
"@cosmjs/crypto": "^0.31.0",
|
||||
"@cosmjs/encoding": "^0.31.0",
|
||||
|
||||
Generated
+189
-1752
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -14,7 +14,7 @@ import { DialogAreaProvider, useDialogArea } from './hooks/useDialogArea';
|
||||
import { LocaleProvider } from './hooks/useLocaleSeparators';
|
||||
import { NotificationsProvider } from './hooks/useNotifications';
|
||||
import { SubaccountProvider } from './hooks/useSubaccount';
|
||||
import { SquidProvider } from '@/hooks/useSquidRouter';
|
||||
import { SquidProvider } from '@/hooks/useSquid';
|
||||
|
||||
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
||||
|
||||
|
||||
@@ -80,10 +80,11 @@ export const Toast = ({
|
||||
</$Header>
|
||||
|
||||
<$Description>{slotDescription}</$Description>
|
||||
|
||||
<$Action asChild altText={actionAltText}>
|
||||
{slotAction}
|
||||
</$Action>
|
||||
{actionDescription && (
|
||||
<$Action asChild altText={actionAltText}>
|
||||
{slotAction}
|
||||
</$Action>
|
||||
)}
|
||||
</$Container>
|
||||
</div>
|
||||
</$Root>
|
||||
@@ -118,8 +119,7 @@ const $Root = styled(Root)`
|
||||
align-items: end;
|
||||
transform-origin: left bottom;
|
||||
|
||||
animation:
|
||||
${keyframes`
|
||||
animation: ${keyframes`
|
||||
from {
|
||||
/* scale: 0; */
|
||||
grid-template-rows: 0fr; // height transition
|
||||
@@ -203,8 +203,8 @@ const $Container = styled.div`
|
||||
// Rules
|
||||
${popoverMixins.popover}
|
||||
padding: 1rem;
|
||||
box-shadow:
|
||||
0 0 0 var(--border-width) var(--color-border), // border
|
||||
box-shadow: 0 0 0 var(--border-width) var(--color-border),
|
||||
// border
|
||||
0 0 0.5rem 0.1rem var(--color-layer-2); // shadow
|
||||
|
||||
${$Root}:focus:not([data-swipe='end']) & {
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import styled, { css, keyframes, type AnyStyledComponent } from 'styled-components';
|
||||
import { Root, Trigger, Content } from '@radix-ui/react-collapsible';
|
||||
|
||||
import type { TransferStatus } from '@/constants/abacus';
|
||||
|
||||
import { useInterval, useStringGetter } from '@/hooks';
|
||||
import { useSquid } from '@/hooks/useSquid';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
import { formatSeconds } from '@/lib/timeUtils';
|
||||
import abacusStateManager from '@/lib/abacus';
|
||||
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { Link } from '@/components/Link';
|
||||
import { WithReceipt } from '@/components/WithReceipt';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { popoverMixins } from '@/styles/popoverMixins';
|
||||
import { DateTime } from 'luxon';
|
||||
import { LoadingDots } from './Loading/LoadingDots';
|
||||
|
||||
type ElementProps = {
|
||||
txHash: string;
|
||||
toChainId: string;
|
||||
fromChainId?: string;
|
||||
toAmount?: number;
|
||||
triggeredAt?: number;
|
||||
};
|
||||
|
||||
export const TransferStatusToast = ({ txHash, toChainId, fromChainId, toAmount, triggeredAt = Date.now() }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [secondsLeft, setSecondsLeft] = useState<number | undefined>();
|
||||
|
||||
const squid = useSquid();
|
||||
|
||||
const { data: status } = useQuery({
|
||||
queryKey: ['getTransactionStatus', { transactionId: txHash, toChainId, fromChainId }],
|
||||
queryFn: async () => await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId }),
|
||||
refetchInterval: 30_000,
|
||||
});
|
||||
|
||||
const updateSecondsLeft = useCallback(() => {
|
||||
const fromChainEta = (status?.fromChain?.chainData?.estimatedRouteDuration || 0) * 1000;
|
||||
const toChainEta = (status?.toChain?.chainData?.estimatedRouteDuration || 0) * 1000;
|
||||
setSecondsLeft(Math.floor((triggeredAt + fromChainEta + toChainEta - Date.now()) / 1000));
|
||||
}, [status]);
|
||||
|
||||
useInterval({ callback: updateSecondsLeft });
|
||||
|
||||
if (!status) return <LoadingDots size={3} />;
|
||||
|
||||
return (
|
||||
<Styled.Root open={open} onOpenChange={setOpen}>
|
||||
<WithReceipt
|
||||
hideReceipt={!open}
|
||||
side="bottom"
|
||||
slotReceipt={
|
||||
<Styled.Content>
|
||||
{/* {statusSteps.map(({ status, label, url, eta }, index) => {
|
||||
return (
|
||||
<Styled.Step key={index}>
|
||||
{url ? (
|
||||
<Link href={url}>
|
||||
<span>{label}</span>
|
||||
<Icon iconName={IconName.LinkOut} />
|
||||
</Link>
|
||||
) : (
|
||||
<span>{label}</span>
|
||||
)}
|
||||
</Styled.Step>
|
||||
);
|
||||
})} */}
|
||||
</Styled.Content>
|
||||
}
|
||||
>
|
||||
<Styled.BridgingStatus>
|
||||
<Styled.Status>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.DEPOSIT_STATUS,
|
||||
params: {
|
||||
AMOUNT_USD: (
|
||||
<Styled.InlineOutput
|
||||
type={OutputType.Fiat}
|
||||
value={toAmount}
|
||||
/>
|
||||
),
|
||||
ESTIMATED_DURATION: (
|
||||
<Styled.InlineOutput
|
||||
type={OutputType.Text}
|
||||
value={formatSeconds(secondsLeft || 0)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
})}
|
||||
</Styled.Status>
|
||||
<Styled.Trigger>
|
||||
<Styled.TriggerIcon>
|
||||
<Icon iconName={IconName.Caret} />
|
||||
</Styled.TriggerIcon>
|
||||
{stringGetter({ key: open ? STRING_KEYS.HIDE_DETAILS : STRING_KEYS.VIEW_DETAILS })}
|
||||
</Styled.Trigger>
|
||||
</Styled.BridgingStatus>
|
||||
</WithReceipt>
|
||||
</Styled.Root>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.Root = styled(Root)`
|
||||
margin: 0 -1rem -1rem -1rem;
|
||||
`;
|
||||
|
||||
Styled.BridgingStatus = styled.div`
|
||||
${layoutMixins.flexColumn};
|
||||
background-color: var(--color-layer-3);
|
||||
|
||||
gap: 0.5rem;
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
`;
|
||||
|
||||
Styled.Status = styled.div`
|
||||
color: var(--color-text-0);
|
||||
|
||||
font-size: 0.875rem;
|
||||
`;
|
||||
|
||||
Styled.InlineOutput = styled(Output)`
|
||||
display: inline-block;
|
||||
|
||||
color: var(--color-text-1);
|
||||
`;
|
||||
|
||||
Styled.Content = styled(Content)`
|
||||
${layoutMixins.flexColumn};
|
||||
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
`;
|
||||
|
||||
Styled.Step = styled.div`
|
||||
${layoutMixins.row};
|
||||
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
Styled.Trigger = styled(Trigger)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
|
||||
color: var(--color-accent);
|
||||
user-select: none;
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
Styled.TriggerIcon = styled.span`
|
||||
width: 0.75em;
|
||||
|
||||
display: inline-flex;
|
||||
transition: transform 0.3s var(--ease-out-expo);
|
||||
|
||||
${Styled.Trigger}[data-state='open'] & {
|
||||
rotate: -0.5turn;
|
||||
}
|
||||
`;
|
||||
@@ -135,6 +135,8 @@ export const TransferType = Abacus.exchange.dydx.abacus.output.input.TransferTyp
|
||||
const transferTypes = [...TransferType.values()] as const;
|
||||
export type TransferTypes = (typeof transferTypes)[number];
|
||||
|
||||
export type TransferStatus = Abacus.exchange.dydx.abacus.output.TransferStatus;
|
||||
|
||||
// ------ Trade Items ------ //
|
||||
export const TradeInputField = Abacus.exchange.dydx.abacus.state.modal.TradeInputField;
|
||||
const tradeInputFields = [...TradeInputField.values()] as const;
|
||||
|
||||
@@ -109,6 +109,7 @@ export const APP_STRING_KEYS = {
|
||||
HEDGIES: 'GENERAL.HEDGIES',
|
||||
HIDE_SECTION: 'GENERAL.HIDE_SECTION',
|
||||
HIDE: 'GENERAL.HIDE',
|
||||
HIDE_DETAILS: 'GENERAL.HIDE_DETAILS',
|
||||
HISTORY: 'GENERAL.HISTORY',
|
||||
INCREASED: 'GENERAL.INCREASED',
|
||||
INCREASING: 'GENERAL.INCREASING',
|
||||
@@ -383,6 +384,7 @@ export const APP_STRING_KEYS = {
|
||||
CONNECTING_SUBTITLE: 'ONBOARDING.CONNECTING_SUBTITLE',
|
||||
COULD_NOT_FIND_AFFILIATE: 'ONBOARDING.COULD_NOT_FIND_AFFILIATE',
|
||||
CREATE_OR_VERIFY_COSMOS_ADDRESS: 'ONBOARDING.CREATE_OR_VERIFY_COSMOS_ADDRESS',
|
||||
DEPOSIT_STATUS: 'ONBOARDING.DEPOSIT_STATUS',
|
||||
DONT_MISS: 'ONBOARDING.DONT_MISS',
|
||||
ENABLE_API: 'ONBOARDING.ENABLE_API',
|
||||
ENABLE_TRADING: 'ONBOARDING.ENABLE_TRADING',
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
/** implemented in useNotificationTypes */
|
||||
export enum NotificationType {
|
||||
OrderStatusChanged = 'OrderStatusChanged',
|
||||
SquidTransfer = 'SquidTransfer',
|
||||
}
|
||||
|
||||
export enum NotificationComponentType {}
|
||||
|
||||
export type NotificationId = string | number
|
||||
export type NotificationId = string | number;
|
||||
|
||||
export type NotificationTypeConfig<_NotificationId extends NotificationId = string, NotificationUpdateKey = any> = {
|
||||
export type NotificationTypeConfig<
|
||||
_NotificationId extends NotificationId = string,
|
||||
NotificationUpdateKey = any
|
||||
> = {
|
||||
type: NotificationType;
|
||||
|
||||
/** React hook to trigger notifications based on app state */
|
||||
/** React hook to trigger notifications based on app state */
|
||||
useTrigger: (_: {
|
||||
trigger: (
|
||||
/** Unique ID for the triggered notification */
|
||||
@@ -30,7 +34,7 @@ export type NotificationTypeConfig<_NotificationId extends NotificationId = stri
|
||||
* @param true (default): Notification initialized with status NotificationStatus.Triggered
|
||||
* @param false: Notification initialized with status NotificationStatus.Cleared
|
||||
*/
|
||||
isNew?: boolean,
|
||||
isNew?: boolean
|
||||
) => void;
|
||||
|
||||
lastUpdated: number;
|
||||
@@ -58,7 +62,10 @@ export enum NotificationStatus {
|
||||
}
|
||||
|
||||
/** Notification state. Serialized and cached into localStorage. */
|
||||
export type Notification<_NotificationId extends NotificationId = string, NotificationUpdateKey = any> = {
|
||||
export type Notification<
|
||||
_NotificationId extends NotificationId = string,
|
||||
NotificationUpdateKey = any
|
||||
> = {
|
||||
id: _NotificationId;
|
||||
type: NotificationType;
|
||||
status: NotificationStatus;
|
||||
@@ -74,7 +81,7 @@ export type NotificationDisplayData = {
|
||||
|
||||
title?: string;
|
||||
|
||||
description?: string;
|
||||
description?: React.ReactNode;
|
||||
|
||||
actionDescription?: string;
|
||||
|
||||
@@ -101,3 +108,12 @@ export type NotificationDisplayData = {
|
||||
*/
|
||||
toastDuration?: number;
|
||||
};
|
||||
|
||||
// Notification types
|
||||
export type TransferNotifcation = {
|
||||
txHash: string;
|
||||
toChainId: string;
|
||||
fromChainId?: string;
|
||||
toAmount?: number;
|
||||
triggeredAt?: number;
|
||||
};
|
||||
|
||||
@@ -5,17 +5,26 @@ import { groupBy } from 'lodash';
|
||||
import { AbacusOrderStatus, ORDER_SIDES, ORDER_STATUS_STRINGS } from '@/constants/abacus';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications';
|
||||
import { type NotificationTypeConfig, type TransferNotifcation, NotificationType } from '@/constants/notifications';
|
||||
import { ORDER_SIDE_STRINGS, TRADE_TYPE_STRINGS, TradeTypes } from '@/constants/trade';
|
||||
|
||||
import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelectors';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { TransferStatusToast } from '@/components/TransferStatus';
|
||||
|
||||
import {
|
||||
getSquidTransfers,
|
||||
getSubaccountFills,
|
||||
getSubaccountOrders,
|
||||
} from '@/state/accountSelectors';
|
||||
import { openDialog } from '@/state/dialogs';
|
||||
|
||||
import { OrderStatusIcon } from '@/views/OrderStatusIcon';
|
||||
|
||||
import { useStringGetter } from './useStringGetter';
|
||||
|
||||
export const notificationTypes = [
|
||||
export const notificationTypes = (
|
||||
transferNotifications: TransferNotifcation[]
|
||||
) => [
|
||||
{
|
||||
type: NotificationType.OrderStatusChanged,
|
||||
|
||||
@@ -93,4 +102,32 @@ export const notificationTypes = [
|
||||
};
|
||||
},
|
||||
} as NotificationTypeConfig<string, [string, number]>,
|
||||
{
|
||||
type: NotificationType.SquidTransfer,
|
||||
useTrigger: ({ trigger, lastUpdated }) => {
|
||||
useEffect(() => {
|
||||
for (const transfer of transferNotifications) {
|
||||
const transferHash = transfer.txHash;
|
||||
trigger(
|
||||
transferHash,
|
||||
{
|
||||
icon: <Icon iconName={IconName.Clock} />,
|
||||
title: `Deposit in progress...`,
|
||||
description: (
|
||||
<TransferStatusToast
|
||||
txHash={transferHash}
|
||||
toChainId={transfer.toChainId}
|
||||
fromChainId={transfer.fromChainId}
|
||||
toAmount={transfer.toAmount}
|
||||
triggeredAt={transfer.triggeredAt}
|
||||
/>
|
||||
),
|
||||
toastSensitivity: 'foreground',
|
||||
},
|
||||
[],
|
||||
);
|
||||
}
|
||||
}, [transferNotifications]);
|
||||
},
|
||||
},
|
||||
] satisfies NotificationTypeConfig[];
|
||||
|
||||
@@ -5,13 +5,15 @@ import {
|
||||
type Notification,
|
||||
type NotificationDisplayData,
|
||||
type Notifications,
|
||||
type TransferNotifcation,
|
||||
NotificationStatus,
|
||||
} from '@/constants/notifications';
|
||||
|
||||
import { useLocalStorage } from './useLocalStorage';
|
||||
import { notificationTypes } from './useNotificationTypes';
|
||||
import { notificationTypes as notificationTypesFactory } from './useNotificationTypes';
|
||||
|
||||
import { renderSvgToDataUrl } from '../lib/renderSvgToDataUrl';
|
||||
import { set } from 'lodash';
|
||||
|
||||
const NotificationsContext = createContext<ReturnType<typeof useNotificationsContext> | undefined>(
|
||||
undefined
|
||||
@@ -37,6 +39,20 @@ const useNotificationsContext = () => {
|
||||
defaultValue: Date.now(),
|
||||
});
|
||||
|
||||
// Front-end only notifications
|
||||
const [transferNotifications, setTransferNotifications] = useState<TransferNotifcation[]>([]);
|
||||
|
||||
const addTransferNotification = useCallback(
|
||||
(notification: TransferNotifcation) =>
|
||||
setTransferNotifications([...transferNotifications, notification]),
|
||||
[transferNotifications]
|
||||
);
|
||||
|
||||
const notificationTypes = useMemo(
|
||||
() => notificationTypesFactory(transferNotifications),
|
||||
[transferNotifications]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setNotificationsLastUpdated(Date.now());
|
||||
}, [notifications]);
|
||||
@@ -106,7 +122,10 @@ const useNotificationsContext = () => {
|
||||
updateKey,
|
||||
} as Notification);
|
||||
|
||||
updateStatus(notification, isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared);
|
||||
updateStatus(
|
||||
notification,
|
||||
isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared
|
||||
);
|
||||
}
|
||||
|
||||
// updateKey changed - update existing notification
|
||||
@@ -141,7 +160,9 @@ const useNotificationsContext = () => {
|
||||
// Push notifications
|
||||
const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({
|
||||
key: LocalStorageKey.PushNotificationsEnabled,
|
||||
defaultValue: Boolean(globalThis.Notification && globalThis.Notification.permission === 'granted'),
|
||||
defaultValue: Boolean(
|
||||
globalThis.Notification && globalThis.Notification.permission === 'granted'
|
||||
),
|
||||
});
|
||||
const [isEnablingPush, setIsEnablingPush] = useState(false);
|
||||
|
||||
@@ -234,6 +255,8 @@ const useNotificationsContext = () => {
|
||||
// Menu state
|
||||
isMenuOpen,
|
||||
setIsMenuOpen,
|
||||
|
||||
addTransferNotification,
|
||||
};
|
||||
};
|
||||
// }
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Squid } from '@0xsquid/sdk';
|
||||
import { USDC_DENOM } from '@dydxprotocol/v4-client';
|
||||
|
||||
import { DydxV4Network, isDydxV4Network } from '@/constants/networks';
|
||||
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
const SQUID_BASE_URL: Record<DydxV4Network, string | undefined> = {
|
||||
[DydxV4Network.V4Testnet2]: 'https://squid-api-git-feat-cosmos-maintestnet-0xsquid.vercel.app',
|
||||
[DydxV4Network.V4Staging]: undefined,
|
||||
[DydxV4Network.V4Local]: undefined,
|
||||
[DydxV4Network.V4Mainnet]: 'https://api.0xsquid.com',
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Deposit route defaults for IBC Testnet
|
||||
* */
|
||||
export const SQUID_DEPOSIT_ROUTE_DEFAULTS = {
|
||||
toChain: 'dydx-testnet-2',
|
||||
toToken: USDC_DENOM,
|
||||
enableForecall: false, // instant execution service, defaults to true
|
||||
};
|
||||
|
||||
export const SQUID_WITHDRAW_ROUTE_DEFAULTS = {
|
||||
fromChain: 'dydx-testnet-2',
|
||||
fromToken: USDC_DENOM,
|
||||
enableForecall: false, // instant execution service, defaults to true
|
||||
};
|
||||
|
||||
const useSquidContext = () => {
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
const [_, setInitialized] = useState(false);
|
||||
|
||||
const initializeClient = async () => {
|
||||
setInitialized(false);
|
||||
if (!squid) return;
|
||||
await squid.init();
|
||||
setInitialized(true);
|
||||
};
|
||||
|
||||
const squid = useMemo(
|
||||
() =>
|
||||
isDydxV4Network(selectedNetwork)
|
||||
? new Squid({ baseUrl: SQUID_BASE_URL[selectedNetwork as DydxV4Network] })
|
||||
: undefined,
|
||||
[selectedNetwork]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (squid) {
|
||||
initializeClient();
|
||||
}
|
||||
}, [squid]);
|
||||
|
||||
return squid;
|
||||
};
|
||||
|
||||
type SquidContextType = ReturnType<typeof useSquidContext>;
|
||||
const SquidContext = createContext<SquidContextType>(undefined);
|
||||
SquidContext.displayName = '0xSquid';
|
||||
|
||||
export const SquidProvider = ({ ...props }) => (
|
||||
<SquidContext.Provider value={useSquidContext()} {...props} />
|
||||
);
|
||||
|
||||
export const useSquid = () => useContext(SquidContext);
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
PerpetualState,
|
||||
PerpetualStateChanges,
|
||||
SubaccountOrder,
|
||||
TransferStatus,
|
||||
} from '@/constants/abacus';
|
||||
|
||||
import { Changes } from '@/constants/abacus';
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
setSubaccount,
|
||||
setTransfers,
|
||||
setWallet,
|
||||
setTransferStatuses,
|
||||
} from '@/state/account';
|
||||
|
||||
import { setApiState } from '@/state/app';
|
||||
@@ -68,6 +70,14 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
|
||||
dispatch(setInputs(updatedState.input));
|
||||
}
|
||||
|
||||
if (changes.has(Changes.transferStatuses) && updatedState.transferStatuses) {
|
||||
const transferStatuses: Record<string, TransferStatus> = {};
|
||||
updatedState.transferStatuses.forEach((transferStatus) => {
|
||||
transferStatuses[transferStatus.k] = transferStatus.v;
|
||||
});
|
||||
dispatch(setTransferStatuses(transferStatuses));
|
||||
}
|
||||
|
||||
if (changes.has(Changes.wallet)) {
|
||||
dispatch(setWallet(updatedState.wallet));
|
||||
}
|
||||
|
||||
@@ -72,3 +72,9 @@ export const getTimeTillNextUnit = (unit: 'minute' | 'hour' | 'day') => {
|
||||
};
|
||||
|
||||
export const getTimeString = (time: number) => time.toString().padStart(2, '0');
|
||||
|
||||
export const formatSeconds = (seconds: number) => {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return `${getTimeString(minutes)}:${getTimeString(remainingSeconds)}`;
|
||||
}
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
"HEDGIES": "Hedgies",
|
||||
"HIDE_SECTION": "Hide section",
|
||||
"HIDE": "Hide",
|
||||
"HIDE_DETAILS": "Hide details",
|
||||
"HISTORY": "History",
|
||||
"INCREASED": "increased",
|
||||
"INCREASING": "Increasing",
|
||||
@@ -388,6 +389,7 @@
|
||||
"CONNECTING_SUBTITLE": "Check your wallet for a connection request. Connecting to dYdX is free and does not affect your funds.",
|
||||
"COULD_NOT_FIND_AFFILIATE": "We couldn't find this affiliate, please check the link.",
|
||||
"CREATE_OR_VERIFY_COSMOS_ADDRESS": "Create or verify Cosmos address.",
|
||||
"DEPOSIT_STATUS": "Your deposit of {AMOUNT_USD} will be available in approximately {ESTIMATED_DURATION}.",
|
||||
"DONT_MISS": "Don't miss your chance to get a {DISCOUNT} on trading fees!",
|
||||
"ENABLE_API": "Enable secure access to our API for lightning quick trading",
|
||||
"ENABLE_TRADING": "Enable trading",
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
SubaccountTransfers,
|
||||
HistoricalPnlPeriods,
|
||||
SubAccountHistoricalPNLs,
|
||||
TransferStatus,
|
||||
} from '@/constants/abacus';
|
||||
|
||||
import { OnboardingGuard, OnboardingState } from '@/constants/account';
|
||||
@@ -35,6 +36,16 @@ export type AccountState = {
|
||||
wallet?: Nullable<Wallet>;
|
||||
walletType?: WalletType;
|
||||
historicalPnlPeriod?: HistoricalPnlPeriods;
|
||||
squidTransfer?: Record<
|
||||
string,
|
||||
{
|
||||
hash: string;
|
||||
toChainId: string;
|
||||
fromChainId?: string;
|
||||
toAmount?: number;
|
||||
status?: TransferStatus;
|
||||
}
|
||||
>;
|
||||
};
|
||||
|
||||
const initialState: AccountState = {
|
||||
@@ -148,6 +159,24 @@ export const accountSlice = createSlice({
|
||||
viewedOrders: (state) => {
|
||||
state.hasUnseenOrderUpdates = false;
|
||||
},
|
||||
addSquidTransfer: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
hash: string;
|
||||
toChainId: string;
|
||||
fromChainId?: string;
|
||||
toAmount?: number;
|
||||
}>
|
||||
) => {
|
||||
if (!state.squidTransfer) state.squidTransfer = {};
|
||||
state.squidTransfer[action.payload.hash] = { ...action.payload };
|
||||
},
|
||||
setTransferStatuses: (state, action: PayloadAction<Record<string, TransferStatus>>) => {
|
||||
Object.keys(action.payload).forEach((key) => {
|
||||
if (state.squidTransfer && state.squidTransfer[key])
|
||||
state.squidTransfer[key].status = action.payload[key];
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -165,4 +194,6 @@ export const {
|
||||
removeUncommittedOrderClientId,
|
||||
viewedFills,
|
||||
viewedOrders,
|
||||
addSquidTransfer,
|
||||
setTransferStatuses,
|
||||
} = accountSlice.actions;
|
||||
|
||||
@@ -319,3 +319,8 @@ export const getUserStats = (state: RootState) => ({
|
||||
makerVolume30D: state.account?.wallet?.user?.makerVolume30D,
|
||||
takerVolume30D: state.account?.wallet?.user?.takerVolume30D,
|
||||
});
|
||||
|
||||
/**
|
||||
* @returns Get the current squid transfers
|
||||
*/
|
||||
export const getSquidTransfers = (state: RootState) => state.account?.squidTransfer;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
import type {
|
||||
InputError,
|
||||
Inputs,
|
||||
Nullable,
|
||||
TradeInputs,
|
||||
ClosePositionInputs,
|
||||
TransferInputs,
|
||||
} from '@/constants/abacus';
|
||||
|
||||
export interface InputsState {
|
||||
current?: Nullable<string>;
|
||||
inputErrors?: Nullable<InputError[]>;
|
||||
tradeInputs?: Nullable<TradeInputs>;
|
||||
closePositionInputs?: Nullable<ClosePositionInputs>;
|
||||
transferInputs?: Nullable<TransferInputs>;
|
||||
}
|
||||
|
||||
const initialState: InputsState = {
|
||||
current: undefined,
|
||||
inputErrors: undefined,
|
||||
tradeInputs: undefined,
|
||||
transferInputs: undefined,
|
||||
};
|
||||
|
||||
export const inputsSlice = createSlice({
|
||||
name: 'Inputs',
|
||||
initialState,
|
||||
reducers: {
|
||||
setInputs: (state, action: PayloadAction<Nullable<Inputs>>) => {
|
||||
const { current, errors, trade, closePosition, transfer } = action.payload || {};
|
||||
|
||||
return {
|
||||
...state,
|
||||
current: current?.rawValue,
|
||||
inputErrors: errors?.toArray(),
|
||||
tradeInputs: trade,
|
||||
closePositionInputs: closePosition,
|
||||
transferInputs: transfer,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { setInputs } = inputsSlice.actions;
|
||||
@@ -0,0 +1,113 @@
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
import type { RootState } from './_store';
|
||||
|
||||
/**
|
||||
* @param state
|
||||
* @returns TradeInputs
|
||||
*/
|
||||
export const getInputTradeData = (state: RootState) => state.inputs.tradeInputs;
|
||||
|
||||
/**
|
||||
* @param state
|
||||
* @returns Size data in TradeInputs
|
||||
*/
|
||||
export const getInputTradeSizeData = (state: RootState) => state.inputs.tradeInputs?.size;
|
||||
|
||||
/**
|
||||
* @param state
|
||||
* @returns AbacusOrderSide in TradeInputs
|
||||
*/
|
||||
export const getTradeSide = (state: RootState) => state.inputs.tradeInputs?.side;
|
||||
|
||||
/**
|
||||
* @param state
|
||||
* @returns TradeInputs options (config for what TradeInputFields to render)
|
||||
*/
|
||||
export const getInputTradeOptions = (state: RootState) => state.inputs.tradeInputs?.options;
|
||||
|
||||
/**
|
||||
* @param state
|
||||
* @returns ValidationErrors of the current Input type (Trade or Transfer)
|
||||
*/
|
||||
export const getInputErrors = (state: RootState) => state.inputs.inputErrors;
|
||||
|
||||
/**
|
||||
* @param state
|
||||
* @returns trade or closePosition transfer, depending on which form was last edited.
|
||||
*/
|
||||
export const getCurrentInput = (state: RootState) => state.inputs.current;
|
||||
|
||||
/**
|
||||
* @param state
|
||||
* @returns input errors for Trade
|
||||
*/
|
||||
export const getTradeInputErrors = (state: RootState) => {
|
||||
const currentInput = state.inputs.current;
|
||||
return currentInput === 'trade' ? getInputErrors(state) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* @param state
|
||||
* @returns input errors for Transfer
|
||||
*/
|
||||
export const getTransferInputErrors = (state: RootState) => {
|
||||
const currentInput = state.inputs.current;
|
||||
return currentInput === 'transfer' ? getInputErrors(state) : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* @param state
|
||||
* @returns TransferInputs
|
||||
*/
|
||||
export const getTransferInputs = (state: RootState) => state.inputs.transferInputs;
|
||||
|
||||
/**
|
||||
* @param state
|
||||
* @returns ClosePositionInputs
|
||||
*/
|
||||
export const getInputClosePositionData = (state: RootState) => state.inputs.closePositionInputs;
|
||||
|
||||
/**
|
||||
* @returns Data needed for the TradeForm (price, size, summary, input render options, and errors/input validation)
|
||||
*/
|
||||
export const useTradeFormData = () => {
|
||||
return useSelector(
|
||||
createSelector(
|
||||
[getInputTradeData, getInputTradeOptions, getTradeInputErrors],
|
||||
(tradeData, tradeOptions, tradeErrors) => {
|
||||
const { price, size, summary } = tradeData || {};
|
||||
|
||||
const {
|
||||
needsLimitPrice,
|
||||
needsTrailingPercent,
|
||||
needsTriggerPrice,
|
||||
executionOptions,
|
||||
needsGoodUntil,
|
||||
needsPostOnly,
|
||||
needsReduceOnly,
|
||||
timeInForceOptions,
|
||||
} = tradeOptions || {};
|
||||
|
||||
return {
|
||||
price,
|
||||
size,
|
||||
summary,
|
||||
|
||||
needsLimitPrice,
|
||||
needsTrailingPercent,
|
||||
needsTriggerPrice,
|
||||
executionOptions,
|
||||
needsGoodUntil,
|
||||
needsPostOnly,
|
||||
needsReduceOnly,
|
||||
timeInForceOptions,
|
||||
|
||||
tradeErrors,
|
||||
};
|
||||
}
|
||||
),
|
||||
shallowEqual
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useInterval } from '@/hooks';
|
||||
|
||||
import { getTimeTillNextUnit, getTimeString } from '@/lib/timeUtils';
|
||||
import { getTimeTillNextUnit, formatSeconds } from '@/lib/timeUtils';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
|
||||
export const NextFundingTimer = () => {
|
||||
@@ -18,7 +18,7 @@ export const NextFundingTimer = () => {
|
||||
type={OutputType.Text}
|
||||
value={
|
||||
secondsLeft !== undefined
|
||||
? `${getTimeString(Math.floor(secondsLeft / 60))}:${getTimeString(secondsLeft % 60)}`
|
||||
? formatSeconds(secondsLeft)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type FormEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { type NumberFormatValues } from 'react-number-format';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { useDispatch, shallowEqual, useSelector } from 'react-redux';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client';
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
@@ -17,6 +17,7 @@ import { NumberSign } from '@/constants/numbers';
|
||||
|
||||
import { useAccounts, useDebounce, useStringGetter } from '@/hooks';
|
||||
import { useAccountBalance } from '@/hooks/useAccountBalance';
|
||||
import { useNotifications } from '@/hooks/useNotifications';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { formMixins } from '@/styles/formMixins';
|
||||
@@ -49,12 +50,15 @@ type DepositFormProps = {
|
||||
|
||||
export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const dispatch = useDispatch();
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { signerWagmi } = useAccounts();
|
||||
|
||||
const { requestPayload, token, chain: chainIdStr, resources } = useSelector(getTransferInputs, shallowEqual) || {};
|
||||
const { addTransferNotification } = useNotifications();
|
||||
|
||||
const { requestPayload, token, chain: chainIdStr, resources, summary } = useSelector(getTransferInputs, shallowEqual) || {};
|
||||
const chainId = chainIdStr ? parseInt(chainIdStr) : undefined
|
||||
|
||||
// User inputs
|
||||
@@ -100,6 +104,13 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
value: TransferType.deposit.rawValue,
|
||||
});
|
||||
|
||||
addTransferNotification({
|
||||
txHash: '0x4a8bb0893f0bfe5b6bf4eab9ee1ff4a02dd088e9946f3404a6b9e9df5205b057',
|
||||
toChainId: TESTNET_CHAIN_ID,
|
||||
fromChainId: '5',
|
||||
toAmount: 2.5832,
|
||||
});
|
||||
|
||||
return () => {
|
||||
abacusStateManager.clearTransferInputValues();
|
||||
abacusStateManager.setTransferValue({
|
||||
@@ -189,11 +200,17 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
|
||||
if (txHash) {
|
||||
setTransactionHash(txHash);
|
||||
abacusStateManager.setTransferStatus({
|
||||
hash: txHash,
|
||||
addTransferNotification({
|
||||
txHash,
|
||||
toChainId: TESTNET_CHAIN_ID,
|
||||
fromChainId: chainId?.toString(),
|
||||
fromChainId: chainIdStr || undefined,
|
||||
toAmount: summary?.usdcSize || undefined,
|
||||
});
|
||||
// abacusStateManager.setTransferStatus({
|
||||
// hash: txHash,
|
||||
// toChainId: TESTNET_CHAIN_ID,
|
||||
// fromChainId: chainId?.toString(),
|
||||
// });
|
||||
abacusStateManager.clearTransferInputValues();
|
||||
setFromAmount('');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user