Compare commits

..
Author SHA1 Message Date
Bill He a21558ad88 do not use TESTNET_CHAIN_ID 2023-11-06 16:07:55 -08:00
48 changed files with 324 additions and 712 deletions
-5
View File
@@ -98,11 +98,6 @@ For the "Build & Development Settings", we recommend the following:
- Framework Preset: `Vite`
- Build Command (override): `pnpm run build`
By default, the dev server runs in development mode and the build command runs in production mode. To override the default mode, you can pass in the `--mode` option flag. For example, if you want to build your app for testnet:
```
pnpm run build --mode testnet
```
If you wish to incorporate analytics via Amplitude and Bugsnag, you can use our scripts:
`pnpm run build:inject-amplitude` and `pnpm run build:inject-bugsnag`. You will need to provide your own API keys for these services. In the Environment Variables section, name the variables as `AMPLITUDE_API_KEY` and `BUGSNAG_API_KEY` and provide the respective keys as their values.
+2 -3
View File
@@ -39,9 +39,9 @@
"@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^1.0.24",
"@dydxprotocol/v4-abacus": "^1.0.10",
"@dydxprotocol/v4-client-js": "^1.0.0",
"@dydxprotocol/v4-localization": "^1.0.5",
"@dydxprotocol/v4-localization": "^1.0.0",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
"@radix-ui/react-collapsible": "^1.0.3",
@@ -96,7 +96,6 @@
"long": "^5.2.3",
"luxon": "^3.3.0",
"qr-code-styling": "1.6.0-rc.1",
"query-string": "^8.1.0",
"react": "^18.2.0",
"react-aria": "^3.25.0",
"react-dom": "^18.2.0",
+12 -11
View File
@@ -27,14 +27,14 @@ dependencies:
specifier: ^0.31.0
version: 0.31.0
'@dydxprotocol/v4-abacus':
specifier: ^1.0.24
version: 1.0.24
specifier: ^1.0.10
version: 1.0.10
'@dydxprotocol/v4-client-js':
specifier: ^1.0.0
version: 1.0.0
'@dydxprotocol/v4-localization':
specifier: ^1.0.5
version: 1.0.5
specifier: ^1.0.0
version: 1.0.0
'@ethersproject/providers':
specifier: ^5.7.2
version: 5.7.2
@@ -197,9 +197,6 @@ dependencies:
qr-code-styling:
specifier: 1.6.0-rc.1
version: 1.6.0-rc.1
query-string:
specifier: ^8.1.0
version: 8.1.0
react:
specifier: ^18.2.0
version: 18.2.0
@@ -982,8 +979,8 @@ packages:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true
/@dydxprotocol/v4-abacus@1.0.24:
resolution: {integrity: sha512-wDGSjkrc3Se6Ev7UTjPgJV7PiyzZSz2mJwOTZikLwH7W3k1iPYGQmaCd7TudFk8h2aSdlEwNsQBCf0sLoyvHaQ==}
/@dydxprotocol/v4-abacus@1.0.10:
resolution: {integrity: sha512-SdxI/JcyxM1ydtR9ImWujeurea3gbxj37qYnw1wz829BQiZ05uNEtll3xJCI0MjpG+01ARsxVys7SHbpjSxwOg==}
dev: false
/@dydxprotocol/v4-client-js@1.0.0:
@@ -1014,8 +1011,8 @@ packages:
- utf-8-validate
dev: false
/@dydxprotocol/v4-localization@1.0.5:
resolution: {integrity: sha512-zZm3GFp/ORz9YjigQFVBlcWmaxYY+XEdIw8AWN5nEbsuiA2Jay+/OA7lO9+nV1J1p1T+TgLo9iczIHJsAo3IsA==}
/@dydxprotocol/v4-localization@1.0.0:
resolution: {integrity: sha512-U7QjOlzpfSAVEchkNDB3xYRx7okzdeYi83XsnHwI+EHa2quFm/2Y2dhrBkT5fqpZmhcwxb/vG8AZmFSToOf9hA==}
dev: false
/@dydxprotocol/v4-proto@0.4.1:
@@ -7878,6 +7875,7 @@ packages:
/decode-uri-component@0.4.1:
resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==}
engines: {node: '>=14.16'}
dev: true
/deep-eql@4.1.3:
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
@@ -8947,6 +8945,7 @@ packages:
/filter-obj@5.1.0:
resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==}
engines: {node: '>=14.16'}
dev: true
/find-cache-dir@3.3.2:
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
@@ -11877,6 +11876,7 @@ packages:
decode-uri-component: 0.4.1
filter-obj: 5.1.0
split-on-first: 3.0.0
dev: true
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
@@ -12701,6 +12701,7 @@ packages:
/split-on-first@3.0.0:
resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==}
engines: {node: '>=12'}
dev: true
/split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
+2 -74
View File
@@ -1,6 +1,6 @@
{
"apps": {
"ios": {
"iOS": {
"scheme": "dydxv4"
}
},
@@ -400,77 +400,6 @@
"signTypedDataDomainName": "dYdX Chain"
}
},
"dydxprotocol-staging-forced-update": {
"name": "v4 Staging Forced Update",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "dydx-chain.png",
"isMainNet": false,
"tokens": {
"chain": {
"name": "Dv4TNT",
"denom": "adv4tnt",
"decimals": 18,
"image": "/currencies/dydx.png"
},
"usdc": {
"name": "USDC",
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
"gasDenom": "uusdc",
"decimals": 6,
"image": "/currencies/usdc.png"
}
},
"endpoints": {
"indexers": [
{
"api": "https://indexer.v4staging.dydx.exchange",
"socket": "wss://indexer.v4staging.dydx.exchange"
}
],
"faucet": "https://faucet.v4staging.dydx.exchange",
"validators": [
"https://validator.v4staging.dydx.exchange"
],
"0xsquid": "https://testnet.api.squidrouter.com"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
"privacy": "https://dydx.exchange/privacy",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"documentation": "https://v4-teacher.vercel.app/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog"
},
"wallets": {
"walletconnect": {
"client": {
"name": "dYdX v4",
"description": "dYdX v4 App",
"iconUrl": "/logos/dydx-x.png"
},
"v2": {
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
}
},
"walletSegue": {
"callbackUrl": "/walletsegue"
},
"images": "/wallets/",
"signTypedDataAction": "dYdX Chain Onboarding",
"signTypedDataDomainName": "dYdX Chain"
},
"apps": {
"ios": {
"minimalVersion": "1.0",
"build":40000,
"url": "https://apps.apple.com/app/dydx/id1564787350"
}
}
},
"dydxprotocol-staging-west": {
"name": "v4 Staging West",
"ethereumChainId": "5",
@@ -964,7 +893,6 @@
"privacy": "https://dydx.exchange/privacy",
"statusPage": "https://status.v4testnet.dydx.exchange/",
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
"documentation": "https://docs.dydx.exchange/",
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
@@ -1105,7 +1033,7 @@
"tos": "[HTTP link to TOS]",
"privacy": "[HTTP link to Privacy Policy]",
"mintscan": "[HTTP link to Mintscan, with {tx_hash} placeholder]",
"mintscanBase": "[HTTP link to TOS mintscan base url]",
"mintscanBase": "[HTTP link to TOS mintsacn base url]",
"feedback": "[HTTP link to feedback form, can be null]",
"blogs": "[HTTP link to blogs, can be null]",
"foundation": "[HTTP link to foundation, can be null]",
-2
View File
@@ -23,7 +23,6 @@ import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications';
import { RestrictionProvider } from '@/hooks/useRestrictions';
import { SubaccountProvider } from '@/hooks/useSubaccount';
import { SquidProvider } from '@/hooks/useSquid';
import { TestFlagsProvider } from '@/hooks/useTestFlags';
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
@@ -124,7 +123,6 @@ const providers = [
wrapProvider(QueryClientProvider, { client: queryClient }),
wrapProvider(GrazProvider),
wrapProvider(WagmiConfig, { config }),
wrapProvider(TestFlagsProvider),
wrapProvider(LocaleProvider),
wrapProvider(RestrictionProvider),
wrapProvider(DydxProvider),
+1 -7
View File
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from 'react';
import { useState } from 'react';
import styled, { type AnyStyledComponent, css } from 'styled-components';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -22,11 +22,6 @@ export const SearchInput = ({
}: SearchInputProps) => {
const [isOpen, setIsOpen] = useState(false);
const [value, setValue] = useState('');
const inputRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
if (isOpen) inputRef?.current?.focus();
}, [inputRef, isOpen]);
return (
<Styled.Search>
@@ -46,7 +41,6 @@ export const SearchInput = ({
/>
<Styled.Input
autoFocus
ref={inputRef}
value={value}
isOpen={isOpen}
type={type}
+8 -12
View File
@@ -831,7 +831,6 @@ Styled.SortArrow = styled.span<{ sortDirection: 'ascending' | 'descending' }>`
Styled.Thead = styled.thead<StyleProps>`
${layoutMixins.stickyHeader}
scroll-snap-align: none;
font: var(--font-mini-book);
> * {
height: var(--stickyArea-topHeight);
@@ -840,17 +839,18 @@ Styled.Thead = styled.thead<StyleProps>`
color: var(--tableHeader-textColor);
background-color: var(--tableHeader-backgroundColor);
${({ withInnerBorders, withGradientCardRows }) =>
withInnerBorders &&
!withGradientCardRows &&
css`
${layoutMixins.withInnerHorizontalBorders}
`}
@media ${breakpoints.notTablet} {
${({ withInnerBorders, withGradientCardRows }) =>
withInnerBorders &&
!withGradientCardRows &&
css`
${layoutMixins.withInnerHorizontalBorders}
`}
}
`;
Styled.Tbody = styled.tbody<StyleProps>`
${layoutMixins.stickyArea2}
font: var(--font-small-book);
// If <table> height is fixed with not enough rows to overflow, vertically center the rows
&:before,
@@ -869,10 +869,6 @@ Styled.Tbody = styled.tbody<StyleProps>`
--stickyArea2-paddingBottom: var(--border-width);
--stickyArea2-paddingLeft: var(--border-width);
--stickyArea2-paddingRight: var(--border-width);
tr:first-of-type {
box-shadow: none;
}
`}
${({ withGradientCardRows }) =>
+6 -12
View File
@@ -1,18 +1,12 @@
import environments from '../../public/configs/env.json';
const CURRENT_MODE = ({
production: 'MAINNET',
testnet: 'TESTNET',
staging: 'DEV',
development: 'DEV',
}[import.meta.env.MODE] ?? 'MAINNET') as 'MAINNET' | 'TESTNET' | 'DEV';
// TODO: Update for Mainnet deployment
export const AVAILABLE_ENVIRONMENTS =
import.meta.env.MODE === 'production'
? environments.deployments.TESTNET
: environments.deployments.DEV;
export const isMainnet = CURRENT_MODE === 'MAINNET';
export const isTestnet = CURRENT_MODE === 'TESTNET';
export const isDev = CURRENT_MODE === 'DEV';
export const AVAILABLE_ENVIRONMENTS = environments.deployments[CURRENT_MODE];
export const CURRENT_ABACUS_DEPLOYMENT = CURRENT_MODE;
export const CURRENT_ABACUS_DEPLOYMENT = import.meta.env.MODE === 'production' ? 'TESTNET' : 'DEV';
export const ENVIRONMENT_CONFIG_MAP = environments.environments;
export type DydxNetwork = keyof typeof ENVIRONMENT_CONFIG_MAP;
export const DEFAULT_APP_ENVIRONMENT = AVAILABLE_ENVIRONMENTS.default as DydxNetwork;
+2 -4
View File
@@ -42,7 +42,6 @@ const useAccountsContext = () => {
selectedWalletError,
evmAddress,
signerWagmi,
publicClientWagmi,
dydxAddress: connectedDydxAddress,
signerGraz,
} = useWalletConnection();
@@ -136,8 +135,8 @@ const useAccountsContext = () => {
getSubaccounts: async ({ dydxAddress }: { dydxAddress: DydxAddress }) => {
try {
const response = await compositeClient?.indexerClient.account.getSubaccounts(dydxAddress);
setDydxSubaccounts(response?.subaccounts);
return response?.subaccounts ?? [];
setDydxSubaccounts(response.subaccounts);
return response.subaccounts;
} catch (error) {
// 404 is expected if the user has no subaccounts
if (error.status === 404) {
@@ -297,7 +296,6 @@ const useAccountsContext = () => {
// Wallet connection (EVM)
evmAddress,
signerWagmi,
publicClientWagmi,
// Wallet connection (Cosmos)
signerGraz,
+1 -2
View File
@@ -1,4 +1,3 @@
import { isDev } from '@/constants/networks';
import { WalletType } from '@/constants/wallets';
import { isTruthy } from '@/lib/isTruthy';
@@ -7,7 +6,7 @@ export const useDisplayedWallets = () => {
return [
WalletType.MetaMask,
isDev && WalletType.Keplr,
import.meta.env.MODE !== 'production' && WalletType.Keplr,
WalletType.WalletConnect2,
+1 -10
View File
@@ -66,16 +66,7 @@ const useLocalNotificationsContext = () => {
status: currentStatus,
} of transferNotifications) {
try {
// skip if error is returned or if the transaction is not ongoing
if (
// @ts-ignore status.errors is not in the type definition but can be returned
currentStatus?.errors ||
currentStatus?.error ||
(currentStatus?.squidTransactionStatus &&
currentStatus?.squidTransactionStatus !== 'ongoing')
) {
continue;
}
if (currentStatus && currentStatus?.squidTransactionStatus !== 'ongoing') continue;
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId });
if (status) statuses[txHash] = status;
+1 -1
View File
@@ -8,7 +8,7 @@ import { getSelectedNetwork } from '@/state/appSelectors';
export const NATIVE_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
export const STATUS_ERROR_GRACE_PERIOD = 300_000;
export const STATUS_ERROR_GRACE_PERIOD = 120_000;
const useSquidContext = () => {
const selectedNetwork = useSelector(getSelectedNetwork);
+60 -91
View File
@@ -48,7 +48,7 @@ export const useSubaccount = () => useContext(SubaccountContext);
export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWallet }) => {
const dispatch = useDispatch();
const { usdcDenom, usdcDecimals } = useTokenConfigs();
const { usdcDenom } = useTokenConfigs();
const { compositeClient, faucetClient } = useDydxClient();
const { getFaucetFunds } = useMemo(
@@ -79,34 +79,16 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
subaccountClient: SubaccountClient;
assetId?: number;
amount: number;
}) => {
try {
return await compositeClient?.depositToSubaccount(
subaccountClient,
amount.toFixed(usdcDecimals)
);
} catch (error) {
log('useSubaccount/depositToSubaccount', error);
throw error;
}
},
}) => await compositeClient?.depositToSubaccount(subaccountClient, amount.toString()),
withdrawFromSubaccount: async ({
subaccountClient,
amount,
}: {
subaccountClient: SubaccountClient;
amount: number;
}) => {
try {
return await compositeClient?.withdrawFromSubaccount(
subaccountClient,
amount.toFixed(usdcDecimals)
);
} catch (error) {
log('useSubaccount/withdrawFromSubaccount', error);
throw error;
}
},
}) => await compositeClient?.withdrawFromSubaccount(subaccountClient, amount.toString()),
transferFromSubaccountToAddress: async ({
subaccountClient,
assetId = 0,
@@ -117,33 +99,27 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
assetId?: number;
amount: number;
recipient: string;
}) => {
try {
return await compositeClient?.validatorClient.post.send(
subaccountClient?.wallet,
() =>
new Promise((resolve) => {
const msg =
compositeClient?.validatorClient.post.composer.composeMsgWithdrawFromSubaccount(
subaccountClient.address,
subaccountClient.subaccountNumber,
assetId,
Long.fromNumber(amount * QUANTUM_MULTIPLIER),
recipient
);
}) =>
await compositeClient?.validatorClient.post.send(
subaccountClient?.wallet,
() =>
new Promise((resolve) => {
const msg =
compositeClient?.validatorClient.post.composer.composeMsgWithdrawFromSubaccount(
subaccountClient.address,
subaccountClient.subaccountNumber,
assetId,
Long.fromNumber(amount * QUANTUM_MULTIPLIER),
recipient
);
resolve([msg]);
}),
false,
undefined,
undefined,
Method.BroadcastTxCommit
);
} catch (error) {
log('useSubaccount/transferFromSubaccountToAddress', error);
throw error;
}
},
resolve([msg]);
}),
false,
undefined,
undefined,
Method.BroadcastTxCommit
),
transferNativeToken: async ({
subaccountClient,
@@ -153,30 +129,24 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
subaccountClient: SubaccountClient;
amount: number;
recipient: string;
}) => {
try {
return await compositeClient?.validatorClient.post.send(
subaccountClient.wallet,
() =>
new Promise((resolve) => {
const msg = compositeClient?.sendTokenMessage(
subaccountClient.wallet,
amount.toString(),
recipient
);
}) =>
await compositeClient?.validatorClient.post.send(
subaccountClient.wallet,
() =>
new Promise((resolve) => {
const msg = compositeClient?.sendTokenMessage(
subaccountClient.wallet,
amount.toString(),
recipient,
);
resolve([msg]);
}),
false,
compositeClient?.validatorClient?.post.defaultDydxGasPrice,
undefined,
Method.BroadcastTxCommit
);
} catch (error) {
log('useSubaccount/transferNativeToken', error);
throw error;
}
},
resolve([msg]);
}),
false,
compositeClient?.validatorClient?.post.defaultDydxGasPrice,
undefined,
Method.BroadcastTxCommit
),
sendSquidWithdrawFromSubaccount: async ({
subaccountClient,
@@ -188,27 +158,20 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
payload: string;
}) => {
if (!compositeClient) throw new Error('client not initialized');
try {
const transaction = JSON.parse(payload);
const msg = compositeClient.withdrawFromSubaccountMessage(
subaccountClient,
amount.toFixed(usdcDecimals)
);
const ibcMsg: EncodeObject = {
typeUrl: transaction.msgTypeUrl,
value: transaction.msg,
};
const transaction = JSON.parse(payload);
return await compositeClient.send(
subaccountClient.wallet,
() => Promise.resolve([msg, ibcMsg]),
false
);
} catch (error) {
log('useSubaccount/sendSquidWithdrawFromSubaccount', error);
throw error;
}
const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount.toString());
const ibcMsg: EncodeObject = {
typeUrl: transaction.msgTypeUrl,
value: transaction.msg,
};
return await compositeClient.send(
subaccountClient.wallet,
() => Promise.resolve([msg, ibcMsg]),
false
);
},
}),
[compositeClient]
@@ -350,6 +313,11 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
placeOrderParams = abacusStateManager.placeOrder(callback);
}
track(AnalyticsEvent.TradePlaceOrder, {
...placeOrderParams,
isClosePosition,
} as HumanReadablePlaceOrderPayload & { isClosePosition: boolean });
return placeOrderParams;
},
[subaccountClient]
@@ -378,6 +346,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
}) => {
const callback = (success: boolean, parsingError?: Nullable<ParsingError>) => {
if (success) {
track(AnalyticsEvent.TradeCancelOrder);
onSuccess?.();
} else {
onError?.({ errorStringKey: parsingError?.stringKey });
-46
View File
@@ -1,46 +0,0 @@
import { createContext, useContext, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import queryString from 'query-string';
class TestFlags {
public queryParams: queryString.ParsedQuery<string>;
constructor() {
this.queryParams = {};
}
setQueryParams(flags: queryString.ParsedQuery<string>) {
this.queryParams = flags;
}
get displayInitializingMarkets() {
return !!this.queryParams.displayInitializingMarkets;
}
}
export const testFlags = new TestFlags();
const useTestFlagsContext = () => {
const [queryParams, setQueryParams] = useState<queryString.ParsedQuery<string>>({});
const location = useLocation();
useEffect(() => {
const parsedQueryParams = queryString.parse(location.search.substring(-1));
testFlags.setQueryParams(parsedQueryParams);
setQueryParams(parsedQueryParams);
}, []);
return {
...queryParams,
};
};
type TestFlagsContextType = ReturnType<typeof useTestFlagsContext>;
const TestFlagsContext = createContext<TestFlagsContextType>({} as TestFlagsContextType);
TestFlagsContext.displayName = 'TestFlags';
export const TestFlagsProvider = ({ ...props }) => (
<TestFlagsContext.Provider value={useTestFlagsContext()} {...props} />
);
export const useTestFlags = () => useContext(TestFlagsContext);
-4
View File
@@ -19,10 +19,8 @@ export const useTokenConfigs = (): {
},
};
usdcDenom: string;
usdcDecimals: number;
usdcLabel: string;
chainTokenDenom: string;
chainTokenDecimals: number;
chainTokenLabel: string;
} => {
const { selectedNetwork } = useSelectedNetwork();
@@ -31,10 +29,8 @@ export const useTokenConfigs = (): {
return {
tokensConfigs,
usdcDenom: tokensConfigs[DydxChainAsset.USDC].denom,
usdcDecimals: tokensConfigs[DydxChainAsset.USDC].decimals,
usdcLabel: tokensConfigs[DydxChainAsset.USDC].name,
chainTokenDenom: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom,
chainTokenDecimals: tokensConfigs[DydxChainAsset.CHAINTOKEN].decimals,
chainTokenLabel: tokensConfigs[DydxChainAsset.CHAINTOKEN].name,
};
};
+9 -11
View File
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState, useMemo } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { LocalStorageKey } from '@/constants/localStorage';
@@ -90,21 +90,19 @@ export const useWalletConnection = () => {
const selectedNetwork = useSelector(getSelectedNetwork);
const walletConnectConfig = ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.walletconnect;
const wagmiConnector = useMemo(
() =>
walletType && walletConnectionType
? resolveWagmiConnector({
const { connectAsync: connectWagmi } =
walletType && walletConnectionType
? useConnectWagmi({
connector: resolveWagmiConnector({
walletType,
walletConnection: {
type: walletConnectionType,
},
walletConnectConfig,
})
: undefined,
[walletConnectConfig, walletType, walletConnectionType]
);
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector })
}),
})
: useConnectWagmi();
const { suggestAndConnect: connectGraz } = useConnectGraz();
const connectWallet = useCallback(
+2 -2
View File
@@ -3,7 +3,7 @@ import styled, { type AnyStyledComponent, css } from 'styled-components';
import { AbacusApiStatus } from '@/constants/abacus';
import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP, isDev } from '@/constants/networks';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { useApiState, useSelectedNetwork, useStringGetter } from '@/hooks';
import { ChatIcon, LinkOutIcon } from '@/icons';
@@ -75,7 +75,7 @@ export const FooterDesktop = () => {
)}
</Styled.Row>
{isDev && (
{import.meta.env.MODE !== 'production' && (
<Styled.Details
withSeparators
items={[
-1
View File
@@ -236,5 +236,4 @@ Styled.IconButton = styled(IconButton)<{ size?: string }>`
${headerMixins.button}
--button-border: none;
--button-icon-size: 1rem;
--button-padding: 0 0.5em;
`;
+10 -10
View File
@@ -30,7 +30,7 @@ import {
import { DialogTypes } from '@/constants/dialogs';
import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade';
import { ENVIRONMENT_CONFIG_MAP, DydxNetwork, isTestnet } from '@/constants/networks';
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
import { RootStore } from '@/state/_store';
import { addUncommittedOrderClientId, removeUncommittedOrderClientId } from '@/state/account';
@@ -198,12 +198,8 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
const parsedTx = this.parseToPrimitives(tx);
const hash = parsedTx?.hash;
if (isTestnet) {
console.log(
`${ENVIRONMENT_CONFIG_MAP[
this.compositeClient.network.getString() as DydxNetwork
]?.links?.mintscan?.replace('{tx_hash}', hash.toString())}`
);
if (import.meta.env.MODE === 'production') {
console.log(`https://testnet.mintscan.io/dydx-testnet/txs/${hash}`);
} else console.log(`txHash: ${hash}`);
return JSON.stringify(parsedTx);
@@ -233,7 +229,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
orderFlags,
clobPairId,
goodTilBlock || undefined,
goodTilBlockTime || undefined
goodTilBlockTime || undefined,
);
const parsedTx = this.parseToPrimitives(tx);
@@ -299,11 +295,15 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
if (!this.localWallet) {
throw new Error('Missing compositeClient or localWallet');
}
const msg = compositeClient?.sendTokenMessage(this.localWallet, amount, recipient);
const msg = compositeClient?.sendTokenMessage(
this.localWallet,
amount,
recipient,
);
resolve([msg]);
}),
this.compositeClient?.validatorClient?.post.defaultDydxGasPrice
this.compositeClient?.validatorClient?.post.defaultDydxGasPrice,
);
const parsedTx = this.parseToPrimitives(tx);
+5
View File
@@ -1,5 +1,10 @@
import type { AbacusFileSystemProtocol, FileLocation, Nullable } from '@/constants/abacus';
export const ENDPOINTS_PATH =
import.meta.env.MODE === 'production'
? 'config/prod/endpoints.json'
: 'config/staging/dev_endpoints.json';
class AbacusFileSystem implements AbacusFileSystemProtocol {
readTextFile(location: FileLocation, path: string): Nullable<string> {
return null;
+1 -1
View File
@@ -41,7 +41,7 @@ import AbacusStateNotifier from './stateNotification';
import AbacusLocalizer from './localizer';
import AbacusFormatter from './formatter';
import AbacusThreading from './threading';
import AbacusFileSystem from './filesystem';
import AbacusFileSystem, { ENDPOINTS_PATH } from './filesystem';
import { LocaleSeparators } from '../numbers';
class AbacusStateManager {
private store: RootStore | undefined;
+7 -2
View File
@@ -56,8 +56,13 @@ class AbacusRest implements AbacusRestProtocol {
.then(async (response) => {
const data = await response.text();
callback(data, response.status);
if (response.ok) {
callback(data, response.status);
} else {
// response not OK, call callback with null data and the status, this includes 400/500 status codes
callback(null, response.status);
}
try {
lastSuccessfulRestRequestByOrigin[new URL(url).origin] = Date.now();
} catch {}
+4 -23
View File
@@ -1,6 +1,5 @@
import type { AbacusWebsocketProtocol } from '@/constants/abacus';
import type { TradingViewBar } from '@/constants/candles';
import { isDev } from '@/constants/networks';
import {
PING_INTERVAL_MS,
@@ -9,12 +8,11 @@ import {
PONG_MESSAGE_TYPE,
} from '@/constants/websocket';
import { lastSuccessfulWebsocketRequestByOrigin } from '@/hooks/useAnalytics';
import { testFlags } from '@/hooks/useTestFlags';
import { subscriptionsByChannelId } from '@/lib/tradingView/dydxfeed/cache';
import { mapCandle } from '@/lib/tradingView/utils';
import { lastSuccessfulWebsocketRequestByOrigin } from '@/hooks/useAnalytics';
import { log } from '../telemetry';
const RECONNECT_INTERVAL_MS = 10_000;
@@ -145,23 +143,6 @@ class AbacusWebsocket implements Omit<AbacusWebsocketProtocol, '__doNotUseOrImpl
break;
}
case 'v4_markets': {
if (testFlags.displayInitializingMarkets) {
shouldProcess = false;
const { contents } = parsedMessage;
Object.keys(contents.markets ?? {}).forEach((market: any) => {
const status = contents.markets[market].status;
if (status === 'INITIALIZING') {
contents.markets[market].status = 'ONLINE';
}
});
this.receivedCallback?.(JSON.stringify(parsedMessage));
}
break;
}
default: {
break;
}
@@ -180,13 +161,13 @@ class AbacusWebsocket implements Omit<AbacusWebsocketProtocol, '__doNotUseOrImpl
this.socket.onclose = (e) => {
this.connectedCallback?.(false);
if (!isDev) return;
if (import.meta.env.MODE === 'production') return;
console.warn('AbacusStateManager > WS > close > ', e);
};
this.socket.onerror = (e) => {
this.connectedCallback?.(false);
if (!isDev) return;
if (import.meta.env.MODE === 'production') return;
console.error('AbacusStateManager > WS > error > ', e);
};
};
+1 -3
View File
@@ -1,7 +1,5 @@
import { isDev } from "@/constants/networks";
export const log = (location: string, error: Error, metadata?: any) => {
if (isDev) {
if (import.meta.env.MODE !== 'production') {
console.warn('telemetry/log:', { location, error, metadata });
}
+4
View File
@@ -304,6 +304,10 @@ Styled.FeeTable = styled(Table)`
@media ${breakpoints.notTablet} {
--tableHeader-backgroundColor: var(--color-layer-1);
thead tr {
--border-width: 0;
}
}
`;
+42 -104
View File
@@ -1,13 +1,11 @@
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { useEffect } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components';
import { Navigate, Route, Routes } from 'react-router-dom';
import { OnboardingState } from '@/constants/account';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { HistoryRoute, PortfolioRoute } from '@/constants/routes';
import { useAccountBalance, useBreakpoints, useDocumentTitle, useStringGetter } from '@/hooks';
import { useBreakpoints, useDocumentTitle, useStringGetter } from '@/hooks';
import { FillsTable, FillsTableColumnKey } from '@/views/tables/FillsTable';
import { FundingPaymentsTable } from '@/views/tables/FundingPaymentsTable';
@@ -16,9 +14,6 @@ import { Icon, IconName } from '@/components/Icon';
import { NavigationMenu } from '@/components/NavigationMenu';
import { WithSidebar } from '@/components/WithSidebar';
import { getOnboardingState, getSubaccount } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs';
import { PortfolioNavMobile } from './PortfolioNavMobile';
import { Overview } from './Overview';
import { Positions } from './Positions';
@@ -27,20 +22,11 @@ import { Fees } from './Fees';
import { History } from './History';
import { layoutMixins } from '@/styles/layoutMixins';
import { Button } from '@/components/Button';
import { ButtonAction } from '@/constants/buttons';
export default () => {
const dispatch = useDispatch();
const stringGetter = useStringGetter();
const { isTablet, isNotTablet } = useBreakpoints();
const onboardingState = useSelector(getOnboardingState);
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
const { nativeTokenBalance } = useAccountBalance();
const usdcBalance = freeCollateral?.current || 0;
useDocumentTitle(stringGetter({ key: STRING_KEYS.PORTFOLIO }));
const routesComponent = (
@@ -98,74 +84,46 @@ export default () => {
<WithSidebar
sidebar={
isTablet ? null : (
<Styled.SideBar>
<Styled.NavigationMenu
items={[
{
group: 'views',
groupLabel: stringGetter({ key: STRING_KEYS.VIEWS }),
items: [
{
value: PortfolioRoute.Overview,
slotBefore: <Styled.Icon iconName={IconName.Overview} />,
label: stringGetter({ key: STRING_KEYS.OVERVIEW }),
href: PortfolioRoute.Overview,
},
{
value: PortfolioRoute.Positions,
slotBefore: <Styled.Icon iconName={IconName.Cube} />,
label: stringGetter({ key: STRING_KEYS.POSITIONS }),
href: PortfolioRoute.Positions,
},
{
value: PortfolioRoute.Orders,
slotBefore: <Styled.Icon iconName={IconName.OrderPending} />,
label: stringGetter({ key: STRING_KEYS.ORDERS }),
href: PortfolioRoute.Orders,
},
{
value: PortfolioRoute.Fees,
slotBefore: <Styled.Icon iconName={IconName.Calculator} />,
label: stringGetter({ key: STRING_KEYS.FEES }),
href: PortfolioRoute.Fees,
},
{
value: PortfolioRoute.History,
slotBefore: <Styled.Icon iconName={IconName.History} />,
label: stringGetter({ key: STRING_KEYS.HISTORY }),
href: PortfolioRoute.History,
},
],
},
]}
/>
{onboardingState === OnboardingState.AccountConnected && (
<Styled.Footer>
<Button
action={ButtonAction.Primary}
onClick={() => dispatch(openDialog({ type: DialogTypes.Deposit }))}
>
{stringGetter({ key: STRING_KEYS.DEPOSIT })}
</Button>
{usdcBalance > 0 && (
<Button
action={ButtonAction.Base}
onClick={() => dispatch(openDialog({ type: DialogTypes.Withdraw }))}
>
{stringGetter({ key: STRING_KEYS.WITHDRAW })}
</Button>
)}
{(usdcBalance > 0 || nativeTokenBalance.gt(0)) && (
<Button
action={ButtonAction.Base}
onClick={() => dispatch(openDialog({ type: DialogTypes.Transfer }))}
>
{stringGetter({ key: STRING_KEYS.TRANSFER })}
</Button>
)}
</Styled.Footer>
)}
</Styled.SideBar>
<Styled.NavigationMenu
items={[
{
group: 'views',
groupLabel: stringGetter({ key: STRING_KEYS.VIEWS }),
items: [
{
value: PortfolioRoute.Overview,
slotBefore: <Styled.Icon iconName={IconName.Overview} />,
label: stringGetter({ key: STRING_KEYS.OVERVIEW }),
href: PortfolioRoute.Overview,
},
{
value: PortfolioRoute.Positions,
slotBefore: <Styled.Icon iconName={IconName.Cube} />,
label: stringGetter({ key: STRING_KEYS.POSITIONS }),
href: PortfolioRoute.Positions,
},
{
value: PortfolioRoute.Orders,
slotBefore: <Styled.Icon iconName={IconName.OrderPending} />,
label: stringGetter({ key: STRING_KEYS.ORDERS }),
href: PortfolioRoute.Orders,
},
{
value: PortfolioRoute.Fees,
slotBefore: <Styled.Icon iconName={IconName.Calculator} />,
label: stringGetter({ key: STRING_KEYS.FEES }),
href: PortfolioRoute.Fees,
},
{
value: PortfolioRoute.History,
slotBefore: <Styled.Icon iconName={IconName.History} />,
label: stringGetter({ key: STRING_KEYS.HISTORY }),
href: PortfolioRoute.History,
},
],
},
]}
/>
)
}
>
@@ -181,26 +139,6 @@ Styled.PortfolioMobile = styled.div`
${layoutMixins.expandingColumnWithHeader}
`;
Styled.SideBar = styled.div`
${layoutMixins.flexColumn}
justify-content: space-between;
height: 100%;
`;
Styled.Footer = styled.div`
${layoutMixins.row}
flex-wrap: wrap;
padding: 1rem;
gap: 0.5rem;
> button {
flex-grow: 1;
}
`;
Styled.NavigationMenu = styled(NavigationMenu)`
padding: 0.5rem;
padding-top: 0;
+17 -8
View File
@@ -43,14 +43,23 @@ export const DYDXBalancePanel = () => {
{!canAccountTrade ? (
<OnboardingTriggerButton size={ButtonSize.Small} />
) : (
<Button
slotLeft={<Icon iconName={IconName.Send} />}
size={ButtonSize.Small}
action={ButtonAction.Primary}
onClick={() => dispatch(openDialog({ type: DialogTypes.Transfer }))}
>
{stringGetter({ key: STRING_KEYS.TRANSFER })}
</Button>
<>
<Styled.ReceiveButton
slotLeft={<Icon iconName={IconName.Qr} />}
size={ButtonSize.Small}
onClick={() => dispatch(openDialog({ type: DialogTypes.Receive }))}
>
{stringGetter({ key: STRING_KEYS.RECEIVE })}
</Styled.ReceiveButton>
<Button
slotLeft={<Icon iconName={IconName.Send} />}
size={ButtonSize.Small}
action={ButtonAction.Primary}
onClick={() => dispatch(openDialog({ type: DialogTypes.Transfer }))}
>
{stringGetter({ key: STRING_KEYS.TRANSFER })}
</Button>
</>
)}
</Styled.ReceiveAndTransferButtons>
</Styled.Header>
+1 -2
View File
@@ -2,7 +2,6 @@ import type { PayloadAction } from '@reduxjs/toolkit';
import { AbacusApiStatus } from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs';
import { isDev } from '@/constants/networks';
import { setApiState, setSelectedNetwork } from '@/state/app';
import { resetPerpetualsState } from '@/state/perpetuals';
@@ -31,7 +30,7 @@ export default (store: any) => (next: any) => async (action: PayloadAction<any>)
store.dispatch(
openDialog({
type: DialogTypes.ExchangeOffline,
dialogProps: { preventClose: !isDev },
dialogProps: { preventClose: import.meta.env.MODE === 'production' },
})
);
}
-9
View File
@@ -48,15 +48,6 @@ export const getTradeInputErrors = (state: RootState) => {
return currentInput === 'trade' ? getInputErrors(state) : [];
};
/**
* @param state
* @returns input errors for Close position
*/
export const getClosePositionInputErrors = (state: RootState) => {
const currentInput = state.inputs.current;
return currentInput === 'closePosition' ? getInputErrors(state) : [];
};
/**
* @param state
* @returns input errors for Transfer
+4
View File
@@ -18,5 +18,9 @@ export const tradeViewMixins: Record<
tbody {
font: var(--font-small-book);
}
thead tr {
box-shadow: none;
}
`,
};
+5 -5
View File
@@ -82,18 +82,18 @@ const MarketsDropdownContent = ({ onRowAction }: { onRowAction?: (market: string
),
},
{
columnKey: 'priceChange24HPercent',
getCellValue: (row) => row.priceChange24HPercent,
columnKey: 'priceChange24H',
getCellValue: (row) => row.priceChange24H,
label: stringGetter({ key: STRING_KEYS._24H }),
renderCell: ({ priceChange24HPercent }) => (
renderCell: ({ priceChange24H, priceChange24HPercent }) => (
<Styled.InlineRow>
{!priceChange24HPercent ? (
{!priceChange24H ? (
<Styled.Output type={OutputType.Text} value={null} />
) : (
<Styled.PriceChangeOutput
type={OutputType.Percent}
value={priceChange24HPercent}
isNegative={MustBigNumber(priceChange24HPercent).isNegative()}
isNegative={MustBigNumber(priceChange24H).isNegative()}
/>
)}
</Styled.InlineRow>
+3 -12
View File
@@ -34,7 +34,7 @@ export const TransferStatusToast = ({
}: ElementProps) => {
const stringGetter = useStringGetter();
const [open, setOpen] = useState<boolean>(false);
const [secondsLeft, setSecondsLeft] = useState<number>(0);
const [secondsLeft, setSecondsLeft] = useState<number | undefined>();
// @ts-ignore status.errors is not in the type definition but can be returned
const error = status?.errors?.length ? status?.errors[0] : status?.error;
@@ -49,23 +49,14 @@ export const TransferStatusToast = ({
if (!status) return <LoadingDots size={3} />;
const inProgressStatusString =
type === 'deposit'
? secondsLeft > 0
? STRING_KEYS.DEPOSIT_STATUS
: STRING_KEYS.DEPOSIT_STATUS_SHORTLY
: secondsLeft > 0
? STRING_KEYS.WITHDRAW_STATUS
: STRING_KEYS.WITHDRAW_STATUS_SHORTLY;
const statusString =
type === 'deposit'
? status?.squidTransactionStatus === 'success'
? STRING_KEYS.DEPOSIT_COMPLETE
: inProgressStatusString
: STRING_KEYS.DEPOSIT_STATUS
: status?.squidTransactionStatus === 'success'
? STRING_KEYS.WITHDRAW_COMPLETE
: inProgressStatusString;
: STRING_KEYS.WITHDRAW_STATUS;
return (
<Styled.Root open={open} onOpenChange={setOpen}>
+5 -19
View File
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
import styled, { css, keyframes, type AnyStyledComponent } from 'styled-components';
import { StatusResponse } from '@0xsquid/sdk';
import { useStringGetter, useSelectedNetwork, useURLConfigs } from '@/hooks';
import { useStringGetter } from '@/hooks';
import { Link } from '@/components/Link';
import { Icon, IconName } from '@/components/Icon';
@@ -11,7 +11,6 @@ import { LoadingSpinner } from '@/components/Loading/LoadingSpinner';
import { layoutMixins } from '@/styles/layoutMixins';
import { STRING_KEYS } from '@/constants/localization';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
type ElementProps = {
status?: StatusResponse;
@@ -27,17 +26,12 @@ enum TransferStatusStep {
export const TransferStatusSteps = ({ status, type }: ElementProps) => {
const stringGetter = useStringGetter();
const { selectedNetwork } = useSelectedNetwork();
const { mintscan: mintscanTxUrl } = useURLConfigs();
const dydxChainId = ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId;
const { currentStep, steps } = useMemo(() => {
const routeStatus = status?.routeStatus;
const fromChain = status?.fromChain?.chainData?.chainId;
const toChain = status?.toChain?.chainData?.chainId;
const currentStatus = routeStatus?.[routeStatus?.length - 1];
const steps = [
{
label: stringGetter({
@@ -45,12 +39,7 @@ export const TransferStatusSteps = ({ status, type }: ElementProps) => {
type === 'deposit' ? STRING_KEYS.INITIATED_DEPOSIT : STRING_KEYS.INITIATED_WITHDRAWAL,
}),
step: TransferStatusStep.FromChain,
link:
type === 'deposit'
? status?.fromChain?.transactionUrl
: routeStatus?.[0]?.chainId === dydxChainId && routeStatus[0].txHash
? `${mintscanTxUrl?.replace('{tx_hash}', routeStatus[0].txHash.replace('0x', ''))}`
: undefined,
link: status?.fromChain?.transactionUrl,
},
{
label: stringGetter({ key: STRING_KEYS.BRIDGING_TOKENS }),
@@ -65,15 +54,12 @@ export const TransferStatusSteps = ({ status, type }: ElementProps) => {
},
}),
step: TransferStatusStep.ToChain,
link:
type === 'withdrawal'
? status?.toChain?.transactionUrl
: currentStatus?.chainId === dydxChainId && currentStatus?.txHash
? `${mintscanTxUrl?.replace('{tx_hash}', currentStatus.txHash.replace('0x', ''))}`
: undefined,
link: status?.toChain?.transactionUrl,
},
];
const currentStatus = routeStatus?.[routeStatus?.length - 1];
let currentStep = TransferStatusStep.Bridge;
if (!routeStatus?.length) {
+6 -13
View File
@@ -1,7 +1,6 @@
import { useState } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components';
import { isMainnet } from '@/constants/networks';
import { STRING_KEYS } from '@/constants/localization';
import { useBreakpoints, useStringGetter } from '@/hooks';
@@ -17,7 +16,7 @@ type ElementProps = {
};
export const DepositDialog = ({ setIsOpen }: ElementProps) => {
const [showFaucet, setShowFaucet] = useState(!isMainnet);
const [showTestDeposit, setShowTestDeposit] = useState(true);
const stringGetter = useStringGetter();
const { isMobile } = useBreakpoints();
@@ -28,20 +27,14 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
isOpen
setIsOpen={setIsOpen}
title={stringGetter({ key: STRING_KEYS.DEPOSIT })}
description={showFaucet && 'Test funds will be sent directly to your dYdX account.'}
description={showTestDeposit && 'Test funds will be sent directly to your dYdX account.'}
placement={isMobile ? DialogPlacement.FullScreen : DialogPlacement.Default}
>
<Styled.Content>
{isMainnet || !showFaucet ? (
<DepositForm />
) : (
<TestnetDepositForm onDeposit={closeDialog} />
)}
{!isMainnet && (
<Styled.TextToggle onClick={() => setShowFaucet(!showFaucet)}>
{showFaucet ? 'Show deposit form' : 'Show test faucet'}
</Styled.TextToggle>
)}
{showTestDeposit ? <TestnetDepositForm onDeposit={closeDialog} /> : <DepositForm />}
<Styled.TextToggle onClick={() => setShowTestDeposit(!showTestDeposit)}>
{showTestDeposit ? 'Show deposit form (Under Construction)' : 'Show test faucet'}
</Styled.TextToggle>
</Styled.Content>
</Dialog>
);
+1 -2
View File
@@ -5,7 +5,6 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { AbacusApiStatus } from '@/constants/abacus';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS } from '@/constants/localization';
import { isDev } from '@/constants/networks';
import { useApiState, useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -46,7 +45,7 @@ export const ExchangeOfflineDialog = ({ preventClose, setIsOpen }: ElementProps)
>
<Styled.Content>
<p>{statusErrorMessage}</p>
{isDev && <NetworkSelectMenu />}
{import.meta.env.MODE !== 'production' && <NetworkSelectMenu />}
</Styled.Content>
</Dialog>
);
+11 -18
View File
@@ -3,7 +3,6 @@ import styled, { AnyStyledComponent, css } from 'styled-components';
import { AnalyticsEvent } from '@/constants/analytics';
import { STRING_KEYS } from '@/constants/localization';
import { isMainnet } from '@/constants/networks';
import { EvmDerivedAccountStatus, OnboardingSteps } from '@/constants/account';
import { wallets } from '@/constants/wallets';
@@ -27,7 +26,6 @@ import { track } from '@/lib/analytics';
import { AcknowledgeTerms } from './OnboardingDialog/AcknowledgeTerms';
import { ChooseWallet } from './OnboardingDialog/ChooseWallet';
import { GenerateKeys } from './OnboardingDialog/GenerateKeys';
import { DepositForm } from '../forms/AccountManagementForms/DepositForm';
type ElementProps = {
setIsOpen?: (open: boolean) => void;
@@ -52,7 +50,7 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
disconnect();
}
setIsOpen?.(open);
};
}
return (
<Styled.Dialog
@@ -84,7 +82,10 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
description: stringGetter({ key: STRING_KEYS.SIGNATURE_CREATES_COSMOS_WALLET }),
children: (
<Styled.Content>
<GenerateKeys status={derivationStatus} setStatus={setDerivationStatus} />
<GenerateKeys
status={derivationStatus}
setStatus={setDerivationStatus}
/>
</Styled.Content>
),
width: '23rem',
@@ -100,22 +101,14 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
},
[OnboardingSteps.DepositFunds]: {
title: stringGetter({ key: STRING_KEYS.DEPOSIT }),
description: !isMainnet && 'Test funds will be sent directly to your dYdX account.',
description: 'Test funds will be sent directly to your dYdX account.',
children: (
<Styled.Content>
{isMainnet ? (
<DepositForm
onDeposit={() => {
track(AnalyticsEvent.TransferDeposit);
}}
/>
) : (
<TestnetDepositForm
onDeposit={() => {
track(AnalyticsEvent.TransferFaucet);
}}
/>
)}
<TestnetDepositForm
onDeposit={() => {
track(AnalyticsEvent.TransferFaucet);
}}
/>
</Styled.Content>
),
},
@@ -1,13 +1,11 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { isDev } from '@/constants/networks';
import { useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { Dialog } from '@/components/Dialog';
import { Icon, IconName } from '@/components/Icon';
import { NetworkSelectMenu } from '@/views/menus/NetworkSelectMenu';
type ElementProps = {
preventClose?: boolean;
@@ -27,7 +25,6 @@ export const RestrictedGeoDialog = ({ preventClose, setIsOpen }: ElementProps) =
>
<Styled.Content>
{stringGetter({ key: STRING_KEYS.REGION_NOT_PERMITTED_SUBTITLE })}
{isDev && <NetworkSelectMenu />}
</Styled.Content>
</Dialog>
);
@@ -1,13 +1,11 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { STRING_KEYS } from '@/constants/localization';
import { isDev } from '@/constants/networks';
import { useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { Dialog } from '@/components/Dialog';
import { Icon, IconName } from '@/components/Icon';
import { NetworkSelectMenu } from '@/views/menus/NetworkSelectMenu';
type ElementProps = {
preventClose?: boolean;
@@ -27,7 +25,6 @@ export const RestrictedWalletDialog = ({ preventClose, setIsOpen }: ElementProps
>
<Styled.Content>
{stringGetter({ key: STRING_KEYS.REGION_NOT_PERMITTED_SUBTITLE })}
{isDev && <NetworkSelectMenu />}
</Styled.Content>
</Dialog>
);
@@ -2,7 +2,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 { parseUnits } from 'viem';
import { parseUnits } from 'viem'
import erc20 from '@/abi/erc20.json';
import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
@@ -17,6 +17,7 @@ import { useAccounts, useDebounce, useStringGetter, useSelectedNetwork } from '@
import { useAccountBalance, CHAIN_DEFAULT_TOKEN_ADDRESS } 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';
@@ -55,7 +56,8 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
const [isLoading, setIsLoading] = useState(false);
const { selectedNetwork } = useSelectedNetwork();
const { evmAddress, signerWagmi, publicClientWagmi } = useAccounts();
const { evmAddress, signerWagmi } = useAccounts();
const { publicClientWagmi } = useWalletConnection();
const { addTransferNotification } = useLocalNotifications();
@@ -65,8 +67,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
chain: chainIdStr,
resources,
summary,
errors: routeErrors,
errorMessage: routeErrorMessage,
} = useSelector(getTransferInputs, shallowEqual) || {};
const chainId = chainIdStr ? parseInt(chainIdStr) : undefined;
@@ -99,7 +99,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
useEffect(() => {
const hasInvalidInput =
debouncedAmountBN.isNaN() || debouncedAmountBN.lte(0) || debouncedAmountBN.gt(balanceBN);
debouncedAmountBN.isNaN() || debouncedAmountBN.lte(0) || debouncedAmountBN.gte(balanceBN);
abacusStateManager.setTransferValue({
value: hasInvalidInput ? 0 : debouncedAmount,
@@ -173,8 +173,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
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 (!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');
@@ -184,11 +183,11 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
address: sourceToken.address as EvmAddress,
abi: erc20,
functionName: 'allowance',
args: [evmAddress as EvmAddress, requestPayload.targetAddress as EvmAddress],
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,
@@ -196,12 +195,12 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
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]);
@@ -231,7 +230,8 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
to: requestPayload.targetAddress as EvmAddress,
data: requestPayload.data as EvmAddress,
gasLimit: BigInt(requestPayload.gasLimit),
value: requestPayload.routeType !== 'SEND' ? BigInt(requestPayload.value) : undefined,
value:
requestPayload.routeType !== 'SEND' ? BigInt(requestPayload.value) : undefined,
};
const txHash = await signerWagmi.sendTransaction(tx);
@@ -289,15 +289,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
return parseWalletError({ error, stringGetter }).message;
}
if (routeErrors) {
return routeErrorMessage
? stringGetter({
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
params: { ERROR_MESSAGE: routeErrorMessage },
})
: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG });
}
if (fromAmount) {
if (!chainId) {
return stringGetter({ key: STRING_KEYS.MUST_SPECIFY_CHAIN });
@@ -311,16 +302,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
}
return undefined;
}, [
error,
routeErrors,
routeErrorMessage,
balance,
chainId,
fromAmount,
sourceToken,
stringGetter,
]);
}, [error, balance, chainId, fromAmount, sourceToken]);
const isDisabled =
Boolean(errorMessage) ||
@@ -164,7 +164,6 @@ export const DepositButtonAndReceipt = ({
label: <span>{stringGetter({ key: STRING_KEYS.SLIPPAGE })}</span>,
value: (
<SlippageEditor
disabled
slippage={slippage}
setIsEditing={setIsEditingSlipapge}
setSlippage={setSlippage}
@@ -24,17 +24,11 @@ type ElementProps = {
slippage: number;
setIsEditing?: Dispatch<SetStateAction<boolean>>;
setSlippage: (slippage: number) => void;
disabled?: boolean;
};
export type SlippageEditorProps = ElementProps;
export const SlippageEditor = ({
disabled,
slippage,
setIsEditing,
setSlippage,
}: SlippageEditorProps) => {
export const SlippageEditor = ({ slippage, setIsEditing, setSlippage }: SlippageEditorProps) => {
const percentSlippage = slippage * 100;
const [slippageInputValue, setSlippageInputValue] = useState(percentSlippage.toString());
const [editorState, setEditorState] = useState(EditorState.Viewing);
@@ -86,10 +80,6 @@ export const SlippageEditor = ({
}
};
if (disabled) {
return <Output type={OutputType.Percent} value={slippage} />;
}
return (
<Styled.WithConfirmationPopover
open={editorState !== EditorState.Viewing}
@@ -47,6 +47,7 @@ import { MustBigNumber } from '@/lib/numbers';
import { TokenSelectMenu } from './TokenSelectMenu';
import { WithdrawButtonAndReceipt } from './WithdrawForm/WithdrawButtonAndReceipt';
import { join } from 'path';
export const WithdrawForm = () => {
const stringGetter = useStringGetter();
@@ -68,8 +69,6 @@ export const WithdrawForm = () => {
chain: chainIdStr,
address: toAddress,
resources,
errors: routeErrors,
errorMessage: routeErrorMessage,
} = useSelector(getTransferInputs, shallowEqual) || {};
const isValidAddress = toAddress && isAddress(toAddress);
@@ -282,15 +281,6 @@ export const WithdrawForm = () => {
});
}
if (routeErrors) {
return routeErrorMessage
? stringGetter({
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
params: { ERROR_MESSAGE: routeErrorMessage },
})
: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG });
}
if (!toAddress) return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ADDRESS });
if (sanctionedAddresses.has(toAddress))
@@ -313,15 +303,12 @@ export const WithdrawForm = () => {
return undefined;
}, [
error,
routeErrors,
routeErrorMessage,
freeCollateralBN,
chainIdStr,
debouncedAmountBN,
toToken,
toAddress,
sanctionedAddresses,
stringGetter,
]);
const isDisabled =
@@ -147,7 +147,6 @@ export const WithdrawButtonAndReceipt = ({
label: <span>{stringGetter({ key: STRING_KEYS.SLIPPAGE })}</span>,
value: (
<SlippageEditor
disabled
slippage={slippage}
setIsEditing={setIsEditingSlipapge}
setSlippage={setSlippage}
+7 -31
View File
@@ -4,10 +4,8 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import {
ClosePositionInputField,
ValidationError,
type HumanReadablePlaceOrderPayload,
type Nullable,
ErrorType,
} from '@/constants/abacus';
import { AlertType } from '@/constants/alerts';
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
@@ -37,14 +35,13 @@ import { PositionPreview } from '@/views/forms/TradeForm/PositionPreview';
import { getCurrentMarketPositionData } from '@/state/accountSelectors';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { getClosePositionInputErrors, getInputClosePositionData } from '@/state/inputsSelectors';
import { getInputClosePositionData } from '@/state/inputsSelectors';
import { getCurrentMarketConfig, getCurrentMarketId } from '@/state/perpetualsSelectors';
import { closeDialog } from '@/state/dialogs';
import { getCurrentInput } from '@/state/inputsSelectors';
import abacusStateManager from '@/lib/abacus';
import { MustBigNumber } from '@/lib/numbers';
import { getTradeInputAlert } from '@/lib/tradeData';
const MAX_KEY = 'MAX';
@@ -84,38 +81,15 @@ export const ClosePositionForm = ({
const market = useSelector(getCurrentMarketId);
const { id } = useSelector(getCurrentMarketAssetData, shallowEqual) || {};
const { stepSizeDecimals, tickSizeDecimals } =
useSelector(getCurrentMarketConfig, shallowEqual) || {};
const { stepSizeDecimals } = useSelector(getCurrentMarketConfig, shallowEqual) || {};
const { size: sizeData, summary } = useSelector(getInputClosePositionData, shallowEqual) || {};
const { size, percent } = sizeData || {};
const currentInput = useSelector(getCurrentInput);
const closePositionInputErrors = useSelector(getClosePositionInputErrors, shallowEqual);
const currentPositionData = useSelector(getCurrentMarketPositionData, shallowEqual);
const { size: currentPositionSize } = currentPositionData || {};
const { current: currentSize } = currentPositionSize || {};
const currentSizeBN = MustBigNumber(currentSize).abs();
const hasInputErrors = closePositionInputErrors?.some(
(error: ValidationError) => error.type !== ErrorType.warning
);
const inputAlert = getTradeInputAlert({
abacusInputErrors: closePositionInputErrors ?? [],
stringGetter,
stepSizeDecimals,
tickSizeDecimals,
});
let alertContent;
let alertType = AlertType.Error;
if (closePositionError) {
alertContent = closePositionError;
} else if (inputAlert) {
alertContent = inputAlert?.alertString;
alertType = inputAlert?.type;
}
useEffect(() => {
if (currentStep && currentStep !== MobilePlaceOrderSteps.EditOrder) return;
@@ -208,7 +182,9 @@ export const ClosePositionForm = ({
});
};
const alertMessage = alertContent && <AlertMessage type={alertType}>{alertContent}</AlertMessage>;
const alertMessage = closePositionError && (
<AlertMessage type={AlertType.Error}>{closePositionError}</AlertMessage>
);
const inputs = (
<Styled.InputsColumn>
@@ -289,8 +265,8 @@ export const ClosePositionForm = ({
<PlaceOrderButtonAndReceipt
isLoading={isClosingPosition}
hasValidationErrors={hasInputErrors}
actionStringKey={inputAlert?.actionStringKey}
hasValidationErrors={!size}
actionStringKey={!size ? STRING_KEYS.ENTER_AMOUNT : undefined}
summary={summary ?? undefined}
currentStep={currentStep}
isClosePosition
+49 -57
View File
@@ -61,53 +61,38 @@ export const TransferForm = ({
const stringGetter = useStringGetter();
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
const { dydxAddress } = useAccounts();
const {
address: recipientAddress,
size,
fee,
} = useSelector(getTransferInputs, shallowEqual) || {};
const { transfer } = useSubaccount();
const { nativeTokenBalance, usdcBalance } = useAccountBalance();
const { selectedNetwork } = useSelectedNetwork();
const { tokensConfigs, usdcLabel, chainTokenLabel } = useTokenConfigs();
const {
address: recipientAddress,
size,
fee,
token,
} = useSelector(getTransferInputs, shallowEqual) || {};
// User Input
const [asset, setAsset] = useState<DydxChainAsset>(selectedAsset);
// Form states
const [error, setError] = useState<string>();
const [isLoading, setIsLoading] = useState(false);
// temp fix: TODO: reset fees when changing token in Abacus
const [currentFee, setCurrentFee] = useState(fee);
useEffect(() => {
setCurrentFee(fee);
}, [fee]);
const balance = asset === DydxChainAsset.USDC ? freeCollateral?.current : nativeTokenBalance;
const newBalance =
asset === DydxChainAsset.USDC
? freeCollateral?.postOrder
: MustBigNumber(nativeTokenBalance)
.minus(size?.size ?? 0)
.toNumber();
const amount = asset === DydxChainAsset.USDC ? size?.usdcSize : size?.size;
const asset = (token ?? selectedAsset) as DydxChainAsset;
const isUSDCSelected = asset === DydxChainAsset.USDC;
const amount = isUSDCSelected ? size?.usdcSize : size?.size;
const showNotEnoughGasWarning = fee && isUSDCSelected && usdcBalance < fee;
const balance = isUSDCSelected ? freeCollateral?.current : nativeTokenBalance;
const showNotEnoughGasWarning = fee && asset === DydxChainAsset.USDC && usdcBalance < fee;
// BN
const newBalanceBN = isUSDCSelected
? MustBigNumber(freeCollateral?.postOrder)
: nativeTokenBalance.minus(size?.size ?? 0);
const amountBN = MustBigNumber(amount);
const balanceBN = MustBigNumber(balance);
const onChangeAsset = (asset: DydxChainAsset) => {
setError(undefined);
setCurrentFee(undefined);
if (asset) {
abacusStateManager.setTransferValue({
value: asset,
field: TransferInputField.token,
});
}
};
const newBalanceBN = MustBigNumber(newBalance);
useEffect(() => {
abacusStateManager.setTransferValue({
@@ -115,13 +100,19 @@ export const TransferForm = ({
field: TransferInputField.type,
});
onChangeAsset(selectedAsset);
return () => {
abacusStateManager.clearTransferInputValues();
};
}, []);
useEffect(() => {
setError(undefined);
abacusStateManager.setTransferValue({
value: asset,
field: TransferInputField.token,
});
}, [asset]);
const { sanctionedAddresses } = useRestrictions();
const isAddressValid = useMemo(
@@ -143,6 +134,11 @@ export const TransferForm = ({
setError(undefined);
try {
// Subtract fees from amount if sending native tokens
const amountToTransfer = (
asset === DydxChainAsset.CHAINTOKEN ? amountBN.minus(fee) : amountBN
).toNumber();
const screenResults = await screenAddresses({
addresses: [recipientAddress!, dydxAddress!],
});
@@ -161,9 +157,9 @@ export const TransferForm = ({
);
} else {
const txResponse = await transfer(
amountBN.toNumber(),
amountToTransfer,
recipientAddress as string,
tokensConfigs[asset]?.denom
tokensConfigs[asset].denom,
);
if (txResponse?.code === 0) {
@@ -204,7 +200,7 @@ export const TransferForm = ({
const onChangeAmount = (value?: number) => {
abacusStateManager.setTransferValue({
value,
field: isUSDCSelected ? TransferInputField.usdcSize : TransferInputField.size,
field: asset === DydxChainAsset.USDC ? TransferInputField.usdcSize : TransferInputField.size,
});
};
@@ -230,7 +226,7 @@ export const TransferForm = ({
value: DydxChainAsset.CHAINTOKEN,
label: (
<Styled.InlineRow>
<AssetIcon symbol={chainTokenLabel} />
{/* <AssetIcon symbol="DYDX" /> */}
{chainTokenLabel}
</Styled.InlineRow>
),
@@ -253,15 +249,15 @@ export const TransferForm = ({
key: 'amount',
label: (
<span>
{stringGetter({ key: STRING_KEYS.AVAILABLE })} <Tag>{tokensConfigs[asset]?.name}</Tag>
{stringGetter({ key: STRING_KEYS.AVAILABLE })} <Tag>{tokensConfigs[asset].name}</Tag>
</span>
),
value: (
<DiffOutput
type={OutputType.Asset}
value={balanceBN}
value={balanceBN.toString()}
newValue={newBalanceBN.toString()}
sign={NumberSign.Negative}
newValue={newBalanceBN}
hasInvalidNewValue={newBalanceBN.isNegative()}
withDiff={Boolean(amount && balance) && !amountBN.isNaN()}
/>
@@ -344,8 +340,8 @@ export const TransferForm = ({
<Styled.SelectMenu
label={stringGetter({ key: STRING_KEYS.ASSET })}
value={token}
onValueChange={onChangeAsset}
value={asset}
onValueChange={setAsset}
disabled={isLoading}
>
{assetOptions.map(({ value, label }) => (
@@ -359,16 +355,12 @@ export const TransferForm = ({
type={InputType.Number}
onChange={({ floatValue }: NumberFormatValues) => onChangeAmount(floatValue)}
value={amount ?? undefined}
slotRight={
isUSDCSelected &&
balanceBN.gt(0) &&
renderFormInputButton({
label: stringGetter({ key: STRING_KEYS.MAX }),
isInputEmpty: size?.usdcSize == null,
onClear: () => onChangeAmount(undefined),
onClick: () => onChangeAmount(balanceBN.toNumber()),
})
}
slotRight={renderFormInputButton({
label: stringGetter({ key: STRING_KEYS.MAX }),
isInputEmpty: size?.usdcSize == null,
onClear: () => onChangeAmount(undefined),
onClick: () => onChangeAmount(balanceBN.toNumber()),
})}
disabled={isLoading}
/>
</WithDetailsReceipt>
@@ -377,7 +369,7 @@ export const TransferForm = ({
<AlertMessage type={AlertType.Warning}>
{stringGetter({
key: STRING_KEYS.TRANSFER_INSUFFICIENT_GAS,
params: { USDC_BALANCE: `(${usdcBalance} USDC)` },
params: { USDC_BALANCE: `(${usdcBalance})` },
})}
</AlertMessage>
)}
@@ -387,9 +379,9 @@ export const TransferForm = ({
<Styled.Footer>
<TransferButtonAndReceipt
selectedAsset={asset}
fee={currentFee || undefined}
isDisabled={!isAmountValid || !isAddressValid || !currentFee || isLoading}
isLoading={isLoading || Boolean(isAmountValid && isAddressValid && !currentFee)}
fees={fee || undefined}
isDisabled={!isAmountValid || !isAddressValid || !fee || isLoading}
isLoading={isLoading || Boolean(isAmountValid && isAddressValid && !fee)}
/>
</Styled.Footer>
</Styled.Form>
@@ -23,14 +23,14 @@ import { MustBigNumber } from '@/lib/numbers';
type ElementProps = {
selectedAsset: DydxChainAsset;
fee?: number;
fees?: number;
isDisabled?: boolean;
isLoading?: boolean;
};
export const TransferButtonAndReceipt = ({
selectedAsset,
fee,
fees,
isDisabled,
isLoading,
}: ElementProps) => {
@@ -47,46 +47,43 @@ export const TransferButtonAndReceipt = ({
const { current: equity, postOrder: newEquity } = equityInfo || {};
const { current: leverage, postOrder: newLeverage } = leverageInfo || {};
const isUSDCSelected = selectedAsset === DydxChainAsset.USDC;
const balance = isUSDCSelected ? equity : nativeTokenBalance;
const newNativeTokenBalance = nativeTokenBalance
.minus(size?.size ?? 0)
.minus(fee ?? 0) // show balance after fees for button receipt
.toNumber();
const newBalance = isUSDCSelected ? newEquity : newNativeTokenBalance;
// TODO(@aforaleka): add wallet balance for DYDX
const balance = selectedAsset === DydxChainAsset.USDC ? equity : nativeTokenBalance;
const newBalance =
selectedAsset === DydxChainAsset.USDC
? newEquity
: MustBigNumber(nativeTokenBalance)
.minus(size?.size ?? 0)
.toNumber();
const transferDetailItems = [
{
key: 'fees',
label: (
<span>
{stringGetter({ key: STRING_KEYS.FEES })} <Tag>{tokensConfigs[selectedAsset]?.name}</Tag>
{stringGetter({ key: STRING_KEYS.FEES })} <Tag>{tokensConfigs[selectedAsset].name}</Tag>
</span>
),
value: <Output type={OutputType.Asset} value={fee} />,
value: <Output type={OutputType.Asset} value={fees} />,
},
{
key: 'balance',
label: (
<span>
{stringGetter({ key: STRING_KEYS.BALANCE })}{' '}
<Tag>{tokensConfigs[selectedAsset]?.name}</Tag>
{stringGetter({ key: STRING_KEYS.BALANCE })} <Tag>{tokensConfigs[selectedAsset].name}</Tag>
</span>
),
value: (
<DiffOutput
type={OutputType.Asset}
value={balance}
sign={NumberSign.Negative}
newValue={newBalance}
hasInvalidNewValue={MustBigNumber(newBalance).isNegative()}
withDiff={newBalance !== null && !MustBigNumber(balance).eq(newBalance ?? 0)}
sign={NumberSign.Negative}
withDiff={Boolean(newBalance) && balance !== newBalance}
/>
),
},
isUSDCSelected && {
selectedAsset === DydxChainAsset.USDC && {
key: 'leverage',
label: <span>{stringGetter({ key: STRING_KEYS.LEVERAGE })}</span>,
value: (
-2
View File
@@ -33,8 +33,6 @@ const Styled: Record<string, AnyStyledComponent> = {};
Styled.DropdownSelectMenu = styled(DropdownSelectMenu)`
${headerMixins.dropdownTrigger}
width: max-content;
& > span:first-of-type {
${layoutMixins.textOverflow}
max-width: 5.625rem;
+3 -10
View File
@@ -7,7 +7,7 @@ import { ButtonAction } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { STRING_KEYS, StringGetterFunction } from '@/constants/localization';
import { useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { layoutMixins } from '@/styles/layoutMixins';
import { tradeViewMixins } from '@/styles/tradeViewMixins';
@@ -40,13 +40,11 @@ const getTransferHistoryTableColumnDef = ({
key,
stringGetter,
width,
mintscanTxUrl,
}: {
key: TransferHistoryTableColumnKey;
isTablet?: boolean;
stringGetter: StringGetterFunction;
width?: ColumnSize;
mintscanTxUrl?: string;
}): ColumnDef<SubaccountTransfer> => ({
width,
...(
@@ -100,12 +98,9 @@ const getTransferHistoryTableColumnDef = ({
columnKey: TransferHistoryTableColumnKey.TxHash,
getCellValue: (row) => row.transactionHash,
label: stringGetter({ key: STRING_KEYS.TRANSACTION }),
renderCell: ({ transactionHash }) =>
renderCell: ({ transactionHash, resources }) =>
transactionHash ? (
<Styled.TxHash
withIcon
href={`${mintscanTxUrl?.replace('{tx_hash}', transactionHash)}`}
>
<Styled.TxHash withIcon href={resources.blockExplorerUrl}>
{truncateAddress(transactionHash, '')}
</Styled.TxHash>
) : (
@@ -135,7 +130,6 @@ export const TransferHistoryTable = ({
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { isMobile, isTablet } = useBreakpoints();
const { mintscan: mintscanTxUrl } = useURLConfigs();
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
@@ -152,7 +146,6 @@ export const TransferHistoryTable = ({
isTablet,
stringGetter,
width: columnWidths?.[key],
mintscanTxUrl,
})
)}
slotEmpty={