Compare commits
41
Commits
equity-tiers
...
env-list
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
606c82d940 | ||
|
|
76100c9857 | ||
|
|
9bbd8d30ed | ||
|
|
7a24e2acfd | ||
|
|
90d2ede5c6 | ||
|
|
e635778438 | ||
|
|
4ca6df3d31 | ||
|
|
7a456b33d4 | ||
|
|
271c98c9bb | ||
|
|
c397c0dec9 | ||
|
|
cc53ac59d6 | ||
|
|
80be9b357f | ||
|
|
cda3e2bb6e | ||
|
|
cc6d61344b | ||
|
|
7df6e39e87 | ||
|
|
6d25a072bd | ||
|
|
9d3413b04d | ||
|
|
d6baa6ef3f | ||
|
|
195ddd5af7 | ||
|
|
628041e537 | ||
|
|
2a5373c972 | ||
|
|
64f663ed5b | ||
|
|
983128fd74 | ||
|
|
e15f4b919c | ||
|
|
71c29524b1 | ||
|
|
0e26433e29 | ||
|
|
3afd5ef783 | ||
|
|
b2cac81235 | ||
|
|
e48ec4ca73 | ||
|
|
78f9ddff7a | ||
|
|
c1dcca43c8 | ||
|
|
029476fa19 | ||
|
|
6ecdcd981e | ||
|
|
591166e93d | ||
|
|
e30940e7df | ||
|
|
2d0a944097 | ||
|
|
0dd2ecb6d2 | ||
|
|
76819e0839 | ||
|
|
32b375cfa4 | ||
|
|
3dc87e873d | ||
|
|
ed61b3112e |
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
- Node.js version 18 and `pnpm` installed on your system
|
- Node.js version 18 and `pnpm` installed on your system
|
||||||
|
|
||||||
|
For deploying with Vercel, create an account with [Vercel](https://vercel.com/signup) if you don't have one already.
|
||||||
|
|
||||||
For deploying to IPFS, choose one of the following:
|
For deploying to IPFS, choose one of the following:
|
||||||
|
|
||||||
- **Option 1:** A free [web3.storage](https://web3.storage/) account
|
- **Option 1:** A free [web3.storage](https://web3.storage/) account
|
||||||
@@ -51,7 +53,24 @@ pnpm ladle
|
|||||||
|
|
||||||
This will automatically open your default browser at `http://localhost:61000`.
|
This will automatically open your default browser at `http://localhost:61000`.
|
||||||
|
|
||||||
## Part 3: Deploying to IPFS
|
## Part 3: Deploying with Vercel
|
||||||
|
|
||||||
|
### Step 1: Connect your repository to Vercel
|
||||||
|
|
||||||
|
Select "Import Git Repository" from your dashboard, and provide the URL of this repository or your forked repository.
|
||||||
|
|
||||||
|
### Step 2: Configure your project
|
||||||
|
|
||||||
|
For the "Build & Development Settings", we recommend the following:
|
||||||
|
- Framework Preset: `Vite`
|
||||||
|
- Build Command (override): `pnpm run build`
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
For more details, check out Vercel's [official documentation](https://vercel.com/docs).
|
||||||
|
|
||||||
|
## Part 4: Deploying to IPFS
|
||||||
|
|
||||||
### web3.storage: deploy to IPFS via web3.storage using the provided script
|
### web3.storage: deploy to IPFS via web3.storage using the provided script
|
||||||
|
|
||||||
@@ -74,7 +93,7 @@ ipfs add -r dist
|
|||||||
|
|
||||||
Save the CID provided in the output.
|
Save the CID provided in the output.
|
||||||
|
|
||||||
## Part 4: Accessing your content on IPFS
|
### Accessing your content on IPFS
|
||||||
|
|
||||||
To access your content on IPFS:
|
To access your content on IPFS:
|
||||||
|
|
||||||
@@ -82,4 +101,4 @@ To access your content on IPFS:
|
|||||||
|
|
||||||
2. **Public IPFS gateway:** Access your content via a public IPFS gateway, such as [https://dweb.link](https://dweb.link/) or [https://w3s.link/](https://w3s.link/). Use the gateway URL with your CID appended, like `https://dweb.link/ipfs/your_cid`.
|
2. **Public IPFS gateway:** Access your content via a public IPFS gateway, such as [https://dweb.link](https://dweb.link/) or [https://w3s.link/](https://w3s.link/). Use the gateway URL with your CID appended, like `https://dweb.link/ipfs/your_cid`.
|
||||||
|
|
||||||
Replace `your_cid` with the actual CID.
|
Replace `your_cid` with the actual CID.
|
||||||
|
|||||||
+3
-2
@@ -35,9 +35,10 @@
|
|||||||
"@cosmjs/encoding": "^0.31.0",
|
"@cosmjs/encoding": "^0.31.0",
|
||||||
"@cosmjs/proto-signing": "^0.31.0",
|
"@cosmjs/proto-signing": "^0.31.0",
|
||||||
"@cosmjs/stargate": "^0.31.0",
|
"@cosmjs/stargate": "^0.31.0",
|
||||||
"@dydxprotocol/abacus": "^0.2.28",
|
|
||||||
"@cosmjs/tendermint-rpc": "^0.31.0",
|
"@cosmjs/tendermint-rpc": "^0.31.0",
|
||||||
"@dydxprotocol/v4-client": "^0.29.0",
|
"@dydxprotocol/abacus": "^0.3.10",
|
||||||
|
"@dydxprotocol/v4-client-js": "^0.30.3",
|
||||||
|
"@dydxprotocol/v4-localization": "^0.0.16",
|
||||||
"@ethersproject/providers": "^5.7.2",
|
"@ethersproject/providers": "^5.7.2",
|
||||||
"@js-joda/core": "^5.5.3",
|
"@js-joda/core": "^5.5.3",
|
||||||
"@radix-ui/react-collapsible": "^1.0.3",
|
"@radix-ui/react-collapsible": "^1.0.3",
|
||||||
|
|||||||
Generated
+19
-12
@@ -27,11 +27,14 @@ dependencies:
|
|||||||
specifier: ^0.31.0
|
specifier: ^0.31.0
|
||||||
version: 0.31.0
|
version: 0.31.0
|
||||||
'@dydxprotocol/abacus':
|
'@dydxprotocol/abacus':
|
||||||
specifier: ^0.2.28
|
specifier: ^0.3.10
|
||||||
version: 0.2.28
|
version: 0.3.10
|
||||||
'@dydxprotocol/v4-client':
|
'@dydxprotocol/v4-client-js':
|
||||||
specifier: ^0.29.0
|
specifier: ^0.30.3
|
||||||
version: 0.29.0
|
version: 0.30.3
|
||||||
|
'@dydxprotocol/v4-localization':
|
||||||
|
specifier: ^0.0.16
|
||||||
|
version: 0.0.16
|
||||||
'@ethersproject/providers':
|
'@ethersproject/providers':
|
||||||
specifier: ^5.7.2
|
specifier: ^5.7.2
|
||||||
version: 5.7.2
|
version: 5.7.2
|
||||||
@@ -1143,8 +1146,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
|
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@dydxprotocol/abacus@0.2.28:
|
/@dydxprotocol/abacus@0.3.10:
|
||||||
resolution: {integrity: sha512-mGmhwQM8aiV/cD+d3lvW76ysoPN7uVsDyAiVcRdzGqgj+gbM0IIJDYXG14xHruOTi+gK1Aga5MUz2pdvmPDhYA==}
|
resolution: {integrity: sha512-CJV6zyoQLAoDWJdgLbP1Y2QYvSPybCGpykQY+WEWk4kQKnZkjxz25LPL0b8I9LrNDt2JknOxPJfAoUDXJPfNkg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@dydxprotocol/dydxjs@0.3.0:
|
/@dydxprotocol/dydxjs@0.3.0:
|
||||||
@@ -1164,8 +1167,8 @@ packages:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@dydxprotocol/v4-client@0.29.0:
|
/@dydxprotocol/v4-client-js@0.30.3:
|
||||||
resolution: {integrity: sha512-BMgm6e91LFGKB0l3lC2/0aIgayejgBa7qCwCUxr05265tggZsI3l3Cui8LNDHeb8IAQ4KPcfAJ29c96lx+rLlw==}
|
resolution: {integrity: sha512-h9nB2cU+6sWwfOTQz6sMKZZxVa/Iwp1JciUqKi5HDUyWl/4wfbczDlUqbMUUeXgBgfbb9CImxn+PjF0v7lcD5A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@cosmjs/amino': 0.30.1
|
'@cosmjs/amino': 0.30.1
|
||||||
'@cosmjs/proto-signing': 0.30.1
|
'@cosmjs/proto-signing': 0.30.1
|
||||||
@@ -1187,6 +1190,10 @@ packages:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@dydxprotocol/v4-localization@0.0.16:
|
||||||
|
resolution: {integrity: sha512-IuTjuVdGwV6nulBLCxbNydsEkwvx7mBn8SQtPP2pQ16yfUNWMyBG3GDEXlfAk41NcPSCGXlK5Wbl0R1xaOKCRA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@emotion/is-prop-valid@1.2.1:
|
/@emotion/is-prop-valid@1.2.1:
|
||||||
resolution: {integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==}
|
resolution: {integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8277,7 +8284,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-WIdaQ8uW1vIbYvNnAVunkC6yxTrneJC7VQ5UUQ0kuw8b0C0A39KTIpoQHCfc8tV7o9vF4niwRhdXEdfAgQEsQQ==}
|
resolution: {integrity: sha512-WIdaQ8uW1vIbYvNnAVunkC6yxTrneJC7VQ5UUQ0kuw8b0C0A39KTIpoQHCfc8tV7o9vF4niwRhdXEdfAgQEsQQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
cosmos-directory-types: 0.0.6
|
cosmos-directory-types: 0.0.6
|
||||||
node-fetch-native: 1.2.0
|
node-fetch-native: 1.4.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/cosmos-directory-types@0.0.6:
|
/cosmos-directory-types@0.0.6:
|
||||||
@@ -12462,8 +12469,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
|
||||||
engines: {node: '>=10.5.0'}
|
engines: {node: '>=10.5.0'}
|
||||||
|
|
||||||
/node-fetch-native@1.2.0:
|
/node-fetch-native@1.4.0:
|
||||||
resolution: {integrity: sha512-5IAMBTl9p6PaAjYCnMv5FmqIF6GcZnawAVnzaCG0rX2aYZJ4CxEkZNtVPuTRug7fL7wyM5BQYTlAzcyMPi6oTQ==}
|
resolution: {integrity: sha512-F5kfEj95kX8tkDhUCYdV8dg3/8Olx/94zB8+ZNthFs6Bz31UpUi8Xh40TN3thLwXgrwXry1pEg9lJ++tLWTcqA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/node-fetch@2.6.12:
|
/node-fetch@2.6.12:
|
||||||
|
|||||||
+19
-16
@@ -10,9 +10,11 @@ import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes';
|
|||||||
import { useBreakpoints, useInitializePage, useShouldShowFooter, useAnalytics } from '@/hooks';
|
import { useBreakpoints, useInitializePage, useShouldShowFooter, useAnalytics } from '@/hooks';
|
||||||
import { DydxProvider } from '@/hooks/useDydxClient';
|
import { DydxProvider } from '@/hooks/useDydxClient';
|
||||||
import { AccountsProvider } from '@/hooks/useAccounts';
|
import { AccountsProvider } from '@/hooks/useAccounts';
|
||||||
import { SubaccountProvider } from './hooks/useSubaccount';
|
|
||||||
import { DialogAreaProvider, useDialogArea } from './hooks/useDialogArea';
|
import { DialogAreaProvider, useDialogArea } from './hooks/useDialogArea';
|
||||||
|
import { LocaleProvider } from './hooks/useLocaleSeparators';
|
||||||
import { NotificationsProvider } from './hooks/useNotifications';
|
import { NotificationsProvider } from './hooks/useNotifications';
|
||||||
|
import { SubaccountProvider } from './hooks/useSubaccount';
|
||||||
|
import { SquidProvider } from '@/hooks/useSquidRouter';
|
||||||
|
|
||||||
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
||||||
|
|
||||||
@@ -61,10 +63,7 @@ const Content = () => {
|
|||||||
<Routes>
|
<Routes>
|
||||||
<Route path={AppRoute.Trade}>
|
<Route path={AppRoute.Trade}>
|
||||||
<Route path=":market" element={<TradePage />} />
|
<Route path=":market" element={<TradePage />} />
|
||||||
<Route
|
<Route path={AppRoute.Trade} element={<TradePage />} />
|
||||||
path={AppRoute.Trade}
|
|
||||||
element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
<Route path={AppRoute.Markets} element={<MarketsPage />} />
|
<Route path={AppRoute.Markets} element={<MarketsPage />} />
|
||||||
{import.meta.env.MODE !== 'production' && (
|
{import.meta.env.MODE !== 'production' && (
|
||||||
@@ -103,17 +102,21 @@ const App = () => (
|
|||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<GrazProvider>
|
<GrazProvider>
|
||||||
<WagmiConfig config={config}>
|
<WagmiConfig config={config}>
|
||||||
<DydxProvider>
|
<LocaleProvider>
|
||||||
<AccountsProvider>
|
<DydxProvider>
|
||||||
<SubaccountProvider>
|
<AccountsProvider>
|
||||||
<NotificationsProvider>
|
<SubaccountProvider>
|
||||||
<DialogAreaProvider>
|
<NotificationsProvider>
|
||||||
<Content />
|
<DialogAreaProvider>
|
||||||
</DialogAreaProvider>
|
<SquidProvider>
|
||||||
</NotificationsProvider>
|
<Content />
|
||||||
</SubaccountProvider>
|
</SquidProvider>
|
||||||
</AccountsProvider>
|
</DialogAreaProvider>
|
||||||
</DydxProvider>
|
</NotificationsProvider>
|
||||||
|
</SubaccountProvider>
|
||||||
|
</AccountsProvider>
|
||||||
|
</DydxProvider>
|
||||||
|
</LocaleProvider>
|
||||||
</WagmiConfig>
|
</WagmiConfig>
|
||||||
</GrazProvider>
|
</GrazProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
|||||||
@@ -1,68 +1,63 @@
|
|||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
|
import { Nullable } from '@/constants/abacus';
|
||||||
|
|
||||||
export type AssetSymbol = keyof typeof assetIcons;
|
export type AssetSymbol = keyof typeof assetIcons;
|
||||||
|
|
||||||
const assetIcons = {
|
const assetIcons = {
|
||||||
'1INCH': '/currencies/1inch.svg',
|
'1INCH': '/currencies/1inch.svg',
|
||||||
'AAVE': '/currencies/aave.svg',
|
AAVE: '/currencies/aave.svg',
|
||||||
'ADA': '/currencies/ada.svg',
|
ADA: '/currencies/ada.svg',
|
||||||
'ALGO': '/currencies/algo.svg',
|
ALGO: '/currencies/algo.svg',
|
||||||
'ATOM': '/currencies/atom.svg',
|
ATOM: '/currencies/atom.svg',
|
||||||
'AVAX': '/currencies/avax.svg',
|
AVAX: '/currencies/avax.svg',
|
||||||
'BCH': '/currencies/bch.svg',
|
BCH: '/currencies/bch.svg',
|
||||||
'BTC': '/currencies/btc.svg',
|
BTC: '/currencies/btc.svg',
|
||||||
'CELO': '/currencies/celo.svg',
|
CELO: '/currencies/celo.svg',
|
||||||
'COMP': '/currencies/comp.svg',
|
COMP: '/currencies/comp.svg',
|
||||||
'CRV': '/currencies/crv.svg',
|
CRV: '/currencies/crv.svg',
|
||||||
'DAI': '/currencies/dai.svg',
|
DAI: '/currencies/dai.svg',
|
||||||
'DOGE': '/currencies/doge.svg',
|
DOGE: '/currencies/doge.svg',
|
||||||
'DOT': '/currencies/dot.svg',
|
DOT: '/currencies/dot.svg',
|
||||||
'DYDX': '/currencies/dydx.svg',
|
DYDX: '/currencies/dydx.svg',
|
||||||
'ENJ': '/currencies/enj.svg',
|
ENJ: '/currencies/enj.svg',
|
||||||
'EOS': '/currencies/eos.svg',
|
EOS: '/currencies/eos.svg',
|
||||||
'ETC': '/currencies/etc.svg',
|
ETC: '/currencies/etc.svg',
|
||||||
'ETH': '/currencies/eth.svg',
|
ETH: '/currencies/eth.svg',
|
||||||
'FIL': '/currencies/fil.svg',
|
FIL: '/currencies/fil.svg',
|
||||||
'ICP': '/currencies/icp.svg',
|
ICP: '/currencies/icp.svg',
|
||||||
'LINK': '/currencies/link.svg',
|
LINK: '/currencies/link.svg',
|
||||||
'LTC': '/currencies/ltc.svg',
|
LTC: '/currencies/ltc.svg',
|
||||||
'LUNA': '/currencies/luna.svg',
|
LUNA: '/currencies/luna.svg',
|
||||||
'MATIC': '/currencies/matic.svg',
|
MATIC: '/currencies/matic.svg',
|
||||||
'MKR': '/currencies/mkr.svg',
|
MKR: '/currencies/mkr.svg',
|
||||||
'NEAR': '/currencies/near.svg',
|
NEAR: '/currencies/near.svg',
|
||||||
'RUNE': '/currencies/rune.svg',
|
RUNE: '/currencies/rune.svg',
|
||||||
'SNX': '/currencies/snx.svg',
|
SNX: '/currencies/snx.svg',
|
||||||
'SOL': '/currencies/sol.svg',
|
SOL: '/currencies/sol.svg',
|
||||||
'SUSHI': '/currencies/sushi.svg',
|
SUSHI: '/currencies/sushi.svg',
|
||||||
'TRX': '/currencies/trx.svg',
|
TRX: '/currencies/trx.svg',
|
||||||
'UMA': '/currencies/uma.svg',
|
UMA: '/currencies/uma.svg',
|
||||||
'UNI': '/currencies/uni.svg',
|
UNI: '/currencies/uni.svg',
|
||||||
'USDC': '/currencies/usdc.svg',
|
USDC: '/currencies/usdc.svg',
|
||||||
'USDT': '/currencies/usdt.svg',
|
USDT: '/currencies/usdt.svg',
|
||||||
'WBTC': '/currencies/wbtc.svg',
|
WBTC: '/currencies/wbtc.svg',
|
||||||
'WETH': '/currencies/weth.svg',
|
WETH: '/currencies/weth.svg',
|
||||||
'XLM': '/currencies/xlm.svg',
|
XLM: '/currencies/xlm.svg',
|
||||||
'XMR': '/currencies/xmr.svg',
|
XMR: '/currencies/xmr.svg',
|
||||||
'XTZ': '/currencies/xtz.svg',
|
XTZ: '/currencies/xtz.svg',
|
||||||
'YFI': '/currencies/yfi.svg',
|
YFI: '/currencies/yfi.svg',
|
||||||
'ZEC': '/currencies/zec.svg',
|
ZEC: '/currencies/zec.svg',
|
||||||
'ZRX': '/currencies/zrx.svg',
|
ZRX: '/currencies/zrx.svg',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export const AssetIcon = ({
|
export const AssetIcon = ({
|
||||||
symbol,
|
symbol,
|
||||||
className
|
className,
|
||||||
}: {
|
}: {
|
||||||
symbol?: AssetSymbol,
|
symbol?: Nullable<AssetSymbol>;
|
||||||
className?: string
|
className?: string;
|
||||||
}) => (
|
}) => (symbol ? <Styled.Img src={assetIcons[symbol]} className={className} /> : null);
|
||||||
symbol ? (
|
|
||||||
<Styled.Img
|
|
||||||
src={assetIcons[symbol]}
|
|
||||||
className={className}
|
|
||||||
/>
|
|
||||||
) : null
|
|
||||||
);
|
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import type { Story } from '@ladle/react';
|
||||||
|
|
||||||
|
import { CopyButton, type CopyButtonProps } from '@/components/CopyButton';
|
||||||
|
|
||||||
|
import { StoryWrapper } from '.ladle/components';
|
||||||
|
|
||||||
|
export const CopyButtonStory: Story<CopyButtonProps> = (args) => (
|
||||||
|
<StoryWrapper>
|
||||||
|
<CopyButton {...args} />
|
||||||
|
</StoryWrapper>
|
||||||
|
);
|
||||||
|
|
||||||
|
CopyButtonStory.args = {
|
||||||
|
value: 'some text to copy',
|
||||||
|
};
|
||||||
|
|
||||||
|
CopyButtonStory.argTypes = {
|
||||||
|
shownAsText: {
|
||||||
|
options: [true, false],
|
||||||
|
control: { type: 'select' },
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
children: {
|
||||||
|
options: ['some text to copy'],
|
||||||
|
control: { type: 'select' },
|
||||||
|
defaultValue: undefined,
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import styled, { css, type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
|
import { ButtonAction } from '@/constants/buttons';
|
||||||
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
|
||||||
|
import { useStringGetter } from '@/hooks';
|
||||||
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
|
import { Button, ButtonProps } from './Button';
|
||||||
|
import { Icon, IconName } from './Icon';
|
||||||
|
|
||||||
|
export type CopyButtonProps = {
|
||||||
|
value?: string;
|
||||||
|
shownAsText?: boolean;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
} & ButtonProps;
|
||||||
|
|
||||||
|
export const CopyButton = ({ value, shownAsText, children, ...buttonProps }: CopyButtonProps) => {
|
||||||
|
const stringGetter = useStringGetter();
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const onCopy = () => {
|
||||||
|
if (!value) return;
|
||||||
|
|
||||||
|
setCopied(true);
|
||||||
|
navigator.clipboard.writeText(value);
|
||||||
|
setTimeout(() => setCopied(false), 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
return shownAsText ? (
|
||||||
|
<Styled.InlineRow onClick={onCopy} copied={copied}>
|
||||||
|
{children}
|
||||||
|
<Icon iconName={IconName.Copy} />
|
||||||
|
</Styled.InlineRow>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
{...buttonProps}
|
||||||
|
action={copied ? ButtonAction.Create : ButtonAction.Primary}
|
||||||
|
onClick={onCopy}
|
||||||
|
>
|
||||||
|
<Icon iconName={IconName.Copy} />
|
||||||
|
{children ?? stringGetter({ key: copied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
Styled.InlineRow = styled.div<{ copied: boolean }>`
|
||||||
|
${layoutMixins.inlineRow}
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
${({ copied }) =>
|
||||||
|
copied
|
||||||
|
? css`
|
||||||
|
filter: brightness(0.8);
|
||||||
|
`
|
||||||
|
: css`
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(1.1);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
`;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Story } from '@ladle/react';
|
import type { Story } from '@ladle/react';
|
||||||
import { OrderSide } from '@dydxprotocol/v4-client';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import { OrderSideTag } from '@/components/OrderSideTag';
|
import { OrderSideTag } from '@/components/OrderSideTag';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { OrderSide } from '@dydxprotocol/v4-client';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { useStringGetter } from '@/hooks';
|
import { useStringGetter } from '@/hooks';
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type ElementProps = {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
label?: string;
|
label?: string;
|
||||||
items: MenuConfig<string, string>;
|
items: MenuConfig<string, string>;
|
||||||
|
slotEmpty?: ReactNode;
|
||||||
withSearch?: boolean;
|
withSearch?: boolean;
|
||||||
withReceiptItems?: DetailsItem[];
|
withReceiptItems?: DetailsItem[];
|
||||||
};
|
};
|
||||||
@@ -52,12 +53,10 @@ export const SearchSelectMenu = ({
|
|||||||
const Trigger = asChild ? (
|
const Trigger = asChild ? (
|
||||||
children
|
children
|
||||||
) : (
|
) : (
|
||||||
<>
|
<Styled.MenuTrigger>
|
||||||
<Styled.MenuTrigger>
|
{label ? <Styled.WithLabel label={label}>{children}</Styled.WithLabel> : children}
|
||||||
<Styled.WithLabel label={label}>{children}</Styled.WithLabel>
|
|
||||||
</Styled.MenuTrigger>
|
|
||||||
<Styled.TriggerIcon iconName={IconName.Triangle} open={open} />
|
<Styled.TriggerIcon iconName={IconName.Triangle} open={open} />
|
||||||
</>
|
</Styled.MenuTrigger>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -86,7 +85,7 @@ export const SearchSelectMenu = ({
|
|||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
Styled.MenuTrigger = styled.div`
|
Styled.MenuTrigger = styled.div`
|
||||||
height: 3.5rem;
|
height: var(--form-input-height);
|
||||||
|
|
||||||
${layoutMixins.spacedRow}
|
${layoutMixins.spacedRow}
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -117,7 +116,7 @@ Styled.WithDetailsReceipt = styled(WithDetailsReceipt)`
|
|||||||
Styled.Popover = styled(Popover)`
|
Styled.Popover = styled(Popover)`
|
||||||
max-height: 30vh;
|
max-height: 30vh;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
border: 1px solid var(--color-layer-6);
|
border: var(--border-width) solid var(--color-layer-6);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
@@ -136,6 +135,8 @@ Styled.ComboboxMenu = styled(ComboboxMenu)`
|
|||||||
--comboboxMenu-item-checked-textColor: var(--color-text-2);
|
--comboboxMenu-item-checked-textColor: var(--color-text-2);
|
||||||
--comboboxMenu-item-highlighted-textColor: var(--color-text-2);
|
--comboboxMenu-item-highlighted-textColor: var(--color-text-2);
|
||||||
|
|
||||||
|
--stickyArea1-topHeight: var(--form-input-height);
|
||||||
|
|
||||||
input:focus {
|
input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ Styled.Trigger = styled(Trigger)<{ $withBlur?: boolean }>`
|
|||||||
Styled.DropdownIcon = styled(SelectIcon)`
|
Styled.DropdownIcon = styled(SelectIcon)`
|
||||||
font-size: 0.675em;
|
font-size: 0.675em;
|
||||||
color: var(--color-text-1);
|
color: var(--color-text-1);
|
||||||
|
margin-left: auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Content = styled(Content)`
|
Styled.Content = styled(Content)`
|
||||||
|
|||||||
+33
-2
@@ -1,6 +1,6 @@
|
|||||||
import Abacus, { kollections } from '@dydxprotocol/abacus';
|
import Abacus, { kollections } from '@dydxprotocol/abacus';
|
||||||
import { OrderSide } from '@dydxprotocol/v4-client';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
import { PositionSide } from './trade';
|
import { PositionSide, TradeTypes } from './trade';
|
||||||
import { STRING_KEYS } from './localization';
|
import { STRING_KEYS } from './localization';
|
||||||
|
|
||||||
export type Nullable<T> = T | null | undefined;
|
export type Nullable<T> = T | null | undefined;
|
||||||
@@ -36,6 +36,12 @@ export type AbacusThreadingProtocol = Omit<
|
|||||||
Abacus.exchange.dydx.abacus.protocols.ThreadingProtocol,
|
Abacus.exchange.dydx.abacus.protocols.ThreadingProtocol,
|
||||||
'__doNotUseOrImplementIt'
|
'__doNotUseOrImplementIt'
|
||||||
>;
|
>;
|
||||||
|
export type AbacusFileSystemProtocol = Omit<
|
||||||
|
Abacus.exchange.dydx.abacus.protocols.FileSystemProtocol,
|
||||||
|
'__doNotUseOrImplementIt'
|
||||||
|
>;
|
||||||
|
|
||||||
|
export type FileLocation = Abacus.exchange.dydx.abacus.protocols.FileLocation;
|
||||||
export type ThreadingType = Abacus.exchange.dydx.abacus.protocols.ThreadingType;
|
export type ThreadingType = Abacus.exchange.dydx.abacus.protocols.ThreadingType;
|
||||||
export const CoroutineTimer = Abacus.exchange.dydx.abacus.utils.CoroutineTimer;
|
export const CoroutineTimer = Abacus.exchange.dydx.abacus.utils.CoroutineTimer;
|
||||||
|
|
||||||
@@ -98,7 +104,11 @@ export type ClosePositionInputs = Abacus.exchange.dydx.abacus.output.input.Close
|
|||||||
export type TradeInputSummary = Abacus.exchange.dydx.abacus.output.input.TradeInputSummary;
|
export type TradeInputSummary = Abacus.exchange.dydx.abacus.output.input.TradeInputSummary;
|
||||||
export type TransferInputs = Abacus.exchange.dydx.abacus.output.input.TransferInput;
|
export type TransferInputs = Abacus.exchange.dydx.abacus.output.input.TransferInput;
|
||||||
export type InputError = Abacus.exchange.dydx.abacus.output.input.ValidationError;
|
export type InputError = Abacus.exchange.dydx.abacus.output.input.ValidationError;
|
||||||
|
export type TransferInputTokenResource = Abacus.exchange.dydx.abacus.output.input.TransferInputTokenResource;
|
||||||
|
export type TransferInputChainResource = Abacus.exchange.dydx.abacus.output.input.TransferInputChainResource;
|
||||||
|
export type SelectionOption = Abacus.exchange.dydx.abacus.output.input.SelectionOption;
|
||||||
export const ErrorType = Abacus.exchange.dydx.abacus.output.input.ErrorType;
|
export const ErrorType = Abacus.exchange.dydx.abacus.output.input.ErrorType;
|
||||||
|
export const InputSelectionOption = Abacus.exchange.dydx.abacus.output.input.SelectionOption;
|
||||||
|
|
||||||
// ------ Wallet ------ //
|
// ------ Wallet ------ //
|
||||||
export type Wallet = Abacus.exchange.dydx.abacus.output.Wallet;
|
export type Wallet = Abacus.exchange.dydx.abacus.output.Wallet;
|
||||||
@@ -112,6 +122,8 @@ export type SubaccountFill = Abacus.exchange.dydx.abacus.output.SubaccountFill;
|
|||||||
export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment;
|
export type SubaccountFundingPayment = Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment;
|
||||||
export type SubaccountFundingPayments =
|
export type SubaccountFundingPayments =
|
||||||
Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment[];
|
Abacus.exchange.dydx.abacus.output.SubaccountFundingPayment[];
|
||||||
|
export type SubaccountTransfer = Abacus.exchange.dydx.abacus.output.SubaccountTransfer;
|
||||||
|
export type SubaccountTransfers = Abacus.exchange.dydx.abacus.output.SubaccountTransfer[];
|
||||||
|
|
||||||
// ------ Historical PnL ------ //
|
// ------ Historical PnL ------ //
|
||||||
export type SubAccountHistoricalPNL = Abacus.exchange.dydx.abacus.output.SubaccountHistoricalPNL;
|
export type SubAccountHistoricalPNL = Abacus.exchange.dydx.abacus.output.SubaccountHistoricalPNL;
|
||||||
@@ -174,6 +186,9 @@ export const RiskLevel = Abacus.exchange.dydx.abacus.utils.RiskLevel;
|
|||||||
const riskLevels = [...RiskLevel.values()] as const;
|
const riskLevels = [...RiskLevel.values()] as const;
|
||||||
export type RiskLevels = (typeof riskLevels)[number];
|
export type RiskLevels = (typeof riskLevels)[number];
|
||||||
|
|
||||||
|
// ------ Notifications ------ //
|
||||||
|
export type AbacusNotification = Abacus.exchange.dydx.abacus.output.Notification;
|
||||||
|
|
||||||
// ------ Enum Conversions ------ //
|
// ------ Enum Conversions ------ //
|
||||||
type IfEquals<X, Y, A, B> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
|
type IfEquals<X, Y, A, B> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
|
||||||
? A
|
? A
|
||||||
@@ -215,3 +230,19 @@ export const ORDER_STATUS_STRINGS: Record<KotlinIrEnumValues<typeof AbacusOrderS
|
|||||||
[AbacusOrderStatus.pending.name]: STRING_KEYS.PENDING,
|
[AbacusOrderStatus.pending.name]: STRING_KEYS.PENDING,
|
||||||
[AbacusOrderStatus.untriggered.name]: STRING_KEYS.UNTRIGGERED,
|
[AbacusOrderStatus.untriggered.name]: STRING_KEYS.UNTRIGGERED,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const TRADE_TYPES: Record<
|
||||||
|
KotlinIrEnumValues<typeof AbacusOrderType>,
|
||||||
|
Nullable<TradeTypes>
|
||||||
|
> = {
|
||||||
|
[AbacusOrderType.limit.name]: TradeTypes.LIMIT,
|
||||||
|
[AbacusOrderType.market.name]: TradeTypes.MARKET,
|
||||||
|
[AbacusOrderType.stopLimit.name]: TradeTypes.STOP_LIMIT,
|
||||||
|
[AbacusOrderType.stopMarket.name]: TradeTypes.STOP_MARKET,
|
||||||
|
[AbacusOrderType.takeProfitLimit.name]: TradeTypes.TAKE_PROFIT,
|
||||||
|
[AbacusOrderType.takeProfitMarket.name]: TradeTypes.TAKE_PROFIT_MARKET,
|
||||||
|
|
||||||
|
[AbacusOrderType.liquidated.name]: null,
|
||||||
|
[AbacusOrderType.liquidation.name]: null,
|
||||||
|
[AbacusOrderType.trailingStop.name]: null,
|
||||||
|
};
|
||||||
|
|||||||
+100
-115
@@ -4,7 +4,7 @@ import type { OnboardingState, OnboardingSteps } from './account';
|
|||||||
import type { DydxAddress, WalletType, WalletConnectionType, EthereumAddress } from './wallets';
|
import type { DydxAddress, WalletType, WalletConnectionType, EthereumAddress } from './wallets';
|
||||||
import type { DialogTypes } from './dialogs';
|
import type { DialogTypes } from './dialogs';
|
||||||
import type { TradeTypes } from './trade';
|
import type { TradeTypes } from './trade';
|
||||||
import type { AbacusApiStatus, SubaccountPlaceOrderPayload } from './abacus';
|
import type { AbacusApiStatus, HumanReadablePlaceOrderPayload } from './abacus';
|
||||||
|
|
||||||
// User properties
|
// User properties
|
||||||
export enum AnalyticsUserProperty {
|
export enum AnalyticsUserProperty {
|
||||||
@@ -25,37 +25,28 @@ export enum AnalyticsUserProperty {
|
|||||||
SubaccountNumber = 'subaccountNumber',
|
SubaccountNumber = 'subaccountNumber',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AnalyticsUserPropertyValue<T extends AnalyticsUserProperty> =
|
export type AnalyticsUserPropertyValue<T extends AnalyticsUserProperty> =
|
||||||
// Environment
|
// Environment
|
||||||
T extends AnalyticsUserProperty.Breakpoint ?
|
T extends AnalyticsUserProperty.Breakpoint
|
||||||
| 'MOBILE'
|
? 'MOBILE' | 'TABLET' | 'DESKTOP_SMALL' | 'DESKTOP_MEDIUM' | 'DESKTOP_LARGE' | 'UNSUPPORTED'
|
||||||
| 'TABLET'
|
: T extends AnalyticsUserProperty.Locale
|
||||||
| 'DESKTOP_SMALL'
|
? SupportedLocales
|
||||||
| 'DESKTOP_MEDIUM'
|
: // Network
|
||||||
| 'DESKTOP_LARGE'
|
T extends AnalyticsUserProperty.Network
|
||||||
| 'UNSUPPORTED'
|
? DydxNetwork
|
||||||
: T extends AnalyticsUserProperty.Locale ?
|
: // Wallet
|
||||||
SupportedLocales
|
T extends AnalyticsUserProperty.WalletType
|
||||||
|
? WalletType | undefined
|
||||||
// Network
|
: T extends AnalyticsUserProperty.WalletConnectionType
|
||||||
: T extends AnalyticsUserProperty.Network ?
|
? WalletConnectionType | undefined
|
||||||
DydxNetwork
|
: T extends AnalyticsUserProperty.WalletAddress
|
||||||
|
? EthereumAddress | DydxAddress | undefined
|
||||||
// Wallet
|
: // Account
|
||||||
: T extends AnalyticsUserProperty.WalletType ?
|
T extends AnalyticsUserProperty.DydxAddress
|
||||||
WalletType | undefined
|
? DydxAddress | undefined
|
||||||
: T extends AnalyticsUserProperty.WalletConnectionType ?
|
: T extends AnalyticsUserProperty.SubaccountNumber
|
||||||
WalletConnectionType | undefined
|
? number | undefined
|
||||||
: T extends AnalyticsUserProperty.WalletAddress ?
|
: undefined;
|
||||||
EthereumAddress | DydxAddress | undefined
|
|
||||||
|
|
||||||
// Account
|
|
||||||
: T extends AnalyticsUserProperty.DydxAddress ?
|
|
||||||
DydxAddress | undefined
|
|
||||||
: T extends AnalyticsUserProperty.SubaccountNumber ?
|
|
||||||
number | undefined
|
|
||||||
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
export enum AnalyticsEvent {
|
export enum AnalyticsEvent {
|
||||||
@@ -91,87 +82,81 @@ export enum AnalyticsEvent {
|
|||||||
|
|
||||||
export type AnalyticsEventData<T extends AnalyticsEvent> =
|
export type AnalyticsEventData<T extends AnalyticsEvent> =
|
||||||
// App
|
// App
|
||||||
T extends AnalyticsEvent.AppStart ?
|
T extends AnalyticsEvent.AppStart
|
||||||
{}
|
? {}
|
||||||
: T extends AnalyticsEvent.NetworkStatus ?
|
: T extends AnalyticsEvent.NetworkStatus
|
||||||
{
|
? {
|
||||||
status: typeof AbacusApiStatus['name'];
|
status: (typeof AbacusApiStatus)['name'];
|
||||||
/** Last time indexer node was queried successfully */
|
/** Last time indexer node was queried successfully */
|
||||||
lastSuccessfulIndexerRpcQuery?: number;
|
lastSuccessfulIndexerRpcQuery?: number;
|
||||||
/** Time elapsed since indexer node was queried successfully */
|
/** Time elapsed since indexer node was queried successfully */
|
||||||
elapsedTime?: number;
|
elapsedTime?: number;
|
||||||
blockHeight?: number;
|
blockHeight?: number;
|
||||||
indexerBlockHeight?: number;
|
indexerBlockHeight?: number;
|
||||||
}
|
}
|
||||||
|
: // Navigation
|
||||||
// Navigation
|
T extends AnalyticsEvent.NavigatePage
|
||||||
: T extends AnalyticsEvent.NavigatePage ?
|
? {
|
||||||
{
|
path: string;
|
||||||
path: string;
|
}
|
||||||
}
|
: T extends AnalyticsEvent.NavigateDialog
|
||||||
: T extends AnalyticsEvent.NavigateDialog ?
|
? {
|
||||||
{
|
type: DialogTypes;
|
||||||
type: DialogTypes;
|
}
|
||||||
}
|
: T extends AnalyticsEvent.NavigateDialogClose
|
||||||
: T extends AnalyticsEvent.NavigateDialogClose ?
|
? {
|
||||||
{
|
type: DialogTypes;
|
||||||
type: DialogTypes;
|
}
|
||||||
}
|
: T extends AnalyticsEvent.NavigateExternal
|
||||||
: T extends AnalyticsEvent.NavigateExternal ?
|
? {
|
||||||
{
|
link: string;
|
||||||
link: string;
|
}
|
||||||
}
|
: // Wallet
|
||||||
|
T extends AnalyticsEvent.ConnectWallet
|
||||||
// Wallet
|
? {
|
||||||
: T extends AnalyticsEvent.ConnectWallet ?
|
walletType: WalletType;
|
||||||
{
|
walletConnectionType: WalletConnectionType;
|
||||||
walletType: WalletType;
|
}
|
||||||
walletConnectionType: WalletConnectionType;
|
: T extends AnalyticsEvent.DisconnectWallet
|
||||||
}
|
? {}
|
||||||
: T extends AnalyticsEvent.DisconnectWallet ?
|
: // Onboarding
|
||||||
{}
|
T extends AnalyticsEvent.OnboardingStepChanged
|
||||||
|
? {
|
||||||
// Onboarding
|
state: OnboardingState;
|
||||||
: T extends AnalyticsEvent.OnboardingStepChanged ?
|
step?: OnboardingSteps;
|
||||||
{
|
}
|
||||||
state: OnboardingState;
|
: T extends AnalyticsEvent.OnboardingAccountDerived
|
||||||
step?: OnboardingSteps;
|
? {
|
||||||
}
|
hasPreviousTransactions: boolean;
|
||||||
: T extends AnalyticsEvent.OnboardingAccountDerived ?
|
}
|
||||||
{
|
: // Transfers
|
||||||
hasPreviousTransactions: boolean;
|
T extends AnalyticsEvent.TransferFaucet
|
||||||
}
|
? {}
|
||||||
|
: T extends AnalyticsEvent.TransferDeposit
|
||||||
// Transfers
|
? {}
|
||||||
: T extends AnalyticsEvent.TransferFaucet ?
|
: T extends AnalyticsEvent.TransferWithdraw
|
||||||
{}
|
? {}
|
||||||
: T extends AnalyticsEvent.TransferDeposit ?
|
: // Trading
|
||||||
{}
|
T extends AnalyticsEvent.TradeOrderTypeSelected
|
||||||
: T extends AnalyticsEvent.TransferWithdraw ?
|
? {
|
||||||
{}
|
type: TradeTypes;
|
||||||
|
}
|
||||||
// Trading
|
: T extends AnalyticsEvent.TradePlaceOrder
|
||||||
: T extends AnalyticsEvent.TradeOrderTypeSelected ?
|
? HumanReadablePlaceOrderPayload & {
|
||||||
{
|
isClosePosition: boolean;
|
||||||
type: TradeTypes;
|
}
|
||||||
}
|
: T extends AnalyticsEvent.TradePlaceOrderConfirmed
|
||||||
: T extends AnalyticsEvent.TradePlaceOrder ?
|
? {
|
||||||
SubaccountPlaceOrderPayload & {
|
/** roundtrip time between user placing an order and confirmation from indexer (client → validator → indexer → client) */
|
||||||
isClosePosition: boolean;
|
roundtripMs: number;
|
||||||
}
|
/** URL/IP of node the order was sent to */
|
||||||
: T extends AnalyticsEvent.TradePlaceOrderConfirmed ?
|
validator: string;
|
||||||
{
|
}
|
||||||
/** roundtrip time between user placing an order and confirmation from indexer (client → validator → indexer → client) */
|
: T extends AnalyticsEvent.TradeCancelOrderConfirmed
|
||||||
roundtripMs: number;
|
? {
|
||||||
/** URL/IP of node the order was sent to */
|
/** roundtrip time between user canceling an order and confirmation from indexer (client → validator → indexer → client) */
|
||||||
validator: string;
|
roundtripMs: number;
|
||||||
}
|
/** URL/IP of node the order was sent to */
|
||||||
: T extends AnalyticsEvent.TradeCancelOrderConfirmed ?
|
validator: string;
|
||||||
{
|
}
|
||||||
/** roundtrip time between user canceling an order and confirmation from indexer (client → validator → indexer → client) */
|
: never;
|
||||||
roundtripMs: number;
|
|
||||||
/** URL/IP of node the order was sent to */
|
|
||||||
validator: string;
|
|
||||||
}
|
|
||||||
:
|
|
||||||
never;
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export enum DialogTypes {
|
|||||||
Receive = 'Receive',
|
Receive = 'Receive',
|
||||||
Trade = 'Trade',
|
Trade = 'Trade',
|
||||||
Transfer = 'Transfer',
|
Transfer = 'Transfer',
|
||||||
|
Withdraw = 'Withdraw',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TradeBoxDialogTypes {
|
export enum TradeBoxDialogTypes {
|
||||||
|
|||||||
@@ -177,6 +177,7 @@ export const APP_STRING_KEYS = {
|
|||||||
RECEIVE: 'GENERAL.RECEIVE',
|
RECEIVE: 'GENERAL.RECEIVE',
|
||||||
RECENT: 'GENERAL.RECENT',
|
RECENT: 'GENERAL.RECENT',
|
||||||
RECENT_TRADES_SHORT: 'GENERAL.RECENT_TRADES_SHORT',
|
RECENT_TRADES_SHORT: 'GENERAL.RECENT_TRADES_SHORT',
|
||||||
|
RECIPIENT: 'GENERAL.RECIPIENT',
|
||||||
REFERRAL_CODE: 'GENERAL.REFERRAL_CODE',
|
REFERRAL_CODE: 'GENERAL.REFERRAL_CODE',
|
||||||
REFERRALS: 'GENERAL.REFERRALS',
|
REFERRALS: 'GENERAL.REFERRALS',
|
||||||
REFERRER_PERCENT_OFF: 'GENERAL.REFERRER_PERCENT_OFF',
|
REFERRER_PERCENT_OFF: 'GENERAL.REFERRER_PERCENT_OFF',
|
||||||
@@ -188,6 +189,7 @@ export const APP_STRING_KEYS = {
|
|||||||
SELECT_NETWORK: 'GENERAL.SELECT_NETWORK',
|
SELECT_NETWORK: 'GENERAL.SELECT_NETWORK',
|
||||||
SELL: 'GENERAL.SELL',
|
SELL: 'GENERAL.SELL',
|
||||||
SEND: 'GENERAL.SEND',
|
SEND: 'GENERAL.SEND',
|
||||||
|
SENDER: 'GENERAL.SENDER',
|
||||||
SHARE: 'GENERAL.SHARE',
|
SHARE: 'GENERAL.SHARE',
|
||||||
SHORT_POSITION_SHORT: 'GENERAL.SHORT_POSITION_SHORT',
|
SHORT_POSITION_SHORT: 'GENERAL.SHORT_POSITION_SHORT',
|
||||||
SIDE: 'GENERAL.SIDE',
|
SIDE: 'GENERAL.SIDE',
|
||||||
@@ -472,6 +474,7 @@ export const APP_STRING_KEYS = {
|
|||||||
LEAVING_WEBSITE: 'LEGAL.LEAVING_WEBSITE',
|
LEAVING_WEBSITE: 'LEGAL.LEAVING_WEBSITE',
|
||||||
|
|
||||||
// Deposit Modal
|
// Deposit Modal
|
||||||
|
BRIDGE_FEE: 'DEPOSIT_MODAL.BRIDGE_FEE',
|
||||||
ENABLE_FOR_THIS_DEPOSIT: 'DEPOSIT_MODAL.ENABLE_FOR_THIS_DEPOSIT',
|
ENABLE_FOR_THIS_DEPOSIT: 'DEPOSIT_MODAL.ENABLE_FOR_THIS_DEPOSIT',
|
||||||
ENABLE_SYMBOL_ON_DYDX: 'DEPOSIT_MODAL.ENABLE_SYMBOL_ON_DYDX',
|
ENABLE_SYMBOL_ON_DYDX: 'DEPOSIT_MODAL.ENABLE_SYMBOL_ON_DYDX',
|
||||||
ENABLE_SYMBOL: 'DEPOSIT_MODAL.ENABLE_SYMBOL',
|
ENABLE_SYMBOL: 'DEPOSIT_MODAL.ENABLE_SYMBOL',
|
||||||
@@ -928,6 +931,7 @@ export const APP_STRING_KEYS = {
|
|||||||
SLOW_WITHDRAW_PENDING: 'NOTIFICATIONS.SLOW_WITHDRAW_PENDING',
|
SLOW_WITHDRAW_PENDING: 'NOTIFICATIONS.SLOW_WITHDRAW_PENDING',
|
||||||
SUSPICIOUS_TRADE: 'NOTIFICATIONS.SUSPICIOUS_TRADE',
|
SUSPICIOUS_TRADE: 'NOTIFICATIONS.SUSPICIOUS_TRADE',
|
||||||
SUSPICIOUS_TRADE_BODY: 'NOTIFICATIONS.SUSPICIOUS_TRADE_BODY',
|
SUSPICIOUS_TRADE_BODY: 'NOTIFICATIONS.SUSPICIOUS_TRADE_BODY',
|
||||||
|
WITHDRAW_IN_PROGRESS: 'NOTIFICATIONS.WITHDRAW_IN_PROGRESS',
|
||||||
|
|
||||||
// Email Notifications
|
// Email Notifications
|
||||||
ACCOUNT_DESCRIPTION: 'EMAIL_NOTIFICATIONS.ACCOUNT_DESCRIPTION',
|
ACCOUNT_DESCRIPTION: 'EMAIL_NOTIFICATIONS.ACCOUNT_DESCRIPTION',
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ export const ERROR_STRING_KEYS = {
|
|||||||
RESTRICTED_OPEN_POSITIONS_WITHDRAW: 'ERRORS.WITHDRAW_MODAL.RESTRICTED_OPEN_POSITIONS_WITHDRAW',
|
RESTRICTED_OPEN_POSITIONS_WITHDRAW: 'ERRORS.WITHDRAW_MODAL.RESTRICTED_OPEN_POSITIONS_WITHDRAW',
|
||||||
WITHDRAW_MORE_THAN_FREE_DUE_TO_FEE: 'ERRORS.WITHDRAW_MODAL.WITHDRAW_MORE_THAN_FREE_DUE_TO_FEE',
|
WITHDRAW_MORE_THAN_FREE_DUE_TO_FEE: 'ERRORS.WITHDRAW_MODAL.WITHDRAW_MORE_THAN_FREE_DUE_TO_FEE',
|
||||||
WITHDRAW_MORE_THAN_FREE: 'ERRORS.WITHDRAW_MODAL.WITHDRAW_MORE_THAN_FREE',
|
WITHDRAW_MORE_THAN_FREE: 'ERRORS.WITHDRAW_MODAL.WITHDRAW_MORE_THAN_FREE',
|
||||||
|
WITHDRAW_MUST_SPECIFY_ADDRESS: 'ERRORS.DEPOSIT_MODAL.WITHDRAW_MUST_SPECIFY_ADDRESS',
|
||||||
|
WITHDRAW_MUST_SPECIFY_ASSET: 'ERRORS.DEPOSIT_MODAL.WITHDRAW_MUST_SPECIFY_ASSET',
|
||||||
|
WITHDRAW_MUST_SPECIFY_CHAIN: 'ERRORS.DEPOSIT_MODAL.WITHDRAW_MUST_SPECIFY_CHAIN',
|
||||||
|
|
||||||
// Transfer modal errors
|
// Transfer modal errors
|
||||||
TRANSFER_INVALID_ETH_ADDRESS: 'ERRORS.TRANSFER_MODAL.TRANSFER_INVALID_ETH_ADDRESS',
|
TRANSFER_INVALID_ETH_ADDRESS: 'ERRORS.TRANSFER_MODAL.TRANSFER_INVALID_ETH_ADDRESS',
|
||||||
|
|||||||
+18
-62
@@ -1,73 +1,29 @@
|
|||||||
|
import { ENDPOINTS, DEV_ENDPOINTS } from '@dydxprotocol/v4-localization';
|
||||||
|
|
||||||
|
export const NETWORK_ENDPOINTS = import.meta.env.MODE === 'production' ? ENDPOINTS : DEV_ENDPOINTS;
|
||||||
|
|
||||||
|
export const CLIENT_NETWORK_CONFIGS: Record<
|
||||||
|
string,
|
||||||
|
(typeof NETWORK_ENDPOINTS.environments)[number]
|
||||||
|
> = Object.fromEntries(
|
||||||
|
NETWORK_ENDPOINTS.environments.map((environment) => [environment.environment, environment])
|
||||||
|
);
|
||||||
|
|
||||||
|
export const DEFAULT_APP_ENVIRONMENT = NETWORK_ENDPOINTS.defaultEnvironment;
|
||||||
|
|
||||||
export enum DydxV4Network {
|
export enum DydxV4Network {
|
||||||
V4Mainnet = 'dydxprotocol-mainnet',
|
V4Mainnet = 'dydxprotocol-mainnet',
|
||||||
V4Staging = 'dydxprotocol-staging',
|
V4Staging = 'dydxprotocol-staging',
|
||||||
V4Testnet2 = 'dydxprotocol-testnet-2',
|
V4Testnet2 = 'dydxprotocol-testnet',
|
||||||
V4Local = 'dydxprotocol-dev',
|
V4Local = 'dydxprotocol-dev',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DydxV3Network {
|
export type DydxNetwork = keyof typeof CLIENT_NETWORK_CONFIGS;
|
||||||
V3Mainnet = '1',
|
|
||||||
V3Testnet = '5',
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isDydxV4Network(network: any): network is DydxV4Network {
|
export function isDydxV4Network(network: any): boolean {
|
||||||
return Object.values(DydxV4Network).includes(network);
|
return CLIENT_NETWORK_CONFIGS[network]?.version === 'v4';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidDydxNetwork(network: any): boolean {
|
export function isValidDydxNetwork(network: any): boolean {
|
||||||
return isDydxV4Network(network) || Object.values(DydxV3Network).includes(network);
|
return Object.keys(CLIENT_NETWORK_CONFIGS).includes(network);
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DydxNetwork = DydxV4Network | DydxV3Network;
|
|
||||||
|
|
||||||
export const CHAIN_IDS = {
|
|
||||||
[DydxV4Network.V4Mainnet]: '',
|
|
||||||
[DydxV4Network.V4Staging]: 'dydxprotocol-testnet',
|
|
||||||
[DydxV4Network.V4Testnet2]: 'dydx-testnet-2',
|
|
||||||
[DydxV4Network.V4Local]: 'dydxprotocol-testnet',
|
|
||||||
[DydxV3Network.V3Mainnet]: '1',
|
|
||||||
[DydxV3Network.V3Testnet]: '5',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DEFAULT_APP_ENVIRONMENT =
|
|
||||||
import.meta.env.MODE === 'production' ? DydxV4Network.V4Testnet2 : DydxV4Network.V4Staging;
|
|
||||||
|
|
||||||
export const CLIENT_NETWORK_CONFIGS: Record<
|
|
||||||
DydxV4Network,
|
|
||||||
{
|
|
||||||
chainId: string;
|
|
||||||
validatorUrl: string;
|
|
||||||
faucetUrl: string;
|
|
||||||
indexerUrl: string;
|
|
||||||
ethereumChainId: number;
|
|
||||||
}
|
|
||||||
> = {
|
|
||||||
[DydxV4Network.V4Mainnet]: {
|
|
||||||
chainId: CHAIN_IDS[DydxV4Network.V4Mainnet],
|
|
||||||
validatorUrl: import.meta.env.VITE_VALIDATOR_URL_STAGE,
|
|
||||||
faucetUrl: import.meta.env.VITE_FAUCET_URL_STAGE,
|
|
||||||
indexerUrl: import.meta.env.VITE_INDEXER_URL_STAGE,
|
|
||||||
ethereumChainId: 1,
|
|
||||||
},
|
|
||||||
[DydxV4Network.V4Local]: {
|
|
||||||
chainId: CHAIN_IDS[DydxV4Network.V4Local],
|
|
||||||
validatorUrl: import.meta.env.VITE_VALIDATOR_URL_DEV,
|
|
||||||
faucetUrl: import.meta.env.VITE_FAUCET_URL_DEV,
|
|
||||||
indexerUrl: import.meta.env.VITE_INDEXER_URL_DEV,
|
|
||||||
ethereumChainId: 5,
|
|
||||||
},
|
|
||||||
[DydxV4Network.V4Staging]: {
|
|
||||||
chainId: CHAIN_IDS[DydxV4Network.V4Staging],
|
|
||||||
validatorUrl: import.meta.env.VITE_VALIDATOR_URL_STAGE,
|
|
||||||
faucetUrl: import.meta.env.VITE_FAUCET_URL_STAGE,
|
|
||||||
indexerUrl: import.meta.env.VITE_INDEXER_URL_STAGE,
|
|
||||||
ethereumChainId: 5,
|
|
||||||
},
|
|
||||||
[DydxV4Network.V4Testnet2]: {
|
|
||||||
chainId: CHAIN_IDS[DydxV4Network.V4Testnet2],
|
|
||||||
validatorUrl: import.meta.env.VITE_VALIDATOR_URL_TESTNET2,
|
|
||||||
faucetUrl: import.meta.env.VITE_FAUCET_URL_TESTNET2,
|
|
||||||
indexerUrl: import.meta.env.VITE_INDEXER_URL_TESTNET2,
|
|
||||||
ethereumChainId: 5,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import { DEFAULT_MARKETID } from './markets';
|
||||||
|
|
||||||
export enum AppRoute {
|
export enum AppRoute {
|
||||||
Markets = '/markets',
|
Markets = '/markets',
|
||||||
Portfolio = '/portfolio',
|
Portfolio = '/portfolio',
|
||||||
@@ -5,7 +7,7 @@ export enum AppRoute {
|
|||||||
Profile = '/profile',
|
Profile = '/profile',
|
||||||
Alerts = '/alerts',
|
Alerts = '/alerts',
|
||||||
Settings = '/settings',
|
Settings = '/settings',
|
||||||
Rewards = '/DYDX',
|
Rewards = '/DV4TNT',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PortfolioRoute {
|
export enum PortfolioRoute {
|
||||||
@@ -31,7 +33,7 @@ export enum MobileSettingsRoute {
|
|||||||
export const TRADE_ROUTE = `${AppRoute.Trade}/:market`;
|
export const TRADE_ROUTE = `${AppRoute.Trade}/:market`;
|
||||||
export const PORTFOLIO_ROUTE = `${AppRoute.Portfolio}/:subroute`;
|
export const PORTFOLIO_ROUTE = `${AppRoute.Portfolio}/:subroute`;
|
||||||
export const HISTORY_ROUTE = `${AppRoute.Portfolio}/${PortfolioRoute.History}/:subroute`;
|
export const HISTORY_ROUTE = `${AppRoute.Portfolio}/${PortfolioRoute.History}/:subroute`;
|
||||||
export const DEFAULT_TRADE_ROUTE = `${AppRoute.Trade}/ETH-USD`;
|
export const DEFAULT_TRADE_ROUTE = `${AppRoute.Trade}/${DEFAULT_MARKETID}`;
|
||||||
export const SETTINGS_ROUTE = `${AppRoute.Settings}/*`;
|
export const SETTINGS_ROUTE = `${AppRoute.Settings}/*`;
|
||||||
export const DEFAULT_DOCUMENT_TITLE = 'dYdX';
|
export const DEFAULT_DOCUMENT_TITLE = 'dYdX';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { OrderSide } from '@dydxprotocol/v4-client';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import { AlertType } from '@/constants/alerts';
|
import { AlertType } from '@/constants/alerts';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
@@ -10,7 +10,7 @@ export enum TradeTypes {
|
|||||||
LIMIT = 'LIMIT',
|
LIMIT = 'LIMIT',
|
||||||
STOP_LIMIT = 'STOP_LIMIT',
|
STOP_LIMIT = 'STOP_LIMIT',
|
||||||
STOP_MARKET = 'STOP_MARKET',
|
STOP_MARKET = 'STOP_MARKET',
|
||||||
TAKE_PROFIT_LIMIT = 'TAKE_PROFIT_LIMIT',
|
TAKE_PROFIT = 'TAKE_PROFIT',
|
||||||
TAKE_PROFIT_MARKET = 'TAKE_PROFIT_MARKET',
|
TAKE_PROFIT_MARKET = 'TAKE_PROFIT_MARKET',
|
||||||
TRAILING_STOP = 'TRAILING_STOP',
|
TRAILING_STOP = 'TRAILING_STOP',
|
||||||
}
|
}
|
||||||
@@ -30,9 +30,9 @@ export enum PositionSide {
|
|||||||
export const UNCOMMITTED_ORDER_TIMEOUT = 10_000;
|
export const UNCOMMITTED_ORDER_TIMEOUT = 10_000;
|
||||||
|
|
||||||
export const ORDER_SIDE_STRINGS = {
|
export const ORDER_SIDE_STRINGS = {
|
||||||
[OrderSide.SIDE_BUY]: STRING_KEYS.BUY,
|
[OrderSide.BUY]: STRING_KEYS.BUY,
|
||||||
[OrderSide.SIDE_SELL]: STRING_KEYS.SELL,
|
[OrderSide.SELL]: STRING_KEYS.SELL,
|
||||||
}
|
};
|
||||||
|
|
||||||
export const POSITION_SIDE_STRINGS: Record<PositionSide, string> = {
|
export const POSITION_SIDE_STRINGS: Record<PositionSide, string> = {
|
||||||
[PositionSide.None]: STRING_KEYS.NONE,
|
[PositionSide.None]: STRING_KEYS.NONE,
|
||||||
@@ -68,7 +68,7 @@ export const TRADE_TYPE_STRINGS: Record<
|
|||||||
tradeTypeKey: STRING_KEYS.STOP_MARKET,
|
tradeTypeKey: STRING_KEYS.STOP_MARKET,
|
||||||
descriptionKey: STRING_KEYS.STOP_MARKET_DESCRIPTION,
|
descriptionKey: STRING_KEYS.STOP_MARKET_DESCRIPTION,
|
||||||
},
|
},
|
||||||
[TradeTypes.TAKE_PROFIT_LIMIT]: {
|
[TradeTypes.TAKE_PROFIT]: {
|
||||||
tradeTypeKeyShort: STRING_KEYS.TAKE_PROFIT_LIMIT,
|
tradeTypeKeyShort: STRING_KEYS.TAKE_PROFIT_LIMIT,
|
||||||
tradeTypeKey: STRING_KEYS.TAKE_PROFIT_LIMIT,
|
tradeTypeKey: STRING_KEYS.TAKE_PROFIT_LIMIT,
|
||||||
descriptionKey: STRING_KEYS.TAKE_PROFIT_LIMIT_DESCRIPTION,
|
descriptionKey: STRING_KEYS.TAKE_PROFIT_LIMIT_DESCRIPTION,
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ export type WalletConnection = {
|
|||||||
|
|
||||||
// dYdX Chain wallets
|
// dYdX Chain wallets
|
||||||
|
|
||||||
import { USDC_DENOM, type Onboarding, DYDX_DENOM } from '@dydxprotocol/v4-client';
|
import { USDC_DENOM, type Onboarding, DYDX_DENOM } from '@dydxprotocol/v4-client-js';
|
||||||
import type { suggestChain } from 'graz';
|
import type { suggestChain } from 'graz';
|
||||||
|
|
||||||
export const COSMOS_DERIVATION_PATH = "m/44'/118'/0'/0/0";
|
export const COSMOS_DERIVATION_PATH = "m/44'/118'/0'/0/0";
|
||||||
@@ -377,7 +377,7 @@ export const DYDX_CHAIN_INFO: Parameters<typeof suggestChain>[0] = {
|
|||||||
|
|
||||||
export enum DydxChainAsset {
|
export enum DydxChainAsset {
|
||||||
USDC = 'USDC',
|
USDC = 'USDC',
|
||||||
DYDX = 'DYDX',
|
DYDX = 'Dv4TNT',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DYDX_CHAIN_ASSET_COIN_DENOM: Record<DydxChainAsset, string> = {
|
export const DYDX_CHAIN_ASSET_COIN_DENOM: Record<DydxChainAsset, string> = {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { useApiState } from './useApiState';
|
import { useApiState } from './useApiState';
|
||||||
import { useBreakpoints } from './useBreakpoints';
|
import { useBreakpoints } from './useBreakpoints';
|
||||||
import { useCommandMenu } from './useCommandMenu';
|
import { useCommandMenu } from './useCommandMenu';
|
||||||
|
import { useCurrentMarketId } from './useCurrentMarketId';
|
||||||
|
import { useDebounce } from './useDebounce';
|
||||||
import { useInterval } from './useInterval';
|
import { useInterval } from './useInterval';
|
||||||
import { useDocumentTitle } from './useDocumentTitle';
|
import { useDocumentTitle } from './useDocumentTitle';
|
||||||
import { useDydxClient } from './useDydxClient';
|
import { useDydxClient } from './useDydxClient';
|
||||||
@@ -23,6 +25,8 @@ export {
|
|||||||
useApiState,
|
useApiState,
|
||||||
useBreakpoints,
|
useBreakpoints,
|
||||||
useCommandMenu,
|
useCommandMenu,
|
||||||
|
useCurrentMarketId,
|
||||||
|
useDebounce,
|
||||||
useDocumentTitle,
|
useDocumentTitle,
|
||||||
useDydxClient,
|
useDydxClient,
|
||||||
useAccountBalance,
|
useAccountBalance,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import isEmpty from 'lodash/isEmpty';
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
|
||||||
import { LanguageCode, ResolutionString, widget } from 'public/tradingview/charting_library';
|
import { LanguageCode, ResolutionString, widget } from 'public/tradingview/charting_library';
|
||||||
@@ -14,9 +13,8 @@ import { store } from '@/state/_store';
|
|||||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||||
import { getAppTheme } from '@/state/configsSelectors';
|
import { getAppTheme } from '@/state/configsSelectors';
|
||||||
import { getSelectedLocale } from '@/state/localizationSelectors';
|
import { getSelectedLocale } from '@/state/localizationSelectors';
|
||||||
import { getMarketIds } from '@/state/perpetualsSelectors';
|
import { getCurrentMarketId, getMarketIds } from '@/state/perpetualsSelectors';
|
||||||
|
|
||||||
import { getMarketIdFromLocation } from '@/lib/tradeData';
|
|
||||||
import { getDydxDatafeed } from '@/lib/tradingView/dydxfeed';
|
import { getDydxDatafeed } from '@/lib/tradingView/dydxfeed';
|
||||||
import { getSavedResolution, getWidgetOptions, getWidgetOverrides } from '@/lib/tradingView/utils';
|
import { getSavedResolution, getWidgetOptions, getWidgetOverrides } from '@/lib/tradingView/utils';
|
||||||
|
|
||||||
@@ -30,8 +28,7 @@ export const useTradingView = ({
|
|||||||
tvWidgetRef: React.MutableRefObject<any>;
|
tvWidgetRef: React.MutableRefObject<any>;
|
||||||
setIsChartReady: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsChartReady: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
}) => {
|
}) => {
|
||||||
const location = useLocation();
|
const marketId = useSelector(getCurrentMarketId);
|
||||||
const marketId = getMarketIdFromLocation(location);
|
|
||||||
const appTheme = useSelector(getAppTheme);
|
const appTheme = useSelector(getAppTheme);
|
||||||
const marketIds = useSelector(getMarketIds, shallowEqual);
|
const marketIds = useSelector(getMarketIds, shallowEqual);
|
||||||
const selectedLocale = useSelector(getSelectedLocale);
|
const selectedLocale = useSelector(getSelectedLocale);
|
||||||
@@ -50,7 +47,6 @@ export const useTradingView = ({
|
|||||||
if (hasMarkets) {
|
if (hasMarkets) {
|
||||||
const widgetOptions = getWidgetOptions();
|
const widgetOptions = getWidgetOptions();
|
||||||
const widgetOverrides = getWidgetOverrides(appTheme);
|
const widgetOverrides = getWidgetOverrides(appTheme);
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
// debug: true,
|
// debug: true,
|
||||||
...widgetOptions,
|
...widgetOptions,
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ import { useSelector } from 'react-redux';
|
|||||||
import { useBalance } from 'wagmi';
|
import { useBalance } from 'wagmi';
|
||||||
import { StargateClient } from '@cosmjs/stargate';
|
import { StargateClient } from '@cosmjs/stargate';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { formatUnits } from 'ethers';
|
import { formatUnits } from 'viem';
|
||||||
|
|
||||||
import { CLIENT_NETWORK_CONFIGS, type DydxV4Network } from '@/constants/networks';
|
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
|
||||||
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
|
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
|
||||||
import { EthereumAddress } from '@/constants/wallets';
|
import { EthereumAddress } from '@/constants/wallets';
|
||||||
|
|
||||||
@@ -15,7 +15,6 @@ import { MustBigNumber } from '@/lib/numbers';
|
|||||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||||
|
|
||||||
import { useAccounts } from './useAccounts';
|
import { useAccounts } from './useAccounts';
|
||||||
// import { useDydxClient } from './useDydxClient';
|
|
||||||
import { usePollNativeTokenBalance } from './usePollNativeTokenBalance';
|
import { usePollNativeTokenBalance } from './usePollNativeTokenBalance';
|
||||||
|
|
||||||
type UseAccountBalanceProps = {
|
type UseAccountBalanceProps = {
|
||||||
@@ -43,16 +42,14 @@ export const useAccountBalance = ({
|
|||||||
assetSymbol,
|
assetSymbol,
|
||||||
bech32AddrPrefix,
|
bech32AddrPrefix,
|
||||||
chainId,
|
chainId,
|
||||||
decimals,
|
decimals = 0,
|
||||||
rpc,
|
rpc,
|
||||||
isCosmosChain,
|
isCosmosChain,
|
||||||
}: UseAccountBalanceProps = {}) => {
|
}: UseAccountBalanceProps = {}) => {
|
||||||
const { evmAddress, dydxAddress } = useAccounts();
|
const { evmAddress, dydxAddress } = useAccounts();
|
||||||
|
|
||||||
// TODO: let Abacus / useDydxClient handle EVM chain IDs
|
|
||||||
// const { networkConfig } = useDydxClient();
|
|
||||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||||
const evmChainId = Number(CLIENT_NETWORK_CONFIGS[selectedNetwork as DydxV4Network].ethereumChainId);
|
const evmChainId = Number(CLIENT_NETWORK_CONFIGS[selectedNetwork].ethereumChainId);
|
||||||
|
|
||||||
const evmQuery = useBalance({
|
const evmQuery = useBalance({
|
||||||
enabled: Boolean(!isCosmosChain && addressOrDenom?.startsWith('0x')),
|
enabled: Boolean(!isCosmosChain && addressOrDenom?.startsWith('0x')),
|
||||||
@@ -66,7 +63,7 @@ export const useAccountBalance = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const cosmosQueryFn = useCallback(async () => {
|
const cosmosQueryFn = useCallback(async () => {
|
||||||
if (dydxAddress && bech32AddrPrefix && rpc) {
|
if (dydxAddress && bech32AddrPrefix && rpc && addressOrDenom) {
|
||||||
const address = convertBech32Address({
|
const address = convertBech32Address({
|
||||||
address: dydxAddress,
|
address: dydxAddress,
|
||||||
bech32Prefix: bech32AddrPrefix,
|
bech32Prefix: bech32AddrPrefix,
|
||||||
@@ -76,14 +73,7 @@ export const useAccountBalance = ({
|
|||||||
const balanceAsCoin = await client.getBalance(address, addressOrDenom);
|
const balanceAsCoin = await client.getBalance(address, addressOrDenom);
|
||||||
await client.disconnect();
|
await client.disconnect();
|
||||||
|
|
||||||
/**
|
return formatUnits(BigInt(balanceAsCoin.amount), decimals);
|
||||||
* Hardcode decimals because most cosmos coins default to 6 decimals.
|
|
||||||
* Squids testnet has some coins as having 18 decimals instead of 6.
|
|
||||||
* @url https://github1s.com/cosmos/cosmjs/blob/HEAD/packages/amino/src/coins.ts#L8-L16
|
|
||||||
*
|
|
||||||
* @todo: change '6' to 'decimals' when Squid testnet data is updated.
|
|
||||||
* */
|
|
||||||
return formatUnits(balanceAsCoin.amount, decimals);
|
|
||||||
}
|
}
|
||||||
}, [addressOrDenom, chainId, rpc]);
|
}, [addressOrDenom, chainId, rpc]);
|
||||||
|
|
||||||
@@ -109,11 +99,7 @@ export const useAccountBalance = ({
|
|||||||
return {
|
return {
|
||||||
balance,
|
balance,
|
||||||
nativeTokenBalance,
|
nativeTokenBalance,
|
||||||
isBalanceError: isCosmosChain
|
queryStatus: isCosmosChain ? cosmosQuery.status : evmQuery.status,
|
||||||
? cosmosQuery.status === 'error' && !cosmosQuery.isFetching
|
isQueryFetching: isCosmosChain ? cosmosQuery.isFetching : evmQuery.fetchStatus === 'fetching',
|
||||||
: evmQuery.status === 'error' && evmQuery.fetchStatus === 'idle',
|
|
||||||
isBalanceLoading: isCosmosChain
|
|
||||||
? cosmosQuery.status === 'loading' && cosmosQuery.isFetching
|
|
||||||
: evmQuery.status === 'loading' && evmQuery.fetchStatus === 'fetching',
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import { useCallback, useContext, createContext, useEffect, useState, useMemo }
|
|||||||
|
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { AES, enc } from 'crypto-js';
|
import { AES, enc } from 'crypto-js';
|
||||||
import { LocalWallet, USDC_DENOM, type Subaccount } from '@dydxprotocol/v4-client';
|
import { LocalWallet, USDC_DENOM, type Subaccount } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import { SubAccountHistoricalPNLs } from '@/constants/abacus';
|
|
||||||
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
|
import { OnboardingGuard, OnboardingState, type EvmDerivedAddresses } from '@/constants/account';
|
||||||
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
|
import { LocalStorageKey, LOCAL_STORAGE_VERSIONS } from '@/constants/localStorage';
|
||||||
import { DydxAddress, EthereumAddress, PrivateInformation } from '@/constants/wallets';
|
import { DydxAddress, EthereumAddress, PrivateInformation } from '@/constants/wallets';
|
||||||
@@ -12,8 +11,6 @@ import { DydxAddress, EthereumAddress, PrivateInformation } from '@/constants/wa
|
|||||||
import {
|
import {
|
||||||
setOnboardingState,
|
setOnboardingState,
|
||||||
setOnboardingGuard,
|
setOnboardingGuard,
|
||||||
setSubaccount,
|
|
||||||
setHistoricalPnl,
|
|
||||||
} from '@/state/account';
|
} from '@/state/account';
|
||||||
|
|
||||||
import abacusStateManager from '@/lib/abacus';
|
import abacusStateManager from '@/lib/abacus';
|
||||||
@@ -63,6 +60,8 @@ const useAccountsContext = () => {
|
|||||||
forgetEvmSignature(previousEvmAddress);
|
forgetEvmSignature(previousEvmAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (evmAddress) abacusStateManager.setTransfersSourceAddress(evmAddress);
|
||||||
|
|
||||||
setPreviousEvmAddress(evmAddress);
|
setPreviousEvmAddress(evmAddress);
|
||||||
}, [evmAddress]);
|
}, [evmAddress]);
|
||||||
|
|
||||||
@@ -224,7 +223,7 @@ const useAccountsContext = () => {
|
|||||||
// TODO: useAbacus({ dydxAddress })
|
// TODO: useAbacus({ dydxAddress })
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (dydxAddress) abacusStateManager.setAccount(dydxAddress);
|
if (dydxAddress) abacusStateManager.setAccount(dydxAddress);
|
||||||
else abacusStateManager.disconnectAccount();
|
else abacusStateManager.attemptDisconnectAccount();
|
||||||
}, [dydxAddress]);
|
}, [dydxAddress]);
|
||||||
|
|
||||||
// clear subaccounts when no dydxAddress is set
|
// clear subaccounts when no dydxAddress is set
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useMatch, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { LocalStorageKey } from '@/constants/localStorage';
|
||||||
|
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||||
|
import { AppRoute, DEFAULT_TRADE_ROUTE } from '@/constants/routes';
|
||||||
|
|
||||||
|
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||||
|
import { closeDialogInTradeBox } from '@/state/dialogs';
|
||||||
|
import { setCurrentMarketId } from '@/state/perpetuals';
|
||||||
|
|
||||||
|
import abacusStateManager from '@/lib/abacus';
|
||||||
|
|
||||||
|
import { useLocalStorage } from './useLocalStorage';
|
||||||
|
import { getMarketIds } from '@/state/perpetualsSelectors';
|
||||||
|
|
||||||
|
export const useCurrentMarketId = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const match = useMatch(`/${AppRoute.Trade}/:marketId`);
|
||||||
|
const { marketId } = match?.params ?? {};
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||||
|
const marketIds = useSelector(getMarketIds, shallowEqual);
|
||||||
|
const [lastViewedMarket, setLastViewedMarket] = useLocalStorage({
|
||||||
|
key: LocalStorageKey.LastViewedMarket,
|
||||||
|
defaultValue: DEFAULT_MARKETID,
|
||||||
|
});
|
||||||
|
|
||||||
|
const validId = useMemo(() => {
|
||||||
|
if (marketIds.length === 0) return marketId ?? lastViewedMarket;
|
||||||
|
if (!marketIds.includes(marketId ?? lastViewedMarket)) return DEFAULT_MARKETID;
|
||||||
|
return marketId ?? lastViewedMarket;
|
||||||
|
}, [marketIds, marketId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLastViewedMarket(validId);
|
||||||
|
dispatch(setCurrentMarketId(validId));
|
||||||
|
dispatch(closeDialogInTradeBox());
|
||||||
|
|
||||||
|
navigate(`${AppRoute.Trade}/${validId}`, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
}, [validId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
abacusStateManager.setMarket(marketId ?? DEFAULT_MARKETID);
|
||||||
|
}, [selectedNetwork, marketId]);
|
||||||
|
};
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://usehooks-ts.com/react-hook/use-debounce
|
||||||
|
* @param value
|
||||||
|
* @param delay
|
||||||
|
* @returns the debouncedValue
|
||||||
|
*/
|
||||||
|
export function useDebounce<T>(value: T, delay?: number): T {
|
||||||
|
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => setDebouncedValue(value), delay || 500);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
};
|
||||||
|
}, [value, delay]);
|
||||||
|
|
||||||
|
return debouncedValue;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { DydxV4Network } from '@/constants/networks';
|
import { isDydxV4Network } from '@/constants/networks';
|
||||||
import { WalletType } from '@/constants/wallets';
|
import { WalletType } from '@/constants/wallets';
|
||||||
|
|
||||||
import { isTruthy } from '@/lib/isTruthy';
|
import { isTruthy } from '@/lib/isTruthy';
|
||||||
@@ -11,9 +11,7 @@ export const useDisplayedWallets = () => {
|
|||||||
return [
|
return [
|
||||||
WalletType.MetaMask,
|
WalletType.MetaMask,
|
||||||
|
|
||||||
import.meta.env.MODE !== 'production' &&
|
import.meta.env.MODE !== 'production' && isDydxV4Network(selectedNetwork) && WalletType.Keplr,
|
||||||
[DydxV4Network.V4Testnet2, DydxV4Network.V4Staging].includes(selectedNetwork) &&
|
|
||||||
WalletType.Keplr,
|
|
||||||
|
|
||||||
WalletType.WalletConnect2,
|
WalletType.WalletConnect2,
|
||||||
WalletType.WalletConnect,
|
WalletType.WalletConnect,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
onboarding,
|
onboarding,
|
||||||
Network,
|
Network,
|
||||||
ValidatorConfig,
|
ValidatorConfig,
|
||||||
} from '@dydxprotocol/v4-client';
|
} from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import type { ResolutionString } from 'public/tradingview/charting_library';
|
import type { ResolutionString } from 'public/tradingview/charting_library';
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,7 @@ import { useDispatch } from 'react-redux';
|
|||||||
|
|
||||||
import { LocalStorageKey } from '@/constants/localStorage';
|
import { LocalStorageKey } from '@/constants/localStorage';
|
||||||
|
|
||||||
import {
|
import { DEFAULT_APP_ENVIRONMENT, DydxNetwork, isValidDydxNetwork } from '@/constants/networks';
|
||||||
DEFAULT_APP_ENVIRONMENT,
|
|
||||||
DydxNetwork,
|
|
||||||
isDydxV4Network,
|
|
||||||
isValidDydxNetwork,
|
|
||||||
} from '@/constants/networks';
|
|
||||||
|
|
||||||
import { useLocalStorage } from '@/hooks';
|
import { useLocalStorage } from '@/hooks';
|
||||||
|
|
||||||
@@ -16,8 +11,6 @@ import { initializeLocalization } from '@/state/app';
|
|||||||
|
|
||||||
import abacusStateManager from '@/lib/abacus';
|
import abacusStateManager from '@/lib/abacus';
|
||||||
|
|
||||||
import squidRouter from '@/lib/squidRouter';
|
|
||||||
|
|
||||||
export const useInitializePage = () => {
|
export const useInitializePage = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
@@ -31,9 +24,5 @@ export const useInitializePage = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(initializeLocalization());
|
dispatch(initializeLocalization());
|
||||||
abacusStateManager.start({ network: localStorageNetwork });
|
abacusStateManager.start({ network: localStorageNetwork });
|
||||||
|
|
||||||
if (isDydxV4Network(localStorageNetwork)) {
|
|
||||||
squidRouter.switchNetwork(localStorageNetwork);
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { useEffect, useMemo, useState } from 'react';
|
|
||||||
import { getSeparator } from '@/lib/numbers';
|
|
||||||
|
|
||||||
export const useLocaleSeparators = () => {
|
|
||||||
const [locale, setLocale] = useState(navigator.language || 'en-US');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handler = () => setLocale(navigator.language || 'en-US');
|
|
||||||
globalThis.addEventListener('languagechange', handler);
|
|
||||||
return () => globalThis.removeEventListener('languagechange', handler);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
return {
|
|
||||||
group: getSeparator({ locale, separatorType: 'group' }),
|
|
||||||
decimal: getSeparator({ locale, separatorType: 'decimal' }),
|
|
||||||
};
|
|
||||||
}, [locale]);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { SUPPORTED_BASE_TAGS_LOCALE_MAPPING } from '@/constants/localization';
|
||||||
|
|
||||||
|
import { getSelectedLocale } from '@/state/localizationSelectors';
|
||||||
|
|
||||||
|
import abacusStateManager from '@/lib/abacus';
|
||||||
|
import { getSeparator } from '@/lib/numbers';
|
||||||
|
|
||||||
|
type LocaleContextType = ReturnType<typeof useLocaleContext>;
|
||||||
|
const LocaleContext = createContext<LocaleContextType>({} as LocaleContextType);
|
||||||
|
LocaleContext.displayName = 'Locale';
|
||||||
|
|
||||||
|
export const LocaleProvider = ({ ...props }) => (
|
||||||
|
<LocaleContext.Provider value={useLocaleContext()} {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
const useLocaleContext = () => {
|
||||||
|
const selectedLocale = useSelector(getSelectedLocale);
|
||||||
|
const [browserLanguage, setBrowserLanguage] = useState(navigator.language || 'en-US');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handler = () => setBrowserLanguage(navigator.language || 'en-US');
|
||||||
|
globalThis.addEventListener('languagechange', handler);
|
||||||
|
return () => globalThis.removeEventListener('languagechange', handler);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedLocale) {
|
||||||
|
const updatedBrowserLanguage = Object.entries(SUPPORTED_BASE_TAGS_LOCALE_MAPPING).find(
|
||||||
|
([, value]) => value === selectedLocale
|
||||||
|
);
|
||||||
|
|
||||||
|
if (updatedBrowserLanguage) {
|
||||||
|
setBrowserLanguage(selectedLocale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [selectedLocale]);
|
||||||
|
|
||||||
|
const separators = useMemo(() => {
|
||||||
|
return {
|
||||||
|
group: getSeparator({ browserLanguage, separatorType: 'group' }),
|
||||||
|
decimal: getSeparator({ browserLanguage, separatorType: 'decimal' }),
|
||||||
|
};
|
||||||
|
}, [browserLanguage]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
abacusStateManager.setLocaleSeparators(separators);
|
||||||
|
}, [separators]);
|
||||||
|
|
||||||
|
return separators;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useLocaleSeparators = () => useContext(LocaleContext);
|
||||||
@@ -29,7 +29,7 @@ export const useMarketsData = (
|
|||||||
|
|
||||||
const markets = useMemo(() => {
|
const markets = useMemo(() => {
|
||||||
return Object.values(allPerpetualMarkets)
|
return Object.values(allPerpetualMarkets)
|
||||||
.filter(Boolean)
|
.filter(({ assetId }) => allAssets[assetId]) // Filter out markets with no asset information
|
||||||
.map((marketData) => ({
|
.map((marketData) => ({
|
||||||
asset: allAssets[marketData.assetId],
|
asset: allAssets[marketData.assetId],
|
||||||
tickSizeDecimals: marketData.configs?.tickSizeDecimals,
|
tickSizeDecimals: marketData.configs?.tickSizeDecimals,
|
||||||
@@ -56,7 +56,7 @@ export const useMarketsData = (
|
|||||||
() => [
|
() => [
|
||||||
MarketFilters.ALL,
|
MarketFilters.ALL,
|
||||||
...Object.keys(MARKET_FILTER_LABELS).filter((marketFilter) =>
|
...Object.keys(MARKET_FILTER_LABELS).filter((marketFilter) =>
|
||||||
markets.some((market) => market.asset.tags?.toArray().some((tag) => tag === marketFilter))
|
markets.some((market) => market.asset?.tags?.toArray().some((tag) => tag === marketFilter))
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
[markets]
|
[markets]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { DEFAULT_DOCUMENT_TITLE } from '@/constants/routes';
|
import { DEFAULT_DOCUMENT_TITLE } from '@/constants/routes';
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useQuery } from 'react-query';
|
|||||||
import { useAccounts } from '@/hooks';
|
import { useAccounts } from '@/hooks';
|
||||||
|
|
||||||
import { DydxAddress } from '@/constants/wallets';
|
import { DydxAddress } from '@/constants/wallets';
|
||||||
import { DYDX_DENOM } from '@dydxprotocol/v4-client';
|
import { DYDX_DENOM } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
const ACCOUNT_BALANCE_POLLING_INTERVAL = 60_000;
|
const ACCOUNT_BALANCE_POLLING_INTERVAL = 60_000;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback } from 'react';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { LocalStorageKey } from '@/constants/localStorage';
|
import { LocalStorageKey } from '@/constants/localStorage';
|
||||||
import { DEFAULT_APP_ENVIRONMENT, DydxNetwork, DydxV4Network } from '@/constants/networks';
|
import { DEFAULT_APP_ENVIRONMENT, DydxNetwork } from '@/constants/networks';
|
||||||
|
|
||||||
import { useAccounts, useLocalStorage } from '@/hooks';
|
import { useAccounts, useLocalStorage } from '@/hooks';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { createContext, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { Squid } from '@0xsquid/sdk';
|
||||||
|
|
||||||
|
import { DydxV4Network, isDydxV4Network } from '@/constants/networks';
|
||||||
|
|
||||||
|
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||||
|
|
||||||
|
const SQUID_BASE_URL: Record<DydxV4Network, string | undefined> = {
|
||||||
|
[DydxV4Network.V4Testnet2]: 'https://squid-api-git-feat-cosmos-maintestnet-0xsquid.vercel.app',
|
||||||
|
[DydxV4Network.V4Staging]: undefined,
|
||||||
|
[DydxV4Network.V4Local]: undefined,
|
||||||
|
[DydxV4Network.V4Mainnet]: 'https://api.0xsquid.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSquidContext = () => {
|
||||||
|
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||||
|
const [_, setInitialized] = useState(false);
|
||||||
|
|
||||||
|
const initializeClient = async () => {
|
||||||
|
setInitialized(false);
|
||||||
|
if (!squid) return;
|
||||||
|
await squid.init();
|
||||||
|
setInitialized(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const squid = useMemo(
|
||||||
|
() =>
|
||||||
|
isDydxV4Network(selectedNetwork)
|
||||||
|
? new Squid({ baseUrl: SQUID_BASE_URL[selectedNetwork as DydxV4Network] })
|
||||||
|
: undefined,
|
||||||
|
[selectedNetwork]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (squid) {
|
||||||
|
initializeClient();
|
||||||
|
}
|
||||||
|
}, [squid]);
|
||||||
|
|
||||||
|
return squid;
|
||||||
|
};
|
||||||
|
|
||||||
|
type SquidContextType = ReturnType<typeof useSquidContext>;
|
||||||
|
const SquidContext = createContext<SquidContextType>(undefined);
|
||||||
|
SquidContext.displayName = '0xSquid';
|
||||||
|
|
||||||
|
export const SquidProvider = ({ ...props }) => (
|
||||||
|
<SquidContext.Provider value={useSquidContext()} {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useSquid = () => useContext(SquidContext);
|
||||||
@@ -3,7 +3,7 @@ import { useDispatch } from 'react-redux';
|
|||||||
import type { Nullable } from '@dydxprotocol/abacus';
|
import type { Nullable } from '@dydxprotocol/abacus';
|
||||||
import Long from 'long';
|
import Long from 'long';
|
||||||
import type { IndexedTx } from '@cosmjs/stargate';
|
import type { IndexedTx } from '@cosmjs/stargate';
|
||||||
import { type Coin } from '@cosmjs/proto-signing';
|
import type { EncodeObject, Coin } from '@cosmjs/proto-signing';
|
||||||
import { Method } from '@cosmjs/tendermint-rpc';
|
import { Method } from '@cosmjs/tendermint-rpc';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
DYDX_DENOM,
|
DYDX_DENOM,
|
||||||
USDC_DENOM,
|
USDC_DENOM,
|
||||||
GAS_PRICE_DYDX_DENOM,
|
GAS_PRICE_DYDX_DENOM,
|
||||||
} from '@dydxprotocol/v4-client';
|
} from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
HumanReadableCancelOrderPayload,
|
HumanReadableCancelOrderPayload,
|
||||||
@@ -89,6 +89,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
simulateTransferNativeToken,
|
simulateTransferNativeToken,
|
||||||
placeOrderForSubaccount,
|
placeOrderForSubaccount,
|
||||||
cancelOrderForSubaccount,
|
cancelOrderForSubaccount,
|
||||||
|
sendSquidWithdrawFromSubaccount,
|
||||||
} = useMemo(
|
} = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
depositToSubaccount: async ({
|
depositToSubaccount: async ({
|
||||||
@@ -215,6 +216,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
type,
|
type,
|
||||||
side,
|
side,
|
||||||
price,
|
price,
|
||||||
|
triggerPrice,
|
||||||
size,
|
size,
|
||||||
clientId,
|
clientId,
|
||||||
timeInForce,
|
timeInForce,
|
||||||
@@ -228,6 +230,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
type: OrderType;
|
type: OrderType;
|
||||||
side: OrderSide;
|
side: OrderSide;
|
||||||
price: number;
|
price: number;
|
||||||
|
triggerPrice: Nullable<number>;
|
||||||
size: number;
|
size: number;
|
||||||
clientId: number;
|
clientId: number;
|
||||||
timeInForce: OrderTimeInForce;
|
timeInForce: OrderTimeInForce;
|
||||||
@@ -250,7 +253,8 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
goodTilTimeInSeconds,
|
goodTilTimeInSeconds,
|
||||||
execution,
|
execution,
|
||||||
postOnly,
|
postOnly,
|
||||||
reduceOnly
|
reduceOnly,
|
||||||
|
triggerPrice ?? undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
const endTimestamp = performance.now();
|
const endTimestamp = performance.now();
|
||||||
@@ -287,7 +291,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
clobPairId,
|
clobPairId,
|
||||||
goodTilBlock,
|
goodTilBlock,
|
||||||
goodTilBlockTime
|
goodTilBlockTime
|
||||||
)
|
);
|
||||||
|
|
||||||
const endTimestamp = performance.now();
|
const endTimestamp = performance.now();
|
||||||
|
|
||||||
@@ -298,6 +302,32 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sendSquidWithdrawFromSubaccount: async ({
|
||||||
|
subaccountClient,
|
||||||
|
amount,
|
||||||
|
payload,
|
||||||
|
}: {
|
||||||
|
subaccountClient: SubaccountClient;
|
||||||
|
amount: number;
|
||||||
|
payload: string;
|
||||||
|
}) => {
|
||||||
|
if (!compositeClient) throw new Error('client not initialized');
|
||||||
|
|
||||||
|
const transaction = JSON.parse(payload);
|
||||||
|
|
||||||
|
const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount);
|
||||||
|
const ibcMsg: EncodeObject = {
|
||||||
|
typeUrl: transaction.msgTypeUrl,
|
||||||
|
value: transaction.msg,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await compositeClient.send(
|
||||||
|
subaccountClient.wallet,
|
||||||
|
() => Promise.resolve([msg, ibcMsg]),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
[compositeClient]
|
[compositeClient]
|
||||||
);
|
);
|
||||||
@@ -395,6 +425,28 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
[subaccountClient, simulateWithdrawFromSubaccount, simulateTransferNativeToken]
|
[subaccountClient, simulateWithdrawFromSubaccount, simulateTransferNativeToken]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const simulateWithdraw = useCallback(
|
||||||
|
async (amount: number) => {
|
||||||
|
if (!subaccountClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await simulateWithdrawFromSubaccount({ subaccountClient, amount });
|
||||||
|
},
|
||||||
|
[subaccountClient, simulateWithdrawFromSubaccount]
|
||||||
|
);
|
||||||
|
|
||||||
|
const sendSquidWithdraw = useCallback(
|
||||||
|
async (amount: number, payload: string) => {
|
||||||
|
if (!subaccountClient) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await sendSquidWithdrawFromSubaccount({ subaccountClient, amount, payload });
|
||||||
|
},
|
||||||
|
[subaccountClient, sendSquidWithdrawFromSubaccount]
|
||||||
|
);
|
||||||
|
|
||||||
// ------ Faucet Methods ------ //
|
// ------ Faucet Methods ------ //
|
||||||
const requestFaucetFunds = useCallback(async () => {
|
const requestFaucetFunds = useCallback(async () => {
|
||||||
if (!dydxAddress) return;
|
if (!dydxAddress) return;
|
||||||
@@ -435,6 +487,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
type,
|
type,
|
||||||
side,
|
side,
|
||||||
price,
|
price,
|
||||||
|
triggerPrice,
|
||||||
size,
|
size,
|
||||||
clientId,
|
clientId,
|
||||||
timeInForce,
|
timeInForce,
|
||||||
@@ -461,6 +514,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
type: type as OrderType,
|
type: type as OrderType,
|
||||||
side: side as OrderSide,
|
side: side as OrderSide,
|
||||||
price,
|
price,
|
||||||
|
triggerPrice,
|
||||||
size,
|
size,
|
||||||
clientId,
|
clientId,
|
||||||
timeInForce: timeInForce as OrderTimeInForce,
|
timeInForce: timeInForce as OrderTimeInForce,
|
||||||
@@ -490,7 +544,10 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
track(AnalyticsEvent.TradePlaceOrder, { ...orderParams, isClosePosition });
|
track(AnalyticsEvent.TradePlaceOrder, {
|
||||||
|
...orderParams,
|
||||||
|
isClosePosition,
|
||||||
|
} as HumanReadablePlaceOrderPayload & { isClosePosition: boolean });
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorStringKey = error?.code && ORDER_ERROR_CODE_MAP[error.code as number];
|
const errorStringKey = error?.code && ORDER_ERROR_CODE_MAP[error.code as number];
|
||||||
@@ -581,6 +638,8 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
// Transfer Methods
|
// Transfer Methods
|
||||||
simulateTransfer,
|
simulateTransfer,
|
||||||
transfer,
|
transfer,
|
||||||
|
simulateWithdraw,
|
||||||
|
sendSquidWithdraw,
|
||||||
|
|
||||||
// Trading Methods
|
// Trading Methods
|
||||||
placeOrder,
|
placeOrder,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { OnboardingDialog } from '@/views/dialogs/OnboardingDialog';
|
|||||||
import { ReceiveDialog } from '@/views/dialogs/ReceiveDialog';
|
import { ReceiveDialog } from '@/views/dialogs/ReceiveDialog';
|
||||||
import { TradeDialog } from '@/views/dialogs/TradeDialog';
|
import { TradeDialog } from '@/views/dialogs/TradeDialog';
|
||||||
import { TransferDialog } from '@/views/dialogs/TransferDialog';
|
import { TransferDialog } from '@/views/dialogs/TransferDialog';
|
||||||
|
import { WithdrawDialog } from '@/views/dialogs/WithdrawDialog';
|
||||||
|
|
||||||
import { OrderDetailsDialog } from '@/views/dialogs/DetailsDialog/OrderDetailsDialog';
|
import { OrderDetailsDialog } from '@/views/dialogs/DetailsDialog/OrderDetailsDialog';
|
||||||
import { FillDetailsDialog } from '@/views/dialogs/DetailsDialog/FillDetailsDialog';
|
import { FillDetailsDialog } from '@/views/dialogs/DetailsDialog/FillDetailsDialog';
|
||||||
@@ -53,5 +54,6 @@ export const DialogManager = () => {
|
|||||||
[DialogTypes.Receive]: <ReceiveDialog {...modalProps} />,
|
[DialogTypes.Receive]: <ReceiveDialog {...modalProps} />,
|
||||||
[DialogTypes.Trade]: <TradeDialog {...modalProps} />,
|
[DialogTypes.Trade]: <TradeDialog {...modalProps} />,
|
||||||
[DialogTypes.Transfer]: <TransferDialog {...modalProps} />,
|
[DialogTypes.Transfer]: <TransferDialog {...modalProps} />,
|
||||||
|
[DialogTypes.Withdraw]: <WithdrawDialog {...modalProps} />,
|
||||||
}[type];
|
}[type];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||||
@@ -15,9 +16,8 @@ import { BellIcon, MarketsIcon, PlayIcon, PortfolioIcon, ProfileIcon, TradeIcon
|
|||||||
import { IconButton } from '@/components/IconButton';
|
import { IconButton } from '@/components/IconButton';
|
||||||
|
|
||||||
import { calculateCanAccountTrade } from '@/state/accountCalculators';
|
import { calculateCanAccountTrade } from '@/state/accountCalculators';
|
||||||
import { getCurrentMarketId } from '@/state/perpetualsSelectors';
|
|
||||||
import { openDialog } from '@/state/dialogs';
|
import { openDialog } from '@/state/dialogs';
|
||||||
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
import { getCurrentMarketId } from '@/state/perpetualsSelectors';
|
||||||
|
|
||||||
export const FooterMobile = () => {
|
export const FooterMobile = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { OrderSide } from '@dydxprotocol/v4-client';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AbacusOrderSide,
|
AbacusOrderSide,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
Network,
|
Network,
|
||||||
NetworkOptimizer,
|
NetworkOptimizer,
|
||||||
ValidatorConfig,
|
ValidatorConfig,
|
||||||
} from '@dydxprotocol/v4-client';
|
} from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type AbacusDydxChainQueriesProtocol,
|
type AbacusDydxChainQueriesProtocol,
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import type { AbacusFileSystemProtocol, FileLocation, Nullable } from '@/constants/abacus';
|
||||||
|
import { NETWORK_ENDPOINTS } from '@/constants/networks';
|
||||||
|
|
||||||
|
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> {
|
||||||
|
if (path === ENDPOINTS_PATH) {
|
||||||
|
return JSON.stringify(NETWORK_ENDPOINTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeTextFile(path: string, text: string): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AbacusFileSystem;
|
||||||
@@ -1,12 +1,25 @@
|
|||||||
import type { AbacusFormatterProtocol } from '@/constants/abacus';
|
import type { AbacusFormatterProtocol } from '@/constants/abacus';
|
||||||
|
|
||||||
|
import { type LocaleSeparators, MustBigNumber, getFractionDigits } from '../numbers';
|
||||||
|
|
||||||
class AbacusFormatter implements AbacusFormatterProtocol {
|
class AbacusFormatter implements AbacusFormatterProtocol {
|
||||||
|
localeSeparators: LocaleSeparators;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.localeSeparators = { group: ',', decimal: '.' };
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocaleSeparators({ group, decimal }: LocaleSeparators) {
|
||||||
|
this.localeSeparators = { group, decimal };
|
||||||
|
}
|
||||||
|
|
||||||
percent(value: number, digits: number): string {
|
percent(value: number, digits: number): string {
|
||||||
return value.toString();
|
return MustBigNumber(value).toFixed(digits);
|
||||||
}
|
}
|
||||||
|
|
||||||
dollar(value: number, tickSize: string): string {
|
dollar(value: number, tickSize: string): string {
|
||||||
return value.toString();
|
const tickSizeDecimals = getFractionDigits(tickSize);
|
||||||
|
return MustBigNumber(value).toFixed(tickSizeDecimals);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+40
-4
@@ -18,6 +18,7 @@ import {
|
|||||||
IOImplementations,
|
IOImplementations,
|
||||||
UIImplementations,
|
UIImplementations,
|
||||||
CoroutineTimer,
|
CoroutineTimer,
|
||||||
|
TransferType,
|
||||||
} from '@/constants/abacus';
|
} from '@/constants/abacus';
|
||||||
|
|
||||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||||
@@ -26,6 +27,7 @@ import { type DydxNetwork } from '@/constants/networks';
|
|||||||
import type { RootStore } from '@/state/_store';
|
import type { RootStore } from '@/state/_store';
|
||||||
|
|
||||||
import { getInputTradeOptions } from '@/state/inputsSelectors';
|
import { getInputTradeOptions } from '@/state/inputsSelectors';
|
||||||
|
import { getTransferInputs } from '@/state/inputsSelectors';
|
||||||
|
|
||||||
import AbacusRest from './rest';
|
import AbacusRest from './rest';
|
||||||
import AbacusWebsocket from './websocket';
|
import AbacusWebsocket from './websocket';
|
||||||
@@ -34,7 +36,8 @@ import AbacusStateNotifier from './stateNotification';
|
|||||||
import AbacusLocalizer from './localizer';
|
import AbacusLocalizer from './localizer';
|
||||||
import AbacusFormatter from './formatter';
|
import AbacusFormatter from './formatter';
|
||||||
import AbacusThreading from './threading';
|
import AbacusThreading from './threading';
|
||||||
|
import AbacusFileSystem, { ENDPOINTS_PATH } from './filesystem';
|
||||||
|
import { LocaleSeparators } from '../numbers';
|
||||||
class AbacusStateManager {
|
class AbacusStateManager {
|
||||||
private store: RootStore | undefined;
|
private store: RootStore | undefined;
|
||||||
private currentMarket: string | undefined;
|
private currentMarket: string | undefined;
|
||||||
@@ -42,12 +45,14 @@ class AbacusStateManager {
|
|||||||
stateManager: InstanceType<typeof AsyncAbacusStateManager>;
|
stateManager: InstanceType<typeof AsyncAbacusStateManager>;
|
||||||
websocket: AbacusWebsocket;
|
websocket: AbacusWebsocket;
|
||||||
stateNotifier: AbacusStateNotifier;
|
stateNotifier: AbacusStateNotifier;
|
||||||
|
abacusFormatter: AbacusFormatter;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.store = undefined;
|
this.store = undefined;
|
||||||
this.currentMarket = undefined;
|
this.currentMarket = undefined;
|
||||||
this.stateNotifier = new AbacusStateNotifier();
|
this.stateNotifier = new AbacusStateNotifier();
|
||||||
this.websocket = new AbacusWebsocket();
|
this.websocket = new AbacusWebsocket();
|
||||||
|
this.abacusFormatter = new AbacusFormatter();
|
||||||
|
|
||||||
const ioImplementations = new IOImplementations(
|
const ioImplementations = new IOImplementations(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -57,16 +62,18 @@ class AbacusStateManager {
|
|||||||
null,
|
null,
|
||||||
new AbacusThreading(),
|
new AbacusThreading(),
|
||||||
new CoroutineTimer(),
|
new CoroutineTimer(),
|
||||||
null
|
new AbacusFileSystem()
|
||||||
);
|
);
|
||||||
|
|
||||||
const uiImplementations = new UIImplementations(
|
const uiImplementations = new UIImplementations(
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
new AbacusLocalizer(),
|
new AbacusLocalizer(),
|
||||||
new AbacusFormatter()
|
this.abacusFormatter
|
||||||
);
|
);
|
||||||
|
|
||||||
this.stateManager = new AsyncAbacusStateManager(
|
this.stateManager = new AsyncAbacusStateManager(
|
||||||
|
import.meta.env.SHARED_RESOURCES_URI,
|
||||||
|
ENDPOINTS_PATH,
|
||||||
ioImplementations,
|
ioImplementations,
|
||||||
uiImplementations,
|
uiImplementations,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -87,6 +94,15 @@ class AbacusStateManager {
|
|||||||
disconnectAccount = () => {
|
disconnectAccount = () => {
|
||||||
this.stateManager.accountAddress = null;
|
this.stateManager.accountAddress = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
attemptDisconnectAccount = () => {
|
||||||
|
const state = this.store?.getState();
|
||||||
|
const { type: transferType }= (state && getTransferInputs(state)) || {};
|
||||||
|
// we don't want to disconnect the account if we switch network during the deposit form
|
||||||
|
if (transferType?.rawValue !== TransferType.deposit.rawValue) {
|
||||||
|
this.disconnectAccount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ------ Input Values ------ //
|
// ------ Input Values ------ //
|
||||||
clearTradeInputValues = ({ shouldResetSize }: { shouldResetSize?: boolean } = {}) => {
|
clearTradeInputValues = ({ shouldResetSize }: { shouldResetSize?: boolean } = {}) => {
|
||||||
@@ -144,6 +160,10 @@ class AbacusStateManager {
|
|||||||
this.stateManager.accountAddress = walletAddress;
|
this.stateManager.accountAddress = walletAddress;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setTransfersSourceAddress = (evmAddress: string) => {
|
||||||
|
this.stateManager.sourceAddress = evmAddress;
|
||||||
|
};
|
||||||
|
|
||||||
setSubaccountNumber = (subaccountNumber: number) =>
|
setSubaccountNumber = (subaccountNumber: number) =>
|
||||||
(this.stateManager.subaccountNumber = subaccountNumber);
|
(this.stateManager.subaccountNumber = subaccountNumber);
|
||||||
|
|
||||||
@@ -179,6 +199,22 @@ class AbacusStateManager {
|
|||||||
this.stateManager.closePosition(value, field);
|
this.stateManager.closePosition(value, field);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setLocaleSeparators = ({ group, decimal }: LocaleSeparators) => {
|
||||||
|
this.abacusFormatter.setLocaleSeparators({ group, decimal });
|
||||||
|
};
|
||||||
|
|
||||||
|
setTransferStatus = ({
|
||||||
|
hash,
|
||||||
|
fromChainId,
|
||||||
|
toChainId,
|
||||||
|
}: {
|
||||||
|
hash: string;
|
||||||
|
fromChainId?: string;
|
||||||
|
toChainId?: string;
|
||||||
|
}) => {
|
||||||
|
this.stateManager.transferStatus(hash, fromChainId, toChainId);
|
||||||
|
};
|
||||||
|
|
||||||
// ------ Utils ------ //
|
// ------ Utils ------ //
|
||||||
placeOrderPayload = (): Nullable<HumanReadablePlaceOrderPayload> =>
|
placeOrderPayload = (): Nullable<HumanReadablePlaceOrderPayload> =>
|
||||||
this.stateManager.placeOrderPayload();
|
this.stateManager.placeOrderPayload();
|
||||||
@@ -203,7 +239,7 @@ class AbacusStateManager {
|
|||||||
};
|
};
|
||||||
|
|
||||||
sendSocketRequest = (requestText: string) => {
|
sendSocketRequest = (requestText: string) => {
|
||||||
this.websocket.sendSocketRequest(requestText);
|
this.websocket.send(requestText);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
import { kollections } from '@dydxprotocol/abacus';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
AbacusApiState,
|
AbacusApiState,
|
||||||
|
AbacusNotification,
|
||||||
AbacusStateNotificationProtocol,
|
AbacusStateNotificationProtocol,
|
||||||
Asset,
|
Asset,
|
||||||
Nullable,
|
Nullable,
|
||||||
@@ -19,6 +22,7 @@ import {
|
|||||||
setFundingPayments,
|
setFundingPayments,
|
||||||
setHistoricalPnl,
|
setHistoricalPnl,
|
||||||
setSubaccount,
|
setSubaccount,
|
||||||
|
setTransfers,
|
||||||
setWallet,
|
setWallet,
|
||||||
} from '@/state/account';
|
} from '@/state/account';
|
||||||
|
|
||||||
@@ -35,6 +39,14 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
|
|||||||
this.store = undefined;
|
this.store = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
environmentsChanged(): void {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationsChanged(notifications: kollections.List<AbacusNotification>): void {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
stateChanged(
|
stateChanged(
|
||||||
updatedState: Nullable<PerpetualState>,
|
updatedState: Nullable<PerpetualState>,
|
||||||
incomingChanges: Nullable<PerpetualStateChanges>
|
incomingChanges: Nullable<PerpetualStateChanges>
|
||||||
@@ -102,6 +114,11 @@ class AbacusStateNotifier implements AbacusStateNotificationProtocol {
|
|||||||
dispatch(setFundingPayments(fundingPayments));
|
dispatch(setFundingPayments(fundingPayments));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changes.has(Changes.transfers)) {
|
||||||
|
const transfers = updatedState.subaccountTransfers(subaccountId)?.toArray() || [];
|
||||||
|
dispatch(setTransfers(transfers));
|
||||||
|
}
|
||||||
|
|
||||||
if (changes.has(Changes.historicalPnl)) {
|
if (changes.has(Changes.historicalPnl)) {
|
||||||
const historicalPnl =
|
const historicalPnl =
|
||||||
updatedState.subaccountHistoricalPnl(subaccountId)?.toArray() || [];
|
updatedState.subaccountHistoricalPnl(subaccountId)?.toArray() || [];
|
||||||
|
|||||||
@@ -46,10 +46,6 @@ class AbacusWebsocket implements Omit<AbacusWebsocketProtocol, '__doNotUseOrImpl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendSocketRequest = (requestText: string) => {
|
|
||||||
this.socket?.send(requestText);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleCandlesSubscription = ({
|
handleCandlesSubscription = ({
|
||||||
channelId,
|
channelId,
|
||||||
subscribe,
|
subscribe,
|
||||||
|
|||||||
+4
-3
@@ -1,6 +1,7 @@
|
|||||||
import { BigNumber } from 'bignumber.js';
|
import { BigNumber } from 'bignumber.js';
|
||||||
|
|
||||||
export type BigNumberish = BigNumber | string | number;
|
export type BigNumberish = BigNumber | string | number;
|
||||||
|
export type LocaleSeparators = { group?: string; decimal?: string };
|
||||||
|
|
||||||
export const BIG_NUMBERS = {
|
export const BIG_NUMBERS = {
|
||||||
ZERO: new BigNumber(0),
|
ZERO: new BigNumber(0),
|
||||||
@@ -56,12 +57,12 @@ export const shorternNumberForDisplay = (num?: number) =>
|
|||||||
* @returns separator for the given locale and separator type
|
* @returns separator for the given locale and separator type
|
||||||
*/
|
*/
|
||||||
export const getSeparator = ({
|
export const getSeparator = ({
|
||||||
locale = navigator.language || 'en-US',
|
browserLanguage = navigator.language || 'en-US',
|
||||||
separatorType,
|
separatorType,
|
||||||
}: {
|
}: {
|
||||||
locale?: string;
|
browserLanguage?: string;
|
||||||
separatorType: Intl.NumberFormatPartTypes;
|
separatorType: Intl.NumberFormatPartTypes;
|
||||||
}) =>
|
}) =>
|
||||||
Intl.NumberFormat(locale)
|
Intl.NumberFormat(browserLanguage)
|
||||||
.formatToParts(1000.1)
|
.formatToParts(1000.1)
|
||||||
.find?.((part) => part.type === separatorType)?.value;
|
.find?.((part) => part.type === separatorType)?.value;
|
||||||
|
|||||||
@@ -1,140 +0,0 @@
|
|||||||
import { RouteResponse, Squid } from '@0xsquid/sdk';
|
|
||||||
import type { GetRoute, ChainData, RouteData, TokenData } from '@0xsquid/sdk';
|
|
||||||
|
|
||||||
import { DEFAULT_APP_ENVIRONMENT, DydxV4Network } from '@/constants/networks';
|
|
||||||
|
|
||||||
import { log } from './telemetry';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @todo dYdX chain will eventually be added to squidSDK as an option for toChain and
|
|
||||||
* USDC from Noble chain will also be a possible toToken in the near future. We will
|
|
||||||
* need to eventually change these constants.
|
|
||||||
* */
|
|
||||||
const SQUID_TESTNET_ROUTE_DEFAULTS = {
|
|
||||||
toChain: 'osmo-test-4', // Osmosis Testnet
|
|
||||||
toToken: 'uausdc', // aUSDC
|
|
||||||
enableForecall: true, // instant execution service, defaults to true
|
|
||||||
quoteOnly: false, // optional, defaults to false
|
|
||||||
};
|
|
||||||
|
|
||||||
// This config is for dev only! Used for testing General Message Passing (route provided by 0xSquid)
|
|
||||||
const SQUID_TESTNET_AVAX_TO_AXELAR_WITH_GMP = {
|
|
||||||
toChain: 'axelar-testnet-lisbon-3',
|
|
||||||
toToken: 'uosmo',
|
|
||||||
enableForecall: false,
|
|
||||||
quoteOnly: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const SQUID_MAINNET_ROUTE_DEFAULTS = {
|
|
||||||
toChain: 'osmosis-1', // Osmosis Testnet
|
|
||||||
toToken: 'uusdc', // aUSDC
|
|
||||||
enableForecall: true, // instant execution service, defaults to true
|
|
||||||
quoteOnly: false, // optional, defaults to false
|
|
||||||
};
|
|
||||||
|
|
||||||
class SquidFactory {
|
|
||||||
private client: Squid;
|
|
||||||
private isInitialized: boolean;
|
|
||||||
|
|
||||||
constructor(baseUrl: string) {
|
|
||||||
this.client = new Squid({ baseUrl });
|
|
||||||
this.isInitialized = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
if (this.isInitialized) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await this.client.init();
|
|
||||||
this.isInitialized = true;
|
|
||||||
} catch (error) {
|
|
||||||
log('SquidFactory/init', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getClient = () => this.client;
|
|
||||||
}
|
|
||||||
|
|
||||||
const squidClientTestnet = new SquidFactory('https://testnet.api.0xsquid.com'); // new SquidFactory('https://squid-2gwsw7ij1-0xsquid.vercel.app');
|
|
||||||
const squidClientMainnet = new SquidFactory('https://api.0xsquid.com');
|
|
||||||
|
|
||||||
const SQUID_CLIENT_BY_NETWORK: Record<DydxV4Network, SquidFactory> = {
|
|
||||||
[DydxV4Network.V4Local]: squidClientTestnet,
|
|
||||||
[DydxV4Network.V4Mainnet]: squidClientMainnet,
|
|
||||||
[DydxV4Network.V4Staging]: squidClientTestnet,
|
|
||||||
[DydxV4Network.V4Testnet2]: squidClientTestnet,
|
|
||||||
};
|
|
||||||
|
|
||||||
class SquidRouter {
|
|
||||||
private client: Squid;
|
|
||||||
private network: DydxV4Network;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.network = DEFAULT_APP_ENVIRONMENT;
|
|
||||||
this.client = SQUID_CLIENT_BY_NETWORK[this.network].getClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description the default destination chain/token and other hardcoded options
|
|
||||||
*/
|
|
||||||
get SQUID_ROUTE_DEFAULTS() {
|
|
||||||
return {
|
|
||||||
[DydxV4Network.V4Local]: SQUID_TESTNET_ROUTE_DEFAULTS,
|
|
||||||
[DydxV4Network.V4Staging]: SQUID_TESTNET_ROUTE_DEFAULTS,
|
|
||||||
[DydxV4Network.V4Testnet2]: SQUID_TESTNET_ROUTE_DEFAULTS,
|
|
||||||
[DydxV4Network.V4Mainnet]: SQUID_MAINNET_ROUTE_DEFAULTS,
|
|
||||||
}[this.network];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description updates the client to the correct 0xSquid environment based on which dYdX v4 network is selected.
|
|
||||||
* @param network dYdX v4 network that is currently selected
|
|
||||||
*/
|
|
||||||
switchNetwork = (network: DydxV4Network) => {
|
|
||||||
this.client = SQUID_CLIENT_BY_NETWORK[network].getClient();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description initializes the Squid client if it was not previously initialized (API request)
|
|
||||||
*/
|
|
||||||
initializeClient = () => SQUID_CLIENT_BY_NETWORK[this.network].init();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns list of chains supported by Axelar
|
|
||||||
*/
|
|
||||||
getChains = (): ChainData[] => this.client.chains;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns list of tokens supported by Axelar
|
|
||||||
*/
|
|
||||||
getTokens = (): TokenData[] => this.client.tokens;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param getRouteParams
|
|
||||||
* @returns 0xSquid RouteData
|
|
||||||
*/
|
|
||||||
getRoute = async (getRouteParams: GetRoute): Promise<RouteResponse> => {
|
|
||||||
const resp = await this.client.getRoute(getRouteParams);
|
|
||||||
return resp;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @params {
|
|
||||||
* @route the 0xSquid RouteData
|
|
||||||
* @signer the EVM/Cosmos Signer used to sign the Axelar bridge
|
|
||||||
* }
|
|
||||||
* @returns transaction information after the route has been executed
|
|
||||||
*/
|
|
||||||
executeRoute = async ({ route, signer }: { route: RouteData; signer: any }) => {
|
|
||||||
const tx = await this.client.executeRoute({
|
|
||||||
signer,
|
|
||||||
route,
|
|
||||||
});
|
|
||||||
|
|
||||||
return await tx.wait();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const squidRouter = new SquidRouter();
|
|
||||||
|
|
||||||
export default squidRouter;
|
|
||||||
+19
-4
@@ -1,5 +1,5 @@
|
|||||||
import { type Location, matchPath } from 'react-router-dom';
|
import { type Location, matchPath } from 'react-router-dom';
|
||||||
import { OrderSide } from '@dydxprotocol/v4-client';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import { StringGetterFunction } from '@/constants/localization';
|
import { StringGetterFunction } from '@/constants/localization';
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from '@/constants/abacus';
|
} from '@/constants/abacus';
|
||||||
|
|
||||||
import { AlertType } from '@/constants/alerts';
|
import { AlertType } from '@/constants/alerts';
|
||||||
import { PERCENT_DECIMALS } from '@/constants/numbers';
|
import { PERCENT_DECIMALS, USD_DECIMALS } from '@/constants/numbers';
|
||||||
import { TRADE_ROUTE } from '@/constants/routes';
|
import { TRADE_ROUTE } from '@/constants/routes';
|
||||||
import { PositionSide, TradeTypes } from '@/constants/trade';
|
import { PositionSide, TradeTypes } from '@/constants/trade';
|
||||||
|
|
||||||
@@ -65,9 +65,13 @@ export const hasPositionSideChanged = ({
|
|||||||
const formatErrorParam = ({
|
const formatErrorParam = ({
|
||||||
value,
|
value,
|
||||||
format,
|
format,
|
||||||
|
stepSizeDecimals,
|
||||||
|
tickSizeDecimals,
|
||||||
}: {
|
}: {
|
||||||
value: Nullable<string>;
|
value: Nullable<string>;
|
||||||
format?: Nullable<string>;
|
format?: Nullable<string>;
|
||||||
|
stepSizeDecimals: Nullable<number>;
|
||||||
|
tickSizeDecimals: Nullable<number>;
|
||||||
}) => {
|
}) => {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
case 'percent': {
|
case 'percent': {
|
||||||
@@ -76,7 +80,11 @@ const formatErrorParam = ({
|
|||||||
}
|
}
|
||||||
case 'size': {
|
case 'size': {
|
||||||
const sizeBN = MustBigNumber(value);
|
const sizeBN = MustBigNumber(value);
|
||||||
return sizeBN.toFixed(0);
|
return sizeBN.toFixed(stepSizeDecimals ?? 0);
|
||||||
|
}
|
||||||
|
case 'price': {
|
||||||
|
const dollarBN = MustBigNumber(value);
|
||||||
|
return `$${dollarBN.toFixed(tickSizeDecimals ?? USD_DECIMALS)}`;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return value || '';
|
return value || '';
|
||||||
@@ -90,9 +98,13 @@ const formatErrorParam = ({
|
|||||||
export const getTradeInputAlert = ({
|
export const getTradeInputAlert = ({
|
||||||
abacusInputErrors,
|
abacusInputErrors,
|
||||||
stringGetter,
|
stringGetter,
|
||||||
|
stepSizeDecimals,
|
||||||
|
tickSizeDecimals,
|
||||||
}: {
|
}: {
|
||||||
abacusInputErrors: ValidationError[];
|
abacusInputErrors: ValidationError[];
|
||||||
stringGetter: StringGetterFunction;
|
stringGetter: StringGetterFunction;
|
||||||
|
stepSizeDecimals: Nullable<number>;
|
||||||
|
tickSizeDecimals: Nullable<number>;
|
||||||
}) => {
|
}) => {
|
||||||
const inputAlerts = abacusInputErrors.map(({ action: errorAction, resources, type }) => {
|
const inputAlerts = abacusInputErrors.map(({ action: errorAction, resources, type }) => {
|
||||||
const { action, text } = resources || {};
|
const { action, text } = resources || {};
|
||||||
@@ -104,7 +116,10 @@ export const getTradeInputAlert = ({
|
|||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
stringParams
|
stringParams
|
||||||
.toArray()
|
.toArray()
|
||||||
.map(({ key, value, format }) => [key, formatErrorParam({ value, format })])
|
.map(({ key, value, format }) => [
|
||||||
|
key,
|
||||||
|
formatErrorParam({ value, format, stepSizeDecimals, tickSizeDecimals }),
|
||||||
|
])
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
+15
-12
@@ -162,7 +162,13 @@ const createWalletConnect2ConnectorWithId = (walletconnect2Id: string) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Custom connector from wallet selection
|
// Custom connector from wallet selection
|
||||||
import { type WalletConnection, WalletConnectionType, type WalletType, walletConnectionTypes, wallets } from '@/constants/wallets';
|
import {
|
||||||
|
type WalletConnection,
|
||||||
|
WalletConnectionType,
|
||||||
|
type WalletType,
|
||||||
|
walletConnectionTypes,
|
||||||
|
wallets,
|
||||||
|
} from '@/constants/wallets';
|
||||||
|
|
||||||
export const resolveWagmiConnector = ({
|
export const resolveWagmiConnector = ({
|
||||||
walletType,
|
walletType,
|
||||||
@@ -174,15 +180,12 @@ export const resolveWagmiConnector = ({
|
|||||||
const walletConfig = wallets[walletType];
|
const walletConfig = wallets[walletType];
|
||||||
const walletConnectionConfig = walletConnectionTypes[walletConnection.type];
|
const walletConnectionConfig = walletConnectionTypes[walletConnection.type];
|
||||||
|
|
||||||
return (
|
return walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider
|
||||||
walletConnection.type === WalletConnectionType.InjectedEip1193 && walletConnection.provider ?
|
? createInjectedConnectorWithProvider(walletConnection.provider)
|
||||||
createInjectedConnectorWithProvider(walletConnection.provider)
|
: walletConnection.type === WalletConnectionType.WalletConnect1 &&
|
||||||
: walletConnection.type === WalletConnectionType.WalletConnect1 && walletConfig.walletconnect1Name ?
|
walletConfig.walletconnect1Name
|
||||||
createWalletConnect1ConnectorWithName(walletConfig.walletconnect1Name)
|
? createWalletConnect1ConnectorWithName(walletConfig.walletconnect1Name)
|
||||||
: walletConnection.type === WalletConnectionType.WalletConnect2 && walletConfig.walletconnect2Id ?
|
: walletConnection.type === WalletConnectionType.WalletConnect2 && walletConfig.walletconnect2Id
|
||||||
createWalletConnect2ConnectorWithId(walletConfig.walletconnect2Id)
|
? createWalletConnect2ConnectorWithId(walletConfig.walletconnect2Id)
|
||||||
: connectors.find(
|
: connectors.find(({ id }: { id: string }) => id === walletConnectionConfig.wagmiConnectorId);
|
||||||
({ id }: { id: string }) => id === walletConnectionConfig.wagmiConnectorId
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -182,6 +182,7 @@
|
|||||||
"RECEIVE": "Receive",
|
"RECEIVE": "Receive",
|
||||||
"RECENT": "Recent",
|
"RECENT": "Recent",
|
||||||
"RECENT_TRADES_SHORT": "Trades",
|
"RECENT_TRADES_SHORT": "Trades",
|
||||||
|
"RECIPIENT": "Recipient",
|
||||||
"REFERRAL_CODE": "Referral Code",
|
"REFERRAL_CODE": "Referral Code",
|
||||||
"REFERRALS": "Referrals",
|
"REFERRALS": "Referrals",
|
||||||
"REFERRER_PERCENT_OFF": "{DISCOUNT}% off",
|
"REFERRER_PERCENT_OFF": "{DISCOUNT}% off",
|
||||||
@@ -193,6 +194,7 @@
|
|||||||
"SELECT_NETWORK": "Select Network",
|
"SELECT_NETWORK": "Select Network",
|
||||||
"SELL": "Sell",
|
"SELL": "Sell",
|
||||||
"SEND": "Send",
|
"SEND": "Send",
|
||||||
|
"SENDER": "Sender",
|
||||||
"SHARE": "Share",
|
"SHARE": "Share",
|
||||||
"SHORT_POSITION_SHORT": "Short",
|
"SHORT_POSITION_SHORT": "Short",
|
||||||
"SIDE": "Side",
|
"SIDE": "Side",
|
||||||
@@ -477,6 +479,7 @@
|
|||||||
"LEAVING_WEBSITE": "Leaving website"
|
"LEAVING_WEBSITE": "Leaving website"
|
||||||
},
|
},
|
||||||
"DEPOSIT_MODAL": {
|
"DEPOSIT_MODAL": {
|
||||||
|
"BRIDGE_FEE": "Bridge Fee",
|
||||||
"ENABLE_FOR_THIS_DEPOSIT": "Enable for this deposit",
|
"ENABLE_FOR_THIS_DEPOSIT": "Enable for this deposit",
|
||||||
"ENABLE_SYMBOL_ON_DYDX": "Enable {SYMBOL} on dYdX",
|
"ENABLE_SYMBOL_ON_DYDX": "Enable {SYMBOL} on dYdX",
|
||||||
"ENABLE_SYMBOL": "Enable {SYMBOL}",
|
"ENABLE_SYMBOL": "Enable {SYMBOL}",
|
||||||
@@ -942,7 +945,8 @@
|
|||||||
"SLOW_WITHDRAW_PENDING_DESCRIPTION": "You have {AMOUNT_ELEMENT} in pending slow withdraws. You will receive a notification when your funds have been confirmed on Layer 2.",
|
"SLOW_WITHDRAW_PENDING_DESCRIPTION": "You have {AMOUNT_ELEMENT} in pending slow withdraws. You will receive a notification when your funds have been confirmed on Layer 2.",
|
||||||
"SLOW_WITHDRAW_PENDING": "Slow withdraw(s) pending",
|
"SLOW_WITHDRAW_PENDING": "Slow withdraw(s) pending",
|
||||||
"SUSPICIOUS_TRADE": "Suspicious activity",
|
"SUSPICIOUS_TRADE": "Suspicious activity",
|
||||||
"SUSPICIOUS_TRADE_BODY": "We have noticed suspicious trading activity related to your account on dYdX, including potential wash trading activity. We will be reviewing your activity on an ongoing basis. If we notice similar activity in the future, then you will be permanently blocked from performing transfers within the protocol and from placing orders other than market orders that reduce your positions."
|
"SUSPICIOUS_TRADE_BODY": "We have noticed suspicious trading activity related to your account on dYdX, including potential wash trading activity. We will be reviewing your activity on an ongoing basis. If we notice similar activity in the future, then you will be permanently blocked from performing transfers within the protocol and from placing orders other than market orders that reduce your positions.",
|
||||||
|
"WITHDRAW_IN_PROGRESS": "Withdrawal(s) in progress"
|
||||||
},
|
},
|
||||||
"EMAIL_NOTIFICATIONS": {
|
"EMAIL_NOTIFICATIONS": {
|
||||||
"ACCOUNT_DESCRIPTION": "Deposits, Withdrawals, Account Updates",
|
"ACCOUNT_DESCRIPTION": "Deposits, Withdrawals, Account Updates",
|
||||||
@@ -1205,5 +1209,20 @@
|
|||||||
"NODE_DOWN": "Node cannot be reached.",
|
"NODE_DOWN": "Node cannot be reached.",
|
||||||
"NODE_HALTED": "Node has halted on block {BLOCK_NUMBER}.",
|
"NODE_HALTED": "Node has halted on block {BLOCK_NUMBER}.",
|
||||||
"UNKNOWN_API_ERROR": "Unknown API error"
|
"UNKNOWN_API_ERROR": "Unknown API error"
|
||||||
|
},
|
||||||
|
"CHAIN": {
|
||||||
|
"V3_MAINNET": "V3 Mainnet",
|
||||||
|
"V3_GOERLI": "V3 Staging",
|
||||||
|
"V4_DEVNET": "V4 Dev 1",
|
||||||
|
"V4_DEVNET_2": "V4 Dev 2",
|
||||||
|
"V4_DEVNET_3": "V4 Dev 3",
|
||||||
|
"V4_DEVNET_4": "V4 Dev 4",
|
||||||
|
"V4_DEVNET_5": "V4 Dev 5",
|
||||||
|
"V4_STAGING": "V4 Staging",
|
||||||
|
"V4_TESTNET2": "V4 Testnet",
|
||||||
|
"V4_TESTNET2_DSRV": "V4 Testnet (DSRV)",
|
||||||
|
"V4_TESTNET2_DYDX": "V4 Testnet (dYdX)",
|
||||||
|
"V4_TESTNET2_NODEFLEET": "V4 Testnet (nodefleet)",
|
||||||
|
"V4_TESTNET2_IMPERATOR": "V4 Testnet (Imperator)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,10 @@
|
|||||||
"MIN_SLOW_WITHDRAW_AMOUNT": "Slow withdraw amount must be greater than or equal to 10, or your entire account balance.",
|
"MIN_SLOW_WITHDRAW_AMOUNT": "Slow withdraw amount must be greater than or equal to 10, or your entire account balance.",
|
||||||
"RESTRICTED_OPEN_POSITIONS_WITHDRAW": "Your account is restricted. You must close all your positions before you can withdraw.",
|
"RESTRICTED_OPEN_POSITIONS_WITHDRAW": "Your account is restricted. You must close all your positions before you can withdraw.",
|
||||||
"WITHDRAW_MORE_THAN_FREE_DUE_TO_FEE": "You cannot withdraw more than your free collateral. Make sure you account for the fast withdraw fee when entering an amount.",
|
"WITHDRAW_MORE_THAN_FREE_DUE_TO_FEE": "You cannot withdraw more than your free collateral. Make sure you account for the fast withdraw fee when entering an amount.",
|
||||||
"WITHDRAW_MORE_THAN_FREE": "You cannot withdraw more than your free collateral."
|
"WITHDRAW_MORE_THAN_FREE": "You cannot withdraw more than your free collateral.",
|
||||||
|
"WITHDRAW_MUST_SPECIFY_ADDRESS": "Please enter a destination address.",
|
||||||
|
"WITHDRAW_MUST_SPECIFY_ASSET": "Please select an asset to withdraw to.",
|
||||||
|
"WITHDRAW_MUST_SPECIFY_CHAIN": "Please select a source chain to withdraw to."
|
||||||
},
|
},
|
||||||
"TRANSFER_MODAL": {
|
"TRANSFER_MODAL": {
|
||||||
"TRANSFER_INVALID_ETH_ADDRESS": "We could not find an account associated with this wallet address.",
|
"TRANSFER_INVALID_ETH_ADDRESS": "We could not find an account associated with this wallet address.",
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
import './polyfills';
|
import './polyfills';
|
||||||
import { Fragment, StrictMode } from 'react';
|
import { StrictMode } from 'react';
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from 'react-dom/client';
|
||||||
import { HashRouter } from 'react-router-dom';
|
import { HashRouter } from 'react-router-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
|||||||
@@ -28,13 +28,14 @@ export const History = () => {
|
|||||||
label: <h3>{stringGetter({ key: STRING_KEYS.TRADES })}</h3>,
|
label: <h3>{stringGetter({ key: STRING_KEYS.TRADES })}</h3>,
|
||||||
href: HistoryRoute.Trades,
|
href: HistoryRoute.Trades,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: HistoryRoute.Transfers,
|
||||||
|
label: <h3>{stringGetter({ key: STRING_KEYS.TRANSFERS })}</h3>,
|
||||||
|
href: HistoryRoute.Transfers,
|
||||||
|
tag: 'USDC',
|
||||||
|
},
|
||||||
// TODO - TRCL-1693 -
|
// TODO - TRCL-1693 -
|
||||||
// {
|
// {
|
||||||
// value: HistoryRoute.Transfers,
|
|
||||||
// label: <h3>{stringGetter({ key: STRING_KEYS.TRANSFERS })}</h3>,
|
|
||||||
// href: HistoryRoute.Transfers,
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// value: HistoryRoute.Payments,
|
// value: HistoryRoute.Payments,
|
||||||
// label: <h3>{stringGetter({ key: STRING_KEYS.PAYMENTS })}</h3>,
|
// label: <h3>{stringGetter({ key: STRING_KEYS.PAYMENTS })}</h3>,
|
||||||
// href: HistoryRoute.Payments,
|
// href: HistoryRoute.Payments,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useBreakpoints, useDocumentTitle, useStringGetter } from '@/hooks';
|
|||||||
|
|
||||||
import { FillsTable, FillsTableColumnKey } from '@/views/tables/FillsTable';
|
import { FillsTable, FillsTableColumnKey } from '@/views/tables/FillsTable';
|
||||||
import { FundingPaymentsTable } from '@/views/tables/FundingPaymentsTable';
|
import { FundingPaymentsTable } from '@/views/tables/FundingPaymentsTable';
|
||||||
|
import { TransferHistoryTable } from '@/views/tables/TransferHistoryTable';
|
||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
import { NavigationMenu } from '@/components/NavigationMenu';
|
import { NavigationMenu } from '@/components/NavigationMenu';
|
||||||
import { WithSidebar } from '@/components/WithSidebar';
|
import { WithSidebar } from '@/components/WithSidebar';
|
||||||
@@ -61,7 +62,10 @@ export default () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Route path={HistoryRoute.Transfers} element={<div />} />
|
<Route
|
||||||
|
path={HistoryRoute.Transfers}
|
||||||
|
element={<TransferHistoryTable withOuterBorder={isNotTablet} />}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path={HistoryRoute.Payments}
|
path={HistoryRoute.Payments}
|
||||||
element={<FundingPaymentsTable withOuterBorder={isNotTablet} />}
|
element={<FundingPaymentsTable withOuterBorder={isNotTablet} />}
|
||||||
@@ -118,8 +122,6 @@ export default () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// TODO(aforaleka) Add back subitems when there are clearer designs
|
|
||||||
// or when transfers and payments are ready
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ export const PortfolioNavMobile = () => {
|
|||||||
label: stringGetter({ key: STRING_KEYS.TRADES }),
|
label: stringGetter({ key: STRING_KEYS.TRADES }),
|
||||||
description: stringGetter({ key: STRING_KEYS.TRADES_DESCRIPTION }),
|
description: stringGetter({ key: STRING_KEYS.TRADES_DESCRIPTION }),
|
||||||
},
|
},
|
||||||
// TODO: TRCL-1693 - re-enable when Payments and Transfers are ready
|
{
|
||||||
// {
|
value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Transfers}`,
|
||||||
// value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Transfers}`,
|
label: stringGetter({ key: STRING_KEYS.TRANSFERS }),
|
||||||
// label: stringGetter({ key: STRING_KEYS.TRANSFERS }),
|
description: stringGetter({ key: STRING_KEYS.TRANSFERS_DESCRIPTION }),
|
||||||
// description: stringGetter({ key: STRING_KEYS.TRANSFERS_DESCRIPTION }),
|
},
|
||||||
// },
|
// TODO: TRCL-1693 - re-enable when Payments are ready
|
||||||
// {
|
// {
|
||||||
// value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Payments}`,
|
// value: `${AppRoute.Portfolio}/${PortfolioRoute.History}/${HistoryRoute.Payments}`,
|
||||||
// label: stringGetter({ key: STRING_KEYS.PAYMENTS }),
|
// label: stringGetter({ key: STRING_KEYS.PAYMENTS }),
|
||||||
|
|||||||
@@ -36,8 +36,8 @@ export const DYDXBalancePanel = () => {
|
|||||||
slotHeader={
|
slotHeader={
|
||||||
<Styled.Header>
|
<Styled.Header>
|
||||||
<Styled.Title>
|
<Styled.Title>
|
||||||
<AssetIcon symbol="DYDX" />
|
{/* <AssetIcon symbol="DYDX" /> */}
|
||||||
DYDX
|
Dv4TNT
|
||||||
</Styled.Title>
|
</Styled.Title>
|
||||||
<Styled.ReceiveAndTransferButtons>
|
<Styled.ReceiveAndTransferButtons>
|
||||||
{!canAccountTrade ? (
|
{!canAccountTrade ? (
|
||||||
@@ -106,7 +106,7 @@ export const DYDXBalancePanel = () => {
|
|||||||
{
|
{
|
||||||
key: 'totalBalance',
|
key: 'totalBalance',
|
||||||
label: 'Total balance',
|
label: 'Total balance',
|
||||||
value: <Output type={OutputType.Asset} value={nativeTokenBalance} tag="DYDX" />,
|
value: <Output type={OutputType.Asset} value={nativeTokenBalance} tag="Dv4TNT" />,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import styled, { css, type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,19 @@
|
|||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
import { VerticalSeparator } from '@/components/Separator';
|
import { VerticalSeparator } from '@/components/Separator';
|
||||||
|
|
||||||
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
|
||||||
|
|
||||||
import { getCurrentMarketId } from '@/state/perpetualsSelectors';
|
|
||||||
|
|
||||||
import { MarketsDropdown } from '@/views/MarketsDropdown';
|
import { MarketsDropdown } from '@/views/MarketsDropdown';
|
||||||
import { MarketStatsDetails } from '@/views/MarketStatsDetails';
|
import { MarketStatsDetails } from '@/views/MarketStatsDetails';
|
||||||
|
|
||||||
|
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
||||||
|
import { getCurrentMarketId } from '@/state/perpetualsSelectors';
|
||||||
|
|
||||||
export const MarketSelectorAndStats = ({ className }: { className?: string }) => {
|
export const MarketSelectorAndStats = ({ className }: { className?: string }) => {
|
||||||
const { symbol = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
|
const { symbol = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
|
||||||
const currentMarketId = useSelector(getCurrentMarketId) ?? DEFAULT_MARKETID;
|
const currentMarketId = useSelector(getCurrentMarketId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Container className={className}>
|
<Styled.Container className={className}>
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useRef, useState } from 'react';
|
||||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useSelector } from 'react-redux';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
import { TradeLayouts } from '@/constants/layout';
|
import { TradeLayouts } from '@/constants/layout';
|
||||||
|
|
||||||
import { useBreakpoints, usePageTitlePriceUpdates } from '@/hooks';
|
import { useBreakpoints, useCurrentMarketId, usePageTitlePriceUpdates } from '@/hooks';
|
||||||
|
|
||||||
import { setTradeLocation } from '@/state/navigation';
|
|
||||||
|
|
||||||
import { calculateCanAccountTrade } from '@/state/accountCalculators';
|
import { calculateCanAccountTrade } from '@/state/accountCalculators';
|
||||||
import { getSelectedTradeLayout } from '@/state/layoutSelectors';
|
import { getSelectedTradeLayout } from '@/state/layoutSelectors';
|
||||||
@@ -32,18 +29,12 @@ import { TradeBox } from '@/views/TradeBox';
|
|||||||
const TradePage = () => {
|
const TradePage = () => {
|
||||||
const tradePageRef = useRef<HTMLDivElement>(null);
|
const tradePageRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const location = useLocation();
|
useCurrentMarketId();
|
||||||
const dispatch = useDispatch();
|
|
||||||
const { isTablet } = useBreakpoints();
|
const { isTablet } = useBreakpoints();
|
||||||
const tradeLayout = useSelector(getSelectedTradeLayout);
|
const tradeLayout = useSelector(getSelectedTradeLayout);
|
||||||
|
|
||||||
const [isHorizontalPanelOpen, setIsHorizontalPanelOpen] = useState(true);
|
|
||||||
|
|
||||||
const canAccountTrade = useSelector(calculateCanAccountTrade);
|
const canAccountTrade = useSelector(calculateCanAccountTrade);
|
||||||
|
|
||||||
useEffect(() => {
|
const [isHorizontalPanelOpen, setIsHorizontalPanelOpen] = useState(true);
|
||||||
dispatch(setTradeLocation(location));
|
|
||||||
}, [location.pathname]);
|
|
||||||
|
|
||||||
usePageTitlePriceUpdates();
|
usePageTitlePriceUpdates();
|
||||||
|
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import { STRING_KEYS } from '@/constants/localization';
|
|||||||
|
|
||||||
import { useStringGetter } from '@/hooks';
|
import { useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
import { getCurrentMarketId } from '@/state/perpetualsSelectors';
|
|
||||||
|
|
||||||
import { Tabs } from '@/components/Tabs';
|
import { Tabs } from '@/components/Tabs';
|
||||||
|
|
||||||
import { Orderbook, orderbookMixins, OrderbookScrollBehavior } from '@/views/tables/Orderbook';
|
import { Orderbook, orderbookMixins, OrderbookScrollBehavior } from '@/views/tables/Orderbook';
|
||||||
import { LiveTrades } from '@/views/tables/LiveTrades';
|
import { LiveTrades } from '@/views/tables/LiveTrades';
|
||||||
|
|
||||||
|
import { getCurrentMarketId } from '@/state/perpetualsSelectors';
|
||||||
|
|
||||||
enum Tab {
|
enum Tab {
|
||||||
Orderbook = 'Orderbook',
|
Orderbook = 'Orderbook',
|
||||||
Trades = 'Trades',
|
Trades = 'Trades',
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import type {
|
|||||||
SubaccountFundingPayments,
|
SubaccountFundingPayments,
|
||||||
Wallet,
|
Wallet,
|
||||||
SubaccountOrder,
|
SubaccountOrder,
|
||||||
|
SubaccountTransfers,
|
||||||
HistoricalPnlPeriods,
|
HistoricalPnlPeriods,
|
||||||
SubAccountHistoricalPNLs,
|
SubAccountHistoricalPNLs,
|
||||||
} from '@/constants/abacus';
|
} from '@/constants/abacus';
|
||||||
@@ -22,6 +23,7 @@ import { getLocalStorage } from '@/lib/localStorage';
|
|||||||
export type AccountState = {
|
export type AccountState = {
|
||||||
fills?: SubaccountFills;
|
fills?: SubaccountFills;
|
||||||
fundingPayments?: SubaccountFundingPayments;
|
fundingPayments?: SubaccountFundingPayments;
|
||||||
|
transfers?: SubaccountTransfers;
|
||||||
clearedOrderIds?: string[];
|
clearedOrderIds?: string[];
|
||||||
uncommittedOrderClientIds?: number[];
|
uncommittedOrderClientIds?: number[];
|
||||||
hasUnseenFillUpdates: boolean;
|
hasUnseenFillUpdates: boolean;
|
||||||
@@ -38,6 +40,7 @@ export type AccountState = {
|
|||||||
const initialState: AccountState = {
|
const initialState: AccountState = {
|
||||||
fills: undefined,
|
fills: undefined,
|
||||||
fundingPayments: undefined,
|
fundingPayments: undefined,
|
||||||
|
transfers: undefined,
|
||||||
clearedOrderIds: undefined,
|
clearedOrderIds: undefined,
|
||||||
uncommittedOrderClientIds: undefined,
|
uncommittedOrderClientIds: undefined,
|
||||||
hasUnseenFillUpdates: false,
|
hasUnseenFillUpdates: false,
|
||||||
@@ -81,6 +84,9 @@ export const accountSlice = createSlice({
|
|||||||
setFundingPayments: (state, action: PayloadAction<any>) => {
|
setFundingPayments: (state, action: PayloadAction<any>) => {
|
||||||
state.fundingPayments = action.payload;
|
state.fundingPayments = action.payload;
|
||||||
},
|
},
|
||||||
|
setTransfers: (state, action: PayloadAction<any>) => {
|
||||||
|
state.transfers = action.payload;
|
||||||
|
},
|
||||||
clearOrder: (state, action: PayloadAction<string>) => ({
|
clearOrder: (state, action: PayloadAction<string>) => ({
|
||||||
...state,
|
...state,
|
||||||
clearedOrderIds: [...(state.clearedOrderIds || []), action.payload],
|
clearedOrderIds: [...(state.clearedOrderIds || []), action.payload],
|
||||||
@@ -148,6 +154,7 @@ export const accountSlice = createSlice({
|
|||||||
export const {
|
export const {
|
||||||
setFills,
|
setFills,
|
||||||
setFundingPayments,
|
setFundingPayments,
|
||||||
|
setTransfers,
|
||||||
clearOrder,
|
clearOrder,
|
||||||
setOnboardingGuard,
|
setOnboardingGuard,
|
||||||
setOnboardingState,
|
setOnboardingState,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import { SubaccountPosition } from '@/constants/abacus';
|
import { SubaccountPosition } from '@/constants/abacus';
|
||||||
import { OnboardingGuard, OnboardingState, OnboardingSteps } from '@/constants/account';
|
import { OnboardingState, OnboardingSteps } from '@/constants/account';
|
||||||
import { isDydxV4Network } from '@/constants/networks';
|
import { isDydxV4Network } from '@/constants/networks';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { OrderSide } from '@dydxprotocol/v4-client';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -193,6 +193,12 @@ export const getCurrentMarketFills = createSelector(
|
|||||||
!currentMarketId ? [] : marketFills[currentMarketId]
|
!currentMarketId ? [] : marketFills[currentMarketId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param state
|
||||||
|
* @returns list of transfers for the currently connected subaccount
|
||||||
|
*/
|
||||||
|
export const getSubaccountTransfers = (state: RootState) => state.account?.transfers;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
* @param state
|
||||||
* @returns list of funding payments for the currently connected subaccount
|
* @returns list of funding payments for the currently connected subaccount
|
||||||
|
|||||||
@@ -2,13 +2,11 @@ import type { PayloadAction } from '@reduxjs/toolkit';
|
|||||||
|
|
||||||
import { AbacusApiStatus } from '@/constants/abacus';
|
import { AbacusApiStatus } from '@/constants/abacus';
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
import { isDydxV4Network } from '@/constants/networks';
|
|
||||||
|
|
||||||
import { setApiState, setSelectedNetwork } from '@/state/app';
|
import { setApiState, setSelectedNetwork } from '@/state/app';
|
||||||
import { resetPerpetualsState } from '@/state/perpetuals';
|
import { resetPerpetualsState } from '@/state/perpetuals';
|
||||||
|
|
||||||
import abacusStateManager from '@/lib/abacus';
|
import abacusStateManager from '@/lib/abacus';
|
||||||
import squidRouter from '@/lib/squidRouter';
|
|
||||||
|
|
||||||
import { openDialog } from './dialogs';
|
import { openDialog } from './dialogs';
|
||||||
import { getActiveDialog } from './dialogsSelectors';
|
import { getActiveDialog } from './dialogsSelectors';
|
||||||
@@ -22,10 +20,6 @@ export default (store: any) => (next: any) => async (action: PayloadAction<any>)
|
|||||||
case setSelectedNetwork.type: {
|
case setSelectedNetwork.type: {
|
||||||
store.dispatch(resetPerpetualsState());
|
store.dispatch(resetPerpetualsState());
|
||||||
abacusStateManager.switchNetwork(payload);
|
abacusStateManager.switchNetwork(payload);
|
||||||
|
|
||||||
if (isDydxV4Network(payload)) {
|
|
||||||
squidRouter.switchNetwork(payload);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case setApiState.type: {
|
case setApiState.type: {
|
||||||
|
|||||||
+16
-1
@@ -10,6 +10,10 @@ import type {
|
|||||||
} from '@/constants/abacus';
|
} from '@/constants/abacus';
|
||||||
|
|
||||||
import { Candle, RESOLUTION_MAP } from '@/constants/candles';
|
import { Candle, RESOLUTION_MAP } from '@/constants/candles';
|
||||||
|
import { LocalStorageKey } from '@/constants/localStorage';
|
||||||
|
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||||
|
|
||||||
|
import { getLocalStorage } from '@/lib/localStorage';
|
||||||
|
|
||||||
interface CandleDataByMarket {
|
interface CandleDataByMarket {
|
||||||
data: Record<string, Candle[]>;
|
data: Record<string, Candle[]>;
|
||||||
@@ -17,6 +21,7 @@ interface CandleDataByMarket {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface PerpetualsState {
|
export interface PerpetualsState {
|
||||||
|
currentMarketId?: string;
|
||||||
candles: Record<string, CandleDataByMarket>;
|
candles: Record<string, CandleDataByMarket>;
|
||||||
liveTrades?: Record<string, MarketTrade[]>;
|
liveTrades?: Record<string, MarketTrade[]>;
|
||||||
markets?: Record<string, PerpetualMarket>;
|
markets?: Record<string, PerpetualMarket>;
|
||||||
@@ -25,6 +30,7 @@ export interface PerpetualsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initialState: PerpetualsState = {
|
const initialState: PerpetualsState = {
|
||||||
|
currentMarketId: undefined,
|
||||||
candles: {},
|
candles: {},
|
||||||
liveTrades: {},
|
liveTrades: {},
|
||||||
markets: undefined,
|
markets: undefined,
|
||||||
@@ -38,6 +44,9 @@ export const perpetualsSlice = createSlice({
|
|||||||
name: 'Perpetuals',
|
name: 'Perpetuals',
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
|
setCurrentMarketId: (state: PerpetualsState, action: PayloadAction<string>) => {
|
||||||
|
state.currentMarketId = action.payload;
|
||||||
|
},
|
||||||
setCandles: (
|
setCandles: (
|
||||||
state: PerpetualsState,
|
state: PerpetualsState,
|
||||||
action: PayloadAction<{ candles: Candle[]; marketId: string; resolution: string }>
|
action: PayloadAction<{ candles: Candle[]; marketId: string; resolution: string }>
|
||||||
@@ -116,11 +125,17 @@ export const perpetualsSlice = createSlice({
|
|||||||
) => {
|
) => {
|
||||||
state.historicalFundings[action.payload.marketId] = action.payload.historicalFundings;
|
state.historicalFundings[action.payload.marketId] = action.payload.historicalFundings;
|
||||||
},
|
},
|
||||||
resetPerpetualsState: () => initialState,
|
resetPerpetualsState: () =>
|
||||||
|
({
|
||||||
|
...initialState,
|
||||||
|
currentMarketId:
|
||||||
|
getLocalStorage({ key: LocalStorageKey.LastViewedMarket }) ?? DEFAULT_MARKETID,
|
||||||
|
} as PerpetualsState),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const {
|
||||||
|
setCurrentMarketId,
|
||||||
setCandles,
|
setCandles,
|
||||||
setLiveTrades,
|
setLiveTrades,
|
||||||
setMarkets,
|
setMarkets,
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import { mapCandle } from '@/lib/tradingView/utils';
|
|||||||
* @param state
|
* @param state
|
||||||
* @returns marketId of the market the user is currently viewing
|
* @returns marketId of the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketId = (state: RootState) =>
|
export const getCurrentMarketId = (state: RootState) => state.perpetuals.currentMarketId;
|
||||||
matchPath(TRADE_ROUTE, state.navigation.tradeLocation.pathname)?.params.market;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns assetId of the currentMarket
|
* @returns assetId of the currentMarket
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const headerMixins = {
|
|||||||
--trigger-backgroundColor: transparent;
|
--trigger-backgroundColor: transparent;
|
||||||
--trigger-textColor: var(--color-text-0);
|
--trigger-textColor: var(--color-text-0);
|
||||||
|
|
||||||
--trigger-hover-backgroundColor: var(--color-layer-4);
|
--trigger-hover-backgroundColor: var(--color-layer-3);
|
||||||
--trigger-hover-textColor: var(--color-text-2);
|
--trigger-hover-textColor: var(--color-text-2);
|
||||||
|
|
||||||
--trigger-open-backgroundColor: var(--color-layer-1);
|
--trigger-open-backgroundColor: var(--color-layer-1);
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export const popoverMixins = {
|
|||||||
|
|
||||||
${layoutMixins.spacedRow}
|
${layoutMixins.spacedRow}
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
padding-right: 1rem;
|
gap: 0;
|
||||||
border: 1px solid var(--color-layer-6);
|
border: 1px solid var(--color-layer-6);
|
||||||
`,
|
`,
|
||||||
|
|
||||||
@@ -185,6 +185,7 @@ export const popoverMixins = {
|
|||||||
--item-checked-backgroundColor: var(--color-layer-2);
|
--item-checked-backgroundColor: var(--color-layer-2);
|
||||||
--item-checked-textColor: currentColor;
|
--item-checked-textColor: currentColor;
|
||||||
|
|
||||||
|
--item-highlighted-backgroundColor: var(--color-layer-2);
|
||||||
--item-highlighted-textColor: var(--color-text-2);
|
--item-highlighted-textColor: var(--color-text-2);
|
||||||
|
|
||||||
--item-radius: 0px;
|
--item-radius: 0px;
|
||||||
@@ -218,6 +219,7 @@ export const popoverMixins = {
|
|||||||
&[data-highlighted] // @radix-ui
|
&[data-highlighted] // @radix-ui
|
||||||
{
|
{
|
||||||
filter: brightness(1.1);
|
filter: brightness(1.1);
|
||||||
|
background-color: var(--item-highlighted-backgroundColor);
|
||||||
color: var(--item-highlighted-textColor, var(--trigger-textColor, inherit)) !important;
|
color: var(--item-highlighted-textColor, var(--trigger-textColor, inherit)) !important;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,5 +18,9 @@ export const tradeViewMixins: Record<
|
|||||||
tbody {
|
tbody {
|
||||||
font: var(--font-small-book);
|
font: var(--font-small-book);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thead tr {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -72,16 +72,26 @@ export const AccountInfoConnectedState = () => {
|
|||||||
{!showHeader ? null : (
|
{!showHeader ? null : (
|
||||||
<Styled.Header>
|
<Styled.Header>
|
||||||
<span>{stringGetter({ key: STRING_KEYS.ACCOUNT })}</span>
|
<span>{stringGetter({ key: STRING_KEYS.ACCOUNT })}</span>
|
||||||
|
<Styled.TransferButtons>
|
||||||
<Styled.Button
|
{import.meta.env.MODE !== 'production' && (
|
||||||
state={{ isDisabled: !dydxAccounts }}
|
<Styled.Button
|
||||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Deposit }))}
|
state={{ isDisabled: !dydxAccounts }}
|
||||||
shape={ButtonShape.Pill}
|
onClick={() => dispatch(openDialog({ type: DialogTypes.Withdraw }))}
|
||||||
size={ButtonSize.XSmall}
|
shape={ButtonShape.Pill}
|
||||||
slotLeft={<Icon iconName={IconName.Transfer} />}
|
size={ButtonSize.XSmall}
|
||||||
>
|
>
|
||||||
{stringGetter({ key: STRING_KEYS.MANAGE_FUNDS })}
|
{stringGetter({ key: STRING_KEYS.WITHDRAW })}
|
||||||
</Styled.Button>
|
</Styled.Button>
|
||||||
|
)}
|
||||||
|
<Styled.Button
|
||||||
|
state={{ isDisabled: !dydxAccounts }}
|
||||||
|
onClick={() => dispatch(openDialog({ type: DialogTypes.Deposit }))}
|
||||||
|
shape={ButtonShape.Pill}
|
||||||
|
size={ButtonSize.XSmall}
|
||||||
|
>
|
||||||
|
{stringGetter({ key: STRING_KEYS.DEPOSIT })}
|
||||||
|
</Styled.Button>
|
||||||
|
</Styled.TransferButtons>
|
||||||
</Styled.Header>
|
</Styled.Header>
|
||||||
)}
|
)}
|
||||||
<Styled.Stack>
|
<Styled.Stack>
|
||||||
@@ -273,6 +283,11 @@ Styled.Header = styled.header`
|
|||||||
padding: 0 1.25rem;
|
padding: 0 1.25rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
Styled.TransferButtons = styled.div`
|
||||||
|
${layoutMixins.inlineRow}
|
||||||
|
gap: 1rem;
|
||||||
|
`;
|
||||||
|
|
||||||
Styled.ConnectedAccountInfoContainer = styled.div<{ $showHeader?: boolean }>`
|
Styled.ConnectedAccountInfoContainer = styled.div<{ $showHeader?: boolean }>`
|
||||||
${layoutMixins.column}
|
${layoutMixins.column}
|
||||||
${layoutMixins.withOuterAndInnerBorders}
|
${layoutMixins.withOuterAndInnerBorders}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { AssetIcon } from '@/components/AssetIcon';
|
|||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
import { Output, OutputType } from '@/components/Output';
|
import { Output, OutputType } from '@/components/Output';
|
||||||
import { Popover, TriggerType } from '@/components/Popover';
|
import { Popover, TriggerType } from '@/components/Popover';
|
||||||
import { Tag, TagSize } from '@/components/Tag';
|
import { Tag } from '@/components/Tag';
|
||||||
import { Toolbar } from '@/components/Toolbar';
|
import { Toolbar } from '@/components/Toolbar';
|
||||||
import { ColumnDef, Table } from '@/components/Table';
|
import { ColumnDef, Table } from '@/components/Table';
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ const MarketsDropdownContent = ({ onRowAction }: { onRowAction?: (market: string
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MarketsDropdown: React.FC<{ currentMarketId: string; symbol: string | null }> = memo(
|
export const MarketsDropdown: React.FC<{ currentMarketId?: string; symbol: string | null }> = memo(
|
||||||
({ currentMarketId, symbol = '' }) => {
|
({ currentMarketId, symbol = '' }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
|
|||||||
@@ -1,58 +1,71 @@
|
|||||||
|
import { useCallback } from 'react';
|
||||||
import styled, { AnyStyledComponent } from 'styled-components';
|
import styled, { AnyStyledComponent } from 'styled-components';
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
import { TradeInputField } from '@/constants/abacus';
|
import { TradeInputField } from '@/constants/abacus';
|
||||||
import { TradeTypes, TRADE_TYPE_STRINGS } from '@/constants/trade';
|
import { TradeTypes } from '@/constants/trade';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { useStringGetter } from '@/hooks';
|
import { useStringGetter } from '@/hooks';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
import { Tabs } from '@/components/Tabs';
|
import { Tabs } from '@/components/Tabs';
|
||||||
|
|
||||||
import { getInputTradeData } from '@/state/inputsSelectors';
|
import { getInputTradeData, getInputTradeOptions } from '@/state/inputsSelectors';
|
||||||
|
|
||||||
import abacusStateManager from '@/lib/abacus';
|
import abacusStateManager from '@/lib/abacus';
|
||||||
import { getSelectedTradeType } from '@/lib/tradeData';
|
import { isTruthy } from '@/lib/isTruthy';
|
||||||
|
|
||||||
import { TradeForm } from './forms/TradeForm';
|
import { TradeForm } from './forms/TradeForm';
|
||||||
|
|
||||||
export const TradeBoxOrderView = () => {
|
const useTradeTypeOptions = () => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const currentTradeData = useSelector(getInputTradeData, shallowEqual);
|
const selectedTradeType = useSelector(
|
||||||
const { type } = currentTradeData || {};
|
createSelector(
|
||||||
const selectedTradeType = getSelectedTradeType(type);
|
[getInputTradeData],
|
||||||
|
(currentTradeData) => (currentTradeData?.type?.rawValue as TradeTypes) ?? TradeTypes.LIMIT
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const { typeOptions } = useSelector(getInputTradeOptions, shallowEqual) ?? {};
|
||||||
|
const allTradeTypeItems = typeOptions?.toArray()?.map(({ type, stringKey }) => ({
|
||||||
|
value: type,
|
||||||
|
label: stringGetter({ key: stringKey }),
|
||||||
|
}));
|
||||||
|
|
||||||
const onTradeTypeChange = (tradeType?: TradeTypes) => {
|
return {
|
||||||
|
selectedTradeType,
|
||||||
|
tradeTypeItems: allTradeTypeItems
|
||||||
|
? [
|
||||||
|
allTradeTypeItems?.shift(), // Limit order is always first
|
||||||
|
allTradeTypeItems?.shift(), // Market order is always second
|
||||||
|
// All conditional orders labeled under "Stop Order"
|
||||||
|
allTradeTypeItems?.length && {
|
||||||
|
label: stringGetter({ key: STRING_KEYS.STOP_ORDER_SHORT }),
|
||||||
|
subitems: allTradeTypeItems
|
||||||
|
?.map(
|
||||||
|
({ value, label }) =>
|
||||||
|
value && {
|
||||||
|
value: value as TradeTypes,
|
||||||
|
label,
|
||||||
|
disabled: import.meta.env.MODE === 'production', // TODO: Remove this when stop orders are supported on testnet
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.filter(isTruthy),
|
||||||
|
},
|
||||||
|
].filter(isTruthy)
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TradeBoxOrderView = () => {
|
||||||
|
const onTradeTypeChange = useCallback((tradeType?: TradeTypes) => {
|
||||||
if (tradeType) {
|
if (tradeType) {
|
||||||
abacusStateManager.clearTradeInputValues();
|
abacusStateManager.clearTradeInputValues();
|
||||||
abacusStateManager.setTradeValue({ value: tradeType, field: TradeInputField.type });
|
abacusStateManager.setTradeValue({ value: tradeType, field: TradeInputField.type });
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const tradeTypeItems = [
|
const { selectedTradeType, tradeTypeItems } = useTradeTypeOptions();
|
||||||
{
|
|
||||||
value: TradeTypes.LIMIT,
|
|
||||||
label: stringGetter({ key: TRADE_TYPE_STRINGS[TradeTypes.LIMIT].tradeTypeKeyShort }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: TradeTypes.MARKET,
|
|
||||||
label: stringGetter({ key: TRADE_TYPE_STRINGS[TradeTypes.MARKET].tradeTypeKeyShort }),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: stringGetter({ key: STRING_KEYS.STOP_ORDER_SHORT }),
|
|
||||||
subitems: [
|
|
||||||
TradeTypes.STOP_LIMIT,
|
|
||||||
TradeTypes.STOP_MARKET,
|
|
||||||
TradeTypes.TRAILING_STOP,
|
|
||||||
TradeTypes.TAKE_PROFIT_LIMIT,
|
|
||||||
TradeTypes.TAKE_PROFIT_MARKET,
|
|
||||||
].map((tradeType) => ({
|
|
||||||
value: tradeType,
|
|
||||||
label: stringGetter({ key: TRADE_TYPE_STRINGS[tradeType].tradeTypeKeyShort }),
|
|
||||||
disabled: true,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Tabs
|
<Styled.Tabs
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import { Details } from '@/components/Details';
|
|||||||
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
|
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
|
||||||
import { Output, OutputType } from '@/components/Output';
|
import { Output, OutputType } from '@/components/Output';
|
||||||
|
|
||||||
import { OrderSide } from '@dydxprotocol/v4-client';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const theme = buildChartTheme({
|
const theme = buildChartTheme({
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import { useBreakpoints, useStringGetter } from '@/hooks';
|
|||||||
|
|
||||||
import { Dialog, DialogPlacement } from '@/components/Dialog';
|
import { Dialog, DialogPlacement } from '@/components/Dialog';
|
||||||
|
|
||||||
import { DepositForm } from '@/views/forms/DepositForm';
|
import { DepositForm } from '@/views/forms/AccountManagementForms/DepositForm';
|
||||||
import { TestnetDepositForm } from '@/views/forms/TestnetDepositForm';
|
import { TestnetDepositForm } from '@/views/forms/AccountManagementForms/TestnetDepositForm';
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
@@ -32,12 +32,12 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
|
|||||||
>
|
>
|
||||||
<Styled.Content>
|
<Styled.Content>
|
||||||
{showTestDeposit ? <TestnetDepositForm onDeposit={closeDialog} /> : <DepositForm />}
|
{showTestDeposit ? <TestnetDepositForm onDeposit={closeDialog} /> : <DepositForm />}
|
||||||
{/* TODO TRCL-1693 - uncomment when deposit form is ready
|
{/* TODO TRCL-1693 - uncomment when deposit form is ready */}
|
||||||
{import.meta.env.MODE !== 'production' && (
|
{import.meta.env.MODE !== 'production' && (
|
||||||
<Styled.TextToggle onClick={() => setShowTestDeposit(!showTestDeposit)}>
|
<Styled.TextToggle onClick={() => setShowTestDeposit(!showTestDeposit)}>
|
||||||
{showTestDeposit ? 'Show deposit form (Under Construction)' : 'Show test faucet'}
|
{showTestDeposit ? 'Show deposit form (Under Construction)' : 'Show test faucet'}
|
||||||
</Styled.TextToggle>
|
</Styled.TextToggle>
|
||||||
)} */}
|
)}
|
||||||
</Styled.Content>
|
</Styled.Content>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ import {
|
|||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
orderId: string;
|
orderId: string;
|
||||||
setIsOpen?: (open: boolean) => void;
|
setIsOpen: (open: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
|
export const OrderDetailsDialog = ({ orderId, setIsOpen }: ElementProps) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import styled, { AnyStyledComponent } from 'styled-components';
|
|||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { AbacusApiStatus } from '@/constants/abacus';
|
import { AbacusApiStatus } from '@/constants/abacus';
|
||||||
import { DydxV4Network } from '@/constants/networks';
|
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
|
||||||
import { UNICODE } from '@/constants/unicode';
|
import { UNICODE } from '@/constants/unicode';
|
||||||
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
@@ -44,7 +44,7 @@ export const ExchangeOfflineDialog = ({ preventClose, setIsOpen }: ElementProps)
|
|||||||
>
|
>
|
||||||
<Styled.Content>
|
<Styled.Content>
|
||||||
<p>{statusErrorMessage}</p>
|
<p>{statusErrorMessage}</p>
|
||||||
{selectedNetwork === DydxV4Network.V4Testnet2 && (
|
{CLIENT_NETWORK_CONFIGS[selectedNetwork].dydxChainId === 'dydx-testnet-2' && (
|
||||||
<Styled.Link href="https://status.v4testnet.dydx.exchange">
|
<Styled.Link href="https://status.v4testnet.dydx.exchange">
|
||||||
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} {UNICODE.ARROW_RIGHT}
|
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} {UNICODE.ARROW_RIGHT}
|
||||||
</Styled.Link>
|
</Styled.Link>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { breakpoints } from '@/styles';
|
|||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
import { AlertMessage } from '@/components/AlertMessage';
|
import { AlertMessage } from '@/components/AlertMessage';
|
||||||
import { Button } from '@/components/Button';
|
import { CopyButton } from '@/components/CopyButton';
|
||||||
import { Dialog } from '@/components/Dialog';
|
import { Dialog } from '@/components/Dialog';
|
||||||
import { Checkbox } from '@/components/Checkbox';
|
import { Checkbox } from '@/components/Checkbox';
|
||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
@@ -30,21 +30,12 @@ export const MnemonicExportDialog = ({ setIsOpen }: ElementProps) => {
|
|||||||
const [hasAcknowledged, setHasAcknowledged] = useState(false);
|
const [hasAcknowledged, setHasAcknowledged] = useState(false);
|
||||||
const [currentStep, setCurrentStep] = useState(MnemonicExportStep.AcknowledgeRisk);
|
const [currentStep, setCurrentStep] = useState(MnemonicExportStep.AcknowledgeRisk);
|
||||||
const [isShowing, setIsShowing] = useState(false);
|
const [isShowing, setIsShowing] = useState(false);
|
||||||
const [copied, setCopied] = useState(false);
|
|
||||||
|
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
|
|
||||||
const { hdKey } = useAccounts();
|
const { hdKey } = useAccounts();
|
||||||
const { mnemonic } = hdKey ?? {};
|
const { mnemonic } = hdKey ?? {};
|
||||||
|
|
||||||
const onCopy = () => {
|
|
||||||
setCopied(true);
|
|
||||||
if (mnemonic) {
|
|
||||||
navigator.clipboard.writeText(mnemonic);
|
|
||||||
}
|
|
||||||
setTimeout(() => setCopied(false), 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
const title = {
|
const title = {
|
||||||
[MnemonicExportStep.AcknowledgeRisk]: stringGetter({ key: STRING_KEYS.REVEAL_SECRET_PHRASE }),
|
[MnemonicExportStep.AcknowledgeRisk]: stringGetter({ key: STRING_KEYS.REVEAL_SECRET_PHRASE }),
|
||||||
[MnemonicExportStep.DisplayMnemonic]: stringGetter({ key: STRING_KEYS.EXPORT_SECRET_PHRASE }),
|
[MnemonicExportStep.DisplayMnemonic]: stringGetter({ key: STRING_KEYS.EXPORT_SECRET_PHRASE }),
|
||||||
@@ -116,10 +107,7 @@ export const MnemonicExportDialog = ({ setIsOpen }: ElementProps) => {
|
|||||||
</Styled.WordList>
|
</Styled.WordList>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Button action={copied ? ButtonAction.Create : ButtonAction.Primary} onClick={onCopy}>
|
<CopyButton value={mnemonic} />
|
||||||
<Icon iconName={IconName.Copy} />
|
|
||||||
{stringGetter({ key: copied ? STRING_KEYS.COPIED : STRING_KEYS.COPY })}
|
|
||||||
</Button>
|
|
||||||
</WithReceipt>
|
</WithReceipt>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { GreenCheckCircle } from '@/components/GreenCheckCircle';
|
|||||||
import { Icon } from '@/components/Icon';
|
import { Icon } from '@/components/Icon';
|
||||||
import { Ring } from '@/components/Ring';
|
import { Ring } from '@/components/Ring';
|
||||||
|
|
||||||
import { TestnetDepositForm } from '@/views/forms/TestnetDepositForm';
|
import { TestnetDepositForm } from '@/views/forms/AccountManagementForms/TestnetDepositForm';
|
||||||
|
|
||||||
import { track } from '@/lib/analytics';
|
import { track } from '@/lib/analytics';
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useSignTypedData } from 'wagmi';
|
import { useSignTypedData } from 'wagmi';
|
||||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AES } from 'crypto-js';
|
import { AES } from 'crypto-js';
|
||||||
|
|
||||||
import { EvmDerivedAccountStatus } from '@/constants/account';
|
import { EvmDerivedAccountStatus } from '@/constants/account';
|
||||||
@@ -9,7 +9,7 @@ import { AlertType } from '@/constants/alerts';
|
|||||||
import { AnalyticsEvent } from '@/constants/analytics';
|
import { AnalyticsEvent } from '@/constants/analytics';
|
||||||
import { ButtonAction } from '@/constants/buttons';
|
import { ButtonAction } from '@/constants/buttons';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { CLIENT_NETWORK_CONFIGS, DydxV4Network } from '@/constants/networks';
|
import { CLIENT_NETWORK_CONFIGS } from '@/constants/networks';
|
||||||
import { DydxAddress, SIGN_TYPED_DATA } from '@/constants/wallets';
|
import { DydxAddress, SIGN_TYPED_DATA } from '@/constants/wallets';
|
||||||
|
|
||||||
import { useAccounts, useBreakpoints, useDydxClient, useStringGetter } from '@/hooks';
|
import { useAccounts, useBreakpoints, useDydxClient, useStringGetter } from '@/hooks';
|
||||||
@@ -54,7 +54,7 @@ export const GenerateKeys = ({
|
|||||||
// 1. Switch network
|
// 1. Switch network
|
||||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||||
|
|
||||||
const chainId = Number(CLIENT_NETWORK_CONFIGS[selectedNetwork as DydxV4Network].ethereumChainId);
|
const chainId = Number(CLIENT_NETWORK_CONFIGS[selectedNetwork].ethereumChainId);
|
||||||
|
|
||||||
const { isMatchingNetwork, matchNetwork, isSwitchingNetwork } = useMatchingEvmNetwork({
|
const { isMatchingNetwork, matchNetwork, isSwitchingNetwork } = useMatchingEvmNetwork({
|
||||||
chainId,
|
chainId,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import { ButtonAction } from '@/constants/buttons';
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { DydxChainAsset } from '@/constants/wallets';
|
import { DydxChainAsset } from '@/constants/wallets';
|
||||||
|
|
||||||
@@ -10,9 +9,8 @@ import { layoutMixins } from '@/styles/layoutMixins';
|
|||||||
import { useAccounts, useStringGetter } from '@/hooks';
|
import { useAccounts, useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
import { AssetIcon } from '@/components/AssetIcon';
|
import { AssetIcon } from '@/components/AssetIcon';
|
||||||
import { Button } from '@/components/Button';
|
import { CopyButton } from '@/components/CopyButton';
|
||||||
import { Dialog } from '@/components/Dialog';
|
import { Dialog } from '@/components/Dialog';
|
||||||
import { Icon, IconName } from '@/components/Icon';
|
|
||||||
import { QrCode } from '@/components/QrCode';
|
import { QrCode } from '@/components/QrCode';
|
||||||
import { SelectItem, SelectMenu } from '@/components/SelectMenu';
|
import { SelectItem, SelectMenu } from '@/components/SelectMenu';
|
||||||
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
|
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
|
||||||
@@ -22,22 +20,15 @@ import { truncateAddress } from '@/lib/wallet';
|
|||||||
import { OnboardingTriggerButton } from './OnboardingTriggerButton';
|
import { OnboardingTriggerButton } from './OnboardingTriggerButton';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
|
selectedAsset?: DydxChainAsset;
|
||||||
setIsOpen: (open: boolean) => void;
|
setIsOpen: (open: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ReceiveDialog = ({ setIsOpen }: ElementProps) => {
|
export const ReceiveDialog = ({ selectedAsset = DydxChainAsset.DYDX, setIsOpen }: ElementProps) => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const { dydxAddress } = useAccounts();
|
const { dydxAddress } = useAccounts();
|
||||||
|
|
||||||
const [asset, setAsset] = useState(DydxChainAsset.DYDX);
|
const [asset, setAsset] = useState(selectedAsset);
|
||||||
const [copied, setCopied] = useState(false);
|
|
||||||
|
|
||||||
const onCopy = () => {
|
|
||||||
if (!dydxAddress) return;
|
|
||||||
setCopied(true);
|
|
||||||
navigator.clipboard.writeText(dydxAddress);
|
|
||||||
setTimeout(() => setCopied(false), 500);
|
|
||||||
};
|
|
||||||
|
|
||||||
const assetOptions = [
|
const assetOptions = [
|
||||||
{
|
{
|
||||||
@@ -52,7 +43,8 @@ export const ReceiveDialog = ({ setIsOpen }: ElementProps) => {
|
|||||||
value: DydxChainAsset.DYDX,
|
value: DydxChainAsset.DYDX,
|
||||||
label: (
|
label: (
|
||||||
<Styled.InlineRow>
|
<Styled.InlineRow>
|
||||||
<AssetIcon symbol="DYDX" /> DYDX
|
{/* <AssetIcon symbol="DYDX" /> */}
|
||||||
|
Dv4TNT
|
||||||
</Styled.InlineRow>
|
</Styled.InlineRow>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -87,10 +79,7 @@ export const ReceiveDialog = ({ setIsOpen }: ElementProps) => {
|
|||||||
>
|
>
|
||||||
<QrCode hasLogo value={dydxAddress!} />
|
<QrCode hasLogo value={dydxAddress!} />
|
||||||
</Styled.WithDetailsReceipt>
|
</Styled.WithDetailsReceipt>
|
||||||
<Button action={copied ? ButtonAction.Create : ButtonAction.Primary} onClick={onCopy}>
|
<CopyButton value={dydxAddress} />
|
||||||
<Icon iconName={IconName.Copy} />
|
|
||||||
{stringGetter({ key: copied ? STRING_KEYS.COPIED : STRING_KEYS.COPY_ADDRESS })}
|
|
||||||
</Button>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Styled.Content>
|
</Styled.Content>
|
||||||
|
|||||||
@@ -1,21 +1,23 @@
|
|||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { DydxChainAsset } from '@/constants/wallets';
|
||||||
import { useStringGetter } from '@/hooks';
|
import { useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
import { Dialog } from '@/components/Dialog';
|
import { Dialog } from '@/components/Dialog';
|
||||||
import { TransferForm } from '@/views/forms/TransferForm';
|
import { TransferForm } from '@/views/forms/TransferForm';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
|
selectedAsset?: DydxChainAsset;
|
||||||
setIsOpen?: (open: boolean) => void;
|
setIsOpen?: (open: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TransferDialog = ({ setIsOpen }: ElementProps) => {
|
export const TransferDialog = ({ selectedAsset, setIsOpen }: ElementProps) => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Dialog isOpen setIsOpen={setIsOpen} title={stringGetter({ key: STRING_KEYS.TRANSFER })}>
|
<Styled.Dialog isOpen setIsOpen={setIsOpen} title={stringGetter({ key: STRING_KEYS.TRANSFER })}>
|
||||||
<TransferForm onDone={() => setIsOpen?.(false)} />
|
<TransferForm selectedAsset={selectedAsset} onDone={() => setIsOpen?.(false)} />
|
||||||
</Styled.Dialog>
|
</Styled.Dialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
|
import { Dialog, DialogPlacement } from '@/components/Dialog';
|
||||||
|
|
||||||
|
import { WithdrawForm } from '@/views/forms/AccountManagementForms/WithdrawForm';
|
||||||
|
|
||||||
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
|
type ElementProps = {
|
||||||
|
setIsOpen: (open: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithdrawDialog = ({ setIsOpen }: ElementProps) => {
|
||||||
|
const stringGetter = useStringGetter();
|
||||||
|
const { isTablet } = useBreakpoints();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
isOpen
|
||||||
|
setIsOpen={setIsOpen}
|
||||||
|
title={stringGetter({ key: STRING_KEYS.WITHDRAW })}
|
||||||
|
placement={isTablet ? DialogPlacement.FullScreen : DialogPlacement.Default}
|
||||||
|
>
|
||||||
|
<Styled.Content>
|
||||||
|
<WithdrawForm />
|
||||||
|
</Styled.Content>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
Styled.TextToggle = styled.div`
|
||||||
|
${layoutMixins.stickyFooter}
|
||||||
|
color: var(--color-accent);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
margin-top: auto;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Content = styled.div`
|
||||||
|
${layoutMixins.stickyArea0}
|
||||||
|
--stickyArea0-bottomHeight: 2rem;
|
||||||
|
--stickyArea0-bottomGap: 1rem;
|
||||||
|
--stickyArea0-totalInsetBottom: 0.5rem;
|
||||||
|
|
||||||
|
${layoutMixins.flexColumn}
|
||||||
|
gap: 1rem;
|
||||||
|
`;
|
||||||
+29
-15
@@ -1,31 +1,45 @@
|
|||||||
import type { ChainData } from '@0xsquid/sdk';
|
|
||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { TransferType } from '@/constants/abacus';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { useStringGetter } from '@/hooks';
|
import { useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
import { WithLabel } from '@/components/WithLabel';
|
|
||||||
import { SearchSelectMenu } from '@/components/SearchSelectMenu';
|
import { SearchSelectMenu } from '@/components/SearchSelectMenu';
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { popoverMixins } from '@/styles/popoverMixins';
|
import { popoverMixins } from '@/styles/popoverMixins';
|
||||||
|
|
||||||
|
import { getTransferInputs } from '@/state/inputsSelectors';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
chains?: ChainData[];
|
label?: string;
|
||||||
currentChain?: ChainData;
|
selectedChain?: string;
|
||||||
setCurrentChain: (chain: ChainData) => void;
|
onSelectChain: (chain: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ChainSelectMenu = ({ chains = [], currentChain, setCurrentChain }: ElementProps) => {
|
export const ChainSelectMenu = ({
|
||||||
|
label,
|
||||||
|
selectedChain,
|
||||||
|
onSelectChain,
|
||||||
|
}: ElementProps) => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
|
const { type, depositOptions, withdrawalOptions, resources } =
|
||||||
|
useSelector(getTransferInputs, shallowEqual) || {};
|
||||||
|
const chains =
|
||||||
|
(type === TransferType.deposit ? depositOptions : withdrawalOptions)?.chains?.toArray() || [];
|
||||||
|
|
||||||
const chainItems = Object.values(chains).map((chain: ChainData) => ({
|
const chainItems = Object.values(chains).map((chain) => ({
|
||||||
value: chain.chainId.toString(),
|
value: chain.type,
|
||||||
label: chain.chainName,
|
label: chain.stringKey,
|
||||||
onSelect: () => setCurrentChain(chain),
|
onSelect: () => {
|
||||||
slotBefore: <Styled.Img src={chain.chainIconURI} alt="" />,
|
onSelectChain(chain.type);
|
||||||
|
},
|
||||||
|
slotBefore: <Styled.Img src={chain.iconUrl} alt="" />,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const selectedOption = chains.find((item) => item.type === selectedChain);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SearchSelectMenu
|
<SearchSelectMenu
|
||||||
items={[
|
items={[
|
||||||
@@ -35,12 +49,12 @@ export const ChainSelectMenu = ({ chains = [], currentChain, setCurrentChain }:
|
|||||||
items: chainItems,
|
items: chainItems,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
label="Source"
|
label={label || (type === TransferType.deposit ? 'Source' : 'Destination')}
|
||||||
>
|
>
|
||||||
<Styled.ChainRow>
|
<Styled.ChainRow>
|
||||||
{currentChain ? (
|
{selectedChain ? (
|
||||||
<>
|
<>
|
||||||
<Styled.Img src={currentChain?.chainIconURI} alt="" /> {currentChain?.chainName}
|
<Styled.Img src={selectedOption?.iconUrl} alt="" /> {selectedOption?.stringKey}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
stringGetter({ key: STRING_KEYS.SELECT_CHAIN })
|
stringGetter({ key: STRING_KEYS.SELECT_CHAIN })
|
||||||
@@ -67,4 +81,4 @@ Styled.ChainRow = styled.div`
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
font: var(--font-base-book);
|
font: var(--font-base-book);
|
||||||
`;
|
`;
|
||||||
@@ -0,0 +1,342 @@
|
|||||||
|
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 { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
||||||
|
import { ethers } from 'ethers';
|
||||||
|
|
||||||
|
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 { useAccounts, useDebounce, useStringGetter } from '@/hooks';
|
||||||
|
import { useAccountBalance } from '@/hooks/useAccountBalance';
|
||||||
|
|
||||||
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
import { formMixins } from '@/styles/formMixins';
|
||||||
|
|
||||||
|
import { AlertMessage } from '@/components/AlertMessage';
|
||||||
|
import { Button } from '@/components/Button';
|
||||||
|
import { DiffOutput } from '@/components/DiffOutput';
|
||||||
|
import { FormInput } from '@/components/FormInput';
|
||||||
|
import { InputType } from '@/components/Input';
|
||||||
|
import { Link } from '@/components/Link';
|
||||||
|
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
|
||||||
|
import { OutputType } from '@/components/Output';
|
||||||
|
import { Tag } from '@/components/Tag';
|
||||||
|
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
|
||||||
|
|
||||||
|
import { getTransferInputs } from '@/state/inputsSelectors';
|
||||||
|
|
||||||
|
import abacusStateManager from '@/lib/abacus';
|
||||||
|
import { MustBigNumber } from '@/lib/numbers';
|
||||||
|
|
||||||
|
import { ChainSelectMenu } from './ChainSelectMenu';
|
||||||
|
import { TokenSelectMenu } from './TokenSelectMenu';
|
||||||
|
|
||||||
|
import { DepositButtonAndReceipt } from './DepositForm/DepositButtonAndReceipt';
|
||||||
|
|
||||||
|
type DepositFormProps = {
|
||||||
|
onDeposit?: () => void;
|
||||||
|
onError?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
||||||
|
const stringGetter = useStringGetter();
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const { signerWagmi } = useAccounts();
|
||||||
|
|
||||||
|
const {
|
||||||
|
requestPayload,
|
||||||
|
token,
|
||||||
|
chain: chainIdStr,
|
||||||
|
resources,
|
||||||
|
} = useSelector(getTransferInputs, shallowEqual) || {};
|
||||||
|
const chainId = chainIdStr ? parseInt(chainIdStr) : undefined;
|
||||||
|
|
||||||
|
// User inputs
|
||||||
|
const sourceToken = useMemo(
|
||||||
|
() => (token ? resources?.tokenResources?.get(token) : undefined),
|
||||||
|
[token, 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,
|
||||||
|
chainId: chainId,
|
||||||
|
decimals: sourceToken?.decimals || undefined,
|
||||||
|
isCosmosChain: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// BN
|
||||||
|
const debouncedAmountBN = MustBigNumber(debouncedAmount);
|
||||||
|
const balanceBN = MustBigNumber(balance);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const hasInvalidInput =
|
||||||
|
debouncedAmountBN.isNaN() || debouncedAmountBN.lte(0) || debouncedAmountBN.gte(balanceBN);
|
||||||
|
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
value: hasInvalidInput ? 0 : debouncedAmount,
|
||||||
|
field: TransferInputField.size,
|
||||||
|
});
|
||||||
|
|
||||||
|
setError(null);
|
||||||
|
}, [debouncedAmountBN.toNumber()]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
field: TransferInputField.type,
|
||||||
|
value: TransferType.deposit.rawValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
abacusStateManager.clearTransferInputValues();
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
field: TransferInputField.type,
|
||||||
|
value: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (error) onError?.();
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
const onSelectChain = useCallback((chain: string) => {
|
||||||
|
if (chain) {
|
||||||
|
abacusStateManager.clearTransferInputValues();
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
field: TransferInputField.chain,
|
||||||
|
value: chain,
|
||||||
|
});
|
||||||
|
setFromAmount('');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSelectToken = useCallback((token: TransferInputTokenResource) => {
|
||||||
|
if (token) {
|
||||||
|
abacusStateManager.clearTransferInputValues();
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
field: TransferInputField.token,
|
||||||
|
value: token.address,
|
||||||
|
});
|
||||||
|
setFromAmount('');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onChangeAmount = useCallback(
|
||||||
|
({ value }: NumberFormatValues) => {
|
||||||
|
setFromAmount(value);
|
||||||
|
},
|
||||||
|
[setFromAmount]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSetSlippage = useCallback(
|
||||||
|
(newSlippage: number) => {
|
||||||
|
setSlippage(newSlippage);
|
||||||
|
// TODO: to be implemented via abacus
|
||||||
|
},
|
||||||
|
[setSlippage, debouncedAmount]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClickMax = useCallback(() => {
|
||||||
|
if (balance) {
|
||||||
|
setFromAmount(balanceBN.toString());
|
||||||
|
}
|
||||||
|
}, [balance, setFromAmount]);
|
||||||
|
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
async (e: FormEvent) => {
|
||||||
|
try {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!signerWagmi) {
|
||||||
|
throw new Error('Missing signer');
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!requestPayload?.targetAddress ||
|
||||||
|
!requestPayload.data ||
|
||||||
|
!requestPayload.value ||
|
||||||
|
!requestPayload.gasLimit ||
|
||||||
|
!requestPayload.gasPrice ||
|
||||||
|
!requestPayload.routeType
|
||||||
|
) {
|
||||||
|
throw new Error('Missing request payload');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
let tx = {
|
||||||
|
to: requestPayload.targetAddress as `0x${string}`,
|
||||||
|
data: requestPayload.data as `0x${string}`,
|
||||||
|
gasLimit: ethers.toBigInt(requestPayload.gasLimit),
|
||||||
|
value:
|
||||||
|
requestPayload.routeType !== 'SEND' ? ethers.toBigInt(requestPayload.value) : undefined,
|
||||||
|
};
|
||||||
|
const txHash = await signerWagmi.sendTransaction(tx);
|
||||||
|
onDeposit?.();
|
||||||
|
|
||||||
|
if (txHash) {
|
||||||
|
setTransactionHash(txHash);
|
||||||
|
abacusStateManager.setTransferStatus({
|
||||||
|
hash: txHash,
|
||||||
|
toChainId: TESTNET_CHAIN_ID,
|
||||||
|
fromChainId: chainId?.toString(),
|
||||||
|
});
|
||||||
|
abacusStateManager.clearTransferInputValues();
|
||||||
|
setFromAmount('');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setError(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[requestPayload, signerWagmi, chainId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const amountInputReceipt = [
|
||||||
|
{
|
||||||
|
key: 'amount',
|
||||||
|
label: (
|
||||||
|
<span>
|
||||||
|
{stringGetter({ key: STRING_KEYS.AVAILABLE })}{' '}
|
||||||
|
{sourceToken && <Tag>{sourceToken.symbol}</Tag>}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
value: (
|
||||||
|
<DiffOutput
|
||||||
|
type={OutputType.Asset}
|
||||||
|
value={balance}
|
||||||
|
newValue={balanceBN.minus(debouncedAmountBN).toString()}
|
||||||
|
sign={NumberSign.Negative}
|
||||||
|
hasInvalidNewValue={balanceBN.minus(debouncedAmountBN).isNegative()}
|
||||||
|
withDiff={
|
||||||
|
Boolean(fromAmount && balance) &&
|
||||||
|
!debouncedAmountBN.isNaN() &&
|
||||||
|
!debouncedAmountBN.isZero()
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const errorMessage = useMemo(() => {
|
||||||
|
if (error) {
|
||||||
|
return error?.message
|
||||||
|
? stringGetter({
|
||||||
|
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
|
||||||
|
params: { ERROR_MESSAGE: error.message },
|
||||||
|
})
|
||||||
|
: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromAmount) {
|
||||||
|
if (!chainId) {
|
||||||
|
return stringGetter({ key: STRING_KEYS.MUST_SPECIFY_CHAIN });
|
||||||
|
} else if (!sourceToken) {
|
||||||
|
return stringGetter({ key: STRING_KEYS.MUST_SPECIFY_ASSET });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MustBigNumber(fromAmount).gt(MustBigNumber(balance))) {
|
||||||
|
return stringGetter({ key: STRING_KEYS.DEPOSIT_MORE_THAN_BALANCE });
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}, [error, balance, chainId, fromAmount, sourceToken]);
|
||||||
|
|
||||||
|
const isDisabled =
|
||||||
|
Boolean(errorMessage) ||
|
||||||
|
!sourceToken ||
|
||||||
|
!chainId ||
|
||||||
|
debouncedAmountBN.isNaN() ||
|
||||||
|
debouncedAmountBN.isZero();
|
||||||
|
|
||||||
|
if (!resources) {
|
||||||
|
return <LoadingSpace id="DepositForm" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Styled.Form onSubmit={onSubmit}>
|
||||||
|
<ChainSelectMenu selectedChain={chainIdStr || undefined} onSelectChain={onSelectChain} />
|
||||||
|
<TokenSelectMenu selectedToken={sourceToken || undefined} onSelectToken={onSelectToken} />
|
||||||
|
<Styled.WithDetailsReceipt side="bottom" detailItems={amountInputReceipt}>
|
||||||
|
<FormInput
|
||||||
|
type={InputType.Number}
|
||||||
|
onChange={onChangeAmount}
|
||||||
|
label={stringGetter({ key: STRING_KEYS.AMOUNT })}
|
||||||
|
value={fromAmount}
|
||||||
|
slotRight={
|
||||||
|
<Styled.FormInputButton size={ButtonSize.XSmall} onClick={onClickMax}>
|
||||||
|
{stringGetter({ key: STRING_KEYS.MAX })}
|
||||||
|
</Styled.FormInputButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Styled.WithDetailsReceipt>
|
||||||
|
{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}
|
||||||
|
chainId={chainId || undefined}
|
||||||
|
setSlippage={onSetSlippage}
|
||||||
|
slippage={slippage}
|
||||||
|
sourceToken={sourceToken || undefined}
|
||||||
|
/>
|
||||||
|
</Styled.Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
Styled.Form = styled.form`
|
||||||
|
--form-input-height: 3.5rem;
|
||||||
|
|
||||||
|
${layoutMixins.flexColumn}
|
||||||
|
gap: 1.5rem;
|
||||||
|
|
||||||
|
${layoutMixins.stickyArea1}
|
||||||
|
min-height: calc(100% - var(--stickyArea0-bottomHeight));
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.WithDetailsReceipt = styled(WithDetailsReceipt)`
|
||||||
|
--withReceipt-backgroundColor: var(--color-layer-2);
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Link = styled(Link)`
|
||||||
|
color: var(--color-accent);
|
||||||
|
|
||||||
|
&:visited {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.TransactionInfo = styled.span`
|
||||||
|
${layoutMixins.row}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.FormInputButton = styled(Button)`
|
||||||
|
${formMixins.inputInnerButton}
|
||||||
|
`;
|
||||||
+40
-52
@@ -1,8 +1,7 @@
|
|||||||
import { type Dispatch, type SetStateAction, useState } from 'react';
|
import { type Dispatch, type SetStateAction, useState, type ReactNode } from 'react';
|
||||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
import styled, { type AnyStyledComponent, } from 'styled-components';
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import type { RouteData, TokenData } from '@0xsquid/sdk';
|
import type { RouteData } from '@0xsquid/sdk';
|
||||||
import BigNumber from 'bignumber.js';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ButtonAction,
|
ButtonAction,
|
||||||
@@ -12,6 +11,7 @@ import {
|
|||||||
ButtonType,
|
ButtonType,
|
||||||
} from '@/constants/buttons';
|
} from '@/constants/buttons';
|
||||||
|
|
||||||
|
import { TransferInputTokenResource } from '@/constants/abacus';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { NumberSign } from '@/constants/numbers';
|
import { NumberSign } from '@/constants/numbers';
|
||||||
|
|
||||||
@@ -30,11 +30,9 @@ import { ToggleButton } from '@/components/ToggleButton';
|
|||||||
import { WithReceipt } from '@/components/WithReceipt';
|
import { WithReceipt } from '@/components/WithReceipt';
|
||||||
|
|
||||||
import { getSubaccountBuyingPower, getSubaccountEquity } from '@/state/accountSelectors';
|
import { getSubaccountBuyingPower, getSubaccountEquity } from '@/state/accountSelectors';
|
||||||
|
import { getTransferInputs } from '@/state/inputsSelectors';
|
||||||
|
|
||||||
import { BIG_NUMBERS, MustBigNumber } from '@/lib/numbers';
|
import { SlippageEditor } from '../SlippageEditor';
|
||||||
import squidRouter from '@/lib/squidRouter';
|
|
||||||
|
|
||||||
import { SlippageEditor } from './SlippageEditor';
|
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
@@ -43,8 +41,9 @@ type ElementProps = {
|
|||||||
chainId?: string | number;
|
chainId?: string | number;
|
||||||
setError?: Dispatch<SetStateAction<Error | undefined>>;
|
setError?: Dispatch<SetStateAction<Error | undefined>>;
|
||||||
slippage: number;
|
slippage: number;
|
||||||
|
slotError?: ReactNode;
|
||||||
setSlippage: (slippage: number) => void;
|
setSlippage: (slippage: number) => void;
|
||||||
sourceToken?: TokenData;
|
sourceToken?: TransferInputTokenResource;
|
||||||
squidRoute?: RouteData;
|
squidRoute?: RouteData;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -54,10 +53,10 @@ export const DepositButtonAndReceipt = ({
|
|||||||
slippage,
|
slippage,
|
||||||
setSlippage,
|
setSlippage,
|
||||||
sourceToken,
|
sourceToken,
|
||||||
squidRoute,
|
|
||||||
|
|
||||||
isDisabled,
|
isDisabled,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
slotError,
|
||||||
}: ElementProps) => {
|
}: ElementProps) => {
|
||||||
const [showFeeBreakdown, setShowFeeBreakdown] = useState(false);
|
const [showFeeBreakdown, setShowFeeBreakdown] = useState(false);
|
||||||
const [isEditingSlippage, setIsEditingSlipapge] = useState(false);
|
const [isEditingSlippage, setIsEditingSlipapge] = useState(false);
|
||||||
@@ -78,35 +77,23 @@ export const DepositButtonAndReceipt = ({
|
|||||||
const { current: buyingPower, postOrder: newBuyingPower } =
|
const { current: buyingPower, postOrder: newBuyingPower } =
|
||||||
useSelector(getSubaccountBuyingPower, shallowEqual) || {};
|
useSelector(getSubaccountBuyingPower, shallowEqual) || {};
|
||||||
|
|
||||||
const { gasCosts, feeCosts } = squidRoute?.estimate || {};
|
const { summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
|
||||||
|
|
||||||
const totalGasCosts =
|
const feeSubitems: DetailsItem[] = [];
|
||||||
gasCosts?.reduce(
|
|
||||||
(acc: BigNumber, { amountUSD }: { amountUSD: string }) => acc.plus(MustBigNumber(amountUSD)),
|
|
||||||
BIG_NUMBERS.ZERO
|
|
||||||
) || BIG_NUMBERS.ZERO;
|
|
||||||
|
|
||||||
const totalFeeCosts =
|
if (typeof summary?.gasFee === 'number') {
|
||||||
feeCosts?.reduce(
|
|
||||||
(acc: BigNumber, { amountUSD }: { amountUSD: string }) => acc.plus(MustBigNumber(amountUSD)),
|
|
||||||
BIG_NUMBERS.ZERO
|
|
||||||
) || BIG_NUMBERS.ZERO;
|
|
||||||
|
|
||||||
const totalFees = totalGasCosts.plus(totalFeeCosts);
|
|
||||||
|
|
||||||
const feeSubitems: DetailsItem[] = feeCosts
|
|
||||||
? feeCosts.map(({ amountUSD, name }: { amountUSD: string; name: string }) => ({
|
|
||||||
key: name,
|
|
||||||
label: <span>{name}</span>,
|
|
||||||
value: <Output type={OutputType.Fiat} value={amountUSD} />,
|
|
||||||
}))
|
|
||||||
: [];
|
|
||||||
|
|
||||||
if (gasCosts) {
|
|
||||||
feeSubitems.push({
|
feeSubitems.push({
|
||||||
key: 'gas-fees',
|
key: 'gas-fees',
|
||||||
label: <span>{stringGetter({ key: STRING_KEYS.GAS_FEE })}</span>,
|
label: <span>{stringGetter({ key: STRING_KEYS.GAS_FEE })}</span>,
|
||||||
value: <Output type={OutputType.Fiat} value={totalGasCosts} />,
|
value: <Output type={OutputType.Fiat} value={summary?.gasFee} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof summary?.bridgeFee === 'number') {
|
||||||
|
feeSubitems.push({
|
||||||
|
key: 'bridge-fees',
|
||||||
|
label: <span>Bridge Fee</span>,
|
||||||
|
value: <Output type={OutputType.Fiat} value={summary?.bridgeFee} />,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,9 +115,9 @@ export const DepositButtonAndReceipt = ({
|
|||||||
<DiffOutput
|
<DiffOutput
|
||||||
type={OutputType.Fiat}
|
type={OutputType.Fiat}
|
||||||
value={equity}
|
value={equity}
|
||||||
newValue={newEquity}
|
newValue={newEquity} // using toAmountUSD as a proxy for equity until Abacus supports accounts with no funds.
|
||||||
sign={NumberSign.Positive}
|
sign={NumberSign.Positive}
|
||||||
withDiff={Boolean(newEquity) && equity !== newEquity}
|
withDiff={equity !== newEquity}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -154,27 +141,20 @@ export const DepositButtonAndReceipt = ({
|
|||||||
{
|
{
|
||||||
key: 'exchange-rate',
|
key: 'exchange-rate',
|
||||||
label: <span>{stringGetter({ key: STRING_KEYS.EXCHANGE_RATE })}</span>,
|
label: <span>{stringGetter({ key: STRING_KEYS.EXCHANGE_RATE })}</span>,
|
||||||
value: sourceToken && squidRoute && (
|
value: typeof summary?.exchangeRate === 'number' && (
|
||||||
<Styled.ExchangeRate>
|
<Styled.ExchangeRate>
|
||||||
<Output
|
<Output type={OutputType.Asset} value={1} fractionDigits={0} tag={sourceToken?.symbol} />
|
||||||
type={OutputType.Asset}
|
|
||||||
value={1}
|
|
||||||
fractionDigits={0}
|
|
||||||
tag={<Tag>{sourceToken?.symbol}</Tag>}
|
|
||||||
/>
|
|
||||||
=
|
=
|
||||||
<Output
|
<Output type={OutputType.Asset} value={summary?.exchangeRate} tag="USDC" />
|
||||||
type={OutputType.Asset}
|
|
||||||
value={squidRoute?.estimate?.exchangeRate}
|
|
||||||
tag={<Tag>{squidRouter.SQUID_ROUTE_DEFAULTS.toToken.toUpperCase()}</Tag>}
|
|
||||||
/>
|
|
||||||
</Styled.ExchangeRate>
|
</Styled.ExchangeRate>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'total-fees',
|
key: 'total-fees',
|
||||||
label: <span>{stringGetter({ key: STRING_KEYS.TOTAL_FEES })}</span>,
|
label: <span>{stringGetter({ key: STRING_KEYS.TOTAL_FEES })}</span>,
|
||||||
value: squidRoute && <Output type={OutputType.Fiat} value={totalFees} />,
|
value: typeof summary?.bridgeFee === 'number' && typeof summary?.gasFee === 'number' && (
|
||||||
|
<Output type={OutputType.Fiat} value={summary?.bridgeFee + summary?.gasFee} />
|
||||||
|
),
|
||||||
subitems: feeSubitems,
|
subitems: feeSubitems,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -212,17 +192,25 @@ export const DepositButtonAndReceipt = ({
|
|||||||
</Styled.DetailButtons>
|
</Styled.DetailButtons>
|
||||||
</Styled.CollapsibleDetails>
|
</Styled.CollapsibleDetails>
|
||||||
}
|
}
|
||||||
|
slotError={slotError}
|
||||||
>
|
>
|
||||||
{!isMatchingNetwork ? (
|
{!isMatchingNetwork ? (
|
||||||
<Button
|
<Button
|
||||||
action={ButtonAction.Primary}
|
action={ButtonAction.Primary}
|
||||||
onClick={switchNetwork}
|
onClick={switchNetwork}
|
||||||
state={{ isLoading: isSwitchingNetwork || isLoading }}
|
state={{ isLoading: isSwitchingNetwork }}
|
||||||
>
|
>
|
||||||
{stringGetter({ key: STRING_KEYS.SWITCH_NETWORK })}
|
{stringGetter({ key: STRING_KEYS.SWITCH_NETWORK })}
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button type={ButtonType.Submit} state={{ isDisabled: !isFormValid, isLoading }}>
|
<Button
|
||||||
|
action={ButtonAction.Primary}
|
||||||
|
type={ButtonType.Submit}
|
||||||
|
state={{
|
||||||
|
isDisabled: !isFormValid,
|
||||||
|
isLoading: (isFormValid && !requestPayload) || isLoading,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{stringGetter({ key: STRING_KEYS.DEPOSIT_FUNDS })}
|
{stringGetter({ key: STRING_KEYS.DEPOSIT_FUNDS })}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@@ -234,7 +222,7 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
|||||||
|
|
||||||
Styled.ExchangeRate = styled.span`
|
Styled.ExchangeRate = styled.span`
|
||||||
${layoutMixins.row}
|
${layoutMixins.row}
|
||||||
gap: 1ch;
|
gap: 0.5ch;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.WithReceipt = styled(WithReceipt)`
|
Styled.WithReceipt = styled(WithReceipt)`
|
||||||
+4
-5
@@ -12,6 +12,7 @@ import { Icon, IconName } from '@/components/Icon';
|
|||||||
import { Input, InputType } from '@/components/Input';
|
import { Input, InputType } from '@/components/Input';
|
||||||
import { Output, OutputType } from '@/components/Output';
|
import { Output, OutputType } from '@/components/Output';
|
||||||
import { ToggleGroup } from '@/components/ToggleGroup';
|
import { ToggleGroup } from '@/components/ToggleGroup';
|
||||||
|
import { FormInput } from '@/components/FormInput';
|
||||||
|
|
||||||
enum EditorState {
|
enum EditorState {
|
||||||
Viewing = 'Viewing',
|
Viewing = 'Viewing',
|
||||||
@@ -115,7 +116,7 @@ export const SlippageEditor = ({ slippage, setIsEditing, setSlippage }: Slippage
|
|||||||
</Styled.SlippageInput>
|
</Styled.SlippageInput>
|
||||||
),
|
),
|
||||||
[EditorState.Editing]: (
|
[EditorState.Editing]: (
|
||||||
<Styled.Input
|
<Styled.FormInput
|
||||||
type={InputType.Percent}
|
type={InputType.Percent}
|
||||||
value={slippageInputValue}
|
value={slippageInputValue}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
@@ -151,10 +152,8 @@ Styled.SlippageInput = styled.div`
|
|||||||
border-radius: 0.5em;
|
border-radius: 0.5em;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Input = styled(Input)`
|
Styled.FormInput = styled(FormInput)`
|
||||||
// TODO(@aforaleka): replace with input container
|
--form-input-height: 1.5rem;
|
||||||
--input-height: 1.5rem;
|
|
||||||
--input-backgroundColor: var(--color-layer-5);
|
|
||||||
|
|
||||||
input {
|
input {
|
||||||
text-align: end;
|
text-align: end;
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { TransferInputTokenResource, TransferType } from '@/constants/abacus';
|
||||||
|
import { useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
|
import { DiffArrow } from '@/components/DiffArrow';
|
||||||
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
|
import { SearchSelectMenu } from '@/components/SearchSelectMenu';
|
||||||
|
import { Tag } from '@/components/Tag';
|
||||||
|
|
||||||
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
|
import { getTransferInputs } from '@/state/inputsSelectors';
|
||||||
|
|
||||||
|
type ElementProps = {
|
||||||
|
selectedToken?: TransferInputTokenResource;
|
||||||
|
onSelectToken: (token: TransferInputTokenResource) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TokenSelectMenu = ({ selectedToken, onSelectToken }: ElementProps) => {
|
||||||
|
const stringGetter = useStringGetter();
|
||||||
|
const { type, depositOptions, withdrawalOptions, resources } =
|
||||||
|
useSelector(getTransferInputs, shallowEqual) || {};
|
||||||
|
const tokens =
|
||||||
|
(type === TransferType.deposit ? depositOptions : withdrawalOptions)?.assets?.toArray() || [];
|
||||||
|
|
||||||
|
const tokenItems = Object.values(tokens).map((token) => ({
|
||||||
|
value: token.type,
|
||||||
|
label: token.stringKey,
|
||||||
|
onSelect: () => {
|
||||||
|
const selectedToken = resources?.tokenResources?.get(token.type);
|
||||||
|
selectedToken && onSelectToken(selectedToken);
|
||||||
|
},
|
||||||
|
slotBefore: <Styled.Img src={token.iconUrl} alt="" />,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SearchSelectMenu
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
group: 'assets',
|
||||||
|
groupLabel: stringGetter({ key: STRING_KEYS.ASSET }),
|
||||||
|
items: tokenItems,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
label={stringGetter({ key: STRING_KEYS.ASSET })}
|
||||||
|
withReceiptItems={[
|
||||||
|
{
|
||||||
|
key: 'swap',
|
||||||
|
label: stringGetter({ key: STRING_KEYS.SWAP }),
|
||||||
|
value: selectedToken && (
|
||||||
|
<>
|
||||||
|
<Tag>{type === TransferType.deposit ? selectedToken?.symbol : 'USDC'}</Tag>
|
||||||
|
<DiffArrow />
|
||||||
|
<Tag>{type === TransferType.deposit ? 'USDC' : selectedToken?.symbol}</Tag>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
tooltip: 'swap',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Styled.AssetRow>
|
||||||
|
{selectedToken ? (
|
||||||
|
<>
|
||||||
|
<Styled.Img src={selectedToken?.iconUrl} alt="" /> {selectedToken?.name}{' '}
|
||||||
|
<Tag>{selectedToken?.symbol}</Tag>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
stringGetter({ key: STRING_KEYS.SELECT_ASSET })
|
||||||
|
)}
|
||||||
|
</Styled.AssetRow>
|
||||||
|
</SearchSelectMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
Styled.AssetRow = styled.div`
|
||||||
|
${layoutMixins.row}
|
||||||
|
gap: 0.5rem;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
font: var(--font-base-book);
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Img = styled.img`
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Icon = styled(Icon)`
|
||||||
|
height: 0.5rem;
|
||||||
|
`;
|
||||||
@@ -0,0 +1,358 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import type { ChangeEvent, FormEvent } from 'react';
|
||||||
|
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 { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
|
||||||
|
import { AlertType } from '@/constants/alerts';
|
||||||
|
import { ButtonSize } from '@/constants/buttons';
|
||||||
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { NumberSign, QUANTUM_MULTIPLIER } from '@/constants/numbers';
|
||||||
|
|
||||||
|
import { useDebounce, useStringGetter, useSubaccount } from '@/hooks';
|
||||||
|
|
||||||
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
import { formMixins } from '@/styles/formMixins';
|
||||||
|
|
||||||
|
import { AlertMessage } from '@/components/AlertMessage';
|
||||||
|
import { Button } from '@/components/Button';
|
||||||
|
import { DiffOutput } from '@/components/DiffOutput';
|
||||||
|
import { FormInput } from '@/components/FormInput';
|
||||||
|
import { InputType } from '@/components/Input';
|
||||||
|
import { Link } from '@/components/Link';
|
||||||
|
import { OutputType } from '@/components/Output';
|
||||||
|
import { Tag } from '@/components/Tag';
|
||||||
|
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
|
||||||
|
|
||||||
|
import { ChainSelectMenu } from '@/views/forms/AccountManagementForms/ChainSelectMenu';
|
||||||
|
|
||||||
|
import { getSubaccount } from '@/state/accountSelectors';
|
||||||
|
import { getTransferInputs } from '@/state/inputsSelectors';
|
||||||
|
|
||||||
|
import abacusStateManager from '@/lib/abacus';
|
||||||
|
import { MustBigNumber } from '@/lib/numbers';
|
||||||
|
|
||||||
|
import { TokenSelectMenu } from './TokenSelectMenu';
|
||||||
|
import { WithdrawButtonAndReceipt } from './WithdrawForm/WithdrawButtonAndReceipt';
|
||||||
|
|
||||||
|
export const WithdrawForm = () => {
|
||||||
|
const stringGetter = useStringGetter();
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const { simulateWithdraw, sendSquidWithdraw } = useSubaccount();
|
||||||
|
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
|
||||||
|
|
||||||
|
// User input
|
||||||
|
const [withdrawAmount, setWithdrawAmount] = useState('');
|
||||||
|
const [slippage, setSlippage] = useState(0.01); // 0.1% slippage
|
||||||
|
const debouncedAmount = useDebounce<string>(withdrawAmount, 500);
|
||||||
|
|
||||||
|
const {
|
||||||
|
requestPayload,
|
||||||
|
token,
|
||||||
|
chain: chainIdStr,
|
||||||
|
address: toAddress,
|
||||||
|
resources,
|
||||||
|
} = useSelector(getTransferInputs, shallowEqual) || {};
|
||||||
|
|
||||||
|
const toToken = useMemo(
|
||||||
|
() => (token ? resources?.tokenResources?.get(token) : undefined),
|
||||||
|
[token, resources]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Async Data
|
||||||
|
const [transactionHash, setTransactionHash] = useState<string>();
|
||||||
|
|
||||||
|
const debouncedAmountBN = MustBigNumber(debouncedAmount);
|
||||||
|
const withdrawAmountBN = MustBigNumber(withdrawAmount);
|
||||||
|
const freeCollateralBN = MustBigNumber(freeCollateral?.current);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
field: TransferInputField.type,
|
||||||
|
value: TransferType.withdrawal.rawValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
abacusStateManager.clearTransferInputValues();
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
field: TransferInputField.type,
|
||||||
|
value: null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const setTransferValue = async () => {
|
||||||
|
try {
|
||||||
|
setIsLoading(true);
|
||||||
|
const hasInvalidInput = debouncedAmountBN.isNaN() || debouncedAmountBN.lte(0);
|
||||||
|
if (hasInvalidInput) {
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
value: 0,
|
||||||
|
field: TransferInputField.usdcSize,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const stdFee = await simulateWithdraw(parseFloat(debouncedAmount));
|
||||||
|
const amount =
|
||||||
|
parseFloat(debouncedAmount) -
|
||||||
|
parseFloat(stdFee?.amount[0]?.amount || '0') / QUANTUM_MULTIPLIER;
|
||||||
|
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
value: amount.toString(),
|
||||||
|
field: TransferInputField.usdcSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
setError(null);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setError(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
setTransferValue();
|
||||||
|
}, [debouncedAmountBN.toNumber()]);
|
||||||
|
|
||||||
|
const onSubmit = useCallback(
|
||||||
|
async (e: FormEvent) => {
|
||||||
|
try {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!requestPayload?.data || !debouncedAmountBN.toNumber()) {
|
||||||
|
throw new Error('Invalid request payload');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
const txHash = await sendSquidWithdraw(debouncedAmountBN.toNumber(), requestPayload?.data);
|
||||||
|
|
||||||
|
if (txHash?.hash) {
|
||||||
|
const hash = `0x${Buffer.from(txHash.hash).toString('hex')}`;
|
||||||
|
|
||||||
|
setTransactionHash(hash);
|
||||||
|
abacusStateManager.setTransferStatus({
|
||||||
|
hash,
|
||||||
|
fromChainId: TESTNET_CHAIN_ID,
|
||||||
|
toChainId: chainIdStr || undefined,
|
||||||
|
});
|
||||||
|
abacusStateManager.clearTransferInputValues();
|
||||||
|
setWithdrawAmount('');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setError(error);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[setTransactionHash, requestPayload, debouncedAmountBN, chainIdStr]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onChangeAddress = useCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
field: TransferInputField.address,
|
||||||
|
value: e.target.value,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onChangeAmount = useCallback(
|
||||||
|
({ value }: NumberFormatValues) => {
|
||||||
|
setWithdrawAmount(value);
|
||||||
|
},
|
||||||
|
[setWithdrawAmount]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSetSlippage = useCallback(
|
||||||
|
(newSlippage: number) => {
|
||||||
|
setSlippage(newSlippage);
|
||||||
|
|
||||||
|
// TODO: to be implemented via abacus
|
||||||
|
// if (MustBigNumber(newSlippage).gt(0) && debouncedAmountBN.gt(0)) {
|
||||||
|
// fetchRoute({ newAmount: debouncedAmount, newSlippage });
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
[setSlippage, debouncedAmount]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onClickMax = useCallback(() => {
|
||||||
|
setWithdrawAmount(freeCollateralBN.toString());
|
||||||
|
}, [freeCollateral?.current, setWithdrawAmount]);
|
||||||
|
|
||||||
|
const onSelectChain = useCallback((chain: string) => {
|
||||||
|
if (chain) {
|
||||||
|
abacusStateManager.clearTransferInputValues();
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
field: TransferInputField.chain,
|
||||||
|
value: chain,
|
||||||
|
});
|
||||||
|
setWithdrawAmount('');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onSelectToken = useCallback((token: TransferInputTokenResource) => {
|
||||||
|
if (token) {
|
||||||
|
abacusStateManager.clearTransferInputValues();
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
field: TransferInputField.token,
|
||||||
|
value: token.address,
|
||||||
|
});
|
||||||
|
setWithdrawAmount('');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const amountInputReceipt = [
|
||||||
|
{
|
||||||
|
key: 'freeCollateral',
|
||||||
|
label: (
|
||||||
|
<span>
|
||||||
|
{stringGetter({ key: STRING_KEYS.FREE_COLLATERAL })} <Tag>USDC</Tag>
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
value: (
|
||||||
|
<Styled.DiffOutput
|
||||||
|
type={OutputType.Fiat}
|
||||||
|
value={freeCollateral?.current}
|
||||||
|
newValue={freeCollateral?.postOrder}
|
||||||
|
sign={NumberSign.Negative}
|
||||||
|
hasInvalidNewValue={withdrawAmountBN.minus(freeCollateralBN).isNegative()}
|
||||||
|
withDiff={
|
||||||
|
Boolean(withdrawAmount) && !debouncedAmountBN.isNaN() && !debouncedAmountBN.isZero()
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const errorMessage = useMemo(() => {
|
||||||
|
if (error) {
|
||||||
|
return error?.message
|
||||||
|
? stringGetter({
|
||||||
|
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
|
||||||
|
params: { ERROR_MESSAGE: error.message },
|
||||||
|
})
|
||||||
|
: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!toAddress) return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ADDRESS });
|
||||||
|
|
||||||
|
if (debouncedAmountBN) {
|
||||||
|
if (!chainIdStr) {
|
||||||
|
return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_CHAIN });
|
||||||
|
} else if (!toToken) {
|
||||||
|
return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ASSET });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (MustBigNumber(debouncedAmountBN).gt(MustBigNumber(freeCollateralBN))) {
|
||||||
|
return stringGetter({ key: STRING_KEYS.WITHDRAW_MORE_THAN_FREE });
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}, [error, freeCollateralBN, chainIdStr, debouncedAmountBN, toToken]);
|
||||||
|
|
||||||
|
const isDisabled =
|
||||||
|
!!errorMessage ||
|
||||||
|
!toToken ||
|
||||||
|
!chainIdStr ||
|
||||||
|
!toAddress ||
|
||||||
|
debouncedAmountBN.isNaN() ||
|
||||||
|
debouncedAmountBN.isZero();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Styled.Form onSubmit={onSubmit}>
|
||||||
|
<Styled.DestinationRow>
|
||||||
|
<FormInput
|
||||||
|
type={InputType.Text}
|
||||||
|
placeholder={stringGetter({ key: STRING_KEYS.ADDRESS })}
|
||||||
|
onChange={onChangeAddress}
|
||||||
|
value={toAddress || ''}
|
||||||
|
label={stringGetter({ key: STRING_KEYS.DESTINATION })}
|
||||||
|
/>
|
||||||
|
<ChainSelectMenu
|
||||||
|
label={stringGetter({ key: STRING_KEYS.NETWORK })}
|
||||||
|
selectedChain={chainIdStr || undefined}
|
||||||
|
onSelectChain={onSelectChain}
|
||||||
|
/>
|
||||||
|
</Styled.DestinationRow>
|
||||||
|
<TokenSelectMenu selectedToken={toToken || undefined} onSelectToken={onSelectToken} />
|
||||||
|
<Styled.WithDetailsReceipt side="bottom" detailItems={amountInputReceipt}>
|
||||||
|
<FormInput
|
||||||
|
type={InputType.Number}
|
||||||
|
onChange={onChangeAmount}
|
||||||
|
value={withdrawAmount}
|
||||||
|
label={stringGetter({ key: STRING_KEYS.AMOUNT })}
|
||||||
|
slotRight={
|
||||||
|
<Styled.FormInputButton size={ButtonSize.XSmall} onClick={onClickMax}>
|
||||||
|
{stringGetter({ key: STRING_KEYS.MAX })}
|
||||||
|
</Styled.FormInputButton>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Styled.WithDetailsReceipt>
|
||||||
|
{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}
|
||||||
|
setSlippage={onSetSlippage}
|
||||||
|
slippage={slippage}
|
||||||
|
withdrawChain={chainIdStr || undefined}
|
||||||
|
withdrawToken={toToken || undefined}
|
||||||
|
/>
|
||||||
|
</Styled.Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
Styled.DiffOutput = styled(DiffOutput)`
|
||||||
|
--diffOutput-valueWithDiff-fontSize: 1em;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Form = styled.form`
|
||||||
|
--form-input-height: 3.5rem;
|
||||||
|
|
||||||
|
min-height: calc(100% - var(--stickyArea0-bottomHeight));
|
||||||
|
|
||||||
|
${layoutMixins.flexColumn}
|
||||||
|
gap: 1.25rem;
|
||||||
|
|
||||||
|
${layoutMixins.stickyArea1}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.DestinationRow = styled.div`
|
||||||
|
${layoutMixins.spacedRow}
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.WithDetailsReceipt = styled(WithDetailsReceipt)`
|
||||||
|
--withReceipt-backgroundColor: var(--color-layer-2);
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Link = styled(Link)`
|
||||||
|
color: var(--color-accent);
|
||||||
|
|
||||||
|
&:visited {
|
||||||
|
color: var(--color-accent);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.TransactionInfo = styled.span`
|
||||||
|
${layoutMixins.row}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.FormInputButton = styled(Button)`
|
||||||
|
${formMixins.inputInnerButton}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
|
import { TransferInputTokenResource } from '@/constants/abacus';
|
||||||
|
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
|
||||||
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { NumberSign } from '@/constants/numbers';
|
||||||
|
import { useStringGetter } from '@/hooks';
|
||||||
|
import { useAccountBalance } from '@/hooks/useAccountBalance';
|
||||||
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
|
import { Button } from '@/components/Button';
|
||||||
|
|
||||||
|
import { Details, DetailsItem } from '@/components/Details';
|
||||||
|
import { DiffOutput } from '@/components/DiffOutput';
|
||||||
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
|
import { Output, OutputType } from '@/components/Output';
|
||||||
|
import { ToggleButton } from '@/components/ToggleButton';
|
||||||
|
import { WithReceipt } from '@/components/WithReceipt';
|
||||||
|
|
||||||
|
import { getSubaccount } from '@/state/accountSelectors';
|
||||||
|
import { getTransferInputs } from '@/state/inputsSelectors';
|
||||||
|
|
||||||
|
import { SlippageEditor } from '../SlippageEditor';
|
||||||
|
|
||||||
|
type ElementProps = {
|
||||||
|
setSlippage: (slippage: number) => void;
|
||||||
|
|
||||||
|
slippage: number;
|
||||||
|
withdrawChain?: string;
|
||||||
|
withdrawToken?: TransferInputTokenResource;
|
||||||
|
|
||||||
|
isDisabled?: boolean;
|
||||||
|
isLoading?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithdrawButtonAndReceipt = ({
|
||||||
|
setSlippage,
|
||||||
|
|
||||||
|
slippage,
|
||||||
|
withdrawChain,
|
||||||
|
withdrawToken,
|
||||||
|
|
||||||
|
isDisabled,
|
||||||
|
isLoading,
|
||||||
|
}: ElementProps) => {
|
||||||
|
const [showFeeBreakdown, setShowFeeBreakdown] = useState(false);
|
||||||
|
const [isEditingSlippage, setIsEditingSlipapge] = useState(false);
|
||||||
|
const stringGetter = useStringGetter();
|
||||||
|
|
||||||
|
const { balance, queryStatus } = useAccountBalance({
|
||||||
|
addressOrDenom: withdrawToken?.address || undefined,
|
||||||
|
assetSymbol: withdrawToken?.symbol || undefined,
|
||||||
|
chainId: withdrawChain ? parseInt(withdrawChain) : undefined,
|
||||||
|
decimals: withdrawToken?.decimals || undefined,
|
||||||
|
isCosmosChain: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: uncomment when we have a way to get token amount estimate from abacus
|
||||||
|
// const balanceBN = MustBigNumber(balance);
|
||||||
|
// const newBalance =
|
||||||
|
// // toAmountMin && withdrawToken && parseUnits(toAmountMin, withdrawToken.decimals).toString();
|
||||||
|
|
||||||
|
const { leverage } = useSelector(getSubaccount, shallowEqual) || {};
|
||||||
|
const { summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
|
||||||
|
|
||||||
|
const feeSubitems: DetailsItem[] = [];
|
||||||
|
|
||||||
|
if (typeof summary?.gasFee === 'number') {
|
||||||
|
feeSubitems.push({
|
||||||
|
key: 'gas-fees',
|
||||||
|
label: <span>{stringGetter({ key: STRING_KEYS.GAS_FEE })}</span>,
|
||||||
|
value: <Output type={OutputType.Fiat} value={summary?.gasFee} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof summary?.bridgeFee === 'number') {
|
||||||
|
feeSubitems.push({
|
||||||
|
key: 'bridge-fees',
|
||||||
|
label: <span>{stringGetter({ key: STRING_KEYS.BRIDGE_FEE })}</span>,
|
||||||
|
value: <Output type={OutputType.Fiat} value={summary?.bridgeFee} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasSubitems = feeSubitems.length > 0;
|
||||||
|
|
||||||
|
const showSubitemsToggle = showFeeBreakdown
|
||||||
|
? stringGetter({ key: STRING_KEYS.HIDE_ALL_DETAILS })
|
||||||
|
: stringGetter({ key: STRING_KEYS.SHOW_ALL_DETAILS });
|
||||||
|
|
||||||
|
const submitButtonReceipt = [
|
||||||
|
{
|
||||||
|
key: 'total-fees',
|
||||||
|
label: <span>{stringGetter({ key: STRING_KEYS.TOTAL_FEES })}</span>,
|
||||||
|
value: typeof summary?.bridgeFee === 'number' && typeof summary?.gasFee === 'number' && (
|
||||||
|
<Output type={OutputType.Fiat} value={summary?.bridgeFee + summary?.gasFee} />
|
||||||
|
),
|
||||||
|
subitems: feeSubitems,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'leverage',
|
||||||
|
label: <span>{stringGetter({ key: STRING_KEYS.LEVERAGE })}</span>,
|
||||||
|
value: (
|
||||||
|
<Styled.DiffOutput
|
||||||
|
type={OutputType.Multiple}
|
||||||
|
value={leverage?.current}
|
||||||
|
newValue={leverage?.postOrder}
|
||||||
|
sign={NumberSign.Negative}
|
||||||
|
withDiff={Boolean(leverage?.current && leverage.current !== leverage?.postOrder)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'exchange-rate',
|
||||||
|
label: <span>{stringGetter({ key: STRING_KEYS.EXCHANGE_RATE })}</span>,
|
||||||
|
value: withdrawToken && typeof summary?.exchangeRate === 'number' && (
|
||||||
|
<Styled.ExchangeRate>
|
||||||
|
<Output type={OutputType.Asset} value={1} fractionDigits={0} tag="USDC" />
|
||||||
|
=
|
||||||
|
<Output
|
||||||
|
type={OutputType.Asset}
|
||||||
|
value={summary?.exchangeRate}
|
||||||
|
tag={withdrawToken?.symbol}
|
||||||
|
/>
|
||||||
|
</Styled.ExchangeRate>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'slippage',
|
||||||
|
label: <span>{stringGetter({ key: STRING_KEYS.SLIPPAGE })}</span>,
|
||||||
|
value: (
|
||||||
|
<SlippageEditor
|
||||||
|
slippage={slippage}
|
||||||
|
setIsEditing={setIsEditingSlipapge}
|
||||||
|
setSlippage={setSlippage}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
// TODO: uncomment when we have a way to get token amount estimate from abacus
|
||||||
|
// {
|
||||||
|
// key: 'wallet',
|
||||||
|
// label: (
|
||||||
|
// <span>
|
||||||
|
// {stringGetter({ key: STRING_KEYS.WALLET })}{' '}
|
||||||
|
// {withdrawToken && <Tag>{withdrawToken?.symbol}</Tag>}
|
||||||
|
// </span>
|
||||||
|
// ),
|
||||||
|
// value: (
|
||||||
|
// <Styled.DiffOutput
|
||||||
|
// type={OutputType.Asset}
|
||||||
|
// value={balanceBN?.toString()}
|
||||||
|
// newValue={newBalance}
|
||||||
|
// sign={NumberSign.Negative}
|
||||||
|
// withDiff={Boolean(balance !== newBalance)}
|
||||||
|
// />
|
||||||
|
// ),
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
|
||||||
|
const isFormValid = !isDisabled && !isEditingSlippage && queryStatus !== 'error';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Styled.WithReceipt
|
||||||
|
slotReceipt={
|
||||||
|
<Styled.CollapsibleDetails>
|
||||||
|
<Styled.Details showSubitems={showFeeBreakdown} items={submitButtonReceipt} />
|
||||||
|
{hasSubitems && (
|
||||||
|
<Styled.DetailButtons>
|
||||||
|
<Styled.ToggleButton
|
||||||
|
shape={ButtonShape.Pill}
|
||||||
|
size={ButtonSize.XSmall}
|
||||||
|
isPressed={showFeeBreakdown}
|
||||||
|
onPressedChange={setShowFeeBreakdown}
|
||||||
|
slotLeft={<Icon iconName={IconName.Caret} />}
|
||||||
|
>
|
||||||
|
{showSubitemsToggle}
|
||||||
|
</Styled.ToggleButton>
|
||||||
|
</Styled.DetailButtons>
|
||||||
|
)}
|
||||||
|
</Styled.CollapsibleDetails>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
action={ButtonAction.Primary}
|
||||||
|
type={ButtonType.Submit}
|
||||||
|
state={{
|
||||||
|
isDisabled: !isFormValid,
|
||||||
|
isLoading: (isFormValid && !requestPayload) || isLoading,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{stringGetter({ key: STRING_KEYS.WITHDRAW })}
|
||||||
|
</Button>
|
||||||
|
</Styled.WithReceipt>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
Styled.DiffOutput = styled(DiffOutput)`
|
||||||
|
--diffOutput-valueWithDiff-fontSize: 1em;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.ExchangeRate = styled.span`
|
||||||
|
${layoutMixins.row}
|
||||||
|
gap: 0.5ch;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.WithReceipt = styled(WithReceipt)`
|
||||||
|
--withReceipt-backgroundColor: var(--color-layer-2);
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.CollapsibleDetails = styled.div`
|
||||||
|
${layoutMixins.column}
|
||||||
|
padding: 0.375rem 1rem 0.5rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Details = styled(Details)`
|
||||||
|
font-size: 0.8125em;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.DetailButtons = styled.div`
|
||||||
|
${layoutMixins.spacedRow}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.ToggleButton = styled(ToggleButton)`
|
||||||
|
--button-toggle-off-backgroundColor: transparent;
|
||||||
|
--button-toggle-on-backgroundColor: transparent;
|
||||||
|
--button-toggle-on-textColor: var(--color-text-0);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 0.875em;
|
||||||
|
height: 0.875em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-state='on'] {
|
||||||
|
svg {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -1,428 +0,0 @@
|
|||||||
import { type FormEvent, useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
|
||||||
import type { ChainData, CosmosChain, RouteData, TokenData } from '@0xsquid/sdk';
|
|
||||||
import { type NumberFormatValues } from 'react-number-format';
|
|
||||||
import debounce from 'lodash/debounce';
|
|
||||||
import { parseUnits } from 'ethers';
|
|
||||||
|
|
||||||
import { TransferInputField } from '@/constants/abacus';
|
|
||||||
import { AlertType } from '@/constants/alerts';
|
|
||||||
import { ButtonSize } from '@/constants/buttons';
|
|
||||||
import { StringGetterFunction, STRING_KEYS } from '@/constants/localization';
|
|
||||||
import { NumberSign } from '@/constants/numbers';
|
|
||||||
import { CLIENT_NETWORK_CONFIGS, type DydxV4Network } from '@/constants/networks';
|
|
||||||
|
|
||||||
import { useAccounts, useDydxClient, useStringGetter } from '@/hooks';
|
|
||||||
import { useAccountBalance } from '@/hooks/useAccountBalance';
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
|
||||||
|
|
||||||
import { AlertMessage } from '@/components/AlertMessage';
|
|
||||||
import { Button } from '@/components/Button';
|
|
||||||
import { DiffOutput } from '@/components/DiffOutput';
|
|
||||||
import { FormInput } from '@/components/FormInput';
|
|
||||||
import { InputType } from '@/components/Input';
|
|
||||||
import { Link } from '@/components/Link';
|
|
||||||
import { OutputType } from '@/components/Output';
|
|
||||||
import { Tag } from '@/components/Tag';
|
|
||||||
import { WithLabel } from '@/components/WithLabel';
|
|
||||||
import { WithDetailsReceipt } from '@/components/WithDetailsReceipt';
|
|
||||||
|
|
||||||
import abacusStateManager from '@/lib/abacus';
|
|
||||||
import { convertBech32Address } from '@/lib/addressUtils';
|
|
||||||
import { MustBigNumber } from '@/lib/numbers';
|
|
||||||
import squidRouter from '@/lib/squidRouter';
|
|
||||||
|
|
||||||
import { useSquidData } from './DepositForm/useSquidData';
|
|
||||||
import { ChainSelectMenu } from './DepositForm/ChainSelectMenu';
|
|
||||||
import { DepositButtonAndReceipt } from './DepositForm/DepositButtonAndReceipt';
|
|
||||||
import { TokenSelectMenu } from './DepositForm/TokenSelectMenu';
|
|
||||||
import { log } from '@/lib/telemetry';
|
|
||||||
|
|
||||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
|
||||||
|
|
||||||
type DepositFormProps = {
|
|
||||||
onDeposit?: () => void;
|
|
||||||
onError?: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
// debounced function should be outside our functional component
|
|
||||||
const debouncedGetRoute = debounce(
|
|
||||||
async ({
|
|
||||||
amount,
|
|
||||||
currentChain,
|
|
||||||
cosmosAddress,
|
|
||||||
fromAmount,
|
|
||||||
setAbacusTransferInput,
|
|
||||||
setError,
|
|
||||||
setIsLoading,
|
|
||||||
setSquidRoute,
|
|
||||||
slippage,
|
|
||||||
sourceToken,
|
|
||||||
}) => {
|
|
||||||
if (!currentChain || !sourceToken || !fromAmount || !cosmosAddress) return;
|
|
||||||
setIsLoading(true);
|
|
||||||
setSquidRoute(undefined);
|
|
||||||
setError(undefined);
|
|
||||||
|
|
||||||
const params = {
|
|
||||||
fromChain: currentChain.chainId,
|
|
||||||
fromToken: sourceToken.address,
|
|
||||||
fromAmount: parseUnits(amount, sourceToken.decimals).toString(),
|
|
||||||
toAddress: convertBech32Address({ address: cosmosAddress, bech32Prefix: 'osmo' }), // the recipient of the trade
|
|
||||||
slippage: slippage * 100,
|
|
||||||
...squidRouter.SQUID_ROUTE_DEFAULTS,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { route } = await squidRouter.getRoute(params);
|
|
||||||
setSquidRoute(route);
|
|
||||||
setAbacusTransferInput(route.estimate.toAmountUSD);
|
|
||||||
} catch (error) {
|
|
||||||
log('DepositForm/getRoute', error);
|
|
||||||
setError(error);
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
1000,
|
|
||||||
{ trailing: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
const getDepositFormErrorMessage = ({
|
|
||||||
error,
|
|
||||||
balance,
|
|
||||||
currentChain,
|
|
||||||
fromAmount,
|
|
||||||
sourceToken,
|
|
||||||
stringGetter,
|
|
||||||
}: {
|
|
||||||
error?: any;
|
|
||||||
balance?: string;
|
|
||||||
currentChain?: ChainData;
|
|
||||||
fromAmount: string;
|
|
||||||
sourceToken?: TokenData;
|
|
||||||
stringGetter: StringGetterFunction;
|
|
||||||
}): string | undefined => {
|
|
||||||
if (error) {
|
|
||||||
return error?.message
|
|
||||||
? stringGetter({
|
|
||||||
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
|
|
||||||
params: { ERROR_MESSAGE: error.message },
|
|
||||||
})
|
|
||||||
: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fromAmount) {
|
|
||||||
if (!currentChain) {
|
|
||||||
return stringGetter({ key: STRING_KEYS.MUST_SPECIFY_CHAIN });
|
|
||||||
} else if (!sourceToken) {
|
|
||||||
return stringGetter({ key: STRING_KEYS.MUST_SPECIFY_ASSET });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MustBigNumber(fromAmount).gt(MustBigNumber(balance))) {
|
|
||||||
return stringGetter({ key: STRING_KEYS.DEPOSIT_MORE_THAN_BALANCE });
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|
||||||
const stringGetter = useStringGetter();
|
|
||||||
|
|
||||||
const { dydxAddress: cosmosAddress, signerWagmi, signerGraz } = useAccounts();
|
|
||||||
|
|
||||||
// const { networkConfig } = useDydxClient();
|
|
||||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
|
||||||
const evmChainId = Number(CLIENT_NETWORK_CONFIGS[selectedNetwork as DydxV4Network].ethereumChainId);
|
|
||||||
|
|
||||||
const { chains, tokens } = useSquidData(signerGraz ? 'cosmos' : 'evm');
|
|
||||||
|
|
||||||
// User inputs
|
|
||||||
const defaultChain: ChainData | undefined = useMemo(
|
|
||||||
() => chains?.find(({ chainId }) => chainId === evmChainId) ?? undefined,
|
|
||||||
[chains]
|
|
||||||
);
|
|
||||||
const [currentChain, setCurrentChain] = useState<ChainData | undefined>(defaultChain);
|
|
||||||
|
|
||||||
const defaultToken: TokenData | undefined = useMemo(
|
|
||||||
() => tokens?.filter(({ chainId }) => chainId === currentChain?.chainId)?.[0] ?? undefined,
|
|
||||||
[currentChain]
|
|
||||||
);
|
|
||||||
const [sourceToken, setSourceToken] = useState<TokenData | undefined>(defaultToken);
|
|
||||||
const [fromAmount, setFromAmount] = useState('');
|
|
||||||
const [slippage, setSlippage] = useState(0.001); // 0.1% slippage
|
|
||||||
|
|
||||||
// Async Data
|
|
||||||
const [squidRoute, setSquidRoute] = useState<RouteData>();
|
|
||||||
const [transactionHash, setTransactionHash] = useState<string | undefined>();
|
|
||||||
|
|
||||||
// Form states
|
|
||||||
const [error, setError] = useState<Error | undefined>();
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setCurrentChain(defaultChain);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
abacusStateManager.clearTransferInputValues();
|
|
||||||
};
|
|
||||||
}, [defaultChain]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSourceToken(defaultToken);
|
|
||||||
setSquidRoute(undefined);
|
|
||||||
setFromAmount('');
|
|
||||||
}, [currentChain]);
|
|
||||||
|
|
||||||
// @ts-ignore ibcDenom exists but is not typed properly on 0xSquid SDK
|
|
||||||
const addressOrDenom = sourceToken?.ibcDenom || sourceToken?.address;
|
|
||||||
const bech32AddrPrefix = (currentChain as CosmosChain)?.bech32Config?.bech32PrefixAccAddr;
|
|
||||||
|
|
||||||
const { balance, isBalanceError, isBalanceLoading } = useAccountBalance({
|
|
||||||
addressOrDenom,
|
|
||||||
assetSymbol: sourceToken?.symbol,
|
|
||||||
bech32AddrPrefix,
|
|
||||||
chainId: currentChain?.chainId,
|
|
||||||
decimals: sourceToken?.decimals,
|
|
||||||
rpc: currentChain?.rpc,
|
|
||||||
isCosmosChain: currentChain?.chainType === 'cosmos',
|
|
||||||
});
|
|
||||||
|
|
||||||
// BN
|
|
||||||
const fromAmountBN = MustBigNumber(fromAmount);
|
|
||||||
const balanceBN = MustBigNumber(balance);
|
|
||||||
|
|
||||||
const requestDeposit = async () => {
|
|
||||||
if (!squidRoute) {
|
|
||||||
log('DepositForm/requestDeposit', new Error('No squid route'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
setIsLoading(true);
|
|
||||||
setError(undefined);
|
|
||||||
setTransactionHash(undefined);
|
|
||||||
|
|
||||||
const txReceipt = await squidRouter.executeRoute({
|
|
||||||
route: squidRoute,
|
|
||||||
signer: signerGraz || signerWagmi,
|
|
||||||
});
|
|
||||||
console.log('DepositForm/executeRoute', { txReceipt });
|
|
||||||
setFromAmount('');
|
|
||||||
setTransactionHash(txReceipt?.transactionHash);
|
|
||||||
} catch (error) {
|
|
||||||
log('DepositForm/executeRoute', error);
|
|
||||||
setError(error);
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const setAbacusTransferInput = (value: string | number) => {
|
|
||||||
abacusStateManager.setTransferValue({ value, field: TransferInputField.usdcSize });
|
|
||||||
};
|
|
||||||
|
|
||||||
const getRoute = useCallback(
|
|
||||||
({ newAmount, newSlippage }: { newAmount: string; newSlippage?: number }) => {
|
|
||||||
debouncedGetRoute.cancel();
|
|
||||||
|
|
||||||
if (MustBigNumber(newAmount).gt(0) && MustBigNumber(newAmount).lte(balanceBN)) {
|
|
||||||
debouncedGetRoute({
|
|
||||||
amount: newAmount,
|
|
||||||
currentChain,
|
|
||||||
cosmosAddress,
|
|
||||||
fromAmount,
|
|
||||||
setAbacusTransferInput,
|
|
||||||
setError,
|
|
||||||
setIsLoading,
|
|
||||||
setSquidRoute,
|
|
||||||
slippage: newSlippage || slippage,
|
|
||||||
sourceToken,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setSquidRoute(undefined);
|
|
||||||
setAbacusTransferInput(0);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[currentChain, sourceToken, fromAmount, slippage]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onChangeAmount = ({ value }: NumberFormatValues) => {
|
|
||||||
setFromAmount(value);
|
|
||||||
getRoute({ newAmount: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSetSlippage = (newSlippage: number) => {
|
|
||||||
setSlippage(newSlippage);
|
|
||||||
|
|
||||||
if (MustBigNumber(newSlippage).gt(0) && fromAmountBN.gt(0)) {
|
|
||||||
getRoute({ newAmount: fromAmount, newSlippage });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickMax = () => {
|
|
||||||
if (balance) {
|
|
||||||
setFromAmount(balanceBN.toString());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const amountInputReceipt = [
|
|
||||||
{
|
|
||||||
key: 'amount',
|
|
||||||
label: (
|
|
||||||
<span>
|
|
||||||
{stringGetter({ key: STRING_KEYS.AVAILABLE })}{' '}
|
|
||||||
{sourceToken && <Tag>{sourceToken.symbol}</Tag>}
|
|
||||||
</span>
|
|
||||||
),
|
|
||||||
value: (
|
|
||||||
<DiffOutput
|
|
||||||
type={OutputType.Asset}
|
|
||||||
value={balance}
|
|
||||||
newValue={balanceBN.minus(fromAmountBN).toString()}
|
|
||||||
sign={NumberSign.Negative}
|
|
||||||
hasInvalidNewValue={balanceBN.minus(fromAmountBN).isNegative()}
|
|
||||||
withDiff={Boolean(fromAmount && balance) && !fromAmountBN.isNaN()}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const errorMessage = getDepositFormErrorMessage({
|
|
||||||
error,
|
|
||||||
balance,
|
|
||||||
currentChain,
|
|
||||||
fromAmount,
|
|
||||||
sourceToken,
|
|
||||||
stringGetter,
|
|
||||||
});
|
|
||||||
|
|
||||||
const isDisabled =
|
|
||||||
Boolean(errorMessage) ||
|
|
||||||
!sourceToken ||
|
|
||||||
!currentChain ||
|
|
||||||
fromAmountBN.isNaN() ||
|
|
||||||
fromAmountBN.isZero() ||
|
|
||||||
!squidRoute;
|
|
||||||
|
|
||||||
const alertMessage = {
|
|
||||||
type: errorMessage ? AlertType.Error : AlertType.Info,
|
|
||||||
message:
|
|
||||||
errorMessage ||
|
|
||||||
(transactionHash && (
|
|
||||||
<span>
|
|
||||||
{stringGetter({ key: STRING_KEYS.VIEW_DETAILS })}
|
|
||||||
{' on '}
|
|
||||||
<Styled.Link href={`https://testnet.axelarscan.io/transfer/${transactionHash}`}>
|
|
||||||
Axelar
|
|
||||||
</Styled.Link>
|
|
||||||
.
|
|
||||||
</span>
|
|
||||||
)),
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Styled.Form
|
|
||||||
onSubmit={(e: FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
requestDeposit();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ChainSelectMenu
|
|
||||||
chains={chains}
|
|
||||||
currentChain={currentChain}
|
|
||||||
setCurrentChain={setCurrentChain}
|
|
||||||
/>
|
|
||||||
<TokenSelectMenu
|
|
||||||
tokens={tokens}
|
|
||||||
chain={currentChain}
|
|
||||||
sourceToken={sourceToken}
|
|
||||||
setSourceToken={(token: TokenData) => {
|
|
||||||
setSourceToken(token);
|
|
||||||
|
|
||||||
if (fromAmount) {
|
|
||||||
debouncedGetRoute({
|
|
||||||
amount: fromAmount,
|
|
||||||
currentChain,
|
|
||||||
cosmosAddress,
|
|
||||||
fromAmount,
|
|
||||||
setError,
|
|
||||||
setIsLoading,
|
|
||||||
setSquidRoute,
|
|
||||||
slippage,
|
|
||||||
sourceToken: token,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setSquidRoute(undefined);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<WithLabel label={stringGetter({ key: STRING_KEYS.AMOUNT })}>
|
|
||||||
<Styled.WithDetailsReceipt side="bottom" detailItems={amountInputReceipt}>
|
|
||||||
<Styled.FormInput
|
|
||||||
type={InputType.Number}
|
|
||||||
onChange={onChangeAmount}
|
|
||||||
value={fromAmount}
|
|
||||||
slotRight={
|
|
||||||
<Button size={ButtonSize.XSmall} onClick={onClickMax}>
|
|
||||||
{stringGetter({ key: STRING_KEYS.MAX })}
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Styled.WithDetailsReceipt>
|
|
||||||
</WithLabel>
|
|
||||||
{alertMessage.message && (
|
|
||||||
<Styled.AlertMessage type={alertMessage.type}>{alertMessage.message}</Styled.AlertMessage>
|
|
||||||
)}
|
|
||||||
<Styled.Footer>
|
|
||||||
<DepositButtonAndReceipt
|
|
||||||
isDisabled={isDisabled}
|
|
||||||
isLoading={isLoading || isBalanceLoading}
|
|
||||||
chainId={currentChain?.chainId}
|
|
||||||
setError={setError}
|
|
||||||
setSlippage={onSetSlippage}
|
|
||||||
slippage={slippage}
|
|
||||||
sourceToken={sourceToken}
|
|
||||||
squidRoute={squidRoute}
|
|
||||||
/>
|
|
||||||
</Styled.Footer>
|
|
||||||
</Styled.Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
|
||||||
|
|
||||||
Styled.Form = styled.form`
|
|
||||||
${layoutMixins.flexColumn}
|
|
||||||
gap: 1.5rem;
|
|
||||||
|
|
||||||
${layoutMixins.stickyArea1}
|
|
||||||
min-height: calc(100% - var(--stickyArea0-bottomHeight));
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.WithDetailsReceipt = styled(WithDetailsReceipt)`
|
|
||||||
--withReceipt-backgroundColor: var(--color-layer-2);
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.FormInput = styled(FormInput)`
|
|
||||||
--formInput-backgroundColor: var(--color-layer-4);
|
|
||||||
--formInput-input-height: 3.375rem;
|
|
||||||
border: 1px solid var(--color-layer-6);
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Link = styled(Link)`
|
|
||||||
color: var(--color-accent);
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.AlertMessage = styled(AlertMessage)`
|
|
||||||
cursor: normal;
|
|
||||||
user-select: none;
|
|
||||||
margin: 0;
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Footer = styled.footer`
|
|
||||||
${layoutMixins.stickyFooter}
|
|
||||||
margin-top: auto;
|
|
||||||
`;
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import type { ChainData, TokenData } from '@0xsquid/sdk';
|
|
||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
|
||||||
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
|
||||||
import { useStringGetter } from '@/hooks';
|
|
||||||
|
|
||||||
import { SearchSelectMenu } from '@/components/SearchSelectMenu';
|
|
||||||
import { Tag } from '@/components/Tag';
|
|
||||||
import { WithLabel } from '@/components/WithLabel';
|
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
|
||||||
|
|
||||||
type ElementProps = {
|
|
||||||
chain?: ChainData;
|
|
||||||
tokens?: TokenData[];
|
|
||||||
sourceToken?: TokenData;
|
|
||||||
setSourceToken: (token: TokenData) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TokenSelectMenu = ({
|
|
||||||
tokens = [],
|
|
||||||
chain,
|
|
||||||
sourceToken,
|
|
||||||
setSourceToken,
|
|
||||||
}: ElementProps) => {
|
|
||||||
const stringGetter = useStringGetter();
|
|
||||||
|
|
||||||
const tokenItems = Object.values(tokens)
|
|
||||||
.filter((token: TokenData) => token.chainId === chain?.chainId)
|
|
||||||
.map((token: TokenData) => ({
|
|
||||||
value: token.address,
|
|
||||||
label: token.name,
|
|
||||||
onSelect: () => setSourceToken(token),
|
|
||||||
slotBefore: <Styled.Img src={token.logoURI} alt="" />,
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SearchSelectMenu
|
|
||||||
disabled={!chain}
|
|
||||||
items={[
|
|
||||||
{
|
|
||||||
group: 'assets',
|
|
||||||
groupLabel: stringGetter({ key: STRING_KEYS.ASSET }),
|
|
||||||
items: tokenItems,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
label={stringGetter({ key: STRING_KEYS.ASSET })}
|
|
||||||
withReceiptItems={[
|
|
||||||
{
|
|
||||||
key: 'swap',
|
|
||||||
label: stringGetter({ key: STRING_KEYS.SWAP }),
|
|
||||||
value: sourceToken && (
|
|
||||||
<>
|
|
||||||
<Tag>{sourceToken?.symbol}</Tag>→<Tag>USDC</Tag>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
tooltip: 'swap',
|
|
||||||
withTooltipIcon: true,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
<Styled.AssetRow>
|
|
||||||
{sourceToken ? (
|
|
||||||
<>
|
|
||||||
<Styled.Img src={sourceToken?.logoURI} alt="" /> {sourceToken?.name}{' '}
|
|
||||||
<Tag>{sourceToken?.symbol}</Tag>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
stringGetter({ key: STRING_KEYS.SELECT_ASSET })
|
|
||||||
)}
|
|
||||||
</Styled.AssetRow>
|
|
||||||
</SearchSelectMenu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
|
||||||
|
|
||||||
Styled.AssetRow = styled.div`
|
|
||||||
${layoutMixins.row}
|
|
||||||
gap: 0.5rem;
|
|
||||||
color: var(--color-text-2);
|
|
||||||
font: var(--font-base-book);
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Img = styled.img`
|
|
||||||
width: 1.25rem;
|
|
||||||
height: 1.25rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
`;
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { useEffect, useState } from 'react';
|
|
||||||
import type { ChainData, TokenData } from '@0xsquid/sdk';
|
|
||||||
|
|
||||||
import squidRouter from '@/lib/squidRouter';
|
|
||||||
import { WAGMI_SUPPORTED_CHAINS } from '@/lib/wagmi';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description hook to return chains and tokens supported by squid
|
|
||||||
* @note Limit to EVM chains | Cosmos chains can only be used as destination on 0xSquid as of 02/21/2023
|
|
||||||
*/
|
|
||||||
export const useSquidData = (selectedChainType: 'cosmos' | 'evm') => {
|
|
||||||
const [, setHasInitialized] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
(async () => {
|
|
||||||
await squidRouter.initializeClient();
|
|
||||||
setHasInitialized(true);
|
|
||||||
})();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const chainData: ChainData[] = squidRouter.getChains();
|
|
||||||
const tokenData: TokenData[] = squidRouter.getTokens();
|
|
||||||
const wagmiChainIds = Object.fromEntries(
|
|
||||||
WAGMI_SUPPORTED_CHAINS.map((supportedChain) => [supportedChain.id, supportedChain])
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
chains: chainData.filter(({ chainId, chainType }) =>
|
|
||||||
selectedChainType === 'evm' ? wagmiChainIds[chainId] : chainType === 'cosmos'
|
|
||||||
),
|
|
||||||
tokens: tokenData,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -99,7 +99,8 @@ export const TradeForm = ({
|
|||||||
const { limitPrice, triggerPrice, trailingPercent } = price || {};
|
const { limitPrice, triggerPrice, trailingPercent } = price || {};
|
||||||
const hasUncommittedOrders = useSelector(calculateHasUncommittedOrders);
|
const hasUncommittedOrders = useSelector(calculateHasUncommittedOrders);
|
||||||
const currentInput = useSelector(getCurrentInput);
|
const currentInput = useSelector(getCurrentInput);
|
||||||
const { tickSizeDecimals } = useSelector(getCurrentMarketConfig, shallowEqual) || {};
|
const { tickSizeDecimals, stepSizeDecimals } =
|
||||||
|
useSelector(getCurrentMarketConfig, shallowEqual) || {};
|
||||||
|
|
||||||
const needsAdvancedOptions =
|
const needsAdvancedOptions =
|
||||||
needsGoodUntil || timeInForceOptions || executionOptions || needsPostOnly || needsReduceOnly;
|
needsGoodUntil || timeInForceOptions || executionOptions || needsPostOnly || needsReduceOnly;
|
||||||
@@ -120,6 +121,8 @@ export const TradeForm = ({
|
|||||||
const inputAlert = getTradeInputAlert({
|
const inputAlert = getTradeInputAlert({
|
||||||
abacusInputErrors: tradeErrors ?? [],
|
abacusInputErrors: tradeErrors ?? [],
|
||||||
stringGetter,
|
stringGetter,
|
||||||
|
stepSizeDecimals,
|
||||||
|
tickSizeDecimals,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (placeOrderError) {
|
if (placeOrderError) {
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ export const AdvancedTradeOptions = () => {
|
|||||||
{executionOptions && (
|
{executionOptions && (
|
||||||
<Styled.SelectMenu
|
<Styled.SelectMenu
|
||||||
value={execution}
|
value={execution}
|
||||||
|
label={stringGetter({ key: STRING_KEYS.EXECUTION })}
|
||||||
onValueChange={(selectedTimeInForceOption: string) =>
|
onValueChange={(selectedTimeInForceOption: string) =>
|
||||||
abacusStateManager.setTradeValue({
|
abacusStateManager.setTradeValue({
|
||||||
value: selectedTimeInForceOption,
|
value: selectedTimeInForceOption,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { Story } from '@ladle/react';
|
import type { Story } from '@ladle/react';
|
||||||
import styled, { AnyStyledComponent } from 'styled-components';
|
import styled, { AnyStyledComponent } from 'styled-components';
|
||||||
import { OrderSide } from '@dydxprotocol/v4-client';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import { PositionSide } from '@/constants/trade';
|
import { PositionSide } from '@/constants/trade';
|
||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { type Dispatch, type SetStateAction, useCallback, useMemo } from 'react'
|
|||||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||||
import { Root, Thumb, Track } from '@radix-ui/react-slider';
|
import { Root, Thumb, Track } from '@radix-ui/react-slider';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { OrderSide } from '@dydxprotocol/v4-client';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import { type Nullable, TradeInputField } from '@/constants/abacus';
|
import { type Nullable, TradeInputField } from '@/constants/abacus';
|
||||||
import { PositionSide } from '@/constants/trade';
|
import { PositionSide } from '@/constants/trade';
|
||||||
@@ -43,9 +43,7 @@ export const LeverageSlider = ({
|
|||||||
},
|
},
|
||||||
[PositionSide.Long]: {
|
[PositionSide.Long]: {
|
||||||
min:
|
min:
|
||||||
orderSide === OrderSide.BUY
|
orderSide === OrderSide.BUY ? leverageBN.toNumber() : maxLeverageBN.negated().toNumber(),
|
||||||
? leverageBN.toNumber()
|
|
||||||
: maxLeverageBN.negated().toNumber(),
|
|
||||||
max: orderSide === OrderSide.BUY ? maxLeverageBN.toNumber() : leverageBN.toNumber(),
|
max: orderSide === OrderSide.BUY ? maxLeverageBN.toNumber() : leverageBN.toNumber(),
|
||||||
midpoint:
|
midpoint:
|
||||||
orderSide === OrderSide.SELL
|
orderSide === OrderSide.SELL
|
||||||
@@ -56,9 +54,7 @@ export const LeverageSlider = ({
|
|||||||
},
|
},
|
||||||
[PositionSide.Short]: {
|
[PositionSide.Short]: {
|
||||||
min:
|
min:
|
||||||
orderSide === OrderSide.BUY
|
orderSide === OrderSide.BUY ? leverageBN.toNumber() : maxLeverageBN.negated().toNumber(),
|
||||||
? leverageBN.toNumber()
|
|
||||||
: maxLeverageBN.negated().toNumber(),
|
|
||||||
max: orderSide === OrderSide.BUY ? maxLeverageBN.toNumber() : leverageBN.toNumber(),
|
max: orderSide === OrderSide.BUY ? maxLeverageBN.toNumber() : leverageBN.toNumber(),
|
||||||
midpoint:
|
midpoint:
|
||||||
orderSide === OrderSide.BUY
|
orderSide === OrderSide.BUY
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Dispatch, SetStateAction } from 'react';
|
import type { Dispatch, SetStateAction } from 'react';
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import styled, { AnyStyledComponent } from 'styled-components';
|
import styled, { AnyStyledComponent } from 'styled-components';
|
||||||
import { OrderSide } from '@dydxprotocol/v4-client';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import { TradeInputField, Nullable } from '@/constants/abacus';
|
import { TradeInputField, Nullable } from '@/constants/abacus';
|
||||||
import { ButtonShape } from '@/constants/buttons';
|
import { ButtonShape } from '@/constants/buttons';
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
import { OrderSide } from '@dydxprotocol/v4-client';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
import type { TradeInputSummary } from '@/constants/abacus';
|
import type { TradeInputSummary } from '@/constants/abacus';
|
||||||
import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
|
import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user