Compare commits
16
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b26106ba20 | ||
|
|
944fc6dc95 | ||
|
|
91a97a1c68 | ||
|
|
c5f736d805 | ||
|
|
3769f7078c | ||
|
|
edc9fadfee | ||
|
|
5e2d426cd1 | ||
|
|
766209baf1 | ||
|
|
906faa554d | ||
|
|
c339eeccde | ||
|
|
bd03fb73ca | ||
|
|
31825a8a52 | ||
|
|
8d5bf1a721 | ||
|
|
7f3d57b46a | ||
|
|
c292c84c2c | ||
|
|
2d37ff676b |
@@ -98,6 +98,11 @@ For the "Build & Development Settings", we recommend the following:
|
|||||||
- Framework Preset: `Vite`
|
- Framework Preset: `Vite`
|
||||||
- Build Command (override): `pnpm run build`
|
- Build Command (override): `pnpm run build`
|
||||||
|
|
||||||
|
By default, the dev server runs in development mode and the build command runs in production mode. To override the default mode, you can pass in the `--mode` option flag. For example, if you want to build your app for testnet:
|
||||||
|
```
|
||||||
|
pnpm run build --mode testnet
|
||||||
|
```
|
||||||
|
|
||||||
If you wish to incorporate analytics via Amplitude and Bugsnag, you can use our scripts:
|
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.
|
`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.
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -39,9 +39,9 @@
|
|||||||
"@cosmjs/proto-signing": "^0.31.0",
|
"@cosmjs/proto-signing": "^0.31.0",
|
||||||
"@cosmjs/stargate": "^0.31.0",
|
"@cosmjs/stargate": "^0.31.0",
|
||||||
"@cosmjs/tendermint-rpc": "^0.31.0",
|
"@cosmjs/tendermint-rpc": "^0.31.0",
|
||||||
"@dydxprotocol/v4-abacus": "^1.0.10",
|
"@dydxprotocol/v4-abacus": "^1.0.24",
|
||||||
"@dydxprotocol/v4-client-js": "^1.0.0",
|
"@dydxprotocol/v4-client-js": "^1.0.0",
|
||||||
"@dydxprotocol/v4-localization": "^1.0.0",
|
"@dydxprotocol/v4-localization": "^1.0.5",
|
||||||
"@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",
|
||||||
@@ -96,6 +96,7 @@
|
|||||||
"long": "^5.2.3",
|
"long": "^5.2.3",
|
||||||
"luxon": "^3.3.0",
|
"luxon": "^3.3.0",
|
||||||
"qr-code-styling": "1.6.0-rc.1",
|
"qr-code-styling": "1.6.0-rc.1",
|
||||||
|
"query-string": "^8.1.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-aria": "^3.25.0",
|
"react-aria": "^3.25.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|||||||
Generated
+11
-12
@@ -27,14 +27,14 @@ dependencies:
|
|||||||
specifier: ^0.31.0
|
specifier: ^0.31.0
|
||||||
version: 0.31.0
|
version: 0.31.0
|
||||||
'@dydxprotocol/v4-abacus':
|
'@dydxprotocol/v4-abacus':
|
||||||
specifier: ^1.0.10
|
specifier: ^1.0.24
|
||||||
version: 1.0.10
|
version: 1.0.24
|
||||||
'@dydxprotocol/v4-client-js':
|
'@dydxprotocol/v4-client-js':
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
'@dydxprotocol/v4-localization':
|
'@dydxprotocol/v4-localization':
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.5
|
||||||
version: 1.0.0
|
version: 1.0.5
|
||||||
'@ethersproject/providers':
|
'@ethersproject/providers':
|
||||||
specifier: ^5.7.2
|
specifier: ^5.7.2
|
||||||
version: 5.7.2
|
version: 5.7.2
|
||||||
@@ -197,6 +197,9 @@ dependencies:
|
|||||||
qr-code-styling:
|
qr-code-styling:
|
||||||
specifier: 1.6.0-rc.1
|
specifier: 1.6.0-rc.1
|
||||||
version: 1.6.0-rc.1
|
version: 1.6.0-rc.1
|
||||||
|
query-string:
|
||||||
|
specifier: ^8.1.0
|
||||||
|
version: 8.1.0
|
||||||
react:
|
react:
|
||||||
specifier: ^18.2.0
|
specifier: ^18.2.0
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
@@ -979,8 +982,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
|
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@dydxprotocol/v4-abacus@1.0.10:
|
/@dydxprotocol/v4-abacus@1.0.24:
|
||||||
resolution: {integrity: sha512-SdxI/JcyxM1ydtR9ImWujeurea3gbxj37qYnw1wz829BQiZ05uNEtll3xJCI0MjpG+01ARsxVys7SHbpjSxwOg==}
|
resolution: {integrity: sha512-wDGSjkrc3Se6Ev7UTjPgJV7PiyzZSz2mJwOTZikLwH7W3k1iPYGQmaCd7TudFk8h2aSdlEwNsQBCf0sLoyvHaQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@dydxprotocol/v4-client-js@1.0.0:
|
/@dydxprotocol/v4-client-js@1.0.0:
|
||||||
@@ -1011,8 +1014,8 @@ packages:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@dydxprotocol/v4-localization@1.0.0:
|
/@dydxprotocol/v4-localization@1.0.5:
|
||||||
resolution: {integrity: sha512-U7QjOlzpfSAVEchkNDB3xYRx7okzdeYi83XsnHwI+EHa2quFm/2Y2dhrBkT5fqpZmhcwxb/vG8AZmFSToOf9hA==}
|
resolution: {integrity: sha512-zZm3GFp/ORz9YjigQFVBlcWmaxYY+XEdIw8AWN5nEbsuiA2Jay+/OA7lO9+nV1J1p1T+TgLo9iczIHJsAo3IsA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@dydxprotocol/v4-proto@0.4.1:
|
/@dydxprotocol/v4-proto@0.4.1:
|
||||||
@@ -7875,7 +7878,6 @@ packages:
|
|||||||
/decode-uri-component@0.4.1:
|
/decode-uri-component@0.4.1:
|
||||||
resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==}
|
resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/deep-eql@4.1.3:
|
/deep-eql@4.1.3:
|
||||||
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
|
resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==}
|
||||||
@@ -8945,7 +8947,6 @@ packages:
|
|||||||
/filter-obj@5.1.0:
|
/filter-obj@5.1.0:
|
||||||
resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==}
|
resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==}
|
||||||
engines: {node: '>=14.16'}
|
engines: {node: '>=14.16'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/find-cache-dir@3.3.2:
|
/find-cache-dir@3.3.2:
|
||||||
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
|
resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
|
||||||
@@ -11876,7 +11877,6 @@ packages:
|
|||||||
decode-uri-component: 0.4.1
|
decode-uri-component: 0.4.1
|
||||||
filter-obj: 5.1.0
|
filter-obj: 5.1.0
|
||||||
split-on-first: 3.0.0
|
split-on-first: 3.0.0
|
||||||
dev: true
|
|
||||||
|
|
||||||
/queue-microtask@1.2.3:
|
/queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
@@ -12701,7 +12701,6 @@ packages:
|
|||||||
/split-on-first@3.0.0:
|
/split-on-first@3.0.0:
|
||||||
resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==}
|
resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/split2@4.2.0:
|
/split2@4.2.0:
|
||||||
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||||
|
|||||||
+74
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
"iOS": {
|
"ios": {
|
||||||
"scheme": "dydxv4"
|
"scheme": "dydxv4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -400,6 +400,77 @@
|
|||||||
"signTypedDataDomainName": "dYdX Chain"
|
"signTypedDataDomainName": "dYdX Chain"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dydxprotocol-staging-forced-update": {
|
||||||
|
"name": "v4 Staging Forced Update",
|
||||||
|
"ethereumChainId": "5",
|
||||||
|
"dydxChainId": "dydxprotocol-testnet",
|
||||||
|
"chainName": "dYdX Chain",
|
||||||
|
"chainLogo": "dydx-chain.png",
|
||||||
|
"isMainNet": false,
|
||||||
|
"tokens": {
|
||||||
|
"chain": {
|
||||||
|
"name": "Dv4TNT",
|
||||||
|
"denom": "adv4tnt",
|
||||||
|
"decimals": 18,
|
||||||
|
"image": "/currencies/dydx.png"
|
||||||
|
},
|
||||||
|
"usdc": {
|
||||||
|
"name": "USDC",
|
||||||
|
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||||
|
"gasDenom": "uusdc",
|
||||||
|
"decimals": 6,
|
||||||
|
"image": "/currencies/usdc.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"endpoints": {
|
||||||
|
"indexers": [
|
||||||
|
{
|
||||||
|
"api": "https://indexer.v4staging.dydx.exchange",
|
||||||
|
"socket": "wss://indexer.v4staging.dydx.exchange"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"faucet": "https://faucet.v4staging.dydx.exchange",
|
||||||
|
"validators": [
|
||||||
|
"https://validator.v4staging.dydx.exchange"
|
||||||
|
],
|
||||||
|
"0xsquid": "https://testnet.api.squidrouter.com"
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"tos": "https://dydx.exchange/v4-terms",
|
||||||
|
"privacy": "https://dydx.exchange/privacy",
|
||||||
|
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
|
||||||
|
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
|
||||||
|
"documentation": "https://v4-teacher.vercel.app/",
|
||||||
|
"community": "https://discord.com/invite/dydx",
|
||||||
|
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
|
||||||
|
"blogs": "https://www.dydx.foundation/blog"
|
||||||
|
},
|
||||||
|
"wallets": {
|
||||||
|
"walletconnect": {
|
||||||
|
"client": {
|
||||||
|
"name": "dYdX v4",
|
||||||
|
"description": "dYdX v4 App",
|
||||||
|
"iconUrl": "/logos/dydx-x.png"
|
||||||
|
},
|
||||||
|
"v2": {
|
||||||
|
"projectId": "47559b2ec96c09aed9ff2cb54a31ab0e"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"walletSegue": {
|
||||||
|
"callbackUrl": "/walletsegue"
|
||||||
|
},
|
||||||
|
"images": "/wallets/",
|
||||||
|
"signTypedDataAction": "dYdX Chain Onboarding",
|
||||||
|
"signTypedDataDomainName": "dYdX Chain"
|
||||||
|
},
|
||||||
|
"apps": {
|
||||||
|
"ios": {
|
||||||
|
"minimalVersion": "1.0",
|
||||||
|
"build":40000,
|
||||||
|
"url": "https://apps.apple.com/app/dydx/id1564787350"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dydxprotocol-staging-west": {
|
"dydxprotocol-staging-west": {
|
||||||
"name": "v4 Staging West",
|
"name": "v4 Staging West",
|
||||||
"ethereumChainId": "5",
|
"ethereumChainId": "5",
|
||||||
@@ -893,6 +964,7 @@
|
|||||||
"privacy": "https://dydx.exchange/privacy",
|
"privacy": "https://dydx.exchange/privacy",
|
||||||
"statusPage": "https://status.v4testnet.dydx.exchange/",
|
"statusPage": "https://status.v4testnet.dydx.exchange/",
|
||||||
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
|
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
|
||||||
|
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
|
||||||
"documentation": "https://docs.dydx.exchange/",
|
"documentation": "https://docs.dydx.exchange/",
|
||||||
"community": "https://discord.com/invite/dydx",
|
"community": "https://discord.com/invite/dydx",
|
||||||
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
|
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
|
||||||
@@ -1033,7 +1105,7 @@
|
|||||||
"tos": "[HTTP link to TOS]",
|
"tos": "[HTTP link to TOS]",
|
||||||
"privacy": "[HTTP link to Privacy Policy]",
|
"privacy": "[HTTP link to Privacy Policy]",
|
||||||
"mintscan": "[HTTP link to Mintscan, with {tx_hash} placeholder]",
|
"mintscan": "[HTTP link to Mintscan, with {tx_hash} placeholder]",
|
||||||
"mintscanBase": "[HTTP link to TOS mintsacn base url]",
|
"mintscanBase": "[HTTP link to TOS mintscan base url]",
|
||||||
"feedback": "[HTTP link to feedback form, can be null]",
|
"feedback": "[HTTP link to feedback form, can be null]",
|
||||||
"blogs": "[HTTP link to blogs, can be null]",
|
"blogs": "[HTTP link to blogs, can be null]",
|
||||||
"foundation": "[HTTP link to foundation, can be null]",
|
"foundation": "[HTTP link to foundation, can be null]",
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { LocalNotificationsProvider } from '@/hooks/useLocalNotifications';
|
|||||||
import { RestrictionProvider } from '@/hooks/useRestrictions';
|
import { RestrictionProvider } from '@/hooks/useRestrictions';
|
||||||
import { SubaccountProvider } from '@/hooks/useSubaccount';
|
import { SubaccountProvider } from '@/hooks/useSubaccount';
|
||||||
import { SquidProvider } from '@/hooks/useSquid';
|
import { SquidProvider } from '@/hooks/useSquid';
|
||||||
|
import { TestFlagsProvider } from '@/hooks/useTestFlags';
|
||||||
|
|
||||||
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
|
||||||
|
|
||||||
@@ -123,6 +124,7 @@ const providers = [
|
|||||||
wrapProvider(QueryClientProvider, { client: queryClient }),
|
wrapProvider(QueryClientProvider, { client: queryClient }),
|
||||||
wrapProvider(GrazProvider),
|
wrapProvider(GrazProvider),
|
||||||
wrapProvider(WagmiConfig, { config }),
|
wrapProvider(WagmiConfig, { config }),
|
||||||
|
wrapProvider(TestFlagsProvider),
|
||||||
wrapProvider(LocaleProvider),
|
wrapProvider(LocaleProvider),
|
||||||
wrapProvider(RestrictionProvider),
|
wrapProvider(RestrictionProvider),
|
||||||
wrapProvider(DydxProvider),
|
wrapProvider(DydxProvider),
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
import environments from '../../public/configs/env.json';
|
import environments from '../../public/configs/env.json';
|
||||||
|
|
||||||
// TODO: Update for Mainnet deployment
|
const CURRENT_MODE = ({
|
||||||
export const AVAILABLE_ENVIRONMENTS =
|
production: 'MAINNET',
|
||||||
import.meta.env.MODE === 'production'
|
testnet: 'TESTNET',
|
||||||
? environments.deployments.TESTNET
|
staging: 'DEV',
|
||||||
: environments.deployments.DEV;
|
development: 'DEV',
|
||||||
|
}[import.meta.env.MODE] ?? 'MAINNET') as 'MAINNET' | 'TESTNET' | 'DEV';
|
||||||
|
|
||||||
export const CURRENT_ABACUS_DEPLOYMENT = import.meta.env.MODE === 'production' ? 'TESTNET' : 'DEV';
|
export const isMainnet = CURRENT_MODE === 'MAINNET';
|
||||||
|
export const isTestnet = CURRENT_MODE === 'TESTNET';
|
||||||
|
export const isDev = CURRENT_MODE === 'DEV';
|
||||||
|
|
||||||
|
export const AVAILABLE_ENVIRONMENTS = environments.deployments[CURRENT_MODE];
|
||||||
|
export const CURRENT_ABACUS_DEPLOYMENT = CURRENT_MODE;
|
||||||
export const ENVIRONMENT_CONFIG_MAP = environments.environments;
|
export const ENVIRONMENT_CONFIG_MAP = environments.environments;
|
||||||
export type DydxNetwork = keyof typeof ENVIRONMENT_CONFIG_MAP;
|
export type DydxNetwork = keyof typeof ENVIRONMENT_CONFIG_MAP;
|
||||||
export const DEFAULT_APP_ENVIRONMENT = AVAILABLE_ENVIRONMENTS.default as DydxNetwork;
|
export const DEFAULT_APP_ENVIRONMENT = AVAILABLE_ENVIRONMENTS.default as DydxNetwork;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ const useAccountsContext = () => {
|
|||||||
selectedWalletError,
|
selectedWalletError,
|
||||||
evmAddress,
|
evmAddress,
|
||||||
signerWagmi,
|
signerWagmi,
|
||||||
|
publicClientWagmi,
|
||||||
dydxAddress: connectedDydxAddress,
|
dydxAddress: connectedDydxAddress,
|
||||||
signerGraz,
|
signerGraz,
|
||||||
} = useWalletConnection();
|
} = useWalletConnection();
|
||||||
@@ -296,6 +297,7 @@ const useAccountsContext = () => {
|
|||||||
// Wallet connection (EVM)
|
// Wallet connection (EVM)
|
||||||
evmAddress,
|
evmAddress,
|
||||||
signerWagmi,
|
signerWagmi,
|
||||||
|
publicClientWagmi,
|
||||||
|
|
||||||
// Wallet connection (Cosmos)
|
// Wallet connection (Cosmos)
|
||||||
signerGraz,
|
signerGraz,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { isDev } from '@/constants/networks';
|
||||||
import { WalletType } from '@/constants/wallets';
|
import { WalletType } from '@/constants/wallets';
|
||||||
|
|
||||||
import { isTruthy } from '@/lib/isTruthy';
|
import { isTruthy } from '@/lib/isTruthy';
|
||||||
@@ -6,7 +7,7 @@ export const useDisplayedWallets = () => {
|
|||||||
return [
|
return [
|
||||||
WalletType.MetaMask,
|
WalletType.MetaMask,
|
||||||
|
|
||||||
import.meta.env.MODE !== 'production' && WalletType.Keplr,
|
isDev && WalletType.Keplr,
|
||||||
|
|
||||||
WalletType.WalletConnect2,
|
WalletType.WalletConnect2,
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,16 @@ const useLocalNotificationsContext = () => {
|
|||||||
status: currentStatus,
|
status: currentStatus,
|
||||||
} of transferNotifications) {
|
} of transferNotifications) {
|
||||||
try {
|
try {
|
||||||
if (currentStatus && currentStatus?.squidTransactionStatus !== 'ongoing') continue;
|
// skip if error is returned or if the transaction is not ongoing
|
||||||
|
if (
|
||||||
|
// @ts-ignore status.errors is not in the type definition but can be returned
|
||||||
|
currentStatus?.errors ||
|
||||||
|
currentStatus?.error ||
|
||||||
|
(currentStatus?.squidTransactionStatus &&
|
||||||
|
currentStatus?.squidTransactionStatus !== 'ongoing')
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId });
|
const status = await squid?.getStatus({ transactionId: txHash, toChainId, fromChainId });
|
||||||
if (status) statuses[txHash] = status;
|
if (status) statuses[txHash] = status;
|
||||||
|
|||||||
@@ -7,9 +7,11 @@ import { AlertType } from '@/constants/alerts';
|
|||||||
import { AbacusOrderStatus, ORDER_SIDES } from '@/constants/abacus';
|
import { AbacusOrderStatus, ORDER_SIDES } from '@/constants/abacus';
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
import { STRING_KEYS, StringKey } from '@/constants/localization';
|
import { STRING_KEYS, StringKey } from '@/constants/localization';
|
||||||
|
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||||
import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications';
|
import { type NotificationTypeConfig, NotificationType } from '@/constants/notifications';
|
||||||
import { ORDER_SIDE_STRINGS } from '@/constants/trade';
|
import { ORDER_SIDE_STRINGS } from '@/constants/trade';
|
||||||
|
|
||||||
|
import { useSelectedNetwork } from '@/hooks';
|
||||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||||
|
|
||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
@@ -23,7 +25,6 @@ import { OrderStatusIcon } from '@/views/OrderStatusIcon';
|
|||||||
|
|
||||||
import { useStringGetter } from './useStringGetter';
|
import { useStringGetter } from './useStringGetter';
|
||||||
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
|
import { TransferStatusSteps } from '@/views/TransferStatusSteps';
|
||||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
|
||||||
|
|
||||||
export const notificationTypes = [
|
export const notificationTypes = [
|
||||||
{
|
{
|
||||||
@@ -124,6 +125,7 @@ export const notificationTypes = [
|
|||||||
useTrigger: ({ trigger }) => {
|
useTrigger: ({ trigger }) => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const { transferNotifications } = useLocalNotifications();
|
const { transferNotifications } = useLocalNotifications();
|
||||||
|
const { selectedNetwork } = useSelectedNetwork();
|
||||||
|
|
||||||
const getTitleStringKey = useCallback((type: 'deposit' | 'withdrawal', finished: boolean) => {
|
const getTitleStringKey = useCallback((type: 'deposit' | 'withdrawal', finished: boolean) => {
|
||||||
if (type === 'deposit' && !finished) return STRING_KEYS.DEPOSIT_IN_PROGRESS;
|
if (type === 'deposit' && !finished) return STRING_KEYS.DEPOSIT_IN_PROGRESS;
|
||||||
@@ -136,7 +138,7 @@ export const notificationTypes = [
|
|||||||
for (const transfer of transferNotifications) {
|
for (const transfer of transferNotifications) {
|
||||||
const { fromChainId, status, txHash, toAmount } = transfer;
|
const { fromChainId, status, txHash, toAmount } = transfer;
|
||||||
const finished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
|
const finished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
|
||||||
const type = fromChainId === TESTNET_CHAIN_ID ? 'withdrawal' : 'deposit';
|
const type = fromChainId === ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId ? 'withdrawal' : 'deposit';
|
||||||
// @ts-ignore status.errors is not in the type definition but can be returned
|
// @ts-ignore status.errors is not in the type definition but can be returned
|
||||||
const error = status?.errors?.length ? status?.errors[0] : status?.error;
|
const error = status?.errors?.length ? status?.errors[0] : status?.error;
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { getSelectedNetwork } from '@/state/appSelectors';
|
|||||||
|
|
||||||
export const NATIVE_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
export const NATIVE_TOKEN_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
||||||
|
|
||||||
export const STATUS_ERROR_GRACE_PERIOD = 120_000;
|
export const STATUS_ERROR_GRACE_PERIOD = 300_000;
|
||||||
|
|
||||||
const useSquidContext = () => {
|
const useSquidContext = () => {
|
||||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||||
|
|||||||
+91
-60
@@ -48,7 +48,7 @@ export const useSubaccount = () => useContext(SubaccountContext);
|
|||||||
|
|
||||||
export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWallet }) => {
|
export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: LocalWallet }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { usdcDenom } = useTokenConfigs();
|
const { usdcDenom, usdcDecimals } = useTokenConfigs();
|
||||||
const { compositeClient, faucetClient } = useDydxClient();
|
const { compositeClient, faucetClient } = useDydxClient();
|
||||||
|
|
||||||
const { getFaucetFunds } = useMemo(
|
const { getFaucetFunds } = useMemo(
|
||||||
@@ -79,16 +79,34 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
subaccountClient: SubaccountClient;
|
subaccountClient: SubaccountClient;
|
||||||
assetId?: number;
|
assetId?: number;
|
||||||
amount: number;
|
amount: number;
|
||||||
}) => await compositeClient?.depositToSubaccount(subaccountClient, amount.toString()),
|
}) => {
|
||||||
|
try {
|
||||||
|
return await compositeClient?.depositToSubaccount(
|
||||||
|
subaccountClient,
|
||||||
|
amount.toFixed(usdcDecimals)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
log('useSubaccount/depositToSubaccount', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
withdrawFromSubaccount: async ({
|
withdrawFromSubaccount: async ({
|
||||||
subaccountClient,
|
subaccountClient,
|
||||||
amount,
|
amount,
|
||||||
}: {
|
}: {
|
||||||
subaccountClient: SubaccountClient;
|
subaccountClient: SubaccountClient;
|
||||||
amount: number;
|
amount: number;
|
||||||
}) => await compositeClient?.withdrawFromSubaccount(subaccountClient, amount.toString()),
|
}) => {
|
||||||
|
try {
|
||||||
|
return await compositeClient?.withdrawFromSubaccount(
|
||||||
|
subaccountClient,
|
||||||
|
amount.toFixed(usdcDecimals)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
log('useSubaccount/withdrawFromSubaccount', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
transferFromSubaccountToAddress: async ({
|
transferFromSubaccountToAddress: async ({
|
||||||
subaccountClient,
|
subaccountClient,
|
||||||
assetId = 0,
|
assetId = 0,
|
||||||
@@ -99,27 +117,33 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
assetId?: number;
|
assetId?: number;
|
||||||
amount: number;
|
amount: number;
|
||||||
recipient: string;
|
recipient: string;
|
||||||
}) =>
|
}) => {
|
||||||
await compositeClient?.validatorClient.post.send(
|
try {
|
||||||
subaccountClient?.wallet,
|
return await compositeClient?.validatorClient.post.send(
|
||||||
() =>
|
subaccountClient?.wallet,
|
||||||
new Promise((resolve) => {
|
() =>
|
||||||
const msg =
|
new Promise((resolve) => {
|
||||||
compositeClient?.validatorClient.post.composer.composeMsgWithdrawFromSubaccount(
|
const msg =
|
||||||
subaccountClient.address,
|
compositeClient?.validatorClient.post.composer.composeMsgWithdrawFromSubaccount(
|
||||||
subaccountClient.subaccountNumber,
|
subaccountClient.address,
|
||||||
assetId,
|
subaccountClient.subaccountNumber,
|
||||||
Long.fromNumber(amount * QUANTUM_MULTIPLIER),
|
assetId,
|
||||||
recipient
|
Long.fromNumber(amount * QUANTUM_MULTIPLIER),
|
||||||
);
|
recipient
|
||||||
|
);
|
||||||
|
|
||||||
resolve([msg]);
|
resolve([msg]);
|
||||||
}),
|
}),
|
||||||
false,
|
false,
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
Method.BroadcastTxCommit
|
Method.BroadcastTxCommit
|
||||||
),
|
);
|
||||||
|
} catch (error) {
|
||||||
|
log('useSubaccount/transferFromSubaccountToAddress', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
transferNativeToken: async ({
|
transferNativeToken: async ({
|
||||||
subaccountClient,
|
subaccountClient,
|
||||||
@@ -129,24 +153,30 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
subaccountClient: SubaccountClient;
|
subaccountClient: SubaccountClient;
|
||||||
amount: number;
|
amount: number;
|
||||||
recipient: string;
|
recipient: string;
|
||||||
}) =>
|
}) => {
|
||||||
await compositeClient?.validatorClient.post.send(
|
try {
|
||||||
subaccountClient.wallet,
|
return await compositeClient?.validatorClient.post.send(
|
||||||
() =>
|
subaccountClient.wallet,
|
||||||
new Promise((resolve) => {
|
() =>
|
||||||
const msg = compositeClient?.sendTokenMessage(
|
new Promise((resolve) => {
|
||||||
subaccountClient.wallet,
|
const msg = compositeClient?.sendTokenMessage(
|
||||||
amount.toString(),
|
subaccountClient.wallet,
|
||||||
recipient,
|
amount.toString(),
|
||||||
);
|
recipient
|
||||||
|
);
|
||||||
|
|
||||||
resolve([msg]);
|
resolve([msg]);
|
||||||
}),
|
}),
|
||||||
false,
|
false,
|
||||||
compositeClient?.validatorClient?.post.defaultDydxGasPrice,
|
compositeClient?.validatorClient?.post.defaultDydxGasPrice,
|
||||||
undefined,
|
undefined,
|
||||||
Method.BroadcastTxCommit
|
Method.BroadcastTxCommit
|
||||||
),
|
);
|
||||||
|
} catch (error) {
|
||||||
|
log('useSubaccount/transferNativeToken', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
sendSquidWithdrawFromSubaccount: async ({
|
sendSquidWithdrawFromSubaccount: async ({
|
||||||
subaccountClient,
|
subaccountClient,
|
||||||
@@ -158,20 +188,27 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
payload: string;
|
payload: string;
|
||||||
}) => {
|
}) => {
|
||||||
if (!compositeClient) throw new Error('client not initialized');
|
if (!compositeClient) throw new Error('client not initialized');
|
||||||
|
try {
|
||||||
|
const transaction = JSON.parse(payload);
|
||||||
|
|
||||||
const transaction = JSON.parse(payload);
|
const msg = compositeClient.withdrawFromSubaccountMessage(
|
||||||
|
subaccountClient,
|
||||||
|
amount.toFixed(usdcDecimals)
|
||||||
|
);
|
||||||
|
const ibcMsg: EncodeObject = {
|
||||||
|
typeUrl: transaction.msgTypeUrl,
|
||||||
|
value: transaction.msg,
|
||||||
|
};
|
||||||
|
|
||||||
const msg = compositeClient.withdrawFromSubaccountMessage(subaccountClient, amount.toString());
|
return await compositeClient.send(
|
||||||
const ibcMsg: EncodeObject = {
|
subaccountClient.wallet,
|
||||||
typeUrl: transaction.msgTypeUrl,
|
() => Promise.resolve([msg, ibcMsg]),
|
||||||
value: transaction.msg,
|
false
|
||||||
};
|
);
|
||||||
|
} catch (error) {
|
||||||
return await compositeClient.send(
|
log('useSubaccount/sendSquidWithdrawFromSubaccount', error);
|
||||||
subaccountClient.wallet,
|
throw error;
|
||||||
() => Promise.resolve([msg, ibcMsg]),
|
}
|
||||||
false
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[compositeClient]
|
[compositeClient]
|
||||||
@@ -313,11 +350,6 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
placeOrderParams = abacusStateManager.placeOrder(callback);
|
placeOrderParams = abacusStateManager.placeOrder(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
track(AnalyticsEvent.TradePlaceOrder, {
|
|
||||||
...placeOrderParams,
|
|
||||||
isClosePosition,
|
|
||||||
} as HumanReadablePlaceOrderPayload & { isClosePosition: boolean });
|
|
||||||
|
|
||||||
return placeOrderParams;
|
return placeOrderParams;
|
||||||
},
|
},
|
||||||
[subaccountClient]
|
[subaccountClient]
|
||||||
@@ -346,7 +378,6 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
}) => {
|
}) => {
|
||||||
const callback = (success: boolean, parsingError?: Nullable<ParsingError>) => {
|
const callback = (success: boolean, parsingError?: Nullable<ParsingError>) => {
|
||||||
if (success) {
|
if (success) {
|
||||||
track(AnalyticsEvent.TradeCancelOrder);
|
|
||||||
onSuccess?.();
|
onSuccess?.();
|
||||||
} else {
|
} else {
|
||||||
onError?.({ errorStringKey: parsingError?.stringKey });
|
onError?.({ errorStringKey: parsingError?.stringKey });
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
import { useLocation } from 'react-router-dom';
|
||||||
|
import queryString from 'query-string';
|
||||||
|
|
||||||
|
class TestFlags {
|
||||||
|
public queryParams: queryString.ParsedQuery<string>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.queryParams = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
setQueryParams(flags: queryString.ParsedQuery<string>) {
|
||||||
|
this.queryParams = flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayInitializingMarkets() {
|
||||||
|
return !!this.queryParams.displayInitializingMarkets;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const testFlags = new TestFlags();
|
||||||
|
|
||||||
|
const useTestFlagsContext = () => {
|
||||||
|
const [queryParams, setQueryParams] = useState<queryString.ParsedQuery<string>>({});
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const parsedQueryParams = queryString.parse(location.search.substring(-1));
|
||||||
|
testFlags.setQueryParams(parsedQueryParams);
|
||||||
|
setQueryParams(parsedQueryParams);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...queryParams,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type TestFlagsContextType = ReturnType<typeof useTestFlagsContext>;
|
||||||
|
const TestFlagsContext = createContext<TestFlagsContextType>({} as TestFlagsContextType);
|
||||||
|
TestFlagsContext.displayName = 'TestFlags';
|
||||||
|
|
||||||
|
export const TestFlagsProvider = ({ ...props }) => (
|
||||||
|
<TestFlagsContext.Provider value={useTestFlagsContext()} {...props} />
|
||||||
|
);
|
||||||
|
|
||||||
|
export const useTestFlags = () => useContext(TestFlagsContext);
|
||||||
@@ -19,8 +19,10 @@ export const useTokenConfigs = (): {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
usdcDenom: string;
|
usdcDenom: string;
|
||||||
|
usdcDecimals: number;
|
||||||
usdcLabel: string;
|
usdcLabel: string;
|
||||||
chainTokenDenom: string;
|
chainTokenDenom: string;
|
||||||
|
chainTokenDecimals: number;
|
||||||
chainTokenLabel: string;
|
chainTokenLabel: string;
|
||||||
} => {
|
} => {
|
||||||
const { selectedNetwork } = useSelectedNetwork();
|
const { selectedNetwork } = useSelectedNetwork();
|
||||||
@@ -29,8 +31,10 @@ export const useTokenConfigs = (): {
|
|||||||
return {
|
return {
|
||||||
tokensConfigs,
|
tokensConfigs,
|
||||||
usdcDenom: tokensConfigs[DydxChainAsset.USDC].denom,
|
usdcDenom: tokensConfigs[DydxChainAsset.USDC].denom,
|
||||||
|
usdcDecimals: tokensConfigs[DydxChainAsset.USDC].decimals,
|
||||||
usdcLabel: tokensConfigs[DydxChainAsset.USDC].name,
|
usdcLabel: tokensConfigs[DydxChainAsset.USDC].name,
|
||||||
chainTokenDenom: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom,
|
chainTokenDenom: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom,
|
||||||
|
chainTokenDecimals: tokensConfigs[DydxChainAsset.CHAINTOKEN].decimals,
|
||||||
chainTokenLabel: tokensConfigs[DydxChainAsset.CHAINTOKEN].name,
|
chainTokenLabel: tokensConfigs[DydxChainAsset.CHAINTOKEN].name,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState, useMemo } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { LocalStorageKey } from '@/constants/localStorage';
|
import { LocalStorageKey } from '@/constants/localStorage';
|
||||||
@@ -90,19 +90,21 @@ export const useWalletConnection = () => {
|
|||||||
|
|
||||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||||
const walletConnectConfig = ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.walletconnect;
|
const walletConnectConfig = ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.walletconnect;
|
||||||
|
const wagmiConnector = useMemo(
|
||||||
const { connectAsync: connectWagmi } =
|
() =>
|
||||||
walletType && walletConnectionType
|
walletType && walletConnectionType
|
||||||
? useConnectWagmi({
|
? resolveWagmiConnector({
|
||||||
connector: resolveWagmiConnector({
|
|
||||||
walletType,
|
walletType,
|
||||||
walletConnection: {
|
walletConnection: {
|
||||||
type: walletConnectionType,
|
type: walletConnectionType,
|
||||||
},
|
},
|
||||||
walletConnectConfig,
|
walletConnectConfig,
|
||||||
}),
|
})
|
||||||
})
|
: undefined,
|
||||||
: useConnectWagmi();
|
[walletConnectConfig, walletType, walletConnectionType]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector })
|
||||||
const { suggestAndConnect: connectGraz } = useConnectGraz();
|
const { suggestAndConnect: connectGraz } = useConnectGraz();
|
||||||
|
|
||||||
const connectWallet = useCallback(
|
const connectWallet = useCallback(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import styled, { type AnyStyledComponent, css } from 'styled-components';
|
|||||||
import { AbacusApiStatus } from '@/constants/abacus';
|
import { AbacusApiStatus } from '@/constants/abacus';
|
||||||
import { ButtonSize } from '@/constants/buttons';
|
import { ButtonSize } from '@/constants/buttons';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
import { ENVIRONMENT_CONFIG_MAP, isDev } from '@/constants/networks';
|
||||||
|
|
||||||
import { useApiState, useSelectedNetwork, useStringGetter } from '@/hooks';
|
import { useApiState, useSelectedNetwork, useStringGetter } from '@/hooks';
|
||||||
import { ChatIcon, LinkOutIcon } from '@/icons';
|
import { ChatIcon, LinkOutIcon } from '@/icons';
|
||||||
@@ -75,7 +75,7 @@ export const FooterDesktop = () => {
|
|||||||
)}
|
)}
|
||||||
</Styled.Row>
|
</Styled.Row>
|
||||||
|
|
||||||
{import.meta.env.MODE !== 'production' && (
|
{isDev && (
|
||||||
<Styled.Details
|
<Styled.Details
|
||||||
withSeparators
|
withSeparators
|
||||||
items={[
|
items={[
|
||||||
|
|||||||
@@ -236,4 +236,5 @@ Styled.IconButton = styled(IconButton)<{ size?: string }>`
|
|||||||
${headerMixins.button}
|
${headerMixins.button}
|
||||||
--button-border: none;
|
--button-border: none;
|
||||||
--button-icon-size: 1rem;
|
--button-icon-size: 1rem;
|
||||||
|
--button-padding: 0 0.5em;
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
|
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade';
|
import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade';
|
||||||
import { QUANTUM_MULTIPLIER } from '@/constants/numbers';
|
import { ENVIRONMENT_CONFIG_MAP, DydxNetwork, isTestnet } from '@/constants/networks';
|
||||||
|
|
||||||
import { RootStore } from '@/state/_store';
|
import { RootStore } from '@/state/_store';
|
||||||
import { addUncommittedOrderClientId, removeUncommittedOrderClientId } from '@/state/account';
|
import { addUncommittedOrderClientId, removeUncommittedOrderClientId } from '@/state/account';
|
||||||
@@ -198,8 +198,12 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
const parsedTx = this.parseToPrimitives(tx);
|
const parsedTx = this.parseToPrimitives(tx);
|
||||||
const hash = parsedTx?.hash;
|
const hash = parsedTx?.hash;
|
||||||
|
|
||||||
if (import.meta.env.MODE === 'production') {
|
if (isTestnet) {
|
||||||
console.log(`https://testnet.mintscan.io/dydx-testnet/txs/${hash}`);
|
console.log(
|
||||||
|
`${ENVIRONMENT_CONFIG_MAP[
|
||||||
|
this.compositeClient.network.getString() as DydxNetwork
|
||||||
|
]?.links?.mintscan?.replace('{tx_hash}', hash.toString())}`
|
||||||
|
);
|
||||||
} else console.log(`txHash: ${hash}`);
|
} else console.log(`txHash: ${hash}`);
|
||||||
|
|
||||||
return JSON.stringify(parsedTx);
|
return JSON.stringify(parsedTx);
|
||||||
@@ -229,7 +233,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
orderFlags,
|
orderFlags,
|
||||||
clobPairId,
|
clobPairId,
|
||||||
goodTilBlock || undefined,
|
goodTilBlock || undefined,
|
||||||
goodTilBlockTime || undefined,
|
goodTilBlockTime || undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
const parsedTx = this.parseToPrimitives(tx);
|
const parsedTx = this.parseToPrimitives(tx);
|
||||||
@@ -295,15 +299,11 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
if (!this.localWallet) {
|
if (!this.localWallet) {
|
||||||
throw new Error('Missing compositeClient or localWallet');
|
throw new Error('Missing compositeClient or localWallet');
|
||||||
}
|
}
|
||||||
const msg = compositeClient?.sendTokenMessage(
|
const msg = compositeClient?.sendTokenMessage(this.localWallet, amount, recipient);
|
||||||
this.localWallet,
|
|
||||||
amount,
|
|
||||||
recipient,
|
|
||||||
);
|
|
||||||
|
|
||||||
resolve([msg]);
|
resolve([msg]);
|
||||||
}),
|
}),
|
||||||
this.compositeClient?.validatorClient?.post.defaultDydxGasPrice,
|
this.compositeClient?.validatorClient?.post.defaultDydxGasPrice
|
||||||
);
|
);
|
||||||
|
|
||||||
const parsedTx = this.parseToPrimitives(tx);
|
const parsedTx = this.parseToPrimitives(tx);
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
import type { AbacusFileSystemProtocol, FileLocation, Nullable } from '@/constants/abacus';
|
import type { AbacusFileSystemProtocol, FileLocation, Nullable } from '@/constants/abacus';
|
||||||
|
|
||||||
export const ENDPOINTS_PATH =
|
|
||||||
import.meta.env.MODE === 'production'
|
|
||||||
? 'config/prod/endpoints.json'
|
|
||||||
: 'config/staging/dev_endpoints.json';
|
|
||||||
|
|
||||||
class AbacusFileSystem implements AbacusFileSystemProtocol {
|
class AbacusFileSystem implements AbacusFileSystemProtocol {
|
||||||
readTextFile(location: FileLocation, path: string): Nullable<string> {
|
readTextFile(location: FileLocation, path: string): Nullable<string> {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ 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 AbacusFileSystem from './filesystem';
|
||||||
import { LocaleSeparators } from '../numbers';
|
import { LocaleSeparators } from '../numbers';
|
||||||
class AbacusStateManager {
|
class AbacusStateManager {
|
||||||
private store: RootStore | undefined;
|
private store: RootStore | undefined;
|
||||||
|
|||||||
@@ -56,12 +56,7 @@ class AbacusRest implements AbacusRestProtocol {
|
|||||||
.then(async (response) => {
|
.then(async (response) => {
|
||||||
const data = await response.text();
|
const data = await response.text();
|
||||||
|
|
||||||
if (response.ok) {
|
callback(data, response.status);
|
||||||
callback(data, response.status);
|
|
||||||
} else {
|
|
||||||
// response not OK, call callback with null data and the status, this includes 400/500 status codes
|
|
||||||
callback(null, response.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
lastSuccessfulRestRequestByOrigin[new URL(url).origin] = Date.now();
|
lastSuccessfulRestRequestByOrigin[new URL(url).origin] = Date.now();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { AbacusWebsocketProtocol } from '@/constants/abacus';
|
import type { AbacusWebsocketProtocol } from '@/constants/abacus';
|
||||||
import type { TradingViewBar } from '@/constants/candles';
|
import type { TradingViewBar } from '@/constants/candles';
|
||||||
|
import { isDev } from '@/constants/networks';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
PING_INTERVAL_MS,
|
PING_INTERVAL_MS,
|
||||||
@@ -8,11 +9,12 @@ import {
|
|||||||
PONG_MESSAGE_TYPE,
|
PONG_MESSAGE_TYPE,
|
||||||
} from '@/constants/websocket';
|
} from '@/constants/websocket';
|
||||||
|
|
||||||
|
import { lastSuccessfulWebsocketRequestByOrigin } from '@/hooks/useAnalytics';
|
||||||
|
import { testFlags } from '@/hooks/useTestFlags';
|
||||||
|
|
||||||
import { subscriptionsByChannelId } from '@/lib/tradingView/dydxfeed/cache';
|
import { subscriptionsByChannelId } from '@/lib/tradingView/dydxfeed/cache';
|
||||||
import { mapCandle } from '@/lib/tradingView/utils';
|
import { mapCandle } from '@/lib/tradingView/utils';
|
||||||
|
|
||||||
import { lastSuccessfulWebsocketRequestByOrigin } from '@/hooks/useAnalytics';
|
|
||||||
|
|
||||||
import { log } from '../telemetry';
|
import { log } from '../telemetry';
|
||||||
|
|
||||||
const RECONNECT_INTERVAL_MS = 10_000;
|
const RECONNECT_INTERVAL_MS = 10_000;
|
||||||
@@ -143,6 +145,23 @@ class AbacusWebsocket implements Omit<AbacusWebsocketProtocol, '__doNotUseOrImpl
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'v4_markets': {
|
||||||
|
if (testFlags.displayInitializingMarkets) {
|
||||||
|
shouldProcess = false;
|
||||||
|
const { contents } = parsedMessage;
|
||||||
|
|
||||||
|
Object.keys(contents.markets ?? {}).forEach((market: any) => {
|
||||||
|
const status = contents.markets[market].status;
|
||||||
|
if (status === 'INITIALIZING') {
|
||||||
|
contents.markets[market].status = 'ONLINE';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.receivedCallback?.(JSON.stringify(parsedMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -161,13 +180,13 @@ class AbacusWebsocket implements Omit<AbacusWebsocketProtocol, '__doNotUseOrImpl
|
|||||||
|
|
||||||
this.socket.onclose = (e) => {
|
this.socket.onclose = (e) => {
|
||||||
this.connectedCallback?.(false);
|
this.connectedCallback?.(false);
|
||||||
if (import.meta.env.MODE === 'production') return;
|
if (!isDev) return;
|
||||||
console.warn('AbacusStateManager > WS > close > ', e);
|
console.warn('AbacusStateManager > WS > close > ', e);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket.onerror = (e) => {
|
this.socket.onerror = (e) => {
|
||||||
this.connectedCallback?.(false);
|
this.connectedCallback?.(false);
|
||||||
if (import.meta.env.MODE === 'production') return;
|
if (!isDev) return;
|
||||||
console.error('AbacusStateManager > WS > error > ', e);
|
console.error('AbacusStateManager > WS > error > ', e);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { isDev } from "@/constants/networks";
|
||||||
|
|
||||||
export const log = (location: string, error: Error, metadata?: any) => {
|
export const log = (location: string, error: Error, metadata?: any) => {
|
||||||
if (import.meta.env.MODE !== 'production') {
|
if (isDev) {
|
||||||
console.warn('telemetry/log:', { location, error, metadata });
|
console.warn('telemetry/log:', { location, error, metadata });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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 { isDev } 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';
|
||||||
@@ -30,7 +31,7 @@ export default (store: any) => (next: any) => async (action: PayloadAction<any>)
|
|||||||
store.dispatch(
|
store.dispatch(
|
||||||
openDialog({
|
openDialog({
|
||||||
type: DialogTypes.ExchangeOffline,
|
type: DialogTypes.ExchangeOffline,
|
||||||
dialogProps: { preventClose: import.meta.env.MODE === 'production' },
|
dialogProps: { preventClose: !isDev },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,15 @@ export const getTradeInputErrors = (state: RootState) => {
|
|||||||
return currentInput === 'trade' ? getInputErrors(state) : [];
|
return currentInput === 'trade' ? getInputErrors(state) : [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param state
|
||||||
|
* @returns input errors for Close position
|
||||||
|
*/
|
||||||
|
export const getClosePositionInputErrors = (state: RootState) => {
|
||||||
|
const currentInput = state.inputs.current;
|
||||||
|
return currentInput === 'closePosition' ? getInputErrors(state) : [];
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
* @param state
|
||||||
* @returns input errors for Transfer
|
* @returns input errors for Transfer
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export const TransferStatusToast = ({
|
|||||||
}: ElementProps) => {
|
}: ElementProps) => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const [open, setOpen] = useState<boolean>(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
const [secondsLeft, setSecondsLeft] = useState<number | undefined>();
|
const [secondsLeft, setSecondsLeft] = useState<number>(0);
|
||||||
|
|
||||||
// @ts-ignore status.errors is not in the type definition but can be returned
|
// @ts-ignore status.errors is not in the type definition but can be returned
|
||||||
const error = status?.errors?.length ? status?.errors[0] : status?.error;
|
const error = status?.errors?.length ? status?.errors[0] : status?.error;
|
||||||
@@ -49,14 +49,23 @@ export const TransferStatusToast = ({
|
|||||||
|
|
||||||
if (!status) return <LoadingDots size={3} />;
|
if (!status) return <LoadingDots size={3} />;
|
||||||
|
|
||||||
|
const inProgressStatusString =
|
||||||
|
type === 'deposit'
|
||||||
|
? secondsLeft > 0
|
||||||
|
? STRING_KEYS.DEPOSIT_STATUS
|
||||||
|
: STRING_KEYS.DEPOSIT_STATUS_SHORTLY
|
||||||
|
: secondsLeft > 0
|
||||||
|
? STRING_KEYS.WITHDRAW_STATUS
|
||||||
|
: STRING_KEYS.WITHDRAW_STATUS_SHORTLY;
|
||||||
|
|
||||||
const statusString =
|
const statusString =
|
||||||
type === 'deposit'
|
type === 'deposit'
|
||||||
? status?.squidTransactionStatus === 'success'
|
? status?.squidTransactionStatus === 'success'
|
||||||
? STRING_KEYS.DEPOSIT_COMPLETE
|
? STRING_KEYS.DEPOSIT_COMPLETE
|
||||||
: STRING_KEYS.DEPOSIT_STATUS
|
: inProgressStatusString
|
||||||
: status?.squidTransactionStatus === 'success'
|
: status?.squidTransactionStatus === 'success'
|
||||||
? STRING_KEYS.WITHDRAW_COMPLETE
|
? STRING_KEYS.WITHDRAW_COMPLETE
|
||||||
: STRING_KEYS.WITHDRAW_STATUS;
|
: inProgressStatusString;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Root open={open} onOpenChange={setOpen}>
|
<Styled.Root open={open} onOpenChange={setOpen}>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useMemo } from 'react';
|
|||||||
import styled, { css, keyframes, type AnyStyledComponent } from 'styled-components';
|
import styled, { css, keyframes, type AnyStyledComponent } from 'styled-components';
|
||||||
import { StatusResponse } from '@0xsquid/sdk';
|
import { StatusResponse } from '@0xsquid/sdk';
|
||||||
|
|
||||||
import { useStringGetter } from '@/hooks';
|
import { useStringGetter, useSelectedNetwork, useURLConfigs } from '@/hooks';
|
||||||
|
|
||||||
import { Link } from '@/components/Link';
|
import { Link } from '@/components/Link';
|
||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
@@ -11,6 +11,7 @@ import { LoadingSpinner } from '@/components/Loading/LoadingSpinner';
|
|||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
status?: StatusResponse;
|
status?: StatusResponse;
|
||||||
@@ -26,12 +27,17 @@ enum TransferStatusStep {
|
|||||||
|
|
||||||
export const TransferStatusSteps = ({ status, type }: ElementProps) => {
|
export const TransferStatusSteps = ({ status, type }: ElementProps) => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
|
const { selectedNetwork } = useSelectedNetwork();
|
||||||
|
const { mintscan: mintscanTxUrl } = useURLConfigs();
|
||||||
|
const dydxChainId = ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId;
|
||||||
|
|
||||||
const { currentStep, steps } = useMemo(() => {
|
const { currentStep, steps } = useMemo(() => {
|
||||||
const routeStatus = status?.routeStatus;
|
const routeStatus = status?.routeStatus;
|
||||||
const fromChain = status?.fromChain?.chainData?.chainId;
|
const fromChain = status?.fromChain?.chainData?.chainId;
|
||||||
const toChain = status?.toChain?.chainData?.chainId;
|
const toChain = status?.toChain?.chainData?.chainId;
|
||||||
|
|
||||||
|
const currentStatus = routeStatus?.[routeStatus?.length - 1];
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
label: stringGetter({
|
label: stringGetter({
|
||||||
@@ -54,12 +60,15 @@ export const TransferStatusSteps = ({ status, type }: ElementProps) => {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
step: TransferStatusStep.ToChain,
|
step: TransferStatusStep.ToChain,
|
||||||
link: status?.toChain?.transactionUrl,
|
link:
|
||||||
|
type === 'withdrawal'
|
||||||
|
? status?.toChain?.transactionUrl
|
||||||
|
: currentStatus?.chainId === dydxChainId && currentStatus?.txHash
|
||||||
|
? `${mintscanTxUrl?.replace('{tx_hash}', currentStatus.txHash)}`
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const currentStatus = routeStatus?.[routeStatus?.length - 1];
|
|
||||||
|
|
||||||
let currentStep = TransferStatusStep.Bridge;
|
let currentStep = TransferStatusStep.Bridge;
|
||||||
|
|
||||||
if (!routeStatus?.length) {
|
if (!routeStatus?.length) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
|
import { isMainnet } from '@/constants/networks';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ type ElementProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const DepositDialog = ({ setIsOpen }: ElementProps) => {
|
export const DepositDialog = ({ setIsOpen }: ElementProps) => {
|
||||||
const [showTestDeposit, setShowTestDeposit] = useState(true);
|
const [showFaucet, setShowFaucet] = useState(!isMainnet);
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const { isMobile } = useBreakpoints();
|
const { isMobile } = useBreakpoints();
|
||||||
|
|
||||||
@@ -27,14 +28,20 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
|
|||||||
isOpen
|
isOpen
|
||||||
setIsOpen={setIsOpen}
|
setIsOpen={setIsOpen}
|
||||||
title={stringGetter({ key: STRING_KEYS.DEPOSIT })}
|
title={stringGetter({ key: STRING_KEYS.DEPOSIT })}
|
||||||
description={showTestDeposit && 'Test funds will be sent directly to your dYdX account.'}
|
description={showFaucet && 'Test funds will be sent directly to your dYdX account.'}
|
||||||
placement={isMobile ? DialogPlacement.FullScreen : DialogPlacement.Default}
|
placement={isMobile ? DialogPlacement.FullScreen : DialogPlacement.Default}
|
||||||
>
|
>
|
||||||
<Styled.Content>
|
<Styled.Content>
|
||||||
{showTestDeposit ? <TestnetDepositForm onDeposit={closeDialog} /> : <DepositForm />}
|
{isMainnet || !showFaucet ? (
|
||||||
<Styled.TextToggle onClick={() => setShowTestDeposit(!showTestDeposit)}>
|
<DepositForm />
|
||||||
{showTestDeposit ? 'Show deposit form (Under Construction)' : 'Show test faucet'}
|
) : (
|
||||||
</Styled.TextToggle>
|
<TestnetDepositForm onDeposit={closeDialog} />
|
||||||
|
)}
|
||||||
|
{!isMainnet && (
|
||||||
|
<Styled.TextToggle onClick={() => setShowFaucet(!showFaucet)}>
|
||||||
|
{showFaucet ? 'Show deposit form' : 'Show test faucet'}
|
||||||
|
</Styled.TextToggle>
|
||||||
|
)}
|
||||||
</Styled.Content>
|
</Styled.Content>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
|||||||
import { AbacusApiStatus } from '@/constants/abacus';
|
import { AbacusApiStatus } from '@/constants/abacus';
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { isDev } from '@/constants/networks';
|
||||||
|
|
||||||
import { useApiState, useStringGetter } from '@/hooks';
|
import { useApiState, useStringGetter } from '@/hooks';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
@@ -45,7 +46,7 @@ export const ExchangeOfflineDialog = ({ preventClose, setIsOpen }: ElementProps)
|
|||||||
>
|
>
|
||||||
<Styled.Content>
|
<Styled.Content>
|
||||||
<p>{statusErrorMessage}</p>
|
<p>{statusErrorMessage}</p>
|
||||||
{import.meta.env.MODE !== 'production' && <NetworkSelectMenu />}
|
{isDev && <NetworkSelectMenu />}
|
||||||
</Styled.Content>
|
</Styled.Content>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import styled, { AnyStyledComponent } from 'styled-components';
|
import styled, { AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { isDev } from '@/constants/networks';
|
||||||
import { useStringGetter } from '@/hooks';
|
import { useStringGetter } from '@/hooks';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
import { Dialog } from '@/components/Dialog';
|
import { Dialog } from '@/components/Dialog';
|
||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
|
import { NetworkSelectMenu } from '@/views/menus/NetworkSelectMenu';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
preventClose?: boolean;
|
preventClose?: boolean;
|
||||||
@@ -25,6 +27,7 @@ export const RestrictedGeoDialog = ({ preventClose, setIsOpen }: ElementProps) =
|
|||||||
>
|
>
|
||||||
<Styled.Content>
|
<Styled.Content>
|
||||||
{stringGetter({ key: STRING_KEYS.REGION_NOT_PERMITTED_SUBTITLE })}
|
{stringGetter({ key: STRING_KEYS.REGION_NOT_PERMITTED_SUBTITLE })}
|
||||||
|
{isDev && <NetworkSelectMenu />}
|
||||||
</Styled.Content>
|
</Styled.Content>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
import styled, { AnyStyledComponent } from 'styled-components';
|
import styled, { AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { isDev } from '@/constants/networks';
|
||||||
import { useStringGetter } from '@/hooks';
|
import { useStringGetter } from '@/hooks';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
import { Dialog } from '@/components/Dialog';
|
import { Dialog } from '@/components/Dialog';
|
||||||
import { Icon, IconName } from '@/components/Icon';
|
import { Icon, IconName } from '@/components/Icon';
|
||||||
|
import { NetworkSelectMenu } from '@/views/menus/NetworkSelectMenu';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
preventClose?: boolean;
|
preventClose?: boolean;
|
||||||
@@ -25,6 +27,7 @@ export const RestrictedWalletDialog = ({ preventClose, setIsOpen }: ElementProps
|
|||||||
>
|
>
|
||||||
<Styled.Content>
|
<Styled.Content>
|
||||||
{stringGetter({ key: STRING_KEYS.REGION_NOT_PERMITTED_SUBTITLE })}
|
{stringGetter({ key: STRING_KEYS.REGION_NOT_PERMITTED_SUBTITLE })}
|
||||||
|
{isDev && <NetworkSelectMenu />}
|
||||||
</Styled.Content>
|
</Styled.Content>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,22 +2,21 @@ import { type FormEvent, useCallback, useEffect, useMemo, useState } from 'react
|
|||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
import { type NumberFormatValues } from 'react-number-format';
|
import { type NumberFormatValues } from 'react-number-format';
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
import { parseUnits } from 'viem';
|
||||||
import { parseUnits } from 'viem'
|
|
||||||
|
|
||||||
import erc20 from '@/abi/erc20.json';
|
import erc20 from '@/abi/erc20.json';
|
||||||
import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
|
import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
|
||||||
import { AlertType } from '@/constants/alerts';
|
import { AlertType } from '@/constants/alerts';
|
||||||
import { ButtonSize } from '@/constants/buttons';
|
import { ButtonSize } from '@/constants/buttons';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||||
import { NumberSign } from '@/constants/numbers';
|
import { NumberSign } from '@/constants/numbers';
|
||||||
import type { EvmAddress } from '@/constants/wallets';
|
import type { EvmAddress } from '@/constants/wallets';
|
||||||
|
|
||||||
import { useAccounts, useDebounce, useStringGetter } from '@/hooks';
|
import { useAccounts, useDebounce, useStringGetter, useSelectedNetwork } from '@/hooks';
|
||||||
import { useAccountBalance, CHAIN_DEFAULT_TOKEN_ADDRESS } from '@/hooks/useAccountBalance';
|
import { useAccountBalance, CHAIN_DEFAULT_TOKEN_ADDRESS } from '@/hooks/useAccountBalance';
|
||||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||||
import { NATIVE_TOKEN_ADDRESS, useSquid } from '@/hooks/useSquid';
|
import { NATIVE_TOKEN_ADDRESS, useSquid } from '@/hooks/useSquid';
|
||||||
import { useWalletConnection } from '@/hooks/useWalletConnection';
|
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { formMixins } from '@/styles/formMixins';
|
import { formMixins } from '@/styles/formMixins';
|
||||||
@@ -54,9 +53,9 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const [error, setError] = useState<Error | null>(null);
|
const [error, setError] = useState<Error | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { selectedNetwork } = useSelectedNetwork();
|
||||||
|
|
||||||
const { evmAddress, signerWagmi } = useAccounts();
|
const { evmAddress, signerWagmi, publicClientWagmi } = useAccounts();
|
||||||
const { publicClientWagmi } = useWalletConnection();
|
|
||||||
|
|
||||||
const { addTransferNotification } = useLocalNotifications();
|
const { addTransferNotification } = useLocalNotifications();
|
||||||
|
|
||||||
@@ -66,6 +65,8 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
chain: chainIdStr,
|
chain: chainIdStr,
|
||||||
resources,
|
resources,
|
||||||
summary,
|
summary,
|
||||||
|
errors: routeErrors,
|
||||||
|
errorMessage: routeErrorMessage,
|
||||||
} = useSelector(getTransferInputs, shallowEqual) || {};
|
} = useSelector(getTransferInputs, shallowEqual) || {};
|
||||||
const chainId = chainIdStr ? parseInt(chainIdStr) : undefined;
|
const chainId = chainIdStr ? parseInt(chainIdStr) : undefined;
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const hasInvalidInput =
|
const hasInvalidInput =
|
||||||
debouncedAmountBN.isNaN() || debouncedAmountBN.lte(0) || debouncedAmountBN.gte(balanceBN);
|
debouncedAmountBN.isNaN() || debouncedAmountBN.lte(0) || debouncedAmountBN.gt(balanceBN);
|
||||||
|
|
||||||
abacusStateManager.setTransferValue({
|
abacusStateManager.setTransferValue({
|
||||||
value: hasInvalidInput ? 0 : debouncedAmount,
|
value: hasInvalidInput ? 0 : debouncedAmount,
|
||||||
@@ -172,7 +173,8 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
|
|
||||||
const validateTokenApproval = useCallback(async () => {
|
const validateTokenApproval = useCallback(async () => {
|
||||||
if (!signerWagmi || !publicClientWagmi) throw new Error('Missing signer');
|
if (!signerWagmi || !publicClientWagmi) throw new Error('Missing signer');
|
||||||
if (!sourceToken?.address || !sourceToken.decimals) throw new Error('Missing source token address');
|
if (!sourceToken?.address || !sourceToken.decimals)
|
||||||
|
throw new Error('Missing source token address');
|
||||||
if (!sourceChain?.rpc) throw new Error('Missing source chain rpc');
|
if (!sourceChain?.rpc) throw new Error('Missing source chain rpc');
|
||||||
if (!requestPayload?.targetAddress) throw new Error('Missing target address');
|
if (!requestPayload?.targetAddress) throw new Error('Missing target address');
|
||||||
if (!requestPayload?.value) throw new Error('Missing transaction value');
|
if (!requestPayload?.value) throw new Error('Missing transaction value');
|
||||||
@@ -182,7 +184,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
address: sourceToken.address as EvmAddress,
|
address: sourceToken.address as EvmAddress,
|
||||||
abi: erc20,
|
abi: erc20,
|
||||||
functionName: 'allowance',
|
functionName: 'allowance',
|
||||||
args: [evmAddress as EvmAddress, requestPayload.targetAddress as EvmAddress]
|
args: [evmAddress as EvmAddress, requestPayload.targetAddress as EvmAddress],
|
||||||
});
|
});
|
||||||
|
|
||||||
const sourceAmountBN = parseUnits(debouncedAmount, sourceToken.decimals);
|
const sourceAmountBN = parseUnits(debouncedAmount, sourceToken.decimals);
|
||||||
@@ -194,12 +196,12 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
abi: erc20,
|
abi: erc20,
|
||||||
functionName: 'approve',
|
functionName: 'approve',
|
||||||
args: [requestPayload.targetAddress as EvmAddress, sourceAmountBN],
|
args: [requestPayload.targetAddress as EvmAddress, sourceAmountBN],
|
||||||
})
|
});
|
||||||
|
|
||||||
const approveTx = await signerWagmi.writeContract(request);
|
const approveTx = await signerWagmi.writeContract(request);
|
||||||
await publicClientWagmi.waitForTransactionReceipt({
|
await publicClientWagmi.waitForTransactionReceipt({
|
||||||
hash: approveTx,
|
hash: approveTx,
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}, [signerWagmi, sourceToken, sourceChain, requestPayload, publicClientWagmi]);
|
}, [signerWagmi, sourceToken, sourceChain, requestPayload, publicClientWagmi]);
|
||||||
|
|
||||||
@@ -229,8 +231,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
to: requestPayload.targetAddress as EvmAddress,
|
to: requestPayload.targetAddress as EvmAddress,
|
||||||
data: requestPayload.data as EvmAddress,
|
data: requestPayload.data as EvmAddress,
|
||||||
gasLimit: BigInt(requestPayload.gasLimit),
|
gasLimit: BigInt(requestPayload.gasLimit),
|
||||||
value:
|
value: requestPayload.routeType !== 'SEND' ? BigInt(requestPayload.value) : undefined,
|
||||||
requestPayload.routeType !== 'SEND' ? BigInt(requestPayload.value) : undefined,
|
|
||||||
};
|
};
|
||||||
const txHash = await signerWagmi.sendTransaction(tx);
|
const txHash = await signerWagmi.sendTransaction(tx);
|
||||||
|
|
||||||
@@ -239,7 +240,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
if (txHash) {
|
if (txHash) {
|
||||||
addTransferNotification({
|
addTransferNotification({
|
||||||
txHash: txHash,
|
txHash: txHash,
|
||||||
toChainId: TESTNET_CHAIN_ID,
|
toChainId: ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId,
|
||||||
fromChainId: chainIdStr || undefined,
|
fromChainId: chainIdStr || undefined,
|
||||||
toAmount: summary?.usdcSize || undefined,
|
toAmount: summary?.usdcSize || undefined,
|
||||||
triggeredAt: Date.now(),
|
triggeredAt: Date.now(),
|
||||||
@@ -288,6 +289,15 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
return parseWalletError({ error, stringGetter }).message;
|
return parseWalletError({ error, stringGetter }).message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (routeErrors) {
|
||||||
|
return routeErrorMessage
|
||||||
|
? stringGetter({
|
||||||
|
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
|
||||||
|
params: { ERROR_MESSAGE: routeErrorMessage },
|
||||||
|
})
|
||||||
|
: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG });
|
||||||
|
}
|
||||||
|
|
||||||
if (fromAmount) {
|
if (fromAmount) {
|
||||||
if (!chainId) {
|
if (!chainId) {
|
||||||
return stringGetter({ key: STRING_KEYS.MUST_SPECIFY_CHAIN });
|
return stringGetter({ key: STRING_KEYS.MUST_SPECIFY_CHAIN });
|
||||||
@@ -301,7 +311,16 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [error, balance, chainId, fromAmount, sourceToken]);
|
}, [
|
||||||
|
error,
|
||||||
|
routeErrors,
|
||||||
|
routeErrorMessage,
|
||||||
|
balance,
|
||||||
|
chainId,
|
||||||
|
fromAmount,
|
||||||
|
sourceToken,
|
||||||
|
stringGetter,
|
||||||
|
]);
|
||||||
|
|
||||||
const isDisabled =
|
const isDisabled =
|
||||||
Boolean(errorMessage) ||
|
Boolean(errorMessage) ||
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ export const DepositButtonAndReceipt = ({
|
|||||||
label: <span>{stringGetter({ key: STRING_KEYS.SLIPPAGE })}</span>,
|
label: <span>{stringGetter({ key: STRING_KEYS.SLIPPAGE })}</span>,
|
||||||
value: (
|
value: (
|
||||||
<SlippageEditor
|
<SlippageEditor
|
||||||
|
disabled
|
||||||
slippage={slippage}
|
slippage={slippage}
|
||||||
setIsEditing={setIsEditingSlipapge}
|
setIsEditing={setIsEditingSlipapge}
|
||||||
setSlippage={setSlippage}
|
setSlippage={setSlippage}
|
||||||
|
|||||||
@@ -24,11 +24,17 @@ type ElementProps = {
|
|||||||
slippage: number;
|
slippage: number;
|
||||||
setIsEditing?: Dispatch<SetStateAction<boolean>>;
|
setIsEditing?: Dispatch<SetStateAction<boolean>>;
|
||||||
setSlippage: (slippage: number) => void;
|
setSlippage: (slippage: number) => void;
|
||||||
|
disabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SlippageEditorProps = ElementProps;
|
export type SlippageEditorProps = ElementProps;
|
||||||
|
|
||||||
export const SlippageEditor = ({ slippage, setIsEditing, setSlippage }: SlippageEditorProps) => {
|
export const SlippageEditor = ({
|
||||||
|
disabled,
|
||||||
|
slippage,
|
||||||
|
setIsEditing,
|
||||||
|
setSlippage,
|
||||||
|
}: SlippageEditorProps) => {
|
||||||
const percentSlippage = slippage * 100;
|
const percentSlippage = slippage * 100;
|
||||||
const [slippageInputValue, setSlippageInputValue] = useState(percentSlippage.toString());
|
const [slippageInputValue, setSlippageInputValue] = useState(percentSlippage.toString());
|
||||||
const [editorState, setEditorState] = useState(EditorState.Viewing);
|
const [editorState, setEditorState] = useState(EditorState.Viewing);
|
||||||
@@ -80,6 +86,10 @@ export const SlippageEditor = ({ slippage, setIsEditing, setSlippage }: Slippage
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
|
return <Output type={OutputType.Percent} value={slippage} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.WithConfirmationPopover
|
<Styled.WithConfirmationPopover
|
||||||
open={editorState !== EditorState.Viewing}
|
open={editorState !== EditorState.Viewing}
|
||||||
|
|||||||
@@ -3,13 +3,13 @@ import type { ChangeEvent, FormEvent } from 'react';
|
|||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
import type { NumberFormatValues } from 'react-number-format';
|
import type { NumberFormatValues } from 'react-number-format';
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import { TESTNET_CHAIN_ID } from '@dydxprotocol/v4-client-js';
|
|
||||||
import { isAddress } from 'viem';
|
import { isAddress } from 'viem';
|
||||||
|
|
||||||
import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
|
import { TransferInputField, TransferInputTokenResource, TransferType } from '@/constants/abacus';
|
||||||
import { AlertType } from '@/constants/alerts';
|
import { AlertType } from '@/constants/alerts';
|
||||||
import { ButtonSize } from '@/constants/buttons';
|
import { ButtonSize } from '@/constants/buttons';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||||
import { NumberSign } from '@/constants/numbers';
|
import { NumberSign } from '@/constants/numbers';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
useDebounce,
|
useDebounce,
|
||||||
useDydxClient,
|
useDydxClient,
|
||||||
useRestrictions,
|
useRestrictions,
|
||||||
|
useSelectedNetwork,
|
||||||
useStringGetter,
|
useStringGetter,
|
||||||
useSubaccount,
|
useSubaccount,
|
||||||
} from '@/hooks';
|
} from '@/hooks';
|
||||||
@@ -46,12 +47,12 @@ import { MustBigNumber } from '@/lib/numbers';
|
|||||||
|
|
||||||
import { TokenSelectMenu } from './TokenSelectMenu';
|
import { TokenSelectMenu } from './TokenSelectMenu';
|
||||||
import { WithdrawButtonAndReceipt } from './WithdrawForm/WithdrawButtonAndReceipt';
|
import { WithdrawButtonAndReceipt } from './WithdrawForm/WithdrawButtonAndReceipt';
|
||||||
import { join } from 'path';
|
|
||||||
|
|
||||||
export const WithdrawForm = () => {
|
export const WithdrawForm = () => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const [error, setError] = useState<string>();
|
const [error, setError] = useState<string>();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { selectedNetwork } = useSelectedNetwork();
|
||||||
|
|
||||||
const { sendSquidWithdraw } = useSubaccount();
|
const { sendSquidWithdraw } = useSubaccount();
|
||||||
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
|
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
|
||||||
@@ -67,6 +68,8 @@ export const WithdrawForm = () => {
|
|||||||
chain: chainIdStr,
|
chain: chainIdStr,
|
||||||
address: toAddress,
|
address: toAddress,
|
||||||
resources,
|
resources,
|
||||||
|
errors: routeErrors,
|
||||||
|
errorMessage: routeErrorMessage,
|
||||||
} = useSelector(getTransferInputs, shallowEqual) || {};
|
} = useSelector(getTransferInputs, shallowEqual) || {};
|
||||||
|
|
||||||
const isValidAddress = toAddress && isAddress(toAddress);
|
const isValidAddress = toAddress && isAddress(toAddress);
|
||||||
@@ -164,7 +167,7 @@ export const WithdrawForm = () => {
|
|||||||
const hash = `0x${Buffer.from(txHash.hash).toString('hex')}`;
|
const hash = `0x${Buffer.from(txHash.hash).toString('hex')}`;
|
||||||
addTransferNotification({
|
addTransferNotification({
|
||||||
txHash: hash,
|
txHash: hash,
|
||||||
fromChainId: TESTNET_CHAIN_ID,
|
fromChainId: ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId,
|
||||||
toChainId: chainIdStr || undefined,
|
toChainId: chainIdStr || undefined,
|
||||||
toAmount: debouncedAmountBN.toNumber(),
|
toAmount: debouncedAmountBN.toNumber(),
|
||||||
triggeredAt: Date.now(),
|
triggeredAt: Date.now(),
|
||||||
@@ -279,6 +282,15 @@ export const WithdrawForm = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (routeErrors) {
|
||||||
|
return routeErrorMessage
|
||||||
|
? stringGetter({
|
||||||
|
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
|
||||||
|
params: { ERROR_MESSAGE: routeErrorMessage },
|
||||||
|
})
|
||||||
|
: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG });
|
||||||
|
}
|
||||||
|
|
||||||
if (!toAddress) return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ADDRESS });
|
if (!toAddress) return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ADDRESS });
|
||||||
|
|
||||||
if (sanctionedAddresses.has(toAddress))
|
if (sanctionedAddresses.has(toAddress))
|
||||||
@@ -301,12 +313,15 @@ export const WithdrawForm = () => {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}, [
|
}, [
|
||||||
error,
|
error,
|
||||||
|
routeErrors,
|
||||||
|
routeErrorMessage,
|
||||||
freeCollateralBN,
|
freeCollateralBN,
|
||||||
chainIdStr,
|
chainIdStr,
|
||||||
debouncedAmountBN,
|
debouncedAmountBN,
|
||||||
toToken,
|
toToken,
|
||||||
toAddress,
|
toAddress,
|
||||||
sanctionedAddresses,
|
sanctionedAddresses,
|
||||||
|
stringGetter,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const isDisabled =
|
const isDisabled =
|
||||||
|
|||||||
@@ -147,6 +147,7 @@ export const WithdrawButtonAndReceipt = ({
|
|||||||
label: <span>{stringGetter({ key: STRING_KEYS.SLIPPAGE })}</span>,
|
label: <span>{stringGetter({ key: STRING_KEYS.SLIPPAGE })}</span>,
|
||||||
value: (
|
value: (
|
||||||
<SlippageEditor
|
<SlippageEditor
|
||||||
|
disabled
|
||||||
slippage={slippage}
|
slippage={slippage}
|
||||||
setIsEditing={setIsEditingSlipapge}
|
setIsEditing={setIsEditingSlipapge}
|
||||||
setSlippage={setSlippage}
|
setSlippage={setSlippage}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
ClosePositionInputField,
|
ClosePositionInputField,
|
||||||
|
ValidationError,
|
||||||
type HumanReadablePlaceOrderPayload,
|
type HumanReadablePlaceOrderPayload,
|
||||||
type Nullable,
|
type Nullable,
|
||||||
|
ErrorType,
|
||||||
} from '@/constants/abacus';
|
} from '@/constants/abacus';
|
||||||
import { AlertType } from '@/constants/alerts';
|
import { AlertType } from '@/constants/alerts';
|
||||||
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
|
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
|
||||||
@@ -35,13 +37,14 @@ import { PositionPreview } from '@/views/forms/TradeForm/PositionPreview';
|
|||||||
import { getCurrentMarketPositionData } from '@/state/accountSelectors';
|
import { getCurrentMarketPositionData } from '@/state/accountSelectors';
|
||||||
|
|
||||||
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
||||||
import { getInputClosePositionData } from '@/state/inputsSelectors';
|
import { getClosePositionInputErrors, getInputClosePositionData } from '@/state/inputsSelectors';
|
||||||
import { getCurrentMarketConfig, getCurrentMarketId } from '@/state/perpetualsSelectors';
|
import { getCurrentMarketConfig, getCurrentMarketId } from '@/state/perpetualsSelectors';
|
||||||
import { closeDialog } from '@/state/dialogs';
|
import { closeDialog } from '@/state/dialogs';
|
||||||
import { getCurrentInput } from '@/state/inputsSelectors';
|
import { getCurrentInput } from '@/state/inputsSelectors';
|
||||||
|
|
||||||
import abacusStateManager from '@/lib/abacus';
|
import abacusStateManager from '@/lib/abacus';
|
||||||
import { MustBigNumber } from '@/lib/numbers';
|
import { MustBigNumber } from '@/lib/numbers';
|
||||||
|
import { getTradeInputAlert } from '@/lib/tradeData';
|
||||||
|
|
||||||
const MAX_KEY = 'MAX';
|
const MAX_KEY = 'MAX';
|
||||||
|
|
||||||
@@ -81,15 +84,38 @@ export const ClosePositionForm = ({
|
|||||||
|
|
||||||
const market = useSelector(getCurrentMarketId);
|
const market = useSelector(getCurrentMarketId);
|
||||||
const { id } = useSelector(getCurrentMarketAssetData, shallowEqual) || {};
|
const { id } = useSelector(getCurrentMarketAssetData, shallowEqual) || {};
|
||||||
const { stepSizeDecimals } = useSelector(getCurrentMarketConfig, shallowEqual) || {};
|
const { stepSizeDecimals, tickSizeDecimals } =
|
||||||
|
useSelector(getCurrentMarketConfig, shallowEqual) || {};
|
||||||
const { size: sizeData, summary } = useSelector(getInputClosePositionData, shallowEqual) || {};
|
const { size: sizeData, summary } = useSelector(getInputClosePositionData, shallowEqual) || {};
|
||||||
const { size, percent } = sizeData || {};
|
const { size, percent } = sizeData || {};
|
||||||
const currentInput = useSelector(getCurrentInput);
|
const currentInput = useSelector(getCurrentInput);
|
||||||
|
const closePositionInputErrors = useSelector(getClosePositionInputErrors, shallowEqual);
|
||||||
const currentPositionData = useSelector(getCurrentMarketPositionData, shallowEqual);
|
const currentPositionData = useSelector(getCurrentMarketPositionData, shallowEqual);
|
||||||
const { size: currentPositionSize } = currentPositionData || {};
|
const { size: currentPositionSize } = currentPositionData || {};
|
||||||
const { current: currentSize } = currentPositionSize || {};
|
const { current: currentSize } = currentPositionSize || {};
|
||||||
const currentSizeBN = MustBigNumber(currentSize).abs();
|
const currentSizeBN = MustBigNumber(currentSize).abs();
|
||||||
|
|
||||||
|
const hasInputErrors = closePositionInputErrors?.some(
|
||||||
|
(error: ValidationError) => error.type !== ErrorType.warning
|
||||||
|
);
|
||||||
|
|
||||||
|
const inputAlert = getTradeInputAlert({
|
||||||
|
abacusInputErrors: closePositionInputErrors ?? [],
|
||||||
|
stringGetter,
|
||||||
|
stepSizeDecimals,
|
||||||
|
tickSizeDecimals,
|
||||||
|
});
|
||||||
|
|
||||||
|
let alertContent;
|
||||||
|
let alertType = AlertType.Error;
|
||||||
|
|
||||||
|
if (closePositionError) {
|
||||||
|
alertContent = closePositionError;
|
||||||
|
} else if (inputAlert) {
|
||||||
|
alertContent = inputAlert?.alertString;
|
||||||
|
alertType = inputAlert?.type;
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentStep && currentStep !== MobilePlaceOrderSteps.EditOrder) return;
|
if (currentStep && currentStep !== MobilePlaceOrderSteps.EditOrder) return;
|
||||||
|
|
||||||
@@ -182,9 +208,7 @@ export const ClosePositionForm = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const alertMessage = closePositionError && (
|
const alertMessage = alertContent && <AlertMessage type={alertType}>{alertContent}</AlertMessage>;
|
||||||
<AlertMessage type={AlertType.Error}>{closePositionError}</AlertMessage>
|
|
||||||
);
|
|
||||||
|
|
||||||
const inputs = (
|
const inputs = (
|
||||||
<Styled.InputsColumn>
|
<Styled.InputsColumn>
|
||||||
@@ -265,8 +289,8 @@ export const ClosePositionForm = ({
|
|||||||
|
|
||||||
<PlaceOrderButtonAndReceipt
|
<PlaceOrderButtonAndReceipt
|
||||||
isLoading={isClosingPosition}
|
isLoading={isClosingPosition}
|
||||||
hasValidationErrors={!size}
|
hasValidationErrors={hasInputErrors}
|
||||||
actionStringKey={!size ? STRING_KEYS.ENTER_AMOUNT : undefined}
|
actionStringKey={inputAlert?.actionStringKey}
|
||||||
summary={summary ?? undefined}
|
summary={summary ?? undefined}
|
||||||
currentStep={currentStep}
|
currentStep={currentStep}
|
||||||
isClosePosition
|
isClosePosition
|
||||||
|
|||||||
@@ -61,38 +61,53 @@ export const TransferForm = ({
|
|||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
|
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
|
||||||
const { dydxAddress } = useAccounts();
|
const { dydxAddress } = useAccounts();
|
||||||
const {
|
|
||||||
address: recipientAddress,
|
|
||||||
size,
|
|
||||||
fee,
|
|
||||||
} = useSelector(getTransferInputs, shallowEqual) || {};
|
|
||||||
const { transfer } = useSubaccount();
|
const { transfer } = useSubaccount();
|
||||||
const { nativeTokenBalance, usdcBalance } = useAccountBalance();
|
const { nativeTokenBalance, usdcBalance } = useAccountBalance();
|
||||||
const { selectedNetwork } = useSelectedNetwork();
|
const { selectedNetwork } = useSelectedNetwork();
|
||||||
const { tokensConfigs, usdcLabel, chainTokenLabel } = useTokenConfigs();
|
const { tokensConfigs, usdcLabel, chainTokenLabel } = useTokenConfigs();
|
||||||
|
|
||||||
// User Input
|
const {
|
||||||
const [asset, setAsset] = useState<DydxChainAsset>(selectedAsset);
|
address: recipientAddress,
|
||||||
|
size,
|
||||||
|
fee,
|
||||||
|
token,
|
||||||
|
} = useSelector(getTransferInputs, shallowEqual) || {};
|
||||||
|
|
||||||
// Form states
|
// Form states
|
||||||
const [error, setError] = useState<string>();
|
const [error, setError] = useState<string>();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const balance = asset === DydxChainAsset.USDC ? freeCollateral?.current : nativeTokenBalance;
|
// temp fix: TODO: reset fees when changing token in Abacus
|
||||||
const newBalance =
|
const [currentFee, setCurrentFee] = useState(fee);
|
||||||
asset === DydxChainAsset.USDC
|
useEffect(() => {
|
||||||
? freeCollateral?.postOrder
|
setCurrentFee(fee);
|
||||||
: MustBigNumber(nativeTokenBalance)
|
}, [fee]);
|
||||||
.minus(size?.size ?? 0)
|
|
||||||
.toNumber();
|
|
||||||
const amount = asset === DydxChainAsset.USDC ? size?.usdcSize : size?.size;
|
|
||||||
|
|
||||||
const showNotEnoughGasWarning = fee && asset === DydxChainAsset.USDC && usdcBalance < fee;
|
const asset = (token ?? selectedAsset) as DydxChainAsset;
|
||||||
|
const isUSDCSelected = asset === DydxChainAsset.USDC;
|
||||||
|
const amount = isUSDCSelected ? size?.usdcSize : size?.size;
|
||||||
|
const showNotEnoughGasWarning = fee && isUSDCSelected && usdcBalance < fee;
|
||||||
|
const balance = isUSDCSelected ? freeCollateral?.current : nativeTokenBalance;
|
||||||
|
|
||||||
// BN
|
// BN
|
||||||
|
const newBalanceBN = isUSDCSelected
|
||||||
|
? MustBigNumber(freeCollateral?.postOrder)
|
||||||
|
: nativeTokenBalance.minus(size?.size ?? 0);
|
||||||
|
|
||||||
const amountBN = MustBigNumber(amount);
|
const amountBN = MustBigNumber(amount);
|
||||||
const balanceBN = MustBigNumber(balance);
|
const balanceBN = MustBigNumber(balance);
|
||||||
const newBalanceBN = MustBigNumber(newBalance);
|
|
||||||
|
const onChangeAsset = (asset: DydxChainAsset) => {
|
||||||
|
setError(undefined);
|
||||||
|
setCurrentFee(undefined);
|
||||||
|
|
||||||
|
if (asset) {
|
||||||
|
abacusStateManager.setTransferValue({
|
||||||
|
value: asset,
|
||||||
|
field: TransferInputField.token,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
abacusStateManager.setTransferValue({
|
abacusStateManager.setTransferValue({
|
||||||
@@ -100,19 +115,13 @@ export const TransferForm = ({
|
|||||||
field: TransferInputField.type,
|
field: TransferInputField.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onChangeAsset(selectedAsset);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
abacusStateManager.clearTransferInputValues();
|
abacusStateManager.clearTransferInputValues();
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setError(undefined);
|
|
||||||
abacusStateManager.setTransferValue({
|
|
||||||
value: asset,
|
|
||||||
field: TransferInputField.token,
|
|
||||||
});
|
|
||||||
}, [asset]);
|
|
||||||
|
|
||||||
const { sanctionedAddresses } = useRestrictions();
|
const { sanctionedAddresses } = useRestrictions();
|
||||||
|
|
||||||
const isAddressValid = useMemo(
|
const isAddressValid = useMemo(
|
||||||
@@ -134,11 +143,6 @@ export const TransferForm = ({
|
|||||||
setError(undefined);
|
setError(undefined);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Subtract fees from amount if sending native tokens
|
|
||||||
const amountToTransfer = (
|
|
||||||
asset === DydxChainAsset.CHAINTOKEN ? amountBN.minus(fee) : amountBN
|
|
||||||
).toNumber();
|
|
||||||
|
|
||||||
const screenResults = await screenAddresses({
|
const screenResults = await screenAddresses({
|
||||||
addresses: [recipientAddress!, dydxAddress!],
|
addresses: [recipientAddress!, dydxAddress!],
|
||||||
});
|
});
|
||||||
@@ -157,9 +161,9 @@ export const TransferForm = ({
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const txResponse = await transfer(
|
const txResponse = await transfer(
|
||||||
amountToTransfer,
|
amountBN.toNumber(),
|
||||||
recipientAddress as string,
|
recipientAddress as string,
|
||||||
tokensConfigs[asset].denom,
|
tokensConfigs[asset]?.denom
|
||||||
);
|
);
|
||||||
|
|
||||||
if (txResponse?.code === 0) {
|
if (txResponse?.code === 0) {
|
||||||
@@ -200,7 +204,7 @@ export const TransferForm = ({
|
|||||||
const onChangeAmount = (value?: number) => {
|
const onChangeAmount = (value?: number) => {
|
||||||
abacusStateManager.setTransferValue({
|
abacusStateManager.setTransferValue({
|
||||||
value,
|
value,
|
||||||
field: asset === DydxChainAsset.USDC ? TransferInputField.usdcSize : TransferInputField.size,
|
field: isUSDCSelected ? TransferInputField.usdcSize : TransferInputField.size,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -226,7 +230,7 @@ export const TransferForm = ({
|
|||||||
value: DydxChainAsset.CHAINTOKEN,
|
value: DydxChainAsset.CHAINTOKEN,
|
||||||
label: (
|
label: (
|
||||||
<Styled.InlineRow>
|
<Styled.InlineRow>
|
||||||
{/* <AssetIcon symbol="DYDX" /> */}
|
<AssetIcon symbol={chainTokenLabel} />
|
||||||
{chainTokenLabel}
|
{chainTokenLabel}
|
||||||
</Styled.InlineRow>
|
</Styled.InlineRow>
|
||||||
),
|
),
|
||||||
@@ -249,15 +253,15 @@ export const TransferForm = ({
|
|||||||
key: 'amount',
|
key: 'amount',
|
||||||
label: (
|
label: (
|
||||||
<span>
|
<span>
|
||||||
{stringGetter({ key: STRING_KEYS.AVAILABLE })} <Tag>{tokensConfigs[asset].name}</Tag>
|
{stringGetter({ key: STRING_KEYS.AVAILABLE })} <Tag>{tokensConfigs[asset]?.name}</Tag>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
value: (
|
value: (
|
||||||
<DiffOutput
|
<DiffOutput
|
||||||
type={OutputType.Asset}
|
type={OutputType.Asset}
|
||||||
value={balanceBN.toString()}
|
value={balanceBN}
|
||||||
newValue={newBalanceBN.toString()}
|
|
||||||
sign={NumberSign.Negative}
|
sign={NumberSign.Negative}
|
||||||
|
newValue={newBalanceBN}
|
||||||
hasInvalidNewValue={newBalanceBN.isNegative()}
|
hasInvalidNewValue={newBalanceBN.isNegative()}
|
||||||
withDiff={Boolean(amount && balance) && !amountBN.isNaN()}
|
withDiff={Boolean(amount && balance) && !amountBN.isNaN()}
|
||||||
/>
|
/>
|
||||||
@@ -340,8 +344,8 @@ export const TransferForm = ({
|
|||||||
|
|
||||||
<Styled.SelectMenu
|
<Styled.SelectMenu
|
||||||
label={stringGetter({ key: STRING_KEYS.ASSET })}
|
label={stringGetter({ key: STRING_KEYS.ASSET })}
|
||||||
value={asset}
|
value={token}
|
||||||
onValueChange={setAsset}
|
onValueChange={onChangeAsset}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
{assetOptions.map(({ value, label }) => (
|
{assetOptions.map(({ value, label }) => (
|
||||||
@@ -355,12 +359,16 @@ export const TransferForm = ({
|
|||||||
type={InputType.Number}
|
type={InputType.Number}
|
||||||
onChange={({ floatValue }: NumberFormatValues) => onChangeAmount(floatValue)}
|
onChange={({ floatValue }: NumberFormatValues) => onChangeAmount(floatValue)}
|
||||||
value={amount ?? undefined}
|
value={amount ?? undefined}
|
||||||
slotRight={renderFormInputButton({
|
slotRight={
|
||||||
label: stringGetter({ key: STRING_KEYS.MAX }),
|
isUSDCSelected &&
|
||||||
isInputEmpty: size?.usdcSize == null,
|
balanceBN.gt(0) &&
|
||||||
onClear: () => onChangeAmount(undefined),
|
renderFormInputButton({
|
||||||
onClick: () => onChangeAmount(balanceBN.toNumber()),
|
label: stringGetter({ key: STRING_KEYS.MAX }),
|
||||||
})}
|
isInputEmpty: size?.usdcSize == null,
|
||||||
|
onClear: () => onChangeAmount(undefined),
|
||||||
|
onClick: () => onChangeAmount(balanceBN.toNumber()),
|
||||||
|
})
|
||||||
|
}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</WithDetailsReceipt>
|
</WithDetailsReceipt>
|
||||||
@@ -369,7 +377,7 @@ export const TransferForm = ({
|
|||||||
<AlertMessage type={AlertType.Warning}>
|
<AlertMessage type={AlertType.Warning}>
|
||||||
{stringGetter({
|
{stringGetter({
|
||||||
key: STRING_KEYS.TRANSFER_INSUFFICIENT_GAS,
|
key: STRING_KEYS.TRANSFER_INSUFFICIENT_GAS,
|
||||||
params: { USDC_BALANCE: `(${usdcBalance})` },
|
params: { USDC_BALANCE: `(${usdcBalance} USDC)` },
|
||||||
})}
|
})}
|
||||||
</AlertMessage>
|
</AlertMessage>
|
||||||
)}
|
)}
|
||||||
@@ -379,9 +387,9 @@ export const TransferForm = ({
|
|||||||
<Styled.Footer>
|
<Styled.Footer>
|
||||||
<TransferButtonAndReceipt
|
<TransferButtonAndReceipt
|
||||||
selectedAsset={asset}
|
selectedAsset={asset}
|
||||||
fees={fee || undefined}
|
fee={currentFee || undefined}
|
||||||
isDisabled={!isAmountValid || !isAddressValid || !fee || isLoading}
|
isDisabled={!isAmountValid || !isAddressValid || !currentFee || isLoading}
|
||||||
isLoading={isLoading || Boolean(isAmountValid && isAddressValid && !fee)}
|
isLoading={isLoading || Boolean(isAmountValid && isAddressValid && !currentFee)}
|
||||||
/>
|
/>
|
||||||
</Styled.Footer>
|
</Styled.Footer>
|
||||||
</Styled.Form>
|
</Styled.Form>
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ import { MustBigNumber } from '@/lib/numbers';
|
|||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
selectedAsset: DydxChainAsset;
|
selectedAsset: DydxChainAsset;
|
||||||
fees?: number;
|
fee?: number;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
isLoading?: boolean;
|
isLoading?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TransferButtonAndReceipt = ({
|
export const TransferButtonAndReceipt = ({
|
||||||
selectedAsset,
|
selectedAsset,
|
||||||
fees,
|
fee,
|
||||||
isDisabled,
|
isDisabled,
|
||||||
isLoading,
|
isLoading,
|
||||||
}: ElementProps) => {
|
}: ElementProps) => {
|
||||||
@@ -47,43 +47,46 @@ export const TransferButtonAndReceipt = ({
|
|||||||
const { current: equity, postOrder: newEquity } = equityInfo || {};
|
const { current: equity, postOrder: newEquity } = equityInfo || {};
|
||||||
const { current: leverage, postOrder: newLeverage } = leverageInfo || {};
|
const { current: leverage, postOrder: newLeverage } = leverageInfo || {};
|
||||||
|
|
||||||
// TODO(@aforaleka): add wallet balance for DYDX
|
const isUSDCSelected = selectedAsset === DydxChainAsset.USDC;
|
||||||
const balance = selectedAsset === DydxChainAsset.USDC ? equity : nativeTokenBalance;
|
|
||||||
const newBalance =
|
const balance = isUSDCSelected ? equity : nativeTokenBalance;
|
||||||
selectedAsset === DydxChainAsset.USDC
|
const newNativeTokenBalance = nativeTokenBalance
|
||||||
? newEquity
|
.minus(size?.size ?? 0)
|
||||||
: MustBigNumber(nativeTokenBalance)
|
.minus(fee ?? 0) // show balance after fees for button receipt
|
||||||
.minus(size?.size ?? 0)
|
.toNumber();
|
||||||
.toNumber();
|
|
||||||
|
const newBalance = isUSDCSelected ? newEquity : newNativeTokenBalance;
|
||||||
|
|
||||||
const transferDetailItems = [
|
const transferDetailItems = [
|
||||||
{
|
{
|
||||||
key: 'fees',
|
key: 'fees',
|
||||||
label: (
|
label: (
|
||||||
<span>
|
<span>
|
||||||
{stringGetter({ key: STRING_KEYS.FEES })} <Tag>{tokensConfigs[selectedAsset].name}</Tag>
|
{stringGetter({ key: STRING_KEYS.FEES })} <Tag>{tokensConfigs[selectedAsset]?.name}</Tag>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
value: <Output type={OutputType.Asset} value={fees} />,
|
value: <Output type={OutputType.Asset} value={fee} />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'balance',
|
key: 'balance',
|
||||||
label: (
|
label: (
|
||||||
<span>
|
<span>
|
||||||
{stringGetter({ key: STRING_KEYS.BALANCE })} <Tag>{tokensConfigs[selectedAsset].name}</Tag>
|
{stringGetter({ key: STRING_KEYS.BALANCE })}{' '}
|
||||||
|
<Tag>{tokensConfigs[selectedAsset]?.name}</Tag>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
value: (
|
value: (
|
||||||
<DiffOutput
|
<DiffOutput
|
||||||
type={OutputType.Asset}
|
type={OutputType.Asset}
|
||||||
value={balance}
|
value={balance}
|
||||||
newValue={newBalance}
|
|
||||||
sign={NumberSign.Negative}
|
sign={NumberSign.Negative}
|
||||||
withDiff={Boolean(newBalance) && balance !== newBalance}
|
newValue={newBalance}
|
||||||
|
hasInvalidNewValue={MustBigNumber(newBalance).isNegative()}
|
||||||
|
withDiff={newBalance !== null && !MustBigNumber(balance).eq(newBalance ?? 0)}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
selectedAsset === DydxChainAsset.USDC && {
|
isUSDCSelected && {
|
||||||
key: 'leverage',
|
key: 'leverage',
|
||||||
label: <span>{stringGetter({ key: STRING_KEYS.LEVERAGE })}</span>,
|
label: <span>{stringGetter({ key: STRING_KEYS.LEVERAGE })}</span>,
|
||||||
value: (
|
value: (
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ const Styled: Record<string, AnyStyledComponent> = {};
|
|||||||
Styled.DropdownSelectMenu = styled(DropdownSelectMenu)`
|
Styled.DropdownSelectMenu = styled(DropdownSelectMenu)`
|
||||||
${headerMixins.dropdownTrigger}
|
${headerMixins.dropdownTrigger}
|
||||||
|
|
||||||
|
width: max-content;
|
||||||
|
|
||||||
& > span:first-of-type {
|
& > span:first-of-type {
|
||||||
${layoutMixins.textOverflow}
|
${layoutMixins.textOverflow}
|
||||||
max-width: 5.625rem;
|
max-width: 5.625rem;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { ButtonAction } from '@/constants/buttons';
|
|||||||
import { DialogTypes } from '@/constants/dialogs';
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
import { STRING_KEYS, StringGetterFunction } from '@/constants/localization';
|
import { STRING_KEYS, StringGetterFunction } from '@/constants/localization';
|
||||||
|
|
||||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
import { useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { tradeViewMixins } from '@/styles/tradeViewMixins';
|
import { tradeViewMixins } from '@/styles/tradeViewMixins';
|
||||||
@@ -40,11 +40,13 @@ const getTransferHistoryTableColumnDef = ({
|
|||||||
key,
|
key,
|
||||||
stringGetter,
|
stringGetter,
|
||||||
width,
|
width,
|
||||||
|
mintscanTxUrl,
|
||||||
}: {
|
}: {
|
||||||
key: TransferHistoryTableColumnKey;
|
key: TransferHistoryTableColumnKey;
|
||||||
isTablet?: boolean;
|
isTablet?: boolean;
|
||||||
stringGetter: StringGetterFunction;
|
stringGetter: StringGetterFunction;
|
||||||
width?: ColumnSize;
|
width?: ColumnSize;
|
||||||
|
mintscanTxUrl?: string;
|
||||||
}): ColumnDef<SubaccountTransfer> => ({
|
}): ColumnDef<SubaccountTransfer> => ({
|
||||||
width,
|
width,
|
||||||
...(
|
...(
|
||||||
@@ -98,9 +100,12 @@ const getTransferHistoryTableColumnDef = ({
|
|||||||
columnKey: TransferHistoryTableColumnKey.TxHash,
|
columnKey: TransferHistoryTableColumnKey.TxHash,
|
||||||
getCellValue: (row) => row.transactionHash,
|
getCellValue: (row) => row.transactionHash,
|
||||||
label: stringGetter({ key: STRING_KEYS.TRANSACTION }),
|
label: stringGetter({ key: STRING_KEYS.TRANSACTION }),
|
||||||
renderCell: ({ transactionHash, resources }) =>
|
renderCell: ({ transactionHash }) =>
|
||||||
transactionHash ? (
|
transactionHash ? (
|
||||||
<Styled.TxHash withIcon href={resources.blockExplorerUrl}>
|
<Styled.TxHash
|
||||||
|
withIcon
|
||||||
|
href={`${mintscanTxUrl?.replace('{tx_hash}', transactionHash)}`}
|
||||||
|
>
|
||||||
{truncateAddress(transactionHash, '')}
|
{truncateAddress(transactionHash, '')}
|
||||||
</Styled.TxHash>
|
</Styled.TxHash>
|
||||||
) : (
|
) : (
|
||||||
@@ -130,6 +135,7 @@ export const TransferHistoryTable = ({
|
|||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { isMobile, isTablet } = useBreakpoints();
|
const { isMobile, isTablet } = useBreakpoints();
|
||||||
|
const { mintscan: mintscanTxUrl } = useURLConfigs();
|
||||||
|
|
||||||
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
|
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
|
||||||
|
|
||||||
@@ -146,6 +152,7 @@ export const TransferHistoryTable = ({
|
|||||||
isTablet,
|
isTablet,
|
||||||
stringGetter,
|
stringGetter,
|
||||||
width: columnWidths?.[key],
|
width: columnWidths?.[key],
|
||||||
|
mintscanTxUrl,
|
||||||
})
|
})
|
||||||
)}
|
)}
|
||||||
slotEmpty={
|
slotEmpty={
|
||||||
|
|||||||
Reference in New Issue
Block a user