Compare commits
30 Commits
feature/MO
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d24a02f7b9 | ||
|
|
caaea7e475 | ||
|
|
9d3a124485 | ||
|
|
038eb08e2e | ||
|
|
e1d2a5eda4 | ||
|
|
56be9ba9cc | ||
|
|
859e7dde80 | ||
|
|
d9daf9f583 | ||
|
|
5be9d2b1b2 | ||
|
|
d2c5b7945b | ||
|
|
fc5d8538fe | ||
|
|
fdc262f59f | ||
|
|
7f947804bd | ||
|
|
6620a70a5d | ||
|
|
bff779631b | ||
|
|
b115bf33a0 | ||
|
|
b544b8202b | ||
|
|
ad6f99ffdf | ||
|
|
a7bd6a3ee8 | ||
|
|
c92f218493 | ||
|
|
b09fe9ebb1 | ||
|
|
1c28505ec0 | ||
|
|
5531e21c76 | ||
|
|
4ebeb21841 | ||
|
|
b638ec1ad6 | ||
|
|
f7a1cfc67e | ||
|
|
d66a776ad2 | ||
|
|
e3e00f4f1e | ||
|
|
9ea578b8e4 | ||
|
|
f47fda45bf |
@ -3,6 +3,7 @@ VITE_BASE_URL=
|
||||
VITE_ALCHEMY_API_KEY=
|
||||
|
||||
VITE_PK_ENCRYPTION_KEY=
|
||||
VITE_ROUTER_TYPE=
|
||||
|
||||
VITE_V3_TOKEN_ADDRESS=
|
||||
VITE_TOKEN_MIGRATION_URI=
|
||||
|
||||
@ -1 +1,2 @@
|
||||
node_modules/
|
||||
public/
|
||||
|
||||
26
README.md
@ -66,10 +66,12 @@ 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`).
|
||||
@ -96,10 +98,12 @@ 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
|
||||
```
|
||||
@ -114,6 +118,14 @@ 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:
|
||||
@ -150,12 +162,14 @@ 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
|
||||
|
||||
@ -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.2",
|
||||
"@dydxprotocol/v4-abacus": "^1.4.12",
|
||||
"@dydxprotocol/v4-client-js": "^1.0.20",
|
||||
"@dydxprotocol/v4-localization": "^1.1.28",
|
||||
"@dydxprotocol/v4-localization": "^1.1.35",
|
||||
"@ethersproject/providers": "^5.7.2",
|
||||
"@js-joda/core": "^5.5.3",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
|
||||
16
pnpm-lock.yaml
generated
@ -30,14 +30,14 @@ dependencies:
|
||||
specifier: ^0.32.1
|
||||
version: 0.32.2
|
||||
'@dydxprotocol/v4-abacus':
|
||||
specifier: ^1.4.2
|
||||
version: 1.4.2
|
||||
specifier: ^1.4.12
|
||||
version: 1.4.12
|
||||
'@dydxprotocol/v4-client-js':
|
||||
specifier: ^1.0.20
|
||||
version: 1.0.20
|
||||
'@dydxprotocol/v4-localization':
|
||||
specifier: ^1.1.28
|
||||
version: 1.1.28
|
||||
specifier: ^1.1.35
|
||||
version: 1.1.35
|
||||
'@ethersproject/providers':
|
||||
specifier: ^5.7.2
|
||||
version: 5.7.2
|
||||
@ -1290,8 +1290,8 @@ packages:
|
||||
resolution: {integrity: sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q==}
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-abacus@1.4.2:
|
||||
resolution: {integrity: sha512-+hugk0RulMwMthR2xCMYXohcC3sEYqVW/lmiq0RUuHZ9yrjmgy48xl0aZUmXGUYXyoiHXPS4AULhRKHQ4OOLwg==}
|
||||
/@dydxprotocol/v4-abacus@1.4.12:
|
||||
resolution: {integrity: sha512-dNQFaNrLTujyViMH2JEtf9Vn2ew2le+qTWexktK5h7AeADBbS9uxQknMIFhfwzGkbS1QqqszBT1plrDmQ+E6nQ==}
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-client-js@1.0.20:
|
||||
@ -1323,8 +1323,8 @@ packages:
|
||||
- utf-8-validate
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-localization@1.1.28:
|
||||
resolution: {integrity: sha512-R8hGDbJvZb9HsI0jwYIYDS3aNM3T0aLlXOnogihGrRRibZs874XJy5fCGBW2aRXAyDu56nbjkyuOAQCkF3KP1Q==}
|
||||
/@dydxprotocol/v4-localization@1.1.35:
|
||||
resolution: {integrity: sha512-q5JFYoL/QanHXOtFqRa2owBZJibi1sMpSm3dAcxs9x0/xe8mo6fWcnbQfhl8k7g0/tv7PsBc+e3rbWD0EfvGiA==}
|
||||
dev: false
|
||||
|
||||
/@dydxprotocol/v4-proto@4.0.0-dev.0:
|
||||
|
||||
@ -1,12 +1,22 @@
|
||||
[
|
||||
{
|
||||
"chainId": "1",
|
||||
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
{
|
||||
"chainId": "5",
|
||||
"tokenAddress": "0x07865c6E87B9F70255377e024ace6630C1Eaa37F",
|
||||
"name": "Ethereum Goerli"
|
||||
}
|
||||
]
|
||||
{
|
||||
"chainId": "1",
|
||||
"tokenAddress": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
||||
"name": "Ethereum"
|
||||
},
|
||||
{
|
||||
"chainId": "5",
|
||||
"tokenAddress": "0x07865c6E87B9F70255377e024ace6630C1Eaa37F",
|
||||
"name": "Ethereum Goerli"
|
||||
},
|
||||
{
|
||||
"chainId": "43114",
|
||||
"tokenAddress": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
|
||||
"name": "Avalanche"
|
||||
},
|
||||
{
|
||||
"chainId": "10",
|
||||
"tokenAddress": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
|
||||
"name": "optimism"
|
||||
}
|
||||
]
|
||||
|
||||
@ -725,7 +725,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-dydx": {
|
||||
@ -812,7 +812,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-nodefleet": {
|
||||
@ -899,7 +899,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-kingnodes": {
|
||||
@ -986,7 +986,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-liquify": {
|
||||
@ -1073,7 +1073,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-polkachu": {
|
||||
@ -1151,7 +1151,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-bware": {
|
||||
@ -1238,7 +1238,7 @@
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false
|
||||
"reduceOnlySupported": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-mainnet": {
|
||||
@ -1329,4 +1329,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -20,6 +20,13 @@
|
||||
"whitepaperLink": "https://why.cardano.org/en/introduction/motivation/",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/cardano/"
|
||||
},
|
||||
"AGIX-USD": {
|
||||
"name": "SingularityNET",
|
||||
"tags": ["AI"],
|
||||
"websiteLink": "https://public.singularitynet.io/whitepaper.pdf",
|
||||
"whitepaperLink": "https://public.singularitynet.io/whitepaper.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/singularitynet/"
|
||||
},
|
||||
"ALGO-USD": {
|
||||
"name": "Algorand",
|
||||
"tags": ["Layer 1"],
|
||||
@ -78,7 +85,7 @@
|
||||
},
|
||||
"BLUR-USD": {
|
||||
"name": "Blur",
|
||||
"tags": [],
|
||||
"tags": ["NFT"],
|
||||
"websiteLink": "https://blur.io/",
|
||||
"whitepaperLink": "https://docs.blur.foundation/",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/blur-token/"
|
||||
@ -91,6 +98,13 @@
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/bnb/"
|
||||
|
||||
},
|
||||
"CHZ-USD": {
|
||||
"name": "Chiliz",
|
||||
"tags": ["Layer 1"],
|
||||
"websiteLink": "https://www.chiliz.com/",
|
||||
"whitepaperLink": "https://www.chiliz.com/docs/litepaper-v1.1-20230703.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/chiliz/"
|
||||
},
|
||||
"CELO-USD": {
|
||||
"name": "Celo",
|
||||
"tags": [],
|
||||
@ -147,6 +161,13 @@
|
||||
"whitepaperLink": "https://cdn.enjin.io/downloads/whitepapers/enjin-coin/en.pdf/",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/enjin-coin/"
|
||||
},
|
||||
"ENS-USD": {
|
||||
"name": "Ethereum Name Service",
|
||||
"tags": [],
|
||||
"websiteLink": "https://coinmarketcap.com/currencies/ethereum-name-service/",
|
||||
"whitepaperLink": "https://docs.ens.domains/",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/ethereum-name-service/"
|
||||
},
|
||||
"EOS-USD": {
|
||||
"name": "EOS",
|
||||
"tags": ["Layer 1"],
|
||||
@ -170,6 +191,13 @@
|
||||
"displayStepSize": "0.001",
|
||||
"displayTickSize": "0.1"
|
||||
},
|
||||
"FET-USD": {
|
||||
"name": "Fetch.ai",
|
||||
"tags": ["AI"],
|
||||
"websiteLink": "https://fetch.ai/",
|
||||
"whitepaperLink": "https://www.dropbox.com/s/gxptsecwdl3jjtn/David%20Minarsch%20-%202021-04-26%2010.34.17%20-%20paper_21_finalversion.pdf?e=1&dl=0",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/fetch/"
|
||||
},
|
||||
"FIL-USD": {
|
||||
"name": "Filecoin",
|
||||
"tags": ["Layer 1"],
|
||||
@ -177,6 +205,34 @@
|
||||
"whitepaperLink": "https://filecoin.io/filecoin.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/filecoin/"
|
||||
},
|
||||
"FTM-USD": {
|
||||
"name": "Fantom",
|
||||
"tags": [],
|
||||
"websiteLink": "https://fantom.foundation/",
|
||||
"whitepaperLink": "https://fantom.foundation/_next/static/media/wp_fantom_v1.6.39329cdc5d0ee59684cbc6f228516383.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/fantom/"
|
||||
},
|
||||
"GALA-USD": {
|
||||
"name": "Gala",
|
||||
"tags": ["Gaming", "Layer 1"],
|
||||
"websiteLink": "https://gala.com/",
|
||||
"whitepaperLink": "https://galahackathon.com/v1.0.0/pdf/sdk-documentation.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/gala/"
|
||||
},
|
||||
"GMT-USD": {
|
||||
"name": "GMT",
|
||||
"tags": ["Gaming"],
|
||||
"websiteLink": "https://stepn.com/",
|
||||
"whitepaperLink/": "https://whitepaper.stepn.com/",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/green-metaverse-token/"
|
||||
},
|
||||
"GRT-USD": {
|
||||
"name": "The Graph",
|
||||
"tags": [],
|
||||
"websiteLink": "https://thegraph.com/",
|
||||
"whitepaperLink/": "https://github.com/graphprotocol/research/blob/master/papers/whitepaper/the-graph-whitepaper.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/the-graph/"
|
||||
},
|
||||
"HNT-USD": {
|
||||
"name": "Helium",
|
||||
"tags": ["Layer 1"],
|
||||
@ -184,6 +240,13 @@
|
||||
"whitepaperLink": "http://whitepaper.helium.com",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/helium/"
|
||||
},
|
||||
"HBAR-USD": {
|
||||
"name": "Hedera",
|
||||
"tags": [],
|
||||
"websiteLink": "https://hedera.com/",
|
||||
"whitepaperLink/": "https://files.hedera.com/hh_whitepaper_v2.2-20230918.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/hedera/"
|
||||
},
|
||||
"ICP-USD": {
|
||||
"name": "Internet Computer",
|
||||
"tags": ["Layer 1"],
|
||||
@ -191,6 +254,27 @@
|
||||
"whitepaperLink": "https://dfinity.org/whitepaper.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/internet-computer/"
|
||||
},
|
||||
"IMX-USD": {
|
||||
"name": "Immutable X",
|
||||
"tags": ["Gaming", "Layer 2", "NFT"],
|
||||
"websiteLink": "https://www.immutable.com/",
|
||||
"whitepaperLink": "https://assets.website-files.com/646557ee455c3e16e4a9bcb3/6499367de527dd82ab7475a3_Immutable%20Whitepaper%20Update%202023%20(3).pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/immutable-x/"
|
||||
},
|
||||
"INJ-USD": {
|
||||
"name": "Injective",
|
||||
"tags": ["Layer 1", "Defi"],
|
||||
"websiteLink": "https://injective.com/",
|
||||
"whitepaperLink": "https://docs.injective.network/intro/01_overview.html",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/injective/"
|
||||
},
|
||||
"JTO-USD": {
|
||||
"name": "Jito",
|
||||
"tags": ["Defi"],
|
||||
"websiteLink": "https://www.jito.network/",
|
||||
"whitepaperLink": "https://github.com/jito-foundation",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/jito/"
|
||||
},
|
||||
"JUP-USD": {
|
||||
"name": "Jupiter",
|
||||
"tags": ["Defi"],
|
||||
@ -198,6 +282,13 @@
|
||||
"whitepaperLink": "https://station.jup.ag/blog/green-paper",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/jupiter-ag/"
|
||||
},
|
||||
"KAVA-USD": {
|
||||
"name": "Kava",
|
||||
"tags": ["Layer 1"],
|
||||
"websiteLink": "https://www.kava.io/",
|
||||
"whitepaperLink": "https://docsend.com/view/gwbwpc3",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/kava/"
|
||||
},
|
||||
"LDO-USD": {
|
||||
"name": "Lido DAO",
|
||||
"tags": ["Defi"],
|
||||
@ -219,6 +310,20 @@
|
||||
"whitepaperLink": "https://litecoin.info/index.php/Main_Page",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/litecoin/"
|
||||
},
|
||||
"MANA-USD": {
|
||||
"name": "Decentraland",
|
||||
"tags": ["AR/VR"],
|
||||
"websiteLink": "https://decentraland.org/",
|
||||
"whitepaperLink": "https://decentraland.org/whitepaper.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/decentraland/"
|
||||
},
|
||||
"MASK-USD": {
|
||||
"name": "Mask Network",
|
||||
"tags": [],
|
||||
"websiteLink": "https://mask.io/",
|
||||
"whitepaperLink": "https://masknetwork.medium.com/introducing-mask-network-maskbook-the-future-of-the-internet-5a973d874edd",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/mask-network/"
|
||||
},
|
||||
"MATIC-USD": {
|
||||
"name": "Polygon",
|
||||
"tags": ["Layer 2"],
|
||||
@ -226,6 +331,13 @@
|
||||
"whitepaperLink": "https://polygon.technology/lightpaper-polygon.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/polygon/"
|
||||
},
|
||||
"MINA-USD": {
|
||||
"name": "Mina",
|
||||
"tags": ["Layer 1"],
|
||||
"websiteLink": "https://minaprotocol.com/",
|
||||
"whitepaperLink": "https://docs.minaprotocol.com/assets/economicsWhitepaper.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/mina/"
|
||||
},
|
||||
"MKR-USD": {
|
||||
"name": "Maker",
|
||||
"tags": ["Governance"],
|
||||
@ -240,6 +352,13 @@
|
||||
"whitepaperLink": "https://near.org/papers/the-official-near-white-paper/",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/near-protocol/"
|
||||
},
|
||||
"ORDI-USD": {
|
||||
"name": "Ordinals",
|
||||
"tags": ["NFT"],
|
||||
"websiteLink": "https://ordinals.com/",
|
||||
"whitepaperLink": "https://rodarmor.com/blog/",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/ordi/"
|
||||
},
|
||||
"OP-USD": {
|
||||
"name": "Optimism",
|
||||
"tags": [],
|
||||
@ -253,6 +372,20 @@
|
||||
"websiteLink": "https://www.pepe.vip/",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/pepe/"
|
||||
},
|
||||
"PYTH-USD": {
|
||||
"name": "Pyth Network",
|
||||
"tags": [],
|
||||
"websiteLink": "https://pyth.network/",
|
||||
"whitepaperLink": "https://pyth.network/whitepaper_v2.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/pyth-network/"
|
||||
},
|
||||
"RNDR-USD": {
|
||||
"name": "Render Token",
|
||||
"tags": ["AI"],
|
||||
"websiteLink": "https://rendernetwork.com/",
|
||||
"whitepaperLink": "https://renderfoundation.com/whitepaper",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/render/"
|
||||
},
|
||||
"RUNE-USD": {
|
||||
"name": "THORChain",
|
||||
"tags": ["Layer 1"],
|
||||
@ -260,6 +393,13 @@
|
||||
"whitepaperLink": "https://whitepaper.io/document/709/thorchain-whitepaper",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/thorchain/"
|
||||
},
|
||||
"SAND-USD": {
|
||||
"name": "The Sandbox",
|
||||
"tags": ["Gaming"],
|
||||
"websiteLink": "https://www.sandbox.game/en/",
|
||||
"whitepaperLink": "https://installers.sandbox.game/The_Sandbox_Whitepaper_2020.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/the-sandbox/"
|
||||
},
|
||||
"SEI-USD": {
|
||||
"name": "Sei",
|
||||
"tags": ["Layer 1", "Defi"],
|
||||
@ -288,6 +428,20 @@
|
||||
"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"],
|
||||
"websiteLink": "https://www.stacks.co/",
|
||||
"whitepaperLink": "https://gaia.blockstack.org/hub/1AxyPunHHAHiEffXWESKfbvmBpGQv138Fp/stacks.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/stacks/"
|
||||
},
|
||||
"SUI-USD": {
|
||||
"name": "Sui",
|
||||
"tags": ["Layer 1"],
|
||||
@ -337,6 +491,13 @@
|
||||
"whitepaperLink": "https://whitepaper.worldcoin.org/",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/worldcoin-org/"
|
||||
},
|
||||
"WOO-USD": {
|
||||
"name": "WOO Network",
|
||||
"tags": ["Defi"],
|
||||
"websiteLink": "https://woo.org/",
|
||||
"whitepaperLink": "https://woo.org/Litepaper.pdf",
|
||||
"coinMarketCapsLink": "https://coinmarketcap.com/currencies/wootrade/"
|
||||
},
|
||||
"XLM-USD": {
|
||||
"name": "Stellar",
|
||||
"tags": ["Layer 1"],
|
||||
|
||||
@ -513,6 +513,14 @@
|
||||
{ "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" },
|
||||
|
||||
@ -274,7 +274,7 @@
|
||||
{
|
||||
"baseAsset": "CRV",
|
||||
"referencePrice": 0.48346754113361784,
|
||||
"numOracles": 5,
|
||||
"numOracles": 6,
|
||||
"liquidityTier": 2,
|
||||
"assetName": "Curve DAO Token",
|
||||
"p": -1.0,
|
||||
@ -325,7 +325,7 @@
|
||||
{
|
||||
"baseAsset": "DYM",
|
||||
"referencePrice": 5.544949130771594,
|
||||
"numOracles": 5,
|
||||
"numOracles": 6,
|
||||
"liquidityTier": 2,
|
||||
"assetName": "Dymension",
|
||||
"p": 0.0,
|
||||
@ -412,7 +412,7 @@
|
||||
"referencePrice": 0.5560893147613742,
|
||||
"numOracles": 6,
|
||||
"liquidityTier": 2,
|
||||
"assetName": "Fetch AI",
|
||||
"assetName": "Fetch.ai",
|
||||
"p": -1.0,
|
||||
"atomicResolution": -5.0,
|
||||
"minExchanges": 3,
|
||||
@ -480,7 +480,7 @@
|
||||
"referencePrice": 0.20404992804891367,
|
||||
"numOracles": 6,
|
||||
"liquidityTier": 2,
|
||||
"assetName": "STEPN",
|
||||
"assetName": "GMT",
|
||||
"p": -1.0,
|
||||
"atomicResolution": -5.0,
|
||||
"minExchanges": 3,
|
||||
@ -514,7 +514,7 @@
|
||||
"referencePrice": 0.07686278766140874,
|
||||
"numOracles": 6,
|
||||
"liquidityTier": 2,
|
||||
"assetName": "Hedera Hashgraph",
|
||||
"assetName": "Hedera",
|
||||
"p": -2.0,
|
||||
"atomicResolution": -4.0,
|
||||
"minExchanges": 3,
|
||||
@ -565,7 +565,7 @@
|
||||
"referencePrice": 33.427379051845875,
|
||||
"numOracles": 7,
|
||||
"liquidityTier": 2,
|
||||
"assetName": "Injective Protocol",
|
||||
"assetName": "Injective",
|
||||
"p": 1.0,
|
||||
"atomicResolution": -7.0,
|
||||
"minExchanges": 3,
|
||||
@ -597,7 +597,7 @@
|
||||
{
|
||||
"baseAsset": "JUP",
|
||||
"referencePrice": 0.4339790660244292,
|
||||
"numOracles": 5,
|
||||
"numOracles": 6,
|
||||
"liquidityTier": 2,
|
||||
"assetName": "Jupiter",
|
||||
"p": -1.0,
|
||||
@ -750,7 +750,7 @@
|
||||
{
|
||||
"baseAsset": "MKR",
|
||||
"referencePrice": 1943.1865605831852,
|
||||
"numOracles": 5,
|
||||
"numOracles": 6,
|
||||
"liquidityTier": 2,
|
||||
"assetName": "Maker",
|
||||
"p": 3.0,
|
||||
@ -869,7 +869,7 @@
|
||||
{
|
||||
"baseAsset": "RUNE",
|
||||
"referencePrice": 3.7264809096406806,
|
||||
"numOracles": 5,
|
||||
"numOracles": 6,
|
||||
"liquidityTier": 2,
|
||||
"assetName": "Thorchain",
|
||||
"p": 0.0,
|
||||
@ -888,7 +888,7 @@
|
||||
"referencePrice": 0.37297439115035663,
|
||||
"numOracles": 6,
|
||||
"liquidityTier": 2,
|
||||
"assetName": "SAND",
|
||||
"assetName": "The Sandbox",
|
||||
"p": -1.0,
|
||||
"atomicResolution": -5.0,
|
||||
"minExchanges": 3,
|
||||
@ -968,6 +968,23 @@
|
||||
"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,
|
||||
@ -1073,9 +1090,9 @@
|
||||
{
|
||||
"baseAsset": "WOO",
|
||||
"referencePrice": 0.2944897275608197,
|
||||
"numOracles": 5,
|
||||
"numOracles": 6,
|
||||
"liquidityTier": 2,
|
||||
"assetName": "Wootrade Network",
|
||||
"assetName": "WOO Network",
|
||||
"p": -1.0,
|
||||
"atomicResolution": -5.0,
|
||||
"minExchanges": 3,
|
||||
|
||||
656
public/configs/v1/env.json
Normal file
@ -0,0 +1,656 @@
|
||||
{
|
||||
"apps": {
|
||||
"ios": {
|
||||
"scheme": "dydx-t-v4"
|
||||
}
|
||||
},
|
||||
"tokens": {
|
||||
"dydxprotocol-testnet": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"dydx-testnet-4": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"[mainnet chain id]": {
|
||||
"comment": "Change according to mainnet release",
|
||||
"chain": {
|
||||
"name": "TokenName",
|
||||
"denom": "tokenDenom",
|
||||
"decimals": 18,
|
||||
"image": "/currencies/dydx.png"
|
||||
},
|
||||
"usdc": {
|
||||
"name": "USDC",
|
||||
"denom": "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
|
||||
"gasDenom": "uusdc",
|
||||
"decimals": 6,
|
||||
"image": "/currencies/usdc.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"dydxprotocol-testnet": {
|
||||
"tos": "https://dydx.exchange/v4-terms",
|
||||
"privacy": "https://dydx.exchange/privacy",
|
||||
"statusPage": "https://status.v4testnet.dydx.exchange/",
|
||||
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
|
||||
"blogs": "https://www.dydx.foundation/blog",
|
||||
"foundation": "https://www.dydx.foundation",
|
||||
"help": "https://help.dydx.exchange/",
|
||||
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
|
||||
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
|
||||
"documentation": "https://docs.dydx.exchange/",
|
||||
"community": "https://discord.com/invite/dydx",
|
||||
"governanceLearnMore": "https://help.dydx.exchange",
|
||||
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
|
||||
"stakingLearnMore": "https://help.dydx.exchange",
|
||||
"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",
|
||||
"launchIncentive": "https://cloud.chaoslabs.co"
|
||||
},
|
||||
"dydx-testnet-4": {
|
||||
"tos": "https://dydx.exchange/v4-terms",
|
||||
"privacy": "https://dydx.exchange/privacy",
|
||||
"statusPage": "https://status.v4testnet.dydx.exchange/",
|
||||
"mintscan": "https://testnet.mintscan.io/dydx-testnet/txs/{tx_hash}",
|
||||
"documentation": "https://docs.dydx.exchange/",
|
||||
"community": "https://discord.com/invite/dydx",
|
||||
"feedback": "https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform",
|
||||
"blogs": "https://www.dydx.foundation/blog",
|
||||
"foundation": "https://www.dydx.foundation",
|
||||
"help": "https://help.dydx.exchange/",
|
||||
"reduceOnlyLearnMore": "https://help.dydx.exchange/articles/6345793-reduce-only-orders",
|
||||
"mintscanBase": "https://testnet.mintscan.io/dydx-testnet",
|
||||
"governanceLearnMore": "https://help.dydx.exchange",
|
||||
"newMarketProposalLearnMore": "https://dydx.exchange/blog/new-market-proposals",
|
||||
"stakingLearnMore": "https://help.dydx.exchange",
|
||||
"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",
|
||||
"launchIncentive": "https://cloud.chaoslabs.co"
|
||||
},
|
||||
"[mainnet chain id]": {
|
||||
"tos": "[HTTP link to TOS]",
|
||||
"privacy": "[HTTP link to Privacy Policy]",
|
||||
"statusPage": "[HTTP link to status page]",
|
||||
"mintscan": "[HTTP link to Mintscan, with {tx_hash} placeholder]",
|
||||
"mintscanBase": "[HTTP link to TOS mintscan base url]",
|
||||
"feedback": "[HTTP link to feedback form, can be null]",
|
||||
"blogs": "[HTTP link to blogs, can be null]",
|
||||
"foundation": "[HTTP link to foundation, can be null]",
|
||||
"reduceOnlyLearnMore": "[HTTP link to reduce-only learn more, can be null]",
|
||||
"documentation": "[HTTP link to documentation, can be null]",
|
||||
"community": "[HTTP link to community, can be null]",
|
||||
"help": "[HTTP link to help page, can be null]",
|
||||
"governanceLearnMore": "[HTTP link to governance learn more, can be null]",
|
||||
"newMarketProposalLearnMore": "[HTTP link to new market proposal learn more, can be null]",
|
||||
"stakingLearnMore": "[HTTP link to staking learn more, can be null]",
|
||||
"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]",
|
||||
"launchIncentive": "[HTTP link to launch incentive host, can be null]"
|
||||
}
|
||||
},
|
||||
"wallets": {
|
||||
"dydxprotocol-testnet": {
|
||||
"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"
|
||||
},
|
||||
"dydx-testnet-4": {
|
||||
"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"
|
||||
},
|
||||
"[mainnet chain id]": {
|
||||
"walletconnect": {
|
||||
"client": {
|
||||
"name": "[Name of the app]",
|
||||
"description": "[Description of the app]",
|
||||
"iconUrl": "[Relative URL of the icon URL]"
|
||||
},
|
||||
"v2": {
|
||||
"projectId": "[Project ID]"
|
||||
}
|
||||
},
|
||||
"walletSegue": {
|
||||
"callbackUrl": "[Relative callback URL for WalletSegue, should match apple-app-site-association]"
|
||||
},
|
||||
"images": "[Relative URL for wallet images]",
|
||||
"signTypedDataAction": "dYdX Chain Onboarding",
|
||||
"signTypedDataDomainName": "dYdX Chain"
|
||||
}
|
||||
},
|
||||
"governance": {
|
||||
"dydxprotocol-testnet": {
|
||||
"newMarketProposal": {
|
||||
"initialDepositAmount": 10000000,
|
||||
"delayBlocks": 900,
|
||||
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
|
||||
}
|
||||
},
|
||||
"dydx-testnet-4": {
|
||||
"newMarketProposal": {
|
||||
"initialDepositAmount": 10000000,
|
||||
"delayBlocks": 900,
|
||||
"newMarketsMethodology": "https://docs.google.com/spreadsheets/d/1zjkV9R7R_7KMItuzqzvKGwefSBRfE-ZNAx1LH55OcqY/edit?usp=sharing"
|
||||
}
|
||||
},
|
||||
"[mainnet chain id]": {
|
||||
"newMarketProposal": {
|
||||
"initialDepositAmount": 0,
|
||||
"delayBlocks": 0,
|
||||
"newMarketsMethodology": "[URL to spreadsheet or document that explains methodology]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deployments": {
|
||||
"MAINNET": {
|
||||
"environments": [
|
||||
"dydxprotocol-mainnet"
|
||||
],
|
||||
"default": "dydxprotocol-mainnet"
|
||||
},
|
||||
"TESTFLIGHT": {
|
||||
"environments": [
|
||||
"dydxprotocol-mainnet",
|
||||
"dydxprotocol-testnet"
|
||||
],
|
||||
"default": "dydxprotocol-mainnet"
|
||||
},
|
||||
"TESTNET": {
|
||||
"environments": [
|
||||
"dydxprotocol-testnet"
|
||||
],
|
||||
"default": "dydxprotocol-testnet"
|
||||
},
|
||||
"DEV": {
|
||||
"environments": [
|
||||
"dydxprotocol-dev",
|
||||
"dydxprotocol-dev-2",
|
||||
"dydxprotocol-dev-4",
|
||||
"dydxprotocol-dev-5",
|
||||
"dydxprotocol-staging",
|
||||
"dydxprotocol-staging-west",
|
||||
"dydxprotocol-testnet",
|
||||
"dydxprotocol-testnet-dydx",
|
||||
"dydxprotocol-testnet-nodefleet",
|
||||
"dydxprotocol-testnet-kingnodes",
|
||||
"dydxprotocol-testnet-liquify",
|
||||
"dydxprotocol-testnet-polkachu",
|
||||
"dydxprotocol-testnet-bware"
|
||||
],
|
||||
"default": "dydxprotocol-testnet"
|
||||
}
|
||||
},
|
||||
"environments": {
|
||||
"dydxprotocol-dev": {
|
||||
"name": "v4 Dev",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4dev.dydx.exchange",
|
||||
"socket": "wss://indexer.v4dev.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://validator.v4dev.dydx.exchange"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.0xsquid.com",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||
"faucet": "https://faucet.v4dev.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-dev-2": {
|
||||
"name": "v4 Dev 2",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "http://dev2-indexer-apne1-lb-public-2076363889.ap-northeast-1.elb.amazonaws.com",
|
||||
"socket": "ws://dev2-indexer-apne1-lb-public-2076363889.ap-northeast-1.elb.amazonaws.com"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"http://54.92.118.111"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.0xsquid.com",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-dev-4": {
|
||||
"name": "v4 Dev 4",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4dev4.dydx.exchange",
|
||||
"socket": "wss://indexer.v4dev4.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://validator.v4dev4.dydx.exchange"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.0xsquid.com",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||
"faucet": "https://faucet.v4dev4.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-dev-5": {
|
||||
"name": "v4 Dev 5",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "http://dev5-indexer-apne1-lb-public-1721328151.ap-northeast-1.elb.amazonaws.com",
|
||||
"socket": "ws://dev5-indexer-apne1-lb-public-1721328151.ap-northeast-1.elb.amazonaws.com"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"http://18.223.78.50"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.0xsquid.com",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-staging": {
|
||||
"name": "v4 Staging",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"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",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-staging-forced-update": {
|
||||
"name": "v4 Staging Forced Update",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"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",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||
},
|
||||
"apps": {
|
||||
"ios": {
|
||||
"minimalVersion": "1.0",
|
||||
"build": 40000,
|
||||
"url": "https://apps.apple.com/app/dydx/id1564787350"
|
||||
}
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-staging-west": {
|
||||
"name": "v4 Staging West",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydxprotocol-testnet",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4staging.dydx.exchange",
|
||||
"socket": "wss://indexer.v4staging.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"faucet": "https://faucet.v4staging.dydx.exchange",
|
||||
"validators": [
|
||||
"https://validator-uswest1.v4staging.dydx.exchange"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": false
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet": {
|
||||
"name": "v4 Public Testnet",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://dydx-testnet-full-rpc.public.blastapi.io/",
|
||||
"https://dydx-testnet-rpc.polkachu.com/",
|
||||
"https://dydx-testnet.nodefleet.org",
|
||||
"https://test-dydx.kingnodes.com",
|
||||
"https://dydx-rpc.liquify.com/api=8878132/dydx"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-dydx": {
|
||||
"name": "v4 Public Testnet/dYdX",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://validator.v4testnet.dydx.exchange"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-nodefleet": {
|
||||
"name": "v4 Public Testnet/nodefleet",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://dydx-testnet.nodefleet.org"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-kingnodes": {
|
||||
"name": "v4 Public Testnet/KingNodes",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://test-dydx.kingnodes.com"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-liquify": {
|
||||
"name": "v4 Public Testnet/Liquify",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://dydx-rpc.liquify.com/api=8878132/dydx"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-polkachu": {
|
||||
"name": "v4 Public Testnet/Polkahcu",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://dydx-testnet-rpc.polkachu.com/"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-testnet-bware": {
|
||||
"name": "v4 Public Testnet/BWare",
|
||||
"ethereumChainId": "11155111",
|
||||
"dydxChainId": "dydx-testnet-4",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "dYdX-api",
|
||||
"isMainNet": false,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "https://indexer.v4testnet.dydx.exchange",
|
||||
"socket": "wss://indexer.v4testnet.dydx.exchange"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"https://dydx-testnet-full-rpc.public.blastapi.io/"
|
||||
],
|
||||
"0xsquid": "https://testnet.api.squidrouter.com",
|
||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||
"faucet": "https://faucet.v4testnet.dydx.exchange"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": true,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
},
|
||||
"dydxprotocol-mainnet": {
|
||||
"name": "v4",
|
||||
"ethereumChainId": "1",
|
||||
"dydxChainId": "[mainnet chain id]",
|
||||
"chainName": "dYdX Chain",
|
||||
"chainLogo": "/dydx-chain.png",
|
||||
"squidIntegratorId": "[mainnet squid integrator id]",
|
||||
"isMainNet": true,
|
||||
"endpoints": {
|
||||
"indexers": [
|
||||
{
|
||||
"api": "[REST endpoint]",
|
||||
"socket": "[Websocket endpoint]"
|
||||
}
|
||||
],
|
||||
"validators": [
|
||||
"[Validator endpoint 1",
|
||||
"[Validator endpoint n]"
|
||||
],
|
||||
"0xsquid": "[0xSquid endpoint for mainnet]",
|
||||
"nobleValidator": "[noble validator endpoint for mainnet]"
|
||||
},
|
||||
"featureFlags": {
|
||||
"reduceOnlySupported": false,
|
||||
"usePessimisticCollateralCheck": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
public/currencies/agix.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/currencies/chz.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/currencies/ens.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
public/currencies/fet.png
Normal file
|
After Width: | Height: | Size: 348 KiB |
BIN
public/currencies/ftm.png
Normal file
|
After Width: | Height: | Size: 110 KiB |
BIN
public/currencies/gala.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
public/currencies/gmt.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
public/currencies/grt.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
public/currencies/hbar.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
public/currencies/imx.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
public/currencies/inj.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
public/currencies/jto.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
public/currencies/kava.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
public/currencies/mana.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
public/currencies/mask.png
Normal file
|
After Width: | Height: | Size: 9.8 KiB |
BIN
public/currencies/mina.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
public/currencies/ordi.png
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
public/currencies/pyth.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
public/currencies/rndr.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
public/currencies/sand.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
public/currencies/strk.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/currencies/stx.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
public/currencies/woo.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
1
public/libs/amplitude-analytics-browser-2.0.0-min.js
vendored
Normal file
BIN
public/wallets/okx-wallet.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
@ -14,7 +14,7 @@ if (AMPLITUDE_API_KEY) {
|
||||
const html = await fs.readFile(htmlFilePath, 'utf-8');
|
||||
|
||||
const amplitudeCdnScript = `<script type="text/javascript">
|
||||
!function(){"use strict";!function(e,t){var n=e.amplitude||{_q:[],_iq:{}};if(n.invoked)e.console&&console.error&&console.error("Amplitude snippet has been loaded.");else{var r=function(e,t){e.prototype[t]=function(){return this._q.push({name:t,args:Array.prototype.slice.call(arguments,0)}),this}},s=function(e,t,n){return function(r){e._q.push({name:t,args:Array.prototype.slice.call(n,0),resolve:r})}},o=function(e,t,n){e[t]=function(){if(n)return{promise:new Promise(s(e,t,Array.prototype.slice.call(arguments)))}}},i=function(e){for(var t=0;t<m.length;t++)o(e,m[t],!1);for(var n=0;n<g.length;n++)o(e,g[n],!0)};n.invoked=!0;var u=t.createElement("script");u.type="text/javascript",u.integrity="sha384-x0ik2D45ZDEEEpYpEuDpmj05fY91P7EOZkgdKmq4dKL/ZAVcufJ+nULFtGn0HIZE",u.crossOrigin="anonymous",u.async=!0,u.src="https://cdn.amplitude.com/libs/analytics-browser-2.0.0-min.js.gz",u.onload=function(){e.amplitude.runQueuedFunctions||console.log("[Amplitude] Error: could not load SDK")};var a=t.getElementsByTagName("script")[0];a.parentNode.insertBefore(u,a);for(var c=function(){return this._q=[],this},p=["add","append","clearAll","prepend","set","setOnce","unset","preInsert","postInsert","remove","getUserProperties"],l=0;l<p.length;l++)r(c,p[l]);n.Identify=c;for(var d=function(){return this._q=[],this},f=["getEventProperties","setProductId","setQuantity","setPrice","setRevenue","setRevenueType","setEventProperties"],v=0;v<f.length;v++)r(d,f[v]);n.Revenue=d;var m=["getDeviceId","setDeviceId","getSessionId","setSessionId","getUserId","setUserId","setOptOut","setTransport","reset","extendSession"],g=["init","add","remove","track","logEvent","identify","groupIdentify","setGroup","revenue","flush"];i(n),n.createInstance=function(e){return n._iq[e]={_q:[]},i(n._iq[e]),n._iq[e]},e.amplitude=n}}(window,document)}();
|
||||
!function(){"use strict";!function(e,t){var n=e.amplitude||{_q:[],_iq:{}};if(n.invoked)e.console&&console.error&&console.error("Amplitude snippet has been loaded.");else{var r=function(e,t){e.prototype[t]=function(){return this._q.push({name:t,args:Array.prototype.slice.call(arguments,0)}),this}},s=function(e,t,n){return function(r){e._q.push({name:t,args:Array.prototype.slice.call(n,0),resolve:r})}},o=function(e,t,n){e[t]=function(){if(n)return{promise:new Promise(s(e,t,Array.prototype.slice.call(arguments)))}}},i=function(e){for(var t=0;t<m.length;t++)o(e,m[t],!1);for(var n=0;n<g.length;n++)o(e,g[n],!0)};n.invoked=!0;var u=t.createElement("script");u.type="text/javascript",u.integrity="sha384-BVo5ZjsjH373rWbcjz9Qjb2L6BgLwLADcZtZZPu3nMl8+7LPDhi1NcUEf0Ate41Y",u.crossOrigin="anonymous",u.async=!0,u.src="/libs/amplitude-analytics-browser-2.0.0-min.js",u.onload=function(){e.amplitude.runQueuedFunctions||console.log("[Amplitude] Error: could not load SDK")};var a=t.getElementsByTagName("script")[0];a.parentNode.insertBefore(u,a);for(var c=function(){return this._q=[],this},p=["add","append","clearAll","prepend","set","setOnce","unset","preInsert","postInsert","remove","getUserProperties"],l=0;l<p.length;l++)r(c,p[l]);n.Identify=c;for(var d=function(){return this._q=[],this},f=["getEventProperties","setProductId","setQuantity","setPrice","setRevenue","setRevenueType","setEventProperties"],v=0;v<f.length;v++)r(d,f[v]);n.Revenue=d;var m=["getDeviceId","setDeviceId","getSessionId","setSessionId","getUserId","setUserId","setOptOut","setTransport","reset","extendSession"],g=["init","add","remove","track","logEvent","identify","groupIdentify","setGroup","revenue","flush"];i(n),n.createInstance=function(e){return n._iq[e]={_q:[]},i(n._iq[e]),n._iq[e]},e.amplitude=n}}(window,document)}();
|
||||
</script>
|
||||
`;
|
||||
|
||||
|
||||
21
src/App.tsx
@ -1,5 +1,5 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||
import { lazy, Suspense, useMemo } from 'react';
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||
import { WagmiConfig } from 'wagmi';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
@ -26,6 +26,7 @@ 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';
|
||||
@ -34,12 +35,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';
|
||||
@ -68,6 +69,14 @@ 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 (
|
||||
<>
|
||||
@ -102,8 +111,10 @@ const Content = () => {
|
||||
|
||||
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
|
||||
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
|
||||
|
||||
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={<Navigate to={pathFromHash || DEFAULT_TRADE_ROUTE} replace />}
|
||||
/>
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</Styled.Main>
|
||||
|
||||
@ -8,6 +8,7 @@ const assetIcons = {
|
||||
'1INCH': '/currencies/1inch.png',
|
||||
AAVE: '/currencies/aave.png',
|
||||
ADA: '/currencies/ada.png',
|
||||
AGIX: '/currencies/agix.png',
|
||||
ALGO: '/currencies/algo.png',
|
||||
APE: '/currencies/ape.png',
|
||||
APT: '/currencies/apt.png',
|
||||
@ -20,6 +21,7 @@ const assetIcons = {
|
||||
BONK: '/currencies/bonk.png',
|
||||
BTC: '/currencies/btc.png',
|
||||
CELO: '/currencies/celo.png',
|
||||
CHZ: '/currencies/chz.png',
|
||||
COMP: '/currencies/comp.png',
|
||||
CRV: '/currencies/crv.png',
|
||||
DAI: '/currencies/dai.png',
|
||||
@ -28,25 +30,44 @@ const assetIcons = {
|
||||
DYDX: '/currencies/dydx.png',
|
||||
DYM: '/currencies/dym.png',
|
||||
ENJ: '/currencies/enj.png',
|
||||
ENS: '/currencies/ens.png',
|
||||
EOS: '/currencies/eos.png',
|
||||
ETC: '/currencies/etc.png',
|
||||
ETH: '/currencies/eth.png',
|
||||
FET: '/currencies/fet.png',
|
||||
FIL: '/currencies/fil.png',
|
||||
FTM: '/currencies/ftm.png',
|
||||
GALA: "/currencies/gala.png",
|
||||
GMT: "/currencies/gmt.png",
|
||||
GRT: "/currencies/grt.png",
|
||||
HBAR: "/currencies/hbar.png",
|
||||
ICP: '/currencies/icp.png',
|
||||
IMX: '/currencies/imx.png',
|
||||
INJ: '/currencies/inj.png',
|
||||
JTO: '/currencies/jto.png',
|
||||
JUP: '/currencies/jup.png',
|
||||
KAVA: '/currencies/kava.png',
|
||||
LDO: '/currencies/ldo.png',
|
||||
LINK: '/currencies/link.png',
|
||||
LTC: '/currencies/ltc.png',
|
||||
MANA: '/currencies/mana.png',
|
||||
MATIC: '/currencies/matic.png',
|
||||
MASK: '/currencies/mask.png',
|
||||
MINA: '/currencies/mina.png',
|
||||
MKR: '/currencies/mkr.png',
|
||||
NEAR: '/currencies/near.png',
|
||||
ORDI: "/currencies/ordi.png",
|
||||
OP: '/currencies/op.png',
|
||||
PEPE: '/currencies/pepe.png',
|
||||
PYTH: '/currencies/pyth.png',
|
||||
RNDR: '/currencies/rndr.png',
|
||||
RUNE: '/currencies/rune.png',
|
||||
SAND: '/currencies/sand.png',
|
||||
SEI: '/currencies/sei.png',
|
||||
SHIB: '/currencies/shib.png',
|
||||
SNX: '/currencies/snx.png',
|
||||
SOL: '/currencies/sol.png',
|
||||
STX: '/currencies/stx.png',
|
||||
SUI: '/currencies/sui.png',
|
||||
SUSHI: '/currencies/sushi.png',
|
||||
TIA: '/currencies/tia.png',
|
||||
@ -57,6 +78,7 @@ const assetIcons = {
|
||||
USDT: '/currencies/usdt.png',
|
||||
WBTC: '/currencies/wbtc.png',
|
||||
WETH: '/currencies/weth.png',
|
||||
WOO: '/currencies/woo.png',
|
||||
WLD: '/currencies/wld.png',
|
||||
XLM: '/currencies/xlm.png',
|
||||
XMR: '/currencies/xmr.png',
|
||||
|
||||
@ -39,7 +39,7 @@ export const SearchSelectMenu = ({
|
||||
disabled,
|
||||
label,
|
||||
items,
|
||||
withSearch,
|
||||
withSearch = true,
|
||||
withReceiptItems,
|
||||
}: SearchSelectMenuProps) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
@ -77,6 +77,7 @@ export const SearchSelectMenu = ({
|
||||
withSearch={withSearch}
|
||||
onItemSelected={() => setOpen(false)}
|
||||
withStickyLayout
|
||||
$withSearch={withSearch}
|
||||
/>
|
||||
</Styled.Popover>
|
||||
</Styled.WithDetailsReceipt>
|
||||
@ -127,7 +128,7 @@ Styled.Popover = styled(Popover)`
|
||||
box-shadow: none;
|
||||
`;
|
||||
|
||||
Styled.ComboboxMenu = styled(ComboboxMenu)`
|
||||
Styled.ComboboxMenu = styled(ComboboxMenu)<{ $withSearch?: boolean }>`
|
||||
${layoutMixins.withInnerHorizontalBorders}
|
||||
|
||||
--comboboxMenu-backgroundColor: var(--color-layer-4);
|
||||
@ -140,7 +141,8 @@ Styled.ComboboxMenu = styled(ComboboxMenu)`
|
||||
--comboboxMenu-item-checked-textColor: var(--color-text-2);
|
||||
--comboboxMenu-item-highlighted-textColor: var(--color-text-2);
|
||||
|
||||
--stickyArea1-topHeight: var(--form-input-height);
|
||||
--stickyArea1-topHeight: ${({ $withSearch }) =>
|
||||
!$withSearch ? '0' : 'var(--form-input-height)'};
|
||||
|
||||
input:focus-visible {
|
||||
outline: none;
|
||||
|
||||
@ -33,4 +33,9 @@ TagStory.argTypes = {
|
||||
control: { type: 'select' },
|
||||
defaultValue: undefined,
|
||||
},
|
||||
isHighlighted: {
|
||||
options: [true, false],
|
||||
control: { type: 'select' },
|
||||
defaultValue: false,
|
||||
},
|
||||
};
|
||||
|
||||
@ -76,5 +76,6 @@ export const Tag = styled.span<StyleProps>`
|
||||
isHighlighted &&
|
||||
css`
|
||||
background-color: var(--color-accent);
|
||||
color: var(--color-text-button);
|
||||
`}
|
||||
`;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { type ReactNode, useState } from 'react';
|
||||
import { type ReactNode, useState, useEffect } from 'react';
|
||||
|
||||
import { ButtonAction, ButtonState } from '@/constants/buttons';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
@ -8,6 +8,7 @@ import { Button, type ButtonStateConfig, type ButtonProps } from '@/components/B
|
||||
|
||||
type ElementProps = {
|
||||
timeoutInSeconds: number;
|
||||
onTimeOut?: () => void;
|
||||
slotFinal?: ReactNode;
|
||||
} & ButtonProps;
|
||||
|
||||
@ -16,6 +17,7 @@ export type TimeoutButtonProps = ElementProps;
|
||||
export const TimeoutButton = ({
|
||||
children,
|
||||
timeoutInSeconds,
|
||||
onTimeOut,
|
||||
slotFinal,
|
||||
...otherProps
|
||||
}: TimeoutButtonProps) => {
|
||||
@ -25,6 +27,11 @@ export const TimeoutButton = ({
|
||||
|
||||
const secondsLeft = Math.max(0, (timeoutDeadline - now) / 1000);
|
||||
|
||||
useEffect(() => {
|
||||
if (secondsLeft > 0) return;
|
||||
onTimeOut?.();
|
||||
}, [secondsLeft]);
|
||||
|
||||
if (slotFinal && secondsLeft <= 0) return slotFinal;
|
||||
|
||||
return (
|
||||
|
||||
@ -81,6 +81,9 @@ export enum AnalyticsEvent {
|
||||
TradePlaceOrderConfirmed = 'TradePlaceOrderConfirmed',
|
||||
TradeCancelOrder = 'TradeCancelOrder',
|
||||
TradeCancelOrderConfirmed = 'TradeCancelOrderConfirmed',
|
||||
|
||||
// Notification
|
||||
NotificationAction = 'NotificationAction',
|
||||
}
|
||||
|
||||
export type AnalyticsEventData<T extends AnalyticsEvent> =
|
||||
@ -144,9 +147,17 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
|
||||
validatorUrl: string;
|
||||
}
|
||||
: T extends AnalyticsEvent.TransferDeposit
|
||||
? {}
|
||||
? {
|
||||
chainId?: string;
|
||||
tokenAddress?: string;
|
||||
tokenSymbol?: string;
|
||||
}
|
||||
: T extends AnalyticsEvent.TransferWithdraw
|
||||
? {}
|
||||
? {
|
||||
chainId?: string;
|
||||
tokenAddress?: string;
|
||||
tokenSymbol?: string;
|
||||
}
|
||||
: // Trading
|
||||
T extends AnalyticsEvent.TradeOrderTypeSelected
|
||||
? {
|
||||
@ -172,6 +183,12 @@ 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)';
|
||||
|
||||
@ -10,8 +10,8 @@ export enum DialogTypes {
|
||||
Help = 'Help',
|
||||
ExternalNavKeplr = 'ExternalNavKeplr',
|
||||
MnemonicExport = 'MnemonicExport',
|
||||
MobileDownload = 'MobileDownload',
|
||||
MobileSignIn = 'MobileSignIn',
|
||||
MobileDownload = 'MobileDownload',
|
||||
Onboarding = 'Onboarding',
|
||||
OrderDetails = 'OrderDetails',
|
||||
Preferences = 'Preferences',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import environments from '../../public/configs/env.json';
|
||||
import environments from '../../public/configs/v1/env.json';
|
||||
|
||||
const CURRENT_MODE = ({
|
||||
production: 'MAINNET',
|
||||
@ -14,5 +14,10 @@ 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 TOKEN_CONFIG_MAP = environments.tokens;
|
||||
export const LINKS_CONFIG_MAP = environments.links;
|
||||
export const WALLETS_CONFIG_MAP = environments.wallets;
|
||||
export const GOVERNANCE_CONFIG_MAP = environments.governance;
|
||||
export type DydxNetwork = keyof typeof ENVIRONMENT_CONFIG_MAP;
|
||||
export type DydxChainId = keyof typeof TOKEN_CONFIG_MAP;
|
||||
export const DEFAULT_APP_ENVIRONMENT = AVAILABLE_ENVIRONMENTS.default as DydxNetwork;
|
||||
|
||||
@ -140,8 +140,14 @@ export type TransferNotifcation = {
|
||||
isCctp?: boolean;
|
||||
errorCount?: number;
|
||||
status?: StatusResponse;
|
||||
isExchange?: boolean;
|
||||
};
|
||||
|
||||
export enum ReleaseUpdateNotificationIds {
|
||||
RewardsAndFullTradingLive = 'rewards-and-full-trading-live',
|
||||
IncentivesS3 = 'incentives-s3',
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Struct to store whether a NotificationType should be triggered
|
||||
*/
|
||||
|
||||
@ -22,4 +22,5 @@ export enum NumberSign {
|
||||
|
||||
// Deposit/Withdraw
|
||||
export const MAX_CCTP_TRANSFER_AMOUNT = 1_000_000;
|
||||
export const MIN_CCTP_TRANSFER_AMOUNT = 10;
|
||||
export const MAX_PRICE_IMPACT = 0.02; // 2%
|
||||
|
||||
@ -41,6 +41,7 @@ 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`;
|
||||
|
||||
@ -1,4 +1,12 @@
|
||||
import { AppColorMode } from '@/state/configs';
|
||||
import type { ThemeName } from 'public/tradingview/charting_library';
|
||||
|
||||
import { AppColorMode, AppTheme } from '@/state/configs';
|
||||
|
||||
export const THEME_NAMES: Record<AppTheme, ThemeName> = {
|
||||
[AppTheme.Classic]: 'Classic',
|
||||
[AppTheme.Dark]: 'Dark',
|
||||
[AppTheme.Light]: 'Light',
|
||||
};
|
||||
|
||||
export type Theme = {
|
||||
[AppColorMode.GreenUp]: ThemeColorBase;
|
||||
|
||||
16
src/constants/tvchart.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
import type {
|
||||
IChartingLibraryWidget,
|
||||
IOrderLineAdapter,
|
||||
IPositionLineAdapter,
|
||||
} from 'public/tradingview/charting_library';
|
||||
|
||||
export type TvWidget = IChartingLibraryWidget & { _id?: string; _ready?: boolean };
|
||||
|
||||
export type ChartLineType = OrderSide | 'position';
|
||||
|
||||
export type ChartLine = {
|
||||
line: IOrderLineAdapter | IPositionLineAdapter;
|
||||
chartLineType: ChartLineType;
|
||||
};
|
||||
@ -16,6 +16,7 @@ import {
|
||||
KeplrIcon,
|
||||
MathWalletIcon,
|
||||
MetaMaskIcon,
|
||||
OkxWalletIcon,
|
||||
RainbowIcon,
|
||||
TokenPocketIcon,
|
||||
TrustWalletIcon,
|
||||
@ -24,7 +25,7 @@ import {
|
||||
|
||||
import { isMetaMask } from '@/lib/wallet/providers';
|
||||
|
||||
import { DydxNetwork, ENVIRONMENT_CONFIG_MAP } from './networks';
|
||||
import { DydxChainId, WALLETS_CONFIG_MAP } from './networks';
|
||||
|
||||
// Wallet connection types
|
||||
|
||||
@ -40,6 +41,7 @@ export enum WalletErrorType {
|
||||
// General
|
||||
ChainMismatch,
|
||||
UserCanceled,
|
||||
SwitchChainMethodMissing,
|
||||
|
||||
// Non-Deterministic
|
||||
NonDeterministicWallet,
|
||||
@ -88,6 +90,7 @@ export enum WalletType {
|
||||
// Ledger = 'LEDGER',
|
||||
MathWallet = 'MATH_WALLET',
|
||||
MetaMask = 'METAMASK',
|
||||
OkxWallet = 'OKX_WALLET',
|
||||
Rainbow = 'RAINBOW_WALLET',
|
||||
TokenPocket = 'TOKEN_POCKET',
|
||||
TrustWallet = 'TRUST_WALLET',
|
||||
@ -101,6 +104,7 @@ const WALLET_CONNECT_EXPLORER_RECOMMENDED_WALLETS = {
|
||||
imToken: 'ef333840daf915aafdc4a004525502d6d49d77bd9c65e0642dbaefb3c2893bef',
|
||||
TokenPocket: '20459438007b75f4f4acb98bf29aa3b800550309646d375da5fd4aac6c2a2c66',
|
||||
Trust: '4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0',
|
||||
OkxWallet: '971e689d0a5be527bac79629b4ee9b925e82208e5168b733496a09c0faed0709',
|
||||
Rainbow: '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369',
|
||||
Zerion: 'ecc4036f814562b41a5268adc86270fba1365471402006302e70169465b7ac18',
|
||||
Ledger: '19177a98252e07ddfc9af2083ba8e07ef627cb6103467ffebb3f8f4205fd7927',
|
||||
@ -211,6 +215,14 @@ 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,
|
||||
@ -277,6 +289,10 @@ export type WithInjectedWeb3Provider = {
|
||||
};
|
||||
};
|
||||
|
||||
export type WithInjectedOkxWalletProvider = {
|
||||
okxwallet: InjectedWeb3Provider;
|
||||
};
|
||||
|
||||
// Wallet connections
|
||||
|
||||
export type WalletConnection = {
|
||||
@ -290,17 +306,17 @@ export const COSMOS_DERIVATION_PATH = "m/44'/118'/0'/0/0";
|
||||
/**
|
||||
* @description typed data to sign for dYdX Chain onboarding
|
||||
*/
|
||||
export const getSignTypedData = (selectedNetwork: DydxNetwork) =>
|
||||
export const getSignTypedData = (selectedDydxChainId: DydxChainId) =>
|
||||
({
|
||||
primaryType: 'dYdX',
|
||||
domain: {
|
||||
name: ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.signTypedDataDomainName,
|
||||
name: WALLETS_CONFIG_MAP[selectedDydxChainId].signTypedDataDomainName,
|
||||
},
|
||||
types: {
|
||||
dYdX: [{ name: 'action', type: 'string' }],
|
||||
},
|
||||
message: {
|
||||
action: ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.signTypedDataAction,
|
||||
action: WALLETS_CONFIG_MAP[selectedDydxChainId].signTypedDataAction,
|
||||
},
|
||||
} as const);
|
||||
|
||||
|
||||
@ -1,2 +1,4 @@
|
||||
export { useChartLines } from './useChartLines';
|
||||
export { useChartMarketAndResolution } from './useChartMarketAndResolution';
|
||||
export { useTradingView } from './useTradingView';
|
||||
export { useTradingViewTheme } from './useTradingViewTheme';
|
||||
|
||||
211
src/hooks/tradingView/useChartLines.ts
Normal file
@ -0,0 +1,211 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
|
||||
import { AbacusOrderStatus, ORDER_SIDES, SubaccountOrder } from '@/constants/abacus';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { type OrderType, ORDER_TYPE_STRINGS } from '@/constants/trade';
|
||||
import type { ChartLine, TvWidget } from '@/constants/tvchart';
|
||||
|
||||
import { useStringGetter } from '@/hooks';
|
||||
|
||||
import { getCurrentMarketOrders, getCurrentMarketPositionData } from '@/state/accountSelectors';
|
||||
import { getAppTheme, getAppColorMode } from '@/state/configsSelectors';
|
||||
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
import { getChartLineColors } from '@/lib/tradingView/utils';
|
||||
|
||||
let chartLines: Record<string, ChartLine> = {};
|
||||
|
||||
/**
|
||||
* @description Hook to handle drawing chart lines
|
||||
*/
|
||||
|
||||
export const useChartLines = ({
|
||||
tvWidget,
|
||||
displayButton,
|
||||
isChartReady,
|
||||
}: {
|
||||
tvWidget: TvWidget | null;
|
||||
displayButton: HTMLElement | null;
|
||||
isChartReady?: boolean;
|
||||
}) => {
|
||||
const [showOrderLines, setShowOrderLines] = useState(false);
|
||||
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const appTheme = useSelector(getAppTheme);
|
||||
const appColorMode = useSelector(getAppColorMode);
|
||||
|
||||
const currentMarketPositionData = useSelector(getCurrentMarketPositionData, shallowEqual);
|
||||
const currentMarketOrders: SubaccountOrder[] = useSelector(getCurrentMarketOrders, shallowEqual);
|
||||
|
||||
useEffect(() => {
|
||||
if (isChartReady && displayButton) {
|
||||
displayButton.onclick = () => {
|
||||
const newShowOrderLinesState = !showOrderLines;
|
||||
if (newShowOrderLinesState) {
|
||||
displayButton?.classList?.add('order-lines-active');
|
||||
} else {
|
||||
displayButton?.classList?.remove('order-lines-active');
|
||||
}
|
||||
setShowOrderLines(newShowOrderLinesState);
|
||||
};
|
||||
}
|
||||
}, [isChartReady, showOrderLines]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tvWidget && isChartReady) {
|
||||
tvWidget.onChartReady(() => {
|
||||
tvWidget.headerReady().then(() => {
|
||||
tvWidget.chart().dataReady(() => {
|
||||
if (showOrderLines) {
|
||||
drawOrderLines();
|
||||
drawPositionLine();
|
||||
} else {
|
||||
deleteChartLines();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [isChartReady, showOrderLines, currentMarketPositionData, currentMarketOrders]);
|
||||
|
||||
const drawPositionLine = () => {
|
||||
if (!currentMarketPositionData) return;
|
||||
|
||||
const entryPrice = currentMarketPositionData.entryPrice?.current;
|
||||
const size = currentMarketPositionData.size?.current;
|
||||
|
||||
const key = currentMarketPositionData.id;
|
||||
const price = MustBigNumber(entryPrice).toNumber();
|
||||
|
||||
const maybePositionLine = chartLines[key]?.line;
|
||||
const shouldShow = size && size !== 0;
|
||||
|
||||
if (!shouldShow) {
|
||||
if (maybePositionLine) {
|
||||
maybePositionLine.remove();
|
||||
delete chartLines[key];
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const quantity = size.toString();
|
||||
|
||||
if (maybePositionLine) {
|
||||
if (maybePositionLine.getQuantity() !== quantity) {
|
||||
maybePositionLine.setQuantity(quantity);
|
||||
}
|
||||
if (maybePositionLine.getPrice() !== price) {
|
||||
maybePositionLine.setPrice(price);
|
||||
}
|
||||
} else {
|
||||
const positionLine = tvWidget
|
||||
?.chart()
|
||||
.createPositionLine({ disableUndo: false })
|
||||
.setText(stringGetter({ key: STRING_KEYS.ENTRY_PRICE_SHORT }))
|
||||
.setPrice(price)
|
||||
.setQuantity(quantity);
|
||||
|
||||
if (positionLine) {
|
||||
const chartLine = { line: positionLine, chartLineType: 'position' };
|
||||
setLineColors({ chartLine: chartLine });
|
||||
chartLines[key] = chartLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const drawOrderLines = () => {
|
||||
if (!currentMarketOrders) return;
|
||||
|
||||
currentMarketOrders.forEach(
|
||||
({
|
||||
id,
|
||||
type,
|
||||
status,
|
||||
side,
|
||||
cancelReason,
|
||||
remainingSize,
|
||||
size,
|
||||
triggerPrice,
|
||||
price,
|
||||
trailingPercent,
|
||||
}) => {
|
||||
const key = id;
|
||||
const quantity = (remainingSize ?? size).toString();
|
||||
|
||||
const orderType = type.rawValue as OrderType;
|
||||
const orderLabel = stringGetter({
|
||||
key: ORDER_TYPE_STRINGS[orderType].orderTypeKey,
|
||||
});
|
||||
const orderString = trailingPercent ? `${orderLabel} ${trailingPercent}%` : orderLabel;
|
||||
|
||||
const shouldShow =
|
||||
!cancelReason &&
|
||||
(status === AbacusOrderStatus.open || status === AbacusOrderStatus.untriggered);
|
||||
|
||||
const maybeOrderLine = chartLines[key]?.line;
|
||||
|
||||
if (!shouldShow) {
|
||||
if (maybeOrderLine) {
|
||||
maybeOrderLine.remove();
|
||||
delete chartLines[key];
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (maybeOrderLine) {
|
||||
if (maybeOrderLine.getQuantity() !== quantity) {
|
||||
maybeOrderLine.setQuantity(quantity);
|
||||
}
|
||||
} else {
|
||||
const orderLine = tvWidget
|
||||
?.chart()
|
||||
.createOrderLine({ disableUndo: false })
|
||||
.setPrice(MustBigNumber(triggerPrice ?? price).toNumber())
|
||||
.setQuantity(quantity)
|
||||
.setText(orderString);
|
||||
|
||||
if (orderLine) {
|
||||
const chartLine: ChartLine = {
|
||||
line: orderLine,
|
||||
chartLineType: ORDER_SIDES[side.name],
|
||||
};
|
||||
setLineColors({ chartLine: chartLine });
|
||||
chartLines[key] = chartLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const deleteChartLines = () => {
|
||||
Object.values(chartLines).forEach(({ line }) => {
|
||||
line.remove();
|
||||
});
|
||||
chartLines = {};
|
||||
};
|
||||
|
||||
const setLineColors = ({ chartLine }: { chartLine: ChartLine }) => {
|
||||
const { line, chartLineType } = chartLine;
|
||||
const { maybeQuantityColor, borderColor, backgroundColor, textColor, textButtonColor } =
|
||||
getChartLineColors({
|
||||
appTheme,
|
||||
appColorMode,
|
||||
chartLineType,
|
||||
});
|
||||
|
||||
line
|
||||
.setQuantityBorderColor(borderColor)
|
||||
.setBodyBackgroundColor(backgroundColor)
|
||||
.setBodyBorderColor(borderColor)
|
||||
.setBodyTextColor(textColor)
|
||||
.setQuantityTextColor(textButtonColor);
|
||||
|
||||
maybeQuantityColor &&
|
||||
line.setLineColor(maybeQuantityColor).setQuantityBackgroundColor(maybeQuantityColor);
|
||||
};
|
||||
|
||||
return { chartLines };
|
||||
};
|
||||
72
src/hooks/tradingView/useChartMarketAndResolution.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import type { ResolutionString } from 'public/tradingview/charting_library';
|
||||
|
||||
import { DEFAULT_RESOLUTION, RESOLUTION_CHART_CONFIGS } from '@/constants/candles';
|
||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||
import type { TvWidget } from '@/constants/tvchart';
|
||||
|
||||
import { setTvChartResolution } from '@/state/perpetuals';
|
||||
import { getCurrentMarketId, getSelectedResolutionForMarket } from '@/state/perpetualsSelectors';
|
||||
|
||||
/**
|
||||
* @description Hook to handle changing markets and setting chart resolution
|
||||
*/
|
||||
|
||||
export const useChartMarketAndResolution = ({
|
||||
tvWidget,
|
||||
isWidgetReady,
|
||||
savedResolution,
|
||||
}: {
|
||||
tvWidget: TvWidget | null;
|
||||
isWidgetReady?: boolean;
|
||||
savedResolution?: ResolutionString;
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const currentMarketId: string = useSelector(getCurrentMarketId) || DEFAULT_MARKETID;
|
||||
|
||||
const selectedResolution: string =
|
||||
useSelector(getSelectedResolutionForMarket(currentMarketId)) || DEFAULT_RESOLUTION;
|
||||
|
||||
const chart = isWidgetReady ? tvWidget?.chart() : undefined;
|
||||
const chartResolution = chart?.resolution?.();
|
||||
|
||||
/**
|
||||
* @description Hook to handle changing markets - intentionally should avoid triggering on change of resolutions.
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (currentMarketId && isWidgetReady) {
|
||||
const resolution = savedResolution || selectedResolution;
|
||||
tvWidget?.setSymbol(currentMarketId, resolution as ResolutionString, () => {});
|
||||
}
|
||||
}, [currentMarketId, isWidgetReady]);
|
||||
|
||||
/**
|
||||
* @description Hook to handle changing chart resolution
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (chartResolution) {
|
||||
if (chartResolution !== selectedResolution) {
|
||||
dispatch(setTvChartResolution({ marketId: currentMarketId, resolution: chartResolution }));
|
||||
}
|
||||
|
||||
setVisibleRangeForResolution({ resolution: chartResolution });
|
||||
}
|
||||
}, [currentMarketId, chartResolution, selectedResolution]);
|
||||
|
||||
const setVisibleRangeForResolution = ({ resolution }: { resolution: ResolutionString }) => {
|
||||
// Different resolutions have different timeframes to display data efficiently.
|
||||
const { defaultRange } = RESOLUTION_CHART_CONFIGS[resolution];
|
||||
|
||||
// from/to values converted to epoch seconds
|
||||
const newRange = {
|
||||
from: (Date.now() - defaultRange) / 1000,
|
||||
to: Date.now() / 1000,
|
||||
};
|
||||
|
||||
tvWidget?.activeChart().setVisibleRange(newRange, { percentRightMargin: 10 });
|
||||
};
|
||||
};
|
||||
@ -1,17 +1,19 @@
|
||||
import { useEffect } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
|
||||
import { LanguageCode, ResolutionString, widget } from 'public/tradingview/charting_library';
|
||||
|
||||
import { DEFAULT_RESOLUTION } from '@/constants/candles';
|
||||
import { SUPPORTED_LOCALE_BASE_TAGS, STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
import { LocalStorageKey } from '@/constants/localStorage';
|
||||
import type { TvWidget } from '@/constants/tvchart';
|
||||
|
||||
import { useDydxClient, useLocalStorage, useStringGetter } from '@/hooks';
|
||||
import { store } from '@/state/_store';
|
||||
|
||||
import { store } from '@/state/_store';
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
import { getAppTheme, getAppColorMode } from '@/state/configsSelectors';
|
||||
import { getSelectedLocale } from '@/state/localizationSelectors';
|
||||
@ -28,8 +30,8 @@ export const useTradingView = ({
|
||||
displayButtonRef,
|
||||
setIsChartReady,
|
||||
}: {
|
||||
tvWidgetRef: React.MutableRefObject<any>;
|
||||
displayButtonRef: React.MutableRefObject<any>;
|
||||
tvWidgetRef: React.MutableRefObject<TvWidget | null>;
|
||||
displayButtonRef: React.MutableRefObject<HTMLElement | null>;
|
||||
setIsChartReady: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) => {
|
||||
const stringGetter = useStringGetter();
|
||||
@ -69,15 +71,17 @@ export const useTradingView = ({
|
||||
tvWidgetRef.current = tvChartWidget;
|
||||
|
||||
tvWidgetRef.current.onChartReady(() => {
|
||||
tvWidgetRef?.current?.headerReady().then(() => {
|
||||
displayButtonRef.current = tvWidgetRef?.current?.createButton();
|
||||
displayButtonRef.current.innerHTML = `<span>${stringGetter({
|
||||
key: STRING_KEYS.ORDER_LINES,
|
||||
})}</span> <div class="displayOrdersButton-toggle"></div>`;
|
||||
displayButtonRef.current.setAttribute(
|
||||
'title',
|
||||
stringGetter({ key: STRING_KEYS.ORDER_LINES_TOOLTIP })
|
||||
);
|
||||
tvWidgetRef.current?.headerReady().then(() => {
|
||||
if (displayButtonRef && tvWidgetRef.current) {
|
||||
displayButtonRef.current = tvWidgetRef.current.createButton();
|
||||
displayButtonRef.current.innerHTML = `<span>${stringGetter({
|
||||
key: STRING_KEYS.ORDER_LINES,
|
||||
})}</span> <div class="displayOrdersButton-toggle"></div>`;
|
||||
displayButtonRef.current.setAttribute(
|
||||
'title',
|
||||
stringGetter({ key: STRING_KEYS.ORDER_LINES_TOOLTIP })
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
tvWidgetRef?.current?.subscribe('onAutoSaveNeeded', () =>
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import type {
|
||||
IChartingLibraryWidget,
|
||||
IOrderLineAdapter,
|
||||
ThemeName,
|
||||
} from 'public/tradingview/charting_library';
|
||||
import { THEME_NAMES } from '@/constants/styles/colors';
|
||||
import type { ChartLine, TvWidget } from '@/constants/tvchart';
|
||||
|
||||
import { AppColorMode, AppTheme } from '@/state/configs';
|
||||
import { getAppTheme, getAppColorMode } from '@/state/configsSelectors';
|
||||
|
||||
import { getWidgetOverrides, getOrderLineColors } from '@/lib/tradingView/utils';
|
||||
import { getWidgetOverrides, getChartLineColors } from '@/lib/tradingView/utils';
|
||||
|
||||
/**
|
||||
* @description Method to define a type guard and check that an element is an IFRAME
|
||||
@ -26,12 +24,12 @@ const isIFrame = (element: HTMLElement | null): element is HTMLIFrameElement =>
|
||||
* In order to support our Classic along with Dark/Light, we are directly accessing the <html> within the iFrame.
|
||||
*/
|
||||
export const useTradingViewTheme = ({
|
||||
orderLines,
|
||||
chartLines,
|
||||
tvWidget,
|
||||
isWidgetReady,
|
||||
}: {
|
||||
orderLines: Record<string, IOrderLineAdapter>;
|
||||
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
|
||||
chartLines: Record<string, ChartLine>;
|
||||
tvWidget: TvWidget | null;
|
||||
isWidgetReady?: boolean;
|
||||
}) => {
|
||||
const appTheme: AppTheme = useSelector(getAppTheme);
|
||||
@ -39,70 +37,64 @@ export const useTradingViewTheme = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (tvWidget && isWidgetReady) {
|
||||
tvWidget
|
||||
.changeTheme?.(
|
||||
{
|
||||
[AppTheme.Classic]: '',
|
||||
[AppTheme.Dark]: 'dark',
|
||||
[AppTheme.Light]: 'light',
|
||||
}[appTheme] as ThemeName
|
||||
)
|
||||
.then(() => {
|
||||
const tvChartId = tvWidget?._id;
|
||||
tvWidget.changeTheme?.(THEME_NAMES[appTheme]).then(() => {
|
||||
const tvChartId = tvWidget?._id;
|
||||
|
||||
if (tvChartId) {
|
||||
const frame = document?.getElementById(tvChartId);
|
||||
if (tvChartId) {
|
||||
const frame = document?.getElementById(tvChartId);
|
||||
|
||||
if (isIFrame(frame) && frame.contentWindow) {
|
||||
const innerHtml = frame.contentWindow.document.documentElement;
|
||||
switch (appTheme) {
|
||||
case AppTheme.Classic:
|
||||
innerHtml?.classList.remove('theme-dark', 'theme-light');
|
||||
break;
|
||||
case AppTheme.Dark:
|
||||
innerHtml?.classList.remove('theme-light');
|
||||
innerHtml?.classList.add('theme-dark');
|
||||
break;
|
||||
case AppTheme.Light:
|
||||
innerHtml?.classList.remove('theme-dark');
|
||||
innerHtml?.classList.add('theme-light');
|
||||
}
|
||||
if (isIFrame(frame) && frame.contentWindow) {
|
||||
const innerHtml = frame.contentWindow.document.documentElement;
|
||||
switch (appTheme) {
|
||||
case AppTheme.Classic:
|
||||
innerHtml?.classList.remove('theme-dark', 'theme-light');
|
||||
break;
|
||||
case AppTheme.Dark:
|
||||
innerHtml?.classList.remove('theme-light');
|
||||
innerHtml?.classList.add('theme-dark');
|
||||
break;
|
||||
case AppTheme.Light:
|
||||
innerHtml?.classList.remove('theme-dark');
|
||||
innerHtml?.classList.add('theme-light');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const { overrides, studies_overrides } = getWidgetOverrides({ appTheme, appColorMode });
|
||||
tvWidget?.applyOverrides(overrides);
|
||||
tvWidget?.applyStudiesOverrides(studies_overrides);
|
||||
const { overrides, studies_overrides } = getWidgetOverrides({ appTheme, appColorMode });
|
||||
tvWidget?.applyOverrides(overrides);
|
||||
tvWidget?.applyStudiesOverrides(studies_overrides);
|
||||
|
||||
// Necessary to update existing indicators
|
||||
const volumeStudyId = tvWidget
|
||||
?.activeChart()
|
||||
?.getAllStudies()
|
||||
?.find((x) => x.name === 'Volume')?.id;
|
||||
// Necessary to update existing indicators
|
||||
const volumeStudyId = tvWidget
|
||||
?.activeChart()
|
||||
?.getAllStudies()
|
||||
?.find((x) => x.name === 'Volume')?.id;
|
||||
|
||||
if (volumeStudyId) {
|
||||
const volume = tvWidget?.activeChart()?.getStudyById(volumeStudyId);
|
||||
volume.applyOverrides({
|
||||
'volume.color.0': studies_overrides['volume.volume.color.0'],
|
||||
'volume.color.1': studies_overrides['volume.volume.color.1'],
|
||||
});
|
||||
if (volumeStudyId) {
|
||||
const volume = tvWidget?.activeChart()?.getStudyById(volumeStudyId);
|
||||
volume.applyOverrides({
|
||||
'volume.color.0': studies_overrides['volume.volume.color.0'],
|
||||
'volume.color.1': studies_overrides['volume.volume.color.1'],
|
||||
});
|
||||
}
|
||||
|
||||
// Necessary to update existing chart lines
|
||||
Object.values(chartLines).forEach(({ chartLineType, line }) => {
|
||||
const { maybeQuantityColor, borderColor, backgroundColor, textColor, textButtonColor } =
|
||||
getChartLineColors({ chartLineType: chartLineType, appTheme, appColorMode });
|
||||
|
||||
if (maybeQuantityColor) {
|
||||
line.setLineColor(maybeQuantityColor).setQuantityBackgroundColor(maybeQuantityColor);
|
||||
}
|
||||
|
||||
// Necessary to update existing chart lines
|
||||
Object.entries(orderLines).forEach(([key, line]) => {
|
||||
const { orderColor, borderColor, backgroundColor, textColor, textButtonColor } =
|
||||
getOrderLineColors({ side: key.split('-')[0], appTheme, appColorMode });
|
||||
|
||||
line
|
||||
.setLineColor(orderColor)
|
||||
.setQuantityBackgroundColor(orderColor)
|
||||
.setQuantityBorderColor(borderColor)
|
||||
.setBodyBackgroundColor(backgroundColor)
|
||||
.setBodyBorderColor(borderColor)
|
||||
.setBodyTextColor(textColor)
|
||||
.setQuantityTextColor(textButtonColor);
|
||||
});
|
||||
line
|
||||
.setQuantityBorderColor(borderColor)
|
||||
.setBodyBackgroundColor(backgroundColor)
|
||||
.setBodyBorderColor(borderColor)
|
||||
.setBodyTextColor(textColor)
|
||||
.setQuantityTextColor(textButtonColor);
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [appTheme, appColorMode, isWidgetReady]);
|
||||
};
|
||||
|
||||
@ -10,9 +10,10 @@ 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,
|
||||
|
||||
@ -18,14 +18,13 @@ import type { ResolutionString } from 'public/tradingview/charting_library';
|
||||
import type { ConnectNetworkEvent, NetworkConfig } from '@/constants/abacus';
|
||||
import { DEFAULT_TRANSACTION_MEMO } from '@/constants/analytics';
|
||||
import { type Candle, RESOLUTION_MAP } from '@/constants/candles';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { DydxChainAsset } from '@/constants/wallets';
|
||||
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
|
||||
import { log } from '@/lib/telemetry';
|
||||
|
||||
import { useRestrictions } from './useRestrictions';
|
||||
import { useTokenConfigs } from './useTokenConfigs';
|
||||
|
||||
type DydxContextType = ReturnType<typeof useDydxClientContext>;
|
||||
const DydxContext = createContext<DydxContextType>({} as DydxContextType);
|
||||
@ -41,7 +40,8 @@ const useDydxClientContext = () => {
|
||||
// ------ Network ------ //
|
||||
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
const tokensConfigs = ENVIRONMENT_CONFIG_MAP[selectedNetwork].tokens;
|
||||
const { usdcDenom, usdcDecimals, usdcGasDenom, chainTokenDenom, chainTokenDecimals } =
|
||||
useTokenConfigs();
|
||||
|
||||
const [networkConfig, setNetworkConfig] = useState<NetworkConfig>();
|
||||
|
||||
@ -75,11 +75,11 @@ const useDydxClientContext = () => {
|
||||
networkConfig.validatorUrl,
|
||||
networkConfig.chainId,
|
||||
{
|
||||
USDC_DENOM: tokensConfigs[DydxChainAsset.USDC].denom,
|
||||
USDC_DECIMALS: tokensConfigs[DydxChainAsset.USDC].decimals,
|
||||
USDC_GAS_DENOM: tokensConfigs[DydxChainAsset.USDC].gasDenom,
|
||||
CHAINTOKEN_DENOM: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom,
|
||||
CHAINTOKEN_DECIMALS: tokensConfigs[DydxChainAsset.CHAINTOKEN].decimals,
|
||||
USDC_DENOM: usdcDenom,
|
||||
USDC_DECIMALS: usdcDecimals,
|
||||
USDC_GAS_DENOM: usdcGasDenom,
|
||||
CHAINTOKEN_DENOM: chainTokenDenom,
|
||||
CHAINTOKEN_DECIMALS: chainTokenDecimals,
|
||||
},
|
||||
{
|
||||
broadcastPollIntervalMs: 3_000,
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { useSelectedNetwork } from '@/hooks';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { GOVERNANCE_CONFIG_MAP } from '@/constants/networks';
|
||||
|
||||
import { getSelectedDydxChainId } from '@/state/appSelectors';
|
||||
|
||||
export interface GovernanceVariables {
|
||||
newMarketProposal: {
|
||||
@ -10,7 +13,7 @@ export interface GovernanceVariables {
|
||||
}
|
||||
|
||||
export const useGovernanceVariables = (): GovernanceVariables => {
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
const governanceVars = ENVIRONMENT_CONFIG_MAP[selectedNetwork].governance as GovernanceVariables;
|
||||
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
|
||||
const governanceVars = GOVERNANCE_CONFIG_MAP[selectedDydxChainId] as GovernanceVariables;
|
||||
return governanceVars;
|
||||
};
|
||||
|
||||
@ -82,24 +82,30 @@ const useLocalNotificationsContext = () => {
|
||||
isCctp,
|
||||
errorCount,
|
||||
status: currentStatus,
|
||||
isExchange,
|
||||
} = transferNotification;
|
||||
|
||||
// @ts-ignore status.errors is not in the type definition but can be returned
|
||||
// also error can some time come back as an empty object so we need to ignore for that
|
||||
const hasErrors = !!currentStatus?.errors ||
|
||||
(currentStatus?.error && Object.keys(currentStatus.error).length !== 0);
|
||||
const hasErrors =
|
||||
// @ts-ignore status.errors is not in the type definition but can be returned
|
||||
// also error can some time come back as an empty object so we need to ignore for that
|
||||
!!currentStatus?.errors ||
|
||||
(currentStatus?.error && Object.keys(currentStatus.error).length !== 0);
|
||||
|
||||
if (
|
||||
!isExchange &&
|
||||
!hasErrors &&
|
||||
(!currentStatus?.squidTransactionStatus ||
|
||||
currentStatus?.squidTransactionStatus === 'ongoing')
|
||||
) {
|
||||
try {
|
||||
const status = await fetchSquidStatus({
|
||||
transactionId: txHash,
|
||||
toChainId,
|
||||
fromChainId,
|
||||
}, isCctp);
|
||||
const status = await fetchSquidStatus(
|
||||
{
|
||||
transactionId: txHash,
|
||||
toChainId,
|
||||
fromChainId,
|
||||
},
|
||||
isCctp
|
||||
);
|
||||
|
||||
if (status) {
|
||||
transferNotification.status = status;
|
||||
|
||||
@ -5,8 +5,7 @@ import { isEqual, groupBy } from 'lodash';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
import { AppRoute, TokenRoute } from '@/constants/routes';
|
||||
import { DydxChainAsset } from '@/constants/wallets';
|
||||
|
||||
import {
|
||||
@ -21,9 +20,10 @@ import {
|
||||
NotificationType,
|
||||
DEFAULT_TOAST_AUTO_CLOSE_MS,
|
||||
TransferNotificationTypes,
|
||||
ReleaseUpdateNotificationIds,
|
||||
} from '@/constants/notifications';
|
||||
|
||||
import { useSelectedNetwork, useStringGetter } from '@/hooks';
|
||||
import { useStringGetter, useTokenConfigs } from '@/hooks';
|
||||
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
|
||||
|
||||
import { AssetIcon } from '@/components/AssetIcon';
|
||||
@ -36,6 +36,7 @@ import { getSubaccountFills, getSubaccountOrders } from '@/state/accountSelector
|
||||
import { openDialog } from '@/state/dialogs';
|
||||
import { getAbacusNotifications } from '@/state/notificationsSelectors';
|
||||
import { getMarketIds } from '@/state/perpetualsSelectors';
|
||||
import { getSelectedDydxChainId } from '@/state/appSelectors';
|
||||
|
||||
import { formatSeconds } from '@/lib/timeUtils';
|
||||
|
||||
@ -175,16 +176,17 @@ export const notificationTypes: NotificationTypeConfig[] = [
|
||||
useTrigger: ({ trigger }) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { transferNotifications } = useLocalNotifications();
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
|
||||
|
||||
useEffect(() => {
|
||||
for (const transfer of transferNotifications) {
|
||||
const { fromChainId, status, txHash, toAmount, type } = transfer;
|
||||
const isFinished = Boolean(status) && status?.squidTransactionStatus !== 'ongoing';
|
||||
const { fromChainId, status, txHash, toAmount, type, isExchange } = transfer;
|
||||
const isFinished =
|
||||
(Boolean(status) && status?.squidTransactionStatus !== 'ongoing') || isExchange;
|
||||
const icon = <Icon iconName={isFinished ? IconName.Transfer : IconName.Clock} />;
|
||||
|
||||
const transferType =
|
||||
type ?? fromChainId === ENVIRONMENT_CONFIG_MAP[selectedNetwork].dydxChainId
|
||||
type ?? fromChainId === selectedDydxChainId
|
||||
? TransferNotificationTypes.Withdrawal
|
||||
: TransferNotificationTypes.Deposit;
|
||||
|
||||
@ -237,13 +239,16 @@ 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(
|
||||
'rewards-and-full-trading-live',
|
||||
ReleaseUpdateNotificationIds.RewardsAndFullTradingLive,
|
||||
{
|
||||
icon: <AssetIcon symbol="DYDX" />,
|
||||
icon: <AssetIcon symbol={chainTokenLabel} />,
|
||||
title: stringGetter({ key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.TITLE' }),
|
||||
body: stringGetter({
|
||||
key: 'NOTIFICATIONS.RELEASE_REWARDS_AND_FULL_TRADING.BODY',
|
||||
@ -269,14 +274,41 @@ export const notificationTypes: NotificationTypeConfig[] = [
|
||||
},
|
||||
}),
|
||||
toastSensitivity: 'foreground',
|
||||
groupKey: NotificationType.ReleaseUpdates,
|
||||
groupKey: ReleaseUpdateNotificationIds.RewardsAndFullTradingLive,
|
||||
},
|
||||
[]
|
||||
);
|
||||
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: () => {
|
||||
return () => {};
|
||||
const { chainTokenLabel } = useTokenConfigs();
|
||||
const navigate = useNavigate();
|
||||
return (notificationId: string) => {
|
||||
if (notificationId === ReleaseUpdateNotificationIds.IncentivesS3) {
|
||||
navigate(`${chainTokenLabel}/${TokenRoute.TradingRewards}`);
|
||||
}
|
||||
};
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
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,
|
||||
@ -13,7 +14,8 @@ import {
|
||||
import { useLocalStorage } from './useLocalStorage';
|
||||
import { notificationTypes } from './useNotificationTypes';
|
||||
|
||||
import { renderSvgToDataUrl } from '../lib/renderSvgToDataUrl';
|
||||
import { track } from '@/lib/analytics';
|
||||
import { renderSvgToDataUrl } from '@/lib/renderSvgToDataUrl';
|
||||
|
||||
type NotificationsContextType = ReturnType<typeof useNotificationsContext>;
|
||||
|
||||
@ -48,6 +50,7 @@ const useNotificationsContext = () => {
|
||||
defaultValue: {
|
||||
[NotificationType.AbacusGenerated]: true,
|
||||
[NotificationType.SquidTransfer]: true,
|
||||
[NotificationType.ReleaseUpdates]: true,
|
||||
version: LOCAL_STORAGE_VERSIONS[LocalStorageKey.NotificationPreferences],
|
||||
},
|
||||
});
|
||||
@ -176,8 +179,10 @@ const useNotificationsContext = () => {
|
||||
)
|
||||
);
|
||||
|
||||
const onNotificationAction = async (notification: Notification) =>
|
||||
await actions[notification.type]?.(notification.id);
|
||||
const onNotificationAction = async (notification: Notification) => {
|
||||
track(AnalyticsEvent.NotificationAction, { type: notification.type, id: notification.id });
|
||||
return await actions[notification.type]?.(notification.id);
|
||||
};
|
||||
|
||||
// Push notifications
|
||||
const [hasEnabledPush, setHasEnabledPush] = useLocalStorage({
|
||||
|
||||
@ -4,8 +4,11 @@ import { useSelector } from 'react-redux';
|
||||
import { DEFAULT_DOCUMENT_TITLE } from '@/constants/routes';
|
||||
|
||||
import { getSelectedLocale } from '@/state/localizationSelectors';
|
||||
import { getCurrentMarketData, getCurrentMarketId } from '@/state/perpetualsSelectors';
|
||||
import type { RootState } from '@/state/_store';
|
||||
import {
|
||||
getCurrentMarketId,
|
||||
getCurrentMarketMidMarketPrice,
|
||||
getCurrentMarketOraclePrice,
|
||||
} from '@/state/perpetualsSelectors';
|
||||
|
||||
import { useBreakpoints } from './useBreakpoints';
|
||||
|
||||
@ -13,11 +16,14 @@ export const usePageTitlePriceUpdates = () => {
|
||||
const selectedLocale = useSelector(getSelectedLocale);
|
||||
const { isNotTablet } = useBreakpoints();
|
||||
const id = useSelector(getCurrentMarketId);
|
||||
const oraclePrice = useSelector((state: RootState) => getCurrentMarketData(state)?.oraclePrice);
|
||||
const oraclePrice = useSelector(getCurrentMarketOraclePrice);
|
||||
const orderbookMidMarketPrice = useSelector(getCurrentMarketMidMarketPrice);
|
||||
|
||||
const price = orderbookMidMarketPrice ?? oraclePrice;
|
||||
|
||||
useEffect(() => {
|
||||
if (id && oraclePrice && isNotTablet) {
|
||||
const priceString = oraclePrice.toLocaleString(selectedLocale);
|
||||
if (id && price && isNotTablet) {
|
||||
const priceString = price.toLocaleString(selectedLocale);
|
||||
document.title = `$${priceString} ${id} · ${DEFAULT_DOCUMENT_TITLE}`;
|
||||
} else {
|
||||
document.title = DEFAULT_DOCUMENT_TITLE;
|
||||
@ -26,5 +32,5 @@ export const usePageTitlePriceUpdates = () => {
|
||||
return () => {
|
||||
document.title = DEFAULT_DOCUMENT_TITLE;
|
||||
};
|
||||
}, [oraclePrice]);
|
||||
}, [price]);
|
||||
};
|
||||
|
||||
@ -2,7 +2,7 @@ import { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { LocalStorageKey } from '@/constants/localStorage';
|
||||
import { DEFAULT_APP_ENVIRONMENT, DydxNetwork, ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { DEFAULT_APP_ENVIRONMENT, DydxNetwork } from '@/constants/networks';
|
||||
|
||||
import { useAccounts, useLocalStorage } from '@/hooks';
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ import { setSubaccount, setHistoricalPnl, removeUncommittedOrderClientId } from
|
||||
import { getBalances } from '@/state/accountSelectors';
|
||||
|
||||
import abacusStateManager from '@/lib/abacus';
|
||||
import { hashFromTx } from '@/lib/hashfromTx';
|
||||
import { hashFromTx } from '@/lib/txUtils';
|
||||
import { log } from '@/lib/telemetry';
|
||||
|
||||
import { useAccounts } from './useAccounts';
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { TOKEN_CONFIG_MAP } from '@/constants/networks';
|
||||
import { DydxChainAsset } from '@/constants/wallets';
|
||||
|
||||
import { useSelectedNetwork } from '@/hooks';
|
||||
import { getSelectedDydxChainId } from '@/state/appSelectors';
|
||||
|
||||
export const useTokenConfigs = (): {
|
||||
tokensConfigs: {
|
||||
@ -10,31 +12,33 @@ export const useTokenConfigs = (): {
|
||||
name: string;
|
||||
decimals: number;
|
||||
gasDenom?: string;
|
||||
},
|
||||
};
|
||||
[DydxChainAsset.CHAINTOKEN]: {
|
||||
denom: string;
|
||||
name: string;
|
||||
decimals: number;
|
||||
gasDenom?: string;
|
||||
},
|
||||
};
|
||||
};
|
||||
usdcDenom: string;
|
||||
usdcDecimals: number;
|
||||
usdcGasDenom: string;
|
||||
usdcLabel: string;
|
||||
chainTokenDenom: string;
|
||||
chainTokenDecimals: number;
|
||||
chainTokenLabel: string;
|
||||
} => {
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
const tokensConfigs = ENVIRONMENT_CONFIG_MAP[selectedNetwork].tokens;
|
||||
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
|
||||
const tokensConfigs = TOKEN_CONFIG_MAP[selectedDydxChainId];
|
||||
|
||||
return {
|
||||
return {
|
||||
tokensConfigs,
|
||||
usdcDenom: tokensConfigs[DydxChainAsset.USDC].denom,
|
||||
usdcDecimals: tokensConfigs[DydxChainAsset.USDC].decimals,
|
||||
usdcDenom: tokensConfigs[DydxChainAsset.USDC].denom,
|
||||
usdcDecimals: tokensConfigs[DydxChainAsset.USDC].decimals,
|
||||
usdcGasDenom: tokensConfigs[DydxChainAsset.USDC].gasDenom,
|
||||
usdcLabel: tokensConfigs[DydxChainAsset.USDC].name,
|
||||
chainTokenDenom: tokensConfigs[DydxChainAsset.CHAINTOKEN].denom,
|
||||
chainTokenDecimals: tokensConfigs[DydxChainAsset.CHAINTOKEN].decimals,
|
||||
chainTokenDecimals: tokensConfigs[DydxChainAsset.CHAINTOKEN].decimals,
|
||||
chainTokenLabel: tokensConfigs[DydxChainAsset.CHAINTOKEN].name,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import { useSelectedNetwork } from '@/hooks';
|
||||
import { LINKS_CONFIG_MAP } from '@/constants/networks';
|
||||
|
||||
import { getSelectedDydxChainId } from '@/state/appSelectors';
|
||||
|
||||
const FALLBACK_URL = 'https://help.dydx.exchange/';
|
||||
|
||||
export interface LinksConfigs {
|
||||
tos: string;
|
||||
privacy: string;
|
||||
statusPage: string;
|
||||
mintscan: string;
|
||||
mintscanBase: string;
|
||||
feedback?: string;
|
||||
@ -27,12 +30,13 @@ export interface LinksConfigs {
|
||||
}
|
||||
|
||||
export const useURLConfigs = (): LinksConfigs => {
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
const linksConfigs = ENVIRONMENT_CONFIG_MAP[selectedNetwork].links as LinksConfigs;
|
||||
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
|
||||
const linksConfigs = LINKS_CONFIG_MAP[selectedDydxChainId] as LinksConfigs;
|
||||
|
||||
return {
|
||||
tos: linksConfigs.tos,
|
||||
privacy: linksConfigs.privacy,
|
||||
statusPage: linksConfigs.statusPage,
|
||||
mintscan: linksConfigs.mintscan,
|
||||
mintscanBase: linksConfigs.mintscanBase,
|
||||
feedback: linksConfigs.feedback || FALLBACK_URL,
|
||||
|
||||
@ -4,7 +4,7 @@ import { useSelector } from 'react-redux';
|
||||
import { EvmDerivedAddresses } from '@/constants/account';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { LocalStorageKey } from '@/constants/localStorage';
|
||||
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { ENVIRONMENT_CONFIG_MAP, WALLETS_CONFIG_MAP } from '@/constants/networks';
|
||||
|
||||
import {
|
||||
type DydxAddress,
|
||||
@ -32,7 +32,7 @@ import {
|
||||
WalletType as CosmosWalletType,
|
||||
} from 'graz';
|
||||
|
||||
import { getSelectedNetwork } from '@/state/appSelectors';
|
||||
import { getSelectedDydxChainId } from '@/state/appSelectors';
|
||||
|
||||
import { resolveWagmiConnector } from '@/lib/wagmi';
|
||||
import { getWalletConnection, parseWalletError } from '@/lib/wallet';
|
||||
@ -91,8 +91,8 @@ export const useWalletConnection = () => {
|
||||
|
||||
// Wallet connection
|
||||
|
||||
const selectedNetwork = useSelector(getSelectedNetwork);
|
||||
const walletConnectConfig = ENVIRONMENT_CONFIG_MAP[selectedNetwork].wallets.walletconnect;
|
||||
const selectedDydxChainId = useSelector(getSelectedDydxChainId);
|
||||
const walletConnectConfig = WALLETS_CONFIG_MAP[selectedDydxChainId].walletconnect;
|
||||
const wagmiConnector = useMemo(
|
||||
() =>
|
||||
walletType && walletConnectionType
|
||||
@ -163,17 +163,20 @@ export const useWalletConnection = () => {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
throw Object.assign(
|
||||
new Error([error.message, error.cause?.message].filter(Boolean).join('\n')),
|
||||
{
|
||||
walletConnectionType: walletConnection?.type,
|
||||
}
|
||||
);
|
||||
const { isErrorExpected } = parseWalletError({ error, stringGetter });
|
||||
if (!isErrorExpected) {
|
||||
throw Object.assign(
|
||||
new Error([error.message, error.cause?.message].filter(Boolean).join('\n')),
|
||||
{
|
||||
walletConnectionType: walletConnection?.type,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
walletType,
|
||||
walletConnectionType: walletConnection.type,
|
||||
walletConnectionType: walletConnection?.type,
|
||||
};
|
||||
},
|
||||
[isConnectedGraz, signerGraz, isConnectedWagmi, signerWagmi]
|
||||
|
||||
@ -83,6 +83,7 @@ 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';
|
||||
|
||||
1
src/icons/wallets/okx-wallet.svg
Normal file
@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@ -31,6 +31,7 @@ import { FillDetailsDialog } from '@/views/dialogs/DetailsDialog/FillDetailsDial
|
||||
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();
|
||||
@ -62,8 +63,8 @@ export const DialogManager = () => {
|
||||
[DialogTypes.ExternalLink]: <ExternalLinkDialog {...modalProps} />,
|
||||
[DialogTypes.ExternalNavStride]: <ExternalNavStrideDialog {...modalProps} />,
|
||||
[DialogTypes.MnemonicExport]: <MnemonicExportDialog {...modalProps} />,
|
||||
[DialogTypes.MobileDownload]: <MobilDownloadDialog {...modalProps} />,
|
||||
[DialogTypes.MobileSignIn]: <MobileSignInDialog {...modalProps} />,
|
||||
[DialogTypes.MobileDownload]: <MobileDownloadDialog {...modalProps} />,
|
||||
[DialogTypes.Onboarding]: <OnboardingDialog {...modalProps} />,
|
||||
[DialogTypes.OrderDetails]: <OrderDetailsDialog {...modalProps} />,
|
||||
[DialogTypes.Preferences]: <PreferencesDialog {...modalProps} />,
|
||||
|
||||
@ -3,9 +3,9 @@ import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||
import { AbacusApiStatus } from '@/constants/abacus';
|
||||
import { ButtonSize, ButtonType } from '@/constants/buttons';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { ENVIRONMENT_CONFIG_MAP, isDev } from '@/constants/networks';
|
||||
import { isDev } from '@/constants/networks';
|
||||
|
||||
import { useApiState, useSelectedNetwork, useStringGetter } from '@/hooks';
|
||||
import { useApiState, useStringGetter, useURLConfigs } from '@/hooks';
|
||||
import { ChatIcon, LinkOutIcon } from '@/icons';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
@ -28,8 +28,7 @@ enum ExchangeStatus {
|
||||
export const FooterDesktop = () => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { height, indexerHeight, status, statusErrorMessage } = useApiState();
|
||||
const { selectedNetwork } = useSelectedNetwork();
|
||||
const { statusPage } = ENVIRONMENT_CONFIG_MAP[selectedNetwork].links;
|
||||
const { statusPage } = useURLConfigs();
|
||||
|
||||
const { exchangeStatus, label } =
|
||||
!status || status === AbacusApiStatus.NORMAL
|
||||
|
||||
14
src/lib/__test__/urlUtils.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
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');
|
||||
});
|
||||
});
|
||||
@ -34,7 +34,7 @@ import {
|
||||
import { DEFAULT_TRANSACTION_MEMO } from '@/constants/analytics';
|
||||
import { DialogTypes } from '@/constants/dialogs';
|
||||
import { UNCOMMITTED_ORDER_TIMEOUT_MS } from '@/constants/trade';
|
||||
import { ENVIRONMENT_CONFIG_MAP, DydxNetwork, isTestnet } from '@/constants/networks';
|
||||
import { DydxChainId, isTestnet } from '@/constants/networks';
|
||||
|
||||
import { RootStore } from '@/state/_store';
|
||||
import { addUncommittedOrderClientId, removeUncommittedOrderClientId } from '@/state/account';
|
||||
@ -43,7 +43,11 @@ import { openDialog } from '@/state/dialogs';
|
||||
import { StatefulOrderError } from '../errors';
|
||||
import { bytesToBigInt } from '../numbers';
|
||||
import { log } from '../telemetry';
|
||||
import { hashFromTx } from '../hashfromTx';
|
||||
import { hashFromTx, getMintscanTxLink } from '../txUtils';
|
||||
|
||||
(BigInt.prototype as any).toJSON = function () {
|
||||
return this.toString();
|
||||
};
|
||||
|
||||
class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
private compositeClient: CompositeClient | undefined;
|
||||
@ -238,9 +242,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
|
||||
if (isTestnet) {
|
||||
console.log(
|
||||
`${ENVIRONMENT_CONFIG_MAP[
|
||||
this.compositeClient.network.getString() as DydxNetwork
|
||||
]?.links?.mintscan?.replace('{tx_hash}', hash.toString())}`
|
||||
getMintscanTxLink(this.compositeClient.network.getString() as DydxChainId, hash)
|
||||
);
|
||||
} else console.log(`txHash: ${hash}`);
|
||||
|
||||
@ -536,7 +538,6 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
||||
const result = await this.cctpWithdraw(params);
|
||||
callback(result);
|
||||
break;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
|
||||
@ -25,3 +25,22 @@ export function convertBech32Address({
|
||||
}): string {
|
||||
return toBech32(bech32Prefix, fromHex(toHex(fromBech32(address).data)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a Cosmos address with a specific prefix.
|
||||
* @param {string} address The Cosmos address to validate.
|
||||
* @param {string} prefix The expected prefix for the address.
|
||||
* @returns {boolean} True if the address is valid and matches the prefix, false otherwise.
|
||||
*/
|
||||
export function validateCosmosAddress(address: string, prefix: string) {
|
||||
try {
|
||||
// Decode the address to verify its structure and prefix
|
||||
const { prefix: decodedPrefix } = fromBech32(address);
|
||||
|
||||
// Check if the decoded address has the expected prefix
|
||||
return decodedPrefix === prefix;
|
||||
} catch (error) {
|
||||
// If decoding fails, the address is not valid
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
export const hashFromTx = (
|
||||
txHash: string | Uint8Array
|
||||
): string => `0x${Buffer.from(txHash).toString('hex')}`;
|
||||
@ -1,4 +1,4 @@
|
||||
import { type DydxNetwork, ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import { type DydxNetwork, ENVIRONMENT_CONFIG_MAP, type DydxChainId } from '@/constants/networks';
|
||||
|
||||
export const validateAgainstAvailableEnvironments = (value: DydxNetwork) =>
|
||||
Object.keys(ENVIRONMENT_CONFIG_MAP).includes(value);
|
||||
Object.keys(ENVIRONMENT_CONFIG_MAP).includes(value);
|
||||
@ -31,13 +31,13 @@ export const fetchSquidStatus = async (
|
||||
|
||||
const response = await fetch(url, {
|
||||
headers: {
|
||||
"x-integrator-id": integratorId || 'dYdX-api'
|
||||
'x-integrator-id': integratorId || 'dYdX-api',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return response.json();
|
||||
@ -45,4 +45,4 @@ export const fetchSquidStatus = async (
|
||||
|
||||
export const getNobleChainId = () => {
|
||||
return isMainnet ? 'noble-1' : 'grand-1';
|
||||
}
|
||||
};
|
||||
|
||||
@ -19,10 +19,10 @@ class TestFlags {
|
||||
return !!this.queryParams.displayinitializingmarkets;
|
||||
}
|
||||
|
||||
get addressOverride():string {
|
||||
get addressOverride(): string {
|
||||
return this.queryParams.address;
|
||||
}
|
||||
|
||||
|
||||
get showTradingRewards() {
|
||||
return !!this.queryParams.tradingrewards;
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||
|
||||
import { Candle, TradingViewBar, TradingViewSymbol } from '@/constants/candles';
|
||||
import { THEME_NAMES } from '@/constants/styles/colors';
|
||||
import type { ChartLineType } from '@/constants/tvchart';
|
||||
|
||||
import { AppTheme, type AppColorMode } from '@/state/configs';
|
||||
import { type AppColorMode, AppTheme } from '@/state/configs';
|
||||
|
||||
import { Themes } from '@/styles/themes';
|
||||
|
||||
@ -49,23 +51,24 @@ export const getHistorySlice = ({
|
||||
return bars.filter(({ time }) => time >= fromMs);
|
||||
};
|
||||
|
||||
export const getOrderLineColors = ({
|
||||
export const getChartLineColors = ({
|
||||
appTheme,
|
||||
appColorMode,
|
||||
side,
|
||||
chartLineType,
|
||||
}: {
|
||||
appTheme: AppTheme;
|
||||
appColorMode: AppColorMode;
|
||||
side: OrderSide;
|
||||
chartLineType: ChartLineType;
|
||||
}) => {
|
||||
const theme = Themes[appTheme][appColorMode];
|
||||
const orderColor = {
|
||||
const orderColors = {
|
||||
[OrderSide.BUY]: theme.positive,
|
||||
[OrderSide.SELL]: theme.negative,
|
||||
}[side];
|
||||
['position']: null,
|
||||
};
|
||||
|
||||
return {
|
||||
orderColor,
|
||||
maybeQuantityColor: orderColors[chartLineType],
|
||||
borderColor: theme.borderDefault,
|
||||
backgroundColor: theme.layer1,
|
||||
textColor: theme.textTertiary,
|
||||
@ -83,7 +86,7 @@ export const getWidgetOverrides = ({
|
||||
const theme = Themes[appTheme][appColorMode];
|
||||
|
||||
return {
|
||||
theme: appTheme === AppTheme.Dark ? 'dark' : AppTheme.Light ? 'light' : '',
|
||||
theme: THEME_NAMES[appTheme],
|
||||
overrides: {
|
||||
'paneProperties.background': theme.layer2,
|
||||
'paneProperties.horzGridProperties.color': theme.layer3,
|
||||
|
||||
7
src/lib/txUtils.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { DydxChainId, LINKS_CONFIG_MAP } from '@/constants/networks';
|
||||
|
||||
export const hashFromTx = (txHash: string | Uint8Array): string =>
|
||||
`0x${Buffer.from(txHash).toString('hex')}`;
|
||||
|
||||
export const getMintscanTxLink = (dydxChainId: DydxChainId, txHash: string): string =>
|
||||
`${LINKS_CONFIG_MAP[dydxChainId]?.mintscan?.replace('{tx_hash}', txHash.toString())}`;
|
||||
13
src/lib/urlUtils.ts
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* @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}` : ''}`;
|
||||
};
|
||||
@ -92,6 +92,10 @@ export const getWalletErrorType = ({ error }: { error: Error }) => {
|
||||
return WalletErrorType.ChainMismatch;
|
||||
}
|
||||
|
||||
if (messageLower.includes('Missing or invalid. request() method: wallet_switchEthereumChain')) {
|
||||
return WalletErrorType.SwitchChainMethodMissing;
|
||||
}
|
||||
|
||||
// ImToken - User canceled
|
||||
if (messageLower.includes('用户取消了操作')) {
|
||||
return WalletErrorType.UserCanceled;
|
||||
@ -113,13 +117,18 @@ export const parseWalletError = ({
|
||||
}) => {
|
||||
const walletErrorType = getWalletErrorType({ error });
|
||||
let message;
|
||||
let isErrorExpected;
|
||||
|
||||
switch (walletErrorType) {
|
||||
case WalletErrorType.ChainMismatch:
|
||||
case WalletErrorType.UserCanceled: {
|
||||
case WalletErrorType.UserCanceled:
|
||||
case WalletErrorType.SwitchChainMethodMissing: {
|
||||
isErrorExpected = true;
|
||||
message = error.message;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
isErrorExpected = false;
|
||||
message = stringGetter({
|
||||
key: STRING_KEYS.SOMETHING_WENT_WRONG_WITH_MESSAGE,
|
||||
params: {
|
||||
@ -132,5 +141,6 @@ export const parseWalletError = ({
|
||||
return {
|
||||
walletErrorType,
|
||||
message,
|
||||
isErrorExpected,
|
||||
};
|
||||
};
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
type InjectedCoinbaseWalletExtensionProvider,
|
||||
type WithInjectedEthereumProvider,
|
||||
type WithInjectedWeb3Provider,
|
||||
type WithInjectedOkxWalletProvider,
|
||||
} from '@/constants/wallets';
|
||||
|
||||
import { isTruthy } from '../isTruthy';
|
||||
@ -18,15 +19,6 @@ 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
|
||||
)
|
||||
)
|
||||
|
||||
/*
|
||||
@ -54,5 +46,10 @@ export const detectInjectedEip1193Providers = (): ExternalProvider[] => {
|
||||
? ethereumProvider.providers
|
||||
: [];
|
||||
|
||||
return [...displacedProviders, ethereumProvider, web3Provider].filter(isTruthy);
|
||||
const okxWalletProvider = (globalThis as typeof globalThis & WithInjectedOkxWalletProvider)
|
||||
?.okxwallet;
|
||||
|
||||
return [...displacedProviders, ethereumProvider, web3Provider, okxWalletProvider].filter(
|
||||
isTruthy
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import './polyfills';
|
||||
import { StrictMode } from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { HashRouter } from 'react-router-dom';
|
||||
import { BrowserRouter, HashRouter } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { store } from '@/state/_store';
|
||||
@ -12,11 +12,13 @@ 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}>
|
||||
<HashRouter children={<App />} />
|
||||
<Router children={<App />} />
|
||||
</Provider>
|
||||
</StrictMode>
|
||||
</ErrorBoundary>
|
||||
|
||||
@ -26,8 +26,6 @@ import { openDialog } from '@/state/dialogs';
|
||||
|
||||
import { log } from '@/lib/telemetry';
|
||||
|
||||
const SEASON_NUMBER = 2;
|
||||
|
||||
export const LaunchIncentivesPanel = ({ className }: { className?: string }) => {
|
||||
const { isNotTablet } = useBreakpoints();
|
||||
const dispatch = useDispatch();
|
||||
@ -74,14 +72,38 @@ const EstimatedRewards = () => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { dydxAddress } = useAccounts();
|
||||
|
||||
const { data: seasonNumber } = useQuery({
|
||||
queryKey: 'chaos_labs_season_number',
|
||||
queryFn: async () => {
|
||||
const resp = await fetch('https://cloud.chaoslabs.co/query/ccar-perpetuals', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'apollographql-client-name': 'dydx-v4',
|
||||
'content-type': 'application/json',
|
||||
protocol: 'dydx-v4',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
operationName: 'TradingSeasons',
|
||||
variables: {},
|
||||
query: `query TradingSeasons {
|
||||
tradingSeasons {
|
||||
label
|
||||
}
|
||||
}`,
|
||||
}),
|
||||
});
|
||||
const seasons = (await resp.json())?.data?.tradingSeasons;
|
||||
return seasons && seasons.length > 0 ? seasons[seasons.length - 1].label : undefined;
|
||||
},
|
||||
onError: (error: Error) => log('LaunchIncentives/fetchSeasonNumber', error),
|
||||
});
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
enabled: !!dydxAddress,
|
||||
queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}_${SEASON_NUMBER}`,
|
||||
queryKey: `launch_incentives_rewards_${dydxAddress ?? ''}`,
|
||||
queryFn: async () => {
|
||||
if (!dydxAddress) return undefined;
|
||||
const resp = await fetch(
|
||||
`https://cloud.chaoslabs.co/query/api/dydx/points/${dydxAddress}?n=${SEASON_NUMBER}`
|
||||
);
|
||||
const resp = await fetch(`https://cloud.chaoslabs.co/query/api/dydx/points/${dydxAddress}`);
|
||||
return (await resp.json())?.incentivePoints;
|
||||
},
|
||||
onError: (error: Error) => log('LaunchIncentives/fetchPoints', error),
|
||||
@ -92,12 +114,14 @@ const EstimatedRewards = () => {
|
||||
<Styled.EstimatedRewardsCardContent>
|
||||
<div>
|
||||
<span>{stringGetter({ key: STRING_KEYS.ESTIMATED_REWARDS })}</span>
|
||||
<Styled.Season>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.LAUNCH_INCENTIVES_SEASON_NUM,
|
||||
params: { SEASON_NUMBER },
|
||||
})}
|
||||
</Styled.Season>
|
||||
{seasonNumber !== undefined && (
|
||||
<Styled.Season>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.LAUNCH_INCENTIVES_SEASON_NUM,
|
||||
params: { SEASON_NUMBER: seasonNumber },
|
||||
})}
|
||||
</Styled.Season>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Styled.Points>
|
||||
|
||||
@ -9,10 +9,13 @@ import {
|
||||
HistoricalTradingRewardsPeriods,
|
||||
} from '@/constants/abacus';
|
||||
|
||||
import { isMainnet } from '@/constants/networks';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
|
||||
import breakpoints from '@/styles/breakpoints';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { Output, OutputType } from '@/components/Output';
|
||||
import { Panel } from '@/components/Panel';
|
||||
import { ToggleGroup } from '@/components/ToggleGroup';
|
||||
import { WithTooltip } from '@/components/WithTooltip';
|
||||
@ -20,6 +23,9 @@ import { TradingRewardHistoryTable } from '@/views/tables/TradingRewardHistoryTa
|
||||
|
||||
import abacusStateManager from '@/lib/abacus';
|
||||
|
||||
// TODO: set in env featureFlag config
|
||||
const REWARDS_HISTORY_START_DATE_MS = isMainnet ? 1706486400000 : 1704844800000;
|
||||
|
||||
export const RewardHistoryPanel = () => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
@ -47,7 +53,20 @@ export const RewardHistoryPanel = () => {
|
||||
<WithTooltip tooltip="reward-history">
|
||||
<h3>{stringGetter({ key: STRING_KEYS.REWARD_HISTORY })}</h3>
|
||||
</WithTooltip>
|
||||
<span>{stringGetter({ key: STRING_KEYS.REWARD_HISTORY_DESCRIPTION })}</span>
|
||||
<span>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.REWARD_HISTORY_DESCRIPTION,
|
||||
params: {
|
||||
REWARDS_HISTORY_START_DATE: (
|
||||
<Styled.Output
|
||||
type={OutputType.Date}
|
||||
value={REWARDS_HISTORY_START_DATE_MS}
|
||||
timeOptions={{ useUTC: true }}
|
||||
/>
|
||||
),
|
||||
},
|
||||
})}
|
||||
</span>
|
||||
</Styled.Title>
|
||||
<ToggleGroup
|
||||
items={[
|
||||
@ -103,3 +122,7 @@ Styled.Content = styled.div`
|
||||
${layoutMixins.flexColumn}
|
||||
gap: 0.75rem;
|
||||
`;
|
||||
|
||||
Styled.Output = styled(Output)`
|
||||
display: inline;
|
||||
`;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { isMainnet } from '@/constants/networks';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
|
||||
@ -27,6 +28,8 @@ const RewardsPage = () => {
|
||||
const { isTablet, isNotTablet } = useBreakpoints();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const showTradingRewards = testFlags.showTradingRewards || !isMainnet;
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isTablet && (
|
||||
@ -37,7 +40,7 @@ const RewardsPage = () => {
|
||||
)}
|
||||
<DetachedSection>
|
||||
<Styled.GridLayout
|
||||
showTradingRewards={testFlags.showTradingRewards}
|
||||
showTradingRewards={showTradingRewards}
|
||||
showMigratePanel={import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet}
|
||||
>
|
||||
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <Styled.MigratePanel />}
|
||||
@ -51,7 +54,7 @@ const RewardsPage = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{testFlags.showTradingRewards && (
|
||||
{showTradingRewards && (
|
||||
<Styled.TradingRewardsColumn>
|
||||
<TradingRewardsSummaryPanel />
|
||||
{isTablet && <RewardsHelpPanel />}
|
||||
@ -60,7 +63,7 @@ const RewardsPage = () => {
|
||||
)}
|
||||
|
||||
{isNotTablet && (
|
||||
<Styled.OtherColumn showTradingRewards={testFlags.showTradingRewards}>
|
||||
<Styled.OtherColumn showTradingRewards={showTradingRewards}>
|
||||
<RewardsHelpPanel />
|
||||
</Styled.OtherColumn>
|
||||
)}
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
import { DydxChainId, ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
|
||||
import type { RootState } from './_store';
|
||||
|
||||
export const getApiState = (state: RootState) => state.app.apiState;
|
||||
export const getSelectedNetwork = (state: RootState) => state.app.selectedNetwork;
|
||||
|
||||
export const getSelectedDydxChainId = (state: RootState) =>
|
||||
ENVIRONMENT_CONFIG_MAP[state.app.selectedNetwork].dydxChainId as DydxChainId;
|
||||
|
||||
@ -107,6 +107,12 @@ export const getCurrentMarketHistoricalFundings = createSelector(
|
||||
currentMarketId ? historicalFundings?.[currentMarketId] ?? [] : []
|
||||
);
|
||||
|
||||
/**
|
||||
* @returns oracle price of the market the user is currently viewing
|
||||
*/
|
||||
export const getCurrentMarketOraclePrice = (state: RootState) =>
|
||||
getCurrentMarketData(state)?.oraclePrice;
|
||||
|
||||
/**
|
||||
* @returns Mid market price for the market the user is currently viewing
|
||||
*/
|
||||
|
||||
@ -1,205 +1,40 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import styled, { type AnyStyledComponent, css } from 'styled-components';
|
||||
|
||||
import type {
|
||||
IChartingLibraryWidget,
|
||||
IOrderLineAdapter,
|
||||
ResolutionString,
|
||||
} from 'public/tradingview/charting_library';
|
||||
import type { ResolutionString } from 'public/tradingview/charting_library';
|
||||
|
||||
import { AbacusOrderStatus } from '@/constants/abacus';
|
||||
import { DEFAULT_RESOLUTION, RESOLUTION_CHART_CONFIGS } from '@/constants/candles';
|
||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||
import { type OrderType, ORDER_TYPE_STRINGS } from '@/constants/trade';
|
||||
import type { TvWidget } from '@/constants/tvchart';
|
||||
|
||||
import { useStringGetter } from '@/hooks';
|
||||
import { useTradingView, useTradingViewTheme } from '@/hooks/tradingView';
|
||||
import {
|
||||
useChartLines,
|
||||
useChartMarketAndResolution,
|
||||
useTradingView,
|
||||
useTradingViewTheme,
|
||||
} from '@/hooks/tradingView';
|
||||
|
||||
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
|
||||
|
||||
import { getCurrentMarketOrders } from '@/state/accountSelectors';
|
||||
import { getAppTheme, getAppColorMode } from '@/state/configsSelectors';
|
||||
import { setTvChartResolution } from '@/state/perpetuals';
|
||||
import { getCurrentMarketId, getSelectedResolutionForMarket } from '@/state/perpetualsSelectors';
|
||||
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { MustBigNumber } from '@/lib/numbers';
|
||||
import { getOrderLineColors } from '@/lib/tradingView/utils';
|
||||
|
||||
type TvWidget = IChartingLibraryWidget & { _id?: string; _ready?: boolean };
|
||||
|
||||
let orderLines: Record<string, IOrderLineAdapter> = {};
|
||||
|
||||
export const TvChart = () => {
|
||||
const dispatch = useDispatch();
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const [isChartReady, setIsChartReady] = useState(false);
|
||||
const [showOrderLines, setShowOrderLines] = useState(false);
|
||||
|
||||
const displayButtonRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
const appTheme = useSelector(getAppTheme);
|
||||
const appColorMode = useSelector(getAppColorMode);
|
||||
|
||||
const currentMarketId: string = useSelector(getCurrentMarketId) || DEFAULT_MARKETID;
|
||||
const currentMarketOrders = useSelector(getCurrentMarketOrders, shallowEqual);
|
||||
|
||||
const selectedResolution: string =
|
||||
useSelector(getSelectedResolutionForMarket(currentMarketId)) || DEFAULT_RESOLUTION;
|
||||
|
||||
const tvWidgetRef = useRef<TvWidget | null>(null);
|
||||
const tvWidget = tvWidgetRef.current;
|
||||
const isWidgetReady = tvWidget?._ready;
|
||||
const chart = isWidgetReady ? tvWidget?.chart() : undefined;
|
||||
const chartResolution = chart?.resolution?.();
|
||||
|
||||
const displayButtonRef = useRef<HTMLElement | null>(null);
|
||||
const displayButton = displayButtonRef.current;
|
||||
|
||||
const { savedResolution } = useTradingView({ tvWidgetRef, displayButtonRef, setIsChartReady });
|
||||
useTradingViewTheme({ tvWidget, isWidgetReady, orderLines });
|
||||
|
||||
const setVisibleRangeForResolution = ({ resolution }: { resolution: ResolutionString }) => {
|
||||
// Different resolutions have different timeframes to display data efficiently.
|
||||
const { defaultRange } = RESOLUTION_CHART_CONFIGS[resolution];
|
||||
|
||||
// from/to values converted to epoch seconds
|
||||
const newRange = {
|
||||
from: (Date.now() - defaultRange) / 1000,
|
||||
to: Date.now() / 1000,
|
||||
};
|
||||
|
||||
tvWidget?.activeChart().setVisibleRange(newRange, { percentRightMargin: 10 });
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Hook to handle changing chart resolution
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (chartResolution) {
|
||||
if (chartResolution !== selectedResolution) {
|
||||
dispatch(setTvChartResolution({ marketId: currentMarketId, resolution: chartResolution }));
|
||||
}
|
||||
|
||||
setVisibleRangeForResolution({ resolution: chartResolution });
|
||||
}
|
||||
}, [chartResolution]);
|
||||
|
||||
/**
|
||||
* @description Hook to handle changing markets
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (currentMarketId && isWidgetReady) {
|
||||
const resolution = savedResolution || selectedResolution;
|
||||
tvWidget?.setSymbol(currentMarketId, resolution as ResolutionString, () => {});
|
||||
}
|
||||
}, [currentMarketId, isWidgetReady]);
|
||||
|
||||
/**
|
||||
* @description Hook to handle order line toggle state
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (isChartReady && displayButtonRef && displayButtonRef.current) {
|
||||
displayButtonRef.current.onclick = () => {
|
||||
const newShowOrderLinesState = !showOrderLines;
|
||||
if (newShowOrderLinesState) {
|
||||
displayButtonRef.current?.classList?.add('order-lines-active');
|
||||
} else {
|
||||
displayButtonRef.current?.classList?.remove('order-lines-active');
|
||||
}
|
||||
setShowOrderLines(newShowOrderLinesState);
|
||||
};
|
||||
}
|
||||
}, [isChartReady, showOrderLines]);
|
||||
|
||||
/**
|
||||
* @description Hook to handle drawing order lines
|
||||
*/
|
||||
useEffect(() => {
|
||||
if (tvWidget && isChartReady) {
|
||||
tvWidget.onChartReady(() => {
|
||||
tvWidget.chart().dataReady(() => {
|
||||
if (showOrderLines) {
|
||||
drawOrderLines();
|
||||
} else {
|
||||
deleteOrderLines();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [isChartReady, showOrderLines, currentMarketOrders, currentMarketId]);
|
||||
|
||||
const drawOrderLines = () => {
|
||||
if (!currentMarketOrders) return;
|
||||
currentMarketOrders.forEach(
|
||||
({
|
||||
id,
|
||||
type,
|
||||
status,
|
||||
side,
|
||||
cancelReason,
|
||||
remainingSize,
|
||||
size,
|
||||
triggerPrice,
|
||||
price,
|
||||
trailingPercent,
|
||||
}) => {
|
||||
const key = `${side.rawValue}-${id}`;
|
||||
const quantity = (remainingSize ?? size).toString();
|
||||
|
||||
const orderType = type.rawValue as OrderType;
|
||||
const orderLabel = stringGetter({
|
||||
key: ORDER_TYPE_STRINGS[orderType].orderTypeKey,
|
||||
});
|
||||
const orderString = trailingPercent ? `${orderLabel} ${trailingPercent}%` : orderLabel;
|
||||
|
||||
const shouldShow =
|
||||
!cancelReason &&
|
||||
(status === AbacusOrderStatus.open || status === AbacusOrderStatus.untriggered);
|
||||
|
||||
const maybeOrderLine = key in orderLines ? orderLines[key] : null;
|
||||
if (maybeOrderLine) {
|
||||
if (!shouldShow) {
|
||||
maybeOrderLine.remove();
|
||||
delete orderLines[key];
|
||||
return;
|
||||
} else if (maybeOrderLine.getQuantity() !== quantity) {
|
||||
maybeOrderLine.setQuantity(quantity);
|
||||
return;
|
||||
}
|
||||
} else if (shouldShow) {
|
||||
const { orderColor, borderColor, backgroundColor, textColor, textButtonColor } =
|
||||
getOrderLineColors({ side: side.rawValue, appTheme, appColorMode });
|
||||
|
||||
const orderLine = tvWidget
|
||||
?.chart()
|
||||
.createOrderLine({ disableUndo: false })
|
||||
.setPrice(MustBigNumber(triggerPrice ?? price).toNumber())
|
||||
.setQuantity(quantity)
|
||||
.setText(orderString)
|
||||
.setLineColor(orderColor)
|
||||
.setQuantityBackgroundColor(orderColor)
|
||||
.setQuantityBorderColor(borderColor)
|
||||
.setBodyBackgroundColor(backgroundColor)
|
||||
.setBodyBorderColor(borderColor)
|
||||
.setBodyTextColor(textColor)
|
||||
.setQuantityTextColor(textButtonColor);
|
||||
|
||||
if (orderLine) {
|
||||
orderLines[key] = orderLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const deleteOrderLines = () => {
|
||||
Object.values(orderLines).forEach((line) => {
|
||||
line.remove();
|
||||
});
|
||||
orderLines = {};
|
||||
};
|
||||
useChartMarketAndResolution({
|
||||
tvWidget,
|
||||
isWidgetReady,
|
||||
savedResolution: savedResolution as ResolutionString | undefined,
|
||||
});
|
||||
const { chartLines } = useChartLines({ tvWidget, displayButton, isChartReady });
|
||||
useTradingViewTheme({ tvWidget, isWidgetReady, chartLines });
|
||||
|
||||
return (
|
||||
<Styled.PriceChart isChartReady={isChartReady}>
|
||||
|
||||
@ -13,8 +13,6 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
|
||||
const stringGetter = useStringGetter();
|
||||
const { isMobile } = useBreakpoints();
|
||||
|
||||
const closeDialog = () => setIsOpen?.(false);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen
|
||||
@ -22,7 +20,7 @@ export const DepositDialog = ({ setIsOpen }: ElementProps) => {
|
||||
title={stringGetter({ key: STRING_KEYS.DEPOSIT })}
|
||||
placement={isMobile ? DialogPlacement.FullScreen : DialogPlacement.Default}
|
||||
>
|
||||
<DepositDialogContent onDeposit={closeDialog} />
|
||||
<DepositDialogContent />
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
|
||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { TransferInputField, TransferType } from '@/constants/abacus';
|
||||
import { AnalyticsEvent } from '@/constants/analytics';
|
||||
import { isMainnet } from '@/constants/networks';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
@ -9,6 +10,7 @@ import { DepositForm } from '@/views/forms/AccountManagementForms/DepositForm';
|
||||
import { TestnetDepositForm } from '@/views/forms/AccountManagementForms/TestnetDepositForm';
|
||||
|
||||
import abacusStateManager from '@/lib/abacus';
|
||||
import { track } from '@/lib/analytics';
|
||||
|
||||
type ElementProps = {
|
||||
onDeposit?: () => void;
|
||||
@ -34,9 +36,19 @@ export const DepositDialogContent = ({ onDeposit }: ElementProps) => {
|
||||
return (
|
||||
<Styled.Content>
|
||||
{isMainnet || !showFaucet ? (
|
||||
<DepositForm onDeposit={onDeposit} />
|
||||
<DepositForm
|
||||
onDeposit={(event) => {
|
||||
track(AnalyticsEvent.TransferDeposit, event);
|
||||
onDeposit?.();
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<TestnetDepositForm onDeposit={onDeposit} />
|
||||
<TestnetDepositForm
|
||||
onDeposit={() => {
|
||||
track(AnalyticsEvent.TransferFaucet);
|
||||
onDeposit?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!isMainnet && (
|
||||
<Styled.TextToggle onClick={() => setShowFaucet(!showFaucet)}>
|
||||
|
||||
@ -32,7 +32,15 @@ export const ExternalNavKeplrDialog = ({ setIsOpen }: ElementProps) => {
|
||||
openDialog({
|
||||
type: DialogTypes.ExternalLink,
|
||||
dialogProps: {
|
||||
buttonText: stringGetter({ key: STRING_KEYS.CONTINUE }),
|
||||
link: keplrDashboard,
|
||||
title: stringGetter({ key: STRING_KEYS.LEAVING_WEBSITE_STAKING_GOVERNANCE }),
|
||||
slotContent: stringGetter({
|
||||
key: STRING_KEYS.STAKE_WITH_KEPLR_AND_LEAVING_DESCRIPTION,
|
||||
params: {
|
||||
CTA: stringGetter({ key: STRING_KEYS.CONTINUE }),
|
||||
},
|
||||
}),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@ -1,62 +1,68 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||
import { AES } from 'crypto-js';
|
||||
|
||||
import { AlertType } from '@/constants/alerts';
|
||||
import { ButtonSize } from '@/constants/buttons';
|
||||
import { STRING_KEYS } from '@/constants/localization';
|
||||
import { useAccounts, useStringGetter } from '@/hooks';
|
||||
import { layoutMixins } from '@/styles/layoutMixins';
|
||||
|
||||
import { AlertMessage } from '@/components/AlertMessage';
|
||||
import { Dialog } from '@/components/Dialog';
|
||||
import { Icon, IconName } from '@/components/Icon';
|
||||
import { ToggleButton } from '@/components/ToggleButton';
|
||||
import { QrCode } from '@/components/QrCode';
|
||||
import { log } from '@/lib/telemetry';
|
||||
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;
|
||||
}) => {
|
||||
const stringGetter = useStringGetter();
|
||||
|
||||
const appLink = 'https://apps.apple.com/app/id6475599596';
|
||||
|
||||
return (
|
||||
<Styled.QrCodeContainer isShowing={true}>
|
||||
<QrCode hasLogo size={432} value={appLink} />
|
||||
<span>{stringGetter({ key: STRING_KEYS.CLICK_TO_SHOW })}</span>
|
||||
<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 = (
|
||||
<>
|
||||
<p>
|
||||
{stringGetter({
|
||||
key: STRING_KEYS.WHILE_ONBOARDING,
|
||||
params: {
|
||||
SYNC_WITH_DESKTOP: (
|
||||
<strong>{stringGetter({ key: STRING_KEYS.SYNC_WITH_DESKTOP })}</strong>
|
||||
),
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
<MobileQrCode
|
||||
/>
|
||||
</>
|
||||
);
|
||||
<MobileQrCode url={mobileAppUrl!} />
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog isOpen setIsOpen={setIsOpen} title={'Download mobile app'}>
|
||||
<Dialog isOpen setIsOpen={setIsOpen} title={
|
||||
stringGetter({ key: STRING_KEYS.DOWNLOAD_MOBILE_APP })
|
||||
}>
|
||||
<Styled.Content>{content}</Styled.Content>
|
||||
</Dialog>
|
||||
);
|
||||
|
||||
@ -2,7 +2,7 @@ import { useState } from 'react';
|
||||
import styled, { AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { ButtonAction } from '@/constants/buttons';
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
import { AppRoute, BASE_ROUTE } 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={`/#${AppRoute.Terms}`}>
|
||||
<Styled.Link href={`${BASE_ROUTE}${AppRoute.Terms}`}>
|
||||
{stringGetter({ key: STRING_KEYS.TERMS_OF_USE })}
|
||||
</Styled.Link>
|
||||
),
|
||||
|
||||
@ -105,8 +105,8 @@ export const OnboardingDialog = ({ setIsOpen }: ElementProps) => {
|
||||
<Styled.Content>
|
||||
{isMainnet ? (
|
||||
<DepositForm
|
||||
onDeposit={() => {
|
||||
track(AnalyticsEvent.TransferDeposit);
|
||||
onDeposit={(event) => {
|
||||
track(AnalyticsEvent.TransferDeposit, event);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@ -2,7 +2,7 @@ import styled, { type AnyStyledComponent } from 'styled-components';
|
||||
|
||||
import { useAccounts, useStringGetter } from '@/hooks';
|
||||
|
||||
import { AppRoute } from '@/constants/routes';
|
||||
import { AppRoute, BASE_ROUTE } 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={`/#${AppRoute.Terms}`}>
|
||||
<Styled.Link href={`${BASE_ROUTE}${AppRoute.Terms}`}>
|
||||
{stringGetter({ key: STRING_KEYS.TERMS_OF_USE })}
|
||||
</Styled.Link>
|
||||
),
|
||||
PRIVACY_POLICY_LINK: (
|
||||
<Styled.Link href={`/#${AppRoute.Privacy}`}>
|
||||
<Styled.Link href={`${BASE_ROUTE}${AppRoute.Privacy}`}>
|
||||
{stringGetter({ key: STRING_KEYS.PRIVACY_POLICY })}
|
||||
</Styled.Link>
|
||||
),
|
||||
|
||||