Compare commits

..

11 Commits

Author SHA1 Message Date
jaredvu
3631f51878
Add withdrawalGateLearnMore to v1/env.json 2024-02-20 10:57:17 -08:00
jaredvu
8518cb6837
Add telemetry log fn 2024-02-20 10:48:29 -08:00
jaredvu
b9024ca6df
Add useEnvFeatures to gate fetch when unsupported on env 2024-02-20 10:41:33 -08:00
jaredvu
88fa4e26e4
TransferType prop 2024-02-20 10:10:42 -08:00
jaredvu
daf862aa40
Merge branch 'main' into withdrawal-safety 2024-02-20 10:00:22 -08:00
jaredvu
4e6742ba9d
nits: Add fadedWarning and fix globalStyle type 2024-02-09 20:35:39 -08:00
jaredvu
1ffbead4b3
nits 2024-02-09 20:21:52 -08:00
jaredvu
8cfe4e9d85
✏️ withdrawalGateLearnMore 2024-02-09 20:18:52 -08:00
jaredvu
d8383c1088
🚧 withdrawal gate and capacity 2024-02-09 20:18:32 -08:00
jaredvu
279a5239e5
Merge branch 'main' into withdrawal-safety 2024-02-09 09:12:06 -08:00
jaredvu
c4b42a77df
🚧 withdrwal-safety client calls 2024-02-05 11:32:24 -08:00
48 changed files with 611 additions and 489 deletions

View File

@ -3,7 +3,6 @@ VITE_BASE_URL=
VITE_ALCHEMY_API_KEY=
VITE_PK_ENCRYPTION_KEY=
VITE_ROUTER_TYPE=
VITE_V3_TOKEN_ADDRESS=
VITE_TOKEN_MIGRATION_URI=

View File

@ -66,12 +66,10 @@ This will automatically open your default browser at `http://localhost:61000`.
Add or modify the relevant endpoints, links and options in `/public/configs/env.json`.
You'll need to provide a Wallet Connect project id to enable onboarding and wallet connection:
- Create a project on https://cloud.walletconnect.com/app
- Copy over the project ID into this [field](https://github.com/dydxprotocol/v4-web/blob/67ecbd75b43e0c264b7b4d2d9b3d969830b0621c/public/configs/env.json#L822C33-L822C46)
## Part 4: Set Enviornment variables
Set environment variables via `.env`.
- `VITE_BASE_URL` (required): The base URL of the deployment (e.g., `https://www.example.com`).
@ -98,12 +96,10 @@ Select "Import Git Repository" from your dashboard, and provide the URL of this
### Step 2: Configure your project
For the "Build & Development Settings", we recommend the following:
- Framework Preset: `Vite`
- Build Command (override): `pnpm run build`
By default, the dev server runs in development mode and the build command runs in production mode. To override the default mode, you can pass in the `--mode` option flag. For example, if you want to build your app for testnet:
```
pnpm run build --mode testnet
```
@ -118,14 +114,6 @@ For more details, check out Vercel's [official documentation](https://vercel.com
## Deploying to IPFS
### Must Enable HashRouting
Add the following to `.env` file
```
VITE_ROUTER_TYPE=hash
```
### web3.storage: deploy to IPFS via web3.storage using the provided script
Export the API token as an environment variable (replace `your_token` with the generated token), and run the script to build and deploy to IPFS:
@ -162,14 +150,12 @@ Replace `your_cid` with the actual CID.
We recommend that you add your website to Cloudflare for additional security settings.
To block OFAC Sanctioned countries:
1. Navigate Websites > Domain > Security > WAF
2. Create Rule with the following settings:
- If incoming requests match
`(ip.geoip.country eq "CU") or (ip.geoip.country eq "IR") or (ip.geoip.country eq "KP") or (ip.geoip.country eq "SY") or (ip.geoip.country eq "MM") or (ip.geoip.subdivision_1_iso_code eq "UA-09") or (ip.geoip.subdivision_1_iso_code eq "UA-14") or (ip.geoip.subdivision_1_iso_code eq "UA-43")`
- This rule will bring up a Cloudflare page when a restricted geography tries to access your site. You will have the option to display:
1. Custom Text
- (e.g. `Because you appear to be a resident of, or trading from, a jurisdiction that violates our terms of use, or have engaged in activity that violates our terms of use, you have been blocked. You may withdraw your funds from the protocol at any time.`)
2. Default Cloudflare WAF block page
* If incoming requests match
`(ip.geoip.country eq "CU") or (ip.geoip.country eq "IR") or (ip.geoip.country eq "KP") or (ip.geoip.country eq "SY") or (ip.geoip.country eq "MM") or (ip.geoip.subdivision_1_iso_code eq "UA-09") or (ip.geoip.subdivision_1_iso_code eq "UA-14") or (ip.geoip.subdivision_1_iso_code eq "UA-43")`
* This rule will bring up a Cloudflare page when a restricted geography tries to access your site. You will have the option to display:
1. Custom Text
- (e.g. `Because you appear to be a resident of, or trading from, a jurisdiction that violates our terms of use, or have engaged in activity that violates our terms of use, you have been blocked. You may withdraw your funds from the protocol at any time.`)
2. Default Cloudflare WAF block page

View File

@ -40,9 +40,9 @@
"@cosmjs/proto-signing": "^0.32.1",
"@cosmjs/stargate": "^0.32.1",
"@cosmjs/tendermint-rpc": "^0.32.1",
"@dydxprotocol/v4-abacus": "^1.4.12",
"@dydxprotocol/v4-abacus": "^1.4.6",
"@dydxprotocol/v4-client-js": "^1.0.20",
"@dydxprotocol/v4-localization": "^1.1.35",
"@dydxprotocol/v4-localization": "^1.1.31",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
"@radix-ui/react-accordion": "^1.1.2",

24
pnpm-lock.yaml generated
View File

@ -1,9 +1,5 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides:
follow-redirects: 1.15.3
@ -30,14 +26,14 @@ dependencies:
specifier: ^0.32.1
version: 0.32.2
'@dydxprotocol/v4-abacus':
specifier: ^1.4.12
version: 1.4.12
specifier: ^1.4.6
version: 1.4.6
'@dydxprotocol/v4-client-js':
specifier: ^1.0.20
version: 1.0.20
'@dydxprotocol/v4-localization':
specifier: ^1.1.35
version: 1.1.35
specifier: ^1.1.31
version: 1.1.31
'@ethersproject/providers':
specifier: ^5.7.2
version: 5.7.2
@ -1290,8 +1286,8 @@ packages:
resolution: {integrity: sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q==}
dev: false
/@dydxprotocol/v4-abacus@1.4.12:
resolution: {integrity: sha512-dNQFaNrLTujyViMH2JEtf9Vn2ew2le+qTWexktK5h7AeADBbS9uxQknMIFhfwzGkbS1QqqszBT1plrDmQ+E6nQ==}
/@dydxprotocol/v4-abacus@1.4.6:
resolution: {integrity: sha512-qYq+4TizcMMxYVXckn0LCucWBe5N9ZNtD1XnowCAuBUUifHSgMGvao5OeZIKMgNM/udSKOXLss4zLy6dH/G2SA==}
dev: false
/@dydxprotocol/v4-client-js@1.0.20:
@ -1323,8 +1319,8 @@ packages:
- utf-8-validate
dev: false
/@dydxprotocol/v4-localization@1.1.35:
resolution: {integrity: sha512-q5JFYoL/QanHXOtFqRa2owBZJibi1sMpSm3dAcxs9x0/xe8mo6fWcnbQfhl8k7g0/tv7PsBc+e3rbWD0EfvGiA==}
/@dydxprotocol/v4-localization@1.1.31:
resolution: {integrity: sha512-plJVIgFAKq9/hA/gk5GgKgCQFsH3pNtDWfG/yHLDXyiGX0M0mMEi1bTNVs4podFVoHJu1nSL9YPFlpJ00FteGw==}
dev: false
/@dydxprotocol/v4-proto@4.0.0-dev.0:
@ -16053,3 +16049,7 @@ packages:
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: true
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false

View File

@ -98,7 +98,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -126,7 +127,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-dev-2": {
@ -182,7 +184,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -210,7 +213,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-dev-4": {
@ -267,7 +271,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -295,7 +300,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-dev-5": {
@ -351,7 +357,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -379,7 +386,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-staging": {
@ -437,7 +445,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -465,7 +474,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-staging-forced-update": {
@ -514,7 +524,8 @@
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals"
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -549,7 +560,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-staging-west": {
@ -607,7 +619,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -635,7 +648,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": true,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet": {
@ -697,7 +711,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -725,7 +740,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet-dydx": {
@ -784,7 +800,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -812,7 +829,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet-nodefleet": {
@ -871,7 +889,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -899,7 +918,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet-kingnodes": {
@ -958,7 +978,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -986,7 +1007,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet-liquify": {
@ -1045,7 +1067,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -1073,7 +1096,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet-polkachu": {
@ -1123,7 +1147,8 @@
"community": "https://discord.com/invite/dydx",
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
"blogs": "https://www.dydx.foundation/blog",
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals"
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -1151,7 +1176,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet-bware": {
@ -1210,7 +1236,8 @@
"keplrDashboard": "https://testnet.keplr.app/",
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnmore": "https://help.dydx.exchange",
"walletLearnmore": "https://www.dydx.academy/video/defi-wallet"
"walletLearnmore": "https://www.dydx.academy/video/defi-wallet",
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"wallets": {
"walletconnect": {
@ -1238,7 +1265,8 @@
}
},
"featureFlags": {
"reduceOnlySupported": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-mainnet": {
@ -1297,7 +1325,8 @@
"keplrDashboard": "[HTTP link to keplr dashboard, can be null]",
"strideZoneApp": "[HTTP link to stride zone app, can be null]",
"accountExportLearnMore": "[HTTP link to account export learn more, can be null]",
"walletLearnMore": "[HTTP link to wallet learn more, can be null]"
"walletLearnMore": "[HTTP link to wallet learn more, can be null]",
"withdrawalGateLearnMore": "[HTTP link to withdrawal gate learn more, can be null]"
},
"wallets": {
"walletconnect": {
@ -1325,8 +1354,9 @@
}
},
"featureFlags": {
"reduceOnlySupported": false
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": false
}
}
}
}
}

View File

@ -428,13 +428,6 @@
"whitepaperLink": "https://solana.com/solana-whitepaper.pdf",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/solana/"
},
"STRK-USD": {
"name": "Starknet",
"tags": ["Layer 2"],
"websiteLink": "https://www.starknet.io/en",
"whitepaperLink": "https://docs.starknet.io/documentation/",
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/starknet-token/"
},
"STX-USD": {
"name": "Stacks",
"tags": ["Layer 2"],

View File

@ -513,14 +513,6 @@
{ "exchangeName": "Okx", "ticker": "SOL-USDT", "adjustByMarket": "USDT-USD" },
{ "exchangeName": "Mexc", "ticker": "SOL_USDT", "adjustByMarket": "USDT-USD" }
],
"STRK": [
{ "exchangeName": "Binance", "ticker": "STRKUSDT", "adjustByMarket": "USDT-USD" },
{ "exchangeName": "Bybit", "ticker": "STRKUSDT", "adjustByMarket": "USDT-USD" },
{ "exchangeName": "Kraken", "ticker": "STRKUSD" },
{ "exchangeName": "Kucoin", "ticker": "STRK-USDT", "adjustByMarket": "USDT-USD" },
{ "exchangeName": "Okx", "ticker": "STRK-USDT", "adjustByMarket": "USDT-USD" },
{ "exchangeName": "Gate", "ticker": "STRK_USDT", "adjustByMarket": "USDT-USD" }
],
"STX": [
{ "exchangeName": "Binance", "ticker": "STXUSDT", "adjustByMarket": "USDT-USD" },
{ "exchangeName": "Bybit", "ticker": "STXUSDT", "adjustByMarket": "USDT-USD" },

View File

@ -968,23 +968,6 @@
"minOrderSize": 1000000,
"quantumConversionExponent": -9
},
{
"baseAsset": "STRK",
"referencePrice": 2.05,
"numOracles": 6,
"liquidityTier": 2,
"assetName": "Starknet",
"p": 0.0,
"atomicResolution": -6.0,
"minExchanges": 3,
"minPriceChangePpm": 4000,
"priceExponent": -9.0,
"stepBaseQuantum": 1000000,
"ticksizeExponent": -3,
"subticksPerTick": 1000000,
"minOrderSize": 1000000,
"quantumConversionExponent": -9
},
{
"baseAsset": "STX",
"referencePrice": 1.5144048703611412,

View File

@ -72,7 +72,7 @@
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"launchIncentive": "https://cloud.chaoslabs.co"
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"dydx-testnet-4": {
"tos": "https://dydx.exchange/v4-terms",
@ -94,7 +94,7 @@
"strideZoneApp": "https://testnet.stride.zone",
"accountExportLearnMore": "https://help.dydx.exchange/en/articles/8565867-secret-phrase-on-dydx-chain",
"walletLearnMore": "https://www.dydx.academy/video/defi-wallet",
"launchIncentive": "https://cloud.chaoslabs.co"
"withdrawalGateLearnMore": "https://help.dydx.exchange"
},
"[mainnet chain id]": {
"tos": "[HTTP link to TOS]",
@ -116,7 +116,7 @@
"strideZoneApp": "[HTTP link to stride zone app, can be null]",
"accountExportLearnMore": "[HTTP link to account export learn more, can be null]",
"walletLearnMore": "[HTTP link to wallet learn more, can be null]",
"launchIncentive": "[HTTP link to launch incentive host, can be null]"
"withdrawalGateLearnMore": "[HTTP link to withdrawal gate learn more, can be null]"
}
},
"wallets": {
@ -262,7 +262,7 @@
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-dev-2": {
@ -288,7 +288,7 @@
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-dev-4": {
@ -315,7 +315,7 @@
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-dev-5": {
@ -341,7 +341,7 @@
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-staging": {
@ -368,7 +368,7 @@
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-staging-forced-update": {
@ -402,7 +402,7 @@
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-staging-west": {
@ -429,7 +429,7 @@
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": false
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet": {
@ -459,8 +459,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet-dydx": {
@ -486,8 +486,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet-nodefleet": {
@ -513,8 +513,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet-kingnodes": {
@ -540,8 +540,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet-liquify": {
@ -567,8 +567,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet-polkachu": {
@ -594,8 +594,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-testnet-bware": {
@ -621,8 +621,8 @@
"faucet": "https://faucet.v4testnet.dydx.exchange"
},
"featureFlags": {
"reduceOnlySupported": true,
"usePessimisticCollateralCheck": true
"reduceOnlySupported": false,
"withdrawalSafetyEnabled": true
}
},
"dydxprotocol-mainnet": {
@ -649,8 +649,8 @@
},
"featureFlags": {
"reduceOnlySupported": false,
"usePessimisticCollateralCheck": true
"withdrawalSafetyEnabled": false
}
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

View File

@ -1,5 +1,5 @@
import { lazy, Suspense, useMemo } from 'react';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import { lazy, Suspense } from 'react';
import { Navigate, Route, Routes } from 'react-router-dom';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { WagmiConfig } from 'wagmi';
import { QueryClient, QueryClientProvider } from 'react-query';
@ -26,7 +26,6 @@ import { RestrictionProvider } from '@/hooks/useRestrictions';
import { SubaccountProvider } from '@/hooks/useSubaccount';
import { GuardedMobileRoute } from '@/components/GuardedMobileRoute';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { HeaderDesktop } from '@/layout/Header/HeaderDesktop';
import { FooterDesktop } from '@/layout/Footer/FooterDesktop';
@ -35,12 +34,12 @@ import { NotificationsToastArea } from '@/layout/NotificationsToastArea';
import { DialogManager } from '@/layout/DialogManager';
import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
import { parseLocationHash } from '@/lib/urlUtils';
import { config } from '@/lib/wagmi';
import { breakpoints } from '@/styles';
import { GlobalStyle } from '@/styles/globalStyle';
import { layoutMixins } from '@/styles/layoutMixins';
import { LoadingSpace } from './components/Loading/LoadingSpinner';
import '@/styles/constants.css';
import '@/styles/fonts.css';
@ -69,14 +68,6 @@ const Content = () => {
const isShowingHeader = isNotTablet;
const isShowingFooter = useShouldShowFooter();
const { chainTokenLabel } = useTokenConfigs();
const location = useLocation();
const pathFromHash = useMemo(() => {
if (location.hash === '') {
return '';
}
return parseLocationHash(location.hash);
}, [location.hash]);
return (
<>
@ -111,10 +102,8 @@ const Content = () => {
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
<Route
path="*"
element={<Navigate to={pathFromHash || DEFAULT_TRADE_ROUTE} replace />}
/>
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
</Routes>
</Suspense>
</Styled.Main>

View File

@ -7,7 +7,12 @@ type ElementProps = {
onClick?: () => void;
};
type StyleProps = {
className?: string;
};
export const BackButton = ({
className,
onClick = () => {
// @ts-ignore
const navigation = globalThis.navigation;
@ -21,8 +26,9 @@ export const BackButton = ({
navigation.navigate('/', { replace: true });
}
},
}: ElementProps) => (
}: ElementProps & StyleProps) => (
<IconButton
className={className}
onClick={onClick}
iconName={IconName.ChevronLeft}
size={ButtonSize.Small}

View File

@ -47,6 +47,7 @@ type StyleProps = {
hasHeaderBorder?: boolean;
children?: React.ReactNode;
className?: string;
stacked?: boolean;
withAnimation?: boolean;
};
@ -81,6 +82,7 @@ export const Dialog = ({
slotTrigger,
slotHeaderInner,
slotFooter,
stacked,
withClose = true,
placement = DialogPlacement.Default,
portalContainer,
@ -109,27 +111,48 @@ export const Dialog = ({
e.preventDefault();
}
}}
$stacked={stacked}
$withAnimation={withAnimation}
>
<Styled.Header $withBorder={hasHeaderBorder}>
<Styled.HeaderTopRow>
{onBack && <BackButton onClick={onBack} />}
{stacked ? (
<Styled.StackedHeaderTopRow $withBorder={hasHeaderBorder}>
{onBack && <Styled.BackButton onClick={onBack} />}
{slotIcon && <Styled.Icon>{slotIcon}</Styled.Icon>}
{title && <Styled.Title>{title}</Styled.Title>}
{slotIcon}
{!preventClose && withClose && (
<Styled.Close ref={closeButtonRef}>
<Styled.Close ref={closeButtonRef} $absolute={stacked}>
<Icon iconName={IconName.Close} />
</Styled.Close>
)}
</Styled.HeaderTopRow>
{description && <Styled.Description>{description}</Styled.Description>}
{title && <Styled.Title>{title}</Styled.Title>}
{slotHeaderInner}
</Styled.Header>
{description && <Styled.Description>{description}</Styled.Description>}
{slotHeaderInner}
</Styled.StackedHeaderTopRow>
) : (
<Styled.Header $withBorder={hasHeaderBorder}>
<Styled.HeaderTopRow>
{onBack && <BackButton onClick={onBack} />}
{slotIcon && <Styled.Icon>{slotIcon}</Styled.Icon>}
{title && <Styled.Title>{title}</Styled.Title>}
{!preventClose && withClose && (
<Styled.Close ref={closeButtonRef}>
<Icon iconName={IconName.Close} />
</Styled.Close>
)}
</Styled.HeaderTopRow>
{description && <Styled.Description>{description}</Styled.Description>}
{slotHeaderInner}
</Styled.Header>
)}
<Styled.Content>{children}</Styled.Content>
@ -173,7 +196,11 @@ Styled.Overlay = styled(Overlay)`
}
`;
Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?: boolean }>`
Styled.Container = styled(Content)<{
placement: DialogPlacement;
$stacked?: boolean;
$withAnimation?: boolean;
}>`
/* Params */
--dialog-inset: 1rem;
--dialog-width: 30rem;
@ -353,6 +380,13 @@ Styled.Container = styled(Content)<{ placement: DialogPlacement; $withAnimation?
bottom: 0;
`,
}[placement])}
${({ $stacked }) =>
$stacked &&
css`
justify-content: center;
text-align: center;
`}
`;
Styled.Header = styled.header<{ $withBorder: boolean }>`
@ -379,9 +413,21 @@ Styled.HeaderTopRow = styled.div`
gap: var(--dialog-title-gap);
`;
Styled.HeaderTopRow = styled.div`
${layoutMixins.row}
gap: var(--dialog-title-gap);
Styled.StackedHeaderTopRow = styled.div<{ $withBorder: boolean }>`
${layoutMixins.flexColumn}
align-items: center;
justify-content: center;
padding: var(--dialog-header-paddingTop) var(--dialog-header-paddingLeft)
var(--dialog-header-paddingBottom) var(--dialog-header-paddingRight);
border-top-left-radius: inherit;
border-top-right-radius: inherit;
${({ $withBorder }) =>
$withBorder &&
css`
${layoutMixins.withOuterBorder};
background: var(--dialog-backgroundColor);
`};
`;
Styled.Content = styled.div`
@ -412,7 +458,7 @@ Styled.Icon = styled.div`
line-height: 1;
`;
Styled.Close = styled(Close)`
Styled.Close = styled(Close)<{ $absolute?: boolean }>`
width: 0.7813rem;
height: 0.7813rem;
@ -438,6 +484,14 @@ Styled.Close = styled(Close)`
color: var(--color-text-2);
}
${({ $absolute }) =>
$absolute &&
css`
position: absolute;
right: var(--dialog-header-paddingRight);
top: var(--dialog-header-paddingTop);
`}
@media ${breakpoints.tablet} {
width: 1rem;
height: 1rem;
@ -445,6 +499,12 @@ Styled.Close = styled(Close)`
}
`;
Styled.BackButton = styled(BackButton)`
position: absolute;
left: var(--dialog-header-paddingLeft);
top: var(--dialog-header-paddingTop);
`;
Styled.Title = styled(Title)`
flex: 1;

View File

@ -81,9 +81,6 @@ export enum AnalyticsEvent {
TradePlaceOrderConfirmed = 'TradePlaceOrderConfirmed',
TradeCancelOrder = 'TradeCancelOrder',
TradeCancelOrderConfirmed = 'TradeCancelOrderConfirmed',
// Notification
NotificationAction = 'NotificationAction',
}
export type AnalyticsEventData<T extends AnalyticsEvent> =
@ -183,12 +180,6 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
/** URL/IP of node the order was sent to */
validatorUrl: string;
}
: // Notifcation
T extends AnalyticsEvent.NotificationAction
? {
type: string;
id: string;
}
: never;
export const DEFAULT_TRANSACTION_MEMO = 'dYdX Frontend (web)';

View File

@ -9,9 +9,11 @@ export enum DialogTypes {
FillDetails = 'FillDetails',
Help = 'Help',
ExternalNavKeplr = 'ExternalNavKeplr',
ManageFunds = 'ManageFunds',
MnemonicExport = 'MnemonicExport',
MobileSignIn = 'MobileSignIn',
MobileDownload = 'MobileDownload',
NewMarketAgreement = 'NewMarketAgreement',
NewMarketMessageDetails = 'NewMarketMessageDetails',
Onboarding = 'Onboarding',
OrderDetails = 'OrderDetails',
Preferences = 'Preferences',
@ -21,9 +23,7 @@ export enum DialogTypes {
Trade = 'Trade',
Transfer = 'Transfer',
Withdraw = 'Withdraw',
ManageFunds = 'ManageFunds',
NewMarketMessageDetails = 'NewMarketMessageDetails',
NewMarketAgreement = 'NewMarketAgreement',
WithdrawalGated = 'WithdrawalGated',
}
export enum TradeBoxDialogTypes {

View File

@ -143,11 +143,6 @@ export type TransferNotifcation = {
isExchange?: boolean;
};
export enum ReleaseUpdateNotificationIds {
RewardsAndFullTradingLive = 'rewards-and-full-trading-live',
IncentivesS3 = 'incentives-s3',
}
/**
* @description Struct to store whether a NotificationType should be triggered
*/

View File

@ -41,7 +41,6 @@ export enum MobileSettingsRoute {
Network = 'network',
}
export const BASE_ROUTE = import.meta.env.VITE_ROUTER_TYPE === 'hash' ? '/#' : '';
export const TRADE_ROUTE = `${AppRoute.Trade}/:market`;
export const PORTFOLIO_ROUTE = `${AppRoute.Portfolio}/:subroute`;
export const HISTORY_ROUTE = `${AppRoute.Portfolio}/${PortfolioRoute.History}/:subroute`;

View File

@ -16,7 +16,6 @@ import {
KeplrIcon,
MathWalletIcon,
MetaMaskIcon,
OkxWalletIcon,
RainbowIcon,
TokenPocketIcon,
TrustWalletIcon,
@ -90,7 +89,6 @@ export enum WalletType {
// Ledger = 'LEDGER',
MathWallet = 'MATH_WALLET',
MetaMask = 'METAMASK',
OkxWallet = 'OKX_WALLET',
Rainbow = 'RAINBOW_WALLET',
TokenPocket = 'TOKEN_POCKET',
TrustWallet = 'TRUST_WALLET',
@ -104,7 +102,6 @@ const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = {
imToken: 'ef333840daf915aafdc4a004525502d6d49d77bd9c65e0642dbaefb3c2893bef',
TokenPocket: '20459438007b75f4f4acb98bf29aa3b800550309646d375da5fd4aac6c2a2c66',
Trust: '4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0',
OkxWallet: '971e689d0a5be527bac79629b4ee9b925e82208e5168b733496a09c0faed0709',
Rainbow: '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369',
Zerion: 'ecc4036f814562b41a5268adc86270fba1365471402006302e70169465b7ac18',
Ledger: '19177a98252e07ddfc9af2083ba8e07ef627cb6103467ffebb3f8f4205fd7927',
@ -215,14 +212,6 @@ export const wallets: Record<WalletType, WalletConfig> = {
matchesInjectedEip1193: isMetaMask,
walletconnect2Id: WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS.Metamask,
},
[WalletType.OkxWallet]: {
type: WalletType.OkxWallet,
stringKey: STRING_KEYS.OKX_WALLET,
icon: OkxWalletIcon,
connectionTypes: [WalletConnectionType.InjectedEip1193, WalletConnectionType.WalletConnect2],
matchesInjectedEip1193: (provider) => provider.isOkxWallet,
walletconnect2Id: WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS.OkxWallet,
},
[WalletType.Rainbow]: {
type: WalletType.Rainbow,
stringKey: STRING_KEYS.RAINBOW_WALLET,
@ -289,10 +278,6 @@ export type WithInjectedWeb3Provider = {
};
};
export type WithInjectedOkxWalletProvider = {
okxwallet: InjectedWeb3Provider;
};
// Wallet connections
export type WalletConnection = {

View File

@ -7,6 +7,7 @@ import { useDebounce } from './useDebounce';
import { useInterval } from './useInterval';
import { useDocumentTitle } from './useDocumentTitle';
import { useDydxClient } from './useDydxClient';
import { useEnvFeatures } from './useEnvFeatures';
import { useGovernanceVariables } from './useGovernanceVariables';
import { useAccountBalance } from './useAccountBalance';
import { useAccounts } from './useAccounts';
@ -26,6 +27,7 @@ import { useStringGetter } from './useStringGetter';
import { useSubaccount } from './useSubaccount';
import { useTradeFormInputs } from './useTradeFormInputs';
import { useURLConfigs } from './useURLConfigs';
import { useWithdrawalInfo } from './useWithdrawalInfo';
export {
useApiState,
@ -36,6 +38,7 @@ export {
useDebounce,
useDocumentTitle,
useDydxClient,
useEnvFeatures,
useGovernanceVariables,
useAccountBalance,
useAccounts,
@ -56,4 +59,5 @@ export {
useSubaccount,
useTradeFormInputs,
useURLConfigs,
useWithdrawalInfo,
};

View File

@ -57,15 +57,13 @@ export const useChartLines = ({
useEffect(() => {
if (tvWidget && isChartReady) {
tvWidget.onChartReady(() => {
tvWidget.headerReady().then(() => {
tvWidget.chart().dataReady(() => {
if (showOrderLines) {
drawOrderLines();
drawPositionLine();
} else {
deleteChartLines();
}
});
tvWidget.chart().dataReady(() => {
if (showOrderLines) {
drawOrderLines();
drawPositionLine();
} else {
deleteChartLines();
}
});
});
}

View File

@ -10,10 +10,9 @@ export const useDisplayedWallets = () => {
isDev && WalletType.Keplr,
WalletType.WalletConnect2,
WalletType.CoinbaseWallet,
WalletType.OkxWallet,
// Hide these wallet options until they can be properly tested on mainnet
// WalletType.ImToken,
// WalletType.Rainbow,

View File

@ -251,6 +251,17 @@ const useDydxClientContext = () => {
[compositeClient]
);
const getWithdrawalAndTransferGatingStatus = useCallback(async () => {
return await compositeClient?.validatorClient.get.GetWithdrawalAndTransferGatingStatus();
}, [compositeClient]);
const getWithdrawalCapacityByDenom = useCallback(
async ({ denom }: { denom: string }) => {
return await compositeClient?.validatorClient.get.getWithdrawalCapacityByDenom(denom);
},
[compositeClient]
);
return {
// Client initialization
connect: setNetworkConfig,
@ -267,5 +278,7 @@ const useDydxClientContext = () => {
requestAllGovernanceProposals,
getCandlesForDatafeed,
screenAddresses,
getWithdrawalAndTransferGatingStatus,
getWithdrawalCapacityByDenom,
};
};

View File

@ -0,0 +1,15 @@
import { useSelector } from 'react-redux';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
import { getSelectedNetwork } from '@/state/appSelectors';
export interface EnvironmentFeatures {
reduceOnlySupported: boolean;
withdrawalSafetyEnabled: boolean;
}
export const useEnvFeatures = (): EnvironmentFeatures => {
const selectedNetwork = useSelector(getSelectedNetwork);
return ENVIRONMENT_CONFIG_MAP[selectedNetwork].featureFlags;
};

View File

@ -5,7 +5,7 @@ import { isEqual, groupBy } from 'lodash';
import { useNavigate } from 'react-router-dom';
import { DialogTypes } from '@/constants/dialogs';
import { AppRoute, TokenRoute } from '@/constants/routes';
import { AppRoute } from '@/constants/routes';
import { DydxChainAsset } from '@/constants/wallets';
import {
@ -20,10 +20,9 @@ import {
NotificationType,
DEFAULT_TOAST_AUTO_CLOSE_MS,
TransferNotificationTypes,
ReleaseUpdateNotificationIds,
} from '@/constants/notifications';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { useStringGetter } from '@/hooks';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { AssetIcon } from '@/components/AssetIcon';
@ -239,16 +238,13 @@ export const notificationTypes: NotificationTypeConfig[] = [
{
type: NotificationType.ReleaseUpdates,
useTrigger: ({ trigger }) => {
const { chainTokenLabel } = useTokenConfigs();
const stringGetter = useStringGetter();
const expirationDate = new Date('2024-03-08T23:59:59');
const currentDate = new Date();
useEffect(() => {
trigger(
ReleaseUpdateNotificationIds.RewardsAndFullTradingLive,
'rewards-and-full-trading-live',
{
icon: <AssetIcon symbol={chainTokenLabel} />,
icon: <AssetIcon symbol="DYDX" />,
title: stringGetter({ key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.TITLE' }),
body: stringGetter({
key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.BODY',
@ -274,41 +270,14 @@ export const notificationTypes: NotificationTypeConfig[] = [
},
}),
toastSensitivity: 'foreground',
groupKey: ReleaseUpdateNotificationIds.RewardsAndFullTradingLive,
groupKey: NotificationType.ReleaseUpdates,
},
[]
);
if (currentDate <= expirationDate) {
trigger(
ReleaseUpdateNotificationIds.IncentivesS3,
{
icon: <AssetIcon symbol={chainTokenLabel} />,
title: stringGetter({ key: 'NOTIFICATIONS.INCENTIVES_SEASON_BEGUN.TITLE' }),
body: stringGetter({
key: 'NOTIFICATIONS.INCENTIVES_SEASON_BEGUN.BODY',
params: {
SEASON_NUMBER: '3',
PREV_SEASON_NUMBER: '1',
DYDX_AMOUNT: '34',
USDC_AMOUNT: '100',
},
}),
toastSensitivity: 'foreground',
groupKey: ReleaseUpdateNotificationIds.IncentivesS3,
},
[]
);
}
}, [stringGetter]);
},
useNotificationAction: () => {
const { chainTokenLabel } = useTokenConfigs();
const navigate = useNavigate();
return (notificationId: string) => {
if (notificationId === ReleaseUpdateNotificationIds.IncentivesS3) {
navigate(`${chainTokenLabel}/${TokenRoute.TradingRewards}`);
}
};
return () => {};
},
},
];

View File

@ -1,6 +1,5 @@
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { AnalyticsEvent } from '@/constants/analytics';
import { LOCAL_STORAGE_VERSIONS, LocalStorageKey } from '@/constants/localStorage';
import {
type Notification,
@ -14,8 +13,7 @@ import {
import { useLocalStorage } from './useLocalStorage';
import { notificationTypes } from './useNotificationTypes';
import { track } from '@/lib/analytics';
import { renderSvgToDataUrl } from '@/lib/renderSvgToDataUrl';
import { renderSvgToDataUrl } from '../lib/renderSvgToDataUrl';
type NotificationsContextType = ReturnType<typeof useNotificationsContext>;
@ -50,7 +48,6 @@ const useNotificationsContext = () => {
defaultValue: {
[NotificationType.AbacusGenerated]: true,
[NotificationType.SquidTransfer]: true,
[NotificationType.ReleaseUpdates]: true,
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
},
});
@ -179,10 +176,8 @@ const useNotificationsContext = () => {
)
);
const onNotificationAction = async (notification: Notification) => {
track(AnalyticsEvent.NotificationAction, { type: notification.type, id: notification.id });
return await actions[notification.type]?.(notification.id);
};
const onNotificationAction = async (notification: Notification) =>
await actions[notification.type]?.(notification.id);
// Push notifications
const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({

View File

@ -27,6 +27,7 @@ export interface LinksConfigs {
strideZoneApp?: string;
accountExportLearnMore?: string;
walletLearnMore?: string;
withdrawalGateLearnMore?: string;
}
export const useURLConfigs = (): LinksConfigs => {
@ -54,5 +55,6 @@ export const useURLConfigs = (): LinksConfigs => {
strideZoneApp: linksConfigs.strideZoneApp || FALLBACK_URL,
accountExportLearnMore: linksConfigs.accountExportLearnMore || FALLBACK_URL,
walletLearnMore: linksConfigs.walletLearnMore || FALLBACK_URL,
withdrawalGateLearnMore: linksConfigs.withdrawalGateLearnMore || FALLBACK_URL,
};
};

View File

@ -0,0 +1,131 @@
import { useEffect, useMemo } from 'react';
import { useQuery } from 'react-query';
import BigNumber from 'bignumber.js';
import { encodeJson } from '@dydxprotocol/v4-client-js';
import { ByteArrayEncoding } from '@dydxprotocol/v4-client-js/build/src/lib/helpers';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { DialogTypes } from '@/constants/dialogs';
import { isMainnet } from '@/constants/networks';
import { useEnvFeatures } from './useEnvFeatures';
import { getApiState } from '@/state/appSelectors';
import { closeDialog, openDialog } from '@/state/dialogs';
import { getSelectedLocale } from '@/state/localizationSelectors';
import { formatRelativeTime } from '@/lib/dateTime';
import { BIG_NUMBERS, MustBigNumber } from '@/lib/numbers';
import { log } from '@/lib/telemetry';
import { useDydxClient } from './useDydxClient';
import { useTokenConfigs } from './useTokenConfigs';
const BLOCK_TIME = isMainnet ? 1_000 : 1_500;
export const useWithdrawalInfo = ({
transferType,
}: {
transferType: 'withdrawal' | 'transfer';
}) => {
const { getWithdrawalAndTransferGatingStatus, getWithdrawalCapacityByDenom } = useDydxClient();
const { usdcDenom, usdcDecimals } = useTokenConfigs();
const apiState = useSelector(getApiState, shallowEqual);
const { height } = apiState || {};
const selectedLocale = useSelector(getSelectedLocale);
const dispatch = useDispatch();
const { withdrawalSafetyEnabled } = useEnvFeatures();
const { data: usdcWithdrawalCapacity } = useQuery({
enabled: withdrawalSafetyEnabled,
queryKey: 'usdcWithdrawalCapacity',
queryFn: async () => {
try {
const response = await getWithdrawalCapacityByDenom({ denom: usdcDenom });
return JSON.parse(encodeJson(response, ByteArrayEncoding.BIGINT));
} catch (error) {
log('useWithdrawalInfo/getWithdrawalCapacityByDenom', error);
}
},
refetchInterval: 60_000,
staleTime: 60_000,
});
const { data: withdrawalAndTransferGatingStatus } = useQuery({
enabled: withdrawalSafetyEnabled,
queryKey: 'withdrawalTransferGateStatus',
queryFn: async () => {
try {
return await getWithdrawalAndTransferGatingStatus();
} catch (error) {
log('useWithdrawalInfo/getWithdrawalAndTransferGatingStatus', error);
}
},
refetchInterval: 60_000,
staleTime: 60_000,
});
const capacity = useMemo(() => {
const capacityList = usdcWithdrawalCapacity?.limiterCapacityList;
if (!capacityList || capacityList.length < 2) {
if (!withdrawalSafetyEnabled) {
return BigNumber(Infinity);
}
return BIG_NUMBERS.ZERO;
}
const [{ capacity: daily }, { capacity: weekly }] = capacityList;
const dailyBN = MustBigNumber(daily);
const weeklyBN = MustBigNumber(weekly);
return BigNumber.minimum(dailyBN, weeklyBN).div(10 ** usdcDecimals);
}, [usdcDecimals, usdcWithdrawalCapacity]);
const withdrawalAndTransferGatingStatusValue = useMemo(() => {
const { withdrawalsAndTransfersUnblockedAtBlock } = withdrawalAndTransferGatingStatus ?? {};
if (
height &&
withdrawalsAndTransfersUnblockedAtBlock &&
height < withdrawalsAndTransfersUnblockedAtBlock &&
withdrawalSafetyEnabled
) {
return {
estimatedUnblockTime: formatRelativeTime(
Date.now() + (withdrawalsAndTransfersUnblockedAtBlock - height) * BLOCK_TIME,
{
locale: selectedLocale,
largestUnit: 'day',
}
),
isGated: true,
};
}
return {
estimatedUnblockTime: null,
isGated: false,
};
}, [height, withdrawalAndTransferGatingStatus, withdrawalSafetyEnabled]);
useEffect(() => {
if (
withdrawalAndTransferGatingStatusValue.isGated &&
withdrawalAndTransferGatingStatusValue.estimatedUnblockTime &&
withdrawalSafetyEnabled
) {
dispatch(closeDialog());
dispatch(
openDialog({
type: DialogTypes.WithdrawalGated,
dialogProps: {
transferType,
estimatedUnblockTime: withdrawalAndTransferGatingStatusValue.estimatedUnblockTime,
},
})
);
}
}, [transferType, withdrawalAndTransferGatingStatusValue.isGated, withdrawalSafetyEnabled]);
return {
usdcWithdrawalCapacity: capacity,
withdrawalAndTransferGatingStatus,
};
};

View File

@ -83,7 +83,6 @@ export { default as LedgerIcon } from './wallets/ledger.svg';
export { default as MagicIcon } from './wallets/magic.svg';
export { default as MathWalletIcon } from './wallets/mathwallet.svg';
export { default as MetaMaskIcon } from './wallets/metamask.svg';
export { default as OkxWalletIcon } from './wallets/okx-wallet.svg';
export { default as RainbowIcon } from './wallets/rainbow-wallet.svg';
export { default as TestWalletIcon } from './wallets/test-wallet.svg';
export { default as TokenPocketIcon } from './wallets/tokenpocket.svg';

View File

@ -1 +0,0 @@
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400" viewBox="0, 0, 400,400"><g id="svgg"><path id="path0" d="M85.200 76.394 C 80.665 76.883,78.308 78.483,76.929 82.006 C 76.085 84.161,75.592 142.346,76.360 149.217 C 76.910 154.145,78.020 156.078,81.141 157.541 L 83.400 158.600 117.600 158.600 C 154.653 158.600,154.238 158.623,156.431 156.431 C 158.623 154.238,158.600 154.653,158.600 117.600 L 158.600 83.400 157.541 81.141 C 156.025 77.909,154.089 76.841,148.902 76.377 C 144.325 75.968,89.012 75.983,85.200 76.394 M249.890 76.409 C 245.967 76.882,243.812 78.255,242.459 81.141 L 241.400 83.400 241.400 117.600 C 241.400 154.653,241.377 154.238,243.569 156.431 C 245.762 158.623,245.347 158.600,282.400 158.600 L 316.600 158.600 318.859 157.541 C 324.048 155.108,324.091 154.742,323.907 114.679 C 323.739 78.113,323.917 79.248,317.999 76.930 C 316.018 76.154,255.815 75.693,249.890 76.409 M163.813 159.382 C 158.668 160.777,158.800 159.708,158.800 200.000 C 158.800 240.779,158.632 239.561,164.419 240.762 C 168.838 241.680,235.468 241.166,237.395 240.200 C 241.459 238.161,241.400 238.753,241.400 200.000 C 241.400 161.247,241.459 161.839,237.395 159.800 C 235.329 158.764,167.515 158.378,163.813 159.382 M84.192 241.586 C 75.955 242.833,75.887 243.209,76.093 286.273 C 76.252 319.496,76.167 318.455,78.918 321.120 C 81.744 323.860,80.445 323.758,114.679 323.911 C 154.743 324.090,155.109 324.047,157.541 318.859 L 158.600 316.600 158.600 282.400 C 158.600 242.256,158.762 243.612,153.768 241.955 C 151.993 241.366,87.894 241.026,84.192 241.586 M248.251 241.633 C 241.106 242.699,241.228 241.990,241.215 282.600 C 241.202 321.601,241.152 321.289,247.760 323.301 C 250.708 324.198,315.213 324.005,317.830 323.091 C 323.901 320.971,323.748 321.925,323.911 285.321 C 324.090 245.267,324.075 245.137,319.000 242.600 L 316.600 241.400 283.600 241.341 C 265.450 241.309,249.543 241.440,248.251 241.633 " stroke="none" fill="#fafafa" fill-rule="evenodd"></path><path id="path1" d="M0.000 200.000 L 0.000 400.000 200.000 400.000 L 400.000 400.000 400.000 200.000 L 400.000 0.000 200.000 0.000 L 0.000 0.000 0.000 200.000 M148.902 76.377 C 154.089 76.841,156.025 77.909,157.541 81.141 L 158.600 83.400 158.600 117.600 C 158.600 154.653,158.623 154.238,156.431 156.431 C 154.238 158.623,154.653 158.600,117.600 158.600 L 83.400 158.600 81.141 157.541 C 75.953 155.109,75.910 154.743,76.089 114.679 C 76.250 78.643,76.131 79.509,81.200 77.288 C 83.883 76.112,137.816 75.387,148.902 76.377 M317.999 76.930 C 323.917 79.248,323.739 78.113,323.907 114.679 C 324.091 154.742,324.048 155.108,318.859 157.541 L 316.600 158.600 282.400 158.600 C 245.347 158.600,245.762 158.623,243.569 156.431 C 241.377 154.238,241.400 154.653,241.400 117.600 L 241.400 83.400 242.459 81.141 C 243.812 78.255,245.967 76.882,249.890 76.409 C 255.815 75.693,316.018 76.154,317.999 76.930 M237.395 159.800 C 241.459 161.839,241.400 161.247,241.400 200.000 C 241.400 238.753,241.459 238.161,237.395 240.200 C 235.468 241.166,168.838 241.680,164.419 240.762 C 158.632 239.561,158.800 240.779,158.800 200.000 C 158.800 159.708,158.668 160.777,163.813 159.382 C 167.515 158.378,235.329 158.764,237.395 159.800 M153.768 241.955 C 158.762 243.612,158.600 242.256,158.600 282.400 L 158.600 316.600 157.541 318.859 C 155.109 324.047,154.743 324.090,114.679 323.911 C 80.445 323.758,81.744 323.860,78.918 321.120 C 76.167 318.455,76.252 319.496,76.093 286.273 C 75.887 243.209,75.955 242.833,84.192 241.586 C 87.894 241.026,151.993 241.366,153.768 241.955 M319.000 242.600 C 324.075 245.137,324.090 245.267,323.911 285.321 C 323.748 321.925,323.901 320.971,317.830 323.091 C 315.213 324.005,250.708 324.198,247.760 323.301 C 244.032 322.166,242.292 320.037,241.590 315.751 C 241.084 312.662,241.101 252.756,241.608 248.844 C 242.629 240.980,241.034 241.265,283.600 241.341 L 316.600 241.400 319.000 242.600 " stroke="none" fill="#040404" fill-rule="evenodd"></path></g></svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -11,27 +11,27 @@ import { DepositDialog } from '@/views/dialogs/DepositDialog';
import { DisconnectDialog } from '@/views/dialogs/DisconnectDialog';
import { DisplaySettingsDialog } from '@/views/dialogs/DisplaySettingsDialog';
import { ExchangeOfflineDialog } from '@/views/dialogs/ExchangeOfflineDialog';
import { ExternalNavStrideDialog } from '@/views/dialogs/ExternalNavStrideDialog';
import { HelpDialog } from '@/views/dialogs/HelpDialog';
import { ExternalLinkDialog } from '@/views/dialogs/ExternalLinkDialog';
import { ExternalNavKeplrDialog } from '@/views/dialogs/ExternalNavKeplrDialog';
import { ManageFundsDialog } from '@/views/dialogs/ManageFundsDialog';
import { MnemonicExportDialog } from '@/views/dialogs/MnemonicExportDialog';
import { MobileSignInDialog } from '@/views/dialogs/MobileSignInDialog';
import { NewMarketAgreementDialog } from '@/views/dialogs/NewMarketAgreementDialog';
import { NewMarketMessageDetailsDialog } from '@/views/dialogs/NewMarketMessageDetailsDialog';
import { OnboardingDialog } from '@/views/dialogs/OnboardingDialog';
import { PreferencesDialog } from '@/views/dialogs/PreferencesDialog';
import { RateLimitDialog } from '@/views/dialogs/RateLimitDialog';
import { RestrictedGeoDialog } from '@/views/dialogs/RestrictedGeoDialog';
import { RestrictedWalletDialog } from '@/views/dialogs/RestrictedWalletDialog';
import { TradeDialog } from '@/views/dialogs/TradeDialog';
import { TransferDialog } from '@/views/dialogs/TransferDialog';
import { RestrictedWalletDialog } from '@/views/dialogs/RestrictedWalletDialog';
import { WithdrawDialog } from '@/views/dialogs/WithdrawDialog';
import { ManageFundsDialog } from '@/views/dialogs/ManageFundsDialog';
import { WithdrawalGateDialog } from '@/views/dialogs/WithdrawalGateDialog';
import { OrderDetailsDialog } from '@/views/dialogs/DetailsDialog/OrderDetailsDialog';
import { FillDetailsDialog } from '@/views/dialogs/DetailsDialog/FillDetailsDialog';
import { NewMarketMessageDetailsDialog } from '@/views/dialogs/NewMarketMessageDetailsDialog';
import { NewMarketAgreementDialog } from '@/views/dialogs/NewMarketAgreementDialog';
import { ExternalNavStrideDialog } from '@/views/dialogs/ExternalNavStrideDialog';
import { MobileDownloadDialog } from '@/views/dialogs/MobileDownloadDialog';
export const DialogManager = () => {
const dispatch = useDispatch();
@ -64,7 +64,6 @@ export const DialogManager = () => {
[DialogTypes.ExternalNavStride]: <ExternalNavStrideDialog {...modalProps} />,
[DialogTypes.MnemonicExport]: <MnemonicExportDialog {...modalProps} />,
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
[DialogTypes.MobileDownload]: <MobileDownloadDialog {...modalProps} />,
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,
[DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />,
[DialogTypes.Preferences]: <PreferencesDialog {...modalProps} />,
@ -74,6 +73,7 @@ export const DialogManager = () => {
[DialogTypes.Trade]: <TradeDialog {...modalProps} />,
[DialogTypes.Transfer]: <TransferDialog {...modalProps} />,
[DialogTypes.Withdraw]: <WithdrawDialog {...modalProps} />,
[DialogTypes.WithdrawalGated]: <WithdrawalGateDialog {...modalProps} />,
[DialogTypes.ManageFunds]: <ManageFundsDialog {...modalProps} />,
[DialogTypes.NewMarketMessageDetails]: <NewMarketMessageDetailsDialog {...modalProps} />,
[DialogTypes.NewMarketAgreement]: <NewMarketAgreementDialog {...modalProps} />,

View File

@ -1,14 +0,0 @@
import { describe, expect, it } from 'vitest';
import { parseLocationHash } from '@/lib/urlUtils';
describe('parseLocationHash', () => {
it('returns the path separated from hash', () => {
const hash = '#/markets';
expect(parseLocationHash(hash)).toEqual('/markets');
});
it('returns the path and query string separated from hash', () => {
const hash = '#/markets?displayinitializingmarkets=true';
expect(parseLocationHash(hash)).toEqual('/markets?displayinitializingmarkets=true');
});
});

View File

@ -26,6 +26,10 @@ class TestFlags {
get showTradingRewards() {
return !!this.queryParams.tradingrewards;
}
get showCexWithdrawal() {
return !!this.queryParams.cexwithdrawal;
}
}
export const testFlags = new TestFlags();

View File

@ -1,13 +0,0 @@
/**
* @param hash location.hash
* @returns path and query string if hash parameter is not empty
*/
export const parseLocationHash = (hash: string) => {
if (!hash || hash.length === 0) return '';
// Remove '#' and split by '?'
const [path, queryString] = hash.substring(1).split('?');
// Reconstruct path and query string
return `${path}${queryString ? `?${queryString}` : ''}`;
};

View File

@ -5,7 +5,6 @@ import {
type InjectedCoinbaseWalletExtensionProvider,
type WithInjectedEthereumProvider,
type WithInjectedWeb3Provider,
type WithInjectedOkxWalletProvider,
} from '@/constants/wallets';
import { isTruthy } from '../isTruthy';
@ -19,6 +18,15 @@ export const isMetaMask = (provider: ExternalProvider) => (
&& (
!(provider as InjectedCoinbaseWalletExtensionProvider).overrideIsMetaMask
)
/* not a MetaMask wannabe! */
&& (
Reflect.ownKeys(provider).filter((key) =>
typeof key === 'string'
&& key.match(/^is/)
&& !['isConnected', 'isMetaMask'].includes(key)
).length === 0
)
)
/*
@ -46,10 +54,5 @@ export const detectInjectedEip1193Providers = (): ExternalProvider[] => {
? ethereumProvider.providers
: [];
const okxWalletProvider = (globalThis as typeof globalThis & WithInjectedOkxWalletProvider)
?.okxwallet;
return [...displacedProviders, ethereumProvider, web3Provider, okxWalletProvider].filter(
isTruthy
);
return [...displacedProviders, ethereumProvider, web3Provider].filter(isTruthy);
};

View File

@ -1,7 +1,7 @@
import './polyfills';
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter, HashRouter } from 'react-router-dom';
import { HashRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { store } from '@/state/_store';
@ -12,13 +12,11 @@ import './index.css';
import App from './App';
const Router = import.meta.env.VITE_ROUTER_TYPE === 'hash' ? HashRouter : BrowserRouter;
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<ErrorBoundary>
<StrictMode>
<Provider store={store}>
<Router children={<App />} />
<HashRouter children={<App />} />
</Provider>
</StrictMode>
</ErrorBoundary>

5
src/styles/styled.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import { ThemeColorBase } from '@/constants/styles/colors';
declare module 'styled-components' {
export interface DefaultTheme extends ThemeColorBase {}
}

View File

@ -1,140 +0,0 @@
import styled, { AnyStyledComponent, css } from 'styled-components';
import { layoutMixins } from '@/styles/layoutMixins';
import { Dialog } from '@/components/Dialog';
import { QrCode } from '@/components/QrCode';
import { useStringGetter } from '@/hooks';
import { STRING_KEYS } from '@/constants/localization';
type ElementProps = {
setIsOpen: (open: boolean) => void;
};
/*
When/if deployer deploys the web app with smartbanner, "smartbanner:button-url-apple" and/or
"smartbanner:button-url-google" <meta> are set.
This implementation assumes "smartbanner:button-url-apple" and "smartbanner:button-url-google"
are set to the same value with onelink or other redirect URL.
Since there is no way for the desktop web app to know what mobile device the user is using,
we should give a onelink URL which redirects to either iOS or Android app store depending on
the mobile device used to scan the link.
*/
// for testing only
// export const mobileAppUrl = "http://example.com";
let mobileAppUrl: string | undefined | null = undefined;
export const getMobileAppUrl = () => {
if (!mobileAppUrl) {
mobileAppUrl =
// for testing to verify <meta> is retrieved by name, QR code should show "@dYdX" as value
// document.querySelector('meta[name="twitter:creator"]')?.getAttribute('content') ??
document.querySelector('meta[name="smartbanner:button-url-apple"]')?.getAttribute('content') ??
document.querySelector('meta[name="smartbanner:button-url-google"]')?.getAttribute('content');
}
return mobileAppUrl;
}
const MobileQrCode = ({
url,
}: {
url: string;
}) => {
return (
<Styled.QrCodeContainer isShowing={true}>
<QrCode hasLogo size={432} value={url} />
</Styled.QrCodeContainer>
);
};
/*
MobileDownloadDialog should only been shown on desktop when mobileAppUrl has value. That's controlled by AccountMenu.tsx.
*/
export const MobileDownloadDialog = ({ setIsOpen }: ElementProps) => {
const stringGetter = useStringGetter();
const content = (
<MobileQrCode url={mobileAppUrl!} />
);
return (
<Dialog isOpen setIsOpen={setIsOpen} title={
stringGetter({ key: STRING_KEYS.DOWNLOAD_MOBILE_APP })
}>
<Styled.Content>{content}</Styled.Content>
</Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Content = styled.div`
${layoutMixins.column}
gap: 1rem;
strong {
font-weight: 900;
color: var(--color-text-2);
}
footer {
${layoutMixins.row}
justify-content: space-between;
svg {
width: auto;
}
}
`;
Styled.WaitingSpan = styled.span`
strong {
color: var(--color-warning);
}
`;
Styled.QrCodeContainer = styled.figure<{ isShowing: boolean }>`
${layoutMixins.stack}
overflow: hidden;
border-radius: 0.75em;
cursor: pointer;
transition: 0.2s;
&:hover {
filter: brightness(var(--hover-filter-base));
}
> * {
position: relative;
transition: 0.16s;
}
> :first-child {
pointer-events: none;
${({ isShowing }) =>
!isShowing &&
css`
filter: blur(1rem) brightness(1.4);
will-change: filter;
`}
}
> span {
place-self: center;
font-size: 1.4em;
color: var(--color-text-2);
${({ isShowing }) =>
isShowing &&
css`
opacity: 0;
`}
}
`;

View File

@ -2,7 +2,7 @@ import { useState } from 'react';
import styled, { AnyStyledComponent } from 'styled-components';
import { ButtonAction } from '@/constants/buttons';
import { AppRoute, BASE_ROUTE } from '@/constants/routes';
import { AppRoute } from '@/constants/routes';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import breakpoints from '@/styles/breakpoints';
@ -40,7 +40,7 @@ export const NewMarketAgreementDialog = ({ acceptTerms, setIsOpen }: ElementProp
</Styled.Link>
),
TERMS_OF_USE: (
<Styled.Link href={`${BASE_ROUTE}${AppRoute.Terms}`}>
<Styled.Link href={`/#${AppRoute.Terms}`}>
{stringGetter({ key: STRING_KEYS.TERMS_OF_USE })}
</Styled.Link>
),

View File

@ -2,7 +2,7 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { useAccounts, useStringGetter } from '@/hooks';
import { AppRoute, BASE_ROUTE } from '@/constants/routes';
import { AppRoute } from '@/constants/routes';
import { STRING_KEYS } from '@/constants/localization';
import { ButtonAction } from '@/constants/buttons';
@ -34,12 +34,12 @@ export const AcknowledgeTerms = ({ onClose, onContinue }: ElementProps) => {
key: STRING_KEYS.TOS_TITLE,
params: {
TERMS_LINK: (
<Styled.Link href={`${BASE_ROUTE}${AppRoute.Terms}`}>
<Styled.Link href={`/#${AppRoute.Terms}`}>
{stringGetter({ key: STRING_KEYS.TERMS_OF_USE })}
</Styled.Link>
),
PRIVACY_POLICY_LINK: (
<Styled.Link href={`${BASE_ROUTE}${AppRoute.Privacy}`}>
<Styled.Link href={`/#${AppRoute.Privacy}`}>
{stringGetter({ key: STRING_KEYS.PRIVACY_POLICY })}
</Styled.Link>
),

View File

@ -0,0 +1,101 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { ButtonAction, ButtonType } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter, useURLConfigs } from '@/hooks';
import { LinkOutIcon } from '@/icons';
import { layoutMixins } from '@/styles/layoutMixins';
import { Button } from '@/components/Button';
import { Dialog } from '@/components/Dialog';
import { Icon, IconName } from '@/components/Icon';
type ElementProps = {
setIsOpen: (open: boolean) => void;
transferType: 'withdrawal' | 'transfer';
estimatedUnblockTime?: string | null;
};
export const WithdrawalGateDialog = ({
setIsOpen,
estimatedUnblockTime,
transferType,
}: ElementProps) => {
const stringGetter = useStringGetter();
const { withdrawalGateLearnMore } = useURLConfigs();
return (
<Dialog
isOpen
stacked
setIsOpen={setIsOpen}
title={
{
withdrawal: stringGetter({ key: STRING_KEYS.WITHDRAWALS_PAUSED }),
transfer: stringGetter({ key: STRING_KEYS.TRANSFERS_PAUSED }),
}[transferType]
}
slotIcon={
<Styled.IconContainer>
<Styled.Icon iconName={IconName.Warning} />
</Styled.IconContainer>
}
slotFooter={
<Styled.ButtonRow>
<Button
type={ButtonType.Link}
action={ButtonAction.Secondary}
href={withdrawalGateLearnMore}
>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
<LinkOutIcon />
</Button>
<Button action={ButtonAction.Primary} onClick={() => setIsOpen(false)}>
{stringGetter({ key: STRING_KEYS.CLOSE })}
</Button>
</Styled.ButtonRow>
}
>
<Styled.Content>
{stringGetter({
key: STRING_KEYS.WITHDRAWALS_PAUSED_DESC,
params: {
ESTIMATED_DURATION: estimatedUnblockTime,
},
})}
</Styled.Content>
</Dialog>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.IconContainer = styled.div`
display: flex;
justify-content: center;
align-items: center;
width: 4.5rem;
height: 4.5rem;
border-radius: 50%;
min-width: 4.5rem;
min-height: 4.5rem;
background-color: var(--color-gradient-warning);
`;
Styled.Icon = styled(Icon)`
color: var(--color-warning);
font-size: 2.5rem;
margin-bottom: 0.125rem;
`;
Styled.Content = styled.div`
${layoutMixins.column}
gap: 1rem;
`;
Styled.ButtonRow = styled.div`
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
`;

View File

@ -63,11 +63,12 @@ export const SourceSelectMenu = ({
return (
<SearchSelectMenu
items={[
exchangeItems.length > 0 && {
group: 'exchanges',
groupLabel: stringGetter({ key: STRING_KEYS.EXCHANGES }),
items: exchangeItems,
},
exchangeItems.length > 0 &&
(testFlags.showCexWithdrawal || type === TransferType.deposit) && {
group: 'exchanges',
groupLabel: stringGetter({ key: STRING_KEYS.EXCHANGES }),
items: exchangeItems,
},
chainItems.length > 0 && {
group: 'chains',
groupLabel: stringGetter({ key: STRING_KEYS.CHAINS }),

View File

@ -17,6 +17,7 @@ import {
MAX_PRICE_IMPACT,
MIN_CCTP_TRANSFER_AMOUNT,
NumberSign,
TOKEN_DECIMALS,
} from '@/constants/numbers';
import {
@ -27,6 +28,8 @@ import {
useSelectedNetwork,
useStringGetter,
useSubaccount,
useTokenConfigs,
useWithdrawalInfo,
} from '@/hooks';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
@ -85,6 +88,8 @@ export const WithdrawForm = () => {
const [withdrawAmount, setWithdrawAmount] = useState('');
const [slippage, setSlippage] = useState(isCctp ? 0 : 0.01); // 0.1% slippage
const debouncedAmount = useDebounce<string>(withdrawAmount, 500);
const { usdcLabel } = useTokenConfigs();
const { usdcWithdrawalCapacity } = useWithdrawalInfo({ transferType: 'withdrawal' });
const isValidAddress = toAddress && isAddress(toAddress);
@ -321,61 +326,102 @@ export const WithdrawForm = () => {
const { sanctionedAddresses } = useRestrictions();
const errorMessage = useMemo(() => {
const { alertType, errorMessage } = useMemo(() => {
if (error) {
return error;
return {
errorMessage: error,
};
}
if (routeErrors) {
return routeErrorMessage
? stringGetter({
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
params: { ERROR_MESSAGE: routeErrorMessage },
})
: stringGetter({ key: STRING_KEYS.SOMETHING_WENT_WRONG });
return {
errorMessage: 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 {
alertType: AlertType.Warning,
errorMessage: stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ADDRESS }),
};
}
if (sanctionedAddresses.has(toAddress))
return stringGetter({
key: STRING_KEYS.TRANSFER_INVALID_DYDX_ADDRESS,
});
return {
errorMessage: stringGetter({
key: STRING_KEYS.TRANSFER_INVALID_DYDX_ADDRESS,
}),
};
if (debouncedAmountBN) {
if (!chainIdStr && !exchange) {
return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_CHAIN });
return {
errorMessage: stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_CHAIN }),
};
} else if (!toToken) {
return stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ASSET });
return {
errorMessage: stringGetter({ key: STRING_KEYS.WITHDRAW_MUST_SPECIFY_ASSET }),
};
}
}
if (MustBigNumber(debouncedAmountBN).gt(MustBigNumber(freeCollateralBN))) {
return stringGetter({ key: STRING_KEYS.WITHDRAW_MORE_THAN_FREE });
if (debouncedAmountBN.gt(MustBigNumber(freeCollateralBN))) {
return {
errorMessage: stringGetter({ key: STRING_KEYS.WITHDRAW_MORE_THAN_FREE }),
};
}
if (isCctp) {
if (MustBigNumber(debouncedAmountBN).gte(MAX_CCTP_TRANSFER_AMOUNT)) {
return stringGetter({
key: STRING_KEYS.MAX_CCTP_TRANSFER_LIMIT_EXCEEDED,
params: {
MAX_CCTP_TRANSFER_AMOUNT: MAX_CCTP_TRANSFER_AMOUNT,
},
});
if (debouncedAmountBN.gte(MAX_CCTP_TRANSFER_AMOUNT)) {
return {
errorMessage: stringGetter({
key: STRING_KEYS.MAX_CCTP_TRANSFER_LIMIT_EXCEEDED,
params: {
MAX_CCTP_TRANSFER_AMOUNT: MAX_CCTP_TRANSFER_AMOUNT,
},
}),
};
}
if (
!debouncedAmountBN.isZero() &&
MustBigNumber(debouncedAmountBN).lte(MIN_CCTP_TRANSFER_AMOUNT)
) {
return 'Amount must be greater than 10 USDC';
return {
errorMessage: 'Amount must be greater than 10 USDC',
};
}
}
if (isMainnet && MustBigNumber(summary?.aggregatePriceImpact).gte(MAX_PRICE_IMPACT)) {
return stringGetter({ key: STRING_KEYS.PRICE_IMPACT_TOO_HIGH });
return { errorMessage: stringGetter({ key: STRING_KEYS.PRICE_IMPACT_TOO_HIGH }) };
}
return undefined;
// Withdrawal Safety
if (usdcWithdrawalCapacity.gt(0) && debouncedAmountBN.gt(usdcWithdrawalCapacity)) {
return {
alertType: AlertType.Warning,
errorMessage: stringGetter({
key: STRING_KEYS.WITHDRAWAL_LIMIT_OVER,
params: {
USDC_LIMIT: (
<span>
{usdcWithdrawalCapacity.toFormat(TOKEN_DECIMALS)}
<Styled.Tag>{usdcLabel}</Styled.Tag>
</span>
),
},
}),
};
}
return {
errorMessage: undefined,
};
}, [
error,
routeErrors,
@ -388,6 +434,7 @@ export const WithdrawForm = () => {
sanctionedAddresses,
stringGetter,
summary,
usdcWithdrawalCapacity,
]);
const isInvalidNobleAddress = Boolean(
@ -448,7 +495,11 @@ export const WithdrawForm = () => {
}
/>
</Styled.WithDetailsReceipt>
{errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>}
{errorMessage && (
<Styled.AlertMessage type={alertType ?? AlertType.Error}>
{errorMessage}
</Styled.AlertMessage>
)}
<Styled.Footer>
<WithdrawButtonAndReceipt
isDisabled={isDisabled}
@ -465,6 +516,10 @@ export const WithdrawForm = () => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Tag = styled(Tag)`
margin-left: 0.5ch;
`;
Styled.DiffOutput = styled(DiffOutput)`
--diffOutput-valueWithDiff-fontSize: 1em;
`;
@ -484,6 +539,10 @@ Styled.DestinationRow = styled.div`
gap: 1rem;
`;
Styled.AlertMessage = styled(AlertMessage)`
display: inline;
`;
Styled.WithDetailsReceipt = styled(WithDetailsReceipt)`
--withReceipt-backgroundColor: var(--color-layer-2);
`;

View File

@ -20,6 +20,7 @@ import {
useStringGetter,
useSubaccount,
useTokenConfigs,
useWithdrawalInfo,
} from '@/hooks';
import { formMixins } from '@/styles/formMixins';
@ -64,6 +65,7 @@ export const TransferForm = ({
const { nativeTokenBalance, usdcBalance } = useAccountBalance();
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
const { tokensConfigs, usdcLabel, chainTokenLabel } = useTokenConfigs();
useWithdrawalInfo({ transferType: 'transfer' });
const {
address: recipientAddress,

View File

@ -40,7 +40,6 @@ import { getAppTheme } from '@/state/configsSelectors';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
import { MustBigNumber } from '@/lib/numbers';
import { getMobileAppUrl } from '../dialogs/MobileDownloadDialog';
export const AccountMenu = () => {
const stringGetter = useStringGetter();
@ -190,18 +189,6 @@ export const AccountMenu = () => {
label: stringGetter({ key: STRING_KEYS.DISPLAY_SETTINGS }),
onSelect: () => dispatch(openDialog({ type: DialogTypes.DisplaySettings })),
},
...(getMobileAppUrl()
? [
{
value: 'MobileDownload',
icon: <Icon iconName={IconName.Qr} />,
label: stringGetter({ key: STRING_KEYS.DOWNLOAD_MOBILE_APP }),
onSelect: () => {
dispatch(openDialog({ type: DialogTypes.MobileDownload }));
},
},
]
: []),
...(onboardingState === OnboardingState.AccountConnected && hdKey
? [
{

View File

@ -22,6 +22,6 @@
"@/*": ["src/*"]
}
},
"include": ["src", "scripts"],
"include": ["src", "scripts", "styled.d.ts"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -1,3 +0,0 @@
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}