This commit is contained in:
Bill He
2023-08-28 15:32:41 -07:00
parent e2596b0654
commit e656c219cd
20 changed files with 770 additions and 1781 deletions
+1 -1
View File
@@ -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",
+189 -1752
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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';
+8 -8
View File
@@ -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']) & {
+174
View File
@@ -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;
}
`;
+2
View File
@@ -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;
+2
View File
@@ -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',
+22 -6
View File
@@ -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;
};
+40 -3
View File
@@ -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[];
+26 -3
View File
@@ -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,
};
};
// }
+68
View File
@@ -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);
+10
View File
@@ -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));
}
+6
View File
@@ -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)}`;
}
+2
View File
@@ -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",
+31
View File
@@ -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;
+5
View File
@@ -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;
+46
View File
@@ -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;
+113
View File
@@ -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
);
};
+2 -2
View File
@@ -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('');
}