Compare commits

..
Author SHA1 Message Date
Bill He 54d7b070d8 bump abacus 2024-01-08 10:53:23 -08:00
Bill He fcf50f1361 Add Kava support for onboarding, and add token symbol to token select 2024-01-05 15:31:22 -08:00
104 changed files with 1341 additions and 3425 deletions
+9 -18
View File
@@ -5,35 +5,29 @@ import styled from 'styled-components';
import { store } from '@/state/_store';
import { GlobalStyle } from '@/styles/globalStyle';
import { SelectMenu, SelectItem } from '@/components/SelectMenu';
import { AppThemeProvider } from '@/hooks/useAppTheme';
import { AppTheme, setAppTheme } from '@/state/configs';
import { setLocaleLoaded } from '@/state/localization';
import '@/index.css';
import './ladle.css';
export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [theme, setTheme] = useState(AppTheme.Classic);
const [theme, setTheme] = useState('Default theme');
useEffect(() => {
store.dispatch(setAppTheme(theme));
switch (theme) {
case AppTheme.Dark: {
case 'Dark theme': {
document?.documentElement?.classList.remove('theme-light');
document?.documentElement?.classList.add('theme-dark');
break;
}
case AppTheme.Light: {
case 'Light theme': {
document?.documentElement?.classList.remove('theme-dark');
document?.documentElement?.classList.add('theme-light');
break;
}
case AppTheme.Classic: {
default: {
document?.documentElement?.classList.remove('theme-dark', 'theme-light');
break;
}
@@ -54,15 +48,15 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
>
{[
{
value: AppTheme.Classic,
value: 'Default theme',
label: 'Default theme',
},
{
value: AppTheme.Dark,
value: 'Dark theme',
label: 'Dark theme',
},
{
value: AppTheme.Light,
value: 'Light theme',
label: 'Light theme',
},
].map(({ value, label }) => (
@@ -75,11 +69,8 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
</SelectMenu>
</StoryHeader>
<hr />
<AppThemeProvider>
<GlobalStyle />
<StoryContent>{children}</StoryContent>
</AppThemeProvider>
</Provider>
<StoryContent>{children}</StoryContent>
</Provider>
);
};
+9 -9
View File
@@ -33,15 +33,15 @@
"packageManager": "pnpm@8.6.6",
"dependencies": {
"@0xsquid/sdk": "^1.10.0",
"@cosmjs/amino": "^0.32.1",
"@cosmjs/crypto": "^0.32.1",
"@cosmjs/encoding": "^0.32.1",
"@cosmjs/proto-signing": "^0.32.1",
"@cosmjs/stargate": "^0.32.1",
"@cosmjs/tendermint-rpc": "^0.32.1",
"@dydxprotocol/v4-abacus": "^1.3.2",
"@dydxprotocol/v4-client-js": "^1.0.17",
"@dydxprotocol/v4-localization": "^1.1.17",
"@cosmjs/amino": "^0.31.0",
"@cosmjs/crypto": "^0.31.0",
"@cosmjs/encoding": "^0.31.0",
"@cosmjs/proto-signing": "^0.31.0",
"@cosmjs/stargate": "^0.31.0",
"@cosmjs/tendermint-rpc": "^0.31.0",
"@dydxprotocol/v4-abacus": "^1.1.33",
"@dydxprotocol/v4-client-js": "^1.0.11",
"@dydxprotocol/v4-localization": "^1.1.6",
"@ethersproject/providers": "^5.7.2",
"@js-joda/core": "^5.5.3",
"@radix-ui/react-accordion": "^1.1.2",
+57 -164
View File
@@ -9,32 +9,32 @@ dependencies:
specifier: ^1.10.0
version: 1.10.0
'@cosmjs/amino':
specifier: ^0.32.1
version: 0.32.2
specifier: ^0.31.0
version: 0.31.0
'@cosmjs/crypto':
specifier: ^0.32.1
version: 0.32.2
specifier: ^0.31.0
version: 0.31.0
'@cosmjs/encoding':
specifier: ^0.32.1
version: 0.32.2
specifier: ^0.31.0
version: 0.31.0
'@cosmjs/proto-signing':
specifier: ^0.32.1
version: 0.32.2
specifier: ^0.31.0
version: 0.31.0
'@cosmjs/stargate':
specifier: ^0.32.1
version: 0.32.2
specifier: ^0.31.0
version: 0.31.0
'@cosmjs/tendermint-rpc':
specifier: ^0.32.1
version: 0.32.2
specifier: ^0.31.0
version: 0.31.0
'@dydxprotocol/v4-abacus':
specifier: ^1.3.2
version: 1.3.2
specifier: ^1.1.33
version: 1.1.33
'@dydxprotocol/v4-client-js':
specifier: ^1.0.17
version: 1.0.17
specifier: ^1.0.11
version: 1.0.11
'@dydxprotocol/v4-localization':
specifier: ^1.1.17
version: 1.1.17
specifier: ^1.1.6
version: 1.1.6
'@ethersproject/providers':
specifier: ^5.7.2
version: 5.7.2
@@ -688,15 +688,6 @@ packages:
'@cosmjs/utils': 0.31.0
dev: false
/@cosmjs/amino@0.32.2:
resolution: {integrity: sha512-lcK5RCVm4OfdAooxKcF2+NwaDVVpghOq6o/A40c2mHXDUzUoRZ33VAHjVJ9Me6vOFxshrw/XEFn1f4KObntjYA==}
dependencies:
'@cosmjs/crypto': 0.32.2
'@cosmjs/encoding': 0.32.2
'@cosmjs/math': 0.32.2
'@cosmjs/utils': 0.32.2
dev: false
/@cosmjs/cosmwasm-stargate@0.30.1:
resolution: {integrity: sha512-W/6SLUCJAJGBN+sJLXouLZikVgmqDd9LCdlMzQaxczcCHTWeJAmRvOiZGSZaSy3shw/JN1qc6g6PKpvTVgj10A==}
dependencies:
@@ -750,19 +741,7 @@ packages:
'@cosmjs/encoding': 0.31.0
'@cosmjs/math': 0.31.0
'@cosmjs/utils': 0.31.0
'@noble/hashes': 1.3.3
bn.js: 5.2.1
elliptic: 6.5.4
libsodium-wrappers-sumo: 0.7.11
dev: false
/@cosmjs/crypto@0.32.2:
resolution: {integrity: sha512-RuxrYKzhrPF9g6NmU7VEq++Hn1vZJjqqJpZ9Tmw9lOYOV8BUsv+j/0BE86kmWi7xVJ7EwxiuxYsKuM8IR18CIA==}
dependencies:
'@cosmjs/encoding': 0.32.2
'@cosmjs/math': 0.32.2
'@cosmjs/utils': 0.32.2
'@noble/hashes': 1.3.3
'@noble/hashes': 1.3.1
bn.js: 5.2.1
elliptic: 6.5.4
libsodium-wrappers-sumo: 0.7.11
@@ -792,8 +771,8 @@ packages:
readonly-date: 1.0.0
dev: false
/@cosmjs/encoding@0.32.2:
resolution: {integrity: sha512-WX7m1wLpA9V/zH0zRcz4EmgZdAv1F44g4dbXOgNj1eXZw1PIGR12p58OEkLN51Ha3S4DKRtCv5CkhK1KHEvQtg==}
/@cosmjs/encoding@0.31.1:
resolution: {integrity: sha512-IuxP6ewwX6vg9sUJ8ocJD92pkerI4lyG8J5ynAM3NaX3q+n+uMoPRSQXNeL9bnlrv01FF1kIm8if/f5F7ZPtkA==}
dependencies:
base64-js: 1.5.1
bech32: 1.1.4
@@ -814,13 +793,6 @@ packages:
xstream: 11.14.0
dev: false
/@cosmjs/json-rpc@0.32.2:
resolution: {integrity: sha512-lan2lOgmz4yVE/HR8eCOSiII/1OudIulk8836koyIDCsPEpt6eKBuctnAD168vABGArKccLAo7Mr2gy9nrKrOQ==}
dependencies:
'@cosmjs/stream': 0.32.2
xstream: 11.14.0
dev: false
/@cosmjs/launchpad@0.27.1:
resolution: {integrity: sha512-DcFwGD/z5PK8CzO2sojDxa+Be9EIEtRZb2YawgVnw2Ht/p5FlNv+OVo8qlishpBdalXEN7FvQ1dVeDFEe9TuJw==}
dependencies:
@@ -853,8 +825,8 @@ packages:
bn.js: 5.2.1
dev: false
/@cosmjs/math@0.32.2:
resolution: {integrity: sha512-b8+ruAAY8aKtVKWSft2IvtCVCUH1LigIlf9ALIiY8n9jtM4kMASiaRbQ/27etnSAInV88IaezKK9rQZrtxTjcw==}
/@cosmjs/math@0.31.1:
resolution: {integrity: sha512-kiuHV6m6DSB8/4UV1qpFhlc4ul8SgLXTGRlYkYiIIP4l0YNeJ+OpPYaOlEgx4Unk2mW3/O2FWYj7Jc93+BWXng==}
dependencies:
bn.js: 5.2.1
dev: false
@@ -883,17 +855,6 @@ packages:
long: 4.0.0
dev: false
/@cosmjs/proto-signing@0.32.2:
resolution: {integrity: sha512-UV4WwkE3W3G3s7wwU9rizNcUEz2g0W8jQZS5J6/3fiN0mRPwtPKQ6EinPN9ASqcAJ7/VQH4/9EPOw7d6XQGnqw==}
dependencies:
'@cosmjs/amino': 0.32.2
'@cosmjs/crypto': 0.32.2
'@cosmjs/encoding': 0.32.2
'@cosmjs/math': 0.32.2
'@cosmjs/utils': 0.32.2
cosmjs-types: 0.9.0
dev: false
/@cosmjs/socket@0.30.1:
resolution: {integrity: sha512-r6MpDL+9N+qOS/D5VaxnPaMJ3flwQ36G+vPvYJsXArj93BjgyFB7BwWwXCQDzZ+23cfChPUfhbINOenr8N2Kow==}
dependencies:
@@ -918,18 +879,6 @@ packages:
- utf-8-validate
dev: false
/@cosmjs/socket@0.32.2:
resolution: {integrity: sha512-Qc8jaw4uSBJm09UwPgkqe3g9TBFx4ZR9HkXpwT6Z9I+6kbLerXPR0Gy3NSJFSUgxIfTpO8O1yqoWAyf0Ay17Mw==}
dependencies:
'@cosmjs/stream': 0.32.2
isomorphic-ws: 4.0.1(ws@7.5.9)
ws: 7.5.9
xstream: 11.14.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
dev: false
/@cosmjs/stargate@0.30.1:
resolution: {integrity: sha512-RdbYKZCGOH8gWebO7r6WvNnQMxHrNXInY/gPHPzMjbQF6UatA6fNM2G2tdgS5j5u7FTqlCI10stNXrknaNdzog==}
dependencies:
@@ -972,25 +921,6 @@ packages:
- utf-8-validate
dev: false
/@cosmjs/stargate@0.32.2:
resolution: {integrity: sha512-AsJa29fT7Jd4xt9Ai+HMqhyj7UQu7fyYKdXj/8+/9PD74xe6lZSYhQPcitUmMLJ1ckKPgXSk5Dd2LbsQT0IhZg==}
dependencies:
'@confio/ics23': 0.6.8
'@cosmjs/amino': 0.32.2
'@cosmjs/encoding': 0.32.2
'@cosmjs/math': 0.32.2
'@cosmjs/proto-signing': 0.32.2
'@cosmjs/stream': 0.32.2
'@cosmjs/tendermint-rpc': 0.32.2
'@cosmjs/utils': 0.32.2
cosmjs-types: 0.9.0
xstream: 11.14.0
transitivePeerDependencies:
- bufferutil
- debug
- utf-8-validate
dev: false
/@cosmjs/stream@0.30.1:
resolution: {integrity: sha512-Fg0pWz1zXQdoxQZpdHRMGvUH5RqS6tPv+j9Eh7Q953UjMlrwZVo0YFLC8OTf/HKVf10E4i0u6aM8D69Q6cNkgQ==}
dependencies:
@@ -1003,12 +933,6 @@ packages:
xstream: 11.14.0
dev: false
/@cosmjs/stream@0.32.2:
resolution: {integrity: sha512-gpCufLfHAD8Zp1ZKge7AHbDf4RA0TZp66wZY6JaQR5bSiEF2Drjtp4mwXZPGejtaUMnaAgff3LrUzPJfKYdQwg==}
dependencies:
xstream: 11.14.0
dev: false
/@cosmjs/tendermint-rpc@0.30.1:
resolution: {integrity: sha512-Z3nCwhXSbPZJ++v85zHObeUggrEHVfm1u18ZRwXxFE9ZMl5mXTybnwYhczuYOl7KRskgwlB+rID0WYACxj4wdQ==}
dependencies:
@@ -1047,25 +971,6 @@ packages:
- utf-8-validate
dev: false
/@cosmjs/tendermint-rpc@0.32.2:
resolution: {integrity: sha512-DXyJHDmcAfCix4H/7/dKR0UMdshP01KxJOXHdHxBCbLIpck94BsWD3B2ZTXwfA6sv98so9wOzhp7qGQa5malxg==}
dependencies:
'@cosmjs/crypto': 0.32.2
'@cosmjs/encoding': 0.32.2
'@cosmjs/json-rpc': 0.32.2
'@cosmjs/math': 0.32.2
'@cosmjs/socket': 0.32.2
'@cosmjs/stream': 0.32.2
'@cosmjs/utils': 0.32.2
axios: 1.6.5
readonly-date: 1.0.0
xstream: 11.14.0
transitivePeerDependencies:
- bufferutil
- debug
- utf-8-validate
dev: false
/@cosmjs/utils@0.27.1:
resolution: {integrity: sha512-VG7QPDiMUzVPxRdJahDV8PXxVdnuAHiIuG56hldV4yPnOz/si/DLNd7VAUUA5923b6jS1Hhev0Hr6AhEkcxBMg==}
dev: false
@@ -1078,53 +983,52 @@ packages:
resolution: {integrity: sha512-nNcycZWUYLNJlrIXgpcgVRqdl6BXjF4YlXdxobQWpW9Tikk61bEGeAFhDYtC0PwHlokCNw0KxWiHGJL4nL7Q5A==}
dev: false
/@cosmjs/utils@0.32.2:
resolution: {integrity: sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q==}
/@cosmjs/utils@0.31.1:
resolution: {integrity: sha512-n4Se1wu4GnKwztQHNFfJvUeWcpvx3o8cWhSbNs9JQShEuB3nv3R5lqFBtDCgHZF/emFQAP+ZjF8bTfCs9UBGhA==}
dev: false
/@cush/relative@1.0.0:
resolution: {integrity: sha512-RpfLEtTlyIxeNPGKcokS+p3BZII/Q3bYxryFRglh5H3A3T8q9fsLYm72VYAMEOOIBLEa8o93kFLiBDUWKrwXZA==}
dev: true
/@dydxprotocol/v4-abacus@1.3.2:
resolution: {integrity: sha512-zo0IHjGMlJRKOYDgNqNFQ9GtBJKJP4+Y9YY7V0X3Wt61ppKAYzodaYQhc9V/RYchcZTtS/xkicLug444YrvehQ==}
/@dydxprotocol/v4-abacus@1.1.33:
resolution: {integrity: sha512-VlIOaOPR4ImSWGmKK3+M4VHdMDdTk7qI77Iopppq11qU7f8wdwk4OeedTSuAMxezKez2nCP3J/x3Da+FtrKRqg==}
dev: false
/@dydxprotocol/v4-client-js@1.0.17:
resolution: {integrity: sha512-PbTKbzcS7VapuFRofkirUxkF9ThNS2tWYyr0asFOMUfebsQWMvTC1sYC/s1NZIiBeq3cFz9vKdZFbfsKi7Z1bw==}
/@dydxprotocol/v4-client-js@1.0.11:
resolution: {integrity: sha512-rJNFGTO+HU1GQppXeRLKXNk4HWx/h+DjU2sUCieEv5mnRIMQpLw8dH/L+Uyhxx2oovccY1MaKB4Fr9IngdrF0A==}
dependencies:
'@cosmjs/amino': 0.32.2
'@cosmjs/encoding': 0.32.2
'@cosmjs/math': 0.32.2
'@cosmjs/proto-signing': 0.32.2
'@cosmjs/stargate': 0.32.2
'@cosmjs/tendermint-rpc': 0.32.2
'@cosmjs/utils': 0.32.2
'@dydxprotocol/v4-proto': 3.0.0-dev.0
'@cosmjs/amino': 0.30.1
'@cosmjs/encoding': 0.31.1
'@cosmjs/math': 0.31.1
'@cosmjs/proto-signing': 0.30.1
'@cosmjs/stargate': 0.30.1
'@cosmjs/tendermint-rpc': 0.30.1
'@cosmjs/utils': 0.31.1
'@dydxprotocol/v4-proto': 0.4.1
'@osmonauts/lcd': 0.6.0
'@scure/bip32': 1.3.2
'@scure/bip39': 1.2.1
axios: 1.1.3
bech32: 1.1.4
bignumber.js: 9.1.1
cosmjs-types: 0.9.0
ethereum-cryptography: 2.1.2
ethers: 6.6.1
long: 4.0.0
protobufjs: 6.11.4
ws: 8.15.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
ws: 8.15.0
transitivePeerDependencies:
- bufferutil
- debug
- utf-8-validate
dev: false
/@dydxprotocol/v4-localization@1.1.17:
resolution: {integrity: sha512-kal1LrcihLMEv5YxaA/hd6Zl10Mp3x6jicoXDcXvtyNdrczAl3YapyI2nmeifRAPvfueOaY3W/sKkm2BiSKSsA==}
/@dydxprotocol/v4-localization@1.1.6:
resolution: {integrity: sha512-Bon6NSRU4/FqneAbnP2G28EAPr0hp4LhvAayX61o0O1PGkxnLzAHkXeFppdM0Zn0fOcp1S1MJ+gvz138ZDephQ==}
dev: false
/@dydxprotocol/v4-proto@3.0.0-dev.0:
resolution: {integrity: sha512-hT6F/AgaTqv8Bwo7twpIhmjXvJE/Fx+3mmHHTuIXMeL6OhVtlOpcEQyHvBEAdh1VrNq/S7qWvdQD0fvC/UgKyA==}
/@dydxprotocol/v4-proto@0.4.1:
resolution: {integrity: sha512-2pBkokufWcxfIpiXE6QuQOBXUe154OTp6ojCIdOPW9TFLxzchoygxXnb5dL6rAQVNj9Qbg+hhXBllutt4m99dw==}
dependencies:
protobufjs: 6.11.4
dev: false
@@ -7476,7 +7380,7 @@ packages:
/axios@0.21.4:
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
dependencies:
follow-redirects: 1.15.5
follow-redirects: 1.15.2
transitivePeerDependencies:
- debug
dev: false
@@ -7493,17 +7397,7 @@ packages:
/axios@1.1.3:
resolution: {integrity: sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==}
dependencies:
follow-redirects: 1.15.5
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
- debug
dev: false
/axios@1.6.5:
resolution: {integrity: sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==}
dependencies:
follow-redirects: 1.15.5
follow-redirects: 1.15.2
form-data: 4.0.0
proxy-from-env: 1.1.0
transitivePeerDependencies:
@@ -8167,10 +8061,6 @@ packages:
protobufjs: 6.11.4
dev: false
/cosmjs-types@0.9.0:
resolution: {integrity: sha512-MN/yUe6mkJwHnCFfsNPeCfXVhyxHYW6c/xDUzrSbBycYzw++XvWDMJArXp2pLdgD6FQ8DW79vkPjeNKVrXaHeQ==}
dev: false
/cosmos-directory-client@0.0.6:
resolution: {integrity: sha512-WIdaQ8uW1vIbYvNnAVunkC6yxTrneJC7VQ5UUQ0kuw8b0C0A39KTIpoQHCfc8tV7o9vF4niwRhdXEdfAgQEsQQ==}
dependencies:
@@ -9536,16 +9426,6 @@ packages:
optional: true
dev: false
/follow-redirects@1.15.5:
resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==}
engines: {node: '>=4.0'}
peerDependencies:
debug: '*'
peerDependenciesMeta:
debug:
optional: true
dev: false
/for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
dependencies:
@@ -14801,6 +14681,19 @@ packages:
optional: true
dev: false
/ws@8.15.0:
resolution: {integrity: sha512-H/Z3H55mrcrgjFwI+5jKavgXvwQLtfPCUEp6pi35VhoB0pfcHnSoyuTzkBEZpzq49g1193CUEwIvmsjcotenYw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
dev: false
/ws@8.15.1(bufferutil@4.0.8)(utf-8-validate@5.0.10):
resolution: {integrity: sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==}
engines: {node: '>=10.0.0'}
+18 -19
View File
@@ -1,7 +1,7 @@
{
"apps": {
"ios": {
"scheme": "dydx-t-v4"
"scheme": "dydxv4"
}
},
"deployments": {
@@ -39,7 +39,7 @@
"environments": {
"dydxprotocol-dev": {
"name": "v4 Dev",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -113,7 +113,7 @@
},
"dydxprotocol-dev-2": {
"name": "v4 Dev 2",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -186,7 +186,7 @@
},
"dydxprotocol-dev-4": {
"name": "v4 Dev 4",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -215,11 +215,10 @@
}
],
"validators": [
"https://validator.v4dev4.dydx.exchange"
"http://validator.v4dev4.dydx.exchange"
],
"0xsquid": "https://testnet.api.0xsquid.com",
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
"faucet": "https://faucet.v4dev4.dydx.exchange"
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
},
"links": {
"tos": "https://dydx.exchange/v4-terms",
@@ -260,7 +259,7 @@
},
"dydxprotocol-dev-5": {
"name": "v4 Dev 5",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -333,7 +332,7 @@
},
"dydxprotocol-staging": {
"name": "v4 Staging",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -408,7 +407,7 @@
},
"dydxprotocol-staging-forced-update": {
"name": "v4 Staging Forced Update",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -474,14 +473,14 @@
"apps": {
"ios": {
"minimalVersion": "1.0",
"build": 40000,
"build":40000,
"url": "https://apps.apple.com/app/dydx/id1564787350"
}
}
},
"dydxprotocol-staging-west": {
"name": "v4 Staging West",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydxprotocol-testnet",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -556,7 +555,7 @@
},
"dydxprotocol-testnet": {
"name": "v4 Public Testnet",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -635,7 +634,7 @@
},
"dydxprotocol-testnet-dydx": {
"name": "v4 Public Testnet/dYdX",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -711,7 +710,7 @@
},
"dydxprotocol-testnet-nodefleet": {
"name": "v4 Public Testnet/nodefleet",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -787,7 +786,7 @@
},
"dydxprotocol-testnet-kingnodes": {
"name": "v4 Public Testnet/KingNodes",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -863,7 +862,7 @@
},
"dydxprotocol-testnet-liquify": {
"name": "v4 Public Testnet/Liquify",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -939,7 +938,7 @@
},
"dydxprotocol-testnet-polkachu": {
"name": "v4 Public Testnet/Polkahcu",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
@@ -1006,7 +1005,7 @@
},
"dydxprotocol-testnet-bware": {
"name": "v4 Public Testnet/BWare",
"ethereumChainId": "11155111",
"ethereumChainId": "5",
"dydxChainId": "dydx-testnet-4",
"chainName": "dYdX Chain",
"chainLogo": "/dydx-chain.png",
-230
View File
@@ -1,230 +0,0 @@
<svg width="254" height="77" viewBox="0 0 254 77" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group 1258">
<g id="Ellipse 9" opacity="0.4" filter="url(#filter0_f_20300_14972)">
<ellipse cx="86.5425" cy="-3.91219" rx="116.542" ry="20.0878" fill="#7774FF"/>
</g>
<g id="Ellipse 10" opacity="0.3" filter="url(#filter1_f_20300_14972)">
<circle cx="74.5734" cy="4.63007" r="1.00507" fill="#9A9AFF"/>
</g>
<g id="Ellipse 12" opacity="0.3" filter="url(#filter2_f_20300_14972)">
<circle cx="82.1105" cy="7.64179" r="1.00507" fill="#9A9AFF"/>
</g>
<g id="Ellipse 22" opacity="0.3" filter="url(#filter3_f_20300_14972)">
<circle cx="116.785" cy="4.63007" r="1.00507" fill="#9A9AFF"/>
</g>
<g id="Ellipse 11" opacity="0.3" filter="url(#filter4_f_20300_14972)">
<circle cx="64.774" cy="8.90488" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 18" opacity="0.3" filter="url(#filter5_f_20300_14972)">
<circle cx="96.4322" cy="3.87363" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 20" opacity="0.3" filter="url(#filter6_f_20300_14972)">
<circle cx="76.0807" cy="14.9361" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 21" opacity="0.3" filter="url(#filter7_f_20300_14972)">
<circle cx="104.725" cy="4.87754" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 23" opacity="0.3" filter="url(#filter8_f_20300_14972)">
<circle cx="114.021" cy="7.13925" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 24" opacity="0.3" filter="url(#filter9_f_20300_14972)">
<circle cx="109.75" cy="15.4361" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 25" opacity="0.3" filter="url(#filter10_f_20300_14972)">
<circle cx="119.801" cy="12.6705" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 27" opacity="0.3" filter="url(#filter11_f_20300_14972)">
<circle cx="94.0885" cy="11.0182" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 30" opacity="0.3" filter="url(#filter12_f_20300_14972)">
<circle cx="154.475" cy="11.6666" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 29" opacity="0.3" filter="url(#filter13_f_20300_14972)">
<circle cx="146.435" cy="8.90488" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 15" opacity="0.3" filter="url(#filter14_f_20300_14972)">
<circle cx="24.0103" cy="4.44785" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 33" opacity="0.3" filter="url(#filter15_f_20300_14972)">
<circle cx="-1.05899" cy="13.6783" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 16" opacity="0.3" filter="url(#filter16_f_20300_14972)">
<circle cx="17.441" cy="16.4908" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 35" opacity="0.3" filter="url(#filter17_f_20300_14972)">
<circle cx="36.0553" cy="6.63535" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 32" opacity="0.3" filter="url(#filter18_f_20300_14972)">
<circle cx="10.8707" cy="14.2994" r="0.502536" fill="#9A9AFF"/>
</g>
<g id="Ellipse 13" opacity="0.3" filter="url(#filter19_f_20300_14972)">
<circle cx="55.7274" cy="9.65615" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 19" opacity="0.3" filter="url(#filter20_f_20300_14972)">
<circle cx="87.3886" cy="20.7108" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 26" opacity="0.3" filter="url(#filter21_f_20300_14972)">
<circle cx="123.82" cy="15.1874" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 36" opacity="0.3" filter="url(#filter22_f_20300_14972)">
<circle cx="134.876" cy="14.4335" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 28" opacity="0.3" filter="url(#filter23_f_20300_14972)">
<circle cx="129.6" cy="6.38662" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 14" opacity="0.3" filter="url(#filter24_f_20300_14972)">
<circle cx="41.6571" cy="14.9335" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 31" opacity="0.3" filter="url(#filter25_f_20300_14972)">
<circle cx="4.21767" cy="17.9491" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 34" opacity="0.3" filter="url(#filter26_f_20300_14972)">
<circle cx="12.2177" cy="6.88662" r="0.753803" fill="#9A9AFF"/>
</g>
<g id="Ellipse 17" opacity="0.3" filter="url(#filter27_f_20300_14972)">
<circle cx="50.2001" cy="3.6249" r="0.753803" fill="#9A9AFF"/>
</g>
</g>
<defs>
<filter id="filter0_f_20300_14972" x="-94" y="-88" width="361.085" height="168.176" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="32" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter1_f_20300_14972" x="72.5633" y="2.61993" width="4.01991" height="4.02186" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter2_f_20300_14972" x="80.6029" y="6.13418" width="3.01484" height="3.01679" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter3_f_20300_14972" x="115.278" y="3.12246" width="3.01484" height="3.01679" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter4_f_20300_14972" x="63.2664" y="7.39727" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter5_f_20300_14972" x="94.9246" y="2.36602" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter6_f_20300_14972" x="74.5731" y="13.4285" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter7_f_20300_14972" x="103.218" y="3.36993" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter8_f_20300_14972" x="112.513" y="5.63165" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter9_f_20300_14972" x="108.242" y="13.9285" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter10_f_20300_14972" x="118.294" y="11.1629" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter11_f_20300_14972" x="92.5809" y="9.51055" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter12_f_20300_14972" x="153.47" y="10.6615" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter13_f_20300_14972" x="144.928" y="7.39727" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter14_f_20300_14972" x="22.5027" y="2.94024" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter15_f_20300_14972" x="-2.56659" y="12.1707" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter16_f_20300_14972" x="15.9334" y="14.9832" width="3.01502" height="3.01405" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter17_f_20300_14972" x="35.0502" y="5.63028" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter18_f_20300_14972" x="9.86563" y="13.2943" width="2.00995" height="2.00898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.251268" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter19_f_20300_14972" x="53.9686" y="7.89727" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter20_f_20300_14972" x="85.6297" y="18.952" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter21_f_20300_14972" x="122.061" y="13.4285" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter22_f_20300_14972" x="133.117" y="12.6746" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter23_f_20300_14972" x="127.842" y="4.62774" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter24_f_20300_14972" x="39.8982" y="13.1746" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter25_f_20300_14972" x="2.4588" y="16.1902" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter26_f_20300_14972" x="10.4588" y="5.12774" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
<filter id="filter27_f_20300_14972" x="48.4412" y="1.86602" width="3.51795" height="3.51795" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="0.502536" result="effect1_foregroundBlur_20300_14972"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 15 KiB

+34 -40
View File
@@ -16,7 +16,6 @@ import {
} from '@/hooks';
import { DydxProvider } from '@/hooks/useDydxClient';
import { AccountsProvider } from '@/hooks/useAccounts';
import { AppThemeProvider } from '@/hooks/useAppTheme';
import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
import { LocaleProvider } from '@/hooks/useLocaleSeparators';
import { NotificationsProvider } from '@/hooks/useNotifications';
@@ -36,7 +35,6 @@ import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
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';
@@ -68,52 +66,49 @@ const Content = () => {
const { chainTokenLabel } = useTokenConfigs();
return (
<>
<GlobalStyle />
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
{isNotTablet && <HeaderDesktop />}
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
{isNotTablet && <HeaderDesktop />}
<Styled.Main>
<Suspense fallback={<LoadingSpace id="main" />}>
<Routes>
<Route path={AppRoute.Trade}>
<Route path=":market" element={<TradePage />} />
<Route path={AppRoute.Trade} element={<TradePage />} />
</Route>
<Styled.Main>
<Suspense fallback={<LoadingSpace id="main" />}>
<Routes>
<Route path={AppRoute.Trade}>
<Route path=":market" element={<TradePage />} />
<Route path={AppRoute.Trade} element={<TradePage />} />
</Route>
<Route path={AppRoute.Markets} element={<MarketsPage />} />
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
{isTablet && (
<>
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
<Route path={AppRoute.Profile} element={<ProfilePage />} />
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
</>
)}
<Route path={AppRoute.Markets} element={<MarketsPage />} />
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
{isTablet && (
<>
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
<Route path={AppRoute.Profile} element={<ProfilePage />} />
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
</>
)}
<Route element={<GuardedMobileRoute />}>
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
</Route>
<Route element={<GuardedMobileRoute />}>
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
</Route>
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
</Routes>
</Suspense>
</Styled.Main>
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
</Routes>
</Suspense>
</Styled.Main>
{isTablet ? <FooterMobile /> : <FooterDesktop />}
{isTablet ? <FooterMobile /> : <FooterDesktop />}
<Styled.NotificationsToastArea />
<Styled.NotificationsToastArea />
<Styled.DialogArea ref={setDialogArea}>
<DialogManager />
</Styled.DialogArea>
<Styled.DialogArea ref={setDialogArea}>
<DialogManager />
</Styled.DialogArea>
<GlobalCommandDialog />
</Styled.Content>
</>
<GlobalCommandDialog />
</Styled.Content>
);
};
@@ -136,7 +131,6 @@ const providers = [
wrapProvider(LocalNotificationsProvider),
wrapProvider(NotificationsProvider),
wrapProvider(DialogAreaProvider),
wrapProvider(AppThemeProvider),
];
const App = () => {
-26
View File
@@ -1,26 +0,0 @@
import type { Story } from '@ladle/react';
import { Accordion as AccordionComponent, AccordionProps } from '@/components/Accordion';
import { StoryWrapper } from '.ladle/components';
export const Accordion: Story<AccordionProps> = (args) => {
return (
<StoryWrapper>
<AccordionComponent {...args} />
</StoryWrapper>
);
};
Accordion.args = {
items: [
{
header: 'Question 1?',
content: 'Answer 1.',
},
{
header: 'Question 2?',
content: 'Answer 2.',
},
],
};
+3 -13
View File
@@ -3,8 +3,6 @@ import styled, { keyframes, type AnyStyledComponent } from 'styled-components';
import { Root, Item, Header, Trigger, Content } from '@radix-ui/react-accordion';
import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
import { PlusIcon } from '@/icons';
export type AccordionItem = {
@@ -12,7 +10,7 @@ export type AccordionItem = {
content: React.ReactNode;
};
export type AccordionProps = {
type AccordionProps = {
items: AccordionItem[];
className?: string;
};
@@ -38,13 +36,6 @@ export const Accordion = ({ items, className }: AccordionProps) => (
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Root = styled(Root)`
--accordion-paddingY: 1rem;
--accordion-paddingX: 1rem;
@media ${breakpoints.notTablet} {
--accordion-paddingX: 1.5rem;
}
> *:not(:last-child) {
border-bottom: var(--border-width) solid var(--border-color);
}
@@ -76,8 +67,7 @@ Styled.Icon = styled.div`
Styled.Trigger = styled(Trigger)`
${layoutMixins.spacedRow}
width: 100%;
padding: var(--accordion-paddingY) var(--accordion-paddingX);
gap: 0.5rem;
padding: 1rem 0.75rem;
color: var(--color-text-1);
text-align: start;
@@ -101,7 +91,7 @@ Styled.Trigger = styled(Trigger)`
Styled.Content = styled(Content)`
overflow: hidden;
margin: 0 var(--accordion-paddingX) var(--accordion-paddingY);
margin: 0 0.75rem 1rem;
color: var(--color-text-0);
-13
View File
@@ -1,13 +0,0 @@
import { forwardRef } from 'react';
type CanvasProps = {
className?: string;
width: number | string;
height: number | string;
};
export const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(
({ className, width, height }, canvasRef) => (
<canvas ref={canvasRef} className={className} width={width} height={height} />
)
);
+2 -10
View File
@@ -5,7 +5,7 @@ import { Checkbox, CheckboxProps } from '@/components/Checkbox';
import { StoryWrapper } from '.ladle/components';
export const CheckboxStory: Story<CheckboxProps> = (args) => {
export const Checkboxes: Story<CheckboxProps> = (args) => {
const [isChecked, setIsChecked] = useState(false);
return (
@@ -21,12 +21,4 @@ export const CheckboxStory: Story<CheckboxProps> = (args) => {
);
};
CheckboxStory.args = {};
CheckboxStory.argTypes = {
disabled: {
options: [true, false],
control: { type: 'select' },
defaultValue: false,
}
}
Checkboxes.args = {};
+4 -20
View File
@@ -1,4 +1,4 @@
import styled, { css, type AnyStyledComponent } from 'styled-components';
import styled, { type AnyStyledComponent } from 'styled-components';
import { Root, Indicator } from '@radix-ui/react-checkbox';
import { CheckIcon } from '@radix-ui/react-icons';
@@ -9,7 +9,6 @@ type ElementProps = {
onCheckedChange: (checked: boolean) => void;
id?: string;
label?: React.ReactNode;
disabled?: boolean;
};
type StyleProps = {
@@ -24,15 +23,14 @@ export const Checkbox: React.FC<CheckboxProps> = ({
onCheckedChange,
id,
label,
disabled
}) => (
<Styled.Container>
<Styled.Root className={className} checked={checked} disabled={disabled} onCheckedChange={onCheckedChange} id={id}>
<Styled.Root className={className} checked={checked} onCheckedChange={onCheckedChange} id={id}>
<Styled.Indicator>
<CheckIcon />
</Styled.Indicator>
</Styled.Root>
{label && <Styled.label disabled={disabled} htmlFor={id}>{label}</Styled.label>}
{label && <label htmlFor={id}>{label}</label>}
</Styled.Container>
);
@@ -50,23 +48,17 @@ Styled.Container = styled.div`
Styled.Root = styled(Root)`
--checkbox-backgroundColor: var(--color-layer-0);
--checkbox-borderColor: var(--color-border);
min-width: 1.25rem;
height: 1.25rem;
border-radius: 0.375rem;
border: var(--border-width) solid var(--checkbox-borderColor);
border: var(--border-width) solid var(--color-border);
background-color: var(--checkbox-backgroundColor);
&[data-state='checked'] {
--checkbox-backgroundColor: var(--color-accent);
}
&[data-disabled] {
cursor: not-allowed;
--checkbox-backgroundColor: var(--color-layer-1);
}
`;
Styled.Indicator = styled(Indicator)`
@@ -76,11 +68,3 @@ Styled.Indicator = styled(Indicator)`
color: var(--color-text-2);
`;
Styled.label = styled.div<{ disabled: boolean; }>`
color: var(--color-text-2);
${({disabled}) => disabled && css`
color: var(--color-text-0);
`}
`;
+1
View File
@@ -11,5 +11,6 @@ export const DetachedScrollableSection = styled.section`
export const AttachedExpandingSection = styled.section`
${layoutMixins.contentSectionAttached}
${layoutMixins.expandingColumnWithHeader}
gap: var(--border-width);
`;
+1 -1
View File
@@ -14,7 +14,7 @@ export const DiffArrowStory: Story<DiffArrowProps> = (args) => (
DiffArrowStory.argTypes = {
direction: {
options: ['left', 'right', 'up', 'down'],
options: ['left', 'right'],
control: { type: 'select' },
defaultValue: 'right',
},
+1 -7
View File
@@ -10,7 +10,7 @@ type ElementProps = {
type StyleProps = {
className?: string;
direction?: 'right' | 'left' | 'up' | 'down';
direction?: 'right' | 'left';
};
export type DiffArrowProps = ElementProps & StyleProps;
@@ -57,11 +57,5 @@ Styled.DiffArrowContainer = styled.span<DiffArrowProps>`
left: css`
transform: scaleX(-1);
`,
up: css`
transform: rotate(-90deg);
`,
down: css`
transform: rotate(90deg);
`
}[direction || 'right'])}
`;
+3 -9
View File
@@ -24,6 +24,7 @@ import {
CoinsIcon,
CommentIcon,
CopyIcon,
CubeIcon,
DepositIcon,
DepthChartIcon,
DiscordIcon,
@@ -44,7 +45,6 @@ import {
MenuIcon,
MigrateIcon,
MintscanIcon,
MoonIcon,
OrderbookIcon,
OrderCanceledIcon,
OrderFilledIcon,
@@ -60,14 +60,12 @@ import {
PriceChartIcon,
PrivacyIcon,
QrIcon,
RewardStarIcon,
SearchIcon,
SendIcon,
ShareIcon,
ShowIcon,
TogglesMenuIcon,
StarIcon,
SunIcon,
TerminalIcon,
TradeIcon,
TransferIcon,
@@ -101,6 +99,7 @@ export enum IconName {
Coins = 'Coins',
Comment = 'Comment',
Copy = 'Copy',
Cube = 'Cube',
Deposit = 'Deposit',
DepthChart = 'DepthChart',
Discord = 'Discord',
@@ -121,7 +120,6 @@ export enum IconName {
Menu = 'Menu',
Migrate = 'Migrate',
Mintscan = 'Mintscan',
Moon = 'Moon',
Onboarding = 'Onboarding',
Orderbook = 'OrderbookIcon',
OrderCanceled = 'OrderCanceled',
@@ -138,13 +136,11 @@ export enum IconName {
PriceChart = 'PriceChart',
Privacy = 'Privacy',
Qr = 'Qr',
RewardStar = 'RewardStar',
Search = 'Search',
Send = 'Send',
Share = 'Share',
Show = 'Show',
Star = 'Star',
Sun = 'Sun',
Terminal = 'Terminal',
TogglesMenu = 'TogglesMenu',
Trade = 'Trade',
@@ -179,6 +175,7 @@ const icons = {
[IconName.Coins]: CoinsIcon,
[IconName.Comment]: CommentIcon,
[IconName.Copy]: CopyIcon,
[IconName.Cube]: CubeIcon,
[IconName.Deposit]: DepositIcon,
[IconName.DepthChart]: DepthChartIcon,
[IconName.Discord]: DiscordIcon,
@@ -199,7 +196,6 @@ const icons = {
[IconName.Menu]: MenuIcon,
[IconName.Migrate]: MigrateIcon,
[IconName.Mintscan]: MintscanIcon,
[IconName.Moon]: MoonIcon,
[IconName.Orderbook]: OrderbookIcon,
[IconName.OrderCanceled]: OrderCanceledIcon,
[IconName.OrderFilled]: OrderFilledIcon,
@@ -215,13 +211,11 @@ const icons = {
[IconName.PriceChart]: PriceChartIcon,
[IconName.Privacy]: PrivacyIcon,
[IconName.Qr]: QrIcon,
[IconName.RewardStar]: RewardStarIcon,
[IconName.Search]: SearchIcon,
[IconName.Send]: SendIcon,
[IconName.Share]: ShareIcon,
[IconName.Show]: ShowIcon,
[IconName.Star]: StarIcon,
[IconName.Sun]: SunIcon,
[IconName.Terminal]: TerminalIcon,
[IconName.TogglesMenu]: TogglesMenuIcon,
[IconName.Trade]: TradeIcon,
+1 -1
View File
@@ -1,11 +1,11 @@
import styled, { type AnyStyledComponent } from 'styled-components';
import { RiskLevels } from '@/constants/abacus';
import { UsageColorFromRiskLevel } from '@/styles/colors';
import { Ring } from '@/components/Ring';
import { abacusHelper } from '@/lib/abacus';
import { UsageColorFromRiskLevel } from '@/lib/styles';
type ElementProps = {
value: number;
+1 -8
View File
@@ -4,10 +4,9 @@ import { Link } from 'react-router-dom';
import { Icon, IconName } from '@/components/Icon';
import { layoutMixins } from '@/styles/layoutMixins';
import { breakpoints } from '@/styles';
type PanelProps = {
slotHeaderContent?: React.ReactNode;
slotHeaderContent?: string;
slotHeader?: React.ReactNode;
slotRight?: React.ReactNode;
children?: React.ReactNode;
@@ -68,12 +67,6 @@ Styled.Panel = styled.section<{ onClick?: () => void }>`
--panel-content-paddingY: var(--panel-paddingY);
--panel-content-paddingX: var(--panel-paddingX);
@media ${breakpoints.notTablet} {
--panel-paddingX: 1.5rem;
--panel-paddingY: 1.25rem;
--panel-content-paddingY: 1rem;
}
${layoutMixins.row}
background-color: var(--color-layer-3);
+1 -1
View File
@@ -47,7 +47,7 @@ Styled.Root = styled(Root)`
--switch-thumb-backgroundColor: var(--color-layer-6);
--switch-active-backgroundColor: var(--color-accent);
--switch-active-thumb-backgroundColor: ${({ theme }) => theme.switchThumbActiveBackground};
--switch-active-thumb-backgroundColor: var(--color-white);
position: relative;
width: var(--switch-width);
+9 -71
View File
@@ -33,20 +33,17 @@ import {
import { useAsyncList } from 'react-stately';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { useBreakpoints } from '@/hooks';
import { MediaQueryKeys } from '@/hooks/useBreakpoints';
import { Checkbox } from '@/components/Checkbox';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { CaretIcon } from '@/icons';
import { STRING_KEYS } from '@/constants/localization';
import { MustBigNumber } from '@/lib/numbers';
import { Icon, IconName } from './Icon';
import { Tag } from './Tag';
import { Button } from './Button';
import { MustBigNumber } from '@/lib/numbers';
export { TableCell } from './Table/TableCell';
export { TableColumnHeader } from './Table/TableColumnHeader';
@@ -68,7 +65,7 @@ export type TableItem<TableRowData> = {
onSelect?: (key: TableRowData) => void;
};
type ColumnDef<TableRowData extends object> = {
export type ColumnDef<TableRowData extends object> = {
columnKey: string;
label: React.ReactNode;
tag?: React.ReactNode;
@@ -83,7 +80,7 @@ type ColumnDef<TableRowData extends object> = {
width?: ColumnSize;
};
export type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = {
type ElementProps<TableRowData extends object | CustomRowConfig, TableRowKey extends Key> = {
label?: string;
columns: ColumnDef<TableRowData>[];
data: TableRowData[];
@@ -95,7 +92,6 @@ export type ElementProps<TableRowData extends object | CustomRowConfig, TableRow
selectionBehavior?: 'replace' | 'toggle';
onRowAction?: (key: TableRowKey, row: TableRowData) => void;
slotEmpty?: React.ReactNode;
initialNumRowsToShow?: number;
// collection: TableCollection<string>;
// children: React.ReactNode;
};
@@ -125,7 +121,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
selectionMode = 'single',
selectionBehavior = 'toggle',
slotEmpty,
initialNumRowsToShow,
// shouldRowRender,
// collection,
@@ -141,8 +136,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
style,
}: ElementProps<TableRowData, TableRowKey> & StyleProps) => {
const [selectedKeys, setSelectedKeys] = useState(new Set<TableRowKey>());
const [numRowsToShow, setNumRowsToShow] = useState(initialNumRowsToShow);
const enableViewMore = numRowsToShow !== undefined;
const currentBreakpoints = useBreakpoints();
const shownColumns = columns.filter(
@@ -216,12 +209,6 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
onRowAction &&
((key: TableRowKey) => onRowAction(key, data.find((row) => getRowKey(row) === key)!))
}
numColumns={shownColumns.length}
onViewMoreClick={
enableViewMore && numRowsToShow < data.length
? () => setNumRowsToShow(data.length)
: undefined
}
// shouldRowRender={shouldRowRender}
hideHeader={hideHeader}
withGradientCardRows={withGradientCardRows}
@@ -246,7 +233,7 @@ export const Table = <TableRowData extends object, TableRowKey extends Key>({
)}
</TableHeader>
<TableBody items={enableViewMore ? list.items.slice(0, numRowsToShow) : list.items}>
<TableBody items={list.items}>
{(item) => (
<Row key={getRowKey(item)}>
{(columnKey) => (
@@ -280,8 +267,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
onRowAction?: (key: TableRowKey) => void;
// shouldRowRender?: (prevRowData: object, currentRowData: object) => boolean;
children: CollectionChildren<TableRowData>;
numColumns: number;
onViewMoreClick?: () => void;
hideHeader?: boolean;
withGradientCardRows?: boolean;
@@ -291,7 +276,7 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
withScrollSnapColumns?: boolean;
withScrollSnapRows?: boolean;
}) => {
const { selectionMode, selectionBehavior, numColumns, onViewMoreClick } = props;
const { selectionMode, selectionBehavior } = props;
const state = useTableState<TableRowData>({
...props,
@@ -352,7 +337,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
<TableBodyRowGroup
withGradientCardRows={props.withGradientCardRows}
withInnerBorders={props.withInnerBorders}
withOuterBorder={props.withOuterBorder}
>
{/* {Array.from(collection.getChildren!(collection.body.key), (row) => */}
{[...collection.body.childNodes].map((row) =>
@@ -398,9 +382,6 @@ const TableRoot = <TableRowData extends object | CustomRowConfig, TableRowKey ex
</TableRow>
)
)}
{onViewMoreClick ? (
<ViewMoreRow colSpan={numColumns} onClick={onViewMoreClick} />
) : undefined}
</TableBodyRowGroup>
</Styled.Table>
);
@@ -434,7 +415,6 @@ const TableBodyRowGroup = ({
children,
withGradientCardRows,
withInnerBorders,
withOuterBorder,
}: { children: React.ReactNode } & StyleProps) => {
const { rowGroupProps } = useTableRowGroup();
@@ -443,7 +423,6 @@ const TableBodyRowGroup = ({
{...rowGroupProps}
withGradientCardRows={withGradientCardRows}
withInnerBorders={withInnerBorders}
withOuterBorder={withOuterBorder}
>
{children}
</Styled.Tbody>
@@ -510,23 +489,6 @@ const TableColumnHeader = <TableRowData extends object>({
);
};
export const ViewMoreRow = ({ colSpan, onClick }: { colSpan: number; onClick: () => void }) => {
const stringGetter = useStringGetter();
return (
<Styled.Tr key="viewmore">
<Styled.Td
colSpan={colSpan}
onMouseDown={(e: MouseEvent) => e.preventDefault()}
onPointerDown={(e: MouseEvent) => e.preventDefault()}
>
<Styled.ViewMoreButton slotRight={<CaretIcon />} onClick={onClick}>
{stringGetter({ key: STRING_KEYS.VIEW_MORE })}
</Styled.ViewMoreButton>
</Styled.Td>
</Styled.Tr>
);
};
export const TableRow = <TableRowData extends object>({
item,
children,
@@ -698,7 +660,7 @@ Styled.Empty = styled.div<{ withOuterBorder: boolean }>`
justify-items: center;
align-content: center;
padding: 4rem;
padding: 2rem;
gap: 0.75em;
color: var(--color-text-0);
@@ -906,18 +868,6 @@ Styled.Tbody = styled.tbody<StyleProps>`
--stickyArea2-paddingLeft: var(--border-width);
--stickyArea2-paddingRight: var(--border-width);
tr:first-of-type {
box-shadow: 0 calc(var(--border-width)) 0 0 var(--border-color);
}
`}
${({ withOuterBorder }) =>
withOuterBorder &&
css`
tr:last-of-type:not(:only-of-type) {
box-shadow: 0 calc(-1 * var(--border-width)) 0 0 var(--border-color);
}
tr:first-of-type {
box-shadow: none;
}
@@ -972,15 +922,3 @@ Styled.Row = styled.div`
${layoutMixins.inlineRow}
padding: var(--tableCell-padding);
`;
Styled.ViewMoreButton = styled(Button)`
--button-backgroundColor: var(--color-layer-2);
--button-textColor: var(--color-text-1);
width: 100%;
svg {
width: 0.675rem;
margin-left: 0.5ch;
}
`;
+2 -1
View File
@@ -27,8 +27,9 @@ export const ToastArea = ({ swipeDirection, children, className }: ToastAreaProp
const $ToastArea = styled.aside`
// Params
--toasts-gap: 0.5rem;
// Rules
${layoutMixins.scrollArea}
z-index: 1;
pointer-events: none;
+2 -2
View File
@@ -2,9 +2,9 @@ import styled, { type AnyStyledComponent } from 'styled-components';
import { type RiskLevels } from '@/constants/abacus';
import { abacusHelper } from '@/lib/abacus';
import { UsageColorFromRiskLevel } from '@/lib/styles';
import { UsageColorFromRiskLevel } from '@/styles/colors';
import { abacusHelper } from '@/lib/abacus';
type ElementProps = {
value: number;
};
+6 -1
View File
@@ -110,7 +110,12 @@ Styled.Abbr = styled.abbr`
Styled.Content = styled(Content)`
--tooltip-backgroundColor: var(--color-layer-4);
--tooltip-backgroundColor: ${({ theme }) => theme.tooltipBackground};
--tooltip-backgroundColor: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) + 4%),
0.66
);
${popoverMixins.popover}
--popover-backgroundColor: var(--tooltip-backgroundColor);
+6 -1
View File
@@ -27,7 +27,12 @@ Styled.TooltipContent = styled.aside<{ accentColor?: string }>`
${popoverMixins.popover}
--popover-radius: 0.5rem;
--popover-background-color: ${({ theme }) => theme.popoverBackground};
--popover-background-color: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness)),
0.9
);
--popover-backdrop-filter: saturate(120%) blur(12px);
display: grid;
@@ -1,33 +1,26 @@
import React, { PropsWithChildren, useContext, useState } from 'react';
import React, { useContext, useState } from 'react';
import { Point } from '@visx/point';
import { localPoint } from '@visx/event';
import { XYChart, DataContext, type EventHandlerParams } from '@visx/xychart';
import { XYChart, DataContext } from '@visx/xychart';
import { getScaleBandwidth } from '@/components/visx/getScaleBandwidth';
export const XYChartWithPointerEvents = ({
onPointerMove,
onPointerUp,
onPointerPressedChange,
...props
onPointerMove, onPointerUp, onPointerPressedChange, ...props
}: {
onPointerMove?: (point: Point | EventHandlerParams<object>) => void;
onPointerUp?: (point: Point | EventHandlerParams<object>) => void;
onPointerMove?: (point: Point) => void;
onPointerUp?: (point: Point) => void;
onPointerPressedChange?: (isPointerPressed: boolean) => void;
} & PropsWithChildren<Parameters<typeof XYChart>[0]>) => {
} & React.PropsWithChildren<Parameters<typeof XYChart>>) => {
const { xScale, yScale } = useContext(DataContext);
const [lastPointerMoveEvent, setLastPointerMoveEvent] = useState<React.PointerEvent>();
const pointerContainerPosition = lastPointerMoveEvent ? localPoint(lastPointerMoveEvent) : null;
const pointerChartPosition =
xScale &&
yScale &&
pointerContainerPosition &&
const pointerChartPosition = xScale && yScale && pointerContainerPosition &&
new Point({
// @ts-expect-error invert supposedly doesn't exist on AxisScale
x: xScale.invert(pointerContainerPosition?.x - getScaleBandwidth(xScale) / 2),
// @ts-expect-error invert supposedly doesn't exist on AxisScale
y: yScale.invert(pointerContainerPosition?.y - getScaleBandwidth(yScale) / 2),
});
@@ -36,13 +29,15 @@ export const XYChartWithPointerEvents = ({
{...props}
onPointerMove={({ event }) => {
setLastPointerMoveEvent(event as React.PointerEvent);
if (pointerChartPosition) onPointerMove?.(pointerChartPosition);
if (pointerChartPosition)
onPointerMove?.(pointerChartPosition);
}}
onPointerOut={() => setLastPointerMoveEvent(undefined)}
onPointerDown={() => onPointerPressedChange?.(true)}
onPointerUp={() => {
onPointerPressedChange?.(false);
if (pointerChartPosition) onPointerUp?.(pointerChartPosition);
if (pointerChartPosition)
onPointerUp?.(pointerChartPosition);
}}
>
{props.children}
+4 -19
View File
@@ -142,7 +142,7 @@ const historicalPnlPeriod = [...HistoricalPnlPeriod.values()] as const;
export type HistoricalPnlPeriods = (typeof historicalPnlPeriod)[number];
// ------ Transfer Items ------ //
export const TransferInputField = Abacus.exchange.dydx.abacus.state.model.TransferInputField;
export const TransferInputField = Abacus.exchange.dydx.abacus.state.modal.TransferInputField;
const transferInputFields = [...TransferInputField.values()] as const;
export type TransferInputFields = (typeof transferInputFields)[number];
@@ -151,7 +151,7 @@ const transferTypes = [...TransferType.values()] as const;
export type TransferTypes = (typeof transferTypes)[number];
// ------ Trade Items ------ //
export const TradeInputField = Abacus.exchange.dydx.abacus.state.model.TradeInputField;
export const TradeInputField = Abacus.exchange.dydx.abacus.state.modal.TradeInputField;
const tradeInputFields = [...TradeInputField.values()] as const;
export type TradeInputFields = (typeof tradeInputFields)[number];
@@ -162,7 +162,7 @@ export type TradeState<T> = {
};
export const ClosePositionInputField =
Abacus.exchange.dydx.abacus.state.model.ClosePositionInputField;
Abacus.exchange.dydx.abacus.state.modal.ClosePositionInputField;
const closePositionInputFields = [...ClosePositionInputField.values()] as const;
export type ClosePositionInputFields = (typeof closePositionInputFields)[number];
@@ -282,22 +282,13 @@ export const TRADE_TYPES: Record<
[AbacusOrderType.takeProfitMarket.rawValue]: TradeTypes.TAKE_PROFIT_MARKET,
[AbacusOrderType.liquidated.name]: null,
[AbacusOrderType.liquidated.rawValue]: null,
[AbacusOrderType.liquidation.name]: null,
[AbacusOrderType.liquidation.name]: null,
[AbacusOrderType.liquidation.rawValue]: null,
[AbacusOrderType.trailingStop.name]: null,
[AbacusOrderType.trailingStop.rawValue]: null,
[AbacusOrderType.offsetting.name]: null,
[AbacusOrderType.offsetting.rawValue]: null,
[AbacusOrderType.deleveraged.name]: null,
[AbacusOrderType.deleveraged.rawValue]: null,
[AbacusOrderType.finalSettlement.name]: null,
[AbacusOrderType.finalSettlement.rawValue]: null,
};
// Custom types involving Abacus
@@ -316,9 +307,3 @@ export type NetworkConfig = Partial<{
}>;
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
export type PerpetualMarketOrderbookLevel = OrderbookLine & {
side?: 'ask' | 'bid';
mine: number;
key: string;
};
-1
View File
@@ -96,7 +96,6 @@ export type AnalyticsEventData<T extends AnalyticsEvent> =
elapsedTime?: number;
blockHeight?: number;
indexerBlockHeight?: number;
trailingBlocks?: number;
}
: // Navigation
T extends AnalyticsEvent.NavigatePage
-40
View File
@@ -1,40 +0,0 @@
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { FundingDirection } from './markets';
// ------ Depth Chart ------ //
export enum DepthChartSeries {
Asks = 'Asks',
Bids = 'Bids',
MidMarket = 'MidMarket',
}
export type DepthChartDatum = {
size: number;
price: number;
depth: number;
seriesKey: DepthChartSeries;
};
export type DepthChartPoint = {
side: OrderSide;
price: number;
size: number;
};
export const SERIES_KEY_FOR_ORDER_SIDE = {
[OrderSide.BUY]: DepthChartSeries.Bids,
[OrderSide.SELL]: DepthChartSeries.Asks,
};
// ------ Funding Chart ------ //
export enum FundingRateResolution {
OneHour = 'OneHour',
EightHour = 'EightHour',
Annualized = 'Annualized',
}
export type FundingChartDatum = {
time: number;
fundingRate: number;
direction: FundingDirection;
};
-1
View File
@@ -25,5 +25,4 @@ export const DEFAULT_MARKETID = 'ETH-USD';
export enum FundingDirection {
ToShort = 'ToShort',
ToLong = 'ToLong',
None = 'None',
}
-2
View File
@@ -87,8 +87,6 @@ export type NotificationDisplayData = {
slotTitleLeft?: React.ReactNode;
slotTitleRight?: React.ReactNode;
groupKey: string; // Grouping key toast notification stacking
// Overrides title/body for Notification in NotificationMenu
renderCustomBody?: ({
isToast,
-15
View File
@@ -1,15 +0,0 @@
/**
* @description Orderbook display constants
*/
export const ORDERBOOK_MAX_ROWS_PER_SIDE = 30;
export const ORDERBOOK_ANIMATION_DURATION = 400;
/**
* @description Orderbook pixel constants
* @note ORDERBOOK_ROW_HEIGHT should be a divisor of ORDERBOOK_HEIGHT so that we do not have a partial row at the bottom
* @note To change the Orderbook width, --orderbook-trades-width and ORDERBOOK_WIDTH must be changed to the same value
*/
export const ORDERBOOK_HEIGHT = 800;
export const ORDERBOOK_WIDTH = 300;
export const ORDERBOOK_ROW_HEIGHT = 24;
export const ORDERBOOK_ROW_PADDING_RIGHT = 8;
-70
View File
@@ -1,70 +0,0 @@
export enum ColorToken {
Black = '#000000',
White = '#FFFFFF',
LightGray0 = '#FAFAFA',
LightGray1 = '#F7F8FA',
LightGray2 = '#FAFAFD',
LightGray3 = '#F5F5F5',
LightGray4 = '#EDF0F2',
LightGray5 = '#EDEDED',
LightGray6 = '#E9ECEE',
LightGray7 = '#E6E6E6',
LightGray8 = '#DBDBDB',
LightGray9 = '#D5DADF',
LightGray10 = '#D1D9E0',
MediumGray0 = '#CACACE',
MediumGray1 = '#C2CBD3',
DarkGray0 = '#888891',
DarkGray1 = '#737373',
DarkGray2 = '#56565C',
DarkGray3 = '#4B4B4B',
DarkGray4 = '#494950',
DarkGray5 = '#3B3B3F',
DarkGray6 = '#303033',
DarkGray7 = '#313135',
DarkGray8 = '#292929',
DarkGray9 = '#23232F',
DarkGray10 = '#212124',
DarkGray11 = '#18181B',
DarkGray12 = '#181818',
DarkGray13 = '#0C0C0D',
GrayBlue0 = '#464659',
GrayBlue1 = '#38384D',
GrayBlue2 = '#303045',
GrayBlue3 = '#29293D',
GrayBlue4 = '#212131',
GrayBlue5 = '#181825',
GrayBlue6 = '#101018',
GrayBlue7 = '#08080C',
GrayPurple0 = '#DFDADF',
GrayPurple1 = '#C8C7D8',
GrayPurple2 = '#807E98',
Purple0 = '#7774FF',
Purple1 = '#6966FF',
Green0 = '#1AFFB9',
Green1 = '#3ED9A4',
Green2 = '#2CCC98',
Green3 = '#3EB68A',
Yellow0 = '#FFCC48',
Yellow1 = '#FFB647',
Red0 = '#FF5C5C',
Red1 = '#E76565',
Red2 = '#E45555',
}
/** Maps opacity to corresponding hex value */
export enum OpacityToken {
Opacity16 = '29',
Opacity20 = '33',
Opacity66 = 'A8',
Opacity90 = 'E6',
}
-77
View File
@@ -1,77 +0,0 @@
export type ThemeColors = LayerColors &
BorderColors &
TextColors &
GradientColors &
AccentColors &
StatusColors &
DirectionalColors &
RiskColors &
IconColors &
ComponentColors;
type LayerColors = {
layer0: string;
layer1: string;
layer2: string;
layer3: string;
layer4: string;
layer5: string;
layer6: string;
layer7: string;
};
type BorderColors = {
borderDefault: string;
borderDestructive: string;
borderButton: string;
};
type TextColors = {
textPrimary: string;
textSecondary: string;
textTertiary: string;
};
type GradientColors = {
gradientBase0: string;
gradientBase1: string;
};
type AccentColors = {
accent: string;
accentFaded: string;
favorite: string;
};
type StatusColors = {
success: string;
warning: string;
error: string;
};
type DirectionalColors = {
positive: string;
negative: string;
positiveFaded: string;
negativeFaded: string;
};
type RiskColors = {
riskLow: string;
riskMedium: string;
riskHigh: string;
};
type IconColors = {
logoFill: string;
profileYellow: string;
profileRed: string;
};
type ComponentColors = {
inputBackground: string;
popoverBackground: string;
switchThumbActiveBackground: string;
toggleBackground: string;
tooltipBackground: string;
};
-12
View File
@@ -138,10 +138,6 @@ export const tradeTooltips: TooltipStrings = {
title: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_BODY }),
}),
'post-only-timeinforce-gtt': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_TIMEINFORCE_GTT_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.POST_ONLY_TIMEINFORCE_GTT_BODY }),
}),
'price-impact': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.PRICE_IMPACT_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.PRICE_IMPACT_BODY }),
@@ -155,14 +151,6 @@ export const tradeTooltips: TooltipStrings = {
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_BODY }),
learnMoreLink: urlConfigs?.reduceOnlyLearnMore,
}),
'reduce-only-execution-ioc-fok': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_EXECUTION_IOC_FOK_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_EXECUTION_IOC_FOK_BODY }),
}),
'reduce-only-timeinforce-ioc-fok': ({ stringGetter }) => ({
title: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_TIMEINFORCE_IOC_FOK_TITLE }),
body: stringGetter({ key: TOOLTIP_STRING_KEYS.REDUCE_ONLY_TIMEINFORCE_IOC_FOK_BODY }),
}),
spread: () => ({
title: 'Spread',
body: 'The difference in price between the highest bid (the price a buyer is willing to buy for) and lowest ask (the price a seller is willing to sell for) an asset.',
-4
View File
@@ -1,4 +0,0 @@
export * from './useCenterOrderbook';
export * from './useSpreadRowScrollListener';
export * from './useDrawOrderbook';
export * from './useOrderbookValues';
-22
View File
@@ -1,22 +0,0 @@
import { useEffect, type RefObject } from 'react';
type Props = {
marketId: string;
orderbookRef: RefObject<HTMLDivElement>;
};
/**
* @description Center Orderbook on load and market change
* Assumed that the two sides are the same height
*/
export const useCenterOrderbook = ({ marketId, orderbookRef }: Props) => {
const orderbookEl = orderbookRef.current;
const { clientHeight, scrollHeight } = orderbookEl ?? {};
const shouldScroll = scrollHeight && clientHeight && scrollHeight > clientHeight;
useEffect(() => {
if (orderbookEl && shouldScroll) {
orderbookEl.scrollTo({ top: (scrollHeight - clientHeight) / 2 });
}
}, [shouldScroll, marketId]);
};
-373
View File
@@ -1,373 +0,0 @@
import { useEffect, useMemo, useRef } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import type { PerpetualMarketOrderbookLevel } from '@/constants/abacus';
import { SMALL_USD_DECIMALS, TOKEN_DECIMALS } from '@/constants/numbers';
import {
ORDERBOOK_ANIMATION_DURATION,
ORDERBOOK_HEIGHT,
ORDERBOOK_ROW_HEIGHT,
ORDERBOOK_ROW_PADDING_RIGHT,
ORDERBOOK_WIDTH,
} from '@/constants/orderbook';
import { getCurrentMarketConfig, getCurrentMarketOrderbookMap } from '@/state/perpetualsSelectors';
import { getAppTheme } from '@/state/configsSelectors';
import { MustBigNumber } from '@/lib/numbers';
import {
getHistogramXValues,
getRektFromIdx,
getXByColumn,
getYForElements,
} from '@/lib/orderbookHelpers';
import { useAppThemeContext } from '../useAppTheme';
type ElementProps = {
data: Array<PerpetualMarketOrderbookLevel | undefined>;
histogramRange: number;
side: PerpetualMarketOrderbookLevel['side'];
};
type StyleProps = {
histogramSide: 'left' | 'right';
};
enum OrderbookRowAnimationType {
REMOVE,
NEW,
NONE,
}
export const useDrawOrderbook = ({
data,
histogramRange,
histogramSide,
side,
}: ElementProps & StyleProps) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const canvas = canvasRef.current;
const currentOrderbookMap = useSelector(getCurrentMarketOrderbookMap, shallowEqual);
const { stepSizeDecimals = TOKEN_DECIMALS, tickSizeDecimals = SMALL_USD_DECIMALS } =
useSelector(getCurrentMarketConfig, shallowEqual) || {};
const prevData = useRef<typeof data>(data);
const theme = useAppThemeContext();
/**
* Scale canvas using device pixel ratio to unblur drawn text
* @url https://stackoverflow.com/questions/15661339/how-do-i-fix-blurry-text-in-my-html5-canvas/65124939#65124939
* @returns adjusted canvas width/height/rowHeight used in coordinates for drawing
**/
const { canvasWidth, canvasHeight, rowHeight } = useMemo(() => {
const ratio = window.devicePixelRatio || 1;
if (!canvas) {
return {
canvasWidth: ORDERBOOK_WIDTH / devicePixelRatio,
canvasHeight: ORDERBOOK_HEIGHT / devicePixelRatio,
rowHeight: ORDERBOOK_ROW_HEIGHT / devicePixelRatio,
};
}
const ctx = canvas.getContext('2d');
canvas.width = canvas.offsetWidth * ratio;
canvas.height = canvas.offsetHeight * ratio;
if (ctx) {
ctx.scale(ratio, ratio);
ctx.font = '12px Satoshi';
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
ctx.imageSmoothingQuality = 'high';
}
return {
canvasWidth: canvas.width / ratio,
canvasHeight: canvas.height / ratio,
rowHeight: ORDERBOOK_ROW_HEIGHT,
};
}, [canvas]);
const drawBars = ({
barType,
ctx,
depthOrSizeValue,
gradientMultiplier,
histogramAccentColor,
histogramSide,
idx,
}: {
barType: 'depth' | 'size';
ctx: CanvasRenderingContext2D;
depthOrSizeValue: number;
gradientMultiplier: number;
histogramAccentColor: string;
histogramSide: 'left' | 'right';
idx: number;
}) => {
const { x1, x2, y1, y2 } = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});
// X values
const maxHistogramBarWidth = x2 - x1 - (barType === 'size' ? 8 : 2);
const barWidth = depthOrSizeValue
? Math.min((depthOrSizeValue / histogramRange) * maxHistogramBarWidth, maxHistogramBarWidth)
: 0;
const { gradient, bar } = getHistogramXValues({
barWidth,
canvasWidth,
gradientMultiplier,
histogramSide,
});
// Gradient
let linearGradient;
try {
linearGradient = ctx.createLinearGradient(gradient.x1, y1, gradient.x2, y2);
linearGradient.addColorStop(0, histogramAccentColor);
linearGradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = linearGradient;
} catch (err) {
ctx.fillStyle = 'rgba(0, 0, 0, 0)';
}
ctx.beginPath();
// Bar
const { bar: y } = getYForElements({ y: y1, rowHeight });
if (ctx.roundRect) {
ctx.roundRect(
bar.x1,
y,
bar.x2,
rowHeight - 2,
histogramSide === 'right' ? [2, 0, 0, 2] : [0, 2, 2, 0]
);
} else {
ctx.rect(bar.x1, y, bar.x2, rowHeight - 2);
}
ctx.fill();
};
const drawText = ({
animationType = OrderbookRowAnimationType.NONE,
ctx,
idx,
mine,
price,
size,
}: {
animationType?: OrderbookRowAnimationType;
ctx: CanvasRenderingContext2D;
idx: number;
mine?: number;
price?: number;
size?: number;
}) => {
const { y1 } = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});
const { text: y } = getYForElements({ y: y1, rowHeight });
let textColor: string;
let updatedTextColor: string | undefined;
switch (animationType) {
case OrderbookRowAnimationType.REMOVE: {
textColor = theme.textSecondary;
break;
}
case OrderbookRowAnimationType.NEW: {
updatedTextColor = side === 'bid' ? theme.positive : theme.negative;
textColor = theme.textPrimary;
break;
}
default: {
textColor = theme.textPrimary;
break;
}
}
// Size text
if (size) {
ctx.fillStyle = updatedTextColor ?? textColor;
ctx.fillText(
MustBigNumber(size).toFixed(stepSizeDecimals ?? TOKEN_DECIMALS),
getXByColumn({ canvasWidth, colIdx: 0 }) - ORDERBOOK_ROW_PADDING_RIGHT,
y
);
}
// Price text
if (price) {
ctx.fillStyle = textColor;
ctx.fillText(
MustBigNumber(price).toFixed(tickSizeDecimals ?? SMALL_USD_DECIMALS),
getXByColumn({ canvasWidth, colIdx: 1 }) - ORDERBOOK_ROW_PADDING_RIGHT,
y
);
}
// Mine text
if (mine) {
ctx.fillStyle = textColor;
ctx.fillText(
MustBigNumber(mine).toFixed(stepSizeDecimals ?? TOKEN_DECIMALS),
getXByColumn({ canvasWidth, colIdx: 2 }) - ORDERBOOK_ROW_PADDING_RIGHT,
y
);
}
};
const drawOrderbookRow = ({
ctx,
idx,
rowToRender,
animationType = OrderbookRowAnimationType.NONE,
}: {
ctx: CanvasRenderingContext2D;
idx: number;
rowToRender?: PerpetualMarketOrderbookLevel;
animationType?: OrderbookRowAnimationType;
}) => {
if (!rowToRender) return;
const { depth, mine, price, size } = rowToRender;
const histogramAccentColor = side === 'bid' ? theme.positiveFaded : theme.negativeFaded;
// Depth Bar
if (depth) {
drawBars({
barType: 'depth',
ctx,
depthOrSizeValue: depth,
gradientMultiplier: 1.3,
histogramAccentColor,
histogramSide,
idx,
});
}
// Size Bar
drawBars({
barType: 'size',
ctx,
depthOrSizeValue: size,
gradientMultiplier: 5,
histogramAccentColor,
histogramSide,
idx,
});
// Size, Price, Mine
drawText({
animationType,
ctx,
idx,
mine,
price,
size,
});
};
// Update histograms and row contents on data change
useEffect(() => {
const ctx = canvas?.getContext('2d');
if (!canvas || !ctx) return;
// Clear canvas before redraw
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// Animate row removal and update
const mapOfOrderbookPriceLevels =
side && currentOrderbookMap?.[side === 'ask' ? 'asks' : 'bids'];
const empty: number[] = [];
const removed: number[] = [];
const updated: number[] = [];
prevData.current.forEach((row, idx) => {
if (!row) {
empty.push(idx);
return;
}
if (mapOfOrderbookPriceLevels?.[row.price] === 0) {
removed.push(idx);
drawOrderbookRow({
ctx,
idx,
rowToRender: row,
animationType: OrderbookRowAnimationType.REMOVE,
});
} else if (mapOfOrderbookPriceLevels?.[row.price] === row?.size) {
drawOrderbookRow({
ctx,
idx,
rowToRender: data[idx],
animationType: OrderbookRowAnimationType.NONE,
});
} else {
updated.push(idx);
drawOrderbookRow({
ctx,
idx,
rowToRender: row,
animationType: OrderbookRowAnimationType.NEW,
});
}
});
setTimeout(() => {
[...empty, ...removed, ...updated].forEach((idx) => {
const { x1, y1, x2, y2 } = getRektFromIdx({
idx,
rowHeight,
canvasWidth,
canvasHeight,
side,
});
ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
drawOrderbookRow({
ctx,
idx,
rowToRender: data[idx],
});
});
}, ORDERBOOK_ANIMATION_DURATION);
prevData.current = data;
}, [
canvasHeight,
canvasWidth,
rowHeight,
data,
histogramRange,
stepSizeDecimals,
tickSizeDecimals,
histogramSide,
side,
theme,
currentOrderbookMap,
]);
return { canvasRef };
};
-126
View File
@@ -1,126 +0,0 @@
import { useMemo } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import { type PerpetualMarketOrderbookLevel } from '@/constants/abacus';
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
import { getSubaccountOpenOrdersBySideAndPrice } from '@/state/accountSelectors';
import { MustBigNumber } from '@/lib/numbers';
export const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide: number }) => {
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
const openOrdersBySideAndPrice =
useSelector(getSubaccountOpenOrdersBySideAndPrice, shallowEqual) || {};
return useMemo(() => {
const asks: Array<PerpetualMarketOrderbookLevel | undefined> = (
orderbook?.asks?.toArray() ?? []
)
.map(
(row, idx: number) =>
({
key: `ask-${idx}`,
side: 'ask',
mine: openOrdersBySideAndPrice[OrderSide.SELL]?.[row.price]?.size,
...row,
} as PerpetualMarketOrderbookLevel)
)
.slice(0, maxRowsPerSide);
const bids: Array<PerpetualMarketOrderbookLevel | undefined> = (
orderbook?.bids?.toArray() ?? []
)
.map(
(row, idx: number) =>
({
key: `bid-${idx}`,
side: 'bid',
mine: openOrdersBySideAndPrice[OrderSide.BUY]?.[row.price]?.size,
...row,
} as PerpetualMarketOrderbookLevel)
)
.slice(0, maxRowsPerSide);
// Prevent the bid/ask sides from crossing by using the offsets.
// While the books are crossing...
while (asks[0] && bids[0] && bids[0]!.price >= asks[0].price) {
// Drop the order on the side with the lower offset.
// The offset of the other side is higher and so supercedes.
if (bids[0]!.offset === asks[0].offset) {
// If offsets are the same, give precedence to the larger size. In this case,
// one of the sizes *should* be zero, but we simply check for the larger size.
if (bids[0]!.size > asks[0].size) {
asks.shift();
} else {
bids.pop();
}
} else {
// Offsets are not equal. Give precedence to the larger offset.
if (bids[0]!.offset > asks[0].offset) {
asks.shift();
} else {
bids.pop();
}
}
}
const spread =
asks[0]?.price && bids[0]?.price ? MustBigNumber(asks[0].price).minus(bids[0].price) : null;
const spreadPercent = orderbook?.spreadPercent;
const histogramRange = Math.max(
isNaN(Number(bids[bids.length - 1]?.depth)) ? 0 : Number(bids[bids.length - 1]?.depth),
isNaN(Number(asks[asks.length - 1]?.depth)) ? 0 : Number(asks[asks.length - 1]?.depth)
);
return {
asks,
bids,
spread,
spreadPercent,
histogramRange,
hasOrderbook: !!orderbook,
};
}, [orderbook, openOrdersBySideAndPrice]);
};
export const useOrderbookValuesForDepthChart = () => {
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
return useMemo(() => {
const bids = (orderbook?.bids?.toArray() ?? [])
.filter(Boolean)
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
const asks = (orderbook?.asks?.toArray() ?? [])
.filter(Boolean)
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
const lowestBid = bids[bids.length - 1];
const highestBid = bids[0];
const lowestAsk = asks[0];
const highestAsk = asks[asks.length - 1];
const midMarketPrice = orderbook?.midPrice;
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
const spreadPercent = orderbook?.spreadPercent;
return {
bids,
asks,
lowestBid,
highestBid,
lowestAsk,
highestAsk,
midMarketPrice,
spread,
spreadPercent,
orderbook,
};
}, [orderbook]);
};
@@ -1,39 +0,0 @@
import { type RefObject, useEffect, useState } from 'react';
export const useSpreadRowScrollListener = ({
orderbookRef,
spreadRowRef,
}: {
orderbookRef: RefObject<HTMLDivElement>;
spreadRowRef: RefObject<HTMLDivElement>;
}) => {
const [displaySide, setDisplaySide] = useState<string>();
useEffect(() => {
const onScroll = () => {
if (spreadRowRef.current && orderbookRef.current) {
const { clientHeight } = orderbookRef.current;
const parent = orderbookRef.current.getBoundingClientRect();
const spread = spreadRowRef.current.getBoundingClientRect();
const spreadTop = spread.top - parent.top;
const spreadBottom = spread.bottom - parent.top;
if (spreadBottom > clientHeight) {
setDisplaySide('bottom');
} else if (spreadTop < 0) {
setDisplaySide('top');
} else {
setDisplaySide(undefined);
}
}
};
orderbookRef.current?.addEventListener('scroll', onScroll, false);
return () => {
orderbookRef.current?.removeEventListener('scroll', onScroll, false);
};
}, [orderbookRef.current, spreadRowRef.current]);
return displaySide;
};
+2 -1
View File
@@ -4,6 +4,7 @@ import { useSelector } from 'react-redux';
import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library';
import { AppTheme } from '@/state/configs';
import { getAppTheme } from '@/state/configsSelectors';
import { getWidgetOverrides } from '@/lib/tradingView/utils';
@@ -28,7 +29,7 @@ export const useTradingViewTheme = ({
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
isWidgetReady?: boolean;
}) => {
const appTheme: AppTheme = useSelector(getAppTheme);
const appTheme = useSelector(getAppTheme);
useEffect(() => {
if (tvWidget && isWidgetReady) {
+1 -2
View File
@@ -97,7 +97,7 @@ export const useAnalytics = () => {
}, []);
// AnalyticsEvent.NetworkStatus
const { height, indexerHeight, status, trailingBlocks} = useApiState();
const { height, indexerHeight, status } = useApiState();
useEffect(() => {
if (status) {
@@ -114,7 +114,6 @@ export const useAnalytics = () => {
elapsedTime: lastSuccessfulIndexerRpcQuery && Date.now() - lastSuccessfulIndexerRpcQuery,
blockHeight: height ?? undefined,
indexerBlockHeight: indexerHeight ?? undefined,
trailingBlocks: trailingBlocks ?? undefined
});
}
}, [status]);
+1 -1
View File
@@ -71,7 +71,7 @@ export const getIndexerHeight = (apiState: Nullable<AbacusApiState>) => {
export const useApiState = () => {
const stringGetter = useStringGetter();
const apiState = useSelector(getApiState, shallowEqual);
const { haltedBlock, height, status, trailingBlocks} = apiState ?? {};
const { haltedBlock, height, status, trailingBlocks } = apiState ?? {};
const statusErrorMessage = getStatusErrorMessage({ apiState, stringGetter });
const indexerHeight = getIndexerHeight(apiState);
-16
View File
@@ -1,16 +0,0 @@
import { useSelector } from 'react-redux';
import { ThemeProvider } from 'styled-components';
import { AppTheme } from '@/state/configs';
import { getAppTheme } from '@/state/configsSelectors';
import { Themes } from '@/styles/themes';
export const AppThemeProvider = ({ ...props }) => {
return <ThemeProvider theme={useAppThemeContext()} {...props} />
};
export const useAppThemeContext = () => {
const theme: AppTheme = useSelector(getAppTheme);
return Themes[theme];
}
-28
View File
@@ -28,7 +28,6 @@ import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { AssetIcon } from '@/components/AssetIcon';
import { Icon, IconName } from '@/components/Icon';
import { BlockRewardNotification } from '@/views/notifications/BlockRewardNotification';
import { TradeNotification } from '@/views/notifications/TradeNotification';
import { TransferStatusNotification } from '@/views/notifications/TransferStatusNotification';
@@ -81,7 +80,6 @@ export const notificationTypes: NotificationTypeConfig[] = [
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
toastSensitivity: 'foreground',
groupKey: abacusNotificationType,
renderCustomBody: ({ isToast, notification }) => (
<TradeNotification
isToast={isToast}
@@ -95,29 +93,6 @@ export const notificationTypes: NotificationTypeConfig[] = [
);
break;
}
case 'blockReward': {
trigger(
abacusNotif.id,
{
icon: abacusNotif.image && <$Icon src={abacusNotif.image} alt="" />,
title: stringGetter({ key: abacusNotif.title }),
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
toastSensitivity: 'foreground',
groupKey: abacusNotificationType,
renderCustomBody: ({ isToast, notification }) => (
<BlockRewardNotification
isToast={isToast}
data={parsedData}
notification={notification}
/>
),
},
[abacusNotif.updateTimeInMilliseconds, abacusNotif.data],
true
);
break;
}
default:
trigger(
abacusNotif.id,
@@ -127,7 +102,6 @@ export const notificationTypes: NotificationTypeConfig[] = [
body: abacusNotif.text ? stringGetter({ key: abacusNotif.text, params }) : '',
toastDuration: DEFAULT_TOAST_AUTO_CLOSE_MS,
toastSensitivity: 'foreground',
groupKey: abacusNotificationType,
},
[abacusNotif.updateTimeInMilliseconds, abacusNotif.data]
);
@@ -223,7 +197,6 @@ export const notificationTypes: NotificationTypeConfig[] = [
/>
),
toastSensitivity: 'foreground',
groupKey: NotificationType.SquidTransfer,
},
[isFinished]
);
@@ -269,7 +242,6 @@ export const notificationTypes: NotificationTypeConfig[] = [
},
}),
toastSensitivity: 'foreground',
groupKey: NotificationType.ReleaseUpdates,
},
[]
);
+1 -7
View File
@@ -198,13 +198,7 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
);
const ibcMsg: EncodeObject = {
typeUrl: transaction.msgTypeUrl,
value: {
...transaction.msg,
timeoutTimestamp: transaction.msg.timeoutTimestamp
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
? BigInt(Long.fromValue(transaction.msg.timeoutTimestamp).toString())
: undefined,
},
value: transaction.msg,
};
return await compositeClient.send(
+3 -20
View File
@@ -1,7 +1,6 @@
import { useCallback, useEffect, useState, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { EvmDerivedAddresses } from '@/constants/account';
import { LocalStorageKey } from '@/constants/localStorage';
import { ENVIRONMENT_CONFIG_MAP } from '@/constants/networks';
@@ -105,17 +104,11 @@ export const useWalletConnection = () => {
[walletConnectConfig, walletType, walletConnectionType]
);
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector });
const { connectAsync: connectWagmi } = useConnectWagmi({ connector: wagmiConnector })
const { suggestAndConnect: connectGraz } = useConnectGraz();
const [evmDerivedAddresses] = useLocalStorage({
key: LocalStorageKey.EvmDerivedAddresses,
defaultValue: {} as EvmDerivedAddresses,
});
const connectWallet = useCallback(
async ({ walletType, forceConnect }: { walletType?: WalletType; forceConnect?: boolean }) => {
if (!walletType) return { walletType, walletConnectionType };
async ({ walletType }: { walletType: WalletType }) => {
const walletConnection = getWalletConnection({ walletType });
try {
@@ -139,11 +132,7 @@ export const useWalletConnection = () => {
});
}
} else {
const isAccountConnected = Boolean(
evmAddress && evmDerivedAddresses[evmAddress]?.encryptedSignature
);
// if account connected (via remember me), do not show wagmi popup until forceConnect
if (!isConnectedWagmi && (forceConnect || !isAccountConnected)) {
if (!isConnectedWagmi) {
await connectWagmi({
connector: resolveWagmiConnector({
walletType,
@@ -239,12 +228,6 @@ export const useWalletConnection = () => {
evmAddressWagmi,
signerWagmi,
publicClientWagmi,
isConnectedWagmi,
connectWallet: () =>
connectWallet({
walletType: selectedWalletType,
forceConnect: true,
}),
// Wallet connection (Cosmos)
dydxAddress,
+3 -1
View File
@@ -1 +1,3 @@
<svg fill="none" height="10" viewBox="0 0 15 10" width="15" xmlns="http://www.w3.org/2000/svg"><path clip-rule="evenodd" d="m.321829 1.09389c.200562-.192959.469529-.298401.747791-.29315.27827.00525.54307.120763.73621.32115l5.194 5.5132 5.19397-5.5132c.0944-.10408.2088-.188152.3363-.247234.1275-.059081.2655-.091968.406-.096713.1404-.004746.2804.018748.4116.069088s.2509.126504.3522.223979c.1012.09747.1818.21427.2371.34347.0552.12921.084.26819.0845.40871.0006.14052-.0271.27972-.0813.40936s-.1339.24707-.2344.34534l-5.94997 6.3c-.09795.10163-.21538.18245-.34527.23766-.1299.05521-.26959.08367-.41073.08367s-.28083-.02846-.41073-.08367c-.12989-.05521-.24732-.13603-.34527-.23766l-5.950001-6.3c-.192962-.20056-.29840394-.46953-.29315364-.74779.00525031-.27827.12076264-.54307.32115364-.73621z" fill="currentColor" fill-rule="evenodd"/></svg>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 6L7.83335 10.5L2 6" stroke="currentColor" stroke-width="2.25" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 840 B

After

Width:  |  Height:  |  Size: 205 B

+3
View File
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.95513 1.00169C7.89506 1.00645 7.83628 1.02054 7.78124 1.04337L1.31971 3.71042C1.2243 3.74983 1.14329 3.81414 1.08653 3.89553C1.02977 3.97691 0.999705 4.07186 1 4.16883V11.8366C1.00078 11.9327 1.03135 12.0265 1.08805 12.1069C1.14474 12.1873 1.22517 12.2508 1.31971 12.2898L7.78124 14.9568C7.85011 14.9853 7.92463 15 8 15C8.07536 15 8.14989 14.9853 8.21876 14.9568L14.6803 12.2898C14.7748 12.2508 14.8552 12.1873 14.9119 12.1069C14.9686 12.0265 14.9992 11.9327 15 11.8366V4.16883C15.0003 4.07186 14.9702 3.97691 14.9134 3.89553C14.8567 3.81414 14.7756 3.74983 14.6803 3.71042L8.21876 1.04337C8.13605 1.00911 8.04538 0.994778 7.95513 1.00169ZM8 2.04351L13.1378 4.16883L8 6.28894L2.86219 4.16883L8 2.04351ZM2.07693 4.93457L7.46153 7.15886V13.7328L2.07693 11.5084V4.93457ZM13.923 4.93457V11.5084L8.53846 13.7328V7.15886L13.923 4.93457Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 969 B

+1 -3
View File
@@ -19,6 +19,7 @@ export { default as CoinMarketCapIcon } from './logos/coinmarketcap.svg';
export { default as CoinsIcon } from './coins.svg';
export { default as CommentIcon } from './comment.svg';
export { default as CopyIcon } from './copy.svg';
export { default as CubeIcon } from './cube.svg';
export { default as DepositIcon } from './deposit.svg';
export { default as DepthChartIcon } from './depth-chart.svg';
export { default as DiscordIcon } from './discord.svg';
@@ -39,7 +40,6 @@ export { default as MarketsIcon } from './markets.svg';
export { default as MenuIcon } from './menu.svg';
export { default as MigrateIcon } from './migrate.svg';
export { default as MintscanIcon } from './logos/mintscan.svg';
export { default as MoonIcon } from './moon.svg';
export { default as OrderbookIcon } from './orderbook.svg';
export { default as OverviewIcon } from './overview.svg';
export { default as PencilIcon } from './pencil.svg';
@@ -51,13 +51,11 @@ export { default as PriceChartIcon } from './price-chart.svg';
export { default as PrivacyIcon } from './privacy.svg';
export { default as ProfileIcon } from './profile.svg';
export { default as QrIcon } from './qr.svg';
export { default as RewardStarIcon } from './reward-star.svg';
export { default as SearchIcon } from './search.svg';
export { default as SendIcon } from './send.svg';
export { default as ShareIcon } from './share.svg';
export { default as ShowIcon } from './show.svg';
export { default as StarIcon } from './star.svg';
export { default as SunIcon } from './sun.svg';
export { default as TerminalIcon } from './terminal.svg';
export { default as TogglesMenuIcon } from './toggles-menu.svg';
export { default as TradeIcon } from './trade.svg';
+43 -50
View File
@@ -1,52 +1,45 @@
import { useAppThemeContext } from '@/hooks/useAppTheme';
const LogoShortIcon: React.FC<{ id?: string }> = ({ id }: { id?: string }) => {
const theme = useAppThemeContext();
const fill = theme.logoFill;
return (
<svg
width="135"
height="145"
viewBox="0 0 135 145"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M100.986 0L0 144.988H31.0048L132.514 0H100.986Z" fill={fill} />
<path
d="M34.2346 0L63.9475 42.7232L48.4451 66.0268L2.58386 0H34.2346Z"
fill={`url(#${id ? `${id}_logo_gradient_0` : 'paint0_linear'})`}
/>
<path
d="M103.995 145L71.0526 97.7455L86.555 75.0893L135 145H103.995Z"
fill={`url(#${id ? `${id}_logo_gradient_1` : 'paint1_linear'})`}
/>
<defs>
<linearGradient
id={id ? `${id}_logo_gradient_0` : 'paint0_linear'}
x1="27.1293"
y1="9.0625"
x2="69.773"
y2="60.4324"
gradientUnits="userSpaceOnUse"
>
<stop stopColor={fill} />
<stop offset="1" stopColor={fill} stopOpacity="0.55" />
</linearGradient>
<linearGradient
id={id ? `${id}_logo_gradient_1` : 'paint1_linear'}
x1="111.1"
y1="133.996"
x2="58.6959"
y2="63.4999"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#6966FF" />
<stop offset="1" stopColor="#6966FF" stopOpacity="0.36" />
</linearGradient>
</defs>
</svg>
);
};
const LogoShortIcon: React.FC<{ id?: string }> = ({ id }: { id?: string }) => (
<svg
width="135"
height="145"
viewBox="0 0 135 145"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M100.986 0L0 144.988H31.0048L132.514 0H100.986Z" fill="white" />
<path
d="M34.2346 0L63.9475 42.7232L48.4451 66.0268L2.58386 0H34.2346Z"
fill={`url(#${id ? `${id}_logo_gradient_0` : 'paint0_linear'})`}
/>
<path
d="M103.995 145L71.0526 97.7455L86.555 75.0893L135 145H103.995Z"
fill={`url(#${id ? `${id}_logo_gradient_1` : 'paint1_linear'})`}
/>
<defs>
<linearGradient
id={id ? `${id}_logo_gradient_0` : 'paint0_linear'}
x1="27.1293"
y1="9.0625"
x2="69.773"
y2="60.4324"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0.55" />
</linearGradient>
<linearGradient
id={id ? `${id}_logo_gradient_1` : 'paint1_linear'}
x1="111.1"
y1="133.996"
x2="58.6959"
y2="63.4999"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#6966FF" />
<stop offset="1" stopColor="#6966FF" stopOpacity="0.36" />
</linearGradient>
</defs>
</svg>
);
export default LogoShortIcon;
-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
</svg>

Before

Width:  |  Height:  |  Size: 380 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 481 KiB

-3
View File
@@ -1,3 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
</svg>

Before

Width:  |  Height:  |  Size: 393 B

+2 -2
View File
@@ -1,5 +1,6 @@
@import 'styles/fonts.css';
@import 'styles/text.css';
@import 'styles/colors.css';
@import 'styles/animations.css';
:root {
@@ -8,7 +9,6 @@
background-color: var(--color-layer-2);
user-select: none;
overflow: hidden;
--border: var(--default-border-width) solid var(--color-border);
}
body,
@@ -54,7 +54,7 @@ button:disabled {
cursor: pointer;
}
:focus-visible {
:focus-visible{
outline: var(--color-accent) 1px solid;
outline-offset: -1px;
/* outline-offset: -2px; */
@@ -1,11 +1,7 @@
import { useState } from 'react';
import { useMemo, useState } from 'react';
import styled, { css } from 'styled-components';
import {
type Notification,
type NotificationDisplayData,
NotificationStatus,
} from '@/constants/notifications';
import { NotificationStatus } from '@/constants/notifications';
import { ButtonShape, ButtonSize } from '@/constants/buttons';
import { useNotifications } from '@/hooks/useNotifications';
import { useBreakpoints } from '@/hooks/useBreakpoints';
@@ -17,31 +13,54 @@ import { Toast } from '@/components/Toast';
import { ToastArea } from '@/components/ToastArea';
import { ToggleButton } from '@/components/ToggleButton';
type ElementProps = {
notifications: {
notification: Notification<any, any>;
key: string;
displayData: NotificationDisplayData;
}[];
};
type StyleProps = {
className?: string;
};
export const NotificationStack = ({ notifications, className }: ElementProps & StyleProps) => {
const MAX_TOASTS = 10;
export const NotificationsToastArea = ({ className }: StyleProps) => {
const [shouldStackNotifications, setshouldStackNotifications] = useState(true);
const { markUnseen, markSeen, onNotificationAction } = useNotifications();
const {
notifications,
getKey,
getDisplayData,
markUnseen,
markSeen,
isMenuOpen,
onNotificationAction,
} = useNotifications();
const { isMobile } = useBreakpoints();
const hasMultipleToasts = notifications.length > 1;
const notificationMap = useMemo(() => {
return (
Object.values(notifications)
// Sort by time of first trigger
.sort(
(n1, n2) =>
n1.timestamps[NotificationStatus.Triggered]! -
n2.timestamps[NotificationStatus.Triggered]!
)
.map((notification) => ({
notification,
key: getKey(notification),
displayData: getDisplayData(notification),
}))
.filter(
({ displayData, notification }) =>
displayData && notification.status < NotificationStatus.Unseen
)
.slice(-MAX_TOASTS)
);
}, [notifications, getKey, getDisplayData]);
if (isMenuOpen) return null;
const hasMultipleToasts = notificationMap.length > 1;
return (
<StyledToastArea
swipeDirection={isMobile ? 'up' : 'right'}
className={className}
size={notifications.length}
>
<StyledToastArea swipeDirection={isMobile ? 'up' : 'right'} className={className}>
{hasMultipleToasts && (
<StyledToggleButton
shape={ButtonShape.Pill}
@@ -53,12 +72,12 @@ export const NotificationStack = ({ notifications, className }: ElementProps & S
</StyledToggleButton>
)}
{notifications.map(({ notification, key, displayData }, idx) => (
{notificationMap.map(({ notification, key, displayData }, idx) => (
<StyledToast
key={key}
isStacked={idx > 0 && shouldStackNotifications}
isStacked={shouldStackNotifications}
isOpen={notification.status < NotificationStatus.Unseen}
layer={idx}
layer={notificationMap.length - 1 - idx}
notification={notification}
slotIcon={displayData.icon}
slotTitle={displayData.title}
@@ -91,13 +110,19 @@ export const NotificationStack = ({ notifications, className }: ElementProps & S
);
};
const StyledToastArea = styled(ToastArea)<{ size: number }>`
position: relative;
${({ size }) =>
size &&
css`
padding-bottom: calc(${size - 1} * 2px);
`}
const StyledToastArea = styled(ToastArea)`
position: absolute;
width: min(17.5rem, 100%);
inset: 0 0 0 auto;
padding: 0.75rem 0.75rem 0.75rem 0;
mask-image: linear-gradient(to left, transparent, white 0.5rem);
@media ${breakpoints.mobile} {
width: 100%;
position: fixed;
}
`;
const StyledToast = styled(Toast)<{ isStacked?: boolean; layer: number }>`
@@ -108,7 +133,7 @@ const StyledToast = styled(Toast)<{ isStacked?: boolean; layer: number }>`
position: absolute;
left: 0;
top: calc(${layer} * 2px);
right: 0;
right: calc(${layer} * -2px);
`
: css`
margin-bottom: 0.75rem;
@@ -120,7 +145,7 @@ const StyledToggleButton = styled(ToggleButton)<{ isPressed: boolean }>`
pointer-events: auto;
display: none;
position: absolute;
top: -0.5rem;
top: 4px;
left: calc(50% - 0.75rem);
--button-width: 2rem;
--button-height: 1rem;
@@ -1,71 +0,0 @@
import { useMemo } from 'react';
import { groupBy } from 'lodash';
import styled from 'styled-components';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { NotificationStatus } from '@/constants/notifications';
import { useNotifications } from '@/hooks/useNotifications';
import { NotificationStack } from './NotifcationStack';
type StyleProps = {
className?: string;
};
const MAX_TOASTS = 10;
export const NotificationsToastArea = ({ className }: StyleProps) => {
const { notifications, getKey, getDisplayData, isMenuOpen } = useNotifications();
const notificationMapByType = useMemo(() => {
const notificationMap = Object.values(notifications)
// Sort by time of first trigger
.sort(
(n1, n2) =>
n1.timestamps[NotificationStatus.Triggered]! -
n2.timestamps[NotificationStatus.Triggered]!
)
.map((notification) => ({
notification,
key: getKey(notification),
displayData: getDisplayData(notification),
}))
.filter(
({ displayData, notification }) =>
displayData && notification.status < NotificationStatus.Unseen
)
.slice(-MAX_TOASTS);
return groupBy(notificationMap, (notification) => notification.displayData?.groupKey);
}, [notifications, getKey, getDisplayData]);
if (isMenuOpen) return null;
return (
<StyledToastArea className={className}>
{Object.keys(notificationMapByType).map((groupKey) => (
<NotificationStack notifications={notificationMapByType[groupKey]} key={groupKey} />
))}
</StyledToastArea>
);
};
const StyledToastArea = styled.div`
${layoutMixins.scrollArea}
position: absolute;
width: min(17.5rem, 100%);
inset: 0 0 0 auto;
padding: 0.75rem 0.75rem 0.75rem 0;
mask-image: linear-gradient(to left, transparent, white 0.5rem);
pointer-events: none;
@media ${breakpoints.mobile} {
width: 100%;
position: fixed;
}
`;
+2 -22
View File
@@ -56,10 +56,6 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
this.store = undefined;
}
get isNobleClientConnected(): boolean {
return this.nobleClient?.isConnected ?? false;
}
setStore(store: RootStore): void {
this.store = store;
}
@@ -173,10 +169,6 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
return parsedObj as T;
}
if (typeof x === 'bigint') {
return x.toString() as T;
}
throw new Error(`Unsupported data type: ${typeof x}`);
}
@@ -367,13 +359,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
try {
const ibcMsg = {
typeUrl: params.msgTypeUrl, // '/ibc.applications.transfer.v1.MsgTransfer',
value: {
...params.msg,
timeoutTimestamp: params.msg.timeoutTimestamp
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
? BigInt(Long.fromValue(params.msg.timeoutTimestamp).toString())
: undefined,
},
value: params.msg,
};
const fee = await this.nobleClient.simulateTransaction([ibcMsg]);
@@ -423,13 +409,7 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
);
const ibcMsg: EncodeObject = {
typeUrl: parsedIbcPayload.msgTypeUrl,
value: {
...parsedIbcPayload.msg,
timeoutTimestamp: parsedIbcPayload.msg.timeoutTimestamp
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
? BigInt(Long.fromValue(parsedIbcPayload.msg.timeoutTimestamp).toString())
: undefined,
},
value: parsedIbcPayload.msg,
};
const tx = await this.compositeClient.send(
+5 -10
View File
@@ -171,15 +171,6 @@ class AbacusStateManager {
this.setTransferValue({ value: null, field: TransferInputField.usdcSize });
};
resetInputState = () => {
this.clearTransferInputValues();
this.setTransferValue({
field: TransferInputField.type,
value: null,
});
this.clearTradeInputValues();
};
// ------ Set Data ------ //
setStore = (store: RootStore) => {
this.store = store;
@@ -271,7 +262,11 @@ class AbacusStateManager {
) => this.stateManager.cancelOrder(orderId, callback);
cctpWithdraw = (
callback: (success: boolean, parsingError: Nullable<ParsingError>, data: string) => void
callback: (
success: boolean,
parsingError: Nullable<ParsingError>,
data: string,
) => void
): void => this.stateManager.commitCCTPWithdraw(callback);
// ------ Utils ------ //
-133
View File
@@ -1,133 +0,0 @@
// ------ Canvas helper methods ------ //
import type { MarketOrderbook, Nullable, PerpetualMarketOrderbookLevel } from '@/constants/abacus';
/**
* @returns top left x,y and bottom x,y from array idx
*/
export const getRektFromIdx = ({
idx,
canvasWidth,
canvasHeight,
rowHeight,
side = 'bid',
}: {
idx: number;
canvasWidth: number;
canvasHeight: number;
rowHeight: number;
side: PerpetualMarketOrderbookLevel['side'];
}) => {
/**
* Does not change
*/
const x1 = 0;
const x2 = Math.floor(canvasWidth);
/**
* Changes based on side
* Asks: The drawing should starts from the bottom of the orderbook
*/
if (side === 'ask') {
const y1 = Math.floor(canvasHeight - (idx + 1) * rowHeight);
const y2 = Math.floor(y1 + rowHeight);
return { x1, x2, y1, y2 };
}
const y1 = Math.floor(idx * rowHeight);
const y2 = Math.floor(y1 + rowHeight);
return { x1, x2, y1, y2 };
};
/**
* @returns y value for text and bar
*/
export const getYForElements = ({ y, rowHeight }: { y: number; rowHeight: number }) => ({
text: Math.floor(y + rowHeight / 2),
bar: Math.floor(y + 1),
});
/**
* @description X coordinate for text
* @returns Get X value by column, colIdx starts at 0
*/
export const getXByColumn = ({ canvasWidth, colIdx }: { canvasWidth: number; colIdx: number }) => {
return Math.floor(((colIdx + 1) * canvasWidth) / 3);
};
/**
* to === 'right' to === 'left'
* |===================| |===================|
* bar: a b c d
* a = 0
* b = depthBarWidth
* c = canvasWidth - depthBarWidth
* d = canvasWidth
*
*/
export const getHistogramXValues = ({
barWidth,
canvasWidth,
gradientMultiplier,
histogramSide,
}: {
barWidth: number;
canvasWidth: number;
gradientMultiplier: number;
histogramSide: 'left' | 'right';
}) => {
const gradient = {
x1: Math.floor(histogramSide === 'left' ? barWidth : canvasWidth - barWidth),
x2: Math.floor(
histogramSide === 'left'
? 0 - (canvasWidth * gradientMultiplier - canvasWidth)
: canvasWidth * gradientMultiplier
),
};
const bar = {
x1: Math.floor(histogramSide === 'left' ? 0 : canvasWidth - barWidth),
x2: Math.floor(histogramSide === 'left' ? Math.min(barWidth, canvasWidth) : canvasWidth),
};
return {
bar,
gradient,
};
};
// ------ Orderbook helper methods ------ //
export const processOrderbookToCreateMap = ({
orderbookMap,
newOrderbook,
}: {
orderbookMap?: {
asks: Record<string, number>;
bids: Record<string, number>;
};
newOrderbook: Nullable<MarketOrderbook>;
}) => {
// Create Orderbooks Map indexed by Price
const asks = newOrderbook?.asks?.toArray() ?? [];
const bids = newOrderbook?.bids?.toArray() ?? [];
const prevAsks = orderbookMap?.asks ?? {};
const prevBids = orderbookMap?.bids ?? {};
const newAsks = Object.fromEntries(asks.map((ask) => [ask.price, ask.size]) ?? {});
const newBids = Object.fromEntries(bids.map((bid) => [bid.price, bid.size]) ?? {});
Object.keys(prevAsks).forEach((price) => {
if (!newAsks[price] || newAsks[price] === 0) {
newAsks[price] = 0;
}
});
Object.keys(prevBids).forEach((price) => {
if (!newBids[price] || newBids[price] === 0) {
newBids[price] = 0;
}
});
return {
newAsks,
newBids,
};
};
-20
View File
@@ -1,20 +0,0 @@
import { css } from 'styled-components';
import { type RiskLevels } from '@/constants/abacus';
export const UsageColorFromRiskLevel = (riskLevel: RiskLevels) =>
({
low: css`
color: var(--color-risk-low);
`,
medium: css`
color: var(--color-risk-medium);
`,
high: css`
color: var(--color-risk-high);
`,
}[riskLevel.name]);
export const generateFadedColorVariant = (colorHex: string, opacityHex: string) => {
return `${colorHex}${opacityHex}`;
};
+4 -4
View File
@@ -11,16 +11,16 @@ class TestFlags {
const params = new URLSearchParams(queryParamsString);
for (const [key, value] of params) {
this.queryParams[key.toLowerCase()] = value;
this.queryParams[key] = value;
}
}
get displayInitializingMarkets() {
return !!this.queryParams.displayinitializingmarkets;
return !!this.queryParams.displayInitializingMarkets;
}
get showMobileSignInOption() {
return !!this.queryParams.mobilesignin;
get withCCTP() {
return !!this.queryParams.withCCTP;
}
}
+23 -21
View File
@@ -1,9 +1,11 @@
import Color from 'color';
import isEmpty from 'lodash/isEmpty';
import { Candle, TradingViewBar, TradingViewSymbol } from '@/constants/candles';
import { Colors } from '@/styles/colors';
import { AppTheme } from '@/state/configs';
import { Themes } from '@/styles/themes';
export const mapCandle = ({
startedAt,
open,
@@ -47,42 +49,42 @@ export const getHistorySlice = ({
return bars.filter(({ time }) => time >= fromMs);
};
export const getWidgetOverrides = (appTheme: AppTheme) => {
const theme = Themes[appTheme];
export const getWidgetOverrides = (theme: AppTheme) => {
const colors = Colors[theme];
return {
overrides: {
'paneProperties.background': theme.layer2,
'paneProperties.horzGridProperties.color': theme.layer3,
'paneProperties.vertGridProperties.color': theme.layer3,
'paneProperties.background': Color(colors.layer2).hex(),
'paneProperties.horzGridProperties.color': Color(colors.layer3).hex(),
'paneProperties.vertGridProperties.color': Color(colors.layer3).hex(),
'paneProperties.crossHairProperties.style': 1,
'paneProperties.legendProperties.showBarChange': false,
'paneProperties.backgroundType': 'solid',
'mainSeriesProperties.style': 1,
'mainSeriesProperties.candleStyle.upColor': theme.positive,
'mainSeriesProperties.candleStyle.borderUpColor': theme.positive,
'mainSeriesProperties.candleStyle.wickUpColor': theme.positive,
'mainSeriesProperties.candleStyle.downColor': theme.negative,
'mainSeriesProperties.candleStyle.borderDownColor': theme.negative,
'mainSeriesProperties.candleStyle.wickDownColor': theme.negative,
'mainSeriesProperties.candleStyle.upColor': Color(colors.positive).hex(),
'mainSeriesProperties.candleStyle.borderUpColor': Color(colors.positive).hex(),
'mainSeriesProperties.candleStyle.wickUpColor': Color(colors.positive).hex(),
'mainSeriesProperties.candleStyle.downColor': Color(colors.negative).hex(),
'mainSeriesProperties.candleStyle.borderDownColor': Color(colors.negative).hex(),
'mainSeriesProperties.candleStyle.wickDownColor': Color(colors.negative).hex(),
'mainSeriesProperties.statusViewStyle.symbolTextSource': 'ticker',
'scalesProperties.textColor': theme.textPrimary,
'scalesProperties.backgroundColor': theme.layer2,
'scalesProperties.lineColor': theme.layer3,
'scalesProperties.textColor': Color(colors.text1).hex(),
'scalesProperties.backgroundColor': Color(colors.layer2).hex(),
'scalesProperties.lineColor': Color(colors.layer3).hex(),
},
studies_overrides: {
'volume.volume.color.0': theme.negative,
'volume.volume.color.1': theme.positive,
'volume.volume.color.0': Color(colors.negative).hex(),
'volume.volume.color.1': Color(colors.positive).hex(),
'volume.volume ma.visible': false,
'relative strength index.plot.color': theme.accent,
'relative strength index.plot.color': Color(colors.accent).hex(),
'relative strength index.plot.linewidth': 1.5,
'relative strength index.hlines background.color': '#134A9F',
},
loading_screen: {
backgroundColor: theme.layer2,
foregroundColor: theme.layer2,
backgroundColor: Color(colors.layer2).hex(),
foregroundColor: Color(colors.layer2).hex(),
},
};
};
+2 -3
View File
@@ -1,4 +1,4 @@
import { createConfig, configureChains, mainnet, Chain } from 'wagmi';
import { createConfig, configureChains, mainnet, Chain, Connector, usePublicClient } from 'wagmi';
import { goerli } from 'wagmi/chains';
import {
@@ -28,7 +28,6 @@ import {
celoAlfajores,
scroll,
kava,
sepolia,
} from 'viem/chains';
import { alchemyProvider } from 'wagmi/providers/alchemy';
@@ -56,7 +55,7 @@ import { isTruthy } from './isTruthy';
export const WAGMI_SUPPORTED_CHAINS: Chain[] = [
mainnet,
goerli,
sepolia,
arbitrum,
arbitrumGoerli,
avalanche,
+17 -38
View File
@@ -5,7 +5,7 @@ import { useEnsName } from 'wagmi';
import { useNavigate } from 'react-router-dom';
import { ButtonSize } from '@/constants/buttons';
import { TransferType } from '@/constants/abacus';
import { TransferInputField, TransferType } from '@/constants/abacus';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -26,14 +26,10 @@ import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks';
import { getOnboardingState } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs';
import abacusStateManager from '@/lib/abacus';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
import { DYDXBalancePanel } from './rewards/DYDXBalancePanel';
import { MigratePanel } from './rewards/MigratePanel';
import { GovernancePanel } from './rewards/GovernancePanel';
import { StakingPanel } from './rewards/StakingPanel';
const ENS_CHAIN_ID = 1; // Ethereum
const Profile = () => {
@@ -180,26 +176,7 @@ const Profile = () => {
/>
</Styled.EqualGrid>
<MigratePanel />
<DYDXBalancePanel />
<Styled.EqualGrid>
<Styled.RewardsPanel
slotHeaderContent="Trading Rewards"
href={`/${chainTokenLabel}`}
hasSeparator
>
<Styled.Details
items={[
{
key: 'week-rewards',
label: stringGetter({ key: STRING_KEYS.THIS_WEEK }),
value: '-',
},
]}
layout="grid"
/>
</Styled.RewardsPanel>
<Panel
slotHeaderContent={stringGetter({ key: STRING_KEYS.FEES })}
href={`${AppRoute.Portfolio}/${PortfolioRoute.Fees}`}
@@ -214,6 +191,19 @@ const Profile = () => {
layout="grid"
/>
</Panel>
<Styled.RewardsPanel
slotHeaderContent={stringGetter({ key: STRING_KEYS.REWARDS })}
href={`/${chainTokenLabel}`}
hasSeparator
>
<Styled.Details
items={[
{ key: 'maker', label: stringGetter({ key: STRING_KEYS.YOU_WILL_EARN }), value: '-' },
{ key: 'taker', label: stringGetter({ key: STRING_KEYS.EPOCH_ENDS_IN }), value: '-' },
]}
layout="grid"
/>
</Styled.RewardsPanel>
</Styled.EqualGrid>
<Styled.TablePanel
@@ -231,9 +221,6 @@ const Profile = () => {
withInnerBorders={false}
/>
</Styled.TablePanel>
<GovernancePanel />
<StakingPanel />
</Styled.MobileProfileLayout>
);
};
@@ -247,7 +234,6 @@ Styled.MobileProfileLayout = styled.div`
gap: 1rem;
padding: 1.25rem 0.9rem;
max-width: 100vw;
`;
Styled.Header = styled.header`
@@ -264,8 +250,8 @@ Styled.ProfileIcon = styled.div`
border-radius: 50%;
background: linear-gradient(
135deg,
${({ theme }) => theme.profileYellow} 0%,
${({ theme }) => theme.profileRed} 100%
var(--theme-classic-color-yellow) 0%,
var(--theme-classic-color-red) 100%
);
`;
@@ -341,13 +327,6 @@ Styled.Details = styled(Details)`
`;
Styled.RewardsPanel = styled(Panel)`
align-self: flex-start;
&,
> * {
height: 100%;
}
dl {
--details-grid-numColumns: 1;
}
+4 -25
View File
@@ -17,14 +17,11 @@ import { TransferHistoryTable } from '@/views/tables/TransferHistoryTable';
import { Button } from '@/components/Button';
import { Icon, IconName } from '@/components/Icon';
import { NavigationMenu } from '@/components/NavigationMenu';
import { Tag, TagType } from '@/components/Tag';
import { WithSidebar } from '@/components/WithSidebar';
import { getOnboardingState, getSubaccount, getTradeInfoNumbers } from '@/state/accountSelectors';
import { getOnboardingState, getSubaccount } from '@/state/accountSelectors';
import { openDialog } from '@/state/dialogs';
import { shortenNumberForDisplay } from '@/lib/numbers';
import { PortfolioNavMobile } from './PortfolioNavMobile';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
@@ -45,10 +42,6 @@ export default () => {
const { freeCollateral } = useSelector(getSubaccount, shallowEqual) || {};
const { nativeTokenBalance } = useAccountBalance();
const { numTotalPositions, numTotalOpenOrders } = useSelector(getTradeInfoNumbers, shallowEqual) || {};
const numPositions = shortenNumberForDisplay(numTotalPositions);
const numOrders = shortenNumberForDisplay(numTotalOpenOrders);
const usdcBalance = freeCollateral?.current || 0;
useDocumentTitle(stringGetter({ key: STRING_KEYS.PORTFOLIO }));
@@ -125,28 +118,14 @@ export default () => {
},
{
value: PortfolioRoute.Positions,
slotBefore: <Styled.Icon iconName={IconName.Positions} />,
label: (
<>
{stringGetter({ key: STRING_KEYS.POSITIONS })}
{numPositions > 0 && (
<Tag type={TagType.Number}> {numPositions} </Tag>
)}
</>
),
slotBefore: <Styled.Icon iconName={IconName.Cube} />,
label: stringGetter({ key: STRING_KEYS.POSITIONS }),
href: PortfolioRoute.Positions,
},
{
value: PortfolioRoute.Orders,
slotBefore: <Styled.Icon iconName={IconName.OrderPending} />,
label: (
<>
{stringGetter({ key: STRING_KEYS.ORDERS })}
{numOrders > 0 && (
<Tag type={TagType.Number}> {numOrders} </Tag>
)}
</>
),
label: stringGetter({ key: STRING_KEYS.ORDERS }),
href: PortfolioRoute.Orders,
},
{
+12 -2
View File
@@ -2,6 +2,7 @@ import type { ElementType } from 'react';
import styled, { AnyStyledComponent } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import breakpoints from '@/styles/breakpoints';
import { layoutMixins } from '@/styles/layoutMixins';
import { useAccountBalance, useAccounts, useTokenConfigs, useStringGetter } from '@/hooks';
@@ -32,7 +33,7 @@ export const DYDXBalancePanel = () => {
const { chainTokenLabel } = useTokenConfigs();
return (
<Panel
<Styled.Panel
slotHeader={
<Styled.Header>
<Styled.Title>
@@ -108,12 +109,21 @@ export const DYDXBalancePanel = () => {
]}
/>
</Styled.Content>
</Panel>
</Styled.Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)`
--panel-paddingX: 1.5rem;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`;
Styled.Header = styled.div`
${layoutMixins.spacedRow}
gap: 1rem;
-75
View File
@@ -1,75 +0,0 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { DialogTypes } from '@/constants/dialogs';
import { useStringGetter, useURLConfigs } from '@/hooks';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
export const GovernancePanel = () => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { governanceLearnMore } = useURLConfigs();
return (
<Panel
slotHeaderContent={
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
}
slotRight={
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
<Link href={governanceLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
a {
display: inline;
::before {
content: ' ';
}
}
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Arrow = styled.div`
padding-right: 1.5rem;
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
margin-bottom: -1rem;
`;
+10 -5
View File
@@ -70,12 +70,10 @@ const EstimatedRewards = () => {
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}?n=${SEASON_NUMBER}`);
return (await resp.json())?.incentivePoints;
},
onError: (error: Error) => log('LaunchIncentives/fetchPoints', error),
@@ -153,8 +151,15 @@ const LaunchIncentivesContent = () => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Panel = styled(Panel)`
--panel-paddingY: 1rem;
--panel-paddingX: 1.5rem;
background-color: var(--color-layer-4);
width: 100%;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
}
`;
Styled.ForV4 = styled.span`
@@ -218,7 +223,7 @@ Styled.Column = styled.div`
Styled.EstimatedRewardsCard = styled.div`
${layoutMixins.spacedRow}
padding: 1rem 1.25rem;
min-width: 19rem;
min-width: 21.25rem;
height: calc(100% - calc(1.5rem * 2));
margin: 1.5rem;
+12 -7
View File
@@ -7,6 +7,8 @@ import { ButtonAction, ButtonSize, ButtonType } from '@/constants/buttons';
import { useAccountBalance, useBreakpoints, useStringGetter } from '@/hooks';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { Details } from '@/components/Details';
@@ -26,7 +28,7 @@ import { MustBigNumber } from '@/lib/numbers';
const TOKEN_MIGRATION_LEARN_MORE_LINK =
'https://www.dydx.foundation/blog/update-on-exploring-the-future-of-dydx';
export const MigratePanel = ({ className }: { className?: string }) => {
export const MigratePanel = () => {
const { isNotTablet } = useBreakpoints();
const stringGetter = useStringGetter();
@@ -43,7 +45,6 @@ export const MigratePanel = ({ className }: { className?: string }) => {
return isNotTablet ? (
<Styled.MigratePanel
className={className}
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.MIGRATE })}</Styled.Title>}
slotRight={
<Styled.MigrateAction>
@@ -73,7 +74,6 @@ export const MigratePanel = ({ className }: { className?: string }) => {
</Styled.MigratePanel>
) : (
<Styled.MigratePanel
className={className}
slotHeader={
<Styled.MobileMigrateHeader>
<h3>
@@ -134,11 +134,17 @@ export const MigratePanel = ({ className }: { className?: string }) => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.MigratePanel = styled(Panel)`
--panel-paddingX: 1.5rem;
width: 100%;
background-image: url('/dots-background.svg');
background-position: right;
background-repeat: no-repeat;
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`;
Styled.Title = styled.h3`
@@ -146,15 +152,14 @@ Styled.Title = styled.h3`
color: var(--color-text-2);
padding: var(--panel-paddingY) var(--panel-paddingX) 0;
margin-bottom: -0.5rem;
`;
Styled.MigrateAction = styled.div`
${layoutMixins.flexEqualColumns}
align-items: center;
margin-right: 1rem;
gap: 1rem;
padding: 1rem;
margin: 1rem;
width: 100%;
background-color: var(--color-layer-2);
@@ -192,7 +197,7 @@ Styled.MobileMigrateHeader = styled.div`
font: var(--font-small-book);
color: var(--color-text-0);
padding: 1rem 1rem 0;
padding: 1rem 1.5rem 0;
h3 {
${layoutMixins.inlineRow}
@@ -234,4 +239,4 @@ Styled.InlineRow = styled.div`
${layoutMixins.inlineRow}
color: var(--color-text-0);
--link-color: var(--color-text-1);
`;
`;
+1 -7
View File
@@ -1,6 +1,5 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { breakpoints } from '@/styles';
import { STRING_KEYS } from '@/constants/localization';
import { layoutMixins } from '@/styles/layoutMixins';
import { useStringGetter } from '@/hooks';
@@ -52,7 +51,6 @@ export const RewardsHelpPanel = () => {
const Styled: Record<string, AnyStyledComponent> = {};
Styled.HelpCard = styled(Panel)`
--panel-content-paddingX: 0;
--panel-content-paddingY: 0;
width: 100%;
height: max-content;
@@ -66,15 +64,11 @@ Styled.Header = styled.div`
${layoutMixins.spacedRow}
gap: 1ch;
padding: 1rem 1rem;
padding: 1.25rem 1.5rem;
border-bottom: var(--border-width) solid var(--border-color);
font: var(--font-small-book);
@media ${breakpoints.notTablet} {
padding: 1.5rem 1.25rem;
}
h3 {
font: var(--font-medium-book);
color: var(--color-text-2);
+84 -38
View File
@@ -1,43 +1,51 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { STRING_KEYS } from '@/constants/localization';
import { AppRoute } from '@/constants/routes';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { DialogTypes } from '@/constants/dialogs';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
import { breakpoints } from '@/styles';
import { layoutMixins } from '@/styles/layoutMixins';
import { BackButton } from '@/components/BackButton';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
import { DYDXBalancePanel } from './DYDXBalancePanel';
import { MigratePanel } from './MigratePanel';
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
import { RewardsHelpPanel } from './RewardsHelpPanel';
import { GovernancePanel } from './GovernancePanel';
import { StakingPanel } from './StakingPanel';
const RewardsPage = () => {
const dispatch = useDispatch();
const stringGetter = useStringGetter();
const { governanceLearnMore, stakingLearnMore } = useURLConfigs();
const { isTablet, isNotTablet } = useBreakpoints();
const navigate = useNavigate();
const panelArrow = (
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
);
return (
<Styled.Page>
{isTablet && (
<Styled.MobileHeader>
<BackButton onClick={() => navigate(AppRoute.Profile)} />
{stringGetter({ key: STRING_KEYS.TRADING_REWARDS })}
</Styled.MobileHeader>
)}
{import.meta.env.VITE_V3_TOKEN_ADDRESS && isNotTablet && <MigratePanel />}
{import.meta.env.VITE_V3_TOKEN_ADDRESS && <MigratePanel />}
{isTablet ? (
<LaunchIncentivesPanel />
<>
<LaunchIncentivesPanel />
<DYDXBalancePanel />
</>
) : (
<Styled.PanelRowIncentivesAndBalance>
<LaunchIncentivesPanel />
@@ -45,14 +53,33 @@ const RewardsPage = () => {
</Styled.PanelRowIncentivesAndBalance>
)}
{isNotTablet && (
<Styled.PanelRow>
<GovernancePanel />
<StakingPanel />
</Styled.PanelRow>
)}
<Styled.PanelRow>
<Styled.Panel
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>}
slotRight={panelArrow}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
<Link href={governanceLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Styled.Panel>
<RewardsHelpPanel />
<Styled.Panel
slotHeader={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
slotRight={panelArrow}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Styled.Panel>
</Styled.PanelRow>
</Styled.Page>
);
};
@@ -68,35 +95,45 @@ Styled.Page = styled.div`
align-items: center;
> * {
--content-max-width: 80rem;
--content-max-width: 70rem;
max-width: min(calc(100vw - 4rem), var(--content-max-width));
}
@media ${breakpoints.tablet} {
--stickyArea-topHeight: var(--page-header-height-mobile);
padding: 0 1rem 1rem;
padding: 1.25rem;
> * {
max-width: calc(100vw - 2rem);
max-width: calc(100vw - 2.5rem);
width: 100%;
}
}
`;
Styled.MobileHeader = styled.header`
${layoutMixins.contentSectionDetachedScrollable}
${layoutMixins.stickyHeader}
z-index: 2;
padding: 1.25rem 0;
margin-bottom: -1.5rem;
Styled.Panel = styled(Panel)`
--panel-paddingX: 1.5rem;
font: var(--font-large-medium);
color: var(--color-text-2);
background-color: var(--color-layer-2);
@media ${breakpoints.tablet} {
--panel-paddingY: 1.5rem;
--panel-content-paddingY: 1rem;
}
`;
Styled.Panel = styled(Panel)`
height: fit-content;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
padding: 1rem 1.5rem 0;
`;
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
a {
display: inline;
::before {
content: ' ';
}
}
`;
Styled.PanelRow = styled.div`
@@ -112,3 +149,12 @@ Styled.PanelRow = styled.div`
Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)`
grid-template-columns: 2fr 1fr;
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Arrow = styled.div`
padding: 1rem;
`;
-73
View File
@@ -1,73 +0,0 @@
import styled, { AnyStyledComponent } from 'styled-components';
import { useDispatch } from 'react-redux';
import { ButtonAction, ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { DialogTypes } from '@/constants/dialogs';
import { useStringGetter, useURLConfigs } from '@/hooks';
import { Panel } from '@/components/Panel';
import { IconName } from '@/components/Icon';
import { IconButton } from '@/components/IconButton';
import { Link } from '@/components/Link';
import { openDialog } from '@/state/dialogs';
export const StakingPanel = () => {
const stringGetter = useStringGetter();
const dispatch = useDispatch();
const { stakingLearnMore } = useURLConfigs();
return (
<Panel
slotHeaderContent={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
slotRight={
<Styled.Arrow>
<Styled.IconButton
action={ButtonAction.Base}
iconName={IconName.Arrow}
size={ButtonSize.Small}
/>
</Styled.Arrow>
}
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
>
<Styled.Description>
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
{stringGetter({ key: STRING_KEYS.LEARN_MORE })}
</Link>
</Styled.Description>
</Panel>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Description = styled.div`
color: var(--color-text-0);
--link-color: var(--color-text-1);
a {
display: inline;
::before {
content: ' ';
}
}
`;
Styled.IconButton = styled(IconButton)`
color: var(--color-text-0);
--color-border: var(--color-layer-6);
`;
Styled.Arrow = styled.div`
padding-right: 1.5rem;
`;
Styled.Title = styled.h3`
font: var(--font-medium-book);
color: var(--color-text-2);
margin-bottom: -1rem;
`;
+36 -6
View File
@@ -1,4 +1,6 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import styled, { AnyStyledComponent } from 'styled-components';
import { TradeLayouts } from '@/constants/layout';
import { STRING_KEYS } from '@/constants/localization';
@@ -7,9 +9,11 @@ import { useStringGetter } from '@/hooks';
import { Tabs } from '@/components/Tabs';
import { CanvasOrderbook } from '@/views/CanvasOrderbook';
import { Orderbook, orderbookMixins, OrderbookScrollBehavior } from '@/views/tables/Orderbook';
import { LiveTrades } from '@/views/tables/LiveTrades';
import { getCurrentMarketId } from '@/state/perpetualsSelectors';
enum Tab {
Orderbook = 'Orderbook',
Trades = 'Trades',
@@ -23,22 +27,29 @@ const HISTOGRAM_SIDES_BY_LAYOUT = {
export const VerticalPanel = ({ tradeLayout }: { tradeLayout: TradeLayouts }) => {
const stringGetter = useStringGetter();
const [value, setValue] = useState(Tab.Orderbook);
const [scrollBehavior, setScrollBehavior] = useState<OrderbookScrollBehavior>('snapToCenter');
const marketId = useSelector(getCurrentMarketId);
useEffect(() => {
setScrollBehavior('snapToCenter');
}, [marketId]);
return (
<Tabs
<Styled.Tabs
fullWidthTabs
value={value}
onValueChange={(value: Tab) => {
setScrollBehavior('snapToCenter');
setValue(value);
}}
items={[
{
asChild: true,
content: <CanvasOrderbook histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
content: <Orderbook histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SHORT }),
value: Tab.Orderbook,
forceMount: true,
},
{
content: <LiveTrades histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
@@ -46,7 +57,26 @@ export const VerticalPanel = ({ tradeLayout }: { tradeLayout: TradeLayouts }) =>
value: Tab.Trades,
},
]}
onWheel={() => setScrollBehavior('free')}
withTransitions={false}
isShowingOrderbook={value === Tab.Orderbook}
scrollBehavior={scrollBehavior}
// style={{
// scrollSnapType: {
// 'snapToCenter': 'y mandatory',
// 'free': 'none',
// 'snapToCenterUnlessHovered': 'none',
// }[scrollBehavior],
// }}
/>
);
};
const Styled: Record<string, AnyStyledComponent> = {};
Styled.Tabs = styled(Tabs)<{
isShowingOrderbook: boolean;
scrollBehavior: OrderbookScrollBehavior;
}>`
${orderbookMixins.scrollArea}
`;
+8 -1
View File
@@ -1,8 +1,10 @@
import { createSelector } from 'reselect';
import { SubaccountPosition } from '@/constants/abacus';
import { OnboardingState, OnboardingSteps } from '@/constants/account';
import {
getExistingOpenPositions,
getOnboardingGuards,
getOnboardingState,
getSubaccountId,
@@ -58,8 +60,13 @@ export const calculateIsAccountViewOnly = createSelector(
);
/**
* @description calculate whether the subaccount has uncommitted positions
* @description calculate whether the subaccount has open positions
*/
export const calculateHasOpenPositions = createSelector(
[getExistingOpenPositions],
(openPositions?: SubaccountPosition[]) => (openPositions?.length || 0) > 0
);
export const calculateHasUncommittedOrders = createSelector(
[getUncommittedOrderClientIds],
(uncommittedOrderClientIds: number[]) => uncommittedOrderClientIds.length > 0
+5 -11
View File
@@ -2,7 +2,6 @@ import { OrderSide } from '@dydxprotocol/v4-client-js';
import { createSelector } from 'reselect';
import {
type AbacusOrderStatuses,
type SubaccountOrder,
type SubaccountFill,
type SubaccountFundingPayment,
@@ -44,6 +43,7 @@ export const getSubaccountBuyingPower = (state: RootState) => state.account.suba
export const getSubaccountEquity = (state: RootState) => state.account.subaccount?.equity;
export const getSubaccountHistoricalPnl = (state: RootState) => state.account?.historicalPnl;
/**
* @param state
* @returns list of a subaccount's open positions. Each item in the list is an open position in a different market.
@@ -258,14 +258,6 @@ export const getCurrentMarketFundingPayments = createSelector(
!currentMarketId ? [] : marketFundingPayments[currentMarketId]
);
/**
* @param state
* @returns boolean on whether an order status is considered open
*/
const isOpenOrderStatus = (status: AbacusOrderStatuses) => {
return status !== AbacusOrderStatus.filled && status !== AbacusOrderStatus.cancelled;
};
/**
* @param state
* @returns Total numbers of the subaccount's open positions, open orders and fills
@@ -274,7 +266,8 @@ export const getTradeInfoNumbers = createSelector(
[getExistingOpenPositions, getSubaccountOrders, getSubaccountFills, getSubaccountFundingPayments],
(positions, orders, fills, fundingPayments) => ({
numTotalPositions: positions?.length,
numTotalOpenOrders: orders?.filter((order) => isOpenOrderStatus(order.status)).length,
numTotalOpenOrders: orders?.filter((order) => order.status !== AbacusOrderStatus.cancelled)
.length,
numTotalFills: fills?.length,
numTotalFundingPayments: fundingPayments?.length,
})
@@ -288,7 +281,8 @@ export const getCurrentMarketTradeInfoNumbers = createSelector(
[getCurrentMarketOrders, getCurrentMarketFills, getCurrentMarketFundingPayments],
(marketOrders, marketFills, marketFundingPayments) => {
return {
numOpenOrders: marketOrders?.filter((order) => isOpenOrderStatus(order.status)).length,
numOpenOrders: marketOrders?.filter((order) => order.status !== AbacusOrderStatus.cancelled)
.length,
numFills: marketFills?.length,
numFundingPayments: marketFundingPayments?.length,
};
-4
View File
@@ -96,8 +96,6 @@ export const useTradeFormData = () => {
needsGoodUntil,
needsPostOnly,
needsReduceOnly,
postOnlyTooltip,
reduceOnlyTooltip,
timeInForceOptions,
} = tradeOptions || {};
@@ -113,8 +111,6 @@ export const useTradeFormData = () => {
needsGoodUntil,
needsPostOnly,
needsReduceOnly,
postOnlyTooltip,
reduceOnlyTooltip,
timeInForceOptions,
tradeErrors,
+5 -26
View File
@@ -14,7 +14,6 @@ import { LocalStorageKey } from '@/constants/localStorage';
import { DEFAULT_MARKETID } from '@/constants/markets';
import { getLocalStorage } from '@/lib/localStorage';
import { processOrderbookToCreateMap } from '@/lib/orderbookHelpers';
interface CandleDataByMarket {
data: Record<string, Candle[]>;
@@ -27,13 +26,6 @@ export interface PerpetualsState {
liveTrades?: Record<string, MarketTrade[]>;
markets?: Record<string, PerpetualMarket>;
orderbooks?: Record<string, MarketOrderbook>;
orderbooksMap?: Record<
string,
{
asks: Record<string, number>;
bids: Record<string, number>;
}
>;
historicalFundings: Record<string, MarketHistoricalFunding[]>;
}
@@ -43,7 +35,6 @@ const initialState: PerpetualsState = {
liveTrades: {},
markets: undefined,
orderbooks: undefined,
orderbooksMap: undefined,
historicalFundings: {},
};
@@ -105,24 +96,12 @@ export const perpetualsSlice = createSlice({
setOrderbook: (
state: PerpetualsState,
action: PayloadAction<{ orderbook?: Nullable<MarketOrderbook>; marketId: string }>
) => {
state.orderbooks = merge({}, state.orderbooks, {
) => ({
...state,
orderbooks: merge({}, state.orderbooks, {
[action.payload.marketId]: action.payload.orderbook,
});
const { newAsks, newBids } = processOrderbookToCreateMap({
orderbookMap: state.orderbooksMap?.[action.payload.marketId],
newOrderbook: action.payload.orderbook,
});
state.orderbooksMap = {
...(state.orderbooksMap ?? {}),
[action.payload.marketId]: {
asks: newAsks,
bids: newBids,
},
};
},
}),
}),
setTvChartResolution: (
state: PerpetualsState,
action: PayloadAction<{ marketId: string; resolution: string }>
+1 -6
View File
@@ -26,12 +26,7 @@ export const calculateFundingRateHistory = createSelector(
return data.map(({ effectiveAtMilliseconds, rate }) => ({
time: effectiveAtMilliseconds,
fundingRate: rate,
direction:
rate === 0
? FundingDirection.None
: rate < 0
? FundingDirection.ToLong
: FundingDirection.ToShort,
direction: rate < 0 ? FundingDirection.ToLong : FundingDirection.ToShort,
}));
}
);
+13 -9
View File
@@ -7,6 +7,7 @@ import { mapCandle } from '@/lib/tradingView/utils';
import type { RootState } from './_store';
/**
* @param state
* @returns marketId of the market the user is currently viewing
*/
export const getCurrentMarketId = (state: RootState) => state.perpetuals.currentMarketId;
@@ -20,6 +21,7 @@ export const getCurrentMarketAssetId = (state: RootState) => {
};
/**
* @param state
* @returns Record of PerpetualMarket indexed by MarketId
*/
export const getPerpetualMarkets = (state: RootState) => state.perpetuals.markets;
@@ -32,11 +34,13 @@ export const getMarketData = (marketId: string) => (state: RootState) =>
getPerpetualMarkets(state)?.[marketId];
/**
* @param state
* @returns marketIds of all markets
*/
export const getMarketIds = (state: RootState) => Object.keys(getPerpetualMarkets(state) ?? []);
/**
* @param state
* @returns PerpetualMarket data of the market the user is currently viewing
*/
export const getCurrentMarketData = (state: RootState) => {
@@ -45,6 +49,7 @@ export const getCurrentMarketData = (state: RootState) => {
};
/**
* @param state
* @returns MarketConfig data of the market the user is currently viewing
*/
export const getCurrentMarketConfig = (state: RootState) => getCurrentMarketData(state)?.configs;
@@ -57,11 +62,13 @@ export const getMarketConfig = (marketId: string) => (state: RootState) =>
getMarketData(marketId)(state)?.configs;
/**
* @param state
* @returns Record of list of MarketTrades indexed by marketId
*/
export const getLiveTrades = (state: RootState) => state.perpetuals.liveTrades;
/**
* @param state
* @returns List of MarketTrades of the market the user is currently viewing
*/
export const getCurrentMarketLiveTrades = (state: RootState) => {
@@ -71,11 +78,13 @@ export const getCurrentMarketLiveTrades = (state: RootState) => {
};
/**
* @param state
* @returns Record of subscribed or previously subscribed Orderbook data, indexed by marketId.
*/
export const getOrderbooks = (state: RootState) => state.perpetuals.orderbooks;
/**
* @param state
* @returns Orderbook data for the market the user is currently viewing
*/
export const getCurrentMarketOrderbook = (state: RootState) => {
@@ -85,20 +94,13 @@ export const getCurrentMarketOrderbook = (state: RootState) => {
};
/**
* @returns Orderbook data as a Map of price and size for the current market
*/
export const getCurrentMarketOrderbookMap = (state: RootState) => {
const orderbookMap = state.perpetuals.orderbooksMap;
const currentMarketId = getCurrentMarketId(state);
return orderbookMap?.[currentMarketId || ''];
};
/**
* @param state
* @returns Record of historical funding indexed by marketId.
*/
export const getHistoricalFundings = (state: RootState) => state.perpetuals.historicalFundings;
/**
* @param state
* @returns Historical funding data for the market the user is currently viewing
*/
export const getCurrentMarketHistoricalFundings = createSelector(
@@ -108,6 +110,7 @@ export const getCurrentMarketHistoricalFundings = createSelector(
);
/**
* @param state
* @returns Mid market price for the market the user is currently viewing
*/
export const getCurrentMarketMidMarketPrice = (state: RootState) => {
@@ -147,6 +150,7 @@ export const getSelectedResolutionForMarket = (marketId: string) => (state: Root
state.perpetuals.candles?.[marketId]?.selectedResolution;
/**
* @param state
* @returns Current market's next funding rate
*/
export const getCurrentMarketNextFundingRate = createSelector(
+180
View File
@@ -0,0 +1,180 @@
:root {
/* Parameters */
/*
--layer-base-hue
--layer-base-saturation
--layer-base-lightness
--color-text-0
--color-text-1
--color-text-2
--color-accent
--color-positive
--color-negative
--color-success
--color-warning
--color-error
--color-favorite
*/
/* Computed: Layers */
--color-layer-0: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) - 12%)
);
--color-layer-1: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) - 8%)
);
--color-layer-2: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) - 4%)
);
--color-layer-3: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
var(--layer-base-lightness)
);
--color-layer-4: hsl(
var(--layer-base-hue),
var(--layer-base-saturation),
calc(var(--layer-base-lightness) + 4%)
);
--color-layer-5: hsl(
var(--layer-base-hue),
calc(var(--layer-base-saturation) - 2%),
calc(var(--layer-base-lightness) + 7%)
);
--color-layer-6: hsl(
var(--layer-base-hue),
calc(var(--layer-base-saturation) - 4%),
calc(var(--layer-base-lightness) + 10%)
);
--color-layer-7: hsla(var(--layer-base-hue), 0%, 100%, 0.07);
--color-gradient-base-0: hsl(
var(--layer-base-hue),
calc(var(--layer-base-saturation) - 5%),
var(--layer-base-lightness)
);
--color-gradient-base-1: hsl(
var(--layer-base-hue),
calc(var(--layer-base-saturation) - 1%),
calc(var(--layer-base-lightness) + 7%)
);
/* Computed: Borders */
--color-border: var(--color-layer-5);
--color-border-white: hsla(0, 0%, 100%, 0.2);
--color-border-red: hsla(360, 73%, 61%, 0.2);
}
/* Theme: Classic (default) */
:root {
/* Constants */
--theme-classic-hue-purple: 240;
--theme-classic-color-green: hsl(159, 67%, 39%);
--theme-classic-color-yellow: hsl(36, 100%, 64%);
--theme-classic-color-red: hsl(360, 73%, 61%);
/* Parameters */
--layer-base-hue: var(--theme-classic-hue-purple);
--layer-base-saturation: 20%;
--layer-base-lightness: 16%;
--color-text-0: hsl(245, 11%, 55%);
--color-text-1: hsl(244, 18%, 81%);
--color-text-2: hsl(240, 43%, 99%);
--color-accent: hsl(var(--theme-classic-hue-purple), 100%, 70%);
--color-positive: var(--theme-classic-color-green);
--color-negative: var(--theme-classic-color-red);
--color-success: var(--theme-classic-color-green);
--color-warning: var(--theme-classic-color-yellow);
--color-error: var(--theme-classic-color-red);
--color-favorite: var(--theme-classic-color-yellow);
--color-white: #fff;
--color-black: #000;
--color-gradient-positive: hsla(158, 49%, 48%, 0.16);
--color-gradient-negative: hsla(0, 100%, 66%, 0.16);
--color-accent-faded: hsla(var(--theme-classic-hue-purple), 100%, 70%, 0.16);
--color-risk-low: var(--theme-classic-color-green);
--color-risk-medium: var(--theme-classic-color-yellow);
--color-risk-high: var(--theme-classic-color-red);
}
/* Theme: Light */
:root.theme-light {
/* Constants */
--theme-light-hue-purple: 234;
--theme-light-color-green: hsl(159, 67%, 39%);
--theme-light-color-yellow: hsl(36, 100%, 64%);
--theme-light-color-red: hsl(360, 73%, 61%);
/* Parameters */
--layer-base-hue: var(--theme-light-hue-purple);
--layer-base-saturation: 0%;
--layer-base-lightness: 86%;
--color-text-0: hsl(0, 0%, 55%);
--color-text-1: hsl(0, 0%, 40%);
--color-text-2: hsl(0, 0%, 5%);
--color-accent: hsl(var(--theme-light-hue-purple), 100%, 70%);
--color-positive: var(--theme-light-color-green);
--color-negative: var(--theme-light-color-red);
--color-success: var(--theme-light-color-green);
--color-warning: var(--theme-light-color-yellow);
--color-error: var(--theme-light-color-red);
--color-favorite: var(--theme-light-color-yellow);
}
/* Theme: Dark */
:root.theme-dark {
/* Constants */
--theme-dark-hue-purple: 240;
--theme-dark-color-green: hsl(159, 67%, 39%);
--theme-dark-color-yellow: hsl(36, 100%, 64%);
--theme-dark-color-red: hsl(360, 73%, 61%);
/* Parameters */
--layer-base-hue: 0;
--layer-base-saturation: 0%;
--layer-base-lightness: 16%;
--color-text-0: hsl(0, 0%, 47%);
--color-text-1: hsl(0, 0%, 67%);
--color-text-2: hsl(0, 0%, 100%);
--color-accent: hsl(var(--theme-dark-hue-purple), 100%, 70%);
--color-positive: var(--theme-dark-color-green);
--color-negative: var(--theme-dark-color-red);
--color-success: var(--theme-dark-color-green);
--color-warning: var(--theme-dark-color-yellow);
--color-error: var(--theme-dark-color-red);
--color-favorite: var(--theme-dark-color-yellow);
}
+125
View File
@@ -0,0 +1,125 @@
import { css } from 'styled-components';
import { type RiskLevels } from '@/constants/abacus';
import { AppTheme } from '@/state/configs';
const BaseElements = {
[AppTheme.Classic]: {
hue: 240,
saturation: 20,
lightness: 16,
},
[AppTheme.Dark]: {
hue: 0,
saturation: 0,
lightness: 16,
},
[AppTheme.Light]: {
hue: 234,
saturation: 0,
lightness: 86,
},
};
const LayerColors = ({ theme }: { theme: AppTheme }) => {
const { hue, saturation, lightness } = BaseElements[theme];
return {
layer0: `hsl(${hue}, ${saturation}%, ${lightness - 12}%)`,
layer1: `hsl(${hue}, ${saturation}%, ${lightness - 8}%)`,
layer2: `hsl(${hue}, ${saturation}%, ${lightness - 4}%)`,
layer3: `hsl(${hue}, ${saturation}%, ${lightness}%)`,
layer4: `hsl(${hue}, ${saturation}%, ${lightness + 4}%)`,
layer5: `hsl(${hue}, ${saturation - 2}%, ${lightness + 7}%)`,
layer6: `hsl(${hue}, ${saturation - 4}%, ${lightness + 10}%)`,
};
};
const AccentColor = ({ theme }: { theme: AppTheme }) => {
const purpleHue = {
Classic: 240,
Dark: 240,
Light: 234,
}[theme];
return {
accent: `hsl(${purpleHue}, 100%, 70%)`,
};
};
export const Colors = {
[AppTheme.Classic]: {
...LayerColors({ theme: AppTheme.Classic }),
text0: 'hsl(245, 11%, 55%)',
text1: 'hsl(244, 18%, 81%)',
text2: 'hsl(240, 43%, 99%)',
...AccentColor({ theme: AppTheme.Classic }),
green: 'hsl(159, 67%, 39%)',
yellow: 'hsl(36, 100%, 64%)',
red: 'hsl(360, 73%, 61%)',
positive: 'hsl(159, 67%, 39%)',
negative: 'hsl(360, 73%, 61%)',
success: 'hsl(159, 67%, 39%)',
warning: 'hsl(36, 100%, 64%)',
error: 'hsl(360, 73%, 61%)',
favorite: 'hsl(36, 100%, 64%)',
},
[AppTheme.Dark]: {
...LayerColors({ theme: AppTheme.Dark }),
text0: 'hsl(0, 0%, 47%)',
text1: 'hsl(0, 0%, 67%)',
text2: 'hsl(0, 0%, 100%)',
...AccentColor({ theme: AppTheme.Dark }),
green: 'hsl(159, 67%, 39%)',
yellow: 'hsl(36, 100%, 64%)',
red: 'hsl(360, 73%, 61%)',
positive: 'hsl(159, 67%, 39%)',
negative: 'hsl(360, 73%, 61%)',
success: 'hsl(159, 67%, 39%)',
warning: 'hsl(36, 100%, 64%)',
error: 'hsl(360, 73%, 61%)',
favorite: 'hsl(36, 100%, 64%)',
},
[AppTheme.Light]: {
...LayerColors({ theme: AppTheme.Light }),
text0: 'hsl(0, 0%, 55%)',
text1: 'hsl(0, 0%, 40%)',
text2: 'hsl(0, 0%, 5%)',
...AccentColor({ theme: AppTheme.Light }),
green: 'hsl(159, 67%, 39%)',
yellow: 'hsl(36, 100%, 64%)',
red: 'hsl(360, 73%, 61%)',
positive: 'hsl(159, 67%, 39%)',
negative: 'hsl(360, 73%, 61%)',
success: 'hsl(159, 67%, 39%)',
warning: 'hsl(36, 100%, 64%)',
error: 'hsl(360, 73%, 61%)',
favorite: 'hsl(36, 100%, 64%)',
},
};
export const UsageColorFromRiskLevel = (riskLevel: RiskLevels) =>
({
low: css`
color: var(--color-risk-low);
`,
medium: css`
color: var(--color-risk-medium);
`,
high: css`
color: var(--color-risk-high);
`,
}[riskLevel.name]);
+1 -1
View File
@@ -21,7 +21,7 @@ export const formMixins: Record<
--input-radius: 0.5em;
--input-height: var(--form-input-height);
--input-width: 100%;
--input-backgroundColor: ${({ theme }) => theme.inputBackground};
--input-backgroundColor: var(--color-layer-4);
--input-borderColor: var(--color-layer-6);
${layoutMixins.row}
-42
View File
@@ -1,42 +0,0 @@
import { createGlobalStyle } from 'styled-components';
export const GlobalStyle = createGlobalStyle`
:root {
--color-layer-0: ${({ theme }) => theme.layer0};
--color-layer-1: ${({ theme }) => theme.layer1};
--color-layer-2: ${({ theme }) => theme.layer2};
--color-layer-3: ${({ theme }) => theme.layer3};
--color-layer-4: ${({ theme }) => theme.layer4};
--color-layer-5: ${({ theme }) => theme.layer5};
--color-layer-6: ${({ theme }) => theme.layer6};
--color-layer-7: ${({ theme }) => theme.layer7};
--color-border: ${({ theme }) => theme.borderDefault};
--color-border-white: ${({ theme }) => theme.borderButton};
--color-border-red: ${({ theme }) => theme.borderDestructive};
--color-text-0: ${({ theme }) => theme.textTertiary};
--color-text-1: ${({ theme }) => theme.textSecondary};
--color-text-2: ${({ theme }) => theme.textPrimary};
--color-gradient-base-0: ${({ theme }) => theme.gradientBase0};
--color-gradient-base-1: ${({ theme }) => theme.gradientBase1};
--color-accent: ${({ theme }) => theme.accent};
--color-accent-faded: ${({ theme }) => theme.accentFaded};
--color-favorite: ${({ theme }) => theme.favorite};
--color-success: ${({ theme }) => theme.success};
--color-warning: ${({ theme }) => theme.warning};
--color-error: ${({ theme }) => theme.error};
--color-positive: ${({ theme }) => theme.positive};
--color-negative: ${({ theme }) => theme.negative};
--color-gradient-positive: ${({ theme }) => theme.positiveFaded};
--color-gradient-negative: ${({ theme }) => theme.negativeFaded};
--color-risk-low: ${({ theme }) => theme.riskLow};
--color-risk-medium: ${({ theme }) => theme.riskMedium};
--color-risk-high: ${({ theme }) => theme.riskHigh};
}
`;
-157
View File
@@ -1,157 +0,0 @@
import { AppTheme } from '@/state/configs';
import type { ThemeColors } from '@/constants/styles/colors';
import { ColorToken, OpacityToken } from '@/constants/styles/base';
import { generateFadedColorVariant } from '@/lib/styles';
const ClassicTheme: ThemeColors = {
layer0: ColorToken.GrayBlue7,
layer1: ColorToken.GrayBlue6,
layer2: ColorToken.GrayBlue5,
layer3: ColorToken.GrayBlue4,
layer4: ColorToken.GrayBlue3,
layer5: ColorToken.GrayBlue2,
layer6: ColorToken.GrayBlue1,
layer7: ColorToken.GrayBlue0,
borderDefault: ColorToken.GrayBlue2,
borderDestructive: generateFadedColorVariant(ColorToken.Red2, OpacityToken.Opacity20),
borderButton: generateFadedColorVariant(ColorToken.White, OpacityToken.Opacity20),
textPrimary: ColorToken.LightGray2,
textSecondary: ColorToken.GrayPurple1,
textTertiary: ColorToken.GrayPurple2,
gradientBase0: ColorToken.DarkGray9,
gradientBase1: ColorToken.GrayBlue2,
accent: ColorToken.Purple1,
accentFaded: generateFadedColorVariant(ColorToken.Purple1, OpacityToken.Opacity16),
favorite: ColorToken.Yellow0,
success: ColorToken.Green1,
warning: ColorToken.Yellow0,
error: ColorToken.Red2,
positive: ColorToken.Green1,
negative: ColorToken.Red2,
positiveFaded: generateFadedColorVariant(ColorToken.Green1, OpacityToken.Opacity16),
negativeFaded: generateFadedColorVariant(ColorToken.Red2, OpacityToken.Opacity16),
riskLow: ColorToken.Green1,
riskMedium: ColorToken.Yellow0,
riskHigh: ColorToken.Red2,
logoFill: ColorToken.White,
profileYellow: ColorToken.Yellow1,
profileRed: ColorToken.Red2,
inputBackground: ColorToken.GrayBlue3,
popoverBackground: generateFadedColorVariant(ColorToken.GrayBlue4, OpacityToken.Opacity90),
switchThumbActiveBackground: ColorToken.White,
toggleBackground: ColorToken.GrayBlue3,
tooltipBackground: generateFadedColorVariant(ColorToken.GrayBlue3, OpacityToken.Opacity66),
};
const DarkTheme: ThemeColors = {
layer0: ColorToken.Black,
layer1: ColorToken.DarkGray11,
layer2: ColorToken.DarkGray13,
layer3: ColorToken.DarkGray10,
layer4: ColorToken.DarkGray6,
layer5: ColorToken.DarkGray5,
layer6: ColorToken.DarkGray4,
layer7: ColorToken.DarkGray2,
borderDefault: ColorToken.DarkGray4,
borderDestructive: generateFadedColorVariant(ColorToken.Red0, OpacityToken.Opacity20),
borderButton: generateFadedColorVariant(ColorToken.White, OpacityToken.Opacity20),
textPrimary: ColorToken.LightGray0,
textSecondary: ColorToken.MediumGray0,
textTertiary: ColorToken.DarkGray0,
gradientBase0: ColorToken.DarkGray8,
gradientBase1: ColorToken.DarkGray5,
accent: ColorToken.Purple0,
accentFaded: generateFadedColorVariant(ColorToken.Purple0, OpacityToken.Opacity16),
favorite: ColorToken.Yellow0,
success: ColorToken.Green0,
warning: ColorToken.Yellow0,
error: ColorToken.Red0,
positive: ColorToken.Green0,
negative: ColorToken.Red0,
positiveFaded: generateFadedColorVariant(ColorToken.Green0, OpacityToken.Opacity16),
negativeFaded: generateFadedColorVariant(ColorToken.Red0, OpacityToken.Opacity16),
riskLow: ColorToken.Green0,
riskMedium: ColorToken.Yellow0,
riskHigh: ColorToken.Red0,
logoFill: ColorToken.White,
profileYellow: ColorToken.Yellow1,
profileRed: ColorToken.Red2,
inputBackground: ColorToken.DarkGray6,
popoverBackground: generateFadedColorVariant(ColorToken.DarkGray8, OpacityToken.Opacity90),
switchThumbActiveBackground: ColorToken.White,
toggleBackground: ColorToken.DarkGray6,
tooltipBackground: generateFadedColorVariant(ColorToken.DarkGray6, OpacityToken.Opacity66),
};
const LightTheme: ThemeColors = {
layer0: ColorToken.White,
layer1: ColorToken.LightGray6,
layer2: ColorToken.White,
layer3: ColorToken.LightGray1,
layer4: ColorToken.White,
layer5: ColorToken.LightGray4,
layer6: ColorToken.LightGray9,
layer7: ColorToken.MediumGray1,
borderDefault: ColorToken.LightGray10,
borderDestructive: generateFadedColorVariant(ColorToken.Red1, OpacityToken.Opacity20),
borderButton: generateFadedColorVariant(ColorToken.Black, OpacityToken.Opacity20),
textPrimary: ColorToken.DarkGray12,
textSecondary: ColorToken.DarkGray3,
textTertiary: ColorToken.DarkGray1,
gradientBase0: ColorToken.LightGray8,
gradientBase1: ColorToken.LightGray5,
accent: ColorToken.Purple0,
accentFaded: generateFadedColorVariant(ColorToken.Purple0, OpacityToken.Opacity16),
favorite: ColorToken.Yellow0,
success: ColorToken.Green2,
warning: ColorToken.Yellow0,
error: ColorToken.Red1,
positive: ColorToken.Green2,
negative: ColorToken.Red1,
positiveFaded: generateFadedColorVariant(ColorToken.Green2, OpacityToken.Opacity16),
negativeFaded: generateFadedColorVariant(ColorToken.Red1, OpacityToken.Opacity16),
riskLow: ColorToken.Green2,
riskMedium: ColorToken.Yellow0,
riskHigh: ColorToken.Red1,
logoFill: ColorToken.Black,
profileYellow: ColorToken.Yellow1,
profileRed: ColorToken.Red2,
inputBackground: ColorToken.White,
popoverBackground: generateFadedColorVariant(ColorToken.LightGray8, OpacityToken.Opacity90),
switchThumbActiveBackground: ColorToken.White,
toggleBackground: ColorToken.LightGray4,
tooltipBackground: generateFadedColorVariant(ColorToken.LightGray7, OpacityToken.Opacity66),
};
export const Themes = {
[AppTheme.Classic]: ClassicTheme,
[AppTheme.Dark]: DarkTheme,
[AppTheme.Light]: LightTheme,
};
@@ -1,81 +0,0 @@
import { forwardRef } from 'react';
import styled, { AnyStyledComponent, css } from 'styled-components';
import type { Nullable } from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization';
import { TOKEN_DECIMALS } from '@/constants/numbers';
import { ORDERBOOK_ROW_HEIGHT } from '@/constants/orderbook';
import { useStringGetter } from '@/hooks';
import { Output, OutputType } from '@/components/Output';
import { WithTooltip } from '@/components/WithTooltip';
type StyleProps = {
side?: 'top' | 'bottom';
};
type ElementProps = {
spread?: Nullable<number>;
spreadPercent?: Nullable<number>;
tickSizeDecimals?: Nullable<number>;
};
export const OrderbookRow = styled.div`
display: flex;
flex-shrink: 0;
align-items: center;
height: ${ORDERBOOK_ROW_HEIGHT}px;
min-height: ${ORDERBOOK_ROW_HEIGHT}px;
position: relative;
padding-right: 0.5rem;
font: var(--font-mini-book);
> span {
flex: 1 1 0%;
text-align: right;
}
`;
export const SpreadRow = forwardRef<HTMLDivElement, StyleProps & ElementProps>(
({ side, spread, spreadPercent, tickSizeDecimals = TOKEN_DECIMALS }, ref) => {
const stringGetter = useStringGetter();
return (
<Styled.SpreadRow ref={ref} side={side}>
<span>
<WithTooltip tooltip="spread">
{stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD })}
</WithTooltip>
</span>
<span>
<Output type={OutputType.Number} value={spread} fractionDigits={tickSizeDecimals} />
</span>
<span>
<Output type={OutputType.Percent} value={spreadPercent} />
</span>
</Styled.SpreadRow>
);
}
);
const Styled: Record<string, AnyStyledComponent> = {};
Styled.SpreadRow = styled(OrderbookRow)<{ side?: 'top' | 'bottom' }>`
height: 2rem;
border-top: var(--border);
border-bottom: var(--border);
span {
margin-bottom: 2px;
}
${({ side }) =>
side &&
{
top: css`
border-top: none;
`,
bottom: css`
border-bottom: none;
`,
}[side]}
`;
-284
View File
@@ -1,284 +0,0 @@
import { forwardRef, useCallback, useMemo, useRef } from 'react';
import styled, { AnyStyledComponent, css } from 'styled-components';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { Nullable, type PerpetualMarketOrderbookLevel } from '@/constants/abacus';
import { STRING_KEYS } from '@/constants/localization';
import { ORDERBOOK_HEIGHT, ORDERBOOK_MAX_ROWS_PER_SIDE } from '@/constants/orderbook';
import { useStringGetter } from '@/hooks';
import {
useCalculateOrderbookData,
useCenterOrderbook,
useDrawOrderbook,
useSpreadRowScrollListener,
} from '@/hooks/Orderbook';
import { Canvas } from '@/components/Canvas';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { Tag } from '@/components/Tag';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { getCurrentMarketConfig, getCurrentMarketId } from '@/state/perpetualsSelectors';
import { setTradeFormInputs } from '@/state/inputs';
import { getCurrentInput } from '@/state/inputsSelectors';
import { OrderbookRow, SpreadRow } from './OrderbookRow';
type ElementProps = {
maxRowsPerSide?: number;
layout?: 'vertical' | 'horizontal';
};
type StyleProps = {
hideHeader?: boolean;
histogramSide?: 'left' | 'right';
className?: string;
};
export const CanvasOrderbook = forwardRef(
(
{
histogramSide = 'right',
maxRowsPerSide = ORDERBOOK_MAX_ROWS_PER_SIDE,
}: ElementProps & StyleProps,
ref
) => {
const { asks, bids, hasOrderbook, histogramRange, spread, spreadPercent } =
useCalculateOrderbookData({
maxRowsPerSide,
});
const stringGetter = useStringGetter();
const currentMarket = useSelector(getCurrentMarketId) ?? '';
const currentMarketConfig = useSelector(getCurrentMarketConfig, shallowEqual);
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { tickSizeDecimals = 2 } = currentMarketConfig ?? {};
/**
* Slice asks and bids to maxRowsPerSide using empty rows
*/
const { asksSlice, bidsSlice } = useMemo(() => {
let asksSlice: Array<PerpetualMarketOrderbookLevel | undefined> = [];
const emptyAskRows =
asks.length < maxRowsPerSide
? new Array<undefined>(maxRowsPerSide - asks.length).fill(undefined)
: [];
asksSlice = [...emptyAskRows, ...asks.reverse()];
let bidsSlice: Array<PerpetualMarketOrderbookLevel | undefined> = [];
const emptyBidRows =
bids.length < maxRowsPerSide
? new Array<undefined>(maxRowsPerSide - bids.length).fill(undefined)
: [];
bidsSlice = [...bids, ...emptyBidRows];
return {
asksSlice,
bidsSlice,
};
}, [asks, bids]);
const orderbookRef = useRef<HTMLDivElement>(null);
useCenterOrderbook({ orderbookRef, marketId: currentMarket });
/**
* Display top or bottom spreadRow when center spreadRow is off screen
*/
const spreadRowRef = useRef<HTMLDivElement>(null);
const displaySide = useSpreadRowScrollListener({
orderbookRef,
spreadRowRef,
});
/**
* Row action
*/
const currentInput = useSelector(getCurrentInput);
const dispatch = useDispatch();
const onRowAction = useCallback(
(price: Nullable<number>) => {
if (currentInput === 'trade' && price) {
dispatch(setTradeFormInputs({ limitPriceInput: price?.toString() }));
}
},
[currentInput]
);
const { canvasRef: asksCanvasRef } = useDrawOrderbook({
data: [...asksSlice].reverse(),
histogramRange,
histogramSide,
side: 'ask',
});
const { canvasRef: bidsCanvasRef } = useDrawOrderbook({
data: bidsSlice,
histogramRange,
histogramSide,
side: 'bid',
});
return (
<Styled.OrderbookContainer ref={ref}>
<Styled.OrderbookContent $isLoading={!hasOrderbook}>
<Styled.Header>
<span>
{stringGetter({ key: STRING_KEYS.SIZE })} {id && <Tag>{id}</Tag>}
</span>
<span>
{stringGetter({ key: STRING_KEYS.PRICE })} <Tag>USD</Tag>
</span>
<span>{stringGetter({ key: STRING_KEYS.MINE })}</span>
</Styled.Header>
{displaySide === 'top' && (
<Styled.SpreadRow
side="top"
spread={spread}
spreadPercent={spreadPercent}
tickSizeDecimals={tickSizeDecimals}
/>
)}
<Styled.OrderbookWrapper ref={orderbookRef}>
<Styled.OrderbookSideContainer $side="asks">
<Styled.HoverRows $bottom>
{asksSlice.map((row: PerpetualMarketOrderbookLevel | undefined, idx) =>
row ? (
<Styled.Row
key={idx}
title={`${row.price}`}
onClick={() => {
onRowAction(row.price);
}}
/>
) : (
<Styled.Row key={idx} />
)
)}
</Styled.HoverRows>
<Styled.OrderbookCanvas ref={asksCanvasRef} width="100%" height="100%" />
</Styled.OrderbookSideContainer>
<SpreadRow
ref={spreadRowRef}
spread={spread?.toNumber()}
spreadPercent={spreadPercent}
tickSizeDecimals={tickSizeDecimals}
/>
<Styled.OrderbookSideContainer $side="bids">
<Styled.HoverRows>
{bidsSlice.map((row: PerpetualMarketOrderbookLevel | undefined, idx) =>
row ? (
<Styled.Row
key={idx}
title={`${row.price}`}
onClick={
row?.price
? () => {
onRowAction(row.price);
}
: undefined
}
/>
) : (
<Styled.Row key={idx} />
)
)}
</Styled.HoverRows>
<Styled.OrderbookCanvas ref={bidsCanvasRef} width="100%" height="100%" />
</Styled.OrderbookSideContainer>
</Styled.OrderbookWrapper>
{displaySide === 'bottom' && (
<Styled.SpreadRow
side="bottom"
spread={spread}
spreadPercent={spreadPercent}
tickSizeDecimals={tickSizeDecimals}
/>
)}
</Styled.OrderbookContent>
{!hasOrderbook && <LoadingSpace id="canvas-orderbook" />}
</Styled.OrderbookContainer>
);
}
);
const Styled: Record<string, AnyStyledComponent> = {};
Styled.OrderbookContainer = styled.div`
display: flex;
flex: 1 1 0%;
flex-direction: column;
overflow: hidden;
`;
Styled.OrderbookContent = styled.div<{ $isLoading?: boolean }>`
max-height: 100%;
display: flex;
flex-direction: column;
position: relative;
${({ $isLoading }) => $isLoading && 'flex: 1;'}
`;
Styled.Header = styled(OrderbookRow)`
height: 2rem;
border-bottom: var(--border);
color: var(--color-text-0);
`;
Styled.OrderbookWrapper = styled.div`
overflow-y: auto;
display: flex;
flex-direction: column;
flex: 1 1 0%;
`;
Styled.OrderbookSideContainer = styled.div<{ $side: 'bids' | 'asks' }>`
min-height: ${ORDERBOOK_HEIGHT}px;
${({ $side }) => css`
--accent-color: ${$side === 'bids' ? 'var(--color-positive)' : 'var(--color-negative)'};
`}
position: relative;
`;
Styled.OrderbookCanvas = styled(Canvas)`
width: 100%;
height: 100%;
position: absolute;
top: 0;
right: 0;
pointer-events: none;
font-feature-settings: var(--fontFeature-monoNumbers);
`;
Styled.HoverRows = styled.div<{ $bottom?: boolean }>`
position: absolute;
width: 100%;
${({ $bottom }) => $bottom && 'bottom: 0;'}
`;
Styled.Row = styled(OrderbookRow)<{ onClick?: () => void }>`
${({ onClick }) =>
onClick
? css`
cursor: pointer;
&:hover {
background-color: var(--color-layer-4);
filter: darkness(0.1);
}
`
: css`
cursor: default;
`}
`;
Styled.SpreadRow = styled(SpreadRow)`
position: absolute;
`;
+7
View File
@@ -29,6 +29,7 @@ export const MarketDetails: React.FC = () => {
if (!configs) return null;
const {
basePositionNotional,
tickSize,
stepSize,
initialMarginFraction,
@@ -118,6 +119,12 @@ export const MarketDetails: React.FC = () => {
tooltip: 'initial-margin-fraction',
value: <Output useGrouping value={initialMarginFraction} type={OutputType.SmallPercent} />,
},
{
key: 'base-position-notional',
label: stringGetter({ key: STRING_KEYS.BASE_POSITION_NOTIONAL }),
tooltip: 'base-position-notional',
value: <Output useGrouping value={basePositionNotional} type={OutputType.Number} />,
},
];
return (
@@ -1,20 +1,14 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import styled, { AnyStyledComponent, css, keyframes } from 'styled-components';
import { useSelector, shallowEqual } from 'react-redux';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import {
DepthChartDatum,
DepthChartPoint,
DepthChartSeries,
SERIES_KEY_FOR_ORDER_SIDE,
} from '@/constants/charts';
import { StringGetterFunction } from '@/constants/localization';
import { StringGetterFunction, STRING_KEYS } from '@/constants/localization';
import { useBreakpoints } from '@/hooks';
import { useOrderbookValuesForDepthChart } from '@/hooks/Orderbook/useOrderbookValues';
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
import { MustBigNumber } from '@/lib/numbers';
import { getCurrentMarketConfig, getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { XYChartWithPointerEvents } from '@/components/visx/XYChartWithPointerEvents';
@@ -27,25 +21,24 @@ import {
darkTheme,
DataProvider,
EventEmitterProvider,
type EventHandlerParams,
} from '@visx/xychart';
import { LinearGradient } from '@visx/gradient';
import { curveStepAfter } from '@visx/curve';
import { Point } from '@visx/point';
import type { Point } from '@visx/point';
import Tooltip from '@/components/visx/XYChartTooltipWithBounds';
import { TooltipContent } from '@/components/visx/TooltipContent';
import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput';
import { Details } from '@/components/Details';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { OutputType } from '@/components/Output';
import { Output, OutputType } from '@/components/Output';
import { MustBigNumber } from '@/lib/numbers';
import { DepthChartTooltipContent } from './Tooltip';
import { OrderSide } from '@dydxprotocol/v4-client-js';
// @ts-ignore
const theme = buildChartTheme({
...darkTheme,
colors: ['var(--color-positive)', 'var(--color-negative)', 'var(--color-layer-6)'], // categorical colors, mapped to series via `dataKey`s
colors: ['var(--color-positive)', 'var(--color-negative)', 'white'], // categorical colors, mapped to series via `dataKey`s
});
const clamp = (n: number, min: number, max: number) => Math.max(min, Math.min(max, n));
@@ -65,6 +58,30 @@ const formatNumber = (n: number, selectedLocale: string, isCompact: boolean = n
: formattedNumber;
};
enum DepthChartSeries {
Asks = 'Asks',
Bids = 'Bids',
MidMarket = 'MidMarket',
}
type DepthChartDatum = {
size: number;
price: number;
depth: number;
seriesKey: DepthChartSeries;
};
const seriesKeyForOrderSide = {
[OrderSide.BUY]: DepthChartSeries.Bids,
[OrderSide.SELL]: DepthChartSeries.Asks,
};
type DepthChartPoint = {
side: OrderSide;
price: number;
size: number;
};
export const DepthChart = ({
onChartClick,
stringGetter,
@@ -79,19 +96,59 @@ export const DepthChart = ({
const { isMobile } = useBreakpoints();
// Chart data
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const { stepSizeDecimals, tickSizeDecimals } =
useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
const { bids, asks, lowestBid, highestBid, lowestAsk, highestAsk, midMarketPrice, orderbook } =
useOrderbookValuesForDepthChart();
const {
bids,
asks,
lowestBid,
highestBid,
lowestAsk,
highestAsk,
midMarketPrice,
spread,
spreadPercent,
} = useMemo(() => {
const bids = (orderbook?.bids?.toArray() ?? [])
.filter(Boolean)
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
const asks = (orderbook?.asks?.toArray() ?? [])
.filter(Boolean)
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
const lowestBid = bids[bids.length - 1];
const highestBid = bids[0];
const lowestAsk = asks[0];
const highestAsk = asks[asks.length - 1];
const midMarketPrice = orderbook?.midPrice;
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
const spreadPercent = orderbook?.spreadPercent;
return {
bids,
asks,
lowestBid,
highestBid,
lowestAsk,
highestAsk,
midMarketPrice,
spread,
spreadPercent,
};
}, [orderbook]);
// Chart state
const [isPointerPressed, setIsPointerPressed] = useState(false);
const [chartPointAtPointer, setChartPointAtPointer] = useState<DepthChartPoint>();
const isEditingOrder = Boolean(isPointerPressed && chartPointAtPointer);
const isEditingOrder = isPointerPressed && chartPointAtPointer;
const [zoomDomain, setZoomDomain] = useState<undefined | number>();
@@ -136,24 +193,12 @@ export const DepthChart = ({
}, [orderbook, zoomDomain]);
const getChartPoint = useCallback(
(point: Point | EventHandlerParams<object>) => {
let price, size;
if (point instanceof Point) {
const { x, y } = point as Point;
price = x;
size = y;
} else {
const { svgPoint: { x, y } = {} } = point as EventHandlerParams<object>;
price = x;
size = y;
}
return {
side: MustBigNumber(price).lt(midMarketPrice!) ? OrderSide.BUY : OrderSide.SELL,
({ x: price, y: size }: Point) =>
({
side: price < midMarketPrice! ? OrderSide.BUY : OrderSide.SELL,
price,
size,
} as DepthChartPoint;
},
} as DepthChartPoint),
[midMarketPrice]
);
@@ -211,7 +256,7 @@ export const DepthChart = ({
}}
onPointerUp={(point) => point && onChartClick?.(getChartPoint(point))}
onPointerMove={(point) => point && setChartPointAtPointer(getChartPoint(point))}
onPointerPressedChange={(isPointerPressed) => setIsPointerPressed(isPointerPressed)}
onPointerPressedChange={setIsPointerPressed}
>
<Axis
orientation="bottom"
@@ -318,7 +363,7 @@ export const DepthChart = ({
<Styled.XAxisLabelOutput
type={OutputType.Fiat}
value={
isEditingOrder && chartPointAtPointer
isEditingOrder
? chartPointAtPointer.price
: tooltipData!.nearestDatum?.datum.price
}
@@ -329,8 +374,8 @@ export const DepthChart = ({
[DepthChartSeries.Bids]: 'var(--color-positive)',
[DepthChartSeries.MidMarket]: 'var(--color-layer-6)',
}[
isEditingOrder && chartPointAtPointer
? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side]
isEditingOrder
? seriesKeyForOrderSide[chartPointAtPointer.side]
: (tooltipData!.nearestDatum?.key as DepthChartSeries)
]
}
@@ -344,7 +389,7 @@ export const DepthChart = ({
<Styled.YAxisLabelOutput
type={OutputType.Asset}
value={
isEditingOrder && chartPointAtPointer
isEditingOrder
? chartPointAtPointer.size
: tooltipData!.nearestDatum?.datum.depth
}
@@ -355,8 +400,8 @@ export const DepthChart = ({
[DepthChartSeries.Bids]: 'var(--color-positive)',
[DepthChartSeries.MidMarket]: 'var(--color-layer-6)',
}[
isEditingOrder && chartPointAtPointer
? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side]
isEditingOrder
? seriesKeyForOrderSide[chartPointAtPointer.side]
: (tooltipData!.nearestDatum?.key as DepthChartSeries)
]
}
@@ -365,22 +410,181 @@ export const DepthChart = ({
}
snapTooltipToDatumX={!isEditingOrder}
snapTooltipToDatumY={isEditingOrder ? false : isMobile}
renderTooltip={({ tooltipData, colorScale }) =>
chartPointAtPointer && (
<DepthChartTooltipContent
{...{
tooltipData,
colorScale,
isEditingOrder,
chartPointAtPointer,
stringGetter,
selectedLocale,
stepSizeDecimals,
tickSizeDecimals,
}}
/>
)
}
renderTooltip={({ tooltipData, colorScale }) => {
const { nearestDatum } = tooltipData || {};
if (!isEditingOrder && !nearestDatum?.datum) return null;
return (
<TooltipContent
accentColor={colorScale?.(
isEditingOrder
? seriesKeyForOrderSide[chartPointAtPointer.side]
: nearestDatum.key
)}
>
<h4>
{isEditingOrder
? 'Release mouse to edit order'
: {
[DepthChartSeries.Bids]: 'Bids',
[DepthChartSeries.Asks]: 'Asks',
[DepthChartSeries.MidMarket]: 'Mid-Market',
}[nearestDatum.key]}
</h4>
<Details
layout="column"
items={
isEditingOrder
? [
{
key: 'side',
label: stringGetter({ key: STRING_KEYS.SIDE }),
value: (
<Output
type={OutputType.Text}
value={
{
[OrderSide.BUY]: stringGetter({
key: STRING_KEYS.BUY,
}),
[OrderSide.SELL]: stringGetter({
key: STRING_KEYS.SELL,
}),
}[chartPointAtPointer.side]
}
/>
),
},
{
key: 'limitPrice',
label: stringGetter({ key: STRING_KEYS.LIMIT_PRICE }),
value: (
<Output
type={OutputType.Fiat}
value={chartPointAtPointer.price}
useGrouping={false}
/>
),
},
{
key: 'size',
label: stringGetter({ key: STRING_KEYS.AMOUNT }),
value: (
<Output
type={OutputType.Asset}
value={chartPointAtPointer.size}
fractionDigits={stepSizeDecimals}
tag={id}
useGrouping={false}
/>
),
},
]
: nearestDatum?.key === DepthChartSeries.MidMarket
? [
{
key: 'midMarketPrice',
// label: stringGetter({ key: STRING_KEYS.ORDERBOOK_MID_MARKET_PRICE }),
label: stringGetter({ key: STRING_KEYS.PRICE }),
value: (
<Output
type={OutputType.Fiat}
value={midMarketPrice}
useGrouping={false}
/>
),
},
{
key: 'spread',
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD }),
value: (
<>
<Output
type={OutputType.Fiat}
value={spread}
fractionDigits={tickSizeDecimals}
useGrouping={false}
/>
<Output
type={OutputType.SmallPercent}
value={spreadPercent}
withParentheses
/>
</>
),
},
]
: [
{
key: 'price',
label: stringGetter({ key: STRING_KEYS.PRICE }),
value: (
<>
{nearestDatum &&
{
[DepthChartSeries.Bids]: '≥',
[DepthChartSeries.Asks]: '≤',
}[nearestDatum.key]}
<Output
type={OutputType.Fiat}
value={nearestDatum?.datum.price}
useGrouping={false}
/>
</>
),
},
{
key: 'depth',
label: stringGetter({ key: STRING_KEYS.TOTAL_SIZE }),
value: (
<Output
type={OutputType.Asset}
value={nearestDatum?.datum.depth}
fractionDigits={stepSizeDecimals}
tag={id}
useGrouping={false}
/>
),
},
{
key: 'cost',
label: stringGetter({ key: STRING_KEYS.TOTAL_COST }),
value: (
<Output
useGrouping
type={OutputType.Fiat}
value={nearestDatum?.datum.price * nearestDatum?.datum.depth}
/>
),
},
{
key: 'priceImpact',
label: stringGetter({ key: STRING_KEYS.PRICE_IMPACT }),
value: (
<Output
useGrouping
type={OutputType.Percent}
value={{
[DepthChartSeries.Asks]: () =>
MustBigNumber(nearestDatum.datum.price)
.minus(lowestAsk.price)
.div(nearestDatum.datum.price),
[DepthChartSeries.Bids]: () =>
MustBigNumber(highestBid.price)
.minus(nearestDatum.datum.price)
.div(highestBid.price),
}[nearestDatum.key]()}
/>
),
},
]
}
/>
</TooltipContent>
);
}}
/>
</XYChartWithPointerEvents>
</EventEmitterProvider>
-220
View File
@@ -1,220 +0,0 @@
import { useMemo } from 'react';
import { OrderSide } from '@dydxprotocol/v4-client-js';
import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';
import { shallowEqual, useSelector } from 'react-redux';
import type { Nullable } from '@/constants/abacus';
import {
DepthChartDatum,
DepthChartPoint,
DepthChartSeries,
SERIES_KEY_FOR_ORDER_SIDE,
} from '@/constants/charts';
import { STRING_KEYS } from '@/constants/localization';
import { useStringGetter } from '@/hooks';
import { useOrderbookValuesForDepthChart } from '@/hooks/Orderbook/useOrderbookValues';
import { TooltipContent } from '@/components/visx/TooltipContent';
import { Details } from '@/components/Details';
import { Output, OutputType } from '@/components/Output';
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
import { MustBigNumber } from '@/lib/numbers';
type DepthChartTooltipProps = {
chartPointAtPointer: DepthChartPoint;
isEditingOrder?: boolean;
stepSizeDecimals: Nullable<number>;
tickSizeDecimals: Nullable<number>;
} & Pick<RenderTooltipParams<DepthChartDatum>, 'colorScale' | 'tooltipData'>;
export const DepthChartTooltipContent = ({
chartPointAtPointer,
colorScale,
isEditingOrder,
stepSizeDecimals,
tickSizeDecimals,
tooltipData,
}: DepthChartTooltipProps) => {
const { nearestDatum } = tooltipData || {};
const stringGetter = useStringGetter();
const { spread, spreadPercent, midMarketPrice } = useOrderbookValuesForDepthChart();
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
const priceImpact = useMemo(() => {
if (nearestDatum) {
const depthChartSeries = nearestDatum.key as DepthChartSeries;
return {
[DepthChartSeries.Bids]: MustBigNumber(nearestDatum?.datum.price)
.minus(chartPointAtPointer.price)
.div(nearestDatum?.datum.price),
[DepthChartSeries.Asks]: MustBigNumber(chartPointAtPointer.price)
.minus(nearestDatum?.datum.price)
.div(chartPointAtPointer.price),
[DepthChartSeries.MidMarket]: undefined,
}[depthChartSeries];
}
return undefined;
}, [nearestDatum, chartPointAtPointer.price]);
if (!isEditingOrder && !nearestDatum?.datum) return null;
return (
<TooltipContent
accentColor={
nearestDatum?.key &&
colorScale?.(
isEditingOrder ? SERIES_KEY_FOR_ORDER_SIDE[chartPointAtPointer.side] : nearestDatum.key
)
}
>
<h4>
{isEditingOrder
? stringGetter({ key: STRING_KEYS.RELEASE_TO_EDIT })
: nearestDatum &&
{
[DepthChartSeries.Bids]: stringGetter({ key: STRING_KEYS.BIDS }),
[DepthChartSeries.Asks]: stringGetter({ key: STRING_KEYS.ASKS }),
[DepthChartSeries.MidMarket]: stringGetter({ key: STRING_KEYS.MID_MARKET }),
}[nearestDatum.key]}
</h4>
<Details
layout="column"
items={
isEditingOrder
? [
{
key: 'side',
label: stringGetter({ key: STRING_KEYS.SIDE }),
value: (
<Output
type={OutputType.Text}
value={
{
[OrderSide.BUY]: stringGetter({
key: STRING_KEYS.BUY,
}),
[OrderSide.SELL]: stringGetter({
key: STRING_KEYS.SELL,
}),
}[chartPointAtPointer.side]
}
/>
),
},
{
key: 'limitPrice',
label: stringGetter({ key: STRING_KEYS.LIMIT_PRICE }),
value: (
<Output
type={OutputType.Fiat}
value={chartPointAtPointer.price}
useGrouping={false}
/>
),
},
{
key: 'size',
label: stringGetter({ key: STRING_KEYS.AMOUNT }),
value: (
<Output
type={OutputType.Asset}
value={chartPointAtPointer.size}
fractionDigits={stepSizeDecimals}
tag={id}
useGrouping={false}
/>
),
},
]
: nearestDatum?.key === DepthChartSeries.MidMarket
? [
{
key: 'midMarketPrice',
label: stringGetter({ key: STRING_KEYS.PRICE }),
value: (
<Output type={OutputType.Fiat} value={midMarketPrice} useGrouping={false} />
),
},
{
key: 'spread',
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD }),
value: (
<>
<Output
type={OutputType.Fiat}
value={spread}
fractionDigits={tickSizeDecimals}
useGrouping={false}
/>
<Output
type={OutputType.SmallPercent}
value={spreadPercent}
withParentheses
/>
</>
),
},
]
: [
{
key: 'price',
label: stringGetter({ key: STRING_KEYS.PRICE }),
value: (
<>
{nearestDatum &&
{
[DepthChartSeries.Bids]: '≥',
[DepthChartSeries.Asks]: '≤',
}[nearestDatum.key]}
<Output
type={OutputType.Fiat}
value={nearestDatum?.datum.price}
useGrouping={false}
/>
</>
),
},
{
key: 'depth',
label: stringGetter({ key: STRING_KEYS.TOTAL_SIZE }),
value: (
<Output
type={OutputType.Asset}
value={nearestDatum?.datum.depth}
fractionDigits={stepSizeDecimals}
tag={id}
useGrouping={false}
/>
),
},
{
key: 'cost',
label: stringGetter({ key: STRING_KEYS.TOTAL_COST }),
value: (
<Output
useGrouping
type={OutputType.Fiat}
value={
nearestDatum
? nearestDatum?.datum.price * nearestDatum?.datum.depth
: undefined
}
/>
),
},
{
key: 'priceImpact',
label: stringGetter({ key: STRING_KEYS.PRICE_IMPACT }),
value: <Output useGrouping type={OutputType.Percent} value={priceImpact} />,
},
]
}
/>
</TooltipContent>
);
};
@@ -3,27 +3,39 @@ import { shallowEqual, useSelector } from 'react-redux';
import styled, { type AnyStyledComponent, css } from 'styled-components';
import { curveMonotoneX, curveStepAfter } from '@visx/curve';
import { ButtonSize } from '@/constants/buttons';
import { FundingRateResolution, type FundingChartDatum } from '@/constants/charts';
import { STRING_KEYS } from '@/constants/localization';
import { FundingDirection } from '@/constants/markets';
import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers';
import { useBreakpoints, useStringGetter } from '@/hooks';
import { ButtonSize } from '@/constants/buttons';
import { STRING_KEYS } from '@/constants/localization';
import { SMALL_PERCENT_DECIMALS, TINY_PERCENT_DECIMALS } from '@/constants/numbers';
import { FundingDirection } from '@/constants/markets';
import { breakpoints } from '@/styles';
import { Output, OutputType } from '@/components/Output';
import { Details, DetailsItem } from '@/components/Details';
import { Output, OutputType, ShowSign } from '@/components/Output';
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
import { ToggleGroup } from '@/components/ToggleGroup';
import { TimeSeriesChart } from '@/components/visx/TimeSeriesChart';
import { AxisLabelOutput } from '@/components/visx/AxisLabelOutput';
import { TooltipContent } from '@/components/visx/TooltipContent';
import type { TooltipContextType } from '@visx/xychart';
import { calculateFundingRateHistory } from '@/state/perpetualsCalculators';
import { MustBigNumber } from '@/lib/numbers';
import { FundingChartTooltipContent } from './Tooltip';
enum FundingRateResolution {
OneHour = 'OneHour',
EightHour = 'EightHour',
Annualized = 'Annualized',
}
type FundingChartDatum = {
time: number;
fundingRate: number;
direction: FundingDirection;
};
const FUNDING_RATE_TIME_RESOLUTION = 60 * 60 * 1000; // 1 hour
@@ -104,20 +116,114 @@ export const FundingChart = ({ selectedLocale }: ElementProps) => {
{
[FundingDirection.ToLong]: 'var(--color-negative)',
[FundingDirection.ToShort]: 'var(--color-positive)',
[FundingDirection.None]: 'var(--color-layer-6)',
}[tooltipDatum.direction]
}
/>
);
}}
renderTooltip={({ tooltipData }) => (
<FundingChartTooltipContent
fundingRateView={fundingRateView}
tooltipData={tooltipData}
latestDatum={latestDatum}
/>
)}
onTooltipContext={(tooltipContext) => setTooltipContext(tooltipContext)}
renderTooltip={({ tooltipData }) => {
const { nearestDatum } = tooltipData || {};
const tooltipDatum = nearestDatum?.datum ?? latestDatum;
const isShowingCurrentFundingRate = tooltipDatum === latestDatum;
return (
<TooltipContent
accentColor={
{
[FundingDirection.ToLong]: 'var(--color-negative)',
[FundingDirection.ToShort]: 'var(--color-positive)',
}[tooltipDatum.direction]
}
>
<h4>
{isShowingCurrentFundingRate
? stringGetter({ key: STRING_KEYS.CURRENT_FUNDING_RATE })
: stringGetter({ key: STRING_KEYS.HISTORICAL_FUNDING_RATE })}
</h4>
<Details
layout="column"
items={
[
{
key: 'direction',
label: stringGetter({ key: STRING_KEYS.DIRECTION }),
value: (
<Output
type={OutputType.Text}
value={
{
[FundingDirection.ToLong]: `${stringGetter({
key: STRING_KEYS.SHORT_POSITION_SHORT,
})} ${stringGetter({
key: STRING_KEYS.LONG_POSITION_SHORT,
})}`,
[FundingDirection.ToShort]: `${stringGetter({
key: STRING_KEYS.LONG_POSITION_SHORT,
})} ${stringGetter({
key: STRING_KEYS.SHORT_POSITION_SHORT,
})}`,
}[tooltipDatum.direction]
}
/>
),
},
{
key: 'fundingRate1h',
label: stringGetter({ key: STRING_KEYS.RATE_1H }),
value: (
<Output
type={OutputType.SmallPercent}
value={tooltipDatum.fundingRate}
showSign={ShowSign.Both}
/>
),
},
{
key: 'fundingRate8h',
label: stringGetter({ key: STRING_KEYS.RATE_8H }),
value: (
<Output
type={OutputType.SmallPercent}
value={tooltipDatum.fundingRate * 8}
// value={
// Math.sign(tooltipDatum.fundingRate) *
// ((Math.abs(tooltipDatum.fundingRate) + 1) ** 8 - 1)
// }
showSign={ShowSign.Both}
/>
),
},
{
key: 'fundingRateAnnualized',
label: stringGetter({ key: STRING_KEYS.ANNUALIZED }),
value: (
<Output
type={OutputType.SmallPercent}
value={tooltipDatum.fundingRate * (24 * 365)}
// value={
// Math.sign(tooltipDatum.fundingRate) *
// ((Math.abs(tooltipDatum.fundingRate) + 1) ** (24 * 365) - 1)
// }
showSign={ShowSign.Both}
/>
),
},
{
key: 'time',
label: isShowingCurrentFundingRate
? 'Time Remaining'
: stringGetter({ key: STRING_KEYS.TIME }),
value: <Output type={OutputType.DateTime} value={tooltipDatum.time} />,
},
].filter(Boolean) as Array<DetailsItem>
}
/>
</TooltipContent>
);
}}
onTooltipContext={setTooltipContext}
minZoomDomain={FUNDING_RATE_TIME_RESOLUTION * 4}
numGridLines={1}
slotEmpty={<LoadingSpace id="funding-chart-loading" />}
@@ -184,7 +290,6 @@ Styled.FundingRateToggle = styled.div`
Styled.CurrentFundingRate = styled.div<{ isShowing?: boolean }>`
place-self: start center;
padding: clamp(1.5rem, 9rem - 15%, 4rem);
pointer-events: none;
font: var(--font-large-book);
-107
View File
@@ -1,107 +0,0 @@
import type { RenderTooltipParams } from '@visx/xychart/lib/components/Tooltip';
import { TooltipContent } from '@/components/visx/TooltipContent';
import { FundingRateResolution, type FundingChartDatum } from '@/constants/charts';
import { STRING_KEYS } from '@/constants/localization';
import { FundingDirection } from '@/constants/markets';
import { useStringGetter } from '@/hooks';
import { Details, DetailsItem } from '@/components/Details';
import { Output, OutputType, ShowSign } from '@/components/Output';
type FundingChartTooltipProps = {
fundingRateView: FundingRateResolution;
latestDatum: FundingChartDatum;
} & Pick<RenderTooltipParams<FundingChartDatum>, 'tooltipData'>;
export const FundingChartTooltipContent = ({
fundingRateView,
latestDatum,
tooltipData,
}: FundingChartTooltipProps) => {
const { nearestDatum } = tooltipData || {};
const stringGetter = useStringGetter();
const tooltipDatum = nearestDatum?.datum ?? latestDatum;
const isShowingCurrentFundingRate = tooltipDatum === latestDatum;
return (
<TooltipContent
accentColor={
{
[FundingDirection.ToLong]: 'var(--color-negative)',
[FundingDirection.ToShort]: 'var(--color-positive)',
[FundingDirection.None]: 'var(--color-layer-6)',
}[tooltipDatum.direction]
}
>
<h4>
{isShowingCurrentFundingRate
? stringGetter({ key: STRING_KEYS.CURRENT_FUNDING_RATE })
: stringGetter({ key: STRING_KEYS.HISTORICAL_FUNDING_RATE })}
</h4>
<Details
layout="column"
items={
[
{
key: 'direction',
label: stringGetter({ key: STRING_KEYS.DIRECTION }),
value: (
<Output
type={OutputType.Text}
value={
{
[FundingDirection.ToLong]: `${stringGetter({
key: STRING_KEYS.SHORT_POSITION_SHORT,
})} → ${stringGetter({
key: STRING_KEYS.LONG_POSITION_SHORT,
})}`,
[FundingDirection.ToShort]: `${stringGetter({
key: STRING_KEYS.LONG_POSITION_SHORT,
})} → ${stringGetter({
key: STRING_KEYS.SHORT_POSITION_SHORT,
})}`,
[FundingDirection.None]: undefined,
}[tooltipDatum.direction]
}
/>
),
},
{
key: 'fundingRate',
label: stringGetter({
key: {
[FundingRateResolution.OneHour]: STRING_KEYS.RATE_1H,
[FundingRateResolution.EightHour]: STRING_KEYS.RATE_8H,
[FundingRateResolution.Annualized]: STRING_KEYS.ANNUALIZED,
}[fundingRateView],
}),
value: (
<Output
type={OutputType.SmallPercent}
value={
{
[FundingRateResolution.OneHour]: tooltipDatum.fundingRate,
[FundingRateResolution.EightHour]: tooltipDatum.fundingRate * 8,
[FundingRateResolution.Annualized]: tooltipDatum.fundingRate * (24 * 365),
}[fundingRateView]
}
showSign={ShowSign.Both}
/>
),
},
{
key: 'time',
label: isShowingCurrentFundingRate
? 'Time Remaining'
: stringGetter({ key: STRING_KEYS.TIME }),
value: <Output type={OutputType.DateTime} value={tooltipDatum.time} />,
},
].filter(Boolean) as Array<DetailsItem>
}
/>
</TooltipContent>
);
};
@@ -72,7 +72,6 @@ export const GenerateKeys = ({
if (message) {
log('GenerateKeys/switchNetwork', error, { walletErrorType });
setError(message);
throw error;
}
}
};
@@ -17,7 +17,6 @@ import type { EvmAddress } from '@/constants/wallets';
import { useAccounts, useDebounce, useStringGetter, useSelectedNetwork } from '@/hooks';
import { useAccountBalance, CHAIN_DEFAULT_TOKEN_ADDRESS } from '@/hooks/useAccountBalance';
import { useLocalNotifications } from '@/hooks/useLocalNotifications';
import { useWalletConnection } from '@/hooks/useWalletConnection';
import { layoutMixins } from '@/styles/layoutMixins';
import { formMixins } from '@/styles/formMixins';
@@ -121,7 +120,11 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
});
return () => {
abacusStateManager.resetInputState();
abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({
field: TransferInputField.type,
value: null,
});
};
}, []);
@@ -232,10 +235,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
throw new Error('Missing request payload');
}
if (isCctp && !abacusStateManager.chainTransactions.isNobleClientConnected) {
throw new Error('Noble RPC endpoint unaccessible');
}
setIsLoading(true);
await validateTokenApproval();
@@ -365,8 +364,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
return <LoadingSpace id="DepositForm" />;
}
const [requireUserActionInWallet, setRequireUserActionInWallet] = useState(false);
return (
<Styled.Form onSubmit={onSubmit}>
<ChainSelectMenu selectedChain={chainIdStr || undefined} onSelectChain={onSelectChain} />
@@ -385,11 +382,7 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
/>
</Styled.WithDetailsReceipt>
{errorMessage && <AlertMessage type={AlertType.Error}>{errorMessage}</AlertMessage>}
{requireUserActionInWallet && (
<AlertMessage type={AlertType.Warning}>
{stringGetter({ key: STRING_KEYS.CHECK_WALLET_FOR_REQUEST })}
</AlertMessage>
)}
<Styled.Footer>
<DepositButtonAndReceipt
isDisabled={isDisabled}
@@ -398,8 +391,6 @@ export const DepositForm = ({ onDeposit, onError }: DepositFormProps) => {
setSlippage={onSetSlippage}
slippage={slippage}
sourceToken={sourceToken || undefined}
setRequireUserActionInWallet={setRequireUserActionInWallet}
setError={setError}
/>
</Styled.Footer>
</Styled.Form>
@@ -1,7 +1,8 @@
import { type Dispatch, type SetStateAction, useState, type ReactNode, useEffect } from 'react';
import { type Dispatch, type ReactNode, type SetStateAction, useState, useMemo } from 'react';
import styled, { type AnyStyledComponent } from 'styled-components';
import { shallowEqual, useSelector } from 'react-redux';
import type { RouteData } from '@0xsquid/sdk';
import { formatUnits } from 'viem';
import { ButtonAction, ButtonShape, ButtonSize, ButtonType } from '@/constants/buttons';
@@ -11,7 +12,6 @@ import { NumberSign, TOKEN_DECIMALS } from '@/constants/numbers';
import { useStringGetter, useTokenConfigs } from '@/hooks';
import { useMatchingEvmNetwork } from '@/hooks/useMatchingEvmNetwork';
import { useWalletConnection } from '@/hooks/useWalletConnection';
import { layoutMixins } from '@/styles/layoutMixins';
@@ -39,8 +39,7 @@ type ElementProps = {
isLoading?: boolean;
chainId?: string | number;
setError?: Dispatch<SetStateAction<Error | null>>;
setRequireUserActionInWallet: (val: boolean) => void;
setError?: Dispatch<SetStateAction<Error | undefined>>;
slippage: number;
slotError?: ReactNode;
setSlippage: (slippage: number) => void;
@@ -58,7 +57,6 @@ export const DepositButtonAndReceipt = ({
isDisabled,
isLoading,
slotError,
setRequireUserActionInWallet,
}: ElementProps) => {
const [showFeeBreakdown, setShowFeeBreakdown] = useState(false);
const [isEditingSlippage, setIsEditingSlipapge] = useState(false);
@@ -66,24 +64,6 @@ export const DepositButtonAndReceipt = ({
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const { connectWallet, isConnectedWagmi } = useWalletConnection();
const connectWagmi = async () => {
try {
setRequireUserActionInWallet(false);
await connectWallet();
setRequireUserActionInWallet(false);
} catch (e) {
setRequireUserActionInWallet(true);
}
};
useEffect(() => {
if (!isConnectedWagmi && canAccountTrade) {
connectWagmi();
}
}, [isConnectedWagmi, canAccountTrade]);
const {
matchNetwork: switchNetwork,
isSwitchingNetwork,
@@ -100,7 +80,7 @@ export const DepositButtonAndReceipt = ({
useSelector(getSubaccountBuyingPower, shallowEqual) || {};
const { isCctp, summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
const { usdcLabel } = useTokenConfigs();
const { usdcDecimals, usdcLabel } = useTokenConfigs();
const feeSubitems: DetailsItem[] = [];
@@ -128,6 +108,20 @@ export const DepositButtonAndReceipt = ({
const totalFees = (summary?.bridgeFee || 0) + (summary?.gasFee || 0);
const { toAmount, toAmountMin } = useMemo(() => {
if (isCctp) {
return {
toAmount: summary?.toAmount,
toAmountMin: summary?.toAmountMin,
};
} else {
return {
toAmount: summary?.toAmount && formatUnits(BigInt(summary.toAmount), usdcDecimals),
toAmountMin: summary?.toAmountMin && formatUnits(BigInt(summary.toAmountMin), usdcDecimals),
};
}
}, [isCctp, summary]);
const submitButtonReceipt = [
{
key: 'expected-deposit-amount',
@@ -136,9 +130,7 @@ export const DepositButtonAndReceipt = ({
{stringGetter({ key: STRING_KEYS.EXPECTED_DEPOSIT_AMOUNT })} <Tag>{usdcLabel}</Tag>
</span>
),
value: (
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={summary?.toAmount} />
),
value: <Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={toAmount} />,
subitems: [
{
key: 'minimum-deposit-amount',
@@ -148,11 +140,7 @@ export const DepositButtonAndReceipt = ({
</span>
),
value: (
<Output
type={OutputType.Fiat}
fractionDigits={TOKEN_DECIMALS}
value={summary?.toAmountMin}
/>
<Output type={OutputType.Fiat} fractionDigits={TOKEN_DECIMALS} value={toAmountMin} />
),
tooltip: 'minimum-deposit-amount',
},
@@ -279,8 +267,6 @@ export const DepositButtonAndReceipt = ({
>
{!canAccountTrade ? (
<OnboardingTriggerButton size={ButtonSize.Base} />
) : !isConnectedWagmi ? (
<Button action={ButtonAction.Primary} onClick={connectWallet} state={{ isLoading: true }} />
) : !isMatchingNetwork ? (
<Button
action={ButtonAction.Primary}
@@ -101,7 +101,11 @@ export const WithdrawForm = () => {
});
return () => {
abacusStateManager.resetInputState();
abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({
field: TransferInputField.type,
value: null,
});
};
}, []);
@@ -144,10 +148,6 @@ export const WithdrawForm = () => {
throw new Error('Invalid request payload');
}
if (isCctp && !abacusStateManager.chainTransactions.isNobleClientConnected) {
throw new Error('Noble RPC endpoint unaccessible');
}
setIsLoading(true);
setError(undefined);
@@ -55,7 +55,7 @@ export const WithdrawButtonAndReceipt = ({
const stringGetter = useStringGetter();
const { leverage } = useSelector(getSubaccount, shallowEqual) || {};
const { summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
const { isCctp, summary, requestPayload } = useSelector(getTransferInputs, shallowEqual) || {};
const canAccountTrade = useSelector(calculateCanAccountTrade, shallowEqual);
const { usdcLabel } = useTokenConfigs();
@@ -85,6 +85,26 @@ export const WithdrawButtonAndReceipt = ({
const totalFees = (summary?.bridgeFee || 0) + (summary?.gasFee || 0);
const { toAmount, toAmountMin } = useMemo(() => {
if (isCctp) {
return {
toAmount: summary?.toAmount,
toAmountMin: summary?.toAmountMin,
};
} else {
return {
toAmount:
summary?.toAmount &&
withdrawToken?.decimals &&
formatUnits(BigInt(summary.toAmount), withdrawToken.decimals),
toAmountMin:
summary?.toAmountMin &&
withdrawToken?.decimals &&
formatUnits(BigInt(summary.toAmountMin), withdrawToken.decimals),
};
}
}, [isCctp, summary, withdrawToken]);
const submitButtonReceipt = [
{
key: 'total-fees',
@@ -133,7 +153,7 @@ export const WithdrawButtonAndReceipt = ({
{withdrawToken && <Tag>{withdrawToken?.symbol}</Tag>}
</span>
),
value: <Output type={OutputType.Asset} value={summary?.toAmount} fractionDigits={TOKEN_DECIMALS} />,
value: <Output type={OutputType.Asset} value={toAmount} fractionDigits={TOKEN_DECIMALS} />,
subitems: [
{
key: 'minimum-amount-received',
@@ -144,7 +164,7 @@ export const WithdrawButtonAndReceipt = ({
</span>
),
value: (
<Output type={OutputType.Asset} value={summary?.toAmountMin} fractionDigits={TOKEN_DECIMALS} />
<Output type={OutputType.Asset} value={toAmountMin} fractionDigits={TOKEN_DECIMALS} />
),
tooltip: 'minimum-amount-received',
},
+2 -3
View File
@@ -10,6 +10,7 @@ import {
type HumanReadablePlaceOrderPayload,
type Nullable,
TradeInputErrorAction,
TradeInputField,
ValidationError,
} from '@/constants/abacus';
@@ -95,8 +96,6 @@ export const TradeForm = ({
needsGoodUntil,
needsPostOnly,
needsReduceOnly,
postOnlyTooltip,
reduceOnlyTooltip,
timeInForceOptions,
tradeErrors,
} = useTradeFormData();
@@ -109,7 +108,7 @@ export const TradeForm = ({
const { limitPriceInput, triggerPriceInput, trailingPercentInput } = tradeFormInputValues;
const needsAdvancedOptions =
needsGoodUntil || timeInForceOptions || executionOptions || (needsPostOnly || postOnlyTooltip) || (needsReduceOnly || reduceOnlyTooltip);
needsGoodUntil || timeInForceOptions || executionOptions || needsPostOnly || needsReduceOnly;
const tradeFormInputs: TradeBoxInputConfig[] = [];
@@ -31,15 +31,12 @@ export const AdvancedTradeOptions = () => {
const { execution, goodTil, postOnly, reduceOnly, timeInForce, type } = inputTradeData || {};
const { executionOptions, needsGoodUntil, needsPostOnly, needsReduceOnly, postOnlyTooltip, reduceOnlyTooltip, timeInForceOptions } =
const { executionOptions, needsGoodUntil, needsPostOnly, needsReduceOnly, timeInForceOptions } =
currentTradeFormConfig || {};
const { duration, unit } = goodTil || {};
const showPostOnly = (needsPostOnly || postOnlyTooltip);
const showReduceOnly = (needsReduceOnly || reduceOnlyTooltip);
const needsExecution = executionOptions || showPostOnly || showReduceOnly;
const needsExecution = executionOptions || needsPostOnly || needsReduceOnly;
const hasTimeInForce = timeInForceOptions?.toArray()?.length;
return (
@@ -132,27 +129,9 @@ export const AdvancedTradeOptions = () => {
))}
</Styled.SelectMenu>
)}
{showReduceOnly && <Checkbox
checked={(reduceOnly && !reduceOnlyTooltip) || false}
disabled={!!reduceOnlyTooltip}
onCheckedChange={(checked) =>
abacusStateManager.setTradeValue({
value: checked,
field: TradeInputField.reduceOnly,
})
}
id="reduce-only"
label={
<WithTooltip tooltip={needsReduceOnly ? "reduce-only" : reduceOnlyTooltip?.titleStringKey.includes("REDUCE_ONLY_EXECUTION_IOC_FOK") ? "reduce-only-execution-ioc-fok" : "reduce-only-timeinforce-ioc-fok"} side="right">
{stringGetter({ key: STRING_KEYS.REDUCE_ONLY })}
</WithTooltip>
}
/>
}
{showPostOnly && (
{needsPostOnly && (
<Checkbox
checked={(postOnly && !postOnlyTooltip) || false}
disabled={!!postOnlyTooltip}
checked={postOnly || false}
onCheckedChange={(checked) =>
abacusStateManager.setTradeValue({
value: checked,
@@ -161,12 +140,29 @@ export const AdvancedTradeOptions = () => {
}
id="post-only"
label={
<WithTooltip tooltip={needsPostOnly ? "post-only" : "post-only-timeinforce-gtt"} side="right">
<WithTooltip tooltip="post-only" side="right">
{stringGetter({ key: STRING_KEYS.POST_ONLY })}
</WithTooltip>
}
/>
)}
{needsReduceOnly && (
<Checkbox
checked={reduceOnly || false}
onCheckedChange={(checked) =>
abacusStateManager.setTradeValue({
value: checked,
field: TradeInputField.reduceOnly,
})
}
id="reduce-only"
label={
<WithTooltip tooltip="reduce-only" side="right">
{stringGetter({ key: STRING_KEYS.REDUCE_ONLY })}
</WithTooltip>
}
/>
)}
</>
)}
</Styled.AdvancedInputsContainer>
@@ -44,7 +44,6 @@ export const TradeSideToggle = memo(() => {
const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>`
--toggle-radius: 0.5em;
--toggle-color: var(--color-negative);
--toggle-background: ${({ theme }) => theme.toggleBackground};
${({ value }) =>
value === OrderSide.BUY &&
@@ -53,7 +52,7 @@ const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>`
`}
border-radius: var(--toggle-radius);
background-color: var(--toggle-background);
background-color: var(--color-layer-4);
position: relative;
> button {
+5 -1
View File
@@ -118,7 +118,11 @@ export const TransferForm = ({
onChangeAsset(selectedAsset);
return () => {
abacusStateManager.resetInputState();
abacusStateManager.clearTransferInputValues();
abacusStateManager.setTransferValue({
field: TransferInputField.type,
value: null,
});
};
}, []);
+4 -5
View File
@@ -10,9 +10,6 @@ import { STRING_KEYS, StringGetterFunction, TOOLTIP_STRING_KEYS } from '@/consta
import { isMainnet } from '@/constants/networks';
import { DydxChainAsset, wallets } from '@/constants/wallets';
import { layoutMixins } from '@/styles/layoutMixins';
import { headerMixins } from '@/styles/headerMixins';
import {
useAccounts,
useBreakpoints,
@@ -38,8 +35,10 @@ import { getOnboardingState, getSubaccount } from '@/state/accountSelectors';
import { isTruthy } from '@/lib/isTruthy';
import { truncateAddress } from '@/lib/wallet';
import { layoutMixins } from '@/styles/layoutMixins';
import { headerMixins } from '@/styles/headerMixins';
import { MustBigNumber } from '@/lib/numbers';
import { testFlags } from '@/lib/testFlags';
export const AccountMenu = () => {
const stringGetter = useStringGetter();
@@ -179,7 +178,7 @@ export const AccountMenu = () => {
},
...(onboardingState === OnboardingState.AccountConnected && hdKey
? [
(!isMainnet || testFlags.showMobileSignInOption) && {
!isMainnet && {
value: 'MobileQrSignIn',
icon: <Icon iconName={IconName.Qr} />,
label: stringGetter({ key: STRING_KEYS.TITLE_SIGN_INTO_MOBILE }),

Some files were not shown because too many files have changed in this diff Show More