Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dfe36998d |
@@ -30,16 +30,16 @@
|
||||
},
|
||||
"packageManager": "pnpm@8.6.6",
|
||||
"dependencies": {
|
||||
"@0xsquid/sdk": "^1.10.0",
|
||||
"@0xsquid/sdk": "^1.7.2",
|
||||
"@cosmjs/amino": "^0.31.0",
|
||||
"@cosmjs/crypto": "^0.31.0",
|
||||
"@cosmjs/encoding": "^0.31.0",
|
||||
"@cosmjs/proto-signing": "^0.31.0",
|
||||
"@cosmjs/stargate": "^0.31.0",
|
||||
"@cosmjs/tendermint-rpc": "^0.31.0",
|
||||
"@dydxprotocol/abacus": "^0.4.8",
|
||||
"@dydxprotocol/abacus": "^0.4.1",
|
||||
"@dydxprotocol/v4-client-js": "^0.32.0",
|
||||
"@dydxprotocol/v4-localization": "^0.0.25",
|
||||
"@dydxprotocol/v4-localization": "^0.0.21",
|
||||
"@ethersproject/providers": "^5.7.2",
|
||||
"@js-joda/core": "^5.5.3",
|
||||
"@radix-ui/react-collapsible": "^1.0.3",
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 817 B |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 973 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
@@ -13,9 +13,8 @@ import { AccountsProvider } from '@/hooks/useAccounts';
|
||||
import { DialogAreaProvider, useDialogArea } from './hooks/useDialogArea';
|
||||
import { LocaleProvider } from './hooks/useLocaleSeparators';
|
||||
import { NotificationsProvider } from './hooks/useNotifications';
|
||||
import { LocalNotificationsProvider } from './hooks/useLocalNotifications';
|
||||
import { SubaccountProvider } from './hooks/useSubaccount';
|
||||
import { SquidProvider } from '@/hooks/useSquid';
|
||||
import { SquidProvider } from '@/hooks/useSquidRouter';
|
||||
|
||||
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
||||
|
||||
@@ -99,31 +98,29 @@ const Content = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const wrapProvider = (Component: React.ComponentType<any>, props?: any) => {
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<Component {...props}>{children}</Component>
|
||||
);
|
||||
};
|
||||
|
||||
const providers = [
|
||||
wrapProvider(QueryClientProvider, { client: queryClient }),
|
||||
wrapProvider(GrazProvider),
|
||||
wrapProvider(WagmiConfig, { config }),
|
||||
wrapProvider(LocaleProvider),
|
||||
wrapProvider(DydxProvider),
|
||||
wrapProvider(AccountsProvider),
|
||||
wrapProvider(SubaccountProvider),
|
||||
wrapProvider(SquidProvider),
|
||||
wrapProvider(LocalNotificationsProvider),
|
||||
wrapProvider(NotificationsProvider),
|
||||
wrapProvider(DialogAreaProvider),
|
||||
];
|
||||
|
||||
const App = () => {
|
||||
return [...providers].reverse().reduce((children, Provider) => {
|
||||
return <Provider>{children}</Provider>;
|
||||
}, <Content />);
|
||||
};
|
||||
const App = () => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<GrazProvider>
|
||||
<WagmiConfig config={config}>
|
||||
<LocaleProvider>
|
||||
<DydxProvider>
|
||||
<AccountsProvider>
|
||||
<SubaccountProvider>
|
||||
<NotificationsProvider>
|
||||
<DialogAreaProvider>
|
||||
<SquidProvider>
|
||||
<Content />
|
||||
</SquidProvider>
|
||||
</DialogAreaProvider>
|
||||
</NotificationsProvider>
|
||||
</SubaccountProvider>
|
||||
</AccountsProvider>
|
||||
</DydxProvider>
|
||||
</LocaleProvider>
|
||||
</WagmiConfig>
|
||||
</GrazProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
@@ -195,9 +192,7 @@ Styled.NotificationsToastArea = styled(NotificationsToastArea)`
|
||||
`;
|
||||
|
||||
Styled.DialogArea = styled.aside`
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
overflow: clip;
|
||||
${layoutMixins.noPointerEvents}
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
[
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "balance",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": false,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"constant": true,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"payable": false,
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"payable": true,
|
||||
"stateMutability": "payable",
|
||||
"type": "fallback"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
@@ -80,9 +80,9 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
||||
{group.items.map((item) => (
|
||||
<Fragment key={item.value}>
|
||||
<Styled.Item
|
||||
value={[group.groupLabel, item.value, item.description, item.label, item.tag]
|
||||
.filter(Boolean)
|
||||
.join('|')}
|
||||
// value={item.value} // search by both value and textContent
|
||||
// value={[group.groupLabel, item.label, item.tag].filter(Boolean).join('|')} // exclude item.value from searchable terms (not guaranteed to be unique)
|
||||
value={[group.groupLabel, item.value, item.description, item.label, item.tag].filter(Boolean).join('|')}
|
||||
onSelect={() => {
|
||||
if (item.subitems) {
|
||||
// open submenu
|
||||
@@ -114,15 +114,9 @@ export const ComboboxMenu = <MenuItemValue extends string, MenuGroupValue extend
|
||||
item.subitems?.map((subitem) => (
|
||||
<Fragment key={subitem.value}>
|
||||
<Styled.Item
|
||||
value={[
|
||||
group.groupLabel,
|
||||
item.value,
|
||||
item.description,
|
||||
item.label,
|
||||
item.tag,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('|')}
|
||||
// value={subitem.value} // search by both value and textContent
|
||||
// value={[group.groupLabel, item.label, subitem.label, subitem.tag].filter(Boolean).join('|')}
|
||||
value={[group.groupLabel, item.value, item.description, item.label, item.tag].filter(Boolean).join('|')}
|
||||
onSelect={() => {
|
||||
subitem.onSelect?.(subitem.value);
|
||||
onItemSelected?.();
|
||||
@@ -174,7 +168,7 @@ Styled.Command = styled(Command)<{ $withStickyLayout?: boolean }>`
|
||||
background-color: var(--comboboxMenu-backgroundColor);
|
||||
border-radius: inherit;
|
||||
|
||||
input:focus-visible {
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -278,7 +272,7 @@ Styled.Item = styled(Command.Item)`
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
&[aria-disabled='true'] {
|
||||
&[aria-disabled="true"] {
|
||||
opacity: 0.75;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -90,7 +90,10 @@ export const Dialog = ({
|
||||
return (
|
||||
<Root modal={showOverlay} open={isOpen} onOpenChange={setIsOpen}>
|
||||
{slotTrigger && <Trigger asChild>{slotTrigger}</Trigger>}
|
||||
<DialogPortal withPortal={placement !== DialogPlacement.Inline} container={portalContainer}>
|
||||
<DialogPortal
|
||||
withPortal={placement !== DialogPlacement.Inline}
|
||||
container={portalContainer}
|
||||
>
|
||||
{showOverlay && <Styled.Overlay />}
|
||||
<Styled.Container
|
||||
placement={placement}
|
||||
@@ -422,7 +425,7 @@ Styled.Close = styled(Close)`
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus-visible {
|
||||
&:focus {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ Styled.ComboboxMenu = styled(ComboboxMenu)`
|
||||
|
||||
--stickyArea1-topHeight: var(--form-input-height);
|
||||
|
||||
input:focus-visible {
|
||||
input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -743,7 +743,7 @@ Styled.Tr = styled.tr<{
|
||||
cursor: pointer;
|
||||
|
||||
&:hover,
|
||||
&:focus-visible,
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
--tableRow-currentBackgroundColor: var(--tableRow-hover-backgroundColor);
|
||||
filter: brightness(1.1);
|
||||
@@ -753,7 +753,7 @@ Styled.Tr = styled.tr<{
|
||||
${({ withFocusStickyRows }) =>
|
||||
withFocusStickyRows &&
|
||||
css`
|
||||
&:focus-visible,
|
||||
&:focus,
|
||||
&:focus-within {
|
||||
${layoutMixins.sticky}
|
||||
z-index: 1;
|
||||
|
||||
@@ -17,7 +17,6 @@ type ElementProps = {
|
||||
slotIcon?: React.ReactNode;
|
||||
slotTitle?: React.ReactNode;
|
||||
slotDescription?: React.ReactNode;
|
||||
slotCustomContent?: React.ReactNode;
|
||||
slotAction?: React.ReactNode;
|
||||
actionDescription?: string;
|
||||
actionAltText?: string;
|
||||
@@ -34,7 +33,6 @@ export const Toast = ({
|
||||
slotIcon,
|
||||
slotTitle,
|
||||
slotDescription,
|
||||
slotCustomContent,
|
||||
slotAction,
|
||||
actionDescription = '',
|
||||
actionAltText = actionDescription,
|
||||
@@ -80,13 +78,12 @@ export const Toast = ({
|
||||
|
||||
<$Title>{slotTitle}</$Title>
|
||||
</$Header>
|
||||
{!slotCustomContent && <$Description>{slotDescription}</$Description>}
|
||||
{slotCustomContent}
|
||||
{actionDescription && (
|
||||
<$Action asChild altText={actionAltText}>
|
||||
{slotAction}
|
||||
</$Action>
|
||||
)}
|
||||
|
||||
<$Description>{slotDescription}</$Description>
|
||||
|
||||
<$Action asChild altText={actionAltText}>
|
||||
{slotAction}
|
||||
</$Action>
|
||||
</$Container>
|
||||
</div>
|
||||
</$Root>
|
||||
@@ -121,7 +118,8 @@ const $Root = styled(Root)`
|
||||
align-items: end;
|
||||
transform-origin: left bottom;
|
||||
|
||||
animation: ${keyframes`
|
||||
animation:
|
||||
${keyframes`
|
||||
from {
|
||||
/* scale: 0; */
|
||||
grid-template-rows: 0fr; // height transition
|
||||
@@ -152,7 +150,7 @@ const $Root = styled(Root)`
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -205,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']) & {
|
||||
|
||||
@@ -91,7 +91,7 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
||||
Styled.Content = styled(Content)`
|
||||
z-index: 1;
|
||||
|
||||
&:focus-visible {
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -40,10 +40,7 @@ export type AbacusFileSystemProtocol = Omit<
|
||||
Abacus.exchange.dydx.abacus.protocols.FileSystemProtocol,
|
||||
'__doNotUseOrImplementIt'
|
||||
>;
|
||||
export type AbacusTrackingProtocol = Omit<
|
||||
Abacus.exchange.dydx.abacus.protocols.TrackingProtocol,
|
||||
'__doNotUseOrImplementIt'
|
||||
>;
|
||||
|
||||
export type FileLocation = Abacus.exchange.dydx.abacus.protocols.FileLocation;
|
||||
export type ThreadingType = Abacus.exchange.dydx.abacus.protocols.ThreadingType;
|
||||
export const CoroutineTimer = Abacus.exchange.dydx.abacus.utils.CoroutineTimer;
|
||||
|
||||
@@ -49,11 +49,11 @@ export enum EvmDerivedAccountStatus {
|
||||
Derived,
|
||||
}
|
||||
|
||||
import type { DydxAddress, EvmAddress } from './wallets';
|
||||
import type { DydxAddress, EthereumAddress } from './wallets';
|
||||
|
||||
export type EvmDerivedAddresses = {
|
||||
version?: string;
|
||||
[EvmAddress: EvmAddress]: {
|
||||
[EthereumAddress: EthereumAddress]: {
|
||||
encryptedSignature?: string;
|
||||
dydxAddress?: DydxAddress;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { SupportedLocales } from './localization';
|
||||
import type { DydxNetwork } from './networks';
|
||||
import type { OnboardingState, OnboardingSteps } from './account';
|
||||
import type { DydxAddress, WalletType, WalletConnectionType, EvmAddress } from './wallets';
|
||||
import type { DydxAddress, WalletType, WalletConnectionType, EthereumAddress } from './wallets';
|
||||
import type { DialogTypes } from './dialogs';
|
||||
import type { TradeTypes } from './trade';
|
||||
import type { AbacusApiStatus, HumanReadablePlaceOrderPayload } from './abacus';
|
||||
@@ -40,7 +40,7 @@ export type AnalyticsUserPropertyValue<T extends AnalyticsUserProperty> =
|
||||
: T extends AnalyticsUserProperty.WalletConnectionType
|
||||
? WalletConnectionType | undefined
|
||||
: T extends AnalyticsUserProperty.WalletAddress
|
||||
? EvmAddress | DydxAddress | undefined
|
||||
? EthereumAddress | DydxAddress | undefined
|
||||
: // Account
|
||||
T extends AnalyticsUserProperty.DydxAddress
|
||||
? DydxAddress | undefined
|
||||
@@ -67,7 +67,6 @@ export enum AnalyticsEvent {
|
||||
// Onboarding
|
||||
OnboardingStepChanged = 'OnboardingStepChanged',
|
||||
OnboardingAccountDerived = 'OnboardingAccountDerived',
|
||||
OnboardingWalletIsNonDeterministic = 'OnboardingWalletIsNonDeterministic',
|
||||
|
||||
// Transfers
|
||||
TransferFaucet = 'TransferFaucet',
|
||||
|
||||
@@ -12,7 +12,6 @@ export enum LocalStorageKey {
|
||||
NotificationsLastUpdated = 'dydx.NotificationsLastUpdated',
|
||||
PushNotificationsEnabled = 'dydx.PushNotificationsEnabled',
|
||||
PushNotificationsLastUpdated = 'dydx.PushNotificationsLastUpdated',
|
||||
TransferNotifications = 'dydx.TransferNotifications',
|
||||
|
||||
// UI State
|
||||
LastViewedMarket = 'dydx.LastViewedMarket',
|
||||
|
||||
@@ -34,8 +34,6 @@ export const STRING_KEYS = {
|
||||
...WARNINGS_STRING_KEYS,
|
||||
};
|
||||
|
||||
export type StringKey = keyof typeof STRING_KEYS;
|
||||
|
||||
export type LocaleData = typeof EN_LOCALE_DATA;
|
||||
|
||||
export type StringGetterFunction = (a: {
|
||||
@@ -90,17 +88,17 @@ export type TooltipStrings = {
|
||||
};
|
||||
|
||||
export const ORDER_ERROR_CODE_MAP: { [key: number]: string } = {
|
||||
2000: STRING_KEYS['2000_FILL_OR_KILL_ORDER_COULD_NOT_BE_FULLY_FILLED'],
|
||||
2001: STRING_KEYS['2001_REDUCE_ONLY_WOULD_INCREASE_POSITION_SIZE'],
|
||||
2002: STRING_KEYS['2002_REDUCE_ONLY_WOULD_CHANGE_POSITION_SIDE'],
|
||||
2003: STRING_KEYS['2003_POST_ONLY_WOULD_CROSS_MAKER_ORDER'],
|
||||
3000: STRING_KEYS['3000_INVALID_ORDER_FLAGS'],
|
||||
3001: STRING_KEYS['3001_INVALID_STATEFUL_ORDER_GOOD_TIL_BLOCK_TIME'],
|
||||
3002: STRING_KEYS['3002_STATEFUL_ORDERS_CANNOT_REQUIRE_IMMEDIATE_EXECUTION'],
|
||||
3003: STRING_KEYS['3003_TIME_EXCEEDS_GOOD_TIL_BLOCK_TIME'],
|
||||
3004: STRING_KEYS['3004_GOOD_TIL_BLOCK_TIME_EXCEEDS_STATEFUL_ORDER_TIME_WINDOW'],
|
||||
3005: STRING_KEYS['3005_STATEFUL_ORDER_ALREADY_EXISTS'],
|
||||
3006: STRING_KEYS['3006_STATEFUL_ORDER_DOES_NOT_EXIST'],
|
||||
3007: STRING_KEYS['3007_STATEFUL_ORDER_COLLATERALIZATION_CHECK_FAILED'],
|
||||
3008: STRING_KEYS['3008_STATEFUL_ORDER_PREVIOUSLY_CANCELLED'],
|
||||
2000: '2000_FILL_OR_KILL_ORDER_COULD_NOT_BE_FULLY_FILLED',
|
||||
2001: '2001_REDUCE_ONLY_WOULD_INCREASE_POSITION_SIZE',
|
||||
2002: '2002_REDUCE_ONLY_WOULD_CHANGE_POSITION_SIDE',
|
||||
2003: '2003_POST_ONLY_WOULD_CROSS_MAKER_ORDER',
|
||||
3000: '3000_INVALID_ORDER_FLAGS',
|
||||
3001: '3001_INVALID_STATEFUL_ORDER_GOOD_TIL_BLOCK_TIME',
|
||||
3002: '3002_STATEFUL_ORDERS_CANNOT_REQUIRE_IMMEDIATE_EXECUTION',
|
||||
3003: '3003_TIME_EXCEEDS_GOOD_TIL_BLOCK_TIME',
|
||||
3004: '3004_GOOD_TIL_BLOCK_TIME_EXCEEDS_STATEFUL_ORDER_TIME_WINDOW',
|
||||
3005: '3005_STATEFUL_ORDER_ALREADY_EXISTS',
|
||||
3006: '3006_STATEFUL_ORDER_DOES_NOT_EXIST',
|
||||
3007: '3007_STATEFUL_ORDER_COLLATERALIZATION_CHECK_FAILED',
|
||||
3008: '3008_STATEFUL_ORDER_PREVIOUSLY_CANCELLED',
|
||||
};
|
||||
|
||||
@@ -1,49 +1,37 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import type { StatusResponse } from '@0xsquid/sdk';
|
||||
|
||||
/** 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: ({
|
||||
id,
|
||||
displayData,
|
||||
updateKey,
|
||||
isNew,
|
||||
}: {
|
||||
trigger: (
|
||||
/** Unique ID for the triggered notification */
|
||||
id: _NotificationId;
|
||||
id: _NotificationId,
|
||||
|
||||
/** Display data for the triggered notification */
|
||||
displayData: NotificationDisplayData;
|
||||
displayData: NotificationDisplayData,
|
||||
|
||||
/**
|
||||
* JSON-serializable key.
|
||||
* Re-triggers the notification if passed a different value from the last trigger() call (even from a previous browser session).
|
||||
* Suggested usage: data dependency array
|
||||
*/
|
||||
updateKey?: NotificationUpdateKey;
|
||||
updateKey?: NotificationUpdateKey,
|
||||
|
||||
/**
|
||||
* @param true (default): Notification initialized with status NotificationStatus.Triggered
|
||||
* @param false: Notification initialized with status NotificationStatus.Cleared
|
||||
*/
|
||||
isNew?: boolean;
|
||||
}) => void;
|
||||
isNew?: boolean,
|
||||
) => void;
|
||||
|
||||
lastUpdated: number;
|
||||
}) => void;
|
||||
@@ -70,10 +58,7 @@ 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;
|
||||
@@ -85,15 +70,11 @@ export type Notifications = Record<NotificationId, Notification<any>>;
|
||||
|
||||
/** Notification display data derived from app state at runtime. */
|
||||
export type NotificationDisplayData = {
|
||||
icon?: React.ReactElement<any, 'svg'>;
|
||||
icon?: React.ReactNode;
|
||||
|
||||
title?: string;
|
||||
|
||||
description?: ReactNode;
|
||||
|
||||
customContent?: ReactNode;
|
||||
|
||||
customMenuContent?: ReactNode;
|
||||
description?: string;
|
||||
|
||||
actionDescription?: string;
|
||||
|
||||
@@ -120,13 +101,3 @@ export type NotificationDisplayData = {
|
||||
*/
|
||||
toastDuration?: number;
|
||||
};
|
||||
|
||||
// Notification types
|
||||
export type TransferNotifcation = {
|
||||
txHash: string;
|
||||
toChainId?: string;
|
||||
fromChainId?: string;
|
||||
toAmount?: number;
|
||||
triggeredAt?: number;
|
||||
status?: StatusResponse;
|
||||
};
|
||||
|
||||
@@ -32,18 +32,6 @@ export enum WalletConnectionType {
|
||||
WalletConnect2 = 'walletConnect2',
|
||||
}
|
||||
|
||||
export enum WalletErrorType {
|
||||
// General
|
||||
ChainMismatch,
|
||||
UserCanceled,
|
||||
|
||||
// Non-Deterministic
|
||||
NonDeterministicWallet,
|
||||
|
||||
// Misc
|
||||
Unknown,
|
||||
}
|
||||
|
||||
type WalletConnectionTypeConfig = {
|
||||
name: string;
|
||||
wagmiConnectorId?: string;
|
||||
@@ -337,7 +325,7 @@ export const SIGN_TYPED_DATA = {
|
||||
|
||||
export type PrivateInformation = ReturnType<typeof onboarding.deriveHDKeyFromEthereumSignature>;
|
||||
|
||||
export type EvmAddress = `0x${string}`;
|
||||
export type EthereumAddress = `0x${string}`;
|
||||
export type DydxAddress = `dydx${string}`;
|
||||
|
||||
export const DYDX_CHAIN_INFO: Parameters<typeof suggestChain>[0] = {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { formatUnits } from 'viem';
|
||||
|
||||
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
|
||||
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
|
||||
import { EvmAddress } from '@/constants/wallets';
|
||||
import { EthereumAddress } from '@/constants/wallets';
|
||||
|
||||
import { convertBech32Address } from '@/lib/addressUtils';
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
@@ -16,7 +16,6 @@ import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
import { useAccounts } from './useAccounts';
|
||||
import { usePollNativeTokenBalance } from './usePollNativeTokenBalance';
|
||||
import { usePollUSDCBalance } from './usePollUSDCBalance';
|
||||
|
||||
type UseAccountBalanceProps = {
|
||||
// Token Items
|
||||
@@ -59,7 +58,7 @@ export const useAccountBalance = ({
|
||||
token:
|
||||
addressOrDenom === CHAIN_DEFAULT_TOKEN_ADDRESS
|
||||
? undefined
|
||||
: (addressOrDenom as EvmAddress),
|
||||
: (addressOrDenom as EthereumAddress),
|
||||
watch: true,
|
||||
});
|
||||
|
||||
@@ -97,13 +96,9 @@ export const useAccountBalance = ({
|
||||
.div(QUANTUM_MULTIPLIER)
|
||||
.toNumber();
|
||||
|
||||
const usdcCoinBalance = usePollUSDCBalance({ dydxAddress });
|
||||
const usdcBalance = MustBigNumber(usdcCoinBalance?.amount).div(QUANTUM_MULTIPLIER).toNumber();
|
||||
|
||||
return {
|
||||
balance,
|
||||
nativeTokenBalance,
|
||||
usdcBalance,
|
||||
queryStatus: isCosmosChain ? cosmosQuery.status : evmQuery.status,
|
||||
isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmQuery.fetchStatus === 'fetching',
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { LocalWallet, USDC_DENOM, type Subaccount } from '@dydxprotocol/v4-clien
|
||||
|
||||
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
|
||||
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
|
||||
import { DydxAddress, EvmAddress, PrivateInformation } from '@/constants/wallets';
|
||||
import { DydxAddress, EthereumAddress, PrivateInformation } from '@/constants/wallets';
|
||||
|
||||
import {
|
||||
setOnboardingState,
|
||||
@@ -82,7 +82,7 @@ const useAccountsContext = () => {
|
||||
evmAddress,
|
||||
dydxAddress,
|
||||
}: {
|
||||
evmAddress: EvmAddress;
|
||||
evmAddress: EthereumAddress;
|
||||
dydxAddress?: DydxAddress;
|
||||
}) => {
|
||||
saveEvmDerivedAddresses({
|
||||
@@ -192,7 +192,7 @@ const useAccountsContext = () => {
|
||||
} catch (error) {
|
||||
log('useAccounts/setLocalDydxWallet', error);
|
||||
}
|
||||
} else if (evmAddress) {
|
||||
} else if (evmAddress && signerWagmi) {
|
||||
if (!localDydxWallet) {
|
||||
dispatch(setOnboardingState(OnboardingState.WalletConnected));
|
||||
|
||||
|
||||
@@ -57,8 +57,8 @@ const useDydxClientContext = () => {
|
||||
networkConfig?.indexerSocketUrl &&
|
||||
networkConfig?.validatorUrl
|
||||
) {
|
||||
try {
|
||||
const initializedClient = await CompositeClient.connect(
|
||||
setCompositeClient(
|
||||
await CompositeClient.connect(
|
||||
new Network(
|
||||
selectedNetwork,
|
||||
new IndexerConfig(networkConfig.indexerUrl, networkConfig.indexerSocketUrl),
|
||||
@@ -67,11 +67,8 @@ const useDydxClientContext = () => {
|
||||
broadcastTimeoutMs: 60_000,
|
||||
})
|
||||
)
|
||||
);
|
||||
setCompositeClient(initializedClient);
|
||||
} catch (error) {
|
||||
log('useDydxClient/initializeCompositeClient', error);
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setCompositeClient(undefined);
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
import { createContext, useContext, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import type { StatusResponse } from '@0xsquid/sdk';
|
||||
|
||||
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';
|
||||
|
||||
const LocalNotificationsContext = createContext<
|
||||
ReturnType<typeof useLocalNotificationsContext> | undefined
|
||||
>(undefined);
|
||||
|
||||
LocalNotificationsContext.displayName = 'LocalNotifications';
|
||||
|
||||
export const LocalNotificationsProvider = ({ ...props }) => (
|
||||
<LocalNotificationsContext.Provider value={useLocalNotificationsContext()} {...props} />
|
||||
);
|
||||
|
||||
export const useLocalNotifications = () => useContext(LocalNotificationsContext)!;
|
||||
|
||||
const TRANSFER_STATUS_FETCH_INTERVAL = 10_000;
|
||||
|
||||
const useLocalNotificationsContext = () => {
|
||||
// transfer notifications
|
||||
const [allTransferNotifications, setAllTransferNotifications] = useLocalStorage<{
|
||||
[key: `dydx${string}`]: TransferNotifcation[];
|
||||
}>({
|
||||
key: LocalStorageKey.TransferNotifications,
|
||||
defaultValue: {},
|
||||
});
|
||||
|
||||
const { dydxAddress } = useAccounts();
|
||||
|
||||
const transferNotifications = 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) {
|
||||
try {
|
||||
if (currentStatus && currentStatus?.squidTransactionStatus !== 'ongoing') continue;
|
||||
|
||||
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId });
|
||||
if (status) statuses[txHash] = status;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
return statuses;
|
||||
},
|
||||
refetchInterval: TRANSFER_STATUS_FETCH_INTERVAL,
|
||||
});
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
@@ -1,26 +1,18 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useSelector, shallowEqual, useDispatch } from 'react-redux';
|
||||
import { groupBy } from 'lodash';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
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 { ORDER_SIDE_STRINGS } from '@/constants/trade';
|
||||
|
||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||
|
||||
import { AssetIcon } from '@/components/AssetIcon';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { OrderStatusIcon } from '@/views/OrderStatusIcon';
|
||||
import { TransferStatusToast } from '@/views/TransferStatus';
|
||||
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
|
||||
import { ORDER_SIDE_STRINGS, TRADE_TYPE_STRINGS, TradeTypes } from '@/constants/trade';
|
||||
|
||||
import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelectors';
|
||||
import { openDialog } from '@/state/dialogs';
|
||||
|
||||
import { OrderStatusIcon } from '@/views/OrderStatusIcon';
|
||||
|
||||
import { useStringGetter } from './useStringGetter';
|
||||
|
||||
export const notificationTypes = [
|
||||
@@ -58,33 +50,34 @@ export const notificationTypes = [
|
||||
}
|
||||
: undefined);
|
||||
|
||||
if (order) console.log(order);
|
||||
trigger({
|
||||
id: order.id,
|
||||
displayData: {
|
||||
icon: <AssetIcon symbol={order.marketId.split('-')[0]} />, //<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} />,
|
||||
title: `${stringGetter({
|
||||
key: order.resources.typeStringKey ?? '',
|
||||
})} ${
|
||||
order.status === AbacusOrderStatus.open && (order?.totalFilled ?? 0) > 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: Infinity,
|
||||
},
|
||||
updateKey: [order.status.name, order.size],
|
||||
isNew: !order.createdAtMilliseconds || order.createdAtMilliseconds > lastUpdated,
|
||||
});
|
||||
if (order)
|
||||
trigger(
|
||||
order.id,
|
||||
{
|
||||
icon: (
|
||||
<OrderStatusIcon status={order.status} totalFilled={order.totalFilled ?? 0} />
|
||||
),
|
||||
title: `${stringGetter({
|
||||
key: TRADE_TYPE_STRINGS[order.type.rawValue as TradeTypes].tradeTypeKey,
|
||||
})} ${
|
||||
order.status === AbacusOrderStatus.open && (order?.totalFilled ?? 0) > 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, stringGetter]);
|
||||
}, [orderIds]);
|
||||
},
|
||||
|
||||
useNotificationAction: () => {
|
||||
@@ -100,51 +93,4 @@ export const notificationTypes = [
|
||||
};
|
||||
},
|
||||
} as NotificationTypeConfig<string, [string, number]>,
|
||||
{
|
||||
type: NotificationType.SquidTransfer,
|
||||
useTrigger: ({ trigger }) => {
|
||||
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({
|
||||
id: txHash,
|
||||
displayData: {
|
||||
icon: <Icon iconName={finished ? IconName.Transfer : IconName.Clock} />,
|
||||
title: stringGetter({ key: getTitleStringKey(type, finished) }),
|
||||
// TODO: confirm with design what the description should be
|
||||
description: (
|
||||
<>
|
||||
<span>{type === 'deposit' ? 'Deposit of ' : 'Withdraw of'}</span>
|
||||
<Output type={OutputType.Fiat} value={toAmount} />
|
||||
</>
|
||||
),
|
||||
customContent: (
|
||||
<TransferStatusToast
|
||||
toAmount={transfer.toAmount}
|
||||
triggeredAt={transfer.triggeredAt}
|
||||
status={transfer.status}
|
||||
/>
|
||||
),
|
||||
customMenuContent: !finished && <TransferStatusSteps status={transfer.status} />,
|
||||
toastSensitivity: 'foreground',
|
||||
},
|
||||
updateKey: [],
|
||||
});
|
||||
}
|
||||
}, [transferNotifications, stringGetter]);
|
||||
},
|
||||
} as NotificationTypeConfig<string, []>,
|
||||
] satisfies NotificationTypeConfig[];
|
||||
|
||||
@@ -92,7 +92,7 @@ const useNotificationsContext = () => {
|
||||
for (const { type, useTrigger } of notificationTypes)
|
||||
useTrigger({
|
||||
trigger: useCallback(
|
||||
({ id, displayData, updateKey, isNew = true }) => {
|
||||
(id, displayData, updateKey, isNew = true) => {
|
||||
const key = getKey({ type, id });
|
||||
|
||||
const notification = notifications[key];
|
||||
@@ -106,10 +106,7 @@ const useNotificationsContext = () => {
|
||||
updateKey,
|
||||
} as Notification);
|
||||
|
||||
updateStatus(
|
||||
notification,
|
||||
isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared
|
||||
);
|
||||
updateStatus(notification, isNew ? NotificationStatus.Triggered : NotificationStatus.Cleared);
|
||||
}
|
||||
|
||||
// updateKey changed - update existing notification
|
||||
@@ -144,9 +141,7 @@ 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);
|
||||
|
||||
@@ -182,23 +177,28 @@ const useNotificationsContext = () => {
|
||||
const iconUrl =
|
||||
displayData.icon && (await renderSvgToDataUrl(displayData.icon).catch(() => undefined));
|
||||
|
||||
if (displayData.title) {
|
||||
const pushNotification = new globalThis.Notification(displayData.title, {
|
||||
renotify: true,
|
||||
tag: getKey(notification),
|
||||
data: notification,
|
||||
body: displayData.description ?? '',
|
||||
icon: (iconUrl as string) ?? '/favicon.svg',
|
||||
badge: (iconUrl as string) ?? '/favicon.svg',
|
||||
image: (iconUrl as string) ?? '/favicon.svg',
|
||||
requireInteraction: displayData.toastDuration === Infinity,
|
||||
});
|
||||
const pushNotification = new globalThis.Notification(displayData.title, {
|
||||
renotify: true,
|
||||
tag: getKey(notification),
|
||||
data: notification,
|
||||
body: displayData.description,
|
||||
icon: iconUrl ?? '/favicon.svg',
|
||||
badge: iconUrl ?? '/favicon.svg',
|
||||
image: iconUrl ?? '/favicon.svg',
|
||||
vibrate: displayData.toastSensitivity === 'foreground',
|
||||
requireInteraction: displayData.toastDuration === Infinity,
|
||||
// actions: [
|
||||
// {
|
||||
// action: displayData.actionDescription,
|
||||
// title: displayData.actionDescription,
|
||||
// }
|
||||
// ].slice(0, globalThis.Notification.maxActions),
|
||||
});
|
||||
|
||||
pushNotification.addEventListener('click', () => {
|
||||
onNotificationAction(notification);
|
||||
markSeen(notification);
|
||||
});
|
||||
}
|
||||
pushNotification.addEventListener('click', () => {
|
||||
onNotificationAction(notification);
|
||||
markSeen(notification);
|
||||
});
|
||||
}
|
||||
|
||||
setPushNotificationsLastUpdated(Date.now());
|
||||
@@ -236,3 +236,4 @@ const useNotificationsContext = () => {
|
||||
setIsMenuOpen,
|
||||
};
|
||||
};
|
||||
// }
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
import { Squid } from '@0xsquid/sdk';
|
||||
|
||||
import { CLIENT_NETWORK_CONFIGS, DydxV4Network, isDydxV4Network } from '@/constants/networks';
|
||||
import { DydxV4Network, isDydxV4Network } from '@/constants/networks';
|
||||
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
export const NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
|
||||
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',
|
||||
};
|
||||
|
||||
const useSquidContext = () => {
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
@@ -23,7 +27,7 @@ const useSquidContext = () => {
|
||||
const squid = useMemo(
|
||||
() =>
|
||||
isDydxV4Network(selectedNetwork)
|
||||
? new Squid({ baseUrl: CLIENT_NETWORK_CONFIGS[selectedNetwork]?.endpoints['0xsquid'] })
|
||||
? new Squid({ baseUrl: SQUID_BASE_URL[selectedNetwork as DydxV4Network] })
|
||||
: undefined,
|
||||
[selectedNetwork]
|
||||
);
|
||||
@@ -551,8 +551,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
||||
} as HumanReadablePlaceOrderPayload & { isClosePosition: boolean });
|
||||
onSuccess?.();
|
||||
} catch (error) {
|
||||
const errorCode: number | undefined = error?.code;
|
||||
const errorStringKey = errorCode ? ORDER_ERROR_CODE_MAP[errorCode] : undefined;
|
||||
const errorStringKey = error?.code && ORDER_ERROR_CODE_MAP[error.code as number];
|
||||
onError?.({ errorStringKey });
|
||||
|
||||
log('useSubaccount/placeOrder', error, {
|
||||
|
||||
@@ -4,20 +4,17 @@ import { LocalStorageKey } from '@/constants/localStorage';
|
||||
|
||||
import {
|
||||
type DydxAddress,
|
||||
type EvmAddress,
|
||||
type EthereumAddress,
|
||||
WalletConnectionType,
|
||||
WalletType,
|
||||
wallets,
|
||||
DYDX_CHAIN_INFO,
|
||||
} from '@/constants/wallets';
|
||||
|
||||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
||||
|
||||
import {
|
||||
useConnect as useConnectWagmi,
|
||||
useAccount as useAccountWagmi,
|
||||
useDisconnect as useDisconnectWagmi,
|
||||
usePublicClient as usePublicClientWagmi,
|
||||
useWalletClient as useWalletClientWagmi,
|
||||
} from 'wagmi';
|
||||
import {
|
||||
@@ -29,21 +26,21 @@ import {
|
||||
} from 'graz';
|
||||
|
||||
import { resolveWagmiConnector } from '@/lib/wagmi';
|
||||
import { getWalletConnection, parseWalletError } from '@/lib/wallet';
|
||||
import { getWalletConnection } from '@/lib/wallet';
|
||||
import { log } from '@/lib/telemetry';
|
||||
|
||||
import { useLocalStorage } from '@/hooks/useLocalStorage';
|
||||
import { useStringGetter } from './useStringGetter';
|
||||
|
||||
export const useWalletConnection = () => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
// EVM wallet connection
|
||||
const [evmAddress, saveEvmAddress] = useLocalStorage<EvmAddress | undefined>({
|
||||
const [evmAddress, saveEvmAddress] = useLocalStorage<EthereumAddress | undefined>({
|
||||
key: LocalStorageKey.EvmAddress,
|
||||
defaultValue: undefined,
|
||||
});
|
||||
const { address: evmAddressWagmi, isConnected: isConnectedWagmi } = useAccountWagmi();
|
||||
const publicClientWagmi = usePublicClientWagmi();
|
||||
const { data: signerWagmi } = useWalletClientWagmi();
|
||||
const { disconnectAsync: disconnectWagmi } = useDisconnectWagmi();
|
||||
|
||||
@@ -132,6 +129,8 @@ export const useWalletConnection = () => {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log('useWalletConnection/connectWallet', error);
|
||||
|
||||
throw Object.assign(
|
||||
new Error([error.message, error.cause?.message].filter(Boolean).join('\n')),
|
||||
{
|
||||
@@ -159,7 +158,7 @@ export const useWalletConnection = () => {
|
||||
// Wallet selection
|
||||
|
||||
const [selectedWalletType, setSelectedWalletType] = useState<WalletType | undefined>(walletType);
|
||||
const [selectedWalletError, setSelectedWalletError] = useState<string>();
|
||||
const [selectedWalletError, setSelectedWalletError] = useState<Error>();
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@@ -174,15 +173,8 @@ export const useWalletConnection = () => {
|
||||
setWalletType(walletType);
|
||||
setWalletConnectionType(walletConnectionType);
|
||||
} catch (error) {
|
||||
const { walletErrorType, message } = parseWalletError({
|
||||
error,
|
||||
stringGetter,
|
||||
});
|
||||
|
||||
if (message) {
|
||||
log('useWalletConnection/connectWallet', error, { walletErrorType });
|
||||
setSelectedWalletError(message);
|
||||
}
|
||||
log('useWalletConnection/connectWallet', error);
|
||||
setSelectedWalletError(error);
|
||||
}
|
||||
} else {
|
||||
setWalletType(undefined);
|
||||
@@ -216,8 +208,7 @@ export const useWalletConnection = () => {
|
||||
evmAddress,
|
||||
evmAddressWagmi,
|
||||
signerWagmi,
|
||||
publicClientWagmi,
|
||||
|
||||
|
||||
// Wallet connection (Cosmos)
|
||||
dydxAddress,
|
||||
dydxAddressGraz,
|
||||
|
||||
@@ -54,7 +54,7 @@ button:disabled {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
:focus-visible{
|
||||
:focus {
|
||||
outline: var(--color-accent) 1px solid;
|
||||
outline-offset: -1px;
|
||||
/* outline-offset: -2px; */
|
||||
|
||||
@@ -41,7 +41,6 @@ export const NotificationsToastArea = ({ className }: StyleProps) => {
|
||||
slotIcon={displayData.icon}
|
||||
slotTitle={displayData.title}
|
||||
slotDescription={displayData.description}
|
||||
slotCustomContent={displayData.customContent}
|
||||
slotAction={
|
||||
<Button size={ButtonSize.Small} onClick={() => onNotificationAction(notification)}>
|
||||
{displayData.actionDescription}
|
||||
|
||||
@@ -36,7 +36,6 @@ import AbacusStateNotifier from './stateNotification';
|
||||
import AbacusLocalizer from './localizer';
|
||||
import AbacusFormatter from './formatter';
|
||||
import AbacusThreading from './threading';
|
||||
import AbacusTracking from './tracking';
|
||||
import AbacusFileSystem, { ENDPOINTS_PATH } from './filesystem';
|
||||
import { LocaleSeparators } from '../numbers';
|
||||
class AbacusStateManager {
|
||||
@@ -60,7 +59,7 @@ class AbacusStateManager {
|
||||
new AbacusRest(),
|
||||
this.websocket,
|
||||
new AbacusChainTransaction(),
|
||||
new AbacusTracking(),
|
||||
null,
|
||||
new AbacusThreading(),
|
||||
new CoroutineTimer(),
|
||||
new AbacusFileSystem()
|
||||
@@ -154,7 +153,6 @@ class AbacusStateManager {
|
||||
|
||||
// ------ Set Data ------ //
|
||||
setStore = (store: RootStore) => {
|
||||
this.store = store;
|
||||
this.stateNotifier.setStore(store);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import type { AbacusTrackingProtocol, Nullable } from '@/constants/abacus';
|
||||
|
||||
class AbacusTracking implements AbacusTrackingProtocol {
|
||||
log(event: string, data: Nullable<string>) {
|
||||
console.log({ event, data });
|
||||
}
|
||||
}
|
||||
|
||||
export default AbacusTracking;
|
||||
@@ -21,10 +21,8 @@ const applyComputedStyles = (html: string) => {
|
||||
const toDataUrl = (bytes: string, type = 'image/svg+xml') =>
|
||||
new Promise<string | ArrayBuffer | null>((resolve, reject) => {
|
||||
Object.assign(new FileReader(), {
|
||||
onload: (e: {
|
||||
target: { result: string | ArrayBuffer | PromiseLike<string | ArrayBuffer | null> | null };
|
||||
}) => resolve(e.target.result),
|
||||
onerror: (e: { target: { error: Error } }) => reject(e.target.error),
|
||||
onload: (e) => resolve(e.target.result),
|
||||
onerror: (e) => reject(e.target.error),
|
||||
}).readAsDataURL(new File([bytes], '', { type }));
|
||||
});
|
||||
|
||||
|
||||
@@ -72,9 +72,3 @@ 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)}`;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { STRING_KEYS, StringGetterFunction } from '@/constants/localization';
|
||||
|
||||
import {
|
||||
type WalletConnection,
|
||||
wallets,
|
||||
WalletConnectionType,
|
||||
WalletErrorType,
|
||||
WalletType,
|
||||
} from '@/constants/wallets';
|
||||
|
||||
@@ -71,66 +68,3 @@ export const getWalletConnection = ({
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getWalletErrorType = ({ error }: { error: Error }) => {
|
||||
const { message } = error;
|
||||
const messageLower = message.toLowerCase();
|
||||
|
||||
// General - Cancelled
|
||||
if (
|
||||
messageLower.includes('connection request reset') ||
|
||||
messageLower.includes('rejected') ||
|
||||
messageLower.includes('reject') ||
|
||||
messageLower.includes('cancelled') ||
|
||||
messageLower.includes('canceled') ||
|
||||
messageLower.includes('user denied')
|
||||
) {
|
||||
return WalletErrorType.UserCanceled;
|
||||
}
|
||||
|
||||
if (messageLower.includes('chain mismatch')) {
|
||||
return WalletErrorType.ChainMismatch;
|
||||
}
|
||||
|
||||
// ImToken - User canceled
|
||||
if (messageLower.includes('用户取消了操作')) {
|
||||
return WalletErrorType.UserCanceled;
|
||||
}
|
||||
|
||||
if (messageLower.includes('does not support deterministic signing')) {
|
||||
return WalletErrorType.NonDeterministicWallet;
|
||||
}
|
||||
|
||||
return WalletErrorType.Unknown;
|
||||
};
|
||||
|
||||
export const parseWalletError = ({
|
||||
error,
|
||||
stringGetter,
|
||||
}: {
|
||||
error: Error;
|
||||
stringGetter: StringGetterFunction;
|
||||
}) => {
|
||||
const walletErrorType = getWalletErrorType({ error });
|
||||
let message;
|
||||
|
||||
switch (walletErrorType) {
|
||||
case WalletErrorType.ChainMismatch:
|
||||
case WalletErrorType.UserCanceled: {
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
message = stringGetter({
|
||||
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
|
||||
params: {
|
||||
ERROR_MESSAGE: error.message || stringGetter({ key: STRING_KEYS.UNKNOWN_ERROR }),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
walletErrorType,
|
||||
message,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -49,7 +49,7 @@ export const DYDXBalancePanel = () => {
|
||||
size={ButtonSize.Small}
|
||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Receive }))}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.RECEIVE })}
|
||||
{stringGetter({ key: STRING_KEYS.EXCHANGE_RECEIVED })}
|
||||
</Styled.ReceiveButton>
|
||||
<Button
|
||||
slotLeft={<Icon iconName={IconName.Send} />}
|
||||
|
||||
@@ -214,7 +214,7 @@ export const popoverMixins = {
|
||||
/* &:hover, */
|
||||
/* :not(:has(* > [data-highlighted])) > &:hover, */
|
||||
&[data-radix-collection-item]:hover, // @radix-ui/react-navigation-menu
|
||||
/* &:focus-visible, */
|
||||
/* &:focus, */
|
||||
&[aria-selected="true"], // cmdk
|
||||
&[data-highlighted] // @radix-ui
|
||||
{
|
||||
|
||||
@@ -304,7 +304,7 @@ Styled.Popover = styled(Popover)`
|
||||
}
|
||||
`} 0.2s;
|
||||
}
|
||||
&:focus-visible {
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { useInterval } from '@/hooks';
|
||||
|
||||
import { getTimeTillNextUnit, formatSeconds } from '@/lib/timeUtils';
|
||||
import { getTimeTillNextUnit, getTimeString } 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
|
||||
? formatSeconds(secondsLeft)
|
||||
? `${getTimeString(Math.floor(secondsLeft / 60))}:${getTimeString(secondsLeft % 60)}`
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
import { useCallback, useState, useMemo } from 'react';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
import { Root, Trigger, Content } from '@radix-ui/react-collapsible';
|
||||
import { StatusResponse } from '@0xsquid/sdk';
|
||||
|
||||
import { useInterval, useStringGetter } from '@/hooks';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
import { formatSeconds } from '@/lib/timeUtils';
|
||||
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { WithReceipt } from '@/components/WithReceipt';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
|
||||
import { LoadingDots } from '@/components/Loading/LoadingDots';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
type ElementProps = {
|
||||
toAmount?: number;
|
||||
triggeredAt?: number;
|
||||
status?: StatusResponse;
|
||||
};
|
||||
|
||||
export const TransferStatusToast = ({
|
||||
toAmount,
|
||||
triggeredAt = Date.now(),
|
||||
status,
|
||||
}: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [secondsLeft, setSecondsLeft] = useState<number | undefined>();
|
||||
|
||||
const type = useMemo(
|
||||
() => (status?.toChain?.chainData?.chainId === TESTNET_CHAIN_ID ? 'deposit' : 'withdrawal'),
|
||||
[status]
|
||||
);
|
||||
|
||||
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.Receipt>
|
||||
<TransferStatusSteps status={status} />
|
||||
</Styled.Receipt>
|
||||
}
|
||||
>
|
||||
<Styled.BridgingStatus>
|
||||
<Styled.Status>
|
||||
{stringGetter({
|
||||
key: type === 'deposit' ? STRING_KEYS.DEPOSIT_STATUS : STRING_KEYS.WITHDRAW_STATUS,
|
||||
params: {
|
||||
AMOUNT_USD: <Styled.InlineOutput type={OutputType.Fiat} value={toAmount} />,
|
||||
ESTIMATED_DURATION: (
|
||||
<Styled.InlineOutput
|
||||
type={OutputType.Text}
|
||||
value={formatSeconds(Math.max(secondsLeft || 0, 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.5rem -1rem -1rem -1rem;
|
||||
color: var(--color-text-0);
|
||||
font: var(--font-small-book);
|
||||
`;
|
||||
|
||||
Styled.Receipt = styled.div`
|
||||
padding: 0 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;
|
||||
}
|
||||
`;
|
||||
@@ -1,175 +0,0 @@
|
||||
import { useMemo } from 'react';
|
||||
import styled, { css, keyframes, type AnyStyledComponent } from 'styled-components';
|
||||
import { StatusResponse } from '@0xsquid/sdk';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
import { useStringGetter } from '@/hooks';
|
||||
|
||||
import { Link } from '@/components/Link';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { LoadingDots } from '@/components/Loading/LoadingDots';
|
||||
import { LoadingSpinner } from '@/components/Loading/LoadingSpinner';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
type ElementProps = {
|
||||
status?: StatusResponse;
|
||||
};
|
||||
|
||||
enum TransferStatusStep {
|
||||
FromChain,
|
||||
Bridge,
|
||||
ToChain,
|
||||
Complete,
|
||||
}
|
||||
|
||||
export const TransferStatusSteps = ({ status }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const { currentStep, steps, type } = useMemo(() => {
|
||||
const routeStatus = status?.routeStatus;
|
||||
const fromChain = status?.fromChain?.chainData?.chainId;
|
||||
const toChain = status?.toChain?.chainData?.chainId;
|
||||
const type = toChain === TESTNET_CHAIN_ID ? 'deposit' : 'withdrawal';
|
||||
|
||||
const steps = [
|
||||
{
|
||||
label: stringGetter({
|
||||
key:
|
||||
type === 'deposit' ? STRING_KEYS.INITIATED_DEPOSIT : STRING_KEYS.INITIATED_WITHDRAWAL,
|
||||
}),
|
||||
step: TransferStatusStep.FromChain,
|
||||
link: status?.fromChain?.transactionUrl,
|
||||
},
|
||||
{
|
||||
label: stringGetter({ key: STRING_KEYS.BRIDGING_TOKENS }),
|
||||
step: TransferStatusStep.Bridge,
|
||||
link: status?.axelarTransactionUrl,
|
||||
},
|
||||
{
|
||||
label: stringGetter({
|
||||
key: type === 'deposit' ? STRING_KEYS.DEPOSIT_TO_CHAIN : STRING_KEYS.WITHDRAW_TO_CHAIN,
|
||||
params: {
|
||||
CHAIN: status?.toChain?.chainData?.chainName,
|
||||
},
|
||||
}),
|
||||
step: TransferStatusStep.ToChain,
|
||||
link: status?.toChain?.transactionUrl,
|
||||
},
|
||||
];
|
||||
|
||||
const currentStatus = routeStatus[routeStatus?.length - 1];
|
||||
|
||||
let currentStep = TransferStatusStep.Bridge;
|
||||
|
||||
if (!routeStatus?.length) {
|
||||
currentStep = TransferStatusStep.FromChain;
|
||||
} else if (currentStatus.chainId === toChain) {
|
||||
currentStep =
|
||||
currentStatus.status !== 'success'
|
||||
? TransferStatusStep.ToChain
|
||||
: TransferStatusStep.Complete;
|
||||
} else if (currentStatus.chainId === fromChain && currentStatus.status !== 'success') {
|
||||
currentStep = TransferStatusStep.FromChain;
|
||||
}
|
||||
|
||||
return {
|
||||
currentStep,
|
||||
steps,
|
||||
type,
|
||||
};
|
||||
}, [status, stringGetter]);
|
||||
|
||||
if (!status) return <LoadingDots size={3} />;
|
||||
|
||||
return (
|
||||
<Styled.BridgingStatus>
|
||||
{steps.map((step) => (
|
||||
<Styled.Step key={step.step}>
|
||||
<Styled.row>
|
||||
{step.step === currentStep ? (
|
||||
<Styled.Icon>
|
||||
<Styled.Spinner />
|
||||
</Styled.Icon>
|
||||
) : step.step < currentStep ? (
|
||||
<Styled.Icon state="complete">
|
||||
<Icon iconName={IconName.Check} />
|
||||
</Styled.Icon>
|
||||
) : (
|
||||
<Styled.Icon state="default">{step.step + 1}</Styled.Icon>
|
||||
)}
|
||||
{step.link && currentStep >= step.step ? (
|
||||
<Link href={step.link}>
|
||||
<Styled.Label highlighted={currentStep >= step.step}>
|
||||
{step.label}
|
||||
<Icon iconName={IconName.LinkOut} />
|
||||
</Styled.Label>
|
||||
</Link>
|
||||
) : (
|
||||
<Styled.Label highlighted={currentStep >= step.step}>{step.label}</Styled.Label>
|
||||
)}
|
||||
</Styled.row>
|
||||
</Styled.Step>
|
||||
))}
|
||||
</Styled.BridgingStatus>
|
||||
);
|
||||
};
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.BridgingStatus = styled.div`
|
||||
${layoutMixins.flexColumn};
|
||||
|
||||
gap: 1rem;
|
||||
padding: 1rem 0;
|
||||
`;
|
||||
|
||||
Styled.Step = styled.div`
|
||||
${layoutMixins.spacedRow};
|
||||
`;
|
||||
|
||||
Styled.row = styled.div`
|
||||
${layoutMixins.inlineRow};
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
Styled.Icon = styled.div<{ state: 'complete' | 'default' }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
|
||||
border-radius: 50%;
|
||||
|
||||
background-color: var(--color-layer-3);
|
||||
|
||||
${({ state }) =>
|
||||
({
|
||||
['complete']: css`
|
||||
color: var(--color-positive);
|
||||
`,
|
||||
['default']: css`
|
||||
color: var(--color-text-0);
|
||||
`,
|
||||
}[state])}
|
||||
`;
|
||||
|
||||
Styled.Spinner = styled(LoadingSpinner)`
|
||||
--spinner-width: 1.25rem;
|
||||
|
||||
color: var(--color-accent);
|
||||
`;
|
||||
|
||||
Styled.Label = styled(Styled.row)<{ highlighted?: boolean }>`
|
||||
${({ highlighted }) =>
|
||||
highlighted
|
||||
? css`
|
||||
color: var(--color-text-2);
|
||||
`
|
||||
: css`
|
||||
color: var(--color-text-0);
|
||||
`}
|
||||
`;
|
||||
@@ -7,7 +7,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { AbacusOrderStatus, AbacusOrderTypes, type Nullable } from '@/constants/abacus';
|
||||
import { ButtonAction } from '@/constants/buttons';
|
||||
import { STRING_KEYS, StringKey } from '@/constants/localization';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
import { AssetIcon } from '@/components/AssetIcon';
|
||||
import { Button } from '@/components/Button';
|
||||
@@ -38,9 +38,6 @@ type ElementProps = {
|
||||
setIsOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
const isStringKey = (key: Nullable<string>): key is StringKey =>
|
||||
key != null && STRING_KEYS.hasOwnProperty(key);
|
||||
|
||||
export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const dispatch = useDispatch();
|
||||
@@ -50,7 +47,7 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
|
||||
const { cancelOrder } = useSubaccount();
|
||||
|
||||
const {
|
||||
asset,
|
||||
asset = {},
|
||||
cancelReason,
|
||||
createdAtMilliseconds,
|
||||
expiresAtMilliseconds,
|
||||
@@ -60,7 +57,7 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
|
||||
price,
|
||||
reduceOnly,
|
||||
totalFilled,
|
||||
resources,
|
||||
resources = {},
|
||||
size,
|
||||
status,
|
||||
stepSizeDecimals,
|
||||
@@ -112,9 +109,9 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
|
||||
<Styled.Row>
|
||||
<Styled.StatusIcon iconName={statusIcon} color={statusIconColor} />
|
||||
<Styled.Status>
|
||||
{isStringKey(statusStringKey)
|
||||
{statusStringKey
|
||||
? stringGetter({ key: statusStringKey })
|
||||
: isStringKey(resources.statusStringKey)
|
||||
: resources.statusStringKey
|
||||
? stringGetter({ key: resources.statusStringKey })
|
||||
: undefined}
|
||||
</Styled.Status>
|
||||
@@ -124,9 +121,7 @@ export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
|
||||
{
|
||||
key: 'cancel-reason',
|
||||
label: stringGetter({ key: STRING_KEYS.CANCEL_REASON }),
|
||||
value: isStringKey(cancelReason)
|
||||
? stringGetter({ key: STRING_KEYS[cancelReason] })
|
||||
: undefined,
|
||||
value: cancelReason ? stringGetter({ key: STRING_KEYS[cancelReason] }) : undefined,
|
||||
},
|
||||
{
|
||||
key: 'amount',
|
||||
|
||||
@@ -32,12 +32,8 @@ export const ChooseWallet = () => {
|
||||
<>
|
||||
{selectedWalletType && selectedWalletError && (
|
||||
<Styled.AlertMessage type={AlertType.Error}>
|
||||
{
|
||||
<h4>
|
||||
Couldn't connect to {stringGetter({ key: wallets[selectedWalletType].stringKey })}.
|
||||
</h4>
|
||||
}
|
||||
{selectedWalletError}
|
||||
{<h4>Couldn't connect to {stringGetter({ key: wallets[selectedWalletType].stringKey })}.</h4>}
|
||||
<code>{selectedWalletError.message}</code>
|
||||
</Styled.AlertMessage>
|
||||
)}
|
||||
|
||||
|
||||
@@ -25,12 +25,11 @@ import { Switch } from '@/components/Switch';
|
||||
import { WithReceipt } from '@/components/WithReceipt';
|
||||
import { WithTooltip } from '@/components/WithTooltip';
|
||||
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
import { track } from '@/lib/analytics';
|
||||
import { isTruthy } from '@/lib/isTruthy';
|
||||
import { log } from '@/lib/telemetry';
|
||||
import { parseWalletError } from '@/lib/wallet';
|
||||
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
type ElementProps = {
|
||||
status: EvmDerivedAccountStatus;
|
||||
@@ -67,12 +66,8 @@ export const GenerateKeys = ({
|
||||
try {
|
||||
await matchNetwork?.();
|
||||
} catch (error) {
|
||||
const { message, walletErrorType } = parseWalletError({ error, stringGetter });
|
||||
|
||||
if (message) {
|
||||
log('GenerateKeys/switchNetwork', error, { walletErrorType });
|
||||
setError(message);
|
||||
}
|
||||
setError(error.message);
|
||||
log('GenerateKeys/switchNetwork', error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -129,12 +124,7 @@ export const GenerateKeys = ({
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
const { message } = parseWalletError({ error, stringGetter });
|
||||
|
||||
if (message) {
|
||||
track(AnalyticsEvent.OnboardingWalletIsNonDeterministic);
|
||||
setError(message);
|
||||
}
|
||||
log('GenerateKeys/getSubaccounts', error);
|
||||
}
|
||||
|
||||
await setWalletFromEvmSignature(signature);
|
||||
@@ -150,48 +140,48 @@ export const GenerateKeys = ({
|
||||
setStatus(EvmDerivedAccountStatus.Derived);
|
||||
} catch (error) {
|
||||
setStatus(EvmDerivedAccountStatus.NotDerived);
|
||||
const { message, walletErrorType } = parseWalletError({ error, stringGetter });
|
||||
setError(error?.message);
|
||||
|
||||
if (message) {
|
||||
setError(message);
|
||||
log('GenerateKeys/deriveKeys', error, { walletErrorType });
|
||||
}
|
||||
log('GenerateKeys/deriveKeys', error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Styled.StatusCardsContainer>
|
||||
{[
|
||||
{
|
||||
status: EvmDerivedAccountStatus.Deriving,
|
||||
title: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
|
||||
description: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
|
||||
},
|
||||
status === EvmDerivedAccountStatus.EnsuringDeterminism && {
|
||||
status: EvmDerivedAccountStatus.EnsuringDeterminism,
|
||||
title: stringGetter({ key: STRING_KEYS.VERIFY_WALLET_COMPATIBILITY }),
|
||||
description: stringGetter({ key: STRING_KEYS.ENSURES_WALLET_SUPPORT }),
|
||||
},
|
||||
]
|
||||
.filter(isTruthy)
|
||||
.map((step) => (
|
||||
<Styled.StatusCard key={step.status} active={status === step.status}>
|
||||
{status < step.status ? (
|
||||
<LoadingSpinner disabled />
|
||||
) : status === step.status ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<Styled.GreenCheckCircle />
|
||||
)}
|
||||
<div>
|
||||
<h3>{step.title}</h3>
|
||||
<p>{step.description}</p>
|
||||
</div>
|
||||
</Styled.StatusCard>
|
||||
))}
|
||||
</Styled.StatusCardsContainer>
|
||||
|
||||
{isMobile && (
|
||||
<Styled.MobileStatusCards>
|
||||
{[
|
||||
{
|
||||
status: EvmDerivedAccountStatus.Deriving,
|
||||
title: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
|
||||
description: stringGetter({ key: STRING_KEYS.GENERATE_COSMOS_WALLET }),
|
||||
},
|
||||
status === EvmDerivedAccountStatus.EnsuringDeterminism && {
|
||||
status: EvmDerivedAccountStatus.EnsuringDeterminism,
|
||||
title: stringGetter({ key: STRING_KEYS.VERIFY_WALLET_COMPATIBILITY }),
|
||||
description: stringGetter({ key: STRING_KEYS.ENSURES_WALLET_SUPPORT }),
|
||||
},
|
||||
]
|
||||
.filter(isTruthy)
|
||||
.map((step) => (
|
||||
<Styled.StatusCard key={step.status} active={status === step.status}>
|
||||
{status < step.status ? (
|
||||
<LoadingSpinner disabled />
|
||||
) : status === step.status ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<Styled.GreenCheckCircle />
|
||||
)}
|
||||
<div>
|
||||
<h3>{step.title}</h3>
|
||||
<p>{step.description}</p>
|
||||
</div>
|
||||
</Styled.StatusCard>
|
||||
))}
|
||||
</Styled.MobileStatusCards>
|
||||
)}
|
||||
<Styled.Footer>
|
||||
<Styled.RememberMe htmlFor="remember-me">
|
||||
<WithTooltip withIcon tooltip="remember-me">
|
||||
@@ -261,7 +251,7 @@ export const GenerateKeys = ({
|
||||
|
||||
const Styled: Record<string, AnyStyledComponent> = {};
|
||||
|
||||
Styled.StatusCardsContainer = styled.div`
|
||||
Styled.MobileStatusCards = styled.div`
|
||||
display: grid;
|
||||
margin-top: 1rem;
|
||||
gap: 1rem;
|
||||
|
||||
@@ -3,21 +3,16 @@ import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
import { type NumberFormatValues } from 'react-number-format';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||
import { parseUnits } from 'viem'
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
import erc20 from '@/abi/erc20.json';
|
||||
import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
|
||||
import { AlertType } from '@/constants/alerts';
|
||||
import { ButtonSize } from '@/constants/buttons';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { NumberSign } from '@/constants/numbers';
|
||||
import type { EvmAddress } from '@/constants/wallets';
|
||||
|
||||
import { useAccounts, useDebounce, useStringGetter } from '@/hooks';
|
||||
import { useAccountBalance } from '@/hooks/useAccountBalance';
|
||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||
import { NATIVE_TOKEN_ADDRESS, useSquid } from '@/hooks/useSquid';
|
||||
import { useWalletConnection } from '@/hooks/useWalletConnection';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { formMixins } from '@/styles/formMixins';
|
||||
@@ -37,7 +32,6 @@ import { getTransferInputs } from '@/state/inputsSelectors';
|
||||
|
||||
import abacusStateManager from '@/lib/abacus';
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
import { log } from '@/lib/telemetry';
|
||||
|
||||
import { ChainSelectMenu } from './ChainSelectMenu';
|
||||
import { TokenSelectMenu } from './TokenSelectMenu';
|
||||
@@ -54,17 +48,13 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { evmAddress, signerWagmi } = useAccounts();
|
||||
const { publicClientWagmi } = useWalletConnection();
|
||||
|
||||
const { addTransferNotification } = useLocalNotifications();
|
||||
const { signerWagmi } = useAccounts();
|
||||
|
||||
const {
|
||||
requestPayload,
|
||||
token,
|
||||
chain: chainIdStr,
|
||||
resources,
|
||||
summary,
|
||||
} = useSelector(getTransferInputs, shallowEqual) || {};
|
||||
const chainId = chainIdStr ? parseInt(chainIdStr) : undefined;
|
||||
|
||||
@@ -74,16 +64,13 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
[token, resources]
|
||||
);
|
||||
|
||||
const sourceChain = useMemo(
|
||||
() => (chainIdStr ? resources?.chainResources?.get(chainIdStr) : undefined),
|
||||
[chainId, resources]
|
||||
);
|
||||
|
||||
const [fromAmount, setFromAmount] = useState('');
|
||||
const [slippage, setSlippage] = useState(0.01); // 1% slippage
|
||||
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,
|
||||
@@ -170,39 +157,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
}
|
||||
}, [balance, setFromAmount]);
|
||||
|
||||
const validateTokenApproval = useCallback(async () => {
|
||||
if (!signerWagmi || !publicClientWagmi) throw new Error('Missing signer');
|
||||
if (!sourceToken?.address || !sourceToken.decimals) throw new Error('Missing source token address');
|
||||
if (!sourceChain?.rpc) throw new Error('Missing source chain rpc');
|
||||
if (!requestPayload?.targetAddress) throw new Error('Missing target address');
|
||||
if (!requestPayload?.value) throw new Error('Missing transaction value');
|
||||
if (sourceToken?.address === NATIVE_TOKEN_ADDRESS) return;
|
||||
|
||||
const allowance = await publicClientWagmi.readContract({
|
||||
address: sourceToken.address as EvmAddress,
|
||||
abi: erc20,
|
||||
functionName: 'allowance',
|
||||
args: [evmAddress as EvmAddress, requestPayload.targetAddress as EvmAddress]
|
||||
});
|
||||
|
||||
const sourceAmountBN = parseUnits(debouncedAmount, sourceToken.decimals);
|
||||
|
||||
if (sourceAmountBN > (allowance as bigint)) {
|
||||
const { request } = await publicClientWagmi.simulateContract({
|
||||
account: evmAddress,
|
||||
address: sourceToken.address as EvmAddress,
|
||||
abi: erc20,
|
||||
functionName: 'approve',
|
||||
args: [requestPayload.targetAddress as EvmAddress, sourceAmountBN],
|
||||
})
|
||||
|
||||
const approveTx = await signerWagmi.writeContract(request);
|
||||
await publicClientWagmi.waitForTransactionReceipt({
|
||||
hash: approveTx,
|
||||
})
|
||||
}
|
||||
}, [signerWagmi, sourceToken, sourceChain, requestPayload, publicClientWagmi]);
|
||||
|
||||
const onSubmit = useCallback(
|
||||
async (e: FormEvent) => {
|
||||
try {
|
||||
@@ -224,32 +178,27 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
await validateTokenApproval();
|
||||
|
||||
let tx = {
|
||||
to: requestPayload.targetAddress as EvmAddress,
|
||||
data: requestPayload.data as EvmAddress,
|
||||
gasLimit: BigInt(requestPayload.gasLimit),
|
||||
to: requestPayload.targetAddress as `0x${string}`,
|
||||
data: requestPayload.data as `0x${string}`,
|
||||
gasLimit: ethers.toBigInt(requestPayload.gasLimit),
|
||||
value:
|
||||
requestPayload.routeType !== 'SEND' ? BigInt(requestPayload.value) : undefined,
|
||||
requestPayload.routeType !== 'SEND' ? ethers.toBigInt(requestPayload.value) : undefined,
|
||||
};
|
||||
const txHash = await signerWagmi.sendTransaction(tx);
|
||||
|
||||
onDeposit?.();
|
||||
|
||||
if (txHash) {
|
||||
addTransferNotification({
|
||||
txHash: txHash,
|
||||
setTransactionHash(txHash);
|
||||
abacusStateManager.setTransferStatus({
|
||||
hash: txHash,
|
||||
toChainId: TESTNET_CHAIN_ID,
|
||||
fromChainId: chainIdStr || undefined,
|
||||
toAmount: summary?.usdcSize || undefined,
|
||||
triggeredAt: Date.now(),
|
||||
fromChainId: chainId?.toString(),
|
||||
});
|
||||
abacusStateManager.clearTransferInputValues();
|
||||
setFromAmount('');
|
||||
}
|
||||
} catch (error) {
|
||||
log('DepositForm/onSubmit', error);
|
||||
setError(error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -337,7 +286,17 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||
}
|
||||
/>
|
||||
</Styled.WithDetailsReceipt>
|
||||
{errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>}
|
||||
{errorMessage ? (
|
||||
<AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>
|
||||
) : (
|
||||
transactionHash && (
|
||||
<AlertMessage type={AlertType.Success}>
|
||||
<Styled.TransactionInfo>
|
||||
{stringGetter({ key: STRING_KEYS.DEPOSIT_IN_PROGRESS })}
|
||||
</Styled.TransactionInfo>
|
||||
</AlertMessage>
|
||||
)
|
||||
)}
|
||||
<DepositButtonAndReceipt
|
||||
isDisabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { STRING_KEYS } from '@/constants/localization';
|
||||
import { NumberSign, QUANTUM_MULTIPLIER } from '@/constants/numbers';
|
||||
|
||||
import { useDebounce, useStringGetter, useSubaccount } from '@/hooks';
|
||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
import { formMixins } from '@/styles/formMixins';
|
||||
@@ -64,9 +63,9 @@ export const WithdrawForm = () => {
|
||||
[token, resources]
|
||||
);
|
||||
|
||||
const { addTransferNotification } = useLocalNotifications();
|
||||
|
||||
// Async Data
|
||||
const [transactionHash, setTransactionHash] = useState<string>();
|
||||
|
||||
const debouncedAmountBN = MustBigNumber(debouncedAmount);
|
||||
const withdrawAmountBN = MustBigNumber(withdrawAmount);
|
||||
const freeCollateralBN = MustBigNumber(freeCollateral?.current);
|
||||
@@ -134,12 +133,11 @@ export const WithdrawForm = () => {
|
||||
if (txHash?.hash) {
|
||||
const hash = `0x${Buffer.from(txHash.hash).toString('hex')}`;
|
||||
|
||||
addTransferNotification({
|
||||
txHash: hash,
|
||||
setTransactionHash(hash);
|
||||
abacusStateManager.setTransferStatus({
|
||||
hash,
|
||||
fromChainId: TESTNET_CHAIN_ID,
|
||||
toChainId: chainIdStr || undefined,
|
||||
toAmount: debouncedAmountBN.toNumber(),
|
||||
triggeredAt: Date.now(),
|
||||
});
|
||||
abacusStateManager.clearTransferInputValues();
|
||||
setWithdrawAmount('');
|
||||
@@ -150,7 +148,7 @@ export const WithdrawForm = () => {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[requestPayload, debouncedAmountBN, chainIdStr]
|
||||
[setTransactionHash, requestPayload, debouncedAmountBN, chainIdStr]
|
||||
);
|
||||
|
||||
const onChangeAddress = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -293,7 +291,17 @@ export const WithdrawForm = () => {
|
||||
}
|
||||
/>
|
||||
</Styled.WithDetailsReceipt>
|
||||
{errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>}
|
||||
{errorMessage ? (
|
||||
<AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>
|
||||
) : (
|
||||
transactionHash && (
|
||||
<AlertMessage type={AlertType.Success}>
|
||||
<Styled.TransactionInfo>
|
||||
{stringGetter({ key: STRING_KEYS.WITHDRAW_IN_PROGRESS })}
|
||||
</Styled.TransactionInfo>
|
||||
</AlertMessage>
|
||||
)
|
||||
)}
|
||||
<WithdrawButtonAndReceipt
|
||||
isDisabled={isDisabled}
|
||||
isLoading={isLoading}
|
||||
|
||||
@@ -84,7 +84,7 @@ export const TransferForm = ({
|
||||
const { dydxAddress } = useAccounts();
|
||||
const { address: recipientAddress, size } = useSelector(getTransferInputs, shallowEqual) || {};
|
||||
const { transfer, simulateTransfer } = useSubaccount();
|
||||
const { nativeTokenBalance, usdcBalance } = useAccountBalance();
|
||||
const { nativeTokenBalance } = useAccountBalance();
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
|
||||
// User Input
|
||||
@@ -104,8 +104,6 @@ export const TransferForm = ({
|
||||
.toNumber();
|
||||
const amount = asset === DydxChainAsset.USDC ? size?.usdcSize : size?.size;
|
||||
|
||||
const showNotEnoughGasWarning = fees && asset === DydxChainAsset.USDC && usdcBalance < fees;
|
||||
|
||||
// BN
|
||||
const amountBN = MustBigNumber(amount);
|
||||
const balanceBN = MustBigNumber(balance);
|
||||
@@ -361,15 +359,6 @@ export const TransferForm = ({
|
||||
/>
|
||||
</WithDetailsReceipt>
|
||||
|
||||
{showNotEnoughGasWarning && (
|
||||
<AlertMessage type={AlertType.Warning}>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.TRANSFER_INSUFFICIENT_GAS,
|
||||
params: { USDC_BALANCE: `(${usdcBalance})` },
|
||||
})}
|
||||
</AlertMessage>
|
||||
)}
|
||||
|
||||
{error && <AlertMessage type={AlertType.Error}>{error.message}</AlertMessage>}
|
||||
|
||||
<Styled.Footer>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { groupBy } from 'lodash';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { groupBy } from 'lodash';
|
||||
|
||||
import { type Notification, NotificationStatus } from '@/constants/notifications';
|
||||
import { useStringGetter } from '@/hooks';
|
||||
|
||||
import { useNotifications } from '@/hooks/useNotifications';
|
||||
import { CloseIcon } from '@/icons';
|
||||
|
||||
import { Button } from '@/components/Button';
|
||||
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
||||
@@ -15,7 +13,9 @@ import { DialogPlacement } from '@/components/Dialog';
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { IconButton } from '@/components/IconButton';
|
||||
import { Toolbar } from '@/components/Toolbar';
|
||||
import { CloseIcon } from '@/icons';
|
||||
|
||||
import styled from 'styled-components';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
type ElementProps = {
|
||||
@@ -27,8 +27,6 @@ export const NotificationsMenu = ({
|
||||
slotTrigger,
|
||||
placement = DialogPlacement.Sidebar,
|
||||
}: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const {
|
||||
notifications,
|
||||
getDisplayData,
|
||||
@@ -71,7 +69,7 @@ export const NotificationsMenu = ({
|
||||
.map(([status, notifications]) => ({
|
||||
group: status,
|
||||
groupLabel: {
|
||||
[NotificationStatus.Triggered]: stringGetter({ key: STRING_KEYS.NEW }),
|
||||
[NotificationStatus.Triggered]: 'New',
|
||||
// [NotificationStatus.Updated]: 'Updates',
|
||||
[NotificationStatus.Seen]: 'Seen',
|
||||
[NotificationStatus.Cleared]: 'Archived',
|
||||
@@ -91,9 +89,9 @@ export const NotificationsMenu = ({
|
||||
.map(({ notification, key, displayData }) => ({
|
||||
value: key,
|
||||
label: displayData.title ?? '',
|
||||
description: displayData.customMenuContent || displayData.description,
|
||||
slotBefore: !displayData.customMenuContent && displayData.icon,
|
||||
slotAfter: !displayData.customMenuContent && (
|
||||
description: displayData.description,
|
||||
slotBefore: displayData.icon,
|
||||
slotAfter: (
|
||||
<>
|
||||
<$Output
|
||||
type={OutputType.RelativeTime}
|
||||
@@ -137,7 +135,7 @@ export const NotificationsMenu = ({
|
||||
isOpen={isMenuOpen || placement === DialogPlacement.Inline}
|
||||
setIsOpen={setIsMenuOpen}
|
||||
items={items}
|
||||
title={stringGetter({ key: STRING_KEYS.NOTIFICATIONS })}
|
||||
title="Notifications"
|
||||
slotTrigger={
|
||||
<$TriggerContainer>
|
||||
{slotTrigger}
|
||||
@@ -170,7 +168,7 @@ export const NotificationsMenu = ({
|
||||
),
|
||||
}}
|
||||
>
|
||||
{stringGetter({ key: STRING_KEYS.CLEAR })}
|
||||
Clear All
|
||||
</Button>
|
||||
</$FooterToolbar>
|
||||
}
|
||||
|
||||
@@ -510,7 +510,7 @@ Styled.OrderbookTable = styled(OrderbookTradesTable)<StyleProps>`
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:not(:active):is(:focus-visible, :focus-within) {
|
||||
&:not(:active):is(:focus, :focus-within) {
|
||||
${orderbookMixins.scrollSnapItem}
|
||||
z-index: 2;
|
||||
|
||||
|
||||
@@ -72,8 +72,8 @@ export const OrderbookTradesTable = styled(Table)<{ histogramSide: 'left' | 'rig
|
||||
}
|
||||
|
||||
tbody tr:hover,
|
||||
tr:focus-visible,
|
||||
td:focus-visible {
|
||||
tr:focus,
|
||||
td:focus {
|
||||
color: var(--color-text-2);
|
||||
background-color: var(--color-layer-3);
|
||||
}
|
||||
|
||||